/******************************************************************************* * Copyright (c) 2000, 2017 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 * Guy Gurfinkel, guy.g@zend.com - [content assist][api] provide better access to ContentAssistant - https://bugs.eclipse.org/bugs/show_bug.cgi?id=169954 * Anton Leherbauer (Wind River Systems) - [content assist][api] ContentAssistEvent should contain information about auto activation - https://bugs.eclipse.org/bugs/show_bug.cgi?id=193728 * Marcel Bruch, bruch@cs.tu-darmstadt.de - [content assist] Allow to re-sort proposals - https://bugs.eclipse.org/bugs/show_bug.cgi?id=350991 * John Glassmyer, jogl@google.com - catch Content Assist exceptions to protect navigation keys - http://bugs.eclipse.org/434901 * Mickael Istria (Red Hat Inc.) - [251156] Allow multiple contentAssitProviders internally & inheritance *******************************************************************************/ package org.eclipse.jface.text.contentassist; import java.util.Collections; import java.util.HashMap; import java.util.LinkedHashSet; import java.util.Map; import java.util.Set; import org.eclipse.swt.SWT; import org.eclipse.swt.SWTError; import org.eclipse.swt.custom.VerifyKeyListener; import org.eclipse.swt.events.ControlEvent; import org.eclipse.swt.events.ControlListener; import org.eclipse.swt.events.DisposeEvent; import org.eclipse.swt.events.DisposeListener; import org.eclipse.swt.events.FocusEvent; import org.eclipse.swt.events.FocusListener; import org.eclipse.swt.events.KeyAdapter; import org.eclipse.swt.events.KeyEvent; import org.eclipse.swt.events.MouseEvent; import org.eclipse.swt.events.MouseListener; import org.eclipse.swt.events.TraverseEvent; import org.eclipse.swt.events.TraverseListener; import org.eclipse.swt.events.VerifyEvent; import org.eclipse.swt.graphics.Color; import org.eclipse.swt.graphics.Point; import org.eclipse.swt.graphics.Rectangle; import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Event; import org.eclipse.swt.widgets.Listener; import org.eclipse.swt.widgets.Monitor; import org.eclipse.swt.widgets.Shell; import org.eclipse.swt.widgets.Widget; import org.eclipse.core.commands.IHandler; import org.eclipse.core.runtime.Assert; import org.eclipse.core.runtime.ISafeRunnable; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.ListenerList; import org.eclipse.core.runtime.Platform; import org.eclipse.core.runtime.SafeRunner; import org.eclipse.core.runtime.Status; import org.eclipse.jface.bindings.keys.KeySequence; import org.eclipse.jface.contentassist.IContentAssistSubjectControl; import org.eclipse.jface.contentassist.ISubjectControlContentAssistProcessor; import org.eclipse.jface.dialogs.IDialogSettings; import org.eclipse.jface.preference.JFacePreferences; import org.eclipse.jface.util.Geometry; import org.eclipse.jface.util.OpenStrategy; import org.eclipse.jface.text.BadLocationException; import org.eclipse.jface.text.IDocument; import org.eclipse.jface.text.IDocumentExtension3; import org.eclipse.jface.text.IEventConsumer; import org.eclipse.jface.text.IInformationControlCreator; import org.eclipse.jface.text.ITextViewer; import org.eclipse.jface.text.IViewportListener; import org.eclipse.jface.text.IWidgetTokenKeeper; import org.eclipse.jface.text.IWidgetTokenKeeperExtension; import org.eclipse.jface.text.IWidgetTokenOwner; import org.eclipse.jface.text.IWidgetTokenOwnerExtension; import org.eclipse.jface.text.TextUtilities; /** * The standard implementation of the {@link IContentAssistant} interface. Usually, clients * instantiate this class and configure it before using it. * * Since 3.12, it can compute and display the proposals asynchronously when invoking * {@link #ContentAssistant(boolean)} with <code>true</code>. */ public class ContentAssistant implements IContentAssistant, IContentAssistantExtension, IContentAssistantExtension2, IContentAssistantExtension3, IContentAssistantExtension4, IWidgetTokenKeeper, IWidgetTokenKeeperExtension { /** * Content assist command identifier for 'select next proposal'. * * @since 3.4 */ public static final String SELECT_NEXT_PROPOSAL_COMMAND_ID= "org.eclipse.ui.edit.text.contentAssist.selectNextProposal"; //$NON-NLS-1$ /** * Content assist command identifier for 'select previous proposal'. * * @since 3.4 */ public static final String SELECT_PREVIOUS_PROPOSAL_COMMAND_ID= "org.eclipse.ui.edit.text.contentAssist.selectPreviousProposal"; //$NON-NLS-1$ /** * A generic closer class used to monitor various interface events in order to determine whether * content-assist should be terminated and all associated windows closed. */ class Closer implements ControlListener, MouseListener, FocusListener, DisposeListener, IViewportListener { /** The shell that a <code>ControlListener</code> is registered with. */ private Shell fShell; /** * The control that a <code>MouseListener</code>, a<code>FocusListener</code> and a * <code>DisposeListener</code> are registered with. */ private Control fControl; /** * Installs this closer on it's viewer's text widget. */ protected void install() { Control control= fContentAssistSubjectControlAdapter.getControl(); fControl= control; if (Helper.okToUse(control)) { Shell shell= control.getShell(); fShell= shell; shell.addControlListener(this); control.addMouseListener(this); control.addFocusListener(this); /* * 1GGYYWK: ITPJUI:ALL - Dismissing editor with code assist up causes lots of * Internal Errors */ control.addDisposeListener(this); } if (fViewer != null) fViewer.addViewportListener(this); } /** * Uninstalls this closer from the viewer's text widget. */ protected void uninstall() { Control shell= fShell; fShell= null; if (Helper.okToUse(shell)) shell.removeControlListener(this); Control control= fControl; fControl= null; if (Helper.okToUse(control)) { control.removeMouseListener(this); control.removeFocusListener(this); /* * 1GGYYWK: ITPJUI:ALL - Dismissing editor with code assist up causes lots of * Internal Errors */ control.removeDisposeListener(this); } if (fViewer != null) fViewer.removeViewportListener(this); } @Override public void controlResized(ControlEvent e) { hide(); } @Override public void controlMoved(ControlEvent e) { hide(); } @Override public void mouseDown(MouseEvent e) { hide(); } @Override public void mouseUp(MouseEvent e) { } @Override public void mouseDoubleClick(MouseEvent e) { hide(); } @Override public void focusGained(FocusEvent e) { } @Override public void focusLost(FocusEvent e) { Control control= fControl; if (Helper.okToUse(control)) { Display d= control.getDisplay(); if (d != null) { d.asyncExec(new Runnable() { @Override public void run() { if (!fProposalPopup.hasFocus() && (fContextInfoPopup == null || !fContextInfoPopup.hasFocus())) hide(); } }); } } } /* * @seeDisposeListener#widgetDisposed(DisposeEvent) */ @Override public void widgetDisposed(DisposeEvent e) { /* * 1GGYYWK: ITPJUI:ALL - Dismissing editor with code assist up causes lots of Internal * Errors */ hide(); } @Override public void viewportChanged(int topIndex) { hide(); } } /** * An implementation of <code>IContentAssistListener</code>, this class is used to monitor * key events in support of automatic activation of the content assistant. If enabled, the * implementation utilizes a thread to watch for input characters matching the activation * characters specified by the content assist processor, and if detected, will wait the * indicated delay interval before activating the content assistant. * * @since 3.4 protected, was added in 2.1 as private class */ protected class AutoAssistListener extends KeyAdapter implements Runnable, VerifyKeyListener { private Thread fThread; private boolean fIsReset= false; private Object fMutex= new Object(); private int fShowStyle; private final static int SHOW_PROPOSALS= 1; private final static int SHOW_CONTEXT_INFO= 2; protected AutoAssistListener() { } protected void start(int showStyle) { fShowStyle= showStyle; fThread= new Thread(this, JFaceTextMessages.getString("ContentAssistant.assist_delay_timer_name")); //$NON-NLS-1$ fThread.start(); } @Override public void run() { try { while (true) { synchronized (fMutex) { if (fAutoActivationDelay != 0) fMutex.wait(fAutoActivationDelay); if (fIsReset) { fIsReset= false; continue; } } showAssist(fShowStyle); break; } } catch (InterruptedException e) { } fThread= null; } protected void reset(int showStyle) { synchronized (fMutex) { fShowStyle= showStyle; fIsReset= true; fMutex.notifyAll(); } } protected void stop() { Thread threadToStop= fThread; if (threadToStop != null && threadToStop.isAlive()) threadToStop.interrupt(); } private boolean contains(char[] characters, char character) { if (characters != null) { for (char c : characters) { if (character == c) { return true; } } } return false; } @Override public void keyPressed(KeyEvent e) { // Only act on typed characters and ignore modifier-only events if (e.character == 0 && (e.keyCode & SWT.KEYCODE_BIT) == 0) return; if (e.character != 0 && (e.stateMask == SWT.ALT)) return; // Only act on characters that are trigger candidates. This // avoids computing the model selection on every keystroke if (computeAllAutoActivationTriggers().indexOf(e.character) < 0) { stop(); return; } int showStyle; int pos= fContentAssistSubjectControlAdapter.getSelectedRange().x; char[] activation; activation= fContentAssistSubjectControlAdapter.getCompletionProposalAutoActivationCharacters(ContentAssistant.this, pos); if (contains(activation, e.character) && !isProposalPopupActive()) showStyle= SHOW_PROPOSALS; else { activation= fContentAssistSubjectControlAdapter.getContextInformationAutoActivationCharacters(ContentAssistant.this, pos); if (contains(activation, e.character) && !isContextInfoPopupActive()) showStyle= SHOW_CONTEXT_INFO; else { stop(); return; } } if (fThread != null && fThread.isAlive()) reset(showStyle); else start(showStyle); } @Override public void verifyKey(VerifyEvent event) { keyPressed(event); } protected void showAssist(final int showStyle) { final Control control= fContentAssistSubjectControlAdapter.getControl(); if (control == null) return; final Display d= control.getDisplay(); if (d == null) return; try { d.syncExec(new Runnable() { @Override public void run() { if (isProposalPopupActive()) return; if (control.isDisposed() || !control.isFocusControl()) return; if (showStyle == SHOW_PROPOSALS) { if (!prepareToShowCompletions(true)) return; fProposalPopup.showProposals(true); fLastAutoActivation= System.currentTimeMillis(); } else if (showStyle == SHOW_CONTEXT_INFO && fContextInfoPopup != null) { promoteKeyListener(); fContextInfoPopup.showContextProposals(true); } } }); } catch (SWTError e) { } } } /** * The layout manager layouts the various windows associated with the content assistant based on * the settings of the content assistant. */ class LayoutManager implements Listener { // Presentation types. /** The presentation type for the proposal selection popup. */ public final static int LAYOUT_PROPOSAL_SELECTOR= 0; /** The presentation type for the context selection popup. */ public final static int LAYOUT_CONTEXT_SELECTOR= 1; /** The presentation type for the context information hover . */ public final static int LAYOUT_CONTEXT_INFO_POPUP= 2; int fContextType= LAYOUT_CONTEXT_SELECTOR; Shell[] fShells= new Shell[3]; Object[] fPopups= new Object[3]; protected void add(Object popup, Shell shell, int type, int offset) { Assert.isNotNull(popup); Assert.isTrue(shell != null && !shell.isDisposed()); checkType(type); if (fShells[type] != shell) { if (fShells[type] != null) fShells[type].removeListener(SWT.Dispose, this); shell.addListener(SWT.Dispose, this); fShells[type]= shell; } fPopups[type]= popup; if (type == LAYOUT_CONTEXT_SELECTOR || type == LAYOUT_CONTEXT_INFO_POPUP) fContextType= type; layout(type, offset); adjustListeners(type); } protected void checkType(int type) { Assert.isTrue(type == LAYOUT_PROPOSAL_SELECTOR || type == LAYOUT_CONTEXT_SELECTOR || type == LAYOUT_CONTEXT_INFO_POPUP); } @Override public void handleEvent(Event event) { Widget source= event.widget; source.removeListener(SWT.Dispose, this); int type= getShellType(source); checkType(type); fShells[type]= null; switch (type) { case LAYOUT_PROPOSAL_SELECTOR: if (fContextType == LAYOUT_CONTEXT_SELECTOR && Helper.okToUse(fShells[LAYOUT_CONTEXT_SELECTOR])) { // Restore event notification to the tip popup. addContentAssistListener((IContentAssistListener) fPopups[LAYOUT_CONTEXT_SELECTOR], CONTEXT_SELECTOR); } break; case LAYOUT_CONTEXT_SELECTOR: if (Helper.okToUse(fShells[LAYOUT_PROPOSAL_SELECTOR])) { if (fProposalPopupOrientation == PROPOSAL_STACKED) layout(LAYOUT_PROPOSAL_SELECTOR, getSelectionOffset()); // Restore event notification to the proposal popup. addContentAssistListener((IContentAssistListener) fPopups[LAYOUT_PROPOSAL_SELECTOR], PROPOSAL_SELECTOR); } fContextType= LAYOUT_CONTEXT_INFO_POPUP; break; case LAYOUT_CONTEXT_INFO_POPUP: if (Helper.okToUse(fShells[LAYOUT_PROPOSAL_SELECTOR])) { if (fContextInfoPopupOrientation == CONTEXT_INFO_BELOW) layout(LAYOUT_PROPOSAL_SELECTOR, getSelectionOffset()); } fContextType= LAYOUT_CONTEXT_SELECTOR; break; } } protected int getShellType(Widget shell) { for (int i= 0; i < fShells.length; i++) { if (fShells[i] == shell) return i; } return -1; } /** * Layouts the popup defined by <code>type</code> at the given widget offset. * * @param type the kind of popup to layout * @param offset the widget offset */ protected void layout(int type, int offset) { switch (type) { case LAYOUT_PROPOSAL_SELECTOR: layoutProposalSelector(offset); break; case LAYOUT_CONTEXT_SELECTOR: layoutContextSelector(offset); break; case LAYOUT_CONTEXT_INFO_POPUP: layoutContextInfoPopup(offset); break; } } protected void layoutProposalSelector(int offset) { if (fContextType == LAYOUT_CONTEXT_INFO_POPUP && fContextInfoPopupOrientation == CONTEXT_INFO_BELOW && Helper.okToUse(fShells[LAYOUT_CONTEXT_INFO_POPUP])) { // Stack proposal selector beneath the tip box. Shell shell= fShells[LAYOUT_PROPOSAL_SELECTOR]; Shell parent= fShells[LAYOUT_CONTEXT_INFO_POPUP]; shell.setLocation(getStackedLocation(shell, parent)); } else if (fContextType != LAYOUT_CONTEXT_SELECTOR || !Helper.okToUse(fShells[LAYOUT_CONTEXT_SELECTOR])) { // There are no other presentations to be concerned with, // so place the proposal selector beneath the cursor line. Shell shell= fShells[LAYOUT_PROPOSAL_SELECTOR]; CompletionProposalPopup popup= (CompletionProposalPopup) fPopups[LAYOUT_PROPOSAL_SELECTOR]; shell.setBounds(computeBoundsBelowAbove(shell, shell.getSize(), offset, popup)); } else { CompletionProposalPopup popup= ((CompletionProposalPopup) fPopups[LAYOUT_PROPOSAL_SELECTOR]); switch (fProposalPopupOrientation) { case PROPOSAL_REMOVE: { // Remove the tip selector and place the // proposal selector beneath the cursor line. fShells[LAYOUT_CONTEXT_SELECTOR].dispose(); Shell shell= fShells[LAYOUT_PROPOSAL_SELECTOR]; shell.setBounds(computeBoundsBelowAbove(shell, shell.getSize(), offset, popup)); break; } case PROPOSAL_OVERLAY: { // Overlay the tip selector with the proposal selector. Shell shell= fShells[LAYOUT_PROPOSAL_SELECTOR]; shell.setBounds(computeBoundsBelowAbove(shell, shell.getSize(), offset, popup)); break; } case PROPOSAL_STACKED: { // Stack the proposal selector beneath the tip selector. Shell shell= fShells[LAYOUT_PROPOSAL_SELECTOR]; Shell parent= fShells[LAYOUT_CONTEXT_SELECTOR]; shell.setLocation(getStackedLocation(shell, parent)); break; } } } } protected void layoutContextSelector(int offset) { // Always place the context selector beneath the cursor line. Shell shell= fShells[LAYOUT_CONTEXT_SELECTOR]; shell.setBounds(computeBoundsBelowAbove(shell, shell.getSize(), offset, null)); if (Helper.okToUse(fShells[LAYOUT_PROPOSAL_SELECTOR])) { switch (fProposalPopupOrientation) { case PROPOSAL_REMOVE: // Remove the proposal selector. fShells[LAYOUT_PROPOSAL_SELECTOR].dispose(); break; case PROPOSAL_OVERLAY: // The proposal selector has been overlaid by the tip selector. break; case PROPOSAL_STACKED: { // Stack the proposal selector beneath the tip selector. shell= fShells[LAYOUT_PROPOSAL_SELECTOR]; Shell parent= fShells[LAYOUT_CONTEXT_SELECTOR]; shell.setLocation(getStackedLocation(shell, parent)); break; } } } } protected void layoutContextInfoPopup(int offset) { switch (fContextInfoPopupOrientation) { case CONTEXT_INFO_ABOVE: { // Place the popup above the cursor line. Shell shell= fShells[LAYOUT_CONTEXT_INFO_POPUP]; shell.setBounds(computeBoundsAboveBelow(shell, shell.getSize(), offset)); break; } case CONTEXT_INFO_BELOW: { // Place the popup beneath the cursor line. Shell parent= fShells[LAYOUT_CONTEXT_INFO_POPUP]; parent.setBounds(computeBoundsBelowAbove(parent, parent.getSize(), offset, null)); if (Helper.okToUse(fShells[LAYOUT_PROPOSAL_SELECTOR])) { // Stack the proposal selector beneath the context info popup. Shell shell= fShells[LAYOUT_PROPOSAL_SELECTOR]; shell.setLocation(getStackedLocation(shell, parent)); } break; } } } /** * Moves <code>point</code> such that <code>rectangle</code> does not bleed outside of * <code>bounds</code>. All coordinates must have the same reference. * * @param point the point to move if needed * @param shellSize the size of the shell that may be moved * @param bounds the bounds * @since 3.3 */ protected void constrainLocation(Point point, Point shellSize, Rectangle bounds) { if (point.x + shellSize.x > bounds.x + bounds.width) point.x= bounds.x + bounds.width - shellSize.x; if (point.x < bounds.x) point.x= bounds.x; if (point.y + shellSize.y > bounds.y + bounds.height) point.y= bounds.y + bounds.height - shellSize.y; if (point.y < bounds.y) point.y= bounds.y; } protected Rectangle constrainHorizontally(Rectangle rect, Rectangle bounds) { // clip width if (rect.width > bounds.width) rect.width= bounds.width; if (rect.x + rect.width > bounds.x + bounds.width) rect.x= bounds.x + bounds.width - rect.width; if (rect.x < bounds.x) rect.x= bounds.x; return rect; } /** * Returns the display bounds for <code>shell</code> such that it appears right above * <code>offset</code>, or below it if above is not suitable. The returned bounds lie * within the monitor at the caret location and never overlap with the caret line. * * @param shell the shell to compute the placement for * @param preferred the preferred size for <code>shell</code> * @param offset the caret offset in the subject control * @return the point right above <code>offset</code> in display coordinates * @since 3.3 */ protected Rectangle computeBoundsAboveBelow(Shell shell, Point preferred, int offset) { Control subjectControl= fContentAssistSubjectControlAdapter.getControl(); Display display= subjectControl.getDisplay(); Rectangle caret= getCaretRectangle(offset); Monitor monitor= getClosestMonitor(display, caret); Rectangle bounds= monitor.getClientArea(); Geometry.moveInside(caret, bounds); int spaceAbove= caret.y - bounds.y; int caretLowerY= caret.y + caret.height; int spaceBelow= bounds.y + bounds.height - caretLowerY; Rectangle rect; if (spaceAbove >= preferred.y) rect= new Rectangle(caret.x, caret.y - preferred.y, preferred.x, preferred.y); else if (spaceBelow >= preferred.y) rect= new Rectangle(caret.x, caretLowerY, preferred.x, preferred.y); // we can't fit in the preferred size - squeeze into larger area else if (spaceBelow <= spaceAbove) rect= new Rectangle(caret.x, bounds.y, preferred.x, spaceAbove); else rect= new Rectangle(caret.x, caretLowerY, preferred.x, spaceBelow); return constrainHorizontally(rect, bounds); } /** * Returns the display bounds for <code>shell</code> such that it appears right below * <code>offset</code>, or above it if below is not suitable. The returned bounds lie * within the monitor at the caret location and never overlap with the caret line. * * @param shell the shell to compute the placement for * @param preferred the preferred size for <code>shell</code> * @param offset the caret offset in the subject control * @param popup a popup to inform if the location was switched to above, <code>null</code> to do nothing * @return the point right below <code>offset</code> in display coordinates * @since 3.3 */ protected Rectangle computeBoundsBelowAbove(Shell shell, Point preferred, int offset, CompletionProposalPopup popup) { Control subjectControl= fContentAssistSubjectControlAdapter.getControl(); Display display= subjectControl.getDisplay(); Rectangle caret= getCaretRectangle(offset); Monitor monitor= getClosestMonitor(display, caret); Rectangle bounds= monitor.getClientArea(); Geometry.moveInside(caret, bounds); int threshold= popup == null ? Integer.MAX_VALUE : popup.getMinimalHeight(); int spaceAbove= caret.y - bounds.y; int spaceBelow= bounds.y + bounds.height - (caret.y + caret.height); Rectangle rect; boolean switched= false; if (spaceBelow >= preferred.y) rect= new Rectangle(caret.x, caret.y + caret.height, preferred.x, preferred.y); // squeeze in below if we have at least threshold space else if (spaceBelow >= threshold) rect= new Rectangle(caret.x, caret.y + caret.height, preferred.x, spaceBelow); else if (spaceAbove >= preferred.y) { rect= new Rectangle(caret.x, caret.y - preferred.y, preferred.x, preferred.y); switched= true; } else if (spaceBelow >= spaceAbove) { // we can't fit in the preferred size - squeeze into larger area rect= new Rectangle(caret.x, caret.y + caret.height, preferred.x, spaceBelow); } else { rect= new Rectangle(caret.x, bounds.y, preferred.x, spaceAbove); switched= true; } if (popup != null) popup.switchedPositionToAbove(switched); return constrainHorizontally(rect, bounds); } private Rectangle getCaretRectangle(int offset) { Point location= fContentAssistSubjectControlAdapter.getLocationAtOffset(offset); Control subjectControl= fContentAssistSubjectControlAdapter.getControl(); Point controlSize= subjectControl.getSize(); constrainLocation(location, new Point(0, 0), new Rectangle(0, 0, controlSize.x, controlSize.y)); location= subjectControl.toDisplay(location); Rectangle subjectRectangle= new Rectangle(location.x, location.y, 1, fContentAssistSubjectControlAdapter.getLineHeight()); return subjectRectangle; } protected Point getStackedLocation(Shell shell, Shell parent) { Point p= parent.getLocation(); Point size= parent.getSize(); p.x += size.x / 4; p.y += size.y; p= parent.toDisplay(p); Point shellSize= shell.getSize(); Monitor monitor= getClosestMonitor(parent.getDisplay(), new Rectangle(p.x, p.y, 0, 0)); Rectangle displayBounds= monitor.getClientArea(); constrainLocation(p, shellSize, displayBounds); return p; } protected void adjustListeners(int type) { switch (type) { case LAYOUT_PROPOSAL_SELECTOR: if (fContextType == LAYOUT_CONTEXT_SELECTOR && Helper.okToUse(fShells[LAYOUT_CONTEXT_SELECTOR])) // Disable event notification to the tip selector. removeContentAssistListener((IContentAssistListener) fPopups[LAYOUT_CONTEXT_SELECTOR], CONTEXT_SELECTOR); break; case LAYOUT_CONTEXT_SELECTOR: if (Helper.okToUse(fShells[LAYOUT_PROPOSAL_SELECTOR])) // Disable event notification to the proposal selector. removeContentAssistListener((IContentAssistListener) fPopups[LAYOUT_PROPOSAL_SELECTOR], PROPOSAL_SELECTOR); break; case LAYOUT_CONTEXT_INFO_POPUP: break; } } /** * Copied from org.eclipse.jface.window.Window. Returns the monitor whose client area * contains the given point. If no monitor contains the point, returns the monitor that is * closest to the point. If this is ever made public, it should be moved into a separate * utility class. * * @param toSearch point to find (display coordinates) * @param rectangle rectangle to find (display coordinates) * @return the monitor closest to the given point * @since 3.3 */ private Monitor getClosestMonitor(Display toSearch, Rectangle rectangle) { int closest = Integer.MAX_VALUE; Point toFind= Geometry.centerPoint(rectangle); Monitor[] monitors = toSearch.getMonitors(); Monitor result = monitors[0]; for (Monitor current : monitors) { Rectangle clientArea = current.getClientArea(); if (clientArea.contains(toFind)) { return current; } int distance = Geometry.distanceSquared(Geometry.centerPoint(clientArea), toFind); if (distance < closest) { closest = distance; result = current; } } return result; } } /** * Internal key listener and event consumer. */ class InternalListener implements VerifyKeyListener, IEventConsumer { /** * Verifies key events by notifying the registered listeners. Each listener is allowed to * indicate that the event has been handled and should not be further processed. * * @param e the verify event * @see VerifyKeyListener#verifyKey(org.eclipse.swt.events.VerifyEvent) */ @Override public void verifyKey(VerifyEvent e) { IContentAssistListener[] listeners= fListeners.clone(); for (int i= 0; i < listeners.length; i++) { if (listeners[i] != null) { if (!listeners[i].verifyKey(e) || !e.doit) break; } } if (fAutoAssistListener != null) fAutoAssistListener.keyPressed(e); } /* * @see IEventConsumer#processEvent */ @Override public void processEvent(VerifyEvent event) { installKeyListener(); IContentAssistListener[] listeners= fListeners.clone(); for (IContentAssistListener listener : listeners) { if (listener != null) { listener.processEvent(event); if (!event.doit) return; } } } } /** * A subclass of ISafeRunnable which, in case of exception, logs a specified error message to the jface.text log and * sets fLastErrorMessage to this message. */ private abstract class ExceptionLoggingSafeRunnable implements ISafeRunnable { private static final String PLUGIN_ID= "org.eclipse.jface.text"; //$NON-NLS-1$ private final String fMessageKey; /** * @param messageKey key passed to JFaceTextMessages to lookup the text of the error message */ ExceptionLoggingSafeRunnable(String messageKey) { fMessageKey= messageKey; } @Override public void handleException(Throwable exception) { String message= JFaceTextMessages.getString(fMessageKey); IStatus status= new Status(IStatus.ERROR, PLUGIN_ID, message, exception); Platform.getLog(Platform.getBundle(PLUGIN_ID)).log(status); fLastErrorMessage= message; } } /** * Dialog store constant for the x-size of the completion proposal pop-up * * @since 3.0 */ public static final String STORE_SIZE_X= "size.x"; //$NON-NLS-1$ /** * Dialog store constant for the y-size of the completion proposal pop-up * * @since 3.0 */ public static final String STORE_SIZE_Y= "size.y"; //$NON-NLS-1$ /** * Dialog store constant for the x-size of the context selector pop-up * * @since 3.9 */ public static final String STORE_CONTEXT_SELECTOR_POPUP_SIZE_X= "contextSelector.size.x"; //$NON-NLS-1$ /** * Dialog store constant for the y-size of the context selector pop-up * * @since 3.9 */ public static final String STORE_CONTEXT_SELECTOR_POPUP_SIZE_Y= "contextSelector.size.y"; //$NON-NLS-1$ // Content-Assist Listener types final static int CONTEXT_SELECTOR= 0; final static int PROPOSAL_SELECTOR= 1; final static int CONTEXT_INFO_POPUP= 2; /** * The popup priority: > linked position proposals and hover pop-ups. Default value: * <code>20</code>; * * @since 3.0 */ public static final int WIDGET_PRIORITY= 20; private static final int DEFAULT_AUTO_ACTIVATION_DELAY= 500; private static final String COMPLETION_ERROR_MESSAGE_KEY= "ContentAssistant.error_computing_completion"; //$NON-NLS-1$ private static final String CONTEXT_ERROR_MESSAGE_KEY= "ContentAssistant.error_computing_context"; //$NON-NLS-1$ private BoldStylerProvider fBoldStylerProvider; private IInformationControlCreator fInformationControlCreator; private int fAutoActivationDelay= DEFAULT_AUTO_ACTIVATION_DELAY; private boolean fIsAutoActivated= false; private boolean fIsAutoInserting= false; private int fProposalPopupOrientation= PROPOSAL_OVERLAY; private int fContextInfoPopupOrientation= CONTEXT_INFO_ABOVE; private Map<String, Set<IContentAssistProcessor>> fProcessors; /** * The partitioning. * * @since 3.0 */ private String fPartitioning; private Color fContextInfoPopupBackground; private Color fContextInfoPopupForeground; private Color fContextSelectorBackground; private Color fContextSelectorForeground; private Color fProposalSelectorBackground; private Color fProposalSelectorForeground; private ITextViewer fViewer; private String fLastErrorMessage; private Closer fCloser; LayoutManager fLayoutManager; private AutoAssistListener fAutoAssistListener; private InternalListener fInternalListener; private CompletionProposalPopup fProposalPopup; private ContextInformationPopup fContextInfoPopup; /** * Flag which tells whether a verify key listener is hooked. * * @since 3.0 */ private boolean fVerifyKeyListenerHooked= false; private IContentAssistListener[] fListeners= new IContentAssistListener[4]; /** * The content assist subject control. * * @since 3.0 */ private IContentAssistSubjectControl fContentAssistSubjectControl; /** * The content assist subject control's shell. * * @since 3.2 */ private Shell fContentAssistSubjectControlShell; /** * The content assist subject control's shell traverse listener. * * @since 3.2 */ private TraverseListener fCASCSTraverseListener; /** * The content assist subject control adapter. * * @since 3.0 */ private ContentAssistSubjectControlAdapter fContentAssistSubjectControlAdapter; /** * The dialog settings for the control's bounds. * * @since 3.0 */ private IDialogSettings fDialogSettings; /** * Prefix completion setting. * * @since 3.0 */ private boolean fIsPrefixCompletionEnabled= false; /** * The list of completion listeners. * * @since 3.2 */ private ListenerList<ICompletionListener> fCompletionListeners= new ListenerList<>(ListenerList.IDENTITY); /** * The message to display at the bottom of the proposal popup. * * @since 3.2 */ private String fMessage= ""; //$NON-NLS-1$ /** * The cycling mode property. * * @since 3.2 */ private boolean fIsRepetitionMode= false; /** * The show empty property. * * @since 3.2 */ private boolean fShowEmptyList= false; /** * The message line property. * * @since 3.2 */ private boolean fIsStatusLineVisible; /** * The last system time when auto activation performed. * * @since 3.2 */ private long fLastAutoActivation= Long.MIN_VALUE; /** * The iteration key sequence to listen for, or <code>null</code>. * * @since 3.2 */ private KeySequence fRepeatedInvocationKeySequence; /** * Maps handler to command identifiers. * * @since 3.4 */ private Map<String, IHandler> fHandlers; /** * Tells whether colored labels support is enabled. * * @since 3.4 */ private boolean fIsColoredLabelsSupportEnabled= false; /** * The sorter to be used for sorting the proposals or <code>null</code> if no sorting is * requested. * * @since 3.8 */ private ICompletionProposalSorter fSorter; /** * Tells whether this content assistant allows to run asynchronous * * @since 3.12 */ private boolean fAsynchronous; /** * Creates a new content assistant. The content assistant is not automatically activated, * overlays the completion proposals with context information list if necessary, and shows the * context information above the location at which it was activated. If auto activation will be * enabled, without further configuration steps, this content assistant is activated after a 500 * milliseconds delay. It uses the default partitioning. */ public ContentAssistant() { this(false); } /** * Creates a new content assistant. The content assistant is not automatically activated, * overlays the completion proposals with context information list if necessary, and shows the * context information above the location at which it was activated. If auto activation will be * enabled, without further configuration steps, this content assistant is activated after a 500 * milliseconds delay. It uses the default partitioning. * * @param asynchronous <true> if this content assistant should present the proposals * asynchronously, <code>false</code> otherwise * @since 3.12 */ public ContentAssistant(boolean asynchronous) { fPartitioning= IDocumentExtension3.DEFAULT_PARTITIONING; fAsynchronous= asynchronous; } /** * Sets the document partitioning this content assistant is using. * * @param partitioning the document partitioning for this content assistant * @since 3.0 */ public void setDocumentPartitioning(String partitioning) { Assert.isNotNull(partitioning); fPartitioning= partitioning; } @Override public String getDocumentPartitioning() { return fPartitioning; } /** * Registers a given content assist processor for a particular content type. If there is already * a processor registered for this type, the new processor is registered instead of the old one. * * @param processor the content assist processor to register, or <code>null</code> to remove * an existing one * @param contentType the content type under which to register */ public void setContentAssistProcessor(IContentAssistProcessor processor, String contentType) { Assert.isNotNull(contentType); if (fProcessors == null) fProcessors= new HashMap<>(); if (processor == null) fProcessors.remove(contentType); else fProcessors.put(contentType, Collections.singleton(processor)); } /** * Registers a given content assist processor for a particular content type. If there is already * a processor registered for this type, it is kept and the new processor is appended to the list * of processors for given content-type. * * @param processor The content-assist process to add * @param contentType Document token content-type it applies to * @since 3.12 */ public void addContentAssistProcessor(IContentAssistProcessor processor, String contentType) { Assert.isNotNull(contentType); if (fProcessors == null) fProcessors= new HashMap<>(); if (processor == null) { fProcessors.remove(contentType); } else { if (!fProcessors.containsKey(contentType)) { fProcessors.put(contentType, new LinkedHashSet<>()); } fProcessors.get(contentType).add(processor); } } /* * @see IContentAssistant#getContentAssistProcessor */ @Override public IContentAssistProcessor getContentAssistProcessor(String contentType) { if (fProcessors == null) return null; Set<IContentAssistProcessor> res = fProcessors.get(contentType); if (res == null || res.isEmpty()) { return null; } else { return res.iterator().next(); // return first one although there might be multiple ones... TODO, consider an aggregator contentAssistProcessor to return here } } /** * Returns the content assist processors to be used for the given content type. * * @param contentType the type of the content for which this content assistant is to be * requested * @return the content assist processors or <code>null</code> if none exists for the specified * content type * @since 3.12 */ Set<IContentAssistProcessor> getContentAssistProcessors(String contentType) { if (fProcessors == null) return null; Set<IContentAssistProcessor> res = fProcessors.get(contentType); if (res == null || res.isEmpty()) { return null; } return res; } /** * Computes the sorted set of all auto activation trigger characters. * * @return the sorted set of all auto activation trigger characters * @since 3.1 */ private String computeAllAutoActivationTriggers() { if (fProcessors == null) return ""; //$NON-NLS-1$ StringBuffer buf= new StringBuffer(5); for (Set<IContentAssistProcessor> processorsForContentType : fProcessors.values()) { for (IContentAssistProcessor processor : processorsForContentType) { char[] triggers= processor.getCompletionProposalAutoActivationCharacters(); if (triggers != null) buf.append(triggers); triggers= processor.getContextInformationAutoActivationCharacters(); if (triggers != null) buf.append(triggers); } } return buf.toString(); } /** * Enables the content assistant's auto activation mode. * * @param enabled indicates whether auto activation is enabled or not */ public void enableAutoActivation(boolean enabled) { fIsAutoActivated= enabled; manageAutoActivation(fIsAutoActivated); } /** * Enables the content assistant's auto insertion mode. If enabled, the content assistant * inserts a proposal automatically if it is the only proposal. In the case of ambiguities, the * user must make the choice. * * @param enabled indicates whether auto insertion is enabled or not * @since 2.0 */ public void enableAutoInsert(boolean enabled) { fIsAutoInserting= enabled; } /** * Returns whether this content assistant is in the auto insertion mode or not. * * @return <code>true</code> if in auto insertion mode * @since 2.0 */ boolean isAutoInserting() { return fIsAutoInserting; } /** * Installs and uninstall the listeners needed for auto activation. * * @param start <code>true</code> if listeners must be installed, <code>false</code> if they * must be removed * @since 2.0 */ private void manageAutoActivation(boolean start) { if (start) { if ((fContentAssistSubjectControlAdapter != null) && fAutoAssistListener == null) { fAutoAssistListener= createAutoAssistListener(); // For details see https://bugs.eclipse.org/bugs/show_bug.cgi?id=49212 if (fContentAssistSubjectControlAdapter.supportsVerifyKeyListener()) fContentAssistSubjectControlAdapter.appendVerifyKeyListener(fAutoAssistListener); else fContentAssistSubjectControlAdapter.addKeyListener(fAutoAssistListener); } } else if (fAutoAssistListener != null) { // For details see https://bugs.eclipse.org/bugs/show_bug.cgi?id=49212 if (fContentAssistSubjectControlAdapter.supportsVerifyKeyListener()) fContentAssistSubjectControlAdapter.removeVerifyKeyListener(fAutoAssistListener); else fContentAssistSubjectControlAdapter.removeKeyListener(fAutoAssistListener); fAutoAssistListener= null; } } /** * This method allows subclasses to provide their own {@link AutoAssistListener}. * * @return a new auto assist listener * @since 3.4 */ protected AutoAssistListener createAutoAssistListener() { return new AutoAssistListener(); } /** * Sets the delay after which the content assistant is automatically invoked if the cursor is * behind an auto activation character. * * @param delay the auto activation delay (as of 3.6 a negative argument will be set to 0) */ public void setAutoActivationDelay(int delay) { fAutoActivationDelay= Math.max(0, delay); } /** * Gets the delay after which the content assistant is automatically invoked if the cursor is * behind an auto activation character. * * @return the auto activation delay (will not be negative) * @since 3.4 */ public int getAutoActivationDelay() { return fAutoActivationDelay; } /** * Sets the proposal pop-ups' orientation. The following values may be used: * <ul> * <li>PROPOSAL_OVERLAY<p> * proposal popup windows should overlay each other * </li> * <li>PROPOSAL_REMOVE<p> * any currently shown proposal popup should be closed * </li> * <li>PROPOSAL_STACKED<p> * proposal popup windows should be vertical stacked, with no overlap, * beneath the line containing the current cursor location * </li> * </ul> * * @param orientation the popup's orientation */ public void setProposalPopupOrientation(int orientation) { fProposalPopupOrientation= orientation; } /** * Sets the context information popup's orientation. * The following values may be used: * <ul> * <li>CONTEXT_ABOVE<p> * context information popup should always appear above the line containing * the current cursor location * </li> * <li>CONTEXT_BELOW<p> * context information popup should always appear below the line containing * the current cursor location * </li> * </ul> * * @param orientation the popup's orientation */ public void setContextInformationPopupOrientation(int orientation) { fContextInfoPopupOrientation= orientation; } /** * Sets the context information popup's background color. * * @param background the background color */ public void setContextInformationPopupBackground(Color background) { fContextInfoPopupBackground= background; } /** * Returns the background of the context information popup. * * @return the background of the context information popup * @since 2.0 */ Color getContextInformationPopupBackground() { return fContextInfoPopupBackground; } /** * Sets the context information popup's foreground color. * * @param foreground the foreground color * @since 2.0 */ public void setContextInformationPopupForeground(Color foreground) { fContextInfoPopupForeground= foreground; } /** * Returns the foreground of the context information popup. * * * @return the foreground of the context information popup * @since 2.0 */ Color getContextInformationPopupForeground() { return fContextInfoPopupForeground; } /** * Sets the proposal selector's background color. * <p> * <strong>Note:</strong> As of 3.4, you should only call this * method if you want to override the {@link JFacePreferences#CONTENT_ASSIST_BACKGROUND_COLOR}. * </p> * * @param background the background color * @since 2.0 */ public void setProposalSelectorBackground(Color background) { fProposalSelectorBackground= background; } /** * Returns the custom background color of the proposal selector. * * @return the background of the proposal selector or <code>null</code> if not set * @since 2.0 */ Color getProposalSelectorBackground() { return fProposalSelectorBackground; } /** * Sets the proposal's foreground color. * <p> * <strong>Note:</strong> As of 3.4, you should only call this * method if you want to override the {@link JFacePreferences#CONTENT_ASSIST_FOREGROUND_COLOR}. * </p> * * @param foreground the foreground color * @since 2.0 */ public void setProposalSelectorForeground(Color foreground) { fProposalSelectorForeground= foreground; } /** * Returns the custom foreground color of the proposal selector. * * @return the foreground of the proposal selector or <code>null</code> if not set * @since 2.0 */ Color getProposalSelectorForeground() { return fProposalSelectorForeground; } /** * Sets the {@link BoldStylerProvider} used to emphasize matches in a proposal's styled display * string. * * @param boldStylerProvider the bold styler provider * * @see ICompletionProposalExtension7#getStyledDisplayString(IDocument, int, BoldStylerProvider) * @since 3.11 */ void setBoldStylerProvider(BoldStylerProvider boldStylerProvider) { fBoldStylerProvider= boldStylerProvider; } /** * Returns the {@link BoldStylerProvider} used to emphasize matches in a proposal's styled * display string. * * @see ICompletionProposalExtension7#getStyledDisplayString(IDocument, int, BoldStylerProvider) * * @return the {@link BoldStylerProvider}, or <code>null</code> if not set * @since 3.11 */ BoldStylerProvider getBoldStylerProvider() { return fBoldStylerProvider; } /** * Sets the context selector's background color. * * @param background the background color * @since 2.0 */ public void setContextSelectorBackground(Color background) { fContextSelectorBackground= background; } /** * Returns the background of the context selector. * * @return the background of the context selector * @since 2.0 */ Color getContextSelectorBackground() { return fContextSelectorBackground; } /** * Sets the context selector's foreground color. * * @param foreground the foreground color * @since 2.0 */ public void setContextSelectorForeground(Color foreground) { fContextSelectorForeground= foreground; } /** * Returns the foreground of the context selector. * * @return the foreground of the context selector * @since 2.0 */ Color getContextSelectorForeground() { return fContextSelectorForeground; } /** * Sets the information control creator for the additional information control. * * @param creator the information control creator for the additional information control * @since 2.0 */ public void setInformationControlCreator(IInformationControlCreator creator) { fInformationControlCreator= creator; } /* * @see IControlContentAssistant#install(IContentAssistSubjectControl) * @since 3.0 */ protected void install(IContentAssistSubjectControl contentAssistSubjectControl) { fContentAssistSubjectControl= contentAssistSubjectControl; fContentAssistSubjectControlAdapter= new ContentAssistSubjectControlAdapter(fContentAssistSubjectControl); install(); } /* * @see IContentAssist#install * @since 3.0 */ @Override public void install(ITextViewer textViewer) { fViewer= textViewer; fContentAssistSubjectControlAdapter= new ContentAssistSubjectControlAdapter(fViewer); install(); } protected void install() { fLayoutManager= new LayoutManager(); fInternalListener= new InternalListener(); AdditionalInfoController controller= null; if (fInformationControlCreator != null) controller= new AdditionalInfoController(fInformationControlCreator, OpenStrategy.getPostSelectionDelay()); fContextInfoPopup= fContentAssistSubjectControlAdapter.createContextInfoPopup(this); fProposalPopup= fContentAssistSubjectControlAdapter.createCompletionProposalPopup(this, controller, fAsynchronous); fProposalPopup.setSorter(fSorter); registerHandler(SELECT_NEXT_PROPOSAL_COMMAND_ID, fProposalPopup.createProposalSelectionHandler(CompletionProposalPopup.ProposalSelectionHandler.SELECT_NEXT)); registerHandler(SELECT_PREVIOUS_PROPOSAL_COMMAND_ID, fProposalPopup.createProposalSelectionHandler(CompletionProposalPopup.ProposalSelectionHandler.SELECT_PREVIOUS)); if (Helper.okToUse(fContentAssistSubjectControlAdapter.getControl())) { fContentAssistSubjectControlShell= fContentAssistSubjectControlAdapter.getControl().getShell(); fCASCSTraverseListener= new TraverseListener() { @Override public void keyTraversed(TraverseEvent e) { if (e.detail == SWT.TRAVERSE_ESCAPE && isProposalPopupActive()) e.doit= false; } }; fContentAssistSubjectControlShell.addTraverseListener(fCASCSTraverseListener); } manageAutoActivation(fIsAutoActivated); } /* * @see IContentAssist#uninstall */ @Override public void uninstall() { hide(); if (fBoldStylerProvider != null) { fBoldStylerProvider.dispose(); fBoldStylerProvider= null; } manageAutoActivation(false); if (fHandlers != null) { fHandlers.clear(); fHandlers= null; } if (fCloser != null) { fCloser.uninstall(); fCloser= null; } if (Helper.okToUse(fContentAssistSubjectControlShell)) fContentAssistSubjectControlShell.removeTraverseListener(fCASCSTraverseListener); fCASCSTraverseListener= null; fContentAssistSubjectControlShell= null; fViewer= null; fContentAssistSubjectControl= null; fContentAssistSubjectControlAdapter= null; } /** * Adds the given shell of the specified type to the layout. Valid types are defined by * <code>LayoutManager</code>. * * @param popup a content assist popup * @param shell the shell of the content-assist popup * @param type the type of popup * @param visibleOffset the offset at which to layout the popup relative to the offset of the * viewer's visible region * @since 2.0 */ void addToLayout(Object popup, Shell shell, int type, int visibleOffset) { fLayoutManager.add(popup, shell, type, visibleOffset); } /** * Layouts the registered popup of the given type relative to the given offset. The offset is * relative to the offset of the viewer's visible region. Valid types are defined by * <code>LayoutManager</code>. * * @param type the type of popup to layout * @param visibleOffset the offset at which to layout relative to the offset of the viewer's * visible region * @since 2.0 */ void layout(int type, int visibleOffset) { fLayoutManager.layout(type, visibleOffset); } /** * Returns the layout manager. * * @return the layout manager * @since 3.3 */ LayoutManager getLayoutManager() { return fLayoutManager; } /** * Notifies the controller that a popup has lost focus. * * @param e the focus event */ void popupFocusLost(FocusEvent e) { fCloser.focusLost(e); } /** * Returns the offset of the selection relative to the offset of the visible region. * * @return the offset of the selection relative to the offset of the visible region * @since 2.0 */ int getSelectionOffset() { return fContentAssistSubjectControlAdapter.getWidgetSelectionRange().x; } /** * Returns whether the widget token could be acquired. The following are valid listener types: * <ul> * <li>AUTO_ASSIST</li> * <li>CONTEXT_SELECTOR</li> * <li>PROPOSAL_SELECTOR</li> * <li>CONTEXT_INFO_POPUP</li> * </ul> * * @param type the listener type for which to acquire * @return <code>true</code> if the widget token could be acquired * @since 2.0 */ private boolean acquireWidgetToken(int type) { switch (type) { case CONTEXT_SELECTOR: case PROPOSAL_SELECTOR: if (fContentAssistSubjectControl instanceof IWidgetTokenOwnerExtension) { IWidgetTokenOwnerExtension extension= (IWidgetTokenOwnerExtension) fContentAssistSubjectControl; return extension.requestWidgetToken(this, WIDGET_PRIORITY); } else if (fContentAssistSubjectControl instanceof IWidgetTokenOwner) { IWidgetTokenOwner owner= (IWidgetTokenOwner) fContentAssistSubjectControl; return owner.requestWidgetToken(this); } else if (fViewer instanceof IWidgetTokenOwnerExtension) { IWidgetTokenOwnerExtension extension= (IWidgetTokenOwnerExtension) fViewer; return extension.requestWidgetToken(this, WIDGET_PRIORITY); } else if (fViewer instanceof IWidgetTokenOwner) { IWidgetTokenOwner owner= (IWidgetTokenOwner) fViewer; return owner.requestWidgetToken(this); } } return true; } /** * Registers a content assist listener. The following are valid listener types: * <ul> * <li>AUTO_ASSIST</li> * <li>CONTEXT_SELECTOR</li> * <li>PROPOSAL_SELECTOR</li> * <li>CONTEXT_INFO_POPUP</li> * </ul> * Returns whether the listener could be added successfully. A listener can not be added if the * widget token could not be acquired. * * @param listener the listener to register * @param type the type of listener * @return <code>true</code> if the listener could be added */ boolean addContentAssistListener(IContentAssistListener listener, int type) { if (acquireWidgetToken(type)) { fListeners[type]= listener; if (fCloser == null && getNumberOfListeners() == 1) { fCloser= new Closer(); fCloser.install(); fContentAssistSubjectControlAdapter.setEventConsumer(fInternalListener); installKeyListener(); } else promoteKeyListener(); return true; } return false; } /** * Re-promotes the key listener to the first position, using prependVerifyKeyListener. This * ensures no other instance is filtering away the keystrokes underneath, if we've been up for a * while (e.g. when the context info is showing. * * @since 3.0 */ private void promoteKeyListener() { uninstallVerifyKeyListener(); installKeyListener(); } /** * Installs a key listener on the text viewer's widget. */ private void installKeyListener() { if (!fVerifyKeyListenerHooked) { if (Helper.okToUse(fContentAssistSubjectControlAdapter.getControl())) { fVerifyKeyListenerHooked= fContentAssistSubjectControlAdapter.prependVerifyKeyListener(fInternalListener); } } } /** * Releases the previously acquired widget token if the token is no longer necessary. The * following are valid listener types: * <ul> * <li>AUTO_ASSIST</li> * <li>CONTEXT_SELECTOR</li> * <li>PROPOSAL_SELECTOR</li> * <li>CONTEXT_INFO_POPUP</li> * </ul> * * @param type the listener type * @since 2.0 */ private void releaseWidgetToken(int type) { if (fListeners[CONTEXT_SELECTOR] == null && fListeners[PROPOSAL_SELECTOR] == null) { IWidgetTokenOwner owner= null; if (fContentAssistSubjectControl instanceof IWidgetTokenOwner) owner= (IWidgetTokenOwner) fContentAssistSubjectControl; else if (fViewer instanceof IWidgetTokenOwner) owner= (IWidgetTokenOwner) fViewer; if (owner != null) owner.releaseWidgetToken(this); } } /** * Unregisters a content assist listener. * * @param listener the listener to unregister * @param type the type of listener * @see #addContentAssistListener(IContentAssistListener, int) */ void removeContentAssistListener(IContentAssistListener listener, int type) { fListeners[type]= null; if (getNumberOfListeners() == 0) { if (fCloser != null) { fCloser.uninstall(); fCloser= null; } uninstallVerifyKeyListener(); fContentAssistSubjectControlAdapter.setEventConsumer(null); } releaseWidgetToken(type); } /** * Uninstall the key listener from the text viewer's widget. * * @since 3.0 */ private void uninstallVerifyKeyListener() { if (fVerifyKeyListenerHooked) { if (Helper.okToUse(fContentAssistSubjectControlAdapter.getControl())) fContentAssistSubjectControlAdapter.removeVerifyKeyListener(fInternalListener); fVerifyKeyListenerHooked= false; } } /** * Returns the number of listeners. * * @return the number of listeners * @since 2.0 */ private int getNumberOfListeners() { int count= 0; for (int i= 0; i <= CONTEXT_INFO_POPUP; i++) { if (fListeners[i] != null) ++count; } return count; } /* * @see IContentAssist#showPossibleCompletions */ @Override public String showPossibleCompletions() { if (!prepareToShowCompletions(false)) return null; if (fIsPrefixCompletionEnabled) return fProposalPopup.incrementalComplete(); return fProposalPopup.showProposals(false); } @Override public String completePrefix() { if (!prepareToShowCompletions(false)) return null; return fProposalPopup.incrementalComplete(); } /** * Prepares to show content assist proposals. It returns false if auto activation has kicked in * recently. * * @param isAutoActivated whether completion was triggered by auto activation * @return <code>true</code> if the caller should continue and show the proposals, * <code>false</code> otherwise. * @since 3.2 */ private boolean prepareToShowCompletions(boolean isAutoActivated) { if (!isAutoActivated) { int gracePeriod= Math.max(fAutoActivationDelay, 200); if (System.currentTimeMillis() < fLastAutoActivation + gracePeriod) { return false; } } promoteKeyListener(); fireSessionBeginEvent(isAutoActivated); return true; } /** * Callback to signal this content assistant that the presentation of the possible completions * has been stopped. * * @since 2.1 */ protected void possibleCompletionsClosed() { fLastAutoActivation= Long.MIN_VALUE; storeCompletionProposalPopupSize(); } /* * @see IContentAssist#showContextInformation */ @Override public String showContextInformation() { promoteKeyListener(); if (fContextInfoPopup != null) return fContextInfoPopup.showContextProposals(false); return null; } /** * Callback to signal this content assistant that the presentation of the context information * has been stopped. * * @since 2.1 */ protected void contextInformationClosed() { } /** * Requests that the specified context information to be shown. * * @param contextInformation the context information to be shown * @param offset the offset to which the context information refers to * @since 2.0 */ void showContextInformation(IContextInformation contextInformation, int offset) { if (fContextInfoPopup != null) fContextInfoPopup.showContextInformation(contextInformation, offset); } /** * Returns the current content assist error message. * * @return an error message or <code>null</code> if no error has occurred */ String getErrorMessage() { return fLastErrorMessage; } /** * Returns the content assist processor for the content type of the specified document position. * * @param viewer the text viewer * @param offset a offset within the document * @return a content-assist processor or <code>null</code> if none exists * @since 3.0 */ private IContentAssistProcessor getProcessor(ITextViewer viewer, int offset) { try { IDocument document= viewer.getDocument(); String type= TextUtilities.getContentType(document, getDocumentPartitioning(), offset, true); return getContentAssistProcessor(type); } catch (BadLocationException x) { } return null; } /** * Returns the content assist processor for the content type of the specified document position. * * @param contentAssistSubjectControl the content assist subject control * @param offset a offset within the document * @return a content-assist processor or <code>null</code> if none exists * @since 3.0 */ private IContentAssistProcessor getProcessor(IContentAssistSubjectControl contentAssistSubjectControl, int offset) { try { IDocument document= contentAssistSubjectControl.getDocument(); String type; if (document != null) type= TextUtilities.getContentType(document, getDocumentPartitioning(), offset, true); else type= IDocument.DEFAULT_CONTENT_TYPE; return getContentAssistProcessor(type); } catch (BadLocationException x) { } return null; } /** * Returns an array of completion proposals computed based on the specified document position. * The position is used to determine the appropriate content assist processor to invoke. * * @param contentAssistSubjectControl the content assist subject control * @param offset a document offset * @return an array of completion proposals or <code>null</code> if no proposals are possible * @see IContentAssistProcessor#computeCompletionProposals(ITextViewer, int) * @since 3.0 */ ICompletionProposal[] computeCompletionProposals( final IContentAssistSubjectControl contentAssistSubjectControl, final int offset) { fLastErrorMessage= null; final ICompletionProposal[][] result= { null }; final IContentAssistProcessor p= getProcessor(contentAssistSubjectControl, offset); if (p instanceof ISubjectControlContentAssistProcessor) { // Ensure that the assist session ends cleanly even if the processor throws an exception. SafeRunner.run(new ExceptionLoggingSafeRunnable(COMPLETION_ERROR_MESSAGE_KEY) { @Override public void run() throws Exception { result[0]= ((ISubjectControlContentAssistProcessor) p) .computeCompletionProposals(contentAssistSubjectControl, offset); fLastErrorMessage= p.getErrorMessage(); } }); } return result[0]; } /** * Returns an array of completion proposals computed based on the specified document position. * The position is used to determine the appropriate content assist processor to invoke. * * @param viewer the viewer for which to compute the proposals * @param offset a document offset * @return an array of completion proposals or <code>null</code> if no proposals are possible * @see IContentAssistProcessor#computeCompletionProposals(ITextViewer, int) */ ICompletionProposal[] computeCompletionProposals(final ITextViewer viewer, final int offset) { fLastErrorMessage= null; final ICompletionProposal[][] result= { null }; final IContentAssistProcessor p= getProcessor(viewer, offset); if (p != null) { // Ensure that the assist session ends cleanly even if the processor throws an exception. SafeRunner.run(new ExceptionLoggingSafeRunnable(COMPLETION_ERROR_MESSAGE_KEY) { @Override public void run() throws Exception { result[0]= p.computeCompletionProposals(viewer, offset); fLastErrorMessage= p.getErrorMessage(); } }); } return result[0]; } /** * Returns an array of context information objects computed based on the specified document * position. The position is used to determine the appropriate content assist processor to * invoke. * * @param viewer the viewer for which to compute the context information * @param offset a document offset * @return an array of context information objects * @see IContentAssistProcessor#computeContextInformation(ITextViewer, int) */ IContextInformation[] computeContextInformation(final ITextViewer viewer, final int offset) { fLastErrorMessage= null; final IContextInformation[][] result= { null }; final IContentAssistProcessor p= getProcessor(viewer, offset); if (p != null) { // Ensure that the assist session ends cleanly even if the processor throws an exception. SafeRunner.run(new ExceptionLoggingSafeRunnable(CONTEXT_ERROR_MESSAGE_KEY) { @Override public void run() throws Exception { result[0]= p.computeContextInformation(viewer, offset); fLastErrorMessage= p.getErrorMessage(); } }); } return result[0]; } /** * Returns an array of context information objects computed based on the specified document * position. The position is used to determine the appropriate content assist processor to * invoke. * * @param contentAssistSubjectControl the content assist subject control * @param offset a document offset * @return an array of context information objects * @see IContentAssistProcessor#computeContextInformation(ITextViewer, int) * @since 3.0 */ IContextInformation[] computeContextInformation( final IContentAssistSubjectControl contentAssistSubjectControl, final int offset) { fLastErrorMessage= null; final IContextInformation[][] result= { null }; final IContentAssistProcessor p= getProcessor(contentAssistSubjectControl, offset); if (p instanceof ISubjectControlContentAssistProcessor) { // Ensure that the assist session ends cleanly even if the processor throws an exception. SafeRunner.run(new ExceptionLoggingSafeRunnable(CONTEXT_ERROR_MESSAGE_KEY) { @Override public void run() throws Exception { result[0]= ((ISubjectControlContentAssistProcessor) p) .computeContextInformation(contentAssistSubjectControl, offset); fLastErrorMessage= p.getErrorMessage(); } }); } return result[0]; } /** * Returns the context information validator that should be used to determine when the currently * displayed context information should be dismissed. The position is used to determine the * appropriate content assist processor to invoke. * * @param viewer the text viewer * @param offset a document offset * @return an validator * @see IContentAssistProcessor#getContextInformationValidator() * @since 3.0 */ IContextInformationValidator getContextInformationValidator(ITextViewer viewer, int offset) { IContentAssistProcessor p= getProcessor(viewer, offset); return p != null ? p.getContextInformationValidator() : null; } /** * Returns the context information validator that should be used to determine when the currently * displayed context information should be dismissed. The position is used to determine the * appropriate content assist processor to invoke. * * @param contentAssistSubjectControl the content assist subject control * @param offset a document offset * @return an validator * @see IContentAssistProcessor#getContextInformationValidator() * @since 3.0 */ IContextInformationValidator getContextInformationValidator(IContentAssistSubjectControl contentAssistSubjectControl, int offset) { IContentAssistProcessor p= getProcessor(contentAssistSubjectControl, offset); return p != null ? p.getContextInformationValidator() : null; } /** * Returns the context information presenter that should be used to display context information. * The position is used to determine the appropriate content assist processor to invoke. * * @param viewer the text viewer * @param offset a document offset * @return a presenter * @since 2.0 */ IContextInformationPresenter getContextInformationPresenter(ITextViewer viewer, int offset) { IContextInformationValidator validator= getContextInformationValidator(viewer, offset); if (validator instanceof IContextInformationPresenter) return (IContextInformationPresenter) validator; return null; } /** * Returns the context information presenter that should be used to display context information. * The position is used to determine the appropriate content assist processor to invoke. * * @param contentAssistSubjectControl the content assist subject control * @param offset a document offset * @return a presenter * @since 3.0 */ IContextInformationPresenter getContextInformationPresenter(IContentAssistSubjectControl contentAssistSubjectControl, int offset) { IContextInformationValidator validator= getContextInformationValidator(contentAssistSubjectControl, offset); if (validator instanceof IContextInformationPresenter) return (IContextInformationPresenter) validator; return null; } /** * Returns the characters which when typed by the user should automatically initiate proposing * completions. The position is used to determine the appropriate content assist processor to * invoke. * * @param contentAssistSubjectControl the content assist subject control * @param offset a document offset * @return the auto activation characters * @see IContentAssistProcessor#getCompletionProposalAutoActivationCharacters() * @since 3.0 */ char[] getCompletionProposalAutoActivationCharacters(IContentAssistSubjectControl contentAssistSubjectControl, int offset) { IContentAssistProcessor p= getProcessor(contentAssistSubjectControl, offset); return p != null ? p.getCompletionProposalAutoActivationCharacters() : null; } /** * Returns the characters which when typed by the user should automatically initiate proposing * completions. The position is used to determine the appropriate content assist processor to * invoke. * * @param viewer the text viewer * @param offset a document offset * @return the auto activation characters * @see IContentAssistProcessor#getCompletionProposalAutoActivationCharacters() */ char[] getCompletionProposalAutoActivationCharacters(ITextViewer viewer, int offset) { IContentAssistProcessor p= getProcessor(viewer, offset); return p != null ? p.getCompletionProposalAutoActivationCharacters() : null; } /** * Returns the characters which when typed by the user should automatically initiate the * presentation of context information. The position is used to determine the appropriate * content assist processor to invoke. * * @param viewer the text viewer * @param offset a document offset * @return the auto activation characters * @see IContentAssistProcessor#getContextInformationAutoActivationCharacters() * @since 3.0 */ char[] getContextInformationAutoActivationCharacters(ITextViewer viewer, int offset) { IContentAssistProcessor p= getProcessor(viewer, offset); return p != null ? p.getContextInformationAutoActivationCharacters() : null; } /** * Returns the characters which when typed by the user should automatically initiate the * presentation of context information. The position is used to determine the appropriate * content assist processor to invoke. * * @param contentAssistSubjectControl the content assist subject control * @param offset a document offset * @return the auto activation characters * @see IContentAssistProcessor#getContextInformationAutoActivationCharacters() * @since 3.0 */ char[] getContextInformationAutoActivationCharacters(IContentAssistSubjectControl contentAssistSubjectControl, int offset) { IContentAssistProcessor p= getProcessor(contentAssistSubjectControl, offset); return p != null ? p.getContextInformationAutoActivationCharacters() : null; } @Override public boolean requestWidgetToken(IWidgetTokenOwner owner) { return false; } @Override public boolean requestWidgetToken(IWidgetTokenOwner owner, int priority) { if (priority > WIDGET_PRIORITY) { hide(); return true; } return false; } @Override public boolean setFocus(IWidgetTokenOwner owner) { if (fProposalPopup != null) { fProposalPopup.setFocus(); return fProposalPopup.hasFocus(); } return false; } /** * Hides any open pop-ups. * * @since 3.0 */ protected void hide() { if (fProposalPopup != null) fProposalPopup.hide(); if (fContextInfoPopup != null) fContextInfoPopup.hide(); } // ------ control's size handling dialog settings ------ /** * Tells this information control manager to open the information control with the values * contained in the given dialog settings and to store the control's last valid size in the * given dialog settings. * <p> * Note: This API is only valid if the information control implements * {@link org.eclipse.jface.text.IInformationControlExtension3}. Not following this restriction * will later result in an {@link UnsupportedOperationException}. * </p> * <p> * The constants used to store the values are: * <ul> * <li>{@link ContentAssistant#STORE_SIZE_X}</li> * <li>{@link ContentAssistant#STORE_SIZE_Y}</li> * <li>{@link ContentAssistant#STORE_CONTEXT_SELECTOR_POPUP_SIZE_X}</li> * <li>{@link ContentAssistant#STORE_CONTEXT_SELECTOR_POPUP_SIZE_Y}</li> * </ul> * </p> * * @param dialogSettings the dialog settings * @since 3.0 */ public void setRestoreCompletionProposalSize(IDialogSettings dialogSettings) { Assert.isTrue(dialogSettings != null); fDialogSettings= dialogSettings; } /** * Stores the content assist's proposal pop-up size. */ protected void storeCompletionProposalPopupSize() { if (fDialogSettings == null || fProposalPopup == null) return; Point size= fProposalPopup.getSize(); if (size == null) return; fDialogSettings.put(STORE_SIZE_X, size.x); fDialogSettings.put(STORE_SIZE_Y, size.y); } /** * Stores the content assist's context selector pop-up size. * * @since 3.9 */ protected void storeContextSelectorPopupSize() { if (fDialogSettings == null || fContextInfoPopup == null) return; Point size= fContextInfoPopup.getContextSelectorPopupSize(); if (size == null) return; fDialogSettings.put(STORE_CONTEXT_SELECTOR_POPUP_SIZE_X, size.x); fDialogSettings.put(STORE_CONTEXT_SELECTOR_POPUP_SIZE_Y, size.y); } /** * Restores the content assist's proposal pop-up size. * * @return the stored size or <code>null</code> if none * @since 3.0 */ protected Point restoreCompletionProposalPopupSize() { if (fDialogSettings == null) return null; Point size= new Point(-1, -1); try { size.x= fDialogSettings.getInt(STORE_SIZE_X); size.y= fDialogSettings.getInt(STORE_SIZE_Y); } catch (NumberFormatException ex) { return null; } // sanity check if (size.x == -1 && size.y == -1) return null; Rectangle maxBounds= null; if (fContentAssistSubjectControl != null && Helper.okToUse(fContentAssistSubjectControl.getControl())) maxBounds= fContentAssistSubjectControl.getControl().getDisplay().getBounds(); else { // fallback Display display= Display.getCurrent(); if (display == null) display= Display.getDefault(); if (display != null && !display.isDisposed()) maxBounds= display.getBounds(); } if (size.x > -1 && size.y > -1) { if (maxBounds != null) { size.x= Math.min(size.x, maxBounds.width); size.y= Math.min(size.y, maxBounds.height); } // Enforce an absolute minimal size size.x= Math.max(size.x, 30); size.y= Math.max(size.y, 30); } return size; } /** * Restores the content assist's context selector pop-up size. * * @return the stored size or <code>null</code> if none * @since 3.9 */ protected Point restoreContextSelectorPopupSize() { if (fDialogSettings == null) return null; Point size= new Point(-1, -1); try { size.x= fDialogSettings.getInt(STORE_CONTEXT_SELECTOR_POPUP_SIZE_X); size.y= fDialogSettings.getInt(STORE_CONTEXT_SELECTOR_POPUP_SIZE_Y); } catch (NumberFormatException ex) { return null; } // sanity check if (size.x == -1 && size.y == -1) return null; Rectangle maxBounds= null; Display display= Display.getCurrent(); if (display == null) display= Display.getDefault(); if (display != null && !display.isDisposed()) maxBounds= display.getBounds(); if (size.x > -1 && size.y > -1) { if (maxBounds != null) { size.x= Math.min(size.x, maxBounds.width); size.y= Math.min(size.y, maxBounds.height); } // Enforce an absolute minimal size size.x= Math.max(size.x, 30); size.y= Math.max(size.y, 30); } return size; } /** * Sets the prefix completion property. If enabled, content assist delegates completion to * prefix completion. * * @param enabled <code>true</code> to enable prefix completion, <code>false</code> to * disable */ public void enablePrefixCompletion(boolean enabled) { fIsPrefixCompletionEnabled= enabled; } /** * Returns the prefix completion state. * * @return <code>true</code> if prefix completion is enabled, <code>false</code> otherwise * @since 3.2 */ boolean isPrefixCompletionEnabled() { return fIsPrefixCompletionEnabled; } /** * Returns whether the content assistant proposal popup has the focus. * * @return <code>true</code> if the proposal popup has the focus * @since 3.0 */ public boolean hasProposalPopupFocus() { return fProposalPopup.hasFocus(); } @Override public void addCompletionListener(ICompletionListener listener) { Assert.isLegal(listener != null); fCompletionListeners.add(listener); } @Override public void removeCompletionListener(ICompletionListener listener) { fCompletionListeners.remove(listener); } /** * Fires a session begin event to all registered {@link ICompletionListener}s. * * @param isAutoActivated <code>true</code> if this session was triggered by auto activation * @since 3.2 */ void fireSessionBeginEvent(boolean isAutoActivated) { if (fContentAssistSubjectControlAdapter != null && !isProposalPopupActive()) { IContentAssistProcessor processor= getProcessor(fContentAssistSubjectControlAdapter, fContentAssistSubjectControlAdapter.getSelectedRange().x); ContentAssistEvent event= new ContentAssistEvent(this, processor, isAutoActivated); for (ICompletionListener listener : fCompletionListeners) { listener.assistSessionStarted(event); } } } /** * Fires a session restart event to all registered {@link ICompletionListener}s. * * @since 3.4 */ void fireSessionRestartEvent() { if (fContentAssistSubjectControlAdapter != null) { IContentAssistProcessor processor= getProcessor(fContentAssistSubjectControlAdapter, fContentAssistSubjectControlAdapter.getSelectedRange().x); ContentAssistEvent event= new ContentAssistEvent(this, processor); for (ICompletionListener listener : fCompletionListeners) { if (listener instanceof ICompletionListenerExtension) ((ICompletionListenerExtension)listener).assistSessionRestarted(event); } } } /** * Fires a session end event to all registered {@link ICompletionListener}s. * * @since 3.2 */ void fireSessionEndEvent() { if (fContentAssistSubjectControlAdapter != null) { IContentAssistProcessor processor= getProcessor(fContentAssistSubjectControlAdapter, fContentAssistSubjectControlAdapter.getSelectedRange().x); ContentAssistEvent event= new ContentAssistEvent(this, processor); for (ICompletionListener listener : fCompletionListeners) { listener.assistSessionEnded(event); } } } @Override public void setRepeatedInvocationMode(boolean cycling) { fIsRepetitionMode= cycling; } /** * Returns <code>true</code> if repeated invocation mode is enabled, <code>false</code> * otherwise. * * @return <code>true</code> if repeated invocation mode is enabled, <code>false</code> * otherwise * @since 3.2 */ boolean isRepeatedInvocationMode() { return fIsRepetitionMode; } @Override public void setShowEmptyList(boolean showEmpty) { fShowEmptyList= showEmpty; } /** * Returns <code>true</code> if empty lists should be displayed, <code>false</code> * otherwise. * * @return <code>true</code> if empty lists should be displayed, <code>false</code> * otherwise * @since 3.2 */ boolean isShowEmptyList() { return fShowEmptyList; } @Override public void setStatusLineVisible(boolean show) { fIsStatusLineVisible= show; if (fProposalPopup != null) fProposalPopup.setStatusLineVisible(show); } /** * Returns <code>true</code> if a message line should be displayed, <code>false</code> * otherwise. * * @return <code>true</code> if a message line should be displayed, <code>false</code> * otherwise * @since 3.2 */ boolean isStatusLineVisible() { return fIsStatusLineVisible; } @Override public void setStatusMessage(String message) { Assert.isLegal(message != null); fMessage= message; if (fProposalPopup != null) fProposalPopup.setMessage(message); } /** * Returns the affordance caption for the cycling affordance at the bottom of the pop-up. * * @return the affordance caption for the cycling affordance at the bottom of the pop-up * @since 3.2 */ String getStatusMessage() { return fMessage; } @Override public void setEmptyMessage(String message) { Assert.isLegal(message != null); if (fProposalPopup != null) fProposalPopup.setEmptyMessage(message); } /** * Fires a selection event, see {@link ICompletionListener}. * * @param proposal the selected proposal, possibly <code>null</code> * @param smartToggle true if the smart toggle is on * @since 3.2 */ void fireSelectionEvent(ICompletionProposal proposal, boolean smartToggle) { for (ICompletionListener listener : fCompletionListeners) { listener.selectionChanged(proposal, smartToggle); } } /** * Fires an event after applying a proposal, see {@link ICompletionListenerExtension2}. * * @param proposal the applied proposal * @since 3.8 */ void fireAppliedEvent(ICompletionProposal proposal) { for (ICompletionListener listener : fCompletionListeners) { if (listener instanceof ICompletionListenerExtension2) ((ICompletionListenerExtension2)listener).applied(proposal); } } /* * @see org.eclipse.jface.text.contentassist.IContentAssistantExtension3#setInvocationTrigger(org.eclipse.jface.bindings.keys.KeySequence) * @since 3.2 */ @Override public void setRepeatedInvocationTrigger(KeySequence sequence) { fRepeatedInvocationKeySequence= sequence; } /** * Returns the repeated invocation key sequence. * * @return the repeated invocation key sequence or <code>null</code>, if none * @since 3.2 */ KeySequence getRepeatedInvocationKeySequence() { return fRepeatedInvocationKeySequence; } /** * Returns whether proposal popup is active. * * @return <code>true</code> if the proposal popup is active, <code>false</code> otherwise * @since 3.4 */ protected boolean isProposalPopupActive(){ return fProposalPopup != null && fProposalPopup.isActive(); } /** * Returns whether the context information popup is active. * * @return <code>true</code> if the context information popup is active, <code>false</code> otherwise * @since 3.4 */ protected boolean isContextInfoPopupActive(){ return fContextInfoPopup != null && fContextInfoPopup.isActive(); } /** * {@inheritDoc} * * @since 3.4 */ @Override public final IHandler getHandler(String commandId) { if (fHandlers == null) throw new IllegalStateException(); IHandler handler= fHandlers.get(commandId); if (handler != null) return handler; Assert.isLegal(false); return null; } /** * Registers the given handler under the given command identifier. * * @param commandId the command identifier * @param handler the handler * @since 3.4 */ protected final void registerHandler(String commandId, IHandler handler) { if (fHandlers == null) fHandlers= new HashMap<>(2); fHandlers.put(commandId, handler); } /** * Tells whether the support for colored labels is enabled. * * @return <code>true</code> if the support for colored labels is enabled, <code>false</code> otherwise * @since 3.4 */ boolean isColoredLabelsSupportEnabled() { return fIsColoredLabelsSupportEnabled; } /** * Enables the support for colored labels in the proposal popup. * <p>Completion proposals can implement {@link ICompletionProposalExtension6} * to provide colored proposal labels.</p> * * @param isEnabled if <code>true</code> the support for colored labels is enabled in the proposal popup * @since 3.4 */ public void enableColoredLabels(boolean isEnabled) { fIsColoredLabelsSupportEnabled= isEnabled; } /** * Sets the proposal sorter. * * @param sorter the sorter to be used, or <code>null</code> if no sorting is requested * @since 3.8 */ public void setSorter(ICompletionProposalSorter sorter) { fSorter= sorter; if (fProposalPopup != null) { fProposalPopup.setSorter(fSorter); } } }