Skip to main content
aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorBoykoAlex2019-09-13 19:46:14 +0000
committerMickael Istria2019-09-20 07:37:24 +0000
commitd82a4a4095918a66621d6c589379d3ba9b10a03f (patch)
tree9ccc94b68a8f3d59f1170480e1018a3f07341dfa
parentb4ca5bddb0bb5b94ee079ba332f8d7e79e6b0047 (diff)
downloadeclipse.platform.text-d82a4a4095918a66621d6c589379d3ba9b10a03f.tar.gz
eclipse.platform.text-d82a4a4095918a66621d6c589379d3ba9b10a03f.tar.xz
eclipse.platform.text-d82a4a4095918a66621d6c589379d3ba9b10a03f.zip
Bug 550920 - Recompute proposals if some cannot be filtered out
Change-Id: I51282e29252b8e27d318fceed7d9634cb708f2e0 Signed-off-by: BoykoAlex <aboyko@pivotal.io>
-rw-r--r--org.eclipse.jface.text.tests/src/org/eclipse/jface/text/tests/JFaceTextTestSuite.java2
-rw-r--r--org.eclipse.jface.text.tests/src/org/eclipse/jface/text/tests/contentassist/FilteringAsyncContentAssistTests.java581
-rw-r--r--org.eclipse.jface.text/src/org/eclipse/jface/text/contentassist/AsyncCompletionProposalPopup.java35
3 files changed, 603 insertions, 15 deletions
diff --git a/org.eclipse.jface.text.tests/src/org/eclipse/jface/text/tests/JFaceTextTestSuite.java b/org.eclipse.jface.text.tests/src/org/eclipse/jface/text/tests/JFaceTextTestSuite.java
index 5790030af..1a315f140 100644
--- a/org.eclipse.jface.text.tests/src/org/eclipse/jface/text/tests/JFaceTextTestSuite.java
+++ b/org.eclipse.jface.text.tests/src/org/eclipse/jface/text/tests/JFaceTextTestSuite.java
@@ -20,6 +20,7 @@ import org.junit.runners.Suite.SuiteClasses;
import org.eclipse.jface.text.tests.codemining.CodeMiningProjectionViewerTest;
import org.eclipse.jface.text.tests.codemining.CodeMiningTest;
import org.eclipse.jface.text.tests.contentassist.AsyncContentAssistTest;
+import org.eclipse.jface.text.tests.contentassist.FilteringAsyncContentAssistTests;
import org.eclipse.jface.text.tests.reconciler.AbstractReconcilerTest;
import org.eclipse.jface.text.tests.rules.DefaultPartitionerTest;
import org.eclipse.jface.text.tests.rules.DefaultPartitionerZeroLengthTest;
@@ -49,6 +50,7 @@ import org.eclipse.jface.text.tests.templates.persistence.TemplatePersistenceDat
DefaultPairMatcherTest.class,
DefaultPairMatcherTest2.class,
AsyncContentAssistTest.class,
+ FilteringAsyncContentAssistTests.class,
AbstractReconcilerTest.class,
diff --git a/org.eclipse.jface.text.tests/src/org/eclipse/jface/text/tests/contentassist/FilteringAsyncContentAssistTests.java b/org.eclipse.jface.text.tests/src/org/eclipse/jface/text/tests/contentassist/FilteringAsyncContentAssistTests.java
new file mode 100644
index 000000000..d7e4882d5
--- /dev/null
+++ b/org.eclipse.jface.text.tests/src/org/eclipse/jface/text/tests/contentassist/FilteringAsyncContentAssistTests.java
@@ -0,0 +1,581 @@
+/*******************************************************************************
+ * Copyright (c) 2019 Pivotal, Inc.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Pivotal, Inc. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.jface.text.tests.contentassist;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import java.lang.reflect.Field;
+import java.util.List;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Test;
+
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.graphics.Point;
+import org.eclipse.swt.widgets.Shell;
+
+import org.eclipse.text.edits.InsertEdit;
+
+import org.eclipse.jface.text.BadLocationException;
+import org.eclipse.jface.text.Document;
+import org.eclipse.jface.text.DocumentEvent;
+import org.eclipse.jface.text.IDocument;
+import org.eclipse.jface.text.ITextViewer;
+import org.eclipse.jface.text.contentassist.ContentAssistant;
+import org.eclipse.jface.text.contentassist.ContextInformationValidator;
+import org.eclipse.jface.text.contentassist.ICompletionProposal;
+import org.eclipse.jface.text.contentassist.ICompletionProposalExtension;
+import org.eclipse.jface.text.contentassist.ICompletionProposalExtension2;
+import org.eclipse.jface.text.contentassist.IContentAssistProcessor;
+import org.eclipse.jface.text.contentassist.IContextInformation;
+import org.eclipse.jface.text.contentassist.IContextInformationValidator;
+import org.eclipse.jface.text.source.SourceViewer;
+import org.eclipse.jface.text.tests.util.DisplayHelper;
+
+/**
+ * Tests for Async completion proposal popup proposals filtering mechanics
+ *
+ * @author Alex Boyko
+ *
+ */
+public class FilteringAsyncContentAssistTests {
+
+ private Shell shell;
+ private SourceViewer viewer;
+ private ContentAssistant ca;
+
+ @Before
+ public void setup() {
+ tearDown();
+
+ shell = new Shell();
+ shell.setSize(300, 300);
+ shell.open();
+
+ viewer = new SourceViewer(shell, null, SWT.NONE);
+ Document document = new Document();
+ viewer.setDocument(document);
+ ca = new ContentAssistant(true);
+ }
+
+ @After
+ public void tearDown() {
+ if (shell != null) {
+ ca.uninstall();
+ if (!shell.isDisposed()) {
+ shell.dispose();
+ }
+ shell = null;
+ }
+ }
+
+ /**
+ * Simple CA with 1 immediate CA processor. Empty text, invoke CA, verify 1
+ * proposal, apply it, verify the resultant text
+ *
+ * @throws Exception exception
+ */
+ @Test
+ public void testSimpleCa() throws Exception {
+
+ ca.addContentAssistProcessor(new ImmediateContentAssistProcessor("xx"), IDocument.DEFAULT_CONTENT_TYPE);
+
+ ca.install(viewer);
+
+ viewer.setSelectedRange(0, 0);
+
+ ca.showPossibleCompletions();
+
+ DisplayHelper.sleep(shell.getDisplay(), 300);
+
+ List<ICompletionProposal> computedProposals = getComputedProposals(ca);
+
+ assertEquals(1, computedProposals.size());
+
+ ICompletionProposal proposal = computedProposals.get(0);
+
+ IDocument document = viewer.getDocument();
+
+ proposal.apply(document);
+
+ assertEquals("xx", document.get());
+ }
+
+ /**
+ * Simple CA with filtering with 2 immediate CA processors. Empty text
+ * initially. Invoke CA, verify 2 proposals, type 'x', verify only 1 proposal 1
+ *
+ * @throws Exception exception
+ */
+ @Test
+ public void testFilteredCa() throws Exception {
+ IDocument document = viewer.getDocument();
+
+ ca.addContentAssistProcessor(new ImmediateContentAssistProcessor("xx"), IDocument.DEFAULT_CONTENT_TYPE);
+ ca.addContentAssistProcessor(new ImmediateContentAssistProcessor("yy"), IDocument.DEFAULT_CONTENT_TYPE);
+
+ ca.install(viewer);
+
+ viewer.setSelectedRange(1, 0);
+
+ ca.showPossibleCompletions();
+
+ DisplayHelper.sleep(shell.getDisplay(), 300);
+
+ List<ICompletionProposal> computedProposals = getComputedProposals(ca);
+ assertEquals(2, computedProposals.size());
+ List<ICompletionProposal> filteredProposals = getFilteredProposals(ca);
+ assertEquals(2, filteredProposals.size());
+
+ new InsertEdit(0, "x").apply(document);
+ viewer.setSelectedRange(1, 0);
+
+ DisplayHelper.sleep(shell.getDisplay(), 300);
+
+ computedProposals = getComputedProposals(ca);
+ assertEquals(2, computedProposals.size());
+ filteredProposals = getFilteredProposals(ca);
+ assertEquals(1, filteredProposals.size());
+
+ ((ICompletionProposalExtension) filteredProposals.get(0)).apply(document, (char) 0,
+ viewer.getSelectedRange().x);
+ assertEquals("xxx", document.get());
+ }
+
+ /**
+ * Simple CA with filtering with 1 immediate CA processors. Empty text
+ * initially. Invoke CA, verify 1 proposal, type 'a', verify no proposals
+ *
+ * @throws Exception exception
+ */
+ @Test
+ public void testFilteredCa_AllFilteredOut() throws Exception {
+ IDocument document = viewer.getDocument();
+
+ ca.addContentAssistProcessor(new ImmediateContentAssistProcessor("xx"), IDocument.DEFAULT_CONTENT_TYPE);
+
+ ca.install(viewer);
+
+ viewer.setSelectedRange(1, 0);
+
+ ca.showPossibleCompletions();
+
+ DisplayHelper.sleep(shell.getDisplay(), 300);
+
+ List<ICompletionProposal> computedProposals = getComputedProposals(ca);
+ assertEquals(1, computedProposals.size());
+ List<ICompletionProposal> filteredProposals = getFilteredProposals(ca);
+ assertEquals(1, filteredProposals.size());
+
+ new InsertEdit(0, "a").apply(document);
+ viewer.setSelectedRange(1, 0);
+
+ DisplayHelper.sleep(shell.getDisplay(), 600);
+
+ filteredProposals = getFilteredProposals(ca);
+ assertTrue(filteredProposals == null || filteredProposals.isEmpty());
+ }
+
+ /**
+ * CA with 1 immediate and 1 delayed CA processors. Empty text initially. Invoke
+ * CA, verify 1 proposal shows right away, and then another added later after
+ * delay
+ *
+ * @throws Exception exception
+ */
+ @Test
+ public void testMultipleCaProcessors() throws Exception {
+ IDocument document = viewer.getDocument();
+
+ ca.addContentAssistProcessor(new ImmediateContentAssistProcessor("xx"), IDocument.DEFAULT_CONTENT_TYPE);
+ ca.addContentAssistProcessor(new DelayedContentAssistProcessor("yy", 3000, false),
+ IDocument.DEFAULT_CONTENT_TYPE);
+
+ ca.install(viewer);
+
+ viewer.setSelectedRange(0, 0);
+
+ ca.showPossibleCompletions();
+
+ DisplayHelper.sleep(shell.getDisplay(), 300);
+
+ List<ICompletionProposal> computedProposals = getComputedProposals(ca,
+ p -> p instanceof IncompleteCompletionProposal);
+ assertEquals(1, computedProposals.size());
+ List<ICompletionProposal> filteredProposals = getFilteredProposals(ca,
+ p -> p instanceof IncompleteCompletionProposal);
+ assertEquals(1, filteredProposals.size());
+
+ DisplayHelper.sleep(shell.getDisplay(), 3000);
+
+ computedProposals = getComputedProposals(ca, p -> p instanceof IncompleteCompletionProposal);
+ assertEquals(2, computedProposals.size());
+ filteredProposals = getFilteredProposals(ca, p -> p instanceof IncompleteCompletionProposal);
+ assertEquals(2, filteredProposals.size());
+
+ ((ICompletionProposalExtension) filteredProposals.get(1)).apply(document, (char) 0,
+ viewer.getSelectedRange().x);
+ assertEquals("yy", document.get());
+ }
+
+ /**
+ * CA with 1 CA processor for which the first request takes long time and consequent request are
+ * instant. Invoke CA. and type 'a' such that completions are not ready yet, but while recompute
+ * was cancelling futures the futures from previous invocation completed and scheduled an async
+ * UI runnable to show completions. Recompute is immediate. Hence proposals shown right away.
+ * However the async UI runnable to show old proposals runs after and overwrites the correct
+ * immediate proposals. Test that this behaviour is fixed
+ *
+ * @throws Exception exception
+ */
+ @Test
+ public void testCA_WithFirstDelayedThenImmediateProposals() throws Exception {
+ IDocument document = viewer.getDocument();
+
+ ca.addContentAssistProcessor(new LongInitialContentAssistProcessor("abc", 500, true),
+ IDocument.DEFAULT_CONTENT_TYPE);
+
+ ca.install(viewer);
+
+ viewer.setSelectedRange(0, 0);
+
+ ca.showPossibleCompletions();
+
+ DisplayHelper.sleep(shell.getDisplay(), 200);
+ new InsertEdit(0, "a").apply(document);
+ viewer.setSelectedRange(1, 0);
+
+ DisplayHelper.sleep(shell.getDisplay(), 3000);
+
+ List<ICompletionProposal> filteredProposals= getFilteredProposals(ca,
+ p -> p instanceof IncompleteCompletionProposal);
+ assertTrue(filteredProposals != null);
+ assertEquals(1, filteredProposals.size());
+
+ filteredProposals.get(0).apply(document);
+
+ assertEquals("aabc", document.get());
+
+ }
+
+ /**
+ * CA with filtering with 1 immediate and 1 delayed CA processors. Empty text
+ * initially. Invoke CA, verify 1 proposal shows right away, type `a` before
+ * delayed proposal calculated, verify immediate proposal filtered out
+ *
+ * Bug: filtering only applied after all CA processors have completed
+ *
+ * @throws Exception exception
+ */
+ @Test @Ignore
+ public void testFastCompletionsNotFilteredUntilLongComplitionsCalculated() throws Exception {
+ IDocument document = viewer.getDocument();
+
+ ca.addContentAssistProcessor(new ImmediateContentAssistProcessor("xxxx"), IDocument.DEFAULT_CONTENT_TYPE);
+ ca.addContentAssistProcessor(new DelayedContentAssistProcessor("yyyy", 5000, false),
+ IDocument.DEFAULT_CONTENT_TYPE);
+
+ ca.install(viewer);
+
+ viewer.setSelectedRange(1, 0);
+
+ ca.showPossibleCompletions();
+
+ DisplayHelper.sleep(shell.getDisplay(), 300);
+
+ List<ICompletionProposal> computedProposals = getComputedProposals(ca,
+ p -> p instanceof IncompleteCompletionProposal);
+ assertEquals(1, computedProposals.size());
+ List<ICompletionProposal> filteredProposals = getFilteredProposals(ca,
+ p -> p instanceof IncompleteCompletionProposal);
+ assertEquals(1, filteredProposals.size());
+
+ new InsertEdit(0, "a").apply(document);
+ viewer.setSelectedRange(1, 0);
+
+ DisplayHelper.sleep(shell.getDisplay(), 1000);
+
+ filteredProposals = getFilteredProposals(ca, p -> p instanceof IncompleteCompletionProposal);
+ assertTrue(filteredProposals == null || filteredProposals.isEmpty());
+ }
+
+ private class ImmediateContentAssistProcessor implements IContentAssistProcessor {
+
+ final private String template;
+ final private boolean incomplete;
+
+ ImmediateContentAssistProcessor(String template) {
+ this(template, false);
+ }
+
+ ImmediateContentAssistProcessor(String template, boolean incomplete) {
+ this.template = template;
+ this.incomplete = incomplete;
+ }
+
+ @Override
+ public ICompletionProposal[] computeCompletionProposals(ITextViewer textViewer, int offset) {
+ try {
+ IDocument document= textViewer.getDocument();
+ if (document != null && (document.getLength() == 0 || isSubstringFoundOrderedInString(document.get(0, offset), template))) {
+ if (incomplete) {
+ return new ICompletionProposal[] {
+ new IncompleteCompletionProposal(template, offset, 0, offset, template) };
+ } else {
+ CompletionProposal proposal = new CompletionProposal(template, offset, 0, offset, template);
+ return new ICompletionProposal[] { proposal };
+ }
+ }
+ } catch (BadLocationException e) {
+ throw new IllegalStateException("Error computing proposals");
+ }
+ return new ICompletionProposal[0];
+ }
+
+ @Override
+ public IContextInformation[] computeContextInformation(ITextViewer textViewer, int offset) {
+ return new IContextInformation[0];
+ }
+
+ @Override
+ public char[] getCompletionProposalAutoActivationCharacters() {
+ return new char[0];
+ }
+
+ @Override
+ public char[] getContextInformationAutoActivationCharacters() {
+ return new char[0];
+ }
+
+ @Override
+ public String getErrorMessage() {
+ return "No proposals!";
+ }
+
+ @Override
+ public IContextInformationValidator getContextInformationValidator() {
+ return new ContextInformationValidator(this);
+ }
+
+ }
+
+ private class DelayedContentAssistProcessor extends ImmediateContentAssistProcessor {
+
+ protected long delay;
+
+ DelayedContentAssistProcessor(String template, long delay, boolean incomplete) {
+ super(template, incomplete);
+ this.delay = delay;
+ }
+
+ @Override
+ public ICompletionProposal[] computeCompletionProposals(ITextViewer textViewer, int offset) {
+ if (delay > 0) {
+ try {
+ Thread.sleep(delay);
+ } catch (InterruptedException e) {
+ throw new IllegalStateException("Cannot generate delayed content assist proposals!");
+ }
+ }
+ return super.computeCompletionProposals(viewer, offset);
+ }
+ }
+
+ private class LongInitialContentAssistProcessor extends DelayedContentAssistProcessor {
+
+ LongInitialContentAssistProcessor(String template, long delay, boolean incomplete) {
+ super(template, delay, incomplete);
+ }
+
+ @Override
+ public ICompletionProposal[] computeCompletionProposals(ITextViewer textViewer, int offset) {
+ ICompletionProposal[] completionProposals= super.computeCompletionProposals(viewer, offset);
+ delay = 0;
+ return completionProposals;
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ private static List<ICompletionProposal> getComputedProposals(ContentAssistant ca) throws Exception {
+ Field f = ContentAssistant.class.getDeclaredField("fProposalPopup");
+ f.setAccessible(true);
+ Object caPopup = f.get(ca);
+ assertEquals("org.eclipse.jface.text.contentassist.AsyncCompletionProposalPopup", caPopup.getClass().getName());
+ Class<?> caPopupSuperClass = caPopup.getClass().getSuperclass();
+ assertEquals("org.eclipse.jface.text.contentassist.CompletionProposalPopup", caPopupSuperClass.getName());
+ Field computedProposals = caPopupSuperClass.getDeclaredField("fComputedProposals");
+ computedProposals.setAccessible(true);
+ return (List<ICompletionProposal>) computedProposals.get(caPopup);
+ }
+
+ @SuppressWarnings("unchecked")
+ private static List<ICompletionProposal> getFilteredProposals(ContentAssistant ca) throws Exception {
+ Field f = ContentAssistant.class.getDeclaredField("fProposalPopup");
+ f.setAccessible(true);
+ Object caPopup = f.get(ca);
+ assertEquals("org.eclipse.jface.text.contentassist.AsyncCompletionProposalPopup", caPopup.getClass().getName());
+ Class<?> caPopupSuperClass = caPopup.getClass().getSuperclass();
+ assertEquals("org.eclipse.jface.text.contentassist.CompletionProposalPopup", caPopupSuperClass.getName());
+ Field computedProposals = caPopupSuperClass.getDeclaredField("fFilteredProposals");
+ computedProposals.setAccessible(true);
+ return (List<ICompletionProposal>) computedProposals.get(caPopup);
+ }
+
+ private static List<ICompletionProposal> getComputedProposals(ContentAssistant ca, Predicate<ICompletionProposal> p)
+ throws Exception {
+ List<ICompletionProposal> computedProposals = getComputedProposals(ca);
+ return computedProposals == null ? null : computedProposals.stream().filter(p).collect(Collectors.toList());
+ }
+
+ private static List<ICompletionProposal> getFilteredProposals(ContentAssistant ca, Predicate<ICompletionProposal> p)
+ throws Exception {
+ List<ICompletionProposal> filteredProposals = getFilteredProposals(ca);
+ return filteredProposals == null ? null : filteredProposals.stream().filter(p).collect(Collectors.toList());
+ }
+
+ private static class IncompleteCompletionProposal implements ICompletionProposal {
+
+ /** The string to be displayed in the completion proposal popup. */
+ private String fDisplayString;
+ /** The replacement string. */
+ protected String fReplacementString;
+ /** The replacement offset. */
+ protected int fReplacementOffset;
+ /** The replacement length. */
+ private int fReplacementLength;
+ /** The cursor position after this proposal has been applied. */
+ private int fCursorPosition;
+
+ public IncompleteCompletionProposal(String replacementString, int replacementOffset, int replacementLength, int cursorPosition, String displayString) {
+ fReplacementString= replacementString;
+ fReplacementOffset= replacementOffset;
+ fReplacementLength= replacementLength;
+ fCursorPosition= cursorPosition;
+ fDisplayString= displayString;
+ }
+
+ @Override
+ public void apply(IDocument document) {
+ try {
+ document.replace(fReplacementOffset, fReplacementLength, fReplacementString);
+ } catch (BadLocationException x) {
+ // ignore
+ }
+ }
+
+ @Override
+ public Point getSelection(IDocument document) {
+ return new Point(fReplacementOffset + fCursorPosition, 0);
+ }
+
+ @Override
+ public IContextInformation getContextInformation() {
+ return null;
+ }
+
+ @Override
+ public Image getImage() {
+ return null;
+ }
+
+ @Override
+ public String getDisplayString() {
+ if (fDisplayString != null)
+ return fDisplayString;
+ return fReplacementString;
+ }
+
+ @Override
+ public String getAdditionalProposalInfo() {
+ return null;
+ }
+ }
+
+ private static class CompletionProposal extends IncompleteCompletionProposal
+ implements ICompletionProposalExtension, ICompletionProposalExtension2 {
+
+ public CompletionProposal(String replacementString, int replacementOffset, int replacementLength,
+ int cursorPosition, String displayString) {
+ super(replacementString, replacementOffset, replacementLength, cursorPosition, displayString);
+ }
+
+ @Override
+ public void apply(ITextViewer viewer, char trigger, int stateMask, int offset) {
+ apply(viewer.getDocument());
+ }
+
+ @Override
+ public void selected(ITextViewer viewer, boolean smartToggle) {
+ // nothing
+ }
+
+ @Override
+ public void unselected(ITextViewer viewer) {
+ // nothing
+ }
+
+ @Override
+ public boolean validate(IDocument document, int offset, DocumentEvent event) {
+ if (offset > fReplacementOffset) {
+ try {
+ return isSubstringFoundOrderedInString(document.get(fReplacementOffset, offset - fReplacementOffset), fReplacementString);
+ } catch (BadLocationException e) {
+ throw new IllegalStateException("Completion validation failed");
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public void apply(IDocument document, char trigger, int offset) {
+ apply(document);
+ }
+
+ @Override
+ public boolean isValidFor(IDocument document, int offset) {
+ return validate(document, offset, null);
+ }
+
+ @Override
+ public char[] getTriggerCharacters() {
+ return new char[0];
+ }
+
+ @Override
+ public int getContextInformationPosition() {
+ return 0;
+ }
+
+ }
+
+ @SuppressWarnings("boxing")
+ private static boolean isSubstringFoundOrderedInString(String subString, String string) {
+ int lastIndex = 0;
+ subString = subString.toLowerCase();
+ string = string.toLowerCase();
+ for (Character c : subString.toCharArray()) {
+ int index = string.indexOf(c, lastIndex);
+ if (index < 0) {
+ return false;
+ } else {
+ lastIndex = index + 1;
+ }
+ }
+ return true;
+ }
+
+}
diff --git a/org.eclipse.jface.text/src/org/eclipse/jface/text/contentassist/AsyncCompletionProposalPopup.java b/org.eclipse.jface.text/src/org/eclipse/jface/text/contentassist/AsyncCompletionProposalPopup.java
index 3c1a81004..89520fefa 100644
--- a/org.eclipse.jface.text/src/org/eclipse/jface/text/contentassist/AsyncCompletionProposalPopup.java
+++ b/org.eclipse.jface.text/src/org/eclipse/jface/text/contentassist/AsyncCompletionProposalPopup.java
@@ -32,7 +32,6 @@ import org.eclipse.osgi.util.NLS;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.widgets.Control;
-import org.eclipse.swt.widgets.Display;
import org.eclipse.core.runtime.ISafeRunnable;
import org.eclipse.core.runtime.SafeRunner;
@@ -231,21 +230,27 @@ class AsyncCompletionProposalPopup extends CompletionProposalPopup {
}
List<ICompletionProposal> newProposals= new ArrayList<>(computedProposals);
fComputedProposals= newProposals;
- Display.getDefault().asyncExec(() -> {
- if (autoInsert && !autoActivated && remaining.isEmpty() && newProposals.size() == 1 && canAutoInsert(newProposals.get(0))) {
- if (Helper.okToUse(fProposalShell)) {
- insertProposal(newProposals.get(0), (char) 0, 0, offset);
- hide();
+ Control control= fContentAssistSubjectControlAdapter.getControl();
+ if (!control.isDisposed()) {
+ control.getDisplay().asyncExec(() -> {
+ // Don't run anything if offset has changed while runnable was scheduled (i.e. filtering might have occurred for fast CA)
+ if (offset == fInvocationOffset) {
+ if (autoInsert && !autoActivated && remaining.isEmpty() && newProposals.size() == 1 && canAutoInsert(newProposals.get(0))) {
+ if (Helper.okToUse(fProposalShell)) {
+ insertProposal(newProposals.get(0), (char) 0, 0, offset);
+ hide();
+ }
+ return;
+ }
+ if (remaining.isEmpty() && callback != null) {
+ callback.accept(newProposals);
+ } else {
+ setProposals(newProposals, false);
+ displayProposals();
+ }
}
- return;
- }
- if (remaining.isEmpty() && callback != null) {
- callback.accept(newProposals);
- } else {
- setProposals(newProposals, false);
- displayProposals();
- }
- });
+ });
+ }
});
}
displayProposals();

Back to the top