/**
* 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.ArrayList;
import java.util.List;
import org.eclipse.core.runtime.Platform;
import org.eclipse.jface.contentassist.IContentAssistSubjectControl;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.jface.preference.JFacePreferences;
import org.eclipse.jface.resource.JFaceResources;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.DocumentEvent;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IDocumentListener;
import org.eclipse.jface.text.IEditingSupport;
import org.eclipse.jface.text.IEditingSupportRegistry;
import org.eclipse.jface.text.IRegion;
import org.eclipse.jface.text.IRewriteTarget;
import org.eclipse.jface.text.ITextViewer;
import org.eclipse.jface.text.ITextViewerExtension;
import org.eclipse.jface.text.TextUtilities;
import org.eclipse.jface.text.contentassist.ICompletionProposal;
import org.eclipse.jface.text.contentassist.ICompletionProposalExtension;
import org.eclipse.jface.text.contentassist.ICompletionProposalExtension2;
import org.eclipse.jface.text.contentassist.ICompletionProposalExtension3;
import org.eclipse.jface.text.contentassist.ICompletionProposalExtension4;
import org.eclipse.jface.text.contentassist.IContextInformation;
import org.eclipse.jface.util.IPropertyChangeListener;
import org.eclipse.jface.util.PropertyChangeEvent;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.BusyIndicator;
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.KeyEvent;
import org.eclipse.swt.events.KeyListener;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.events.VerifyEvent;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Table;
import org.eclipse.swt.widgets.TableColumn;
import org.eclipse.swt.widgets.TableItem;
import com.aptana.ide.core.StringUtils;
import com.aptana.ide.editors.UnifiedEditorsPlugin;
import com.aptana.ide.editors.preferences.IPreferenceConstants;
/**
* This class is used to present proposals to the user. If additional information exists for a proposal, then selecting
* that proposal will result in the information being displayed in a secondary window.
*
* @see org.eclipse.jface.text.contentassist.ICompletionProposal
*/
public class CompletionProposalPopup implements IContentAssistListener
{
/**
* PROPOSAL_ITEM_HEIGHT
*/
public static final int PROPOSAL_ITEM_HEIGHT = 7;
/**
* ProposalSelectionListener
*
* @author Ingo Muschenetz
*/
private final class ProposalSelectionListener implements KeyListener
{
/**
* @see org.eclipse.swt.events.KeyListener#keyPressed(org.eclipse.swt.events.KeyEvent)
*/
public void keyPressed(KeyEvent e)
{
if (!Helper.okToUse(fProposalShell))
{
return;
}
if (e.character == 0 && e.keyCode == SWT.MOD1)
{
// http://dev.eclipse.org/bugs/show_bug.cgi?id=34754
int index = fProposalTable.getSelectionIndex();
if (index >= 0)
{
selectProposal(index, true, true);
}
// else
// {
// fProposalTable.setTopIndex(0);
// }
}
}
/**
* @see org.eclipse.swt.events.KeyListener#keyReleased(org.eclipse.swt.events.KeyEvent)
*/
public void keyReleased(KeyEvent e)
{
if (!Helper.okToUse(fProposalShell))
{
return;
}
if (e.character == 0 && e.keyCode == SWT.MOD1)
{
// http://dev.eclipse.org/bugs/show_bug.cgi?id=34754
int index = fProposalTable.getSelectionIndex();
if (index >= 0)
{
selectProposal(index, false, true);
}
// else
// {
// fProposalTable.setTopIndex(0);
// }
}
}
}
/** The associated text viewer. */
private ITextViewer fViewer;
/** The associated code assistant. */
private ContentAssistant fContentAssistant;
/** The used additional info controller. */
private AdditionalInfoController fAdditionalInfoController;
/** The closing strategy for this completion proposal popup. */
private PopupCloser fPopupCloser = new PopupCloser();
/** The popup shell. */
private Shell fProposalShell;
/** The proposal table. */
private Table fProposalTable;
/** Indicates whether a completion proposal is being inserted. */
private boolean fInserting = false;
/** The key listener to control navigation. */
private ProposalSelectionListener fKeyListener;
/** List of document events used for filtering proposals. */
private List<DocumentEvent> fDocumentEvents = new ArrayList<DocumentEvent>();
/** Listener filling the document event queue. */
private IDocumentListener fDocumentListener;
/** Reentrance count for filtered proposals. */
private long fInvocationCounter = 0;
/** The filter list of proposals. */
private ICompletionProposal[] fFilteredProposals;
/** The computed list of proposals. */
private ICompletionProposal[] fComputedProposals;
/** The offset for which the proposals have been computed. */
private int fInvocationOffset;
/** The offset for which the computed proposals have been filtered. */
private int fFilterOffset;
/** The key last pressed to trigger activation * */
private char fActivationKey;
/** Do we insert the selected proposal on tab? * */
private boolean _insertOnTab;
/** The number of user agents shown */
private int fUserAgents = 0;
/**
* The most recently selected proposal.
*
* @since 3.0
*/
private ICompletionProposal fLastProposal;
/**
* The code assist subject control. This replaces <code>fViewer</code>
*
* @since 3.0
*/
private IContentAssistSubjectControl fContentAssistSubjectControl;
/**
* The code assist subject control adapter. This replaces <code>fViewer</code>
*
* @since 3.0
*/
private ContentAssistSubjectControlAdapter fContentAssistSubjectControlAdapter;
/**
* Remembers the size for this completion proposal popup.
*
* @since 3.0
*/
private Point fSize;
/**
* Editor helper that communicates that the completion proposal popup may have focus while the 'logical focus' is
* still with the editor.
*
* @since 3.1
*/
private IEditingSupport fFocusHelper;
/**
* Set to true by {@link #computeFilteredProposals(int, DocumentEvent)} if the returned proposals are a subset of
* {@link #fFilteredProposals}, <code>false</code> if not.
*
* @since 3.1
*/
private boolean fIsFilteredSubset;
/**
* Creates a new completion proposal popup for the given elements.
*
* @param contentAssistant
* the code assistant feeding this popup
* @param viewer
* the viewer on top of which this popup appears
* @param infoController
* the information control collaborating with this popup
* @since 2.0
*/
public CompletionProposalPopup(ContentAssistant contentAssistant, ITextViewer viewer,
AdditionalInfoController infoController)
{
fContentAssistant = contentAssistant;
fViewer = viewer;
fAdditionalInfoController = infoController;
fContentAssistSubjectControlAdapter = new ContentAssistSubjectControlAdapter(fViewer);
}
/**
* Creates a new completion proposal popup for the given elements.
*
* @param contentAssistant
* the code assistant feeding this popup
* @param contentAssistSubjectControl
* the code assist subject control on top of which this popup appears
* @param infoController
* the information control collaborating with this popup
* @since 3.0
*/
public CompletionProposalPopup(ContentAssistant contentAssistant,
IContentAssistSubjectControl contentAssistSubjectControl, AdditionalInfoController infoController)
{
fContentAssistant = contentAssistant;
fContentAssistSubjectControl = contentAssistSubjectControl;
fAdditionalInfoController = infoController;
fContentAssistSubjectControlAdapter = new ContentAssistSubjectControlAdapter(fContentAssistSubjectControl);
}
/**
* Computes and presents completion proposals. The flag indicates whether this call has be made out of an auto
* activation context.
*
* @param autoActivated
* <code>true</code> if auto activation context
* @return an error message or <code>null</code> in case of no error
*/
public String showProposals(final boolean autoActivated)
{
if (fKeyListener == null)
{
fKeyListener = new ProposalSelectionListener();
}
final Control control = fContentAssistSubjectControlAdapter.getControl();
if (control != null && !control.isDisposed())
{
// add the listener before computing the proposals so we don't move the caret
// when the user types fast.
fContentAssistSubjectControlAdapter.addKeyListener(fKeyListener);
BusyIndicator.showWhile(control.getDisplay(), new Runnable()
{
public void run()
{
fInvocationOffset = fContentAssistSubjectControlAdapter.getSelectedRange().x;
fFilterOffset = fInvocationOffset;
fComputedProposals = computeProposals(fInvocationOffset, autoActivated);
int count = (fComputedProposals == null ? 0 : fComputedProposals.length);
if (count == 0)
{
// if (!autoActivated)
// control.getDisplay().beep();
hide();
}
else
{
if (count == 1 && !autoActivated && canAutoInsert(fComputedProposals[0]))
{
insertProposal(fComputedProposals[0], (char) 0, 0, fInvocationOffset);
hide();
}
else
{
createPopup();
}
}
}
});
}
return getErrorMessage();
}
/**
* Returns the completion proposal available at the given offset of the viewer's document. Delegates the work to the
* code assistant.
*
* @param offset
* the offset
* @param autoActivated
* @return the completion proposals available at this offset
*/
private ICompletionProposal[] computeProposals(int offset, boolean autoActivated)
{
if (fContentAssistSubjectControl != null)
{
return fContentAssistant.computeCompletionProposals(fContentAssistSubjectControl, offset, fActivationKey);
}
return fContentAssistant.computeCompletionProposals(fViewer, offset, fActivationKey, autoActivated);
}
/**
* Returns the error message.
*
* @return the error message
*/
private String getErrorMessage()
{
return fContentAssistant.getErrorMessage();
}
/**
* Creates the proposal selector.
*/
private void createProposalSelector()
{
Control control = fContentAssistSubjectControlAdapter.getControl();
if (Helper.okToUse(fProposalShell))
{
// Custom code to force colors again in case theme changed...
// Not sure why we don't set background for all WS here
if (!"carbon".equals(SWT.getPlatform())) //$NON-NLS-1$
{
fProposalShell.setBackground(getForegroundColor(control));
}
Color c = getBackgroundColor(control);
fProposalTable.setBackground(c);
c = getForegroundColor(control);
fProposalTable.setForeground(c);
return;
}
fProposalShell = new Shell(control.getShell(), SWT.ON_TOP | SWT.RESIZE);
fProposalTable = new Table(fProposalShell, SWT.H_SCROLL | SWT.V_SCROLL | SWT.VIRTUAL);
Listener listener = new Listener()
{
public void handleEvent(Event event)
{
handleSetData(event);
}
};
fProposalTable.addListener(SWT.SetData, listener);
final IPreferenceStore store = UnifiedEditorsPlugin.getDefault().getPreferenceStore();
_insertOnTab = store.getBoolean(IPreferenceConstants.INSERT_ON_TAB);
String agents = store.getString(IPreferenceConstants.USER_AGENT_PREFERENCE);
if (agents != null && !agents.equals(StringUtils.EMPTY))
{
fUserAgents = agents.split(",").length; //$NON-NLS-1$
}
else
{
fUserAgents = 0;
}
TableColumn initialInfo = new TableColumn(fProposalTable, SWT.LEFT);
initialInfo.setWidth(16);
for (int i = 0; i < fUserAgents; i++)
{
TableColumn tc = new TableColumn(fProposalTable, SWT.LEFT);
tc.setWidth(17);
}
TableColumn locationInfo = new TableColumn(fProposalTable, SWT.LEFT);
locationInfo.setWidth(16);
fProposalTable.setLocation(0, 0);
if (fAdditionalInfoController != null)
{
fAdditionalInfoController.setSizeConstraints(40, 10, true, false);
}
GridLayout layout = new GridLayout();
layout.marginWidth = 0;
layout.marginHeight = 0;
fProposalShell.setLayout(layout);
GridData data = new GridData(GridData.FILL_BOTH);
Point size = fContentAssistant.restoreCompletionProposalPopupSize();
if (size != null)
{
fProposalTable.setLayoutData(data);
fProposalShell.setSize(size);
}
else
{
data.heightHint = fProposalTable.getItemHeight() * CompletionProposalPopup.PROPOSAL_ITEM_HEIGHT;
data.widthHint = 300;
fProposalTable.setLayoutData(data);
fProposalShell.pack();
}
fProposalShell.addControlListener(new ControlListener()
{
public void controlMoved(ControlEvent e)
{
}
public void controlResized(ControlEvent e)
{
if (fAdditionalInfoController != null)
{
// reset the cached resize constraints
fAdditionalInfoController.setSizeConstraints(40, 10, true, false);
}
fSize = fProposalShell.getSize();
}
});
if (!"carbon".equals(SWT.getPlatform())) //$NON-NLS-1$
{
fProposalShell.setBackground(getForegroundColor(control));
}
Color c = getBackgroundColor(control);
fProposalTable.setBackground(c);
c = getForegroundColor(control);
fProposalTable.setForeground(c);
fProposalTable.addSelectionListener(new SelectionListener()
{
public void widgetSelected(SelectionEvent e)
{
}
public void widgetDefaultSelected(SelectionEvent e)
{
selectProposalWithMask(e.stateMask);
// This disposal on windows it because of focus issue when a select is manually select and the next time
// the popup is show it has focus and so editor typing focus is lost
if (Platform.OS_WIN32.equals(Platform.getOS()))
{
disposePopup();
}
}
});
fPopupCloser.install(fContentAssistant, fProposalTable);
final IPropertyChangeListener propListener = new IPropertyChangeListener()
{
public void propertyChange(PropertyChangeEvent event)
{
if (event.getProperty().equals(IPreferenceConstants.USER_AGENT_PREFERENCE))
{
if (Helper.okToUse(fProposalShell))
{
fProposalShell.dispose();
}
else
{
// shell already disposed so remove this listener
if (store != null)
{
store.removePropertyChangeListener(this);
}
}
}
}
};
store.addPropertyChangeListener(propListener);
fProposalShell.addDisposeListener(new DisposeListener()
{
public void widgetDisposed(DisposeEvent e)
{
if (store != null && propListener != null)
{
store.removePropertyChangeListener(propListener);
}
unregister(); // but don't dispose the shell, since we're being called from its
// disposal event!
}
});
fProposalTable.setHeaderVisible(false);
}
/**
* Returns the background color to use.
*
* @param control the control to get the display from
* @return the background color
* @since 3.2
*/
private Color getBackgroundColor(Control control)
{
Color c = fContentAssistant.getProposalSelectorBackground();
if (c == null)
{
c = JFaceResources.getColorRegistry().get(JFacePreferences.CONTENT_ASSIST_BACKGROUND_COLOR);
}
return c;
}
/**
* Returns the foreground color to use.
*
* @param control
* the control to get the display from
* @return the foreground color
* @since 3.2
*/
private Color getForegroundColor(Control control)
{
Color c = fContentAssistant.getProposalSelectorForeground();
if (c == null)
{
c = JFaceResources.getColorRegistry().get(JFacePreferences.CONTENT_ASSIST_FOREGROUND_COLOR);
}
return c;
}
/**
* @since 3.1
*/
int defaultIndex = -1;
private char fLastKeyPressed;
private void handleSetData(Event event)
{
TableItem item = (TableItem) event.item;
int index = fProposalTable.indexOf(item);
defaultIndex = -1;
if (0 <= index && index < fFilteredProposals.length)
{
ICompletionProposal current = fFilteredProposals[index];
String entry = current.getDisplayString().trim();
item.setImage(current.getImage());
item.setText(0, entry);
item.setData(current);
if (current instanceof IUnifiedCompletionProposal)
{
IUnifiedCompletionProposal prop = (IUnifiedCompletionProposal) current;
String loc = prop.getFileLocation();
Image[] images = prop.getUserAgentImages();
if (images != null)
{
for (int j = 0; j < images.length; j++)
{
Image image = images[j];
item.setImage(j + 1, image);
}
}
else
{
for (int j = 0; j < fUserAgents; j++)
{
item.setImage(j + 1, null);
}
}
if (fUserAgents > 0)
{
item.setText(fUserAgents + 1, " " + loc); //$NON-NLS-1$
}
else
{
item.setText(fUserAgents + 1, loc);
}
// TODO:
if (current instanceof IUnifiedCompletionProposal
&& ((IUnifiedCompletionProposal) current).isDefaultSelection())
{
defaultIndex = index;
}
}
}
else
{
// this should not happen, but does on win32
}
if (defaultIndex != -1)
{
selectProposal(defaultIndex, false, true);
}
}
/**
* Returns the proposal selected in the proposal selector.
*
* @return the selected proposal
* @since 2.0
*/
private ICompletionProposal getSelectedProposal()
{
int i = fProposalTable.getSelectionIndex();
if (fFilteredProposals == null || i < 0 || i >= fFilteredProposals.length)
{
return null;
}
return fFilteredProposals[i];
}
/**
* Takes the selected proposal and applies it.
*
* @param stateMask
* the state mask
* @since 2.1
*/
private boolean selectProposalWithMask(int stateMask)
{
ICompletionProposal p = getSelectedProposal();
hide();
if (p == null)
{
return false;
}
insertProposal(p, (char) 0, stateMask, fContentAssistSubjectControlAdapter.getSelectedRange().x);
return true;
}
/**
* Applies the given proposal at the given offset. The given character is the one that triggered the insertion of
* this proposal.
*
* @param p
* the completion proposal
* @param trigger
* the trigger character
* @param stateMask
* the state mask
* @param offset
* the offset
* @since 2.1
*/
private void insertProposal(ICompletionProposal p, char trigger, int stateMask, final int offset)
{
fInserting = true;
IRewriteTarget target = null;
IEditingSupport helper = new IEditingSupport()
{
public boolean isOriginator(DocumentEvent event, IRegion focus)
{
return focus.getOffset() <= offset && focus.getOffset() + focus.getLength() >= offset;
}
public boolean ownsFocusShell()
{
return false;
}
};
try
{
IDocument document = fContentAssistSubjectControlAdapter.getDocument();
if (fViewer instanceof ITextViewerExtension)
{
ITextViewerExtension extension = (ITextViewerExtension) fViewer;
target = extension.getRewriteTarget();
}
if (target != null)
{
target.beginCompoundChange();
}
if (fViewer instanceof IEditingSupportRegistry)
{
IEditingSupportRegistry registry = (IEditingSupportRegistry) fViewer;
registry.register(helper);
}
if (p instanceof ICompletionProposalExtension2 && fViewer != null)
{
ICompletionProposalExtension2 e = (ICompletionProposalExtension2) p;
e.apply(fViewer, trigger, stateMask, offset);
}
else if (p instanceof ICompletionProposalExtension)
{
ICompletionProposalExtension e = (ICompletionProposalExtension) p;
e.apply(document, trigger, offset);
}
else
{
p.apply(document);
}
Point selection = p.getSelection(document);
if (selection != null)
{
fContentAssistSubjectControlAdapter.setSelectedRange(selection.x, selection.y);
fContentAssistSubjectControlAdapter.revealRange(selection.x, selection.y);
}
IContextInformation info = p.getContextInformation();
if (info != null)
{
int contextInformationOffset;
if (p instanceof ICompletionProposalExtension)
{
ICompletionProposalExtension e = (ICompletionProposalExtension) p;
contextInformationOffset = e.getContextInformationPosition();
}
else
{
if (selection == null)
{
selection = fContentAssistSubjectControlAdapter.getSelectedRange();
}
contextInformationOffset = selection.x + selection.y;
}
fContentAssistant.showContextInformation(info, contextInformationOffset);
}
else
{
fContentAssistant.showContextInformation(null, -1);
}
}
finally
{
if (target != null)
{
target.endCompoundChange();
}
if (fViewer instanceof IEditingSupportRegistry)
{
IEditingSupportRegistry registry = (IEditingSupportRegistry) fViewer;
registry.unregister(helper);
}
fInserting = false;
}
}
/**
* Returns whether this popup has the focus.
*
* @return <code>true</code> if the popup has the focus
*/
public boolean hasFocus()
{
if (Helper.okToUse(fProposalShell))
{
return (fProposalShell.isFocusControl() || fProposalTable.isFocusControl());
}
return false;
}
/**
* Hides this popup.
*/
public void hide()
{
fLastKeyPressed = '\0';
unregister();
if (fViewer instanceof IEditingSupportRegistry)
{
IEditingSupportRegistry registry = (IEditingSupportRegistry) fViewer;
registry.unregister(fFocusHelper);
}
if (Helper.okToUse(fProposalShell))
{
fContentAssistant.removeContentAssistListener(this, ContentAssistant.PROPOSAL_SELECTOR);
fPopupCloser.uninstall();
if (fAdditionalInfoController != null)
{
fAdditionalInfoController.disposeInformationControl();
}
fProposalShell.setVisible(false);
}
}
/**
* Disposes the popup
*/
public void disposePopup()
{
if (fProposalShell != null && !fProposalShell.isDisposed())
{
fProposalShell.dispose();
}
}
/**
* Unregister this completion proposal popup.
*
* @since 3.0
*/
private void unregister()
{
if (fDocumentListener != null)
{
IDocument document = fContentAssistSubjectControlAdapter.getDocument();
if (document != null)
{
document.removeDocumentListener(fDocumentListener);
}
fDocumentListener = null;
}
fDocumentEvents.clear();
if (fKeyListener != null && fContentAssistSubjectControlAdapter.getControl() != null
&& !fContentAssistSubjectControlAdapter.getControl().isDisposed())
{
fContentAssistSubjectControlAdapter.removeKeyListener(fKeyListener);
fKeyListener = null;
}
if (fLastProposal != null)
{
if (fLastProposal instanceof ICompletionProposalExtension2 && fViewer != null)
{
ICompletionProposalExtension2 extension = (ICompletionProposalExtension2) fLastProposal;
extension.unselected(fViewer);
}
fLastProposal = null;
}
fFilteredProposals = null;
fComputedProposals = null;
fContentAssistant.possibleCompletionsClosed();
}
/**
* Returns whether this popup is active. It is active if the proposal selector is visible.
*
* @return <code>true</code> if this popup is active
*/
public boolean isActive()
{
return fProposalShell != null && !fProposalShell.isDisposed() && fProposalShell.isVisible();
}
/**
* Initializes the proposal selector with these given proposals.
*
* @param proposals
* the proposals
* @param isFilteredSubset
* if <code>true</code>, the proposal table is not cleared, but the proposals that are not in the
* passed array are removed from the displayed set
*/
private void setProposals(ICompletionProposal[] proposals, boolean isFilteredSubset)
{
if (Helper.okToUse(fProposalTable))
{
ICompletionProposal oldProposal = getSelectedProposal();
if (oldProposal instanceof ICompletionProposalExtension2 && fViewer != null)
{
((ICompletionProposalExtension2) oldProposal).unselected(fViewer);
}
fFilteredProposals = proposals;
final int newLen = proposals.length;
int defaultIndex = -1;
int suggestedIndex = -1;
String longestString = StringUtils.EMPTY;
String longestLoc = StringUtils.EMPTY;
int totalItems = 0;
fProposalTable.setItemCount(newLen);
fProposalTable.clearAll();
for (int i = 0; i < proposals.length; i++)
{
ICompletionProposal proposal = proposals[i];
String entry = proposal.getDisplayString().trim();
if (entry.length() > longestString.length())
{
longestString = entry;
}
if (proposal instanceof IUnifiedCompletionProposal)
{
IUnifiedCompletionProposal prop = (IUnifiedCompletionProposal) proposal;
String loc = prop.getFileLocation();
if (loc.length() > longestLoc.length())
{
longestLoc = loc;
}
if (((IUnifiedCompletionProposal) proposal).isDefaultSelection())
{
defaultIndex = i;
}
else if (((IUnifiedCompletionProposal) proposal).isSuggestedSelection())
{
suggestedIndex = i;
}
}
}
String measureString = "MMMM" + longestString + "MMM"; //$NON-NLS-1$ //$NON-NLS-2$
GC gc = new GC(fProposalTable.getShell());
Point locationWidth = gc.stringExtent(longestLoc + "MMM"); //$NON-NLS-1$
measureString += longestLoc + "MMM"; //$NON-NLS-1$
Point widestText = gc.stringExtent(longestString + "MMMMMM"); //$NON-NLS-1$
Point extent = gc.stringExtent(measureString);
gc.dispose();
if (System.getProperty("os.name").startsWith("Mac OS")) //$NON-NLS-1$ //$NON-NLS-2$
{
measureString = "MMMM" + longestString + "MMMM"; //$NON-NLS-1$ //$NON-NLS-2$
for (int j = 1; j < fProposalTable.getColumnCount(); j++)
{
if (j == fUserAgents + 1)
{
if (fProposalTable.getColumn(j).getWidth() != locationWidth.x)
{
fProposalTable.getColumn(j).setWidth(locationWidth.x);
}
}
else if (fProposalTable.getColumn(j).getWidth() != 19)
{
fProposalTable.getColumn(j).setWidth(19);
}
}
}
else
{
fProposalTable.getColumn(fUserAgents + 1).pack();
}
TableColumn first = fProposalTable.getColumn(0);
if (first.getWidth() != widestText.x)
{
fProposalTable.getColumn(0).setWidth(widestText.x);
}
int width = extent.x;
if (fUserAgents > 0)
{
if (System.getProperty("os.name").startsWith("Mac OS")) //$NON-NLS-1$ //$NON-NLS-2$
{
width = width + fUserAgents * (fProposalTable.getColumn(1).getWidth() - 5);
}
else
{
width = width + fUserAgents * 10;
}
}
if (totalItems > PROPOSAL_ITEM_HEIGHT)
{
totalItems = PROPOSAL_ITEM_HEIGHT;
}
int height = (fProposalTable.getItemHeight() * PROPOSAL_ITEM_HEIGHT);
// Point currentLocation = fProposalShell.getLocation();
// Point newLocation = getLocation(width, height);
// if ((newLocation.x < currentLocation.x && newLocation.y == currentLocation.y) ||
// newLocation.y != currentLocation.y)
// fProposalShell.setLocation(newLocation);
GridData data = new GridData(GridData.FILL_BOTH);
data.heightHint = height;
data.widthHint = width;
fProposalTable.setLayoutData(data);
fProposalShell.pack(true);
// IM changed this to deselect the table if there is no default selection
if (defaultIndex != -1)
{
this.selectProposal(defaultIndex, false, true);
}
else if (suggestedIndex != -1)
{
this.fProposalTable.deselectAll();
this.setScroll(suggestedIndex);
}
else if (fProposalTable != null)
{
if (fLastKeyPressed == '\b' && defaultIndex == -1)
{
hide();
}
else
{
this.fProposalTable.setTopIndex(0);
this.fProposalTable.deselectAll();
}
}
}
}
/**
* Returns the graphical location at which this popup should be made visible.
*
* @param width
* @param height
* @return the location of this popup
*/
/*
* private Point getLocation(int width, int height) { // get character index into the source for the current caret
* position int caret = fContentAssistSubjectControlAdapter.getCaretOffset(); // get coordinate of caret position
* Point clientPoint = fContentAssistSubjectControlAdapter.getLocationAtOffset(caret); // make sure clientPoint is
* within the visible client area if (clientPoint.x < 0) clientPoint.x = 0; if (clientPoint.y < 0) clientPoint.y =
* 0; // convert coordinate to screen coordinates Point screenPoint =
* fContentAssistSubjectControlAdapter.getControl().toDisplay(clientPoint); Rectangle screenRect =
* Display.getCurrent().getClientArea(); int lineHeight = fContentAssistSubjectControlAdapter.getLineHeight(); int x =
* screenPoint.x; int y = screenPoint.y + lineHeight + 2; if (x + width > screenRect.width) { // hangs over the
* right side of the screen if (screenPoint.x - width > 0) { x = screenPoint.x - width; } else { // doesn't fit on
* the left either // This should only happen on a really small screen or with a large popup } } if (y + height >
* screenRect.height) { // doesn't fit below current line // TODO: '7' is a magic number. We really need a reliable
* way to get the true height/width of this widget int yOffset = height + lineHeight + 7; if (screenPoint.y -
* yOffset > 0) { y = screenPoint.y - yOffset; } else { // doesn't fit above current line either // This should only
* happen on a really small screen or with a large popup } } return new Point(x, y); }
*/
/**
* Returns the graphical location at which this popup should be made visible.
*
* @param proposalBox
* @param displayBounds
* @param caretLocation
* @param lineHeight
* @return the location of this popup
*/
public static Point computeLocation(Rectangle proposalBox, Rectangle displayBounds, Point caretLocation,
int lineHeight)
{
if (caretLocation.y + proposalBox.height > displayBounds.height + displayBounds.y)
{
return new Point(caretLocation.x, caretLocation.y - (lineHeight + proposalBox.height));
}
else
{
return new Point(caretLocation.x, caretLocation.y + lineHeight);
}
}
/**
* Returns the size of this completion proposal popup.
*
* @return a Point containing the size
* @since 3.0
*/
Point getSize()
{
return fSize;
}
/**
* Displays this popup and install the additional info controller, so that additional info is displayed when a
* proposal is selected and additional info is available.
*/
private void displayProposals()
{
if (!Helper.okToUse(fProposalShell) || !Helper.okToUse(fProposalTable))
{
return;
}
if (fContentAssistant.addContentAssistListener(this, ContentAssistant.PROPOSAL_SELECTOR))
{
if (fDocumentListener == null)
{
fDocumentListener = new IDocumentListener()
{
public void documentAboutToBeChanged(DocumentEvent event)
{
if (!fInserting)
{
fDocumentEvents.add(event);
}
}
public void documentChanged(DocumentEvent event)
{
if (!fInserting)
{
filterProposals();
}
}
};
}
IDocument document = fContentAssistSubjectControlAdapter.getDocument();
if (document != null)
{
document.addDocumentListener(fDocumentListener);
}
if (fFocusHelper == null)
{
fFocusHelper = new IEditingSupport()
{
public boolean isOriginator(DocumentEvent event, IRegion focus)
{
return false; // this helper just covers the focus change to the proposal
// shell, no remote
// editions
}
public boolean ownsFocusShell()
{
return true;
}
};
}
if (fViewer instanceof IEditingSupportRegistry)
{
IEditingSupportRegistry registry = (IEditingSupportRegistry) fViewer;
registry.register(fFocusHelper);
}
/*
* https://bugs.eclipse.org/bugs/show_bug.cgi?id=52646 on GTK, setVisible and such may run the event loop
* (see also https://bugs.eclipse.org/bugs/show_bug.cgi?id=47511) Since the user may have already canceled
* the popup or selected an entry (ESC or RETURN), we have to double check whether the table is still
* okToUse. See comments below
*/
fProposalShell.setVisible(true); // may run event loop on GTK
// transfer focus since no verify key listener can be attached
if (!fContentAssistSubjectControlAdapter.supportsVerifyKeyListener() && Helper.okToUse(fProposalShell))
{
fProposalShell.setFocus(); // may run event loop on GTK ??
}
if (fAdditionalInfoController != null && Helper.okToUse(fProposalTable))
{
fAdditionalInfoController.install(fProposalTable);
fAdditionalInfoController.handleTableSelectionChanged();
}
}
else
{
hide();
}
}
/**
* @see IContentAssistListener#verifyKey(VerifyEvent)
*/
public boolean verifyKey(VerifyEvent e)
{
if (!Helper.okToUse(fProposalShell))
{
return true;
}
char key = e.character;
fLastKeyPressed = e.character;
if (key == 0)
{
int newSelection = fProposalTable.getSelectionIndex();
int visibleRows = (fProposalTable.getSize().y / fProposalTable.getItemHeight()) - 1;
boolean smartToggle = false;
switch (e.keyCode)
{
case SWT.ARROW_LEFT:
case SWT.ARROW_RIGHT:
// filterProposals();
hide();
return true;
case SWT.ARROW_UP:
newSelection -= 1;
if (newSelection < 0)
{
newSelection = fProposalTable.getItemCount() - 1;
}
break;
case SWT.ARROW_DOWN:
newSelection += 1;
if (newSelection > fProposalTable.getItemCount() - 1)
{
newSelection = 0;
}
break;
case SWT.PAGE_DOWN:
newSelection += visibleRows;
if (newSelection >= fProposalTable.getItemCount())
{
newSelection = fProposalTable.getItemCount() - 1;
}
break;
case SWT.PAGE_UP:
newSelection -= visibleRows;
if (newSelection < 0)
{
newSelection = 0;
}
break;
case SWT.HOME:
hide();
return true;
// newSelection= 0;
// break;
//
case SWT.END:
hide();
return true;
// newSelection= fProposalTable.getItemCount() - 1;
// break;
default:
if (e.keyCode != SWT.CAPS_LOCK && e.keyCode != SWT.MOD1 && e.keyCode != SWT.MOD2
&& e.keyCode != SWT.MOD3 && e.keyCode != SWT.MOD4)
{
hide();
}
return true;
}
selectProposal(newSelection, smartToggle, false);
e.doit = false;
return false;
}
// key != 0
switch (key)
{
case '\t':
if (!_insertOnTab)
{
e.doit = true;
hide();
break;
}
else
{
if (selectProposalWithMask(e.stateMask))
{
e.doit = false;
break;
}
else
{
return true;
}
}
case 0x1B: // Esc
e.doit = false;
hide();
break;
case '\n': // Ctrl-Enter on w2k
case '\r': // Enter
if (selectProposalWithMask(e.stateMask))
{
e.doit = false;
break;
}
else
{
return true;
}
default:
ICompletionProposal p = getSelectedProposal();
if (p instanceof ICompletionProposalExtension)
{
ICompletionProposalExtension t = (ICompletionProposalExtension) p;
char[] triggers = t.getTriggerCharacters();
if (contains(triggers, key))
{
e.doit = false;
hide();
insertProposal(p, key, e.stateMask, fContentAssistSubjectControlAdapter.getSelectedRange().x);
}
}
}
return true;
}
/**
* Selects the entry with the given index in the proposal selector and feeds the selection to the additional info
* controller.
*
* @param index
* the index in the list
* @param smartToggle
* <code>true</code> if the smart toggle key has been pressed
* @param autoScroll
* Do we scroll the item into view at the middle of the list
* @since 2.1
*/
private void selectProposal(int index, boolean smartToggle, boolean autoScroll)
{
if (fFilteredProposals == null)
{
return;
}
ICompletionProposal oldProposal = getSelectedProposal();
if (oldProposal instanceof ICompletionProposalExtension2 && fViewer != null)
{
((ICompletionProposalExtension2) oldProposal).unselected(fViewer);
}
ICompletionProposal proposal = fFilteredProposals[index];
if (proposal instanceof ICompletionProposalExtension2 && fViewer != null)
{
((ICompletionProposalExtension2) proposal).selected(fViewer, smartToggle);
}
fLastProposal = proposal;
fProposalTable.setSelection(index);
if (autoScroll)
{
setScroll(index);
}
fProposalTable.showSelection();
if (fAdditionalInfoController != null)
{
fAdditionalInfoController.handleTableSelectionChanged();
}
}
/**
* Sets the postition of the table
*
* @param index
*/
private void setScroll(int index)
{
int topIndex = index - 4 > 0 ? index - 4 : 0;
fProposalTable.setTopIndex(topIndex);
}
/**
* Returns whether the given character is contained in the given array of characters.
*
* @param characters
* the list of characters
* @param c
* the character to look for in the list
* @return <code>true</code> if character belongs to the list
* @since 2.0
*/
private boolean contains(char[] characters, char c)
{
if (characters == null)
{
return false;
}
for (int i = 0; i < characters.length; i++)
{
if (c == characters[i])
{
return true;
}
}
return false;
}
/**
* @see org.eclipse.jface.text.IEventConsumer#processEvent(org.eclipse.swt.events.VerifyEvent)
*/
public void processEvent(VerifyEvent e)
{
}
/**
* Filters the displayed proposal based on the given cursor position and the offset of the original invocation of
* the code assistant.
*/
private void filterProposals()
{
++fInvocationCounter;
final Control control = fContentAssistSubjectControlAdapter.getControl();
control.getDisplay().asyncExec(new Runnable()
{
final long fCounter = fInvocationCounter;
public void run()
{
if (fCounter != fInvocationCounter)
{
return;
}
if (control.isDisposed())
{
return;
}
int offset = fContentAssistSubjectControlAdapter.getSelectedRange().x;
ICompletionProposal[] proposals = null;
try
{
if (offset > -1)
{
DocumentEvent event = TextUtilities.mergeProcessedDocumentEvents(fDocumentEvents);
proposals = computeFilteredProposals(offset, event);
}
}
catch (BadLocationException x)
{
}
finally
{
fDocumentEvents.clear();
}
fFilterOffset = offset;
if (proposals != null && proposals.length > 0)
{
fContentAssistant.promoteKeyListener();
setProposals(proposals, fIsFilteredSubset);
}
else
{
hide();
}
}
});
}
/**
* Computes the subset of already computed proposals that are still valid for the given offset.
*
* @param offset
* the offset
* @param event
* the merged document event
* @return the set of filtered proposals
* @since 3.0
*/
private ICompletionProposal[] computeFilteredProposals(int offset, DocumentEvent event)
{
if (offset == fInvocationOffset && event == null)
{
fIsFilteredSubset = false;
return fComputedProposals;
}
if (offset < fInvocationOffset)
{
fIsFilteredSubset = false;
fInvocationOffset = offset;
fComputedProposals = computeProposals(fInvocationOffset, false);
return fComputedProposals;
}
ICompletionProposal[] proposals;
if (offset < fFilterOffset)
{
proposals = fComputedProposals;
fIsFilteredSubset = false;
}
else
{
proposals = fFilteredProposals;
fIsFilteredSubset = true;
}
if (proposals == null)
{
fIsFilteredSubset = false;
return null;
}
IDocument document = fContentAssistSubjectControlAdapter.getDocument();
int length = proposals.length;
List filtered = new ArrayList(length);
for (int i = 0; i < length; i++)
{
if (proposals[i] instanceof ICompletionProposalExtension2)
{
ICompletionProposalExtension2 p = (ICompletionProposalExtension2) proposals[i];
if (p.validate(document, offset, event))
{
filtered.add(p);
}
}
else if (proposals[i] instanceof ICompletionProposalExtension)
{
ICompletionProposalExtension p = (ICompletionProposalExtension) proposals[i];
if (p.isValidFor(document, offset))
{
filtered.add(p);
}
}
else
{
// restore original behavior
fIsFilteredSubset = false;
fInvocationOffset = offset;
fComputedProposals = computeProposals(fInvocationOffset, false);
return fComputedProposals;
}
}
return (ICompletionProposal[]) filtered.toArray(new ICompletionProposal[filtered.size()]);
}
/**
* Requests the proposal shell to take focus.
*
* @since 3.0
*/
public void setFocus()
{
if (Helper.okToUse(fProposalShell))
{
fProposalShell.setFocus();
}
}
/**
* Returns <code>true</code> if <code>proposal</code> should be auto-inserted, <code>false</code> otherwise.
*
* @param proposal
* the single proposal that might be automatically inserted
* @return <code>true</code> if <code>proposal</code> can be inserted automatically, <code>false</code>
* otherwise
* @since 3.1
*/
private boolean canAutoInsert(ICompletionProposal proposal)
{
if (fContentAssistant.isAutoInserting())
{
if (proposal instanceof ICompletionProposalExtension4)
{
ICompletionProposalExtension4 ext = (ICompletionProposalExtension4) proposal;
return ext.isAutoInsertable();
}
return true; // default behavior before ICompletionProposalExtension4 was introduced
}
return false;
}
/**
* Completes the common prefix of all proposals directly in the code. If no common prefix can be found, the proposal
* popup is shown.
*
* @return an error message if completion failed.
* @since 3.0
*/
public String incrementalComplete()
{
if (Helper.okToUse(fProposalShell) && fFilteredProposals != null)
{
completeCommonPrefix();
}
else
{
final Control control = fContentAssistSubjectControlAdapter.getControl();
if (fKeyListener == null)
{
fKeyListener = new ProposalSelectionListener();
}
if (!Helper.okToUse(fProposalShell) && !control.isDisposed())
{
fContentAssistSubjectControlAdapter.addKeyListener(fKeyListener);
}
BusyIndicator.showWhile(control.getDisplay(), new Runnable()
{
public void run()
{
fInvocationOffset = fContentAssistSubjectControlAdapter.getSelectedRange().x;
fFilterOffset = fInvocationOffset;
fFilteredProposals = computeProposals(fInvocationOffset, false);
int count = (fFilteredProposals == null ? 0 : fFilteredProposals.length);
if (count == 0)
{
// IM turned off for the moment, as it's annoying more than helpful to beep.
// control.getDisplay().beep();
hide();
}
else if (count == 1 && canAutoInsert(fFilteredProposals[0]))
{
insertProposal(fFilteredProposals[0], (char) 0, 0, fInvocationOffset);
hide();
}
else
{
if (completeCommonPrefix())
{
hide();
}
else
{
fComputedProposals = fFilteredProposals;
createPopup();
}
}
}
});
}
return getErrorMessage();
}
/**
* Calls the necessary methods to popup the content assist method. This was moved to one single method to unify the
* path to show the window and to provide an easy way to time the operations needed to populate and create the
* widget.
*/
private void createPopup()
{
createProposalSelector();
setProposals(fComputedProposals, false);
fContentAssistant.addToLayout(this, fProposalShell, ContentAssistant.LayoutManager.LAYOUT_PROPOSAL_SELECTOR,
fContentAssistant.getSelectionOffset());
displayProposals();
}
/**
* Acts upon <code>fFilteredProposals</code>: if there is just one valid proposal, it is inserted, otherwise, the
* common prefix of all proposals is inserted into the document. If there is no common prefix, nothing happens and
* <code>false</code> is returned.
*
* @return <code>true</code> if a single proposal was inserted and the selector can be closed, <code>false</code>
* if more than once choice remain
* @since 3.0
*/
private boolean completeCommonPrefix()
{
// 0: insert single proposals
if (fFilteredProposals.length == 1)
{
if (canAutoInsert(fFilteredProposals[0]))
{
insertProposal(fFilteredProposals[0], (char) 0, 0, fFilterOffset);
hide();
return true;
}
return false;
}
// 1: extract pre- and postfix from all remaining proposals
IDocument document = fContentAssistSubjectControlAdapter.getDocument();
// contains the common postfix in the case that there are any proposals matching our LHS
StringBuffer rightCasePostfix = null;
List rightCase = new ArrayList();
// whether to check for non-case compatible matches. This is initially true, and stays so
// as long as there are i) no case-sensitive matches and ii) all proposals share the same
// (although not corresponding with the document contents) common prefix.
boolean checkWrongCase = true;
// the prefix of all case insensitive matches. This differs from the document
// contents and will be replaced.
CharSequence wrongCasePrefix = null;
int wrongCasePrefixStart = 0;
// contains the common postfix of all case-insensitive matches
StringBuffer wrongCasePostfix = null;
List wrongCase = new ArrayList();
for (int i = 0; i < fFilteredProposals.length; i++)
{
ICompletionProposal proposal = fFilteredProposals[i];
CharSequence insertion = getPrefixCompletion(proposal);
int start = getPrefixCompletionOffset(proposal);
try
{
int prefixLength = fFilterOffset - start;
int relativeCompletionOffset = Math.min(insertion.length(), prefixLength);
String prefix = document.get(start, prefixLength);
if (insertion.toString().startsWith(prefix))
{
checkWrongCase = false;
rightCase.add(proposal);
CharSequence newPostfix = insertion.subSequence(relativeCompletionOffset, insertion.length());
if (rightCasePostfix == null)
{
rightCasePostfix = new StringBuffer(newPostfix.toString());
}
else
{
truncatePostfix(rightCasePostfix, newPostfix);
}
}
else if (checkWrongCase)
{
CharSequence newPrefix = insertion.subSequence(0, relativeCompletionOffset);
if (isPrefixCompatible(wrongCasePrefix, wrongCasePrefixStart, newPrefix, start, document))
{
wrongCasePrefix = newPrefix;
wrongCasePrefixStart = start;
CharSequence newPostfix = insertion.subSequence(relativeCompletionOffset, insertion.length());
if (wrongCasePostfix == null)
{
wrongCasePostfix = new StringBuffer(newPostfix.toString());
}
else
{
truncatePostfix(wrongCasePostfix, newPostfix);
}
wrongCase.add(proposal);
}
else
{
checkWrongCase = false;
}
}
}
catch (BadLocationException e2)
{
// bail out silently
return false;
}
if (rightCasePostfix != null && rightCasePostfix.length() == 0 && rightCase.size() > 1)
{
return false;
}
}
// 2: replace single proposals
if (rightCase.size() == 1)
{
ICompletionProposal proposal = (ICompletionProposal) rightCase.get(0);
if (canAutoInsert(proposal))
{
insertProposal(proposal, (char) 0, 0, fInvocationOffset);
hide();
return true;
}
return false;
}
else if (checkWrongCase && wrongCase.size() == 1)
{
ICompletionProposal proposal = (ICompletionProposal) wrongCase.get(0);
if (canAutoInsert(proposal))
{
insertProposal(proposal, (char) 0, 0, fInvocationOffset);
hide();
return true;
}
return false;
}
// 3: replace post- / prefixes
CharSequence prefix;
if (checkWrongCase)
{
prefix = wrongCasePrefix;
}
else
{
prefix = StringUtils.EMPTY;
}
CharSequence postfix;
if (checkWrongCase)
{
postfix = wrongCasePostfix;
}
else
{
postfix = rightCasePostfix;
}
if (prefix == null || postfix == null)
{
return false;
}
try
{
// 4: check if parts of the postfix are already in the document
int to = Math.min(document.getLength(), fFilterOffset + postfix.length());
StringBuffer inDocument = new StringBuffer(document.get(fFilterOffset, to - fFilterOffset));
truncatePostfix(inDocument, postfix);
// 5: replace and reveal
document.replace(fFilterOffset - prefix.length(), prefix.length() + inDocument.length(), prefix.toString()
+ postfix.toString());
fContentAssistSubjectControlAdapter.setSelectedRange(fFilterOffset + postfix.length(), 0);
fContentAssistSubjectControlAdapter.revealRange(fFilterOffset + postfix.length(), 0);
return false;
}
catch (BadLocationException e)
{
// ignore and return false
return false;
}
}
/**
* @since 3.1
*/
private boolean isPrefixCompatible(CharSequence oneSequence, int oneOffset, CharSequence twoSequence,
int twoOffset, IDocument document) throws BadLocationException
{
if (oneSequence == null || twoSequence == null)
{
return true;
}
int min = Math.min(oneOffset, twoOffset);
int oneEnd = oneOffset + oneSequence.length();
int twoEnd = twoOffset + twoSequence.length();
String one = document.get(oneOffset, min - oneOffset) + oneSequence
+ document.get(oneEnd, Math.min(fFilterOffset, fFilterOffset - oneEnd));
String two = document.get(twoOffset, min - twoOffset) + twoSequence
+ document.get(twoEnd, Math.min(fFilterOffset, fFilterOffset - twoEnd));
return one.equals(two);
}
/**
* Truncates <code>buffer</code> to the common prefix of <code>buffer</code> and <code>sequence</code>.
*
* @param buffer
* the common postfix to truncate
* @param sequence
* the characters to truncate with
*/
private void truncatePostfix(StringBuffer buffer, CharSequence sequence)
{
// find common prefix
int min = Math.min(buffer.length(), sequence.length());
for (int c = 0; c < min; c++)
{
if (sequence.charAt(c) != buffer.charAt(c))
{
buffer.delete(c, buffer.length());
return;
}
}
// all equal up to minimum
buffer.delete(min, buffer.length());
}
/**
* Extracts the completion offset of an <code>ICompletionProposal</code>. If <code>proposal</code> is a
* <code>ICompletionProposalExtension3</code>, its <code>getCompletionOffset</code> method is called,
* otherwise, the invocation offset of this popup is shown.
*
* @param proposal
* the proposal to extract the offset from
* @return the proposals completion offset, or <code>fInvocationOffset</code>
* @since 3.1
*/
private int getPrefixCompletionOffset(ICompletionProposal proposal)
{
if (proposal instanceof ICompletionProposalExtension3)
{
return ((ICompletionProposalExtension3) proposal).getPrefixCompletionStart(
fContentAssistSubjectControlAdapter.getDocument(), fFilterOffset);
}
return fInvocationOffset;
}
/**
* Extracts the replacement string from an <code>ICompletionProposal</code>. If <code>proposal</code> is a
* <code>ICompletionProposalExtension3</code>, its <code>getCompletionText</code> method is called, otherwise,
* the display string is used.
*
* @param proposal
* the proposal to extract the text from
* @return the proposals completion text
* @since 3.1
*/
private CharSequence getPrefixCompletion(ICompletionProposal proposal)
{
CharSequence insertion = null;
if (proposal instanceof ICompletionProposalExtension3)
{
insertion = ((ICompletionProposalExtension3) proposal).getPrefixCompletionText(
fContentAssistSubjectControlAdapter.getDocument(), fFilterOffset);
}
if (insertion == null)
{
insertion = proposal.getDisplayString();
}
return insertion;
}
/**
* Gets the activation key
*
* @return char
*/
public char getActivationKey()
{
return fActivationKey;
}
/**
* Sets the activation key
*
* @param activationKey
*/
public void setActivationKey(char activationKey)
{
fActivationKey = activationKey;
}
}