blob: 5b6298017213f29be5f1efcfdac62503107a8673 [file] [log] [blame]
/*******************************************************************************
* 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;
/**
* <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 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 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;
}
/**
* <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(this.fPreferenceStore != null) {
this.fPreferenceStore.removePropertyChangeListener(this);
this.fPreferenceStore = null;
}
if(this.fViewer != null) {
this.fViewer.removeTextInputListener(this.fTextInputListener);
this.fViewer = null;
}
if (this.fAssistant != null) {
this.fAssistant.removeCompletionListener(fCompletionListener);
this.fAssistant = null;
}
}
/**
* <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();
}
} 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
}
}
}