| /******************************************************************************* |
| * Copyright (c) 2010 IBM Corporation and others. |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License v1.0 |
| * which accompanies this distribution, and is available at |
| * http://www.eclipse.org/legal/epl-v10.html |
| * |
| * Contributors: |
| * IBM Corporation - initial API and implementation |
| *******************************************************************************/ |
| package org.eclipse.wst.sse.ui.contentassist; |
| |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Collections; |
| import java.util.Comparator; |
| import java.util.Iterator; |
| import java.util.List; |
| |
| import org.eclipse.core.runtime.Assert; |
| import org.eclipse.core.runtime.IProgressMonitor; |
| import org.eclipse.core.runtime.NullProgressMonitor; |
| import org.eclipse.core.runtime.SubProgressMonitor; |
| import org.eclipse.jface.action.LegacyActionTools; |
| import org.eclipse.jface.bindings.TriggerSequence; |
| import org.eclipse.jface.bindings.keys.KeySequence; |
| import org.eclipse.jface.dialogs.IDialogConstants; |
| import org.eclipse.jface.dialogs.MessageDialog; |
| import org.eclipse.jface.preference.IPreferenceStore; |
| import org.eclipse.jface.resource.JFaceResources; |
| import org.eclipse.jface.text.IDocument; |
| import org.eclipse.jface.text.ITextInputListener; |
| import org.eclipse.jface.text.ITextViewer; |
| import org.eclipse.jface.text.contentassist.ContentAssistEvent; |
| import org.eclipse.jface.text.contentassist.ContentAssistant; |
| import org.eclipse.jface.text.contentassist.ICompletionListener; |
| import org.eclipse.jface.text.contentassist.ICompletionListenerExtension; |
| import org.eclipse.jface.text.contentassist.ICompletionProposal; |
| import org.eclipse.jface.text.contentassist.IContentAssistProcessor; |
| import org.eclipse.jface.text.contentassist.IContentAssistantExtension2; |
| import org.eclipse.jface.text.contentassist.IContentAssistantExtension3; |
| import org.eclipse.jface.text.contentassist.IContextInformation; |
| import org.eclipse.jface.text.contentassist.IContextInformationValidator; |
| import org.eclipse.jface.util.IPropertyChangeListener; |
| import org.eclipse.jface.util.PropertyChangeEvent; |
| import org.eclipse.osgi.util.NLS; |
| import org.eclipse.swt.SWT; |
| import org.eclipse.swt.events.SelectionAdapter; |
| import org.eclipse.swt.events.SelectionEvent; |
| import org.eclipse.swt.layout.GridData; |
| import org.eclipse.swt.layout.GridLayout; |
| import org.eclipse.swt.widgets.Button; |
| import org.eclipse.swt.widgets.Composite; |
| import org.eclipse.swt.widgets.Control; |
| import org.eclipse.swt.widgets.Link; |
| import org.eclipse.swt.widgets.Shell; |
| import org.eclipse.ui.PlatformUI; |
| import org.eclipse.ui.dialogs.PreferencesUtil; |
| import org.eclipse.ui.keys.IBindingService; |
| import org.eclipse.ui.texteditor.ITextEditorActionDefinitionIds; |
| import org.eclipse.wst.sse.core.StructuredModelManager; |
| import org.eclipse.wst.sse.core.internal.provisional.IStructuredModel; |
| import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocument; |
| import org.eclipse.wst.sse.ui.internal.ExtendedConfigurationBuilder; |
| import org.eclipse.wst.sse.ui.internal.IReleasable; |
| import org.eclipse.wst.sse.ui.internal.SSEUIMessages; |
| import org.eclipse.wst.sse.ui.internal.SSEUIPlugin; |
| import org.eclipse.wst.sse.ui.internal.contentassist.CompletionProposalCategory; |
| import org.eclipse.wst.sse.ui.internal.contentassist.CompletionProposalComputerRegistry; |
| import org.eclipse.wst.sse.ui.internal.contentassist.CompletionProposoalCatigoriesConfigurationRegistry; |
| import org.eclipse.wst.sse.ui.internal.contentassist.CompoundContentAssistProcessor; |
| import org.eclipse.wst.sse.ui.internal.contentassist.ContextInformationValidator; |
| import org.eclipse.wst.sse.ui.internal.contentassist.OptionalMessageDialog; |
| import org.eclipse.wst.sse.ui.preferences.ICompletionProposalCategoriesConfigurationReader; |
| import org.eclipse.wst.sse.ui.preferences.ICompletionProposalCategoriesConfigurationWriter; |
| |
| |
| /** |
| * <p>A content assist processor that aggregates the proposals of the |
| * {@link org.eclipse.wst.sse.ui.contentassist.ICompletionProposalComputer}s contributed via the |
| * <code>org.eclipse.wst.sse.ui.completionProposal</code> extension point.</p> |
| * <p> |
| * Extenders may extend: |
| * <ul> |
| * <li>{@link #propertyChange(PropertyChangeEvent)}to react to property change events |
| * that occur in the {@link IPreferenceStore} given to the constructor in case the behavior |
| * of the processor needs to change according to user preferences</li> |
| * <li>{@link #getCompletionProposalAutoActivationCharacters()}</li> |
| * <li>{@link #getContextInformationAutoActivationCharacters()}</li> |
| * <li>{@link #filterAndSortProposals(List, IProgressMonitor, CompletionProposalInvocationContext)} |
| * to add sorting and filtering</li> |
| * <li>{@link #filterAndSortContextInformation(List, IProgressMonitor)} to add sorting and filtering</li> |
| * <li>{@link #createProgressMonitor()} to change the way progress is reported</li> |
| * <li>{@link #createContext(ITextViewer, int)} to provide the context object |
| * passed to the computers</li> |
| * <li>{@link #getContextInformationValidator()} to add context validation (needed if any |
| * contexts are provided)</li> |
| * <li>{@link #getErrorMessage()} to change error reporting</li> |
| * </ul> |
| * </p> |
| * |
| * @base org.eclipse.jdt.internal.ui.text.java.ContentAssistProcessor |
| */ |
| public class StructuredContentAssistProcessor implements IContentAssistProcessor, IPropertyChangeListener, IReleasable { |
| |
| /** Legacy editor configuration extension point. */ |
| private static final String CONTENT_ASSIST_PROCESSOR_EXTENDED_ID = "contentassistprocessor"; //$NON-NLS-1$ |
| |
| /** Content assist processors added through the now legacy editor configuration extension point */ |
| private List fLegacyExtendedContentAssistProcessors; |
| |
| /** |
| * Dialog settings key for the "all categories are disabled" warning dialog. See |
| * {@link OptionalMessageDialog}. |
| */ |
| private static final String PREF_WARN_ABOUT_EMPTY_ASSIST_CATEGORY= "EmptyDefaultAssistCategory"; //$NON-NLS-1$ |
| |
| /** |
| * Used to sort categories by their page order so they are cycled in the correct order |
| */ |
| private final Comparator PAGE_ORDER_COMPARATOR = new Comparator() { |
| public int compare(Object o1, Object o2) { |
| CompletionProposalCategory d1= (CompletionProposalCategory) o1; |
| CompletionProposalCategory d2= (CompletionProposalCategory) o2; |
| |
| return d1.getPageSortRank(fContentTypeID) - d2.getPageSortRank(fContentTypeID); |
| } |
| }; |
| |
| /** |
| * Used to sort categories by their default page order so they are |
| * ordered correctly on the default page |
| */ |
| private final Comparator DEFAULT_PAGE_ORDER_COMPARATOR = new Comparator() { |
| public int compare(Object o1, Object o2) { |
| CompletionProposalCategory d1= (CompletionProposalCategory) o1; |
| CompletionProposalCategory d2= (CompletionProposalCategory) o2; |
| |
| return d1.getDefaultPageSortRank(fContentTypeID) - d2.getDefaultPageSortRank(fContentTypeID); |
| } |
| }; |
| |
| /** List of {@link CompletionProposalCategory}s supported by this processor */ |
| private List fCategories; |
| |
| /** content type ID this processor is associated with */ |
| String fContentTypeID; |
| |
| /** partition type ID this processor is associated with */ |
| private final String fPartitionTypeID; |
| |
| /** Content assistant used for giving the user status messages and listening to completion results*/ |
| private ContentAssistant fAssistant; |
| |
| /* cycling stuff */ |
| private int fRepetition= -1; |
| private List fCategoryIteration = null; |
| private String fIterationGesture = null; |
| private int fNumberOfComputedResults = 0; |
| private String fErrorMessage; |
| |
| /** Optionally specified preference store for listening to property change events */ |
| private IPreferenceStore fPreferenceStore; |
| |
| /** The viewer this processor is associated with */ |
| private ITextViewer fViewer; |
| |
| /** |
| * the {@link ITextInputListener} used to set the content type when |
| * a document is set for this processors associated viewer. |
| */ |
| private ITextInputListener fTextInputListener; |
| |
| private CompletionListener fCompletionListener; |
| |
| /** the context information validator for this processor */ |
| private IContextInformationValidator fContextInformationValidator; |
| |
| private AutoActivationDelegate fAutoActivation; |
| |
| /** |
| * <p>Create a new content assist processor for a specific partition type. |
| * The content type will be determined when a document is set on the viewer</p> |
| * |
| * <p>If the given {@link IPreferenceStore} is not <code>null</code> then this |
| * processor will be registered as a {@link IPropertyChangeListener} on the given store |
| * so that implementers of this class can change the way the processor acts based on |
| * user preferences</p> |
| * |
| * @param assistant {@link ContentAssistant} to use |
| * @param partitionTypeID the partition type this processor is for |
| * @param viewer {@link ITextViewer} this processor is acting in |
| * @param preferenceStore This processor will be registered as a {@link IPropertyChangeListener} |
| * on this store and the processor itself will take care of removing itself as a listener, if |
| * <code>null</code> then will not be registered as a {@link IPropertyChangeListener} |
| */ |
| public StructuredContentAssistProcessor(ContentAssistant assistant, String partitionTypeID, |
| ITextViewer viewer, IPreferenceStore preferenceStore) { |
| |
| Assert.isNotNull(partitionTypeID); |
| Assert.isNotNull(assistant); |
| |
| //be sure the registry has been loaded, none blocking |
| CompletionProposalComputerRegistry.getDefault().initialize(); |
| |
| //register on the preference store |
| this.fPreferenceStore = preferenceStore; |
| if(this.fPreferenceStore != null) { |
| this.fPreferenceStore.addPropertyChangeListener(this); |
| } |
| |
| //The content type can not be determined until a document has been set |
| this.fContentTypeID = null; |
| this.fViewer = viewer; |
| if(viewer != null) { |
| this.fTextInputListener = new TextInputListener(); |
| viewer.addTextInputListener(this.fTextInputListener); |
| |
| if(viewer.getDocument() != null) { |
| /* it is highly unlike the document has already been set, but check |
| * just for sanity |
| */ |
| this.fTextInputListener.inputDocumentChanged(null, viewer.getDocument()); |
| } |
| } |
| |
| //set the associated partition type |
| this.fPartitionTypeID = partitionTypeID; |
| |
| //add completion listener |
| fAssistant = assistant; |
| fCompletionListener = new CompletionListener(); |
| fAssistant.addCompletionListener(fCompletionListener); |
| |
| //lazy load these to speed up initial editor opening |
| fLegacyExtendedContentAssistProcessors = null; |
| fCategories = null; |
| } |
| |
| /** |
| * <p>Collect the proposals using the extension points</p> |
| * |
| * @see org.eclipse.jface.text.contentassist.IContentAssistProcessor#computeCompletionProposals(org.eclipse.jface.text.ITextViewer, int) |
| */ |
| public final ICompletionProposal[] computeCompletionProposals(ITextViewer viewer, int offset) { |
| clearState(); |
| |
| IProgressMonitor monitor= createProgressMonitor(); |
| monitor.beginTask(SSEUIMessages.ContentAssist_computing_proposals, getProposalCategories().size() + 1); |
| |
| CompletionProposalInvocationContext context = createContext(viewer, offset); |
| |
| monitor.subTask(SSEUIMessages.ContentAssist_collecting_proposals); |
| List proposals = collectProposals(viewer, offset, monitor, context); |
| |
| monitor.subTask(SSEUIMessages.ContentAssist_sorting_proposals); |
| List filtered = filterAndSortProposals(proposals, monitor, context); |
| fNumberOfComputedResults= filtered.size(); |
| |
| ICompletionProposal[] result= (ICompletionProposal[]) filtered.toArray(new ICompletionProposal[filtered.size()]); |
| monitor.done(); |
| |
| return result; |
| } |
| |
| /** |
| * <p>Collect the context information using the extension points</p> |
| * |
| * @see org.eclipse.jface.text.contentassist.IContentAssistProcessor#computeContextInformation(org.eclipse.jface.text.ITextViewer, int) |
| */ |
| public IContextInformation[] computeContextInformation(ITextViewer viewer, int offset) { |
| clearState(); |
| |
| IProgressMonitor monitor= createProgressMonitor(); |
| monitor.beginTask(SSEUIMessages.ContentAssist_computing_contexts, getProposalCategories().size() + 1); |
| |
| monitor.subTask(SSEUIMessages.ContentAssist_collecting_contexts); |
| List proposals= collectContextInformation(viewer, offset, monitor); |
| |
| monitor.subTask(SSEUIMessages.ContentAssist_sorting_contexts); |
| List filtered= filterAndSortContextInformation(proposals, monitor); |
| fNumberOfComputedResults= filtered.size(); |
| |
| IContextInformation[] result= (IContextInformation[]) filtered.toArray(new IContextInformation[filtered.size()]); |
| monitor.done(); |
| return result; |
| } |
| |
| /** |
| * <p>Default implementation is to return <code>null</code></p> |
| * <p>Extenders may override</p> |
| * |
| * @see org.eclipse.jface.text.contentassist.IContentAssistProcessor#getCompletionProposalAutoActivationCharacters() |
| */ |
| public char[] getCompletionProposalAutoActivationCharacters() { |
| return (fAutoActivation != null) ? fAutoActivation.getCompletionProposalAutoActivationCharacters() : null; |
| } |
| |
| /** |
| * <p>Default implementation is to return <code>null</code></p> |
| * <p>Extenders may override</p> |
| * |
| * @see org.eclipse.jface.text.contentassist.IContentAssistProcessor#getContextInformationAutoActivationCharacters() |
| */ |
| public char[] getContextInformationAutoActivationCharacters() { |
| return (fAutoActivation != null) ? fAutoActivation.getContextInformationAutoActivationCharacters() : null; |
| } |
| |
| /** |
| * <p>Extenders may override this function to change error reporting</p> |
| * |
| * @see org.eclipse.jface.text.contentassist.IContentAssistProcessor#getErrorMessage() |
| */ |
| public String getErrorMessage() { |
| if (fErrorMessage != null) |
| return fErrorMessage; |
| if (fNumberOfComputedResults > 0) |
| return null; |
| return SSEUIMessages.ContentAssist_no_completions; |
| } |
| |
| /** |
| * @see org.eclipse.wst.sse.ui.contentassist.StructuredContentAssistProcessor#getContextInformationValidator() |
| */ |
| public IContextInformationValidator getContextInformationValidator() { |
| if (this.fContextInformationValidator == null) { |
| this.fContextInformationValidator = new ContextInformationValidator(); |
| } |
| return this.fContextInformationValidator; |
| } |
| |
| public void install(ITextViewer viewer) { |
| if (fPreferenceStore != null) { |
| fPreferenceStore.addPropertyChangeListener(this); |
| } |
| if (fViewer != null) { |
| fViewer.removeTextInputListener(fTextInputListener); |
| } |
| fViewer = viewer; |
| if (fViewer != null) { |
| fViewer.addTextInputListener(fTextInputListener); |
| } |
| if (fAssistant != null) { |
| fAssistant.addCompletionListener(fCompletionListener); |
| } |
| } |
| |
| /** |
| * <p>Extenders may override, but should always be sure to call the super implementation</p> |
| * |
| * @see org.eclipse.wst.sse.ui.internal.IReleasable#release() |
| */ |
| public void release() { |
| if (fAutoActivation != null) { |
| fAutoActivation.dispose(); |
| fAutoActivation = null; |
| } |
| if(this.fPreferenceStore != null) { |
| this.fPreferenceStore.removePropertyChangeListener(this); |
| } |
| |
| if(this.fViewer != null) { |
| this.fViewer.removeTextInputListener(this.fTextInputListener); |
| this.fViewer = null; |
| } |
| if (this.fAssistant != null) { |
| this.fAssistant.removeCompletionListener(fCompletionListener); |
| } |
| } |
| |
| /** |
| * <p>Intended to be overridden by extenders wishing to change the behavior |
| * of the processor based on user preferences from the store optionally |
| * associated with this processor. If no store was given to the constructor |
| * when creating this assistant then this method will never be invoked.</p> |
| * |
| * <p>The default implementation does not react to the events in any way</p> |
| * |
| * @see org.eclipse.jface.util.IPropertyChangeListener#propertyChange(org.eclipse.jface.util.PropertyChangeEvent) |
| */ |
| public void propertyChange(PropertyChangeEvent event) { |
| } |
| |
| /** |
| * <p>Filters and sorts the proposals. The passed list may be modified |
| * and returned, or a new list may be created and returned.</p> |
| * |
| * <p>The default implementation does not do any sorting or filtering.</p> |
| * <p>Extenders may override this function.</p> |
| * |
| * @param proposals the list of collected proposals (element type: |
| * {@link ICompletionProposal}) |
| * @param monitor a progress monitor |
| * @param context TODO |
| * @return the list of filtered and sorted proposals, ready for |
| * display (element type: {@link ICompletionProposal}) |
| */ |
| protected List filterAndSortProposals(List proposals, IProgressMonitor monitor, CompletionProposalInvocationContext context) { |
| return proposals; |
| } |
| |
| /** |
| * <p>Filters and sorts the context information objects. The passed |
| * list may be modified and returned, or a new list may be created |
| * and returned.</p> |
| * |
| * <p>The default implementation does not do any sorting or filtering</p> |
| * <p>Extenders may override this method</p> |
| * |
| * @param contexts the list of collected proposals (element type: |
| * {@link IContextInformation}) |
| * @param monitor a progress monitor |
| * @return the list of filtered and sorted proposals, ready for |
| * display (element type: {@link IContextInformation}) |
| */ |
| protected List filterAndSortContextInformation(List contexts, IProgressMonitor monitor) { |
| return contexts; |
| } |
| |
| /** |
| * <p>Creates a progress monitor.</p> |
| * <p>The default implementation creates a {@link NullProgressMonitor}.</p> |
| * |
| * <p>Extenders may override this method</p> |
| * |
| * @return a progress monitor |
| */ |
| protected IProgressMonitor createProgressMonitor() { |
| return new NullProgressMonitor(); |
| } |
| |
| /** |
| * <p>Creates the context that is passed to the completion proposal |
| * computers.</p> |
| * |
| * <p>Extenders may override this method</p> |
| * |
| * @param viewer the viewer that content assist is invoked on |
| * @param offset the content assist offset |
| * @return the context to be passed to the computers |
| */ |
| protected CompletionProposalInvocationContext createContext(ITextViewer viewer, int offset) { |
| return new CompletionProposalInvocationContext(viewer, offset); |
| } |
| |
| /** |
| * @return the associated preference store |
| */ |
| protected IPreferenceStore getPreferenceStore() { |
| return this.fPreferenceStore; |
| } |
| |
| /** |
| * Clears the state |
| */ |
| private void clearState() { |
| fErrorMessage=null; |
| fNumberOfComputedResults= 0; |
| } |
| |
| /** |
| * <p>Collects the proposals from the extensions.</p> |
| * |
| * @param viewer the text viewer |
| * @param offset the offset |
| * @param monitor the progress monitor |
| * @param context the code assist invocation context |
| * @return the list of proposals |
| */ |
| private List collectProposals(ITextViewer viewer, int offset, IProgressMonitor monitor, CompletionProposalInvocationContext context) { |
| List proposals = new ArrayList(); |
| List categories = getCategories(); |
| for (Iterator it = categories.iterator(); it.hasNext();) { |
| CompletionProposalCategory cat = (CompletionProposalCategory) it.next(); |
| List computed = cat.computeCompletionProposals(context, this.fContentTypeID, this.fPartitionTypeID, new SubProgressMonitor(monitor, 1)); |
| proposals.addAll(computed); |
| if (fErrorMessage == null) { |
| fErrorMessage= cat.getErrorMessage(); |
| } |
| } |
| |
| // if default page |
| // Deal with adding in proposals from processors added through the legacy extension |
| if (isFirstPage() && getLegacyExtendedContentAssistProcessors() != null && |
| !getLegacyExtendedContentAssistProcessors().isEmpty()) { |
| |
| Iterator iter = getLegacyExtendedContentAssistProcessors().iterator(); |
| while (iter.hasNext()) { |
| IContentAssistProcessor legacyProcessor = (IContentAssistProcessor) iter.next(); |
| ICompletionProposal[] legacyComputed = legacyProcessor.computeCompletionProposals(viewer, offset); |
| if (legacyComputed != null) { |
| proposals.addAll(Arrays.asList(legacyComputed)); |
| } |
| } |
| } |
| |
| return proposals; |
| } |
| |
| /** |
| * <p>Collects the context information from the extensions.</p> |
| * |
| * @param viewer |
| * @param offset |
| * @param monitor |
| * @return |
| */ |
| private List collectContextInformation(ITextViewer viewer, int offset, IProgressMonitor monitor) { |
| List proposals = new ArrayList(); |
| CompletionProposalInvocationContext context= createContext(viewer, offset); |
| |
| List providers= getCategories(); |
| for (Iterator it= providers.iterator(); it.hasNext();) { |
| CompletionProposalCategory cat= (CompletionProposalCategory) it.next(); |
| List computed= cat.computeContextInformation(context, this.fContentTypeID, this.fPartitionTypeID, new SubProgressMonitor(monitor, 1)); |
| proposals.addAll(computed); |
| if (fErrorMessage == null) { |
| fErrorMessage= cat.getErrorMessage(); |
| } |
| } |
| |
| // Deal with adding in contexts from processors added through the legacy extension |
| if (getLegacyExtendedContentAssistProcessors() != null && |
| !getLegacyExtendedContentAssistProcessors().isEmpty()) { |
| |
| Iterator iter = getLegacyExtendedContentAssistProcessors().iterator(); |
| while (iter.hasNext()) { |
| IContentAssistProcessor legacyProcessor = (IContentAssistProcessor) iter.next(); |
| IContextInformation[] legacyComputed = legacyProcessor.computeContextInformation(viewer, offset); |
| if(legacyComputed != null) { |
| proposals.addAll(Arrays.asList(legacyComputed)); |
| } |
| } |
| } |
| |
| return proposals; |
| } |
| |
| /** |
| * @return the next set of categories |
| */ |
| private List getCategories() { |
| List categories; |
| if (fCategoryIteration == null) { |
| categories = getProposalCategories(); |
| } else { |
| int iteration= fRepetition % fCategoryIteration.size(); |
| fAssistant.setStatusMessage(createIterationMessage()); |
| fAssistant.setEmptyMessage(createEmptyMessage()); |
| fRepetition++; |
| |
| categories = (List) fCategoryIteration.get(iteration); |
| } |
| |
| return categories; |
| } |
| |
| /** |
| * This may show the warning dialog if all categories are disabled |
| */ |
| private void resetCategoryIteration() { |
| fCategoryIteration = getCategoryIteration(); |
| } |
| |
| /** |
| * @return {@link List} of {@link List}s of {@link CompletionProposalCategory}s, this is |
| * the ordered list of the completion categories to cycle through |
| */ |
| private List getCategoryIteration() { |
| List sequence= new ArrayList(); |
| sequence.add(getDefaultCategories()); |
| for (Iterator it= getSortedOwnPageCategories().iterator(); it.hasNext();) { |
| CompletionProposalCategory cat= (CompletionProposalCategory) it.next(); |
| sequence.add(Collections.singletonList(cat)); |
| } |
| return sequence; |
| } |
| |
| /** |
| * @return the sorted categories for the default page |
| */ |
| private List getDefaultCategories() { |
| // default mix - enable all included computers |
| List included= getDefaultCategoriesUnchecked(); |
| |
| if (included.size() == 0 && CompletionProposalComputerRegistry.getDefault().hasUninstalledComputers()) { |
| if (informUserAboutEmptyDefaultCategory()) { |
| // preferences were restored - recompute the default categories |
| included= getDefaultCategoriesUnchecked(); |
| } |
| CompletionProposalComputerRegistry.getDefault().resetUnistalledComputers(); |
| } |
| |
| Collections.sort(included, DEFAULT_PAGE_ORDER_COMPARATOR); |
| |
| return included; |
| } |
| |
| /** |
| * <p>Gets the default categories with no error checking.</p> |
| * |
| * @return the default {@link CompletionProposalCategory}s |
| */ |
| private List getDefaultCategoriesUnchecked() { |
| List included = new ArrayList(); |
| for (Iterator it = getProposalCategories().iterator(); it.hasNext();) { |
| CompletionProposalCategory category = (CompletionProposalCategory) it.next(); |
| if (category.isIncludedOnDefaultPage(this.fContentTypeID) && category.hasComputers(fContentTypeID, fPartitionTypeID)) |
| included.add(category); |
| } |
| return included; |
| } |
| |
| /** |
| * <p>Informs the user about the fact that there are no enabled categories in the default content |
| * assist set and shows a link to the preferences.</p> |
| * |
| * @return <code>true</code> if the default should be restored |
| */ |
| private boolean informUserAboutEmptyDefaultCategory() { |
| /*If warn about empty default category and there are associated properties for this |
| * processors content type and those properties have an associated properties page then |
| * display warning message to user. |
| */ |
| ICompletionProposalCategoriesConfigurationReader properties = CompletionProposoalCatigoriesConfigurationRegistry.getDefault().getReadableConfiguration(this.fContentTypeID); |
| if (OptionalMessageDialog.isDialogEnabled(PREF_WARN_ABOUT_EMPTY_ASSIST_CATEGORY) && |
| properties instanceof ICompletionProposalCategoriesConfigurationWriter && |
| ((ICompletionProposalCategoriesConfigurationWriter)properties).hasAssociatedPropertiesPage()) { |
| |
| ICompletionProposalCategoriesConfigurationWriter propertiesExtension = (ICompletionProposalCategoriesConfigurationWriter)properties; |
| |
| final Shell shell= SSEUIPlugin.getActiveWorkbenchShell(); |
| String title= SSEUIMessages.ContentAssist_all_disabled_title; |
| String message= SSEUIMessages.ContentAssist_all_disabled_message; |
| // see PreferencePage#createControl for the 'defaults' label |
| final String restoreButtonLabel= JFaceResources.getString("defaults"); //$NON-NLS-1$ |
| final String linkMessage= NLS.bind(SSEUIMessages.ContentAssist_all_disabled_preference_link, LegacyActionTools.removeMnemonics(restoreButtonLabel)); |
| final int restoreId= IDialogConstants.CLIENT_ID + 10; |
| final int settingsId= IDialogConstants.CLIENT_ID + 11; |
| final OptionalMessageDialog dialog= new OptionalMessageDialog(PREF_WARN_ABOUT_EMPTY_ASSIST_CATEGORY, shell, title, null /* default image */, message, MessageDialog.WARNING, new String[] { restoreButtonLabel, IDialogConstants.CLOSE_LABEL }, 1) { |
| /* |
| * @see org.eclipse.jdt.internal.ui.dialogs.OptionalMessageDialog#createCustomArea(org.eclipse.swt.widgets.Composite) |
| */ |
| protected Control createCustomArea(Composite composite) { |
| // wrap link and checkbox in one composite without space |
| Composite parent= new Composite(composite, SWT.NONE); |
| GridLayout layout= new GridLayout(); |
| layout.marginHeight= 0; |
| layout.marginWidth= 0; |
| layout.verticalSpacing= 0; |
| parent.setLayout(layout); |
| |
| Composite linkComposite= new Composite(parent, SWT.NONE); |
| layout= new GridLayout(); |
| layout.marginHeight= convertVerticalDLUsToPixels(IDialogConstants.VERTICAL_MARGIN); |
| layout.marginWidth= convertHorizontalDLUsToPixels(IDialogConstants.HORIZONTAL_MARGIN); |
| layout.horizontalSpacing= convertHorizontalDLUsToPixels(IDialogConstants.HORIZONTAL_SPACING); |
| linkComposite.setLayout(layout); |
| |
| Link link= new Link(linkComposite, SWT.NONE); |
| link.setText(linkMessage); |
| link.addSelectionListener(new SelectionAdapter() { |
| public void widgetSelected(SelectionEvent e) { |
| setReturnCode(settingsId); |
| close(); |
| } |
| }); |
| GridData gridData= new GridData(SWT.FILL, SWT.BEGINNING, true, false); |
| gridData.widthHint= this.getMinimumMessageWidth(); |
| link.setLayoutData(gridData); |
| |
| // create checkbox and "don't show this message" prompt |
| super.createCustomArea(parent); |
| |
| return parent; |
| } |
| |
| /* |
| * @see org.eclipse.jface.dialogs.MessageDialog#createButtonsForButtonBar(org.eclipse.swt.widgets.Composite) |
| */ |
| protected void createButtonsForButtonBar(Composite parent) { |
| Button[] buttons= new Button[2]; |
| buttons[0]= createButton(parent, restoreId, restoreButtonLabel, false); |
| buttons[1]= createButton(parent, IDialogConstants.CLOSE_ID, IDialogConstants.CLOSE_LABEL, true); |
| setButtons(buttons); |
| } |
| }; |
| int returnValue = dialog.open(); |
| |
| //based on user actions either reset defaults or open preference dialog |
| if (restoreId == returnValue || settingsId == returnValue) { |
| if (restoreId == returnValue) { |
| propertiesExtension.loadDefaults(); |
| propertiesExtension.saveConfiguration(); |
| } |
| if (settingsId == returnValue) { |
| PreferencesUtil.createPreferenceDialogOn(shell, |
| propertiesExtension.getPropertiesPageID(), null, null).open(); |
| } |
| |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| /** |
| * @return a sorted {@link List} of {@link CompletionProposalCategory}s that |
| * should be displayed on their own content assist page |
| */ |
| private List getSortedOwnPageCategories() { |
| ArrayList sorted= new ArrayList(); |
| for (Iterator it= getProposalCategories().iterator(); it.hasNext();) { |
| CompletionProposalCategory category= (CompletionProposalCategory) it.next(); |
| if (category.isDisplayedOnOwnPage(this.fContentTypeID) && |
| category.hasComputers(fContentTypeID, fPartitionTypeID)) { |
| |
| sorted.add(category); |
| } |
| } |
| Collections.sort(sorted, PAGE_ORDER_COMPARATOR); |
| return sorted; |
| } |
| |
| /** |
| * @return a user message describing that there are no content assist suggestions for the current page |
| */ |
| private String createEmptyMessage() { |
| return NLS.bind(SSEUIMessages.ContentAssist_no_message, new String[]{getCategoryLabel(fRepetition)}); |
| } |
| |
| /** |
| * @return user message describing what the next page of content assist holds |
| */ |
| private String createIterationMessage() { |
| return NLS.bind(SSEUIMessages.ContentAssist_toggle_affordance_update_message, |
| new String[]{ getCategoryLabel(fRepetition), fIterationGesture, getCategoryLabel(fRepetition + 1) }); |
| } |
| |
| /** |
| * @param repetition which category to get the label for |
| * @return the label of the category |
| */ |
| private String getCategoryLabel(int repetition) { |
| int iteration= (fCategoryIteration != null ? repetition % fCategoryIteration.size() : 0); |
| if (iteration == 0) |
| return SSEUIMessages.ContentAssist_defaultProposalCategory_title; |
| return ((CompletionProposalCategory) ((List) fCategoryIteration.get(iteration)).get(0)).getDisplayName(); |
| } |
| |
| /** |
| * @return {@link String} representing the user command to iterate to the next page |
| */ |
| private String getIterationGesture() { |
| TriggerSequence binding= getIterationBinding(); |
| return binding != null ? |
| NLS.bind(SSEUIMessages.ContentAssist_press, new Object[] { binding.format() }) |
| : SSEUIMessages.ContentAssist_click; |
| } |
| |
| /** |
| * @return {@link KeySequence} used by user to iterate to the next page |
| */ |
| private KeySequence getIterationBinding() { |
| final IBindingService bindingSvc= (IBindingService) PlatformUI.getWorkbench().getAdapter(IBindingService.class); |
| TriggerSequence binding= bindingSvc.getBestActiveBindingFor(ITextEditorActionDefinitionIds.CONTENT_ASSIST_PROPOSALS); |
| if (binding instanceof KeySequence) |
| return (KeySequence) binding; |
| return null; |
| } |
| |
| /** |
| * @return <code>true</code> if displaying first page, <code>false</code> otherwise |
| */ |
| private boolean isFirstPage() { |
| return fCategoryIteration == null || fCategoryIteration.size() == 1 || fRepetition % fCategoryIteration.size() == 1; |
| } |
| |
| /** |
| * <p><b>NOTE: </b>This method should be used over accessing the |
| * {@link #fLegacyExtendedContentAssistProcessors} field directly so as to |
| * facilitate the lazy initialization of the field.</p> |
| * |
| * @return the legacy extended content assist processors |
| */ |
| private List getLegacyExtendedContentAssistProcessors() { |
| if(fLegacyExtendedContentAssistProcessors == null) { |
| fLegacyExtendedContentAssistProcessors = |
| ExtendedConfigurationBuilder.getInstance().getConfigurations( |
| CONTENT_ASSIST_PROCESSOR_EXTENDED_ID, fPartitionTypeID); |
| } |
| |
| return fLegacyExtendedContentAssistProcessors; |
| } |
| |
| /** |
| * <p><b>NOTE: </b>This method should be used over accessing the {@link #fCategories} |
| * field directly so as to facilitate the lazy initialization of the field.</p> |
| * |
| * @return the categories associated with the content type this processor is associated with |
| */ |
| private List getProposalCategories() { |
| if(fCategories == null) { |
| fCategories = |
| CompletionProposalComputerRegistry.getDefault().getProposalCategories(fContentTypeID); |
| } |
| |
| return fCategories; |
| } |
| |
| /** |
| * The completion listener class for this processor. |
| */ |
| private final class CompletionListener implements ICompletionListener, ICompletionListenerExtension { |
| /** |
| * @see org.eclipse.jface.text.contentassist.ICompletionListener#assistSessionStarted(org.eclipse.jface.text.contentassist.ContentAssistEvent) |
| */ |
| public void assistSessionStarted(ContentAssistEvent event) { |
| if (event.processor == StructuredContentAssistProcessor.this || |
| (event.processor instanceof CompoundContentAssistProcessor && |
| ((CompoundContentAssistProcessor)event.processor).containsProcessor(StructuredContentAssistProcessor.this))) { |
| |
| fIterationGesture= getIterationGesture(); |
| KeySequence binding= getIterationBinding(); |
| |
| // This may show the warning dialog if all categories are disabled |
| resetCategoryIteration(); |
| for (Iterator it= StructuredContentAssistProcessor.this.getProposalCategories().iterator(); it.hasNext();) { |
| CompletionProposalCategory cat= (CompletionProposalCategory) it.next(); |
| cat.sessionStarted(); |
| } |
| |
| fRepetition= 0; |
| if (event.assistant instanceof IContentAssistantExtension2) { |
| IContentAssistantExtension2 extension= (IContentAssistantExtension2) event.assistant; |
| |
| if (fCategoryIteration.size() == 1) { |
| extension.setRepeatedInvocationMode(false); |
| extension.setShowEmptyList(false); |
| } else { |
| extension.setRepeatedInvocationMode(true); |
| extension.setStatusLineVisible(true); |
| extension.setStatusMessage(createIterationMessage()); |
| extension.setShowEmptyList(true); |
| if (extension instanceof IContentAssistantExtension3) { |
| IContentAssistantExtension3 ext3= (IContentAssistantExtension3) extension; |
| ((ContentAssistant) ext3).setRepeatedInvocationTrigger(binding); |
| } |
| } |
| } |
| } |
| } |
| |
| /** |
| * @see org.eclipse.jface.text.contentassist.ICompletionListener#assistSessionEnded(org.eclipse.jface.text.contentassist.ContentAssistEvent) |
| */ |
| public void assistSessionEnded(ContentAssistEvent event) { |
| if (event.processor == StructuredContentAssistProcessor.this || (event.processor instanceof CompoundContentAssistProcessor && ((CompoundContentAssistProcessor)event.processor).containsProcessor(StructuredContentAssistProcessor.this))) { |
| for (Iterator it= StructuredContentAssistProcessor.this.getProposalCategories().iterator(); it.hasNext();) { |
| CompletionProposalCategory cat= (CompletionProposalCategory) it.next(); |
| cat.sessionEnded(); |
| } |
| |
| fCategoryIteration= null; |
| fRepetition= -1; |
| fIterationGesture= null; |
| if (event.assistant instanceof IContentAssistantExtension2) { |
| IContentAssistantExtension2 extension= (IContentAssistantExtension2) event.assistant; |
| extension.setShowEmptyList(false); |
| extension.setRepeatedInvocationMode(false); |
| extension.setStatusLineVisible(false); |
| if (extension instanceof IContentAssistantExtension3) { |
| IContentAssistantExtension3 ext3= (IContentAssistantExtension3) extension; |
| ((ContentAssistant) ext3).setRepeatedInvocationTrigger(null); |
| } |
| } |
| } |
| } |
| |
| /** |
| * @see org.eclipse.jface.text.contentassist.ICompletionListener#selectionChanged(org.eclipse.jface.text.contentassist.ICompletionProposal, boolean) |
| */ |
| public void selectionChanged(ICompletionProposal proposal, boolean smartToggle) { |
| //ignore |
| } |
| |
| /** |
| * @see org.eclipse.jface.text.contentassist.ICompletionListenerExtension#assistSessionRestarted(org.eclipse.jface.text.contentassist.ContentAssistEvent) |
| */ |
| public void assistSessionRestarted(ContentAssistEvent event) { |
| fRepetition= 0; |
| } |
| } |
| |
| /** |
| * |
| */ |
| private class TextInputListener implements ITextInputListener { |
| |
| /** |
| * <p>Set the content type based on the new document if it has not already been |
| * set yet.</p> |
| * |
| * @see org.eclipse.jface.text.ITextInputListener#inputDocumentChanged(org.eclipse.jface.text.IDocument, org.eclipse.jface.text.IDocument) |
| */ |
| public void inputDocumentChanged(IDocument oldInput, IDocument newInput) { |
| if(fContentTypeID == null) { |
| if(newInput instanceof IStructuredDocument) { |
| IStructuredModel model = null; |
| try { |
| model = StructuredModelManager.getModelManager().getModelForRead((IStructuredDocument)newInput); |
| if(model != null) { |
| fContentTypeID = model.getContentTypeIdentifier(); |
| if (fAutoActivation != null) { |
| fAutoActivation.dispose(); |
| } |
| fAutoActivation = CompletionProposalComputerRegistry.getDefault().getActivator(fContentTypeID, fPartitionTypeID); |
| } |
| } finally { |
| if(model != null) { |
| model.releaseFromRead(); |
| } |
| } |
| } |
| } |
| } |
| |
| /** |
| * <p>Ignored</p> |
| * |
| * @see org.eclipse.jface.text.ITextInputListener#inputDocumentAboutToBeChanged(org.eclipse.jface.text.IDocument, org.eclipse.jface.text.IDocument) |
| */ |
| public void inputDocumentAboutToBeChanged(IDocument oldInput, |
| IDocument newInput) { |
| //ignore |
| } |
| } |
| } |