org.eclipse.jface.text.contentassist.CompletionProposalPopup Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of org.eclipse.jface.text Show documentation
Show all versions of org.eclipse.jface.text Show documentation
This is org.eclipse.jface.text jar used by Scout SDK
The newest version!
/*******************************************************************************
* Copyright (c) 2000, 2016 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
* Sean Montgomery, [email protected] - https://bugs.eclipse.org/bugs/show_bug.cgi?id=116454
* Marcel Bruch, [email protected] - [content assist] Allow to re-sort proposals - https://bugs.eclipse.org/bugs/show_bug.cgi?id=350991
* Terry Parker, [email protected] - Protect against poorly behaved completion proposers - http://bugs.eclipse.org/429925
*******************************************************************************/
package org.eclipse.jface.text.contentassist;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.eclipse.osgi.util.TextProcessor;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.BusyIndicator;
import org.eclipse.swt.custom.StyleRange;
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.MouseAdapter;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.events.TraverseEvent;
import org.eclipse.swt.events.TraverseListener;
import org.eclipse.swt.events.VerifyEvent;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.graphics.FontData;
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.Display;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Table;
import org.eclipse.swt.widgets.TableItem;
import org.eclipse.core.commands.AbstractHandler;
import org.eclipse.core.commands.ExecutionEvent;
import org.eclipse.core.commands.ExecutionException;
import org.eclipse.core.commands.IHandler;
import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.ILog;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.Status;
import org.eclipse.jface.bindings.keys.KeySequence;
import org.eclipse.jface.bindings.keys.SWTKeySupport;
import org.eclipse.jface.contentassist.IContentAssistSubjectControl;
import org.eclipse.jface.internal.text.InformationControlReplacer;
import org.eclipse.jface.internal.text.TableOwnerDrawSupport;
import org.eclipse.jface.preference.JFacePreferences;
import org.eclipse.jface.resource.JFaceResources;
import org.eclipse.jface.util.Geometry;
import org.eclipse.jface.viewers.StyledString;
import org.eclipse.jface.text.AbstractInformationControlManager;
import org.eclipse.jface.text.AbstractInformationControlManager.Anchor;
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.IInformationControl;
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;
/**
* 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
* @see org.eclipse.jface.text.contentassist.AdditionalInfoController
*/
class CompletionProposalPopup implements IContentAssistListener {
/**
* Set to true
to use a Table with SWT.VIRTUAL.
* XXX: This is a workaround for: https://bugs.eclipse.org/bugs/show_bug.cgi?id=90321
* More details see also: https://bugs.eclipse.org/bugs/show_bug.cgi?id=98585#c36
* @since 3.1
*/
private static final boolean USE_VIRTUAL= !"motif".equals(SWT.getPlatform()); //$NON-NLS-1$
/**
* Completion proposal selection handler.
*
* @since 3.4
*/
final class ProposalSelectionHandler extends AbstractHandler {
/**
* Selection operation codes.
*/
static final int SELECT_NEXT= 1;
static final int SELECT_PREVIOUS= 2;
private final int fOperationCode;
/**
* Creates a new selection handler.
*
* @param operationCode the operation code
* @since 3.4
*/
public ProposalSelectionHandler(int operationCode) {
Assert.isLegal(operationCode == SELECT_NEXT || operationCode == SELECT_PREVIOUS);
fOperationCode= operationCode;
}
@Override
public Object execute(ExecutionEvent event) throws ExecutionException {
int itemCount= fProposalTable.getItemCount();
int selectionIndex= fProposalTable.getSelectionIndex();
switch (fOperationCode) {
case SELECT_NEXT:
selectionIndex+= 1;
if (selectionIndex > itemCount - 1)
selectionIndex= 0;
break;
case SELECT_PREVIOUS:
selectionIndex-= 1;
if (selectionIndex < 0)
selectionIndex= itemCount - 1;
break;
default:
break;
}
selectProposal(selectionIndex, false);
return null;
}
}
/**
* The empty proposal displayed if there is nothing else to show.
*
* @since 3.2
*/
private static final class EmptyProposal implements ICompletionProposal, ICompletionProposalExtension {
String fDisplayString;
int fOffset;
@Override
public void apply(IDocument document) {
}
@Override
public Point getSelection(IDocument document) {
return new Point(fOffset, 0);
}
@Override
public IContextInformation getContextInformation() {
return null;
}
@Override
public Image getImage() {
return null;
}
@Override
public String getDisplayString() {
return fDisplayString;
}
@Override
public String getAdditionalProposalInfo() {
return null;
}
@Override
public void apply(IDocument document, char trigger, int offset) {
}
@Override
public boolean isValidFor(IDocument document, int offset) {
return false;
}
@Override
public char[] getTriggerCharacters() {
return null;
}
@Override
public int getContextInformationPosition() {
return -1;
}
}
private final class ProposalSelectionListener implements KeyListener {
@Override
public void keyPressed(KeyEvent e) {
if (!Helper.okToUse(fProposalShell))
return;
if (e.character == 0 && e.keyCode == SWT.CTRL) {
// http://dev.eclipse.org/bugs/show_bug.cgi?id=34754
int index= fProposalTable.getSelectionIndex();
if (index >= 0)
selectProposal(index, true);
}
}
@Override
public void keyReleased(KeyEvent e) {
if (!Helper.okToUse(fProposalShell))
return;
if (e.character == 0 && e.keyCode == SWT.CTRL) {
// http://dev.eclipse.org/bugs/show_bug.cgi?id=34754
int index= fProposalTable.getSelectionIndex();
if (index >= 0)
selectProposal(index, false);
}
}
}
private final class CommandKeyListener extends KeyAdapter {
private final KeySequence fCommandSequence;
private CommandKeyListener(KeySequence keySequence) {
fCommandSequence= keySequence;
}
@Override
public void keyPressed(KeyEvent e) {
if (!Helper.okToUse(fProposalShell))
return;
int accelerator= SWTKeySupport.convertEventToUnmodifiedAccelerator(e);
KeySequence sequence= KeySequence.getInstance(SWTKeySupport.convertAcceleratorToKeyStroke(accelerator));
if (sequence.equals(fCommandSequence))
if (fContentAssistant.isPrefixCompletionEnabled())
incrementalComplete();
else
showProposals(false);
}
}
/** The associated text viewer. */
private ITextViewer fViewer;
/** The associated content assistant. */
private final ContentAssistant fContentAssistant;
/** The used additional info controller, or null
if none. */
private final AdditionalInfoController fAdditionalInfoController;
/** The closing strategy for this completion proposal popup. */
private final 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 final List fDocumentEvents= new ArrayList<>();
/** Listener filling the document event queue. */
private IDocumentListener fDocumentListener;
/** 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 most recently selected proposal.
* @since 3.0
*/
private ICompletionProposal fLastProposal;
/**
* The content assist subject control.
* This replaces fViewer
*
* @since 3.0
*/
private IContentAssistSubjectControl fContentAssistSubjectControl;
/**
* The content assist subject control adapter.
* This replaces fViewer
*
* @since 3.0
*/
private final 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},
* false
if not.
* @since 3.1
*/
private boolean fIsFilteredSubset;
/**
* The filter runnable.
*
* @since 3.1.1
*/
private final Runnable fFilterRunnable= new Runnable() {
@Override
public void run() {
if (!fIsFilterPending)
return;
fIsFilterPending= false;
if (!Helper.okToUse(fContentAssistSubjectControlAdapter.getControl()))
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)
setProposals(proposals, fIsFilteredSubset);
else
hide();
}
};
/**
* true
if fFilterRunnable
has been
* posted, false
if not.
*
* @since 3.1.1
*/
private boolean fIsFilterPending= false;
/**
* The info message at the bottom of the popup, or null
for no popup (if
* ContentAssistant does not provide one).
*
* @since 3.2
*/
private Label fMessageText;
/**
* The font used for fMessageText
or null; dispose when done.
*
* @since 3.2
*/
private Font fMessageTextFont;
/**
* The most recent completion offset (used to determine repeated invocation)
*
* @since 3.2
*/
private int fLastCompletionOffset;
/**
* The (reusable) empty proposal.
*
* @since 3.2
*/
private final EmptyProposal fEmptyProposal= new EmptyProposal();
/**
* The text for the empty proposal, or null
to use the default text.
*
* @since 3.2
*/
private String fEmptyMessage= null;
/**
* Tells whether colored labels support is enabled.
* Only valid while the popup is active.
*
* @since 3.4
*/
private boolean fIsColoredLabelsSupportEnabled= false;
/**
* The sorter to be used for sorting the proposals or null
if no sorting is
* requested.
*
* @since 3.8
*/
private ICompletionProposalSorter fSorter;
/**
* Set to true by {@link #computeProposals(int)} when initial sorting is performed on the
* computed proposals using {@link #fSorter}.
*
* @since 3.11
*/
private boolean fIsInitialSort;
/**
* Creates a new completion proposal popup for the given elements.
*
* @param contentAssistant the content assistant feeding this popup
* @param viewer the viewer on top of which this popup appears
* @param infoController the information control collaborating with this popup, or null
* @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 content assistant feeding this popup
* @param contentAssistSubjectControl the content assist subject control on top of which this popup appears
* @param infoController the information control collaborating with this popup, or null
* @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 true
if auto activation context
* @return an error message or null
in case of no error
*/
public String showProposals(final boolean autoActivated) {
if (fKeyListener == null)
fKeyListener= new ProposalSelectionListener();
final Control control= fContentAssistSubjectControlAdapter.getControl();
if (!Helper.okToUse(fProposalShell) && 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() {
@Override
public void run() {
fInvocationOffset= fContentAssistSubjectControlAdapter.getSelectedRange().x;
fFilterOffset= fInvocationOffset;
fLastCompletionOffset= fFilterOffset;
fComputedProposals= computeProposals(fInvocationOffset);
int count= (fComputedProposals == null ? 0 : fComputedProposals.length);
if (count == 0 && hideWhenNoProposals(autoActivated))
return;
if (count == 1 && !autoActivated && canAutoInsert(fComputedProposals[0])) {
insertProposal(fComputedProposals[0], (char) 0, 0, fInvocationOffset);
hide();
} else {
createProposalSelector();
setProposals(fComputedProposals, false);
displayProposals();
}
}
});
} else {
fLastCompletionOffset= fFilterOffset;
handleRepeatedInvocation();
}
return getErrorMessage();
}
/**
* Hides the popup and returns true
if the popup is configured
* to never display an empty list. Returns false
otherwise.
*
* @param autoActivated whether the invocation was auto-activated
* @return false
if an empty list should be displayed, true
otherwise
* @since 3.2
*/
private boolean hideWhenNoProposals(boolean autoActivated) {
if (autoActivated || !fContentAssistant.isShowEmptyList()) {
if (!autoActivated) {
Control control= fContentAssistSubjectControlAdapter.getControl();
if (control != null && !control.isDisposed())
control.getDisplay().beep();
}
hide();
return true;
}
return false;
}
/**
* If content assist is set up to handle cycling, then the proposals are recomputed. Otherwise,
* nothing happens.
*
* @since 3.2
*/
private void handleRepeatedInvocation() {
if (fContentAssistant.isRepeatedInvocationMode()) {
fComputedProposals= computeProposals(fFilterOffset);
setProposals(fComputedProposals, false);
}
}
/**
* Returns the completion proposals available at the given offset of the viewer's document.
* Delegates the work to the content assistant. Sorts the computed proposals if sorting is
* requested with {@link #fSorter}.
*
* @param offset the offset
* @return the completion proposals available at this offset
*/
private ICompletionProposal[] computeProposals(int offset) {
ICompletionProposal[] proposals;
if (fContentAssistSubjectControl != null) {
proposals= fContentAssistant.computeCompletionProposals(fContentAssistSubjectControl, offset);
} else {
proposals= fContentAssistant.computeCompletionProposals(fViewer, offset);
}
if (fSorter != null) {
sortProposals(proposals);
fIsInitialSort= true;
}
return proposals;
}
/**
* Returns the error message.
*
* @return the error message
*/
private String getErrorMessage() {
return fContentAssistant.getErrorMessage();
}
/**
* Creates the proposal selector.
*/
private void createProposalSelector() {
if (Helper.okToUse(fProposalShell))
return;
Control control= fContentAssistSubjectControlAdapter.getControl();
fProposalShell= new Shell(control.getShell(), SWT.ON_TOP | SWT.RESIZE );
fProposalShell.setFont(JFaceResources.getDefaultFont());
if (USE_VIRTUAL) {
fProposalTable= new Table(fProposalShell, SWT.H_SCROLL | SWT.V_SCROLL | SWT.VIRTUAL);
Listener listener= new Listener() {
@Override
public void handleEvent(Event event) {
handleSetData(event);
}
};
fProposalTable.addListener(SWT.SetData, listener);
} else {
fProposalTable= new Table(fProposalShell, SWT.H_SCROLL | SWT.V_SCROLL);
}
fIsColoredLabelsSupportEnabled= fContentAssistant.isColoredLabelsSupportEnabled();
if (fIsColoredLabelsSupportEnabled)
TableOwnerDrawSupport.install(fProposalTable);
fProposalTable.setLocation(0, 0);
if (fAdditionalInfoController != null)
fAdditionalInfoController.setSizeConstraints(50, 10, true, true);
GridLayout layout= new GridLayout();
layout.marginWidth= 0;
layout.marginHeight= 0;
layout.verticalSpacing= 1;
fProposalShell.setLayout(layout);
if (fContentAssistant.isStatusLineVisible()) {
createMessageText();
}
GridData data= new GridData(GridData.FILL_BOTH);
Point size= fContentAssistant.restoreCompletionProposalPopupSize();
if (size != null) {
fProposalTable.setLayoutData(data);
fProposalShell.setSize(size);
} else {
int height= fProposalTable.getItemHeight() * 10;
// use golden ratio as default aspect ratio
final double aspectRatio= (1 + Math.sqrt(5)) / 2;
int width= (int) (height * aspectRatio);
Rectangle trim= fProposalTable.computeTrim(0, 0, width, height);
data.heightHint= trim.height;
data.widthHint= trim.width;
fProposalTable.setLayoutData(data);
fProposalShell.pack();
}
fContentAssistant.addToLayout(this, fProposalShell, ContentAssistant.LayoutManager.LAYOUT_PROPOSAL_SELECTOR, fContentAssistant.getSelectionOffset());
fProposalShell.addControlListener(new ControlListener() {
@Override
public void controlMoved(ControlEvent e) {}
@Override
public void controlResized(ControlEvent e) {
if (fAdditionalInfoController != null) {
// reset the cached resize constraints
fAdditionalInfoController.setSizeConstraints(50, 10, true, false);
fAdditionalInfoController.hideInformationControl();
fAdditionalInfoController.handleTableSelectionChanged();
}
fSize= fProposalShell.getSize();
}
});
fProposalShell.setBackground(control.getDisplay().getSystemColor(SWT.COLOR_GRAY));
Color c= getBackgroundColor(control);
fProposalTable.setBackground(c);
c= getForegroundColor(control);
fProposalTable.setForeground(c);
fProposalTable.addSelectionListener(new SelectionListener() {
@Override
public void widgetSelected(SelectionEvent e) {}
@Override
public void widgetDefaultSelected(SelectionEvent e) {
insertSelectedProposalWithMask(e.stateMask);
}
});
fPopupCloser.install(fContentAssistant, fProposalTable, fAdditionalInfoController);
fProposalShell.addDisposeListener(new DisposeListener() {
@Override
public void widgetDisposed(DisposeEvent e) {
unregister(); // but don't dispose the shell, since we're being called from its disposal event!
}
});
fProposalTable.setHeaderVisible(false);
addCommandSupport(fProposalTable);
}
/**
* Returns the minimal required height for the proposal, may return 0 if the popup has not been
* created yet.
*
* @return the minimal height
* @since 3.3
*/
int getMinimalHeight() {
int height= 0;
if (Helper.okToUse(fProposalTable)) {
int items= fProposalTable.getItemHeight() * 10;
Rectangle trim= fProposalTable.computeTrim(0, 0, SWT.DEFAULT, items);
height= trim.height;
}
if (Helper.okToUse(fMessageText))
height+= fMessageText.getSize().y + 1;
return height;
}
/**
* Adds command support to the given control.
*
* @param control the control to watch for focus
* @since 3.2
*/
private void addCommandSupport(final Control control) {
final KeySequence commandSequence= fContentAssistant.getRepeatedInvocationKeySequence();
if (commandSequence != null && !commandSequence.isEmpty() && fContentAssistant.isRepeatedInvocationMode()) {
control.addFocusListener(new FocusListener() {
private CommandKeyListener fCommandKeyListener;
@Override
public void focusGained(FocusEvent e) {
if (Helper.okToUse(control)) {
if (fCommandKeyListener == null) {
fCommandKeyListener= new CommandKeyListener(commandSequence);
fProposalTable.addKeyListener(fCommandKeyListener);
}
}
}
@Override
public void focusLost(FocusEvent e) {
if (fCommandKeyListener != null) {
control.removeKeyListener(fCommandKeyListener);
fCommandKeyListener= null;
}
}
});
}
if (fAdditionalInfoController != null) {
control.addFocusListener(new FocusListener() {
private TraverseListener fTraverseListener;
@Override
public void focusGained(FocusEvent e) {
if (Helper.okToUse(control)) {
if (fTraverseListener == null) {
fTraverseListener= new TraverseListener() {
@Override
public void keyTraversed(TraverseEvent event) {
if (event.detail == SWT.TRAVERSE_TAB_NEXT) {
IInformationControl iControl= fAdditionalInfoController.getCurrentInformationControl2();
if (fAdditionalInfoController.getInternalAccessor().canReplace(iControl)) {
fAdditionalInfoController.getInternalAccessor().replaceInformationControl(true);
event.doit= false;
}
}
}
};
fProposalTable.addTraverseListener(fTraverseListener);
}
}
}
@Override
public void focusLost(FocusEvent e) {
if (fTraverseListener != null) {
control.removeTraverseListener(fTraverseListener);
fTraverseListener= null;
}
}
});
}
}
/**
* 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;
}
/**
* Creates the caption line under the proposal table.
*
* @since 3.2
*/
private void createMessageText() {
if (fMessageText == null) {
fMessageText= new Label(fProposalShell, SWT.RIGHT);
GridData textData= new GridData(SWT.FILL, SWT.BOTTOM, true, false);
fMessageText.setLayoutData(textData);
fMessageText.setText(fContentAssistant.getStatusMessage() + " "); //$NON-NLS-1$
if (fMessageTextFont == null) {
Font font= fMessageText.getFont();
Display display= fProposalShell.getDisplay();
FontData[] fontDatas= font.getFontData();
for (int i= 0; i < fontDatas.length; i++)
fontDatas[i].setHeight(fontDatas[i].getHeight() * 9 / 10);
fMessageTextFont= new Font(display, fontDatas);
}
fMessageText.setFont(fMessageTextFont);
fMessageText.setBackground(getBackgroundColor(fProposalShell));
fMessageText.setForeground(getForegroundColor(fProposalShell));
if (fContentAssistant.isRepeatedInvocationMode()) {
fMessageText.setCursor(fProposalShell.getDisplay().getSystemCursor(SWT.CURSOR_HAND));
fMessageText.addMouseListener(new MouseAdapter() {
@Override
public void mouseUp(MouseEvent e) {
fLastCompletionOffset= fFilterOffset;
fProposalTable.setFocus();
handleRepeatedInvocation();
}
@Override
public void mouseDown(MouseEvent e) {
}
});
}
}
}
/*
* @since 3.1
*/
private void handleSetData(Event event) {
TableItem item= (TableItem) event.item;
int index= fProposalTable.indexOf(item);
if (0 <= index && index < fFilteredProposals.length) {
ICompletionProposal current= fFilteredProposals[index];
String displayString;
StyleRange[] styleRanges= null;
Image image= null;
try {
if (fIsColoredLabelsSupportEnabled && current instanceof ICompletionProposalExtension7 && Helper.okToUse(fProposalShell)) {
BoldStylerProvider boldStylerProvider= fContentAssistant.getBoldStylerProvider();
if (boldStylerProvider == null) {
boldStylerProvider= new BoldStylerProvider(fProposalShell.getFont());
fContentAssistant.setBoldStylerProvider(boldStylerProvider);
}
StyledString styledString= ((ICompletionProposalExtension7) current).getStyledDisplayString(fContentAssistSubjectControlAdapter.getDocument(), fFilterOffset,
boldStylerProvider);
displayString= styledString.getString();
styleRanges= styledString.getStyleRanges();
} else if (fIsColoredLabelsSupportEnabled && current instanceof ICompletionProposalExtension6) {
StyledString styledString= ((ICompletionProposalExtension6) current).getStyledDisplayString();
displayString= styledString.getString();
styleRanges= styledString.getStyleRanges();
} else {
displayString= current.getDisplayString();
}
} catch (RuntimeException e) {
// On failures to retrieve the proposal's text, insert a dummy entry and log the error.
displayString= JFaceTextMessages.getString("CompletionProposalPopup.error_retrieving_proposal"); //$NON-NLS-1$
String PLUGIN_ID= "org.eclipse.jface.text"; //$NON-NLS-1$
ILog log= Platform.getLog(Platform.getBundle(PLUGIN_ID));
log.log(new Status(IStatus.ERROR, PLUGIN_ID, IStatus.OK, JFaceTextMessages.getString("CompletionProposalPopup.unexpected_error"), e)); //$NON-NLS-1$
}
try {
image= current.getImage();
} catch (RuntimeException e) {
// If we are unable to retrieve the proposal's image, leave it blank.
}
item.setText(displayString);
if (fIsColoredLabelsSupportEnabled)
TableOwnerDrawSupport.storeStyleRanges(item, 0, styleRanges);
item.setImage(image);
item.setData(current);
} else {
// this should not happen, but does on win32
}
}
/**
* Returns the proposal selected in the proposal selector.
*
* @return the selected proposal
* @since 2.0
*/
private ICompletionProposal getSelectedProposal() {
/* Make sure that there is no filter runnable pending.
* See https://bugs.eclipse.org/bugs/show_bug.cgi?id=31427
*/
if (fIsFilterPending)
fFilterRunnable.run();
// filter runnable may have hidden the proposals
if (!Helper.okToUse(fProposalTable))
return null;
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 3.2
*/
private void insertSelectedProposalWithMask(int stateMask) {
ICompletionProposal p= getSelectedProposal();
hide();
if (p != null)
insertProposal(p, (char) 0, stateMask, fContentAssistSubjectControlAdapter.getSelectedRange().x);
}
/**
* 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() {
@Override
public boolean isOriginator(DocumentEvent event, IRegion focus) {
return focus.getOffset() <= offset && focus.getOffset() + focus.getLength() >= offset;
}
@Override
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);
}
fireAppliedEvent(p);
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 true
if the popup has the focus
*/
public boolean hasFocus() {
if (Helper.okToUse(fProposalShell)) {
if ((fProposalShell.getDisplay().getActiveShell() == fProposalShell))
return true;
/*
* We have to delegate this query to the additional info controller
* as well, since the content assistant is the widget token owner
* and its closer does not know that the additional info control can
* now also take focus.
*/
if (fAdditionalInfoController != null) {
IInformationControl informationControl= fAdditionalInfoController.getCurrentInformationControl2();
if (informationControl != null && informationControl.isFocusControl())
return true;
InformationControlReplacer replacer= fAdditionalInfoController.getInternalAccessor().getInformationControlReplacer();
if (replacer != null) {
informationControl= replacer.getCurrentInformationControl2();
if (informationControl != null && informationControl.isFocusControl())
return true;
}
}
}
return false;
}
/**
* Hides this popup.
*/
public void hide() {
unregister();
if (fViewer instanceof IEditingSupportRegistry) {
IEditingSupportRegistry registry= (IEditingSupportRegistry) fViewer;
registry.unregister(fFocusHelper);
}
if (Helper.okToUse(fProposalShell)) {
fContentAssistant.removeContentAssistListener(this, ContentAssistant.PROPOSAL_SELECTOR);
fPopupCloser.uninstall();
fProposalShell.setVisible(false);
fProposalShell.dispose();
fProposalShell= null;
}
if (fMessageTextFont != null) {
fMessageTextFont.dispose();
fMessageTextFont= null;
}
if (fMessageText != null) {
fMessageText= null;
}
fEmptyMessage= null;
fLastCompletionOffset= -1;
fContentAssistant.fireSessionEndEvent();
}
/**
* 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 true
if this popup is active
*/
public boolean isActive() {
return fProposalShell != null && !fProposalShell.isDisposed();
}
/**
* Initializes the proposal selector with these given proposals. If a proposal sorter is
* configured, the given proposals are sorted before.
*
* @param proposals the proposals
* @param isFilteredSubset if true
, 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) {
ICompletionProposal[] oldProposals= fFilteredProposals;
ICompletionProposal oldProposal= getSelectedProposal(); // may trigger filtering and a reentrant call to setProposals()
if (oldProposals != fFilteredProposals) // reentrant call was first - abort
return;
if (Helper.okToUse(fProposalTable)) {
if (oldProposal instanceof ICompletionProposalExtension2 && fViewer != null)
((ICompletionProposalExtension2) oldProposal).unselected(fViewer);
if (proposals == null || proposals.length == 0) {
fEmptyProposal.fOffset= fFilterOffset;
fEmptyProposal.fDisplayString= fEmptyMessage != null ? fEmptyMessage : JFaceTextMessages.getString("CompletionProposalPopup.no_proposals"); //$NON-NLS-1$
proposals= new ICompletionProposal[] { fEmptyProposal };
}
if (fSorter != null && !fIsInitialSort) {
sortProposals(proposals);
}
fIsInitialSort= false;
fFilteredProposals= proposals;
final int newLen= proposals.length;
if (USE_VIRTUAL) {
fProposalTable.clearAll();
fProposalTable.setItemCount(newLen);
} else {
fProposalTable.setRedraw(false);
fProposalTable.setItemCount(newLen);
TableItem[] items= fProposalTable.getItems();
for (int i= 0; i < items.length; i++) {
TableItem item= items[i];
ICompletionProposal proposal= proposals[i];
Image image= null;
String displayString= null;
try {
displayString= proposal.getDisplayString();
image= proposal.getImage();
} catch (RuntimeException e) {
// On failures to retrieve the proposal's text, insert a dummy entry and log the error.
String PLUGIN_ID= "org.eclipse.jface.text"; //$NON-NLS-1$
ILog log= Platform.getLog(Platform.getBundle(PLUGIN_ID));
log.log(new Status(IStatus.ERROR, PLUGIN_ID, IStatus.OK, JFaceTextMessages.getString("CompletionProposalPopup.unexpected_error"), e)); //$NON-NLS-1$
displayString= JFaceTextMessages.getString("CompletionProposalPopup.error_retrieving_proposal"); //$NON-NLS-1$
}
item.setText(displayString);
item.setImage(image);
item.setData(proposal);
}
fProposalTable.setRedraw(true);
}
Point currentLocation= fProposalShell.getLocation();
Point newLocation= getLocation();
if ((newLocation.x < currentLocation.x && newLocation.y == currentLocation.y) || newLocation.y < currentLocation.y)
fProposalShell.setLocation(newLocation);
selectProposal(0, false);
}
}
/**
* Returns the graphical location at which this popup should be made visible.
*
* @return the location of this popup
*/
private Point getLocation() {
int caret= fContentAssistSubjectControlAdapter.getCaretOffset();
Rectangle location= fContentAssistant.getLayoutManager().computeBoundsBelowAbove(fProposalShell, fSize == null ? fProposalShell.getSize() : fSize, caret, this);
return Geometry.getLocation(location);
}
/**
* 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)) {
ensureDocumentListenerInstalled();
if (fFocusHelper == null) {
fFocusHelper= new IEditingSupport() {
@Override
public boolean isOriginator(DocumentEvent event, IRegion focus) {
return false; // this helper just covers the focus change to the proposal shell, no remote editions
}
@Override
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();
}
/**
* Installs the document listener if not already done.
*
* @since 3.2
*/
private void ensureDocumentListenerInstalled() {
if (fDocumentListener == null) {
fDocumentListener= new IDocumentListener() {
@Override
public void documentAboutToBeChanged(DocumentEvent event) {
if (!fInserting)
fDocumentEvents.add(event);
}
@Override
public void documentChanged(DocumentEvent event) {
if (!fInserting)
filterProposals();
}
};
IDocument document= fContentAssistSubjectControlAdapter.getDocument();
if (document != null)
document.addDocumentListener(fDocumentListener);
}
}
@Override
public boolean verifyKey(VerifyEvent e) {
if (!Helper.okToUse(fProposalShell))
return true;
char key= e.character;
if (key == 0) {
int newSelection= fProposalTable.getSelectionIndex();
int visibleRows= (fProposalTable.getSize().y / fProposalTable.getItemHeight()) - 1;
int itemCount= fProposalTable.getItemCount();
switch (e.keyCode) {
case SWT.ARROW_LEFT :
case SWT.ARROW_RIGHT :
filterProposals();
return true;
case SWT.ARROW_UP :
newSelection -= 1;
if (newSelection < 0)
newSelection= itemCount - 1;
break;
case SWT.ARROW_DOWN :
newSelection += 1;
if (newSelection > itemCount - 1)
newSelection= 0;
break;
case SWT.PAGE_DOWN :
newSelection += visibleRows;
if (newSelection >= itemCount)
newSelection= itemCount - 1;
break;
case SWT.PAGE_UP :
newSelection -= visibleRows;
if (newSelection < 0)
newSelection= 0;
break;
case SWT.HOME :
newSelection= 0;
break;
case SWT.END :
newSelection= itemCount - 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, (e.stateMask & SWT.CTRL) != 0);
e.doit= false;
return false;
}
// key != 0
switch (key) {
case 0x1B: // Esc
e.doit= false;
hide();
break;
case '\n': // Ctrl-Enter on w2k
case '\r': // Enter
e.doit= false;
insertSelectedProposalWithMask(e.stateMask);
break;
case '\t':
e.doit= false;
fProposalShell.setFocus();
return false;
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 true
if the smart toggle key has been pressed
* @since 2.1
*/
private void selectProposal(int index, boolean smartToggle) {
ICompletionProposal oldProposal= getSelectedProposal();
if (oldProposal instanceof ICompletionProposalExtension2 && fViewer != null)
((ICompletionProposalExtension2) oldProposal).unselected(fViewer);
if (fFilteredProposals == null) {
fireSelectionEvent(null, smartToggle);
return;
}
ICompletionProposal proposal= fFilteredProposals[index];
if (proposal instanceof ICompletionProposalExtension2 && fViewer != null)
((ICompletionProposalExtension2) proposal).selected(fViewer, smartToggle);
fireSelectionEvent(proposal, smartToggle);
fLastProposal= proposal;
fProposalTable.setSelection(index);
fProposalTable.showSelection();
if (fAdditionalInfoController != null)
fAdditionalInfoController.handleTableSelectionChanged();
}
/**
* Fires a selection event, see {@link ICompletionListener}.
*
* @param proposal the selected proposal, possibly null
* @param smartToggle true if the smart toggle is on
* @since 3.2
*/
private void fireSelectionEvent(ICompletionProposal proposal, boolean smartToggle) {
fContentAssistant.fireSelectionEvent(proposal, smartToggle);
}
/**
* Fires an event after applying the given proposal, see {@link ICompletionListenerExtension2}.
*
* @param proposal the applied proposal
* @since 3.8
*/
private void fireAppliedEvent(ICompletionProposal proposal) {
fContentAssistant.fireAppliedEvent(proposal);
}
/**
* 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 true
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;
}
@Override
public void processEvent(VerifyEvent e) {
}
/**
* Filters the displayed proposal based on the given cursor position and the
* offset of the original invocation of the content assistant.
*/
private void filterProposals() {
if (!fIsFilterPending) {
fIsFilterPending= true;
Control control= fContentAssistSubjectControlAdapter.getControl();
control.getDisplay().asyncExec(fFilterRunnable);
}
}
/**
* 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;
fContentAssistant.fireSessionRestartEvent();
fComputedProposals= computeProposals(fInvocationOffset);
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