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);
+    }
+
+}