/******************************************************************************* * Copyright (c) 2010, 2013 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 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.core.internal.provisional.text.IStructuredDocumentRegion; import org.eclipse.wst.sse.core.internal.provisional.text.ITextRegion; 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; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.Iterator; import java.util.List; /** * <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() { if (isCaretInAttributeValue() || isInMustache()) { return new char[]{'.'}; } return (fAutoActivation != null) ? fAutoActivation.getCompletionProposalAutoActivationCharacters() : null; } private boolean isCaretInAttributeValue() { IDocument document = fViewer.getDocument(); int offset = fViewer.getSelectedRange().x; return isCaretInAttributeValue(document, offset); } private boolean isInMustache() { IDocument document = fViewer.getDocument(); int offset = fViewer.getSelectedRange().x; return isInMustache(document, offset); } public static boolean isCaretInAttributeValue(IDocument document, int offset) { if (document instanceof IStructuredDocument) { IStructuredDocument structuredDocument = (IStructuredDocument) document; IStructuredDocumentRegion region = structuredDocument.getRegionAtCharacterOffset(offset); if (region != null) { ITextRegion textRegion = region.getRegionAtCharacterOffset(offset); if (textRegion != null) { return "XML_TAG_ATTRIBUTE_VALUE".equals(textRegion.getType()); } } } return false; } public static boolean isInMustache(IDocument document, int offset) { if (document instanceof IStructuredDocument) { IStructuredDocument structuredDocument = (IStructuredDocument) document; IStructuredDocumentRegion region = structuredDocument.getRegionAtCharacterOffset(offset); if (region == null) { return false; } ITextRegion textRegion = region.getRegionAtCharacterOffset(offset); if (textRegion == null || !"XML_CONTENT".equals(textRegion.getType())) { return false; } } try { while (offset + 2 < document.getLength()) { if (document.getChar(offset) == '<') { return false; } String str2 = document.get(offset, 2); if (str2.equals("{{")) { return false; } if (str2.equals("}}")) { return true; } offset++; } } catch (Throwable e) { } return false; } /** * <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 } } protected void setAutoActivationDelay(int delay) { fAssistant.setAutoActivationDelay(delay); } }