/** * Copyright (c) 2005-2006 Aptana, Inc. * * 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. If redistributing this code, * this entire header must remain intact. */ package com.aptana.ide.editors.unified.contentassist; /*********************************************************************************************************************** * Copyright (c) 2000, 2005 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 **********************************************************************************************************************/ import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.Map.Entry; import org.eclipse.jface.contentassist.IContentAssistSubjectControl; import org.eclipse.jface.contentassist.ISubjectControlContentAssistProcessor; import org.eclipse.jface.dialogs.IDialogSettings; import org.eclipse.jface.text.Assert; 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.ITypedRegion; 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; import org.eclipse.jface.text.contentassist.ICompletionProposal; import org.eclipse.jface.text.contentassist.IContentAssistProcessor; import org.eclipse.jface.text.contentassist.IContentAssistant; import org.eclipse.jface.text.contentassist.IContentAssistantExtension; import org.eclipse.jface.text.contentassist.IContextInformation; import org.eclipse.jface.text.contentassist.IContextInformationPresenter; import org.eclipse.jface.text.contentassist.IContextInformationValidator; import org.eclipse.swt.SWT; import org.eclipse.swt.SWTError; import org.eclipse.swt.custom.StyledText; 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.KeyListener; import org.eclipse.swt.events.MouseEvent; import org.eclipse.swt.events.MouseListener; 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.Shell; import org.eclipse.swt.widgets.Widget; import com.aptana.ide.core.StringUtils; import com.aptana.ide.editors.unified.EditorFileContext; import com.aptana.ide.editors.unified.IUnifiedEditor; import com.aptana.ide.editors.unified.IUnifiedEditorContributor; /** * The standard implementation of the <code>IContentAssistant</code> interface. Usually, clients instantiate this * class and configure it before using it. */ public class ContentAssistant implements IContentAssistant, IContentAssistantExtension, IWidgetTokenKeeper, IWidgetTokenKeeperExtension, IUnifiedContentAssistant { /** * 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); } } /** * @see org.eclipse.swt.events.ControlListener#controlResized(org.eclipse.swt.events.ControlEvent) */ public void controlResized(ControlEvent e) { hide(); } /** * @see org.eclipse.swt.events.ControlListener#controlMoved(org.eclipse.swt.events.ControlEvent) */ public void controlMoved(ControlEvent e) { hide(); } /** * @see org.eclipse.swt.events.MouseListener#mouseDown(org.eclipse.swt.events.MouseEvent) */ public void mouseDown(MouseEvent e) { hide(); } /** * @see org.eclipse.swt.events.MouseListener#mouseUp(org.eclipse.swt.events.MouseEvent) */ public void mouseUp(MouseEvent e) { } /** * @see org.eclipse.swt.events.MouseListener#mouseDoubleClick(org.eclipse.swt.events.MouseEvent) */ public void mouseDoubleClick(MouseEvent e) { hide(); } /** * @see org.eclipse.swt.events.FocusListener#focusGained(org.eclipse.swt.events.FocusEvent) */ public void focusGained(FocusEvent e) { } /** * @see org.eclipse.swt.events.FocusListener#focusLost(org.eclipse.swt.events.FocusEvent) */ public void focusLost(FocusEvent e) { Control control = fControl; if (Helper.okToUse(control)) { Display d = control.getDisplay(); if (d != null) { d.asyncExec(new Runnable() { public void run() { if (!fProposalPopup.hasFocus() && (fContextInfoPopup == null || !fContextInfoPopup.hasFocus())) { hide(); } } }); } } } /** * @see org.eclipse.swt.events.DisposeListener#widgetDisposed(org.eclipse.swt.events.DisposeEvent) */ public void widgetDisposed(DisposeEvent e) { /* * 1GGYYWK: ITPJUI:ALL - Dismissing editor with code assist up causes lots of Internal Errors */ hide(); } /** * @see org.eclipse.jface.text.IViewportListener#viewportChanged(int) */ 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 code assistant. If enabled, the implementation utilizes a thread to watch for * input characters matching the activation characters specified by the code assist processor, and if detected, will * wait the indicated delay interval before activating the code assistant. */ class AutoAssistListener extends KeyAdapter implements KeyListener, Runnable, VerifyKeyListener { private Thread fThread; private boolean fIsReset = false; private Object fMutex = new Object(); private int fShowStyle; private static final int SHOW_PROPOSALS = 1; private static final int SHOW_CONTEXT_INFO = 2; /** * AutoAssistListener */ protected AutoAssistListener() { } /** * start * * @param showStyle */ protected void start(int showStyle) { fShowStyle = showStyle; fThread = new Thread(this, "Aptana: ContentAssistant.assist_delay_timer_name"); //$NON-NLS-1$ fThread.start(); } /** * @see java.lang.Runnable#run() */ 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; } /** * reset * * @param showStyle */ protected void reset(int showStyle) { synchronized (fMutex) { fShowStyle = showStyle; fIsReset = true; fMutex.notifyAll(); } } /** * stop */ protected void stop() { Thread threadToStop = fThread; if (threadToStop != null && threadToStop.isAlive()) { threadToStop.interrupt(); } } /** * contains * * @param characters * @param character * @return boolean */ private boolean contains(char[] characters, char character) { if (characters != null) { for (int i = 0; i < characters.length; i++) { if (character == characters[i]) { return true; } } } return false; } /** * @see org.eclipse.swt.events.KeyListener#keyPressed(org.eclipse.swt.events.KeyEvent) */ 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; } // 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) && !fProposalPopup.isActive()) { showStyle = SHOW_PROPOSALS; fProposalPopup.setActivationKey(e.character); } else { activation = fContentAssistSubjectControlAdapter.getContextInformationAutoActivationCharacters( ContentAssistant.this, pos); if (contains(activation, e.character) && fContextInfoPopup != null && !fContextInfoPopup.isActive()) { showStyle = SHOW_CONTEXT_INFO; fContextInfoPopup.setActivationKey(e.character); } else { stop(); return; } } if (fThread != null && fThread.isAlive()) { reset(showStyle); } else { start(showStyle); } } /** * @see org.eclipse.swt.custom.VerifyKeyListener#verifyKey(org.eclipse.swt.events.VerifyEvent) */ public void verifyKey(VerifyEvent event) { keyPressed(event); } /** * showAssist * * @param showStyle */ protected void showAssist(final int showStyle) { final Display d = fContentAssistSubjectControlAdapter.getControl().getDisplay(); if (d != null) { try { d.syncExec(new Runnable() { public void run() { if (fViewer == null || editor == null) { return; } StyledText styledText = fViewer.getTextWidget(); EditorFileContext context = editor.getFileContext(); IUnifiedEditorContributor baseContrib = editor.getBaseContributor(); if (context == null || baseContrib == null) { return; } ITypedRegion reg = context.getPartitionAtOffset(styledText.getCaretOffset()); if (reg == null) { return; } final String contentType = reg.getType(); IUnifiedEditorContributor contributor = baseContrib.findChildContributor(contentType); if (contributor != null && contributor.isAutoActivateContentAssist()) { Control c = d.getFocusControl(); if (c == null) { return; } if (showStyle == SHOW_PROPOSALS) { fProposalPopup.showProposals(true); } else if (showStyle == SHOW_CONTEXT_INFO && fContextInfoPopup != null) { fContextInfoPopup.showContextProposals(true); } } } }); } catch (SWTError e) { } } } } /** * The layout manager layouts the various windows associated with the code assistant based on the settings of the * code assistant. */ class LayoutManager implements Listener { // Presentation types. /** The presentation type for the proposal selection popup. */ public static final int LAYOUT_PROPOSAL_SELECTOR = 0; /** The presentation type for the context selection popup. */ public static final int LAYOUT_CONTEXT_SELECTOR = 1; /** The presentation type for the context information hover . */ public static final int LAYOUT_CONTEXT_INFO_POPUP = 2; int fContextType = LAYOUT_CONTEXT_SELECTOR; Shell[] fShells = new Shell[3]; Object[] fPopups = new Object[3]; /** * add * * @param popup * @param shell * @param type * @param offset */ 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); } /** * checkType * * @param type */ protected void checkType(int type) { Assert.isTrue(type == LAYOUT_PROPOSAL_SELECTOR || type == LAYOUT_CONTEXT_SELECTOR || type == LAYOUT_CONTEXT_INFO_POPUP); } /** * @see org.eclipse.swt.widgets.Listener#handleEvent(org.eclipse.swt.widgets.Event) */ 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; default: break; } } /** * getShellType * * @param shell * @return int */ protected int getShellType(Widget shell) { for (int i = 0; i < fShells.length; i++) { if (fShells[i] == shell) { return i; } } return -1; } /** * layout * * @param type * @param 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; default: break; } } /** * layoutProposalSelector * * @param offset */ 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]; Point p = getAboveLocation(shell, offset); if (p.y <= 0) { p = getStackedLocation(shell, parent); } shell.setLocation(p); } 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]; offset = findOffsetOfFirstCharacter(fContentAssistSubjectControlAdapter, offset); Point proposedLocation = getBelowLocation(shell, offset, true); // NOTE: this gives the position at the top of the line at the current offset Point cursorLocation = fContentAssistSubjectControlAdapter.getLocationAtOffset(offset); // convert to screen coordinates cursorLocation = fContentAssistSubjectControlAdapter.getControl().toDisplay(cursorLocation); int lineHeight = fContentAssistSubjectControlAdapter.getLineHeight(); // bounding box of the proposal selector Rectangle shellBounds = shell.getBounds(); int shellBoundsBottom = proposedLocation.y + shellBounds.height; if (proposedLocation.y <= (cursorLocation.y + lineHeight) && cursorLocation.y < shellBoundsBottom) { proposedLocation = getAboveLocation(shell, offset); } proposedLocation.x -= 32; shell.setLocation(proposedLocation); // clip right side of popup to right side of screen, if necessary Rectangle bounds = Display.getCurrent().getBounds(); int screenRight = bounds.x + bounds.width; int proposalRight = proposedLocation.x + shell.getSize().x; if (proposalRight > screenRight) { int newWidth = shell.getSize().x - (proposalRight - screenRight); shell.setSize(newWidth, shell.getSize().y); } } else { 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.setLocation(getBelowLocation(shell, offset, true)); break; } case PROPOSAL_OVERLAY: { // Overlay the tip selector with the proposal selector. Shell shell = fShells[LAYOUT_PROPOSAL_SELECTOR]; shell.setLocation(getBelowLocation(shell, offset, true)); 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; } default: break; } } } private int findOffsetOfFirstCharacter(ContentAssistSubjectControlAdapter adapter, int offset) { int origOffset = offset; offset--; while (offset >= 0) { char c; try { c = adapter.getDocument().getChar(offset); } catch (BadLocationException e) { return origOffset; } if (Character.isWhitespace(c) || c == '.' || c == ',' || c == '(' || c == ':' || c == '#') { return offset + 1; } else { offset--; } } return offset; } /** * layoutContextSelector * * @param offset */ protected void layoutContextSelector(int offset) { // Always place the context selector beneath the cursor line. Shell shell = fShells[LAYOUT_CONTEXT_SELECTOR]; shell.setLocation(getBelowLocation(shell, offset, true)); 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]; Point p = getAboveLocation(shell, offset); if (p.y <= 0) { p = getStackedLocation(shell, parent); } shell.setLocation(p); break; } default: break; } } } /** * layoutContextInfoPopup * * @param offset */ 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.setLocation(getAboveLocation(shell, offset)); break; } case CONTEXT_INFO_BELOW: { // Place the popup beneath the cursor line. Shell parent = fShells[LAYOUT_CONTEXT_INFO_POPUP]; parent.setLocation(getBelowLocation(parent, offset, false)); if (Helper.okToUse(fShells[LAYOUT_PROPOSAL_SELECTOR])) { // Stack the proposal selector beneath the context info popup. Shell shell = fShells[LAYOUT_PROPOSAL_SELECTOR]; Point p = getAboveLocation(shell, offset); if (p.y <= 0) { p = getStackedLocation(shell, parent); } shell.setLocation(p); } break; } default: break; } } /** * shiftHorizontalLocation * * @param location * @param shellBounds * @param displayBounds */ protected void shiftHorizontalLocation(Point location, Rectangle shellBounds, Rectangle displayBounds) { if (location.x + shellBounds.width > displayBounds.width) { location.x = displayBounds.width - shellBounds.width; } if (location.x < displayBounds.x) { location.x = displayBounds.x; } } /** * shiftVerticalLocation * * @param location * @param shellBounds * @param displayBounds * @param keepOnScreen */ protected void shiftVerticalLocation(Point location, Rectangle shellBounds, Rectangle displayBounds, boolean keepOnScreen) { if (keepOnScreen) { // Move to the bottom of the screen if we hang over it if (location.y + shellBounds.height > displayBounds.height) { location.y = displayBounds.height - shellBounds.height; } } // Move to the top of the screen if we're above it if (location.y < displayBounds.y) { location.y = displayBounds.y; } } /** * getAboveLocation * * @param shell * @param offset * @return Point */ protected Point getAboveLocation(Shell shell, int offset) { Point location = fContentAssistSubjectControlAdapter.getLocationAtOffset(offset); location = fContentAssistSubjectControlAdapter.getControl().toDisplay(location); Rectangle shellBounds = shell.getBounds(); Rectangle displayBounds = shell.getDisplay().getClientArea(); location.y = location.y - (shellBounds.height + fContentAssistSubjectControlAdapter.getLineHeight()); shiftHorizontalLocation(location, shellBounds, displayBounds); shiftVerticalLocation(location, shellBounds, displayBounds, true); return location; } /** * getBelowLocation * * @param shell * @param offset * @param keepOnScreen * @return Point */ protected Point getBelowLocation(Shell shell, int offset, boolean keepOnScreen) { // NOTE: this gives the position at the top of the line at the current offset Point location = fContentAssistSubjectControlAdapter.getLocationAtOffset(offset); // make sure we're not off the left or top of the screen if (location.x < 0) { location.x = 0; } if (location.y < 0) { location.y = 0; } // convert to screen coordinates location = fContentAssistSubjectControlAdapter.getControl().toDisplay(location); // bounding box of the proposal selector Rectangle shellBounds = shell.getBounds(); // bounding box of the display (screen) Rectangle displayBounds = shell.getDisplay().getClientArea(); // Move down one line and add padding location.y = location.y + fContentAssistSubjectControlAdapter.getLineHeight() + 5; // shiftHorizontalLocation(location, shellBounds, displayBounds); shiftVerticalLocation(location, shellBounds, displayBounds, keepOnScreen); return location; } /** * getStackedLocation * * @param shell * @param parent * @return Point */ protected Point getStackedLocation(Shell shell, Shell parent) { Point p = parent.getLocation(); Point parentSize = parent.getSize(); // p.x += parentSize.x / 4; p.y += parentSize.y + 5; // p= parent.toDisplay(p); Rectangle shellBounds = shell.getBounds(); Rectangle displayBounds = shell.getDisplay().getClientArea(); shiftHorizontalLocation(p, shellBounds, displayBounds); shiftVerticalLocation(p, shellBounds, displayBounds, true); return p; } /** * adjustListeners * * @param type */ 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; default: break; } } } /** * 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) */ public void verifyKey(VerifyEvent e) { IContentAssistListener[] listeners = (IContentAssistListener[]) 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 org.eclipse.jface.text.IEventConsumer#processEvent(org.eclipse.swt.events.VerifyEvent) */ public void processEvent(VerifyEvent event) { installKeyListener(); IContentAssistListener[] listeners = (IContentAssistListener[]) fListeners.clone(); for (int i = 0; i < listeners.length; i++) { if (listeners[i] != null) { listeners[i].processEvent(event); if (!event.doit) { return; } } } } } /** * Dialog store constants. * * @since 3.0 */ public static final String STORE_SIZE_X = "size.x"; //$NON-NLS-1$ /** * STORE_SIZE_Y */ public static final String STORE_SIZE_Y = "size.y"; //$NON-NLS-1$ // Content-Assist Listener types static final int CONTEXT_SELECTOR = 0; static final int PROPOSAL_SELECTOR = 1; static final 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 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 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; private LayoutManager fLayoutManager; private AutoAssistListener fAutoAssistListener; private InternalListener fInternalListener; private CompletionProposalPopup fProposalPopup; private ContextInformationPopup fContextInfoPopup; private IUnifiedEditor editor; /** * Flag which tells whether a verify key listener is hooked. * * @since 3.0 */ private boolean fVerifyKeyListenerHooked = false; private IContentAssistListener[] fListeners = new IContentAssistListener[4]; /** * The code assist subject control. * * @since 3.0 */ private IContentAssistSubjectControl fContentAssistSubjectControl; /** * The code 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; /** * Creates a new code assistant. The code 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 code * assistant is activated after a 500 milli-seconds delay. It uses the default partitioning. */ public ContentAssistant() { fPartitioning = IDocumentExtension3.DEFAULT_PARTITIONING; } /** * Sets the document partitioning this code assistant is using. * * @param partitioning * the document partitioning for this code assistant * @since 3.0 */ public void setDocumentPartitioning(String partitioning) { Assert.isNotNull(partitioning); fPartitioning = partitioning; } /** * @see org.eclipse.jface.text.contentassist.IContentAssistantExtension#getDocumentPartitioning() */ public String getDocumentPartitioning() { return fPartitioning; } /** * Registers a given code 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 code 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, processor); } } /** * @see org.eclipse.jface.text.contentassist.IContentAssistant#getContentAssistProcessor(java.lang.String) */ public IContentAssistProcessor getContentAssistProcessor(String contentType) { if (fProcessors == null) { return null; } return (IContentAssistProcessor) fProcessors.get(contentType); } /** * 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 StringUtils.EMPTY; } StringBuffer buf = new StringBuffer(5); Iterator iter = fProcessors.entrySet().iterator(); while (iter.hasNext()) { Entry entry = (Entry) iter.next(); IContentAssistProcessor processor = (IContentAssistProcessor) entry.getValue(); char[] triggers = processor.getCompletionProposalAutoActivationCharacters(); if (triggers != null) { buf.append(triggers); } triggers = processor.getContextInformationAutoActivationCharacters(); if (triggers != null) { buf.append(triggers); } } return buf.toString(); } /** * Enables the code 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 code assistant's auto insertion mode. If enabled, the code 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 code 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 = new AutoAssistListener(); // 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; } } /** * Sets the delay after which the code assistant is automatically invoked if the cursor is behind an auto activation * character. * * @param delay * the auto activation delay */ public void setAutoActivationDelay(int delay) { fAutoActivationDelay = delay; } /** * 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. * * @param background * the background color * @since 2.0 */ public void setProposalSelectorBackground(Color background) { fProposalSelectorBackground = background; } /** * Returns the background of the proposal selector. * * @return the background of the proposal selector * @since 2.0 */ Color getProposalSelectorBackground() { return fProposalSelectorBackground; } /** * Sets the proposal's foreground color. * * @param foreground * the foreground color * @since 2.0 */ public void setProposalSelectorForeground(Color foreground) { fProposalSelectorForeground = foreground; } /** * Returns the foreground of the proposal selector. * * @return the foreground of the proposal selector * @since 2.0 */ Color getProposalSelectorForeground() { return fProposalSelectorForeground; } /** * 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 com.aptana.ide.editors.unified.contentassist.IUnifiedContentAssistant#setEditor(com.aptana.ide.editors.unified.IUnifiedEditor) */ public void setEditor(IUnifiedEditor editor) { this.editor = editor; } /** * install * * @param contentAssistSubjectControl */ protected void install(IContentAssistSubjectControl contentAssistSubjectControl) { fContentAssistSubjectControl = contentAssistSubjectControl; fContentAssistSubjectControlAdapter = new ContentAssistSubjectControlAdapter(fContentAssistSubjectControl); install(); } /** * @see org.eclipse.jface.text.contentassist.IContentAssistant#install(org.eclipse.jface.text.ITextViewer) */ public void install(ITextViewer textViewer) { fViewer = textViewer; fContentAssistSubjectControlAdapter = new ContentAssistSubjectControlAdapter(fViewer); install(); } /** * install */ protected void install() { fLayoutManager = new LayoutManager(); fInternalListener = new InternalListener(); AdditionalInfoController controller = null; if (fInformationControlCreator != null) { int delay = fAutoActivationDelay; if (delay == 0) { delay = DEFAULT_AUTO_ACTIVATION_DELAY; } delay = Math.round(delay * 1.5f); controller = new AdditionalInfoController(fInformationControlCreator, delay); } fContextInfoPopup = fContentAssistSubjectControlAdapter.createContextInfoPopup(this); fProposalPopup = fContentAssistSubjectControlAdapter.createCompletionProposalPopup(this, controller); manageAutoActivation(fIsAutoActivated); } /** * @see org.eclipse.jface.text.contentassist.IContentAssistant#uninstall() */ public void uninstall() { hide(); if (fProposalPopup != null) { fProposalPopup.disposePopup(); } manageAutoActivation(false); if (fCloser != null) { fCloser.uninstall(); fCloser = 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 code 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); } /** * 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); } default: break; } return true; } /** * Registers a code 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 */ public 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 code 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(); if (fContentAssistSubjectControlAdapter != null) { 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 org.eclipse.jface.text.contentassist.IContentAssistant#showPossibleCompletions() */ public String showPossibleCompletions() { promoteKeyListener(); if (fIsPrefixCompletionEnabled) { return fProposalPopup.incrementalComplete(); } return fProposalPopup.showProposals(false); } /** * @see org.eclipse.jface.text.contentassist.IContentAssistantExtension#completePrefix() */ public String completePrefix() { promoteKeyListener(); return fProposalPopup.incrementalComplete(); } /** * Callback to signal this code assistant that the presentation of the possible completions has been stopped. * * @since 2.1 */ protected void possibleCompletionsClosed() { storeCompletionProposalPopupSize(); } /** * @see org.eclipse.jface.text.contentassist.IContentAssistant#showContextInformation() */ public String showContextInformation() { promoteKeyListener(); if (fContextInfoPopup != null) { return fContextInfoPopup.showContextProposals(false); } return null; } /** * Callback to signal this code 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 code assist error message. * * @return an error message or <code>null</code> if no error has occurred */ String getErrorMessage() { return fLastErrorMessage; } /** * Returns the code 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 code assist processor for the content type of the specified document position. * * @param contentAssistSubjectControl * the code 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 code assist processor to invoke. * * @param contentAssistSubjectControl * the code assist subject control * @param offset * a document offset * @return an array of completion proposals * @see IContentAssistProcessor#computeCompletionProposals(ITextViewer, int) * @since 3.0 */ ICompletionProposal[] computeCompletionProposals(IContentAssistSubjectControl contentAssistSubjectControl, int offset, char activationChar) { fLastErrorMessage = null; ICompletionProposal[] result = null; IContentAssistProcessor p = getProcessor(contentAssistSubjectControl, offset); if (p instanceof ISubjectControlContentAssistProcessor) { result = ((ISubjectControlContentAssistProcessor) p).computeCompletionProposals( contentAssistSubjectControl, offset); fLastErrorMessage = p.getErrorMessage(); } return result; } /** * Returns an array of completion proposals computed based on the specified document position. The position is used * to determine the appropriate code assist processor to invoke. * * @param viewer * the viewer for which to compute the proposals * @param offset * a document offset * @param autoActivated * determiens whetehr we were autoActivated or not * @return an array of completion proposals * @see IContentAssistProcessor#computeCompletionProposals(ITextViewer, int) */ ICompletionProposal[] computeCompletionProposals(ITextViewer viewer, int offset, char activationChar, boolean autoActivated) { fLastErrorMessage = null; ICompletionProposal[] result = null; IContentAssistProcessor p = getProcessor(viewer, offset); if (p != null) { if (p instanceof IUnifiedContentAssistProcessor) { result = ((IUnifiedContentAssistProcessor) p).computeCompletionProposals(viewer, offset, activationChar, autoActivated); } else { result = p.computeCompletionProposals(viewer, offset); } fLastErrorMessage = p.getErrorMessage(); } return result; } /** * Returns an array of context information objects computed based on the specified document position. The position * is used to determine the appropriate code 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(ITextViewer viewer, int offset) { fLastErrorMessage = null; IContextInformation[] result = null; IContentAssistProcessor p = getProcessor(viewer, offset); if (p != null) { result = p.computeContextInformation(viewer, offset); fLastErrorMessage = p.getErrorMessage(); } return result; } /** * Returns an array of context information objects computed based on the specified document position. The position * is used to determine the appropriate code assist processor to invoke. * * @param contentAssistSubjectControl * the code 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(IContentAssistSubjectControl contentAssistSubjectControl, int offset) { fLastErrorMessage = null; IContextInformation[] result = null; IContentAssistProcessor p = getProcessor(contentAssistSubjectControl, offset); if (p instanceof ISubjectControlContentAssistProcessor) { result = ((ISubjectControlContentAssistProcessor) p).computeContextInformation(contentAssistSubjectControl, offset); fLastErrorMessage = p.getErrorMessage(); } return result; } /** * 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 code 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 code assist processor to * invoke. * * @param contentAssistSubjectControl * the code 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 code 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 code assist processor to invoke. * * @param contentAssistSubjectControl * the code 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 code assist processor to invoke. * * @param contentAssistSubjectControl * the code 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 code 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 code 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 code assist processor to invoke. * * @param contentAssistSubjectControl * the code 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; } /** * @see org.eclipse.jface.text.IWidgetTokenKeeper#requestWidgetToken(org.eclipse.jface.text.IWidgetTokenOwner) */ public boolean requestWidgetToken(IWidgetTokenOwner owner) { return false; } /** * @see org.eclipse.jface.text.IWidgetTokenKeeperExtension#requestWidgetToken(org.eclipse.jface.text.IWidgetTokenOwner, * int) */ public boolean requestWidgetToken(IWidgetTokenOwner owner, int priority) { if (priority > WIDGET_PRIORITY) { hide(); return true; } return false; } /** * @see org.eclipse.jface.text.IWidgetTokenKeeperExtension#setFocus(org.eclipse.jface.text.IWidgetTokenOwner) */ public boolean setFocus(IWidgetTokenOwner owner) { if (fProposalPopup != null) { fProposalPopup.setFocus(); return fProposalPopup.hasFocus(); } return false; } /** * Hides any open pop-ups. * * @since 3.0 */ public 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> * </ul> * </p> * * @param dialogSettings * @since 3.0 */ public void setRestoreCompletionProposalSize(IDialogSettings dialogSettings) { Assert.isTrue(dialogSettings != null); fDialogSettings = dialogSettings; } /** * Stores the code assist pop-up's 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); } /** * Restores the code assist pop-up's size. * * @return the stored size * @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) { size.x = -1; size.y = -1; } // sanity check if (size.x == -1 && size.y == -1) { return null; } Rectangle maxBounds = null; if (fContentAssistSubjectControl != null && !fContentAssistSubjectControl.getControl().isDisposed()) { 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; } /** * Sets the prefix completion property. If enabled, code 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 whether the code 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(); } }