Mercurial > projects > dwt-addons
diff dwtx/jface/text/contentassist/CompletionProposalPopup.d @ 129:eb30df5ca28b
Added JFace Text sources
author | Frank Benoit <benoit@tionex.de> |
---|---|
date | Sat, 23 Aug 2008 19:10:48 +0200 |
parents | |
children | c4fb132a086c |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/jface/text/contentassist/CompletionProposalPopup.d Sat Aug 23 19:10:48 2008 +0200 @@ -0,0 +1,1819 @@ +/******************************************************************************* + * Copyright (c) 2000, 2008 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, sean_montgomery@comcast.net - https://bugs.eclipse.org/bugs/show_bug.cgi?id=116454 + * Port to the D programming language: + * Frank Benoit <benoit@tionex.de> + *******************************************************************************/ +module dwtx.jface.text.contentassist.CompletionProposalPopup; + +import dwt.dwthelper.utils; + +import java.util.ArrayList; +import java.util.List; + +import dwt.DWT; +import dwt.custom.BusyIndicator; +import dwt.custom.StyleRange; +import dwt.events.ControlEvent; +import dwt.events.ControlListener; +import dwt.events.DisposeEvent; +import dwt.events.DisposeListener; +import dwt.events.FocusEvent; +import dwt.events.FocusListener; +import dwt.events.KeyAdapter; +import dwt.events.KeyEvent; +import dwt.events.KeyListener; +import dwt.events.MouseAdapter; +import dwt.events.MouseEvent; +import dwt.events.SelectionEvent; +import dwt.events.SelectionListener; +import dwt.events.TraverseEvent; +import dwt.events.TraverseListener; +import dwt.events.VerifyEvent; +import dwt.graphics.Color; +import dwt.graphics.Font; +import dwt.graphics.FontData; +import dwt.graphics.Image; +import dwt.graphics.Point; +import dwt.graphics.Rectangle; +import dwt.layout.GridData; +import dwt.layout.GridLayout; +import dwt.widgets.Control; +import dwt.widgets.Display; +import dwt.widgets.Event; +import dwt.widgets.Label; +import dwt.widgets.Listener; +import dwt.widgets.Shell; +import dwt.widgets.Table; +import dwt.widgets.TableItem; +import dwtx.core.commands.AbstractHandler; +import dwtx.core.commands.ExecutionEvent; +import dwtx.core.commands.ExecutionException; +import dwtx.core.commands.IHandler; +import dwtx.core.runtime.Assert; +import dwtx.jface.bindings.keys.KeySequence; +import dwtx.jface.bindings.keys.SWTKeySupport; +import dwtx.jface.contentassist.IContentAssistSubjectControl; +import dwtx.jface.internal.text.InformationControlReplacer; +import dwtx.jface.internal.text.TableOwnerDrawSupport; +import dwtx.jface.preference.JFacePreferences; +import dwtx.jface.resource.JFaceResources; +import dwtx.jface.text.AbstractInformationControlManager; +import dwtx.jface.text.BadLocationException; +import dwtx.jface.text.DocumentEvent; +import dwtx.jface.text.IDocument; +import dwtx.jface.text.IDocumentListener; +import dwtx.jface.text.IEditingSupport; +import dwtx.jface.text.IEditingSupportRegistry; +import dwtx.jface.text.IInformationControl; +import dwtx.jface.text.IRegion; +import dwtx.jface.text.IRewriteTarget; +import dwtx.jface.text.ITextViewer; +import dwtx.jface.text.ITextViewerExtension; +import dwtx.jface.text.TextUtilities; +import dwtx.jface.text.AbstractInformationControlManager.Anchor; +import dwtx.jface.util.Geometry; +import dwtx.jface.viewers.StyledString; + + +/** + * 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 dwtx.jface.text.contentassist.ICompletionProposal + * @see dwtx.jface.text.contentassist.AdditionalInfoController + */ +class CompletionProposalPopup : IContentAssistListener { + /** + * Set to <code>true</code> to use a Table with DWT.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 bool USE_VIRTUAL= !"motif".equals(DWT.getPlatform()); //$NON-NLS-1$ + + + /** + * Completion proposal selection handler. + * + * @since 3.4 + */ + final class ProposalSelectionHandler : AbstractHandler { + + /** + * Selection operation codes. + */ + static final int SELECT_NEXT= 1; + static final int SELECT_PREVIOUS= 2; + + + private int fOperationCode; + + /** + * Creates a new selection handler. + * + * @param operationCode the operation code + * @since 3.4 + */ + public ProposalSelectionHandler(int operationCode) { + Assert.isLegal(operationCode is SELECT_NEXT || operationCode is SELECT_PREVIOUS); + fOperationCode= operationCode; + } + + /* + * @see dwtx.core.commands.AbstractHandler#execute(dwtx.core.commands.ExecutionEvent) + * @since 3.4 + */ + 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; + } + selectProposal(selectionIndex, false); + return null; + } + + } + + + /** + * The empty proposal displayed if there is nothing else to show. + * + * @since 3.2 + */ + private static final class EmptyProposal : ICompletionProposal, ICompletionProposalExtension { + + String fDisplayString; + int fOffset; + /* + * @see ICompletionProposal#apply(IDocument) + */ + public void apply(IDocument document) { + } + + /* + * @see ICompletionProposal#getSelection(IDocument) + */ + public Point getSelection(IDocument document) { + return new Point(fOffset, 0); + } + + /* + * @see ICompletionProposal#getContextInformation() + */ + public IContextInformation getContextInformation() { + return null; + } + + /* + * @see ICompletionProposal#getImage() + */ + public Image getImage() { + return null; + } + + /* + * @see ICompletionProposal#getDisplayString() + */ + public String getDisplayString() { + return fDisplayString; + } + + /* + * @see ICompletionProposal#getAdditionalProposalInfo() + */ + public String getAdditionalProposalInfo() { + return null; + } + + /* + * @see dwtx.jface.text.contentassist.ICompletionProposalExtension#apply(dwtx.jface.text.IDocument, char, int) + */ + public void apply(IDocument document, char trigger, int offset) { + } + + /* + * @see dwtx.jface.text.contentassist.ICompletionProposalExtension#isValidFor(dwtx.jface.text.IDocument, int) + */ + public bool isValidFor(IDocument document, int offset) { + return false; + } + + /* + * @see dwtx.jface.text.contentassist.ICompletionProposalExtension#getTriggerCharacters() + */ + public char[] getTriggerCharacters() { + return null; + } + + /* + * @see dwtx.jface.text.contentassist.ICompletionProposalExtension#getContextInformationPosition() + */ + public int getContextInformationPosition() { + return -1; + } + } + + private final class ProposalSelectionListener : KeyListener { + public void keyPressed(KeyEvent e) { + if (!Helper.okToUse(fProposalShell)) + return; + + if (e.character is 0 && e.keyCode is DWT.CTRL) { + // http://dev.eclipse.org/bugs/show_bug.cgi?id=34754 + int index= fProposalTable.getSelectionIndex(); + if (index >= 0) + selectProposal(index, true); + } + } + + public void keyReleased(KeyEvent e) { + if (!Helper.okToUse(fProposalShell)) + return; + + if (e.character is 0 && e.keyCode is DWT.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 : KeyAdapter { + private KeySequence fCommandSequence; + + private CommandKeyListener(KeySequence keySequence) { + fCommandSequence= keySequence; + } + + 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 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 bool fInserting= false; + /** The key listener to control navigation. */ + private ProposalSelectionListener fKeyListener; + /** List of document events used for filtering proposals. */ + private 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 <code>fViewer</code> + * + * @since 3.0 + */ + private IContentAssistSubjectControl fContentAssistSubjectControl; + /** + * The content 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 bool fIsFilteredSubset; + /** + * The filter runnable. + * + * @since 3.1.1 + */ + private final Runnable fFilterRunnable= new Runnable() { + 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 !is null && proposals.length > 0) + setProposals(proposals, fIsFilteredSubset); + else + hide(); + } + }; + /** + * <code>true</code> if <code>fFilterRunnable</code> has been + * posted, <code>false</code> if not. + * + * @since 3.1.1 + */ + private bool fIsFilterPending= false; + /** + * The info message at the bottom of the popup, or <code>null</code> for no popup (if + * ContentAssistant does not provide one). + * + * @since 3.2 + */ + private Label fMessageText; + /** + * The font used for <code>fMessageText</code> 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 <code>null</code> 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 bool fIsColoredLabelsSupportEnabled= false; + + + /** + * 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 + * @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 + * @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 bool autoActivated) { + + if (fKeyListener is null) + fKeyListener= new ProposalSelectionListener(); + + final Control control= fContentAssistSubjectControlAdapter.getControl(); + + if (!Helper.okToUse(fProposalShell) && control !is 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; + fLastCompletionOffset= fFilterOffset; + fComputedProposals= computeProposals(fInvocationOffset); + + int count= (fComputedProposals is null ? 0 : fComputedProposals.length); + if (count is 0 && hideWhenNoProposals(autoActivated)) + return; + + if (count is 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 <code>true</code> if the popup is configured + * to never display an empty list. Returns <code>false</code> otherwise. + * + * @param autoActivated whether the invocation was auto-activated + * @return <code>false</code> if an empty list should be displayed, <code>true</code> otherwise + * @since 3.2 + */ + private bool hideWhenNoProposals(bool autoActivated) { + if (autoActivated || !fContentAssistant.isShowEmptyList()) { + if (!autoActivated) { + Control control= fContentAssistSubjectControlAdapter.getControl(); + if (control !is 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 proposal available at the given offset of the + * viewer's document. Delegates the work to the content assistant. + * + * @param offset the offset + * @return the completion proposals available at this offset + */ + private ICompletionProposal[] computeProposals(int offset) { + if (fContentAssistSubjectControl !is null) + return fContentAssistant.computeCompletionProposals(fContentAssistSubjectControl, offset); + return fContentAssistant.computeCompletionProposals(fViewer, offset); + } + + /** + * 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(), DWT.ON_TOP | DWT.RESIZE ); + fProposalShell.setFont(JFaceResources.getDefaultFont()); + if (USE_VIRTUAL) { + fProposalTable= new Table(fProposalShell, DWT.H_SCROLL | DWT.V_SCROLL | DWT.VIRTUAL); + + Listener listener= new Listener() { + public void handleEvent(Event event) { + handleSetData(event); + } + }; + fProposalTable.addListener(DWT.SetData, listener); + } else { + fProposalTable= new Table(fProposalShell, DWT.H_SCROLL | DWT.V_SCROLL); + } + + fIsColoredLabelsSupportEnabled= fContentAssistant.isColoredLabelsSupportEnabled(); + if (fIsColoredLabelsSupportEnabled) + TableOwnerDrawSupport.install(fProposalTable); + + fProposalTable.setLocation(0, 0); + if (fAdditionalInfoController !is 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 !is 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() { + + public void controlMoved(ControlEvent e) {} + + public void controlResized(ControlEvent e) { + if (fAdditionalInfoController !is null) { + // reset the cached resize constraints + fAdditionalInfoController.setSizeConstraints(50, 10, true, false); + } + + fSize= fProposalShell.getSize(); + } + }); + + fProposalShell.setBackground(control.getDisplay().getSystemColor(DWT.COLOR_GRAY)); + + 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) { + insertSelectedProposalWithMask(e.stateMask); + } + }); + + fPopupCloser.install(fContentAssistant, fProposalTable, fAdditionalInfoController); + + fProposalShell.addDisposeListener(new DisposeListener() { + 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, DWT.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 !is null && !commandSequence.isEmpty() && fContentAssistant.isRepeatedInvocationMode()) { + control.addFocusListener(new FocusListener() { + private CommandKeyListener fCommandKeyListener; + public void focusGained(FocusEvent e) { + if (Helper.okToUse(control)) { + if (fCommandKeyListener is null) { + fCommandKeyListener= new CommandKeyListener(commandSequence); + fProposalTable.addKeyListener(fCommandKeyListener); + } + } + } + public void focusLost(FocusEvent e) { + if (fCommandKeyListener !is null) { + control.removeKeyListener(fCommandKeyListener); + fCommandKeyListener= null; + } + } + }); + } + control.addFocusListener(new FocusListener() { + private TraverseListener fTraverseListener; + public void focusGained(FocusEvent e) { + if (Helper.okToUse(control)) { + if (fTraverseListener is null) { + fTraverseListener= new TraverseListener() { + public void keyTraversed(TraverseEvent event) { + if (event.detail is DWT.TRAVERSE_TAB_NEXT) { + IInformationControl iControl= fAdditionalInfoController.getCurrentInformationControl2(); + if (fAdditionalInfoController.getInternalAccessor().canReplace(iControl)) { + fAdditionalInfoController.getInternalAccessor().replaceInformationControl(true); + event.doit= false; + } + } + } + }; + fProposalTable.addTraverseListener(fTraverseListener); + } + } + } + public void focusLost(FocusEvent e) { + if (fTraverseListener !is 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 is 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 is 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 is null) { + fMessageText= new Label(fProposalShell, DWT.RIGHT); + GridData textData= new GridData(DWT.FILL, DWT.BOTTOM, true, false); + fMessageText.setLayoutData(textData); + fMessageText.setText(fContentAssistant.getStatusMessage() + " "); //$NON-NLS-1$ + if (fMessageTextFont is 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(DWT.CURSOR_HAND)); + fMessageText.addMouseListener(new MouseAdapter() { + public void mouseUp(MouseEvent e) { + fLastCompletionOffset= fFilterOffset; + fProposalTable.setFocus(); + handleRepeatedInvocation(); + } + + 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; + if (fIsColoredLabelsSupportEnabled && current instanceof ICompletionProposalExtension6) { + StyledString styledString= ((ICompletionProposalExtension6)current).getStyledDisplayString(); + displayString= styledString.getString(); + styleRanges= styledString.getStyleRanges(); + } else + displayString= current.getDisplayString(); + + item.setText(displayString); + if (fIsColoredLabelsSupportEnabled) + TableOwnerDrawSupport.storeStyleRanges(item, 0, styleRanges); + + item.setImage(current.getImage()); + 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 is 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 !is 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() { + + public bool isOriginator(DocumentEvent event, IRegion focus) { + return focus.getOffset() <= offset && focus.getOffset() + focus.getLength() >= offset; + } + + public bool ownsFocusShell() { + return false; + } + + }; + + try { + + IDocument document= fContentAssistSubjectControlAdapter.getDocument(); + + if (fViewer instanceof ITextViewerExtension) { + ITextViewerExtension extension= (ITextViewerExtension) fViewer; + target= extension.getRewriteTarget(); + } + + if (target !is null) + target.beginCompoundChange(); + + if (fViewer instanceof IEditingSupportRegistry) { + IEditingSupportRegistry registry= (IEditingSupportRegistry) fViewer; + registry.register(helper); + } + + + if (p instanceof ICompletionProposalExtension2 && fViewer !is 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 !is null) { + fContentAssistSubjectControlAdapter.setSelectedRange(selection.x, selection.y); + fContentAssistSubjectControlAdapter.revealRange(selection.x, selection.y); + } + + IContextInformation info= p.getContextInformation(); + if (info !is null) { + + int contextInformationOffset; + if (p instanceof ICompletionProposalExtension) { + ICompletionProposalExtension e= (ICompletionProposalExtension) p; + contextInformationOffset= e.getContextInformationPosition(); + } else { + if (selection is null) + selection= fContentAssistSubjectControlAdapter.getSelectedRange(); + contextInformationOffset= selection.x + selection.y; + } + + fContentAssistant.showContextInformation(info, contextInformationOffset); + } else + fContentAssistant.showContextInformation(null, -1); + + + } finally { + if (target !is 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 bool hasFocus() { + if (Helper.okToUse(fProposalShell)) { + if ((fProposalShell.isFocusControl() || fProposalTable.isFocusControl())) + 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 !is null) { + IInformationControl informationControl= fAdditionalInfoController.getCurrentInformationControl2(); + if (informationControl !is null && informationControl.isFocusControl()) + return true; + InformationControlReplacer replacer= fAdditionalInfoController.getInternalAccessor().getInformationControlReplacer(); + if (replacer !is null) { + informationControl= replacer.getCurrentInformationControl2(); + if (informationControl !is 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 !is null) { + fMessageTextFont.dispose(); + fMessageTextFont= null; + } + + if (fMessageText !is null) { + fMessageText= null; + } + + fEmptyMessage= null; + + fLastCompletionOffset= -1; + + fContentAssistant.fireSessionEndEvent(); + } + + /** + * Unregister this completion proposal popup. + * + * @since 3.0 + */ + private void unregister() { + if (fDocumentListener !is null) { + IDocument document= fContentAssistSubjectControlAdapter.getDocument(); + if (document !is null) + document.removeDocumentListener(fDocumentListener); + fDocumentListener= null; + } + fDocumentEvents.clear(); + + if (fKeyListener !is null && fContentAssistSubjectControlAdapter.getControl() !is null && !fContentAssistSubjectControlAdapter.getControl().isDisposed()) { + fContentAssistSubjectControlAdapter.removeKeyListener(fKeyListener); + fKeyListener= null; + } + + if (fLastProposal !is null) { + if (fLastProposal instanceof ICompletionProposalExtension2 && fViewer !is 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 bool isActive() { + return fProposalShell !is null && !fProposalShell.isDisposed(); + } + + /** + * 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, bool isFilteredSubset) { + ICompletionProposal[] oldProposals= fFilteredProposals; + ICompletionProposal oldProposal= getSelectedProposal(); // may trigger filtering and a reentrant call to setProposals() + if (oldProposals !is fFilteredProposals) // reentrant call was first - abort + return; + + if (Helper.okToUse(fProposalTable)) { + if (oldProposal instanceof ICompletionProposalExtension2 && fViewer !is null) + ((ICompletionProposalExtension2) oldProposal).unselected(fViewer); + + if (proposals is null || proposals.length is 0) { + fEmptyProposal.fOffset= fFilterOffset; + fEmptyProposal.fDisplayString= fEmptyMessage !is null ? fEmptyMessage : JFaceTextMessages.getString("CompletionProposalPopup.no_proposals"); //$NON-NLS-1$ + proposals= new ICompletionProposal[] { fEmptyProposal }; + } + + fFilteredProposals= proposals; + final int newLen= proposals.length; + if (USE_VIRTUAL) { + fProposalTable.setItemCount(newLen); + fProposalTable.clearAll(); + } 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]; + item.setText(proposal.getDisplayString()); + item.setImage(proposal.getImage()); + item.setData(proposal); + } + fProposalTable.setRedraw(true); + } + + Point currentLocation= fProposalShell.getLocation(); + Point newLocation= getLocation(); + if ((newLocation.x < currentLocation.x && newLocation.y is 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 is 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 is null) { + fFocusHelper= new IEditingSupport() { + + public bool isOriginator(DocumentEvent event, IRegion focus) { + return false; // this helper just covers the focus change to the proposal shell, no remote editions + } + + public bool 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 !is 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 is 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 !is null) + document.addDocumentListener(fDocumentListener); + } + } + + /* + * @see IContentAssistListener#verifyKey(VerifyEvent) + */ + public bool verifyKey(VerifyEvent e) { + if (!Helper.okToUse(fProposalShell)) + return true; + + char key= e.character; + if (key is 0) { + int newSelection= fProposalTable.getSelectionIndex(); + int visibleRows= (fProposalTable.getSize().y / fProposalTable.getItemHeight()) - 1; + int itemCount= fProposalTable.getItemCount(); + bool smartToggle= false; + switch (e.keyCode) { + + case DWT.ARROW_LEFT : + case DWT.ARROW_RIGHT : + filterProposals(); + return true; + + case DWT.ARROW_UP : + newSelection -= 1; + if (newSelection < 0) + newSelection= itemCount - 1; + break; + + case DWT.ARROW_DOWN : + newSelection += 1; + if (newSelection > itemCount - 1) + newSelection= 0; + break; + + case DWT.PAGE_DOWN : + newSelection += visibleRows; + if (newSelection >= itemCount) + newSelection= itemCount - 1; + break; + + case DWT.PAGE_UP : + newSelection -= visibleRows; + if (newSelection < 0) + newSelection= 0; + break; + + case DWT.HOME : + newSelection= 0; + break; + + case DWT.END : + newSelection= itemCount - 1; + break; + + default : + if (e.keyCode !is DWT.CAPS_LOCK && e.keyCode !is DWT.MOD1 && e.keyCode !is DWT.MOD2 && e.keyCode !is DWT.MOD3 && e.keyCode !is DWT.MOD4) + hide(); + return true; + } + + selectProposal(newSelection, smartToggle); + + e.doit= false; + return false; + + } + + // key !is 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 <code>true</code> if the smart toggle key has been pressed + * @since 2.1 + */ + private void selectProposal(int index, bool smartToggle) { + + ICompletionProposal oldProposal= getSelectedProposal(); + if (oldProposal instanceof ICompletionProposalExtension2 && fViewer !is null) + ((ICompletionProposalExtension2) oldProposal).unselected(fViewer); + + if (fFilteredProposals is null) { + fireSelectionEvent(null, smartToggle); + return; + } + + ICompletionProposal proposal= fFilteredProposals[index]; + if (proposal instanceof ICompletionProposalExtension2 && fViewer !is null) + ((ICompletionProposalExtension2) proposal).selected(fViewer, smartToggle); + + fireSelectionEvent(proposal, smartToggle); + + fLastProposal= proposal; + + fProposalTable.setSelection(index); + fProposalTable.showSelection(); + if (fAdditionalInfoController !is null) + fAdditionalInfoController.handleTableSelectionChanged(); + } + + /** + * Fires a selection event, see {@link ICompletionListener}. + * + * @param proposal the selected proposal, possibly <code>null</code> + * @param smartToggle true if the smart toggle is on + * @since 3.2 + */ + private void fireSelectionEvent(ICompletionProposal proposal, bool smartToggle) { + fContentAssistant.fireSelectionEvent(proposal, smartToggle); + } + + /** + * 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 bool contains(char[] characters, char c) { + + if (characters is null) + return false; + + for (int i= 0; i < characters.length; i++) { + if (c is characters[i]) + return true; + } + + return false; + } + + /* + * @see IEventConsumer#processEvent(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 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 is fInvocationOffset && event is 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 is 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; + fContentAssistant.fireSessionRestartEvent(); + fComputedProposals= computeProposals(fInvocationOffset); + 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 bool 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 !is null) { + if (fLastCompletionOffset is fFilterOffset) { + handleRepeatedInvocation(); + } else { + fLastCompletionOffset= fFilterOffset; + completeCommonPrefix(); + } + } else { + final Control control= fContentAssistSubjectControlAdapter.getControl(); + + if (fKeyListener is 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; + fLastCompletionOffset= fFilterOffset; + fFilteredProposals= computeProposals(fInvocationOffset); + + int count= (fFilteredProposals is null ? 0 : fFilteredProposals.length); + if (count is 0 && hideWhenNoProposals(false)) + return; + + if (count is 1 && canAutoInsert(fFilteredProposals[0])) { + insertProposal(fFilteredProposals[0], (char) 0, 0, fInvocationOffset); + hide(); + } else { + ensureDocumentListenerInstalled(); + if (count > 0 && completeCommonPrefix()) + hide(); + else { + fComputedProposals= fFilteredProposals; + createProposalSelector(); + setProposals(fComputedProposals, false); + displayProposals(); + } + } + } + }); + } + return getErrorMessage(); + } + + /** + * 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> otherwise + * @since 3.0 + */ + private bool completeCommonPrefix() { + + // 0: insert single proposals + if (fFilteredProposals.length is 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(); + + bool isWrongCaseMatch= false; + + // 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]; + + if (!(proposal instanceof ICompletionProposalExtension3)) + return false; + + int start= ((ICompletionProposalExtension3)proposal).getPrefixCompletionStart(fContentAssistSubjectControlAdapter.getDocument(), fFilterOffset); + CharSequence insertion= ((ICompletionProposalExtension3)proposal).getPrefixCompletionText(fContentAssistSubjectControlAdapter.getDocument(), fFilterOffset); + if (insertion is null) + insertion= proposal.getDisplayString(); + try { + int prefixLength= fFilterOffset - start; + int relativeCompletionOffset= Math.min(insertion.length(), prefixLength); + String prefix= document.get(start, prefixLength); + if (!isWrongCaseMatch && insertion.toString().startsWith(prefix)) { + isWrongCaseMatch= false; + rightCase.add(proposal); + CharSequence newPostfix= insertion.subSequence(relativeCompletionOffset, insertion.length()); + if (rightCasePostfix is null) + rightCasePostfix= new StringBuffer(newPostfix.toString()); + else + truncatePostfix(rightCasePostfix, newPostfix); + } else if (i is 0 || isWrongCaseMatch) { + CharSequence newPrefix= insertion.subSequence(0, relativeCompletionOffset); + if (isPrefixCompatible(wrongCasePrefix, wrongCasePrefixStart, newPrefix, start, document)) { + isWrongCaseMatch= true; + wrongCasePrefix= newPrefix; + wrongCasePrefixStart= start; + CharSequence newPostfix= insertion.subSequence(relativeCompletionOffset, insertion.length()); + if (wrongCasePostfix is null) + wrongCasePostfix= new StringBuffer(newPostfix.toString()); + else + truncatePostfix(wrongCasePostfix, newPostfix); + wrongCase.add(proposal); + } else { + return false; + } + } else + return false; + } catch (BadLocationException e2) { + // bail out silently + return false; + } + + if (rightCasePostfix !is null && rightCasePostfix.length() is 0 && rightCase.size() > 1) + return false; + } + + // 2: replace single proposals + + if (rightCase.size() is 1) { + ICompletionProposal proposal= (ICompletionProposal) rightCase.get(0); + if (canAutoInsert(proposal) && rightCasePostfix.length() > 0) { + insertProposal(proposal, (char) 0, 0, fInvocationOffset); + hide(); + return true; + } + return false; + } else if (isWrongCaseMatch && wrongCase.size() is 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 (isWrongCaseMatch) + prefix= wrongCasePrefix; + else + prefix= ""; //$NON-NLS-1$ + + CharSequence postfix; + if (isWrongCaseMatch) + postfix= wrongCasePostfix; + else + postfix= rightCasePostfix; + + if (prefix is null || postfix is 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); + fFilterOffset+= postfix.length(); + fLastCompletionOffset= fFilterOffset; + + return false; + } catch (BadLocationException e) { + // ignore and return false + return false; + } + } + + /* + * @since 3.1 + */ + private bool isPrefixCompatible(CharSequence oneSequence, int oneOffset, CharSequence twoSequence, int twoOffset, IDocument document) throws BadLocationException { + if (oneSequence is null || twoSequence is 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) !is buffer.charAt(c)) { + buffer.delete(c, buffer.length()); + return; + } + } + + // all equal up to minimum + buffer.delete(min, buffer.length()); + } + + /** + * Sets the message for the repetition affordance text at the bottom of the proposal. Only has + * an effect if {@link ContentAssistant#isRepeatedInvocationMode()} returns <code>true</code>. + * + * @param message the new caption + * @since 3.2 + */ + void setMessage(String message) { + Assert.isNotNull(message); + if (isActive() && fMessageText !is null) + fMessageText.setText(message + " "); //$NON-NLS-1$ + } + + /** + * Sets the text to be displayed if no proposals are available. Only has an effect if + * {@link ContentAssistant#isShowEmptyList()} returns <code>true</code>. + * + * @param message the empty message + * @since 3.2 + */ + void setEmptyMessage(String message) { + Assert.isNotNull(message); + fEmptyMessage= message; + } + + /** + * Enables or disables showing of the caption line. See also {@link #setMessage(String)}. + * + * @param show + * @since 3.2 + */ + public void setStatusLineVisible(bool show) { + if (!isActive() || show is (fMessageText !is null)) + return; // nothing to do + + if (show) { + createMessageText(); + } else { + fMessageText.dispose(); + fMessageText= null; + } + fProposalShell.layout(); + } + + /** + * Informs the popup that it is being placed above the caret line instead of below. + * + * @param above <code>true</code> if the location of the popup is above the caret line, <code>false</code> if it is below + * @since 3.3 + */ + void switchedPositionToAbove(bool above) { + if (fAdditionalInfoController !is null) { + fAdditionalInfoController.setFallbackAnchors(new Anchor[] { + AbstractInformationControlManager.ANCHOR_RIGHT, + AbstractInformationControlManager.ANCHOR_LEFT, + above ? AbstractInformationControlManager.ANCHOR_TOP : AbstractInformationControlManager.ANCHOR_BOTTOM + }); + } + } + + /** + * Returns a new proposal selection handler. + * + * @param operationCode the operation code + * @return the handler + * @since 3.4 + */ + IHandler createProposalSelectionHandler(int operationCode) { + return new ProposalSelectionHandler(operationCode); + } + +}