Bug 564305 - fixed unsorted filtered proposals on incremental complete

On incremental completion with async content assist and insert common
prefix enabled, the proposal order in fFilteredProposals did not match
the displayed order, causing unexpected proposals to be inserted.

The fComputedPropsals list will not be sorted in this scenario, so it
can't be used to overwrite fFilteredProposals.
fFilteredProposals seems to be always initialized by
computeAndPopulateProposals anyway.

Change-Id: I0da117281502b0a11ea462a38e129e3c59c252f1
Signed-off-by: Julian Honnen <julian.honnen@vector.com>
diff --git a/org.eclipse.jface.text.tests/META-INF/MANIFEST.MF b/org.eclipse.jface.text.tests/META-INF/MANIFEST.MF
index af38031..861096b 100644
--- a/org.eclipse.jface.text.tests/META-INF/MANIFEST.MF
+++ b/org.eclipse.jface.text.tests/META-INF/MANIFEST.MF
@@ -2,7 +2,7 @@
 Bundle-ManifestVersion: 2
 Bundle-Name: %Plugin.name
 Bundle-SymbolicName: org.eclipse.jface.text.tests
-Bundle-Version: 3.11.1100.qualifier
+Bundle-Version: 3.11.1200.qualifier
 Bundle-Vendor: %Plugin.providerName
 Bundle-Localization: plugin
 Export-Package: 
diff --git a/org.eclipse.jface.text.tests/pom.xml b/org.eclipse.jface.text.tests/pom.xml
index 74f7dec..4cd808d 100644
--- a/org.eclipse.jface.text.tests/pom.xml
+++ b/org.eclipse.jface.text.tests/pom.xml
@@ -19,7 +19,7 @@
   </parent>
   <groupId>org.eclipse.jface</groupId>
   <artifactId>org.eclipse.jface.text.tests</artifactId>
-  <version>3.11.1100-SNAPSHOT</version>
+  <version>3.11.1200-SNAPSHOT</version>
   <packaging>eclipse-test-plugin</packaging>
   <properties>
   	<testSuite>${project.artifactId}</testSuite>
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 05f39a7..29c3603 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
@@ -23,6 +23,7 @@
 import org.eclipse.jface.text.tests.contentassist.ContextInformationPresenterTest;
 import org.eclipse.jface.text.tests.contentassist.ContextInformationTest;
 import org.eclipse.jface.text.tests.contentassist.FilteringAsyncContentAssistTests;
+import org.eclipse.jface.text.tests.contentassist.IncrementalAsyncContentAssistTests;
 import org.eclipse.jface.text.tests.reconciler.AbstractReconcilerTest;
 import org.eclipse.jface.text.tests.rules.DefaultPartitionerTest;
 import org.eclipse.jface.text.tests.rules.DefaultPartitionerZeroLengthTest;
@@ -53,6 +54,7 @@
 		DefaultPairMatcherTest2.class,
 		AsyncContentAssistTest.class,
 		FilteringAsyncContentAssistTests.class,
+		IncrementalAsyncContentAssistTests.class,
 		ContextInformationTest.class,
 		ContextInformationPresenterTest.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
index 405b41c..4ff6095 100644
--- 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
@@ -10,10 +10,13 @@
  *******************************************************************************/
 package org.eclipse.jface.text.tests.contentassist;
 
+import static java.util.Collections.singletonList;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 
 import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.List;
 import java.util.concurrent.CountDownLatch;
 import java.util.function.Predicate;
@@ -35,12 +38,14 @@
 import org.eclipse.jface.text.Document;
 import org.eclipse.jface.text.DocumentEvent;
 import org.eclipse.jface.text.IDocument;
+import org.eclipse.jface.text.IInformationControlCreator;
 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.ICompletionProposalExtension3;
 import org.eclipse.jface.text.contentassist.IContentAssistProcessor;
 import org.eclipse.jface.text.contentassist.IContextInformation;
 import org.eclipse.jface.text.contentassist.IContextInformationValidator;
@@ -203,7 +208,7 @@
 		IDocument document = viewer.getDocument();
 
 		ca.addContentAssistProcessor(new ImmediateContentAssistProcessor("xx"), IDocument.DEFAULT_CONTENT_TYPE);
-		ca.addContentAssistProcessor(new DelayedContentAssistProcessor("yy", 3000, false),
+		ca.addContentAssistProcessor(new DelayedContentAssistProcessor(singletonList("yy"), 3000, false),
 				IDocument.DEFAULT_CONTENT_TYPE);
 
 		ca.install(viewer);
@@ -247,7 +252,7 @@
 	public void testCA_WithFirstDelayedThenImmediateProposals() throws Exception {
 		IDocument document = viewer.getDocument();
 
-		ca.addContentAssistProcessor(new LongInitialContentAssistProcessor("abc", 500, true),
+		ca.addContentAssistProcessor(new LongInitialContentAssistProcessor(singletonList("abc"), 500, true),
 				IDocument.DEFAULT_CONTENT_TYPE);
 
 		ca.install(viewer);
@@ -287,7 +292,7 @@
 		IDocument document = viewer.getDocument();
 
 		ca.addContentAssistProcessor(new ImmediateContentAssistProcessor("xxxx"), IDocument.DEFAULT_CONTENT_TYPE);
-		ca.addContentAssistProcessor(new DelayedContentAssistProcessor("yyyy", 5000, false),
+		ca.addContentAssistProcessor(new DelayedContentAssistProcessor(singletonList("yyyy"), 5000, false),
 				IDocument.DEFAULT_CONTENT_TYPE);
 
 		ca.install(viewer);
@@ -354,37 +359,38 @@
 
 	}
 
-	private class ImmediateContentAssistProcessor implements IContentAssistProcessor {
+	static class ImmediateContentAssistProcessor implements IContentAssistProcessor {
 
-		final private String template;
+		final private List<String> templates;
 		final private boolean incomplete;
 
-		ImmediateContentAssistProcessor(String template) {
-			this(template, false);
+		ImmediateContentAssistProcessor(String... templates) {
+			this(Arrays.asList(templates), false);
 		}
 
-		ImmediateContentAssistProcessor(String template, boolean incomplete) {
-			this.template = template;
+		ImmediateContentAssistProcessor(List<String> templates, boolean incomplete) {
+			this.templates= templates;
 			this.incomplete = incomplete;
 		}
 
 		@Override
 		public ICompletionProposal[] computeCompletionProposals(ITextViewer textViewer, int offset) {
+			List<ICompletionProposal> proposals= new ArrayList<>();
 			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 };
+				for (String template : templates) {
+					if (document != null && (document.getLength() == 0 || isSubstringFoundOrderedInString(document.get(0, offset), template))) {
+						if (incomplete) {
+							proposals.add(new IncompleteCompletionProposal(template, offset, 0, offset, template));
+						} else {
+							proposals.add(new CompletionProposal(template, offset, 0, offset, template));
+						}
 					}
 				}
 			} catch (BadLocationException e) {
 				throw new IllegalStateException("Error computing proposals");
 			}
-			return new ICompletionProposal[0];
+			return proposals.toArray(new ICompletionProposal[0]);
 		}
 
 		@Override
@@ -414,12 +420,12 @@
 
 	}
 
-	private class DelayedContentAssistProcessor extends ImmediateContentAssistProcessor {
+	static class DelayedContentAssistProcessor extends ImmediateContentAssistProcessor {
 
 		protected long delay;
 
-		DelayedContentAssistProcessor(String template, long delay, boolean incomplete) {
-			super(template, incomplete);
+		DelayedContentAssistProcessor(List<String> templates, long delay, boolean incomplete) {
+			super(templates, incomplete);
 			this.delay = delay;
 		}
 
@@ -432,14 +438,14 @@
 					throw new IllegalStateException("Cannot generate delayed content assist proposals!");
 				}
 			}
-			return super.computeCompletionProposals(viewer, offset);
+			return super.computeCompletionProposals(textViewer, offset);
 		}
 	}
 
 	private class LongInitialContentAssistProcessor extends DelayedContentAssistProcessor {
 
-		LongInitialContentAssistProcessor(String template, long delay, boolean incomplete) {
-			super(template, delay, incomplete);
+		LongInitialContentAssistProcessor(List<String> templates, long delay, boolean incomplete) {
+			super(templates, delay, incomplete);
 		}
 
 		@Override
@@ -454,8 +460,8 @@
 
 		final CountDownLatch blocked= new CountDownLatch(1);
 
-		BlockingProcessor(String template) {
-			super(template, false);
+		BlockingProcessor(String... templates) {
+			super(Arrays.asList(templates), false);
 		}
 
 		@Override
@@ -484,7 +490,7 @@
 	}
 
 	@SuppressWarnings("unchecked")
-	private static List<ICompletionProposal> getFilteredProposals(ContentAssistant ca) throws Exception {
+	static List<ICompletionProposal> getFilteredProposals(ContentAssistant ca) throws Exception {
 		Field f = ContentAssistant.class.getDeclaredField("fProposalPopup");
 		f.setAccessible(true);
 		Object caPopup = f.get(ca);
@@ -566,8 +572,8 @@
 		}
 	}
 
-	private static class CompletionProposal extends IncompleteCompletionProposal
-		implements ICompletionProposalExtension, ICompletionProposalExtension2 {
+	static class CompletionProposal extends IncompleteCompletionProposal
+			implements ICompletionProposalExtension, ICompletionProposalExtension2, ICompletionProposalExtension3 {
 
 		public CompletionProposal(String replacementString, int replacementOffset, int replacementLength,
 				int cursorPosition, String displayString) {
@@ -624,6 +630,25 @@
 			return 0;
 		}
 
+		@Override
+		public int getPrefixCompletionStart(IDocument document, int completionOffset) {
+			return 0;
+		}
+
+		@Override
+		public CharSequence getPrefixCompletionText(IDocument document, int completionOffset) {
+			return getDisplayString();
+		}
+
+		@Override
+		public IInformationControlCreator getInformationControlCreator() {
+			return null;
+		}
+
+		@Override
+		public String toString() {
+			return getDisplayString();
+		}
 	}
 
 	@SuppressWarnings("boxing")
diff --git a/org.eclipse.jface.text.tests/src/org/eclipse/jface/text/tests/contentassist/IncrementalAsyncContentAssistTests.java b/org.eclipse.jface.text.tests/src/org/eclipse/jface/text/tests/contentassist/IncrementalAsyncContentAssistTests.java
new file mode 100644
index 0000000..ef79f05
--- /dev/null
+++ b/org.eclipse.jface.text.tests/src/org/eclipse/jface/text/tests/contentassist/IncrementalAsyncContentAssistTests.java
@@ -0,0 +1,124 @@
+/*******************************************************************************
+ * Copyright (c) 2020 Julian Honnen
+ * 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:
+ *     Julian Honnen - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.jface.text.tests.contentassist;
+
+import static java.util.stream.Collectors.toList;
+import static org.junit.Assert.assertEquals;
+
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.List;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.widgets.Shell;
+
+import org.eclipse.jface.text.Document;
+import org.eclipse.jface.text.IDocument;
+import org.eclipse.jface.text.contentassist.ContentAssistant;
+import org.eclipse.jface.text.contentassist.ICompletionProposal;
+import org.eclipse.jface.text.source.SourceViewer;
+import org.eclipse.jface.text.tests.util.DisplayHelper;
+
+public class IncrementalAsyncContentAssistTests {
+
+	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);
+
+		Comparator<ICompletionProposal> comparator= Comparator.comparing(p -> p.getDisplayString());
+		ca.setSorter(comparator::compare);
+	}
+
+	@After
+	public void tearDown() {
+		if (shell != null) {
+			ca.uninstall();
+			if (!shell.isDisposed()) {
+				shell.dispose();
+			}
+			shell= null;
+		}
+	}
+
+	@Test
+	public void testIncrementalComplete() throws Exception {
+		ca.addContentAssistProcessor(new FilteringAsyncContentAssistTests.ImmediateContentAssistProcessor("testC", "testB", "testA"), IDocument.DEFAULT_CONTENT_TYPE);
+
+		viewer.getDocument().set("t");
+
+		ca.install(viewer);
+		viewer.setSelectedRange(1, 0);
+
+		ca.completePrefix();
+
+		DisplayHelper.sleep(shell.getDisplay(), 300);
+
+		List<String> filteredProposals= FilteringAsyncContentAssistTests.getFilteredProposals(ca).stream() //
+				.map(p -> p.getDisplayString()) //
+				.collect(toList());
+		assertEquals(Arrays.asList("testA", "testB", "testC"), filteredProposals);
+	}
+
+	@Test
+	public void testIncrementalComplete_async() throws Exception {
+		long delay= 200;
+		ca.addContentAssistProcessor(new FilteringAsyncContentAssistTests.DelayedContentAssistProcessor(Arrays.asList("testC", "testB", "testA"), delay, false), IDocument.DEFAULT_CONTENT_TYPE);
+
+		viewer.getDocument().set("t");
+
+		ca.install(viewer);
+		viewer.setSelectedRange(1, 0);
+
+		ca.completePrefix();
+
+		DisplayHelper.sleep(shell.getDisplay(), delay + 100);
+
+		List<String> filteredProposals= FilteringAsyncContentAssistTests.getFilteredProposals(ca).stream() //
+				.map(p -> p.getDisplayString()) //
+				.collect(toList());
+		assertEquals(Arrays.asList("testA", "testB", "testC"), filteredProposals);
+	}
+
+	@Test
+	public void testIncrementalCompleteOfSingleProposal() throws Exception {
+		ca.enableAutoInsert(true);
+		ca.addContentAssistProcessor(new FilteringAsyncContentAssistTests.ImmediateContentAssistProcessor("testA"), IDocument.DEFAULT_CONTENT_TYPE);
+
+		ca.install(viewer);
+		viewer.setSelectedRange(0, 0);
+
+		ca.completePrefix();
+
+		DisplayHelper.sleep(shell.getDisplay(), 300);
+
+		assertEquals("testA", viewer.getDocument().get());
+	}
+
+}
diff --git a/org.eclipse.jface.text/META-INF/MANIFEST.MF b/org.eclipse.jface.text/META-INF/MANIFEST.MF
index 7b622c5..02dfbb3 100644
--- a/org.eclipse.jface.text/META-INF/MANIFEST.MF
+++ b/org.eclipse.jface.text/META-INF/MANIFEST.MF
@@ -2,7 +2,7 @@
 Bundle-ManifestVersion: 2
 Bundle-Name: %pluginName
 Bundle-SymbolicName: org.eclipse.jface.text
-Bundle-Version: 3.16.300.qualifier
+Bundle-Version: 3.16.400.qualifier
 Bundle-Vendor: %providerName
 Bundle-Localization: plugin
 Export-Package: 
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 30b06d8..b29c6b2 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
@@ -293,7 +293,6 @@
 				displayProposals();
 			}
 		}, true, false, true);
-		fFilteredProposals= new ArrayList<>(fComputedProposals != null ? fComputedProposals : Collections.emptyList());
 		return getErrorMessage();
 	}