Bug 522255 - Filter/restart completion session only uses 1st processor

Change-Id: Ie5697da68555f80569c870c6d54ea7f0ec94370a
Signed-off-by: Mickael Istria <mistria@redhat.com>
diff --git a/org.eclipse.jface.text/src/org/eclipse/jface/text/contentassist/ContentAssistant.java b/org.eclipse.jface.text/src/org/eclipse/jface/text/contentassist/ContentAssistant.java
index 72c62e9..838eba1 100644
--- a/org.eclipse.jface.text/src/org/eclipse/jface/text/contentassist/ContentAssistant.java
+++ b/org.eclipse.jface.text/src/org/eclipse/jface/text/contentassist/ContentAssistant.java
@@ -15,11 +15,17 @@
  *******************************************************************************/
 package org.eclipse.jface.text.contentassist;
 
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.LinkedHashSet;
+import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 import java.util.Set;
+import java.util.function.Function;
 
 import org.eclipse.swt.SWT;
 import org.eclipse.swt.SWTError;
@@ -1909,16 +1915,16 @@
 	 *
 	 * @param viewer the text viewer
 	 * @param offset a offset within the document
-	 * @return a content-assist processor or <code>null</code> if none exists
-	 * @since 3.0
+	 * @return the content-assist processors or <code>null</code> if none exists
+	 * @since 3.13
 	 */
-	private IContentAssistProcessor getProcessor(ITextViewer viewer, int offset) {
+	private Set<IContentAssistProcessor> getProcessors(ITextViewer viewer, int offset) {
 		try {
 
 			IDocument document= viewer.getDocument();
 			String type= TextUtilities.getContentType(document, getDocumentPartitioning(), offset, true);
 
-			return getContentAssistProcessor(type);
+			return getContentAssistProcessors(type);
 
 		} catch (BadLocationException x) {
 		}
@@ -1927,14 +1933,14 @@
 	}
 
 	/**
-	 * Returns the content assist processor for the content type of the specified document position.
+	 * Returns the content assist processors for the content type of the specified document position.
 	 *
 	 * @param contentAssistSubjectControl the content assist subject control
 	 * @param offset a offset within the document
-	 * @return a content-assist processor or <code>null</code> if none exists
-	 * @since 3.0
+	 * @return the content-assist processors or <code>null</code> if none exists
+	 * @since 3.13
 	 */
-	private IContentAssistProcessor getProcessor(IContentAssistSubjectControl contentAssistSubjectControl, int offset) {
+	private Set<IContentAssistProcessor> getProcessors(IContentAssistSubjectControl contentAssistSubjectControl, int offset) {
 		try {
 
 			IDocument document= contentAssistSubjectControl.getDocument();
@@ -1944,7 +1950,7 @@
 			else
 				type= IDocument.DEFAULT_CONTENT_TYPE;
 
-			return getContentAssistProcessor(type);
+			return getContentAssistProcessors(type);
 
 		} catch (BadLocationException x) {
 		}
@@ -1966,22 +1972,25 @@
 			final IContentAssistSubjectControl contentAssistSubjectControl, final int offset) {
 		fLastErrorMessage= null;
 
-		final ICompletionProposal[][] result= { null };
-
-		final IContentAssistProcessor p= getProcessor(contentAssistSubjectControl, offset);
-		if (p instanceof ISubjectControlContentAssistProcessor) {
-			// Ensure that the assist session ends cleanly even if the processor throws an exception.
-			SafeRunner.run(new ExceptionLoggingSafeRunnable(COMPLETION_ERROR_MESSAGE_KEY) {
-				@Override
-				public void run() throws Exception {
-					result[0]= ((ISubjectControlContentAssistProcessor) p)
-							.computeCompletionProposals(contentAssistSubjectControl, offset);
-					fLastErrorMessage= p.getErrorMessage();
+		final List<ICompletionProposal> result= new ArrayList<>();
+		final Set<IContentAssistProcessor> processors= getProcessors(contentAssistSubjectControl, offset);
+		if (processors != null) {
+			processors.forEach(p -> {
+				if (p instanceof ISubjectControlContentAssistProcessor) {
+					// Ensure that the assist session ends cleanly even if the processor throws an exception.
+					SafeRunner.run(new ExceptionLoggingSafeRunnable(COMPLETION_ERROR_MESSAGE_KEY) {
+						@Override
+						public void run() throws Exception {
+							result.addAll(Arrays.asList( ((ISubjectControlContentAssistProcessor) p)
+									.computeCompletionProposals(contentAssistSubjectControl, offset)));
+							fLastErrorMessage= p.getErrorMessage();
+						}
+					});
 				}
 			});
 		}
 
-		return result[0];
+		return result.isEmpty() ? null : result.toArray(new ICompletionProposal[result.size()]);
 	}
 
 	/**
@@ -1996,21 +2005,22 @@
 	ICompletionProposal[] computeCompletionProposals(final ITextViewer viewer, final int offset) {
 		fLastErrorMessage= null;
 
-		final ICompletionProposal[][] result= { null };
-
-		final IContentAssistProcessor p= getProcessor(viewer, offset);
-		if (p != null) {
+		final Set<IContentAssistProcessor> processors= getProcessors(viewer, offset);
+		final List<ICompletionProposal> res = new ArrayList<>();
+		if (processors != null && !processors.isEmpty()) {
 			// Ensure that the assist session ends cleanly even if the processor throws an exception.
 			SafeRunner.run(new ExceptionLoggingSafeRunnable(COMPLETION_ERROR_MESSAGE_KEY) {
 				@Override
 				public void run() throws Exception {
-					result[0]= p.computeCompletionProposals(viewer, offset);
-					fLastErrorMessage= p.getErrorMessage();
+					processors.forEach(p -> {
+						res.addAll(Arrays.asList(p.computeCompletionProposals(viewer, offset)));
+						fLastErrorMessage= p.getErrorMessage();
+					});
 				}
 			});
 		}
 
-		return result[0];
+		return res.isEmpty() ? null : res.toArray(new ICompletionProposal[res.size()]);
 	}
 
 	/**
@@ -2026,21 +2036,22 @@
 	IContextInformation[] computeContextInformation(final ITextViewer viewer, final int offset) {
 		fLastErrorMessage= null;
 
-		final IContextInformation[][] result= { null };
-
-		final IContentAssistProcessor p= getProcessor(viewer, offset);
-		if (p != null) {
+		final List<IContextInformation> result= new ArrayList<>();
+		final Set<IContentAssistProcessor> processors= getProcessors(viewer, offset);
+		if (processors != null && !processors.isEmpty()) {
 			// Ensure that the assist session ends cleanly even if the processor throws an exception.
 			SafeRunner.run(new ExceptionLoggingSafeRunnable(CONTEXT_ERROR_MESSAGE_KEY) {
 				@Override
 				public void run() throws Exception {
-					result[0]= p.computeContextInformation(viewer, offset);
-					fLastErrorMessage= p.getErrorMessage();
+					processors.forEach(p -> {
+						result.addAll(Arrays.asList(p.computeContextInformation(viewer, offset)));
+						fLastErrorMessage= p.getErrorMessage();
+					});
 				}
 			});
 		}
 
-		return result[0];
+		return result.isEmpty() ? null : result.toArray(new IContextInformation[result.size()]);
 	}
 
 	/**
@@ -2058,22 +2069,25 @@
 			final IContentAssistSubjectControl contentAssistSubjectControl, final int offset) {
 		fLastErrorMessage= null;
 
-		final IContextInformation[][] result= { null };
-
-		final IContentAssistProcessor p= getProcessor(contentAssistSubjectControl, offset);
-		if (p instanceof ISubjectControlContentAssistProcessor) {
-			// Ensure that the assist session ends cleanly even if the processor throws an exception.
-			SafeRunner.run(new ExceptionLoggingSafeRunnable(CONTEXT_ERROR_MESSAGE_KEY) {
-				@Override
-				public void run() throws Exception {
-					result[0]= ((ISubjectControlContentAssistProcessor) p)
-							.computeContextInformation(contentAssistSubjectControl, offset);
-					fLastErrorMessage= p.getErrorMessage();
+		final List<IContextInformation> result= new ArrayList<>();
+		final Set<IContentAssistProcessor> processors = getProcessors(contentAssistSubjectControl, offset);
+		if (processors != null) {
+			processors.forEach(p -> {
+				if (p instanceof ISubjectControlContentAssistProcessor) {
+					// Ensure that the assist session ends cleanly even if the processor throws an exception.
+					SafeRunner.run(new ExceptionLoggingSafeRunnable(CONTEXT_ERROR_MESSAGE_KEY) {
+						@Override
+						public void run() throws Exception {
+							result.addAll(Arrays.asList( ((ISubjectControlContentAssistProcessor) p)
+									.computeContextInformation(contentAssistSubjectControl, offset)));
+							fLastErrorMessage= p.getErrorMessage();
+						}
+					});
 				}
 			});
 		}
 
-		return result[0];
+		return result.isEmpty() ? null : result.toArray(new IContextInformation[result.size()]);
 	}
 
 	/**
@@ -2088,7 +2102,12 @@
 	 * @since 3.0
 	 */
 	IContextInformationValidator getContextInformationValidator(ITextViewer viewer, int offset) {
-		IContentAssistProcessor p= getProcessor(viewer, offset);
+		Set<IContentAssistProcessor> processors= getProcessors(viewer, offset);
+		if (processors == null || processors.isEmpty()) {
+			return null;
+		}
+		// pick first one, arbitrary
+		IContentAssistProcessor p = processors.iterator().next();
 		return p != null ? p.getContextInformationValidator() : null;
 	}
 
@@ -2104,7 +2123,12 @@
 	 * @since 3.0
 	 */
 	IContextInformationValidator getContextInformationValidator(IContentAssistSubjectControl contentAssistSubjectControl, int offset) {
-		IContentAssistProcessor p= getProcessor(contentAssistSubjectControl, offset);
+		Set<IContentAssistProcessor> processors= getProcessors(contentAssistSubjectControl, offset);
+		if (processors == null || processors.isEmpty()) {
+			return null;
+		}
+		// pick first one, arbitrary
+		IContentAssistProcessor p = processors.iterator().next();
 		return p != null ? p.getContextInformationValidator() : null;
 	}
 
@@ -2152,8 +2176,7 @@
 	 * @since 3.0
 	 */
 	char[] getCompletionProposalAutoActivationCharacters(IContentAssistSubjectControl contentAssistSubjectControl, int offset) {
-		IContentAssistProcessor p= getProcessor(contentAssistSubjectControl, offset);
-		return p != null ? p.getCompletionProposalAutoActivationCharacters() : null;
+		return mergeResults(getProcessors(contentAssistSubjectControl, offset), IContentAssistProcessor::getCompletionProposalAutoActivationCharacters);
 	}
 
 	/**
@@ -2167,8 +2190,7 @@
 	 * @see IContentAssistProcessor#getCompletionProposalAutoActivationCharacters()
 	 */
 	char[] getCompletionProposalAutoActivationCharacters(ITextViewer viewer, int offset) {
-		IContentAssistProcessor p= getProcessor(viewer, offset);
-		return p != null ? p.getCompletionProposalAutoActivationCharacters() : null;
+		return mergeResults(getProcessors(viewer, offset), IContentAssistProcessor::getCompletionProposalAutoActivationCharacters);
 	}
 
 	/**
@@ -2183,8 +2205,7 @@
 	 * @since 3.0
 	 */
 	char[] getContextInformationAutoActivationCharacters(ITextViewer viewer, int offset) {
-		IContentAssistProcessor p= getProcessor(viewer, offset);
-		return p != null ? p.getContextInformationAutoActivationCharacters() : null;
+		return mergeResults(getProcessors(viewer, offset), IContentAssistProcessor::getContextInformationAutoActivationCharacters);
 	}
 
 	/**
@@ -2199,8 +2220,34 @@
 	 * @since 3.0
 	 */
 	char[] getContextInformationAutoActivationCharacters(IContentAssistSubjectControl contentAssistSubjectControl, int offset) {
-		IContentAssistProcessor p= getProcessor(contentAssistSubjectControl, offset);
-		return p != null ? p.getContextInformationAutoActivationCharacters() : null;
+		return mergeResults(getProcessors(contentAssistSubjectControl, offset), IContentAssistProcessor::getContextInformationAutoActivationCharacters);
+	}
+
+	private char[] mergeResults(Collection<IContentAssistProcessor> processors, Function<IContentAssistProcessor, char[]> f) {
+		char[][] arrays = processors.stream()
+			.map(f)
+			.filter(Objects::nonNull)
+			.filter(array -> array.length > 0)
+			.toArray(char[][]::new);
+		if (arrays.length == 0) {
+			return null;
+		} else if (arrays.length == 1) {
+			return arrays[0];
+		} else {
+			LinkedHashSet<Character> res = new LinkedHashSet<>();
+			for (char[] current : arrays) {
+				for (char c : current) {
+					res.add(Character.valueOf(c));
+				}
+			}
+			char[] array = new char[res.size()];
+			int index = 0;
+			for (Character c : res) {
+				array[index] = c.charValue();
+				index++;
+			}
+			return array;
+		}
 	}
 
 	@Override
@@ -2443,11 +2490,12 @@
 	 */
 	void fireSessionBeginEvent(boolean isAutoActivated) {
 		if (fContentAssistSubjectControlAdapter != null && !isProposalPopupActive()) {
-			IContentAssistProcessor processor= getProcessor(fContentAssistSubjectControlAdapter, fContentAssistSubjectControlAdapter.getSelectedRange().x);
-			ContentAssistEvent event= new ContentAssistEvent(this, processor, isAutoActivated);
-			for (ICompletionListener listener : fCompletionListeners) {
-				listener.assistSessionStarted(event);
-			}
+			getProcessors(fContentAssistSubjectControlAdapter, fContentAssistSubjectControlAdapter.getSelectedRange().x).forEach(processor -> {
+				ContentAssistEvent event= new ContentAssistEvent(this, processor, isAutoActivated);
+				for (ICompletionListener listener : fCompletionListeners) {
+					listener.assistSessionStarted(event);
+				}
+			});
 		}
 	}
 
@@ -2458,12 +2506,13 @@
 	 */
 	void fireSessionRestartEvent() {
 		if (fContentAssistSubjectControlAdapter != null) {
-			IContentAssistProcessor processor= getProcessor(fContentAssistSubjectControlAdapter, fContentAssistSubjectControlAdapter.getSelectedRange().x);
-			ContentAssistEvent event= new ContentAssistEvent(this, processor);
-			for (ICompletionListener listener : fCompletionListeners) {
-				if (listener instanceof ICompletionListenerExtension)
-					((ICompletionListenerExtension)listener).assistSessionRestarted(event);
-			}
+			getProcessors(fContentAssistSubjectControlAdapter, fContentAssistSubjectControlAdapter.getSelectedRange().x).forEach(processor -> {
+				ContentAssistEvent event= new ContentAssistEvent(this, processor);
+				for (ICompletionListener listener : fCompletionListeners) {
+					if (listener instanceof ICompletionListenerExtension)
+						((ICompletionListenerExtension)listener).assistSessionRestarted(event);
+				}
+			});
 		}
 	}
 
@@ -2474,11 +2523,12 @@
 	 */
 	void fireSessionEndEvent() {
 		if (fContentAssistSubjectControlAdapter != null) {
-			IContentAssistProcessor processor= getProcessor(fContentAssistSubjectControlAdapter, fContentAssistSubjectControlAdapter.getSelectedRange().x);
-			ContentAssistEvent event= new ContentAssistEvent(this, processor);
-			for (ICompletionListener listener : fCompletionListeners) {
-				listener.assistSessionEnded(event);
-			}
+			getProcessors(fContentAssistSubjectControlAdapter, fContentAssistSubjectControlAdapter.getSelectedRange().x).forEach(processor -> {
+				ContentAssistEvent event= new ContentAssistEvent(this, processor);
+				for (ICompletionListener listener : fCompletionListeners) {
+					listener.assistSessionEnded(event);
+				}
+			});
 		}
 	}
 
diff --git a/org.eclipse.ui.genericeditor.tests/src/org/eclipse/ui/genericeditor/tests/CompletionTest.java b/org.eclipse.ui.genericeditor.tests/src/org/eclipse/ui/genericeditor/tests/CompletionTest.java
index 7ed46a8..31b0543 100644
--- a/org.eclipse.ui.genericeditor.tests/src/org/eclipse/ui/genericeditor/tests/CompletionTest.java
+++ b/org.eclipse.ui.genericeditor.tests/src/org/eclipse/ui/genericeditor/tests/CompletionTest.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2016 Red Hat Inc. and others
+ * Copyright (c) 2016-2017 Red Hat Inc. 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
@@ -14,9 +14,10 @@
 import static org.junit.Assert.assertTrue;
 
 import java.util.Arrays;
-import java.util.HashSet;
 import java.util.Set;
+import java.util.stream.Collectors;
 
+import org.junit.After;
 import org.junit.Test;
 
 import org.eclipse.swt.SWT;
@@ -24,13 +25,13 @@
 import org.eclipse.swt.custom.StyledText;
 import org.eclipse.swt.widgets.Composite;
 import org.eclipse.swt.widgets.Control;
-import org.eclipse.swt.widgets.Display;
 import org.eclipse.swt.widgets.Event;
 import org.eclipse.swt.widgets.Shell;
 import org.eclipse.swt.widgets.Table;
 import org.eclipse.swt.widgets.TableItem;
 import org.eclipse.swt.widgets.Widget;
 
+import org.eclipse.jface.text.ITextSelection;
 import org.eclipse.jface.text.contentassist.ICompletionProposal;
 import org.eclipse.jface.text.tests.util.DisplayHelper;
 
@@ -45,65 +46,78 @@
  */
 public class CompletionTest extends AbstratGenericEditorTest {
 
+	private Shell completionShell;
+
 	@Test
 	public void testCompletion() throws Exception {
-		Set<Shell> beforeShell = new HashSet<>(Arrays.asList(Display.getDefault().getShells()));
+		final Set<Shell> beforeShells = Arrays.stream(editor.getSite().getShell().getDisplay().getShells()).filter(Shell::isVisible).collect(Collectors.toSet());
 		editor.selectAndReveal(3, 0);
 		ContentAssistAction action = (ContentAssistAction) editor.getAction(ITextEditorActionConstants.CONTENT_ASSIST);
 		action.update();
 		action.run();
 		waitAndDispatch(100);
-		Set<Shell> afterShell = new HashSet<>(Arrays.asList(Display.getDefault().getShells()));
-		afterShell.removeAll(beforeShell);
-		assertEquals("No completion", 1, afterShell.size());
-		Shell completionShell= afterShell.iterator().next();
+		this.completionShell= findNewShell(beforeShells);
 		final Table completionProposalList = findCompletionSelectionControl(completionShell);
+		checkCompletionContent(completionProposalList);
+		// TODO find a way to actually trigger completion and verify result against Editor content
+		// Assert.assertEquals("Completion didn't complete", "bars are good for a beer.", ((StyledText)editor.getAdapter(Control.class)).getText());
+	}
+
+	/**
+	 * Checks that completion behaves as expected:
+	 * 1. Computing is shown instantaneously
+	 * 2. 1st proposal shown instantaneously
+	 * 3. 2s later, 2nd proposal is shown
+	 * @param completionProposalList the completion list
+	 */
+	private void checkCompletionContent(final Table completionProposalList) {
 		// should be instantaneous, but happens to go asynchronous on CI so let's allow a wait
 		new DisplayHelper() {
 			@Override
 			protected boolean condition() {
 				return completionProposalList.getItemCount() == 2;
 			}
-		}.waitForCondition(completionShell.getDisplay(), 200);
+		}.waitForCondition(completionProposalList.getDisplay(), 200);
 		assertEquals(2, completionProposalList.getItemCount());
 		final TableItem computingItem = completionProposalList.getItem(0);
 		assertTrue("Missing computing info entry", computingItem.getText().contains("Computing")); //$NON-NLS-1$ //$NON-NLS-2$
 		TableItem completionProposalItem = completionProposalList.getItem(1);
-		ICompletionProposal completionProposal = (ICompletionProposal)completionProposalItem.getData();
-		assertEquals(BarContentAssistProcessor.PROPOSAL, completionProposal .getDisplayString());
+		final ICompletionProposal selectedProposal = (ICompletionProposal)completionProposalItem.getData();
+		assertTrue("Incorrect proposal content", BarContentAssistProcessor.PROPOSAL.endsWith(selectedProposal .getDisplayString()));
 		completionProposalList.setSelection(completionProposalItem);
 		// asynchronous
 		new DisplayHelper() {
 			@Override
 			protected boolean condition() {
-				return completionProposalList.getItem(0) != computingItem;
+				return completionProposalList.getItem(0) != computingItem && completionProposalList.getItemCount() == 2;
 			}
-		}.waitForCondition(completionShell.getDisplay(), LongRunningBarContentAssistProcessor.DELAY + 200);
-		assertEquals(2, completionProposalList.getItemCount());
+		}.waitForCondition(completionProposalList.getDisplay(), LongRunningBarContentAssistProcessor.DELAY + 200);
 		completionProposalItem = completionProposalList.getItem(0);
-		assertEquals(BarContentAssistProcessor.PROPOSAL, ((ICompletionProposal)completionProposalItem.getData()).getDisplayString());
+		assertTrue("Proposal content seems incorrect", BarContentAssistProcessor.PROPOSAL.endsWith(((ICompletionProposal)completionProposalItem.getData()).getDisplayString()));
 		TableItem otherProposalItem = completionProposalList.getItem(1);
-		assertEquals(LongRunningBarContentAssistProcessor.PROPOSAL, ((ICompletionProposal)otherProposalItem.getData()).getDisplayString());
-		assertEquals("Addition of completion proposal should keep selection", completionProposal, completionProposalList.getSelection()[0].getData());
+		assertTrue("Proposal content seems incorrect", LongRunningBarContentAssistProcessor.PROPOSAL.endsWith(((ICompletionProposal)otherProposalItem.getData()).getDisplayString()));
+		assertEquals("Addition of completion proposal should keep selection", selectedProposal, completionProposalList.getSelection()[0].getData());
+	}
 
-		// TODO find a way to actually trigger completion and verify result against Editor content
-		// Assert.assertEquals("Completion didn't complete", "bars are good for a beer.", ((StyledText)editor.getAdapter(Control.class)).getText());
-		completionShell.close();
+	private Shell findNewShell(Set<Shell> beforeShells) {
+		Shell[] afterShells = Arrays.stream(editor.getSite().getShell().getDisplay().getShells())
+				.filter(Shell::isVisible)
+				.filter(shell -> !beforeShells.contains(shell))
+				.toArray(Shell[]::new);
+		assertEquals("No new shell found", 1, afterShells.length);
+		return afterShells[0];
 	}
 
 	@Test
 	public void testCompletionFreeze_bug521484() throws Exception {
-		Set<Shell> beforeShell = new HashSet<>(Arrays.asList(Display.getDefault().getShells()));
+		final Set<Shell> beforeShells = Arrays.stream(editor.getSite().getShell().getDisplay().getShells()).filter(Shell::isVisible).collect(Collectors.toSet());
 		editor.selectAndReveal(3, 0);
 		ContentAssistAction action = (ContentAssistAction) editor.getAction(ITextEditorActionConstants.CONTENT_ASSIST);
 		action.update();
 		action.run();
 		waitAndDispatch(100);
-		Set<Shell> afterShell = new HashSet<>(Arrays.asList(Display.getDefault().getShells()));
-		afterShell.removeAll(beforeShell);
-		assertEquals("No completion", 1, afterShell.size());
-		Shell completionShell= afterShell.iterator().next();
-		final Table completionProposalList = findCompletionSelectionControl(completionShell);
+		this.completionShell= findNewShell(beforeShells);
+		final Table completionProposalList = findCompletionSelectionControl(this.completionShell);
 		// should be instantaneous, but happens to go asynchronous on CI so let's allow a wait
 		new DisplayHelper() {
 			@Override
@@ -116,22 +130,34 @@
 		assertTrue("Missing computing info entry", computingItem.getText().contains("Computing")); //$NON-NLS-1$ //$NON-NLS-2$
 		// Some processors are long running, moving cursor can cause freeze (bug 521484)
 		// asynchronous
+		long timestamp = System.currentTimeMillis();
+		emulatePressLeftArrowKey();
+		DisplayHelper.sleep(editor.getSite().getShell().getDisplay(), 200); //give time to process events
+		long processingDuration = System.currentTimeMillis() - timestamp;
+		assertTrue("UI Thread frozen for " + processingDuration + "ms", processingDuration < LongRunningBarContentAssistProcessor.DELAY);		
+	}
+
+	@Test
+	public void testMoveCaretBackUsesAllProcessors_bug522255() throws Exception {
+		final Set<Shell> beforeShells = Arrays.stream(editor.getSite().getShell().getDisplay().getShells()).filter(Shell::isVisible).collect(Collectors.toSet());
+		testCompletion();
+		emulatePressLeftArrowKey();
+		DisplayHelper.sleep(editor.getSite().getShell().getDisplay(), LongRunningBarContentAssistProcessor.DELAY + 500); // adding delay is a workaround for bug521484, use only 100ms without the bug
+		this.completionShell= findNewShell(beforeShells);
+		final Table completionProposalList = findCompletionSelectionControl(this.completionShell);
+		assertEquals("Missing proposals from a Processor", 2, completionProposalList.getItemCount()); // replace with line below when #5214894 is done
+		// checkCompletionContent(completionProposalList); // use this instead of assert above when #521484 is done
+	}
+
+	private void emulatePressLeftArrowKey() {
+		editor.selectAndReveal(((ITextSelection)editor.getSelectionProvider().getSelection()).getOffset() - 1, 0);
 		StyledText styledText = (StyledText) editor.getAdapter(Control.class);
-		styledText.setSelection(styledText.getSelectionRange().x - 1);
 		Event e = new Event();
 		e.type = ST.VerifyKey;
 		e.widget = styledText;
 		e.keyCode = SWT.ARROW_LEFT;
 		e.display = styledText.getDisplay();
-		long timestamp = System.currentTimeMillis();
 		styledText.notifyListeners(ST.VerifyKey, e);
-		DisplayHelper.sleep(styledText.getDisplay(), 200); //give time to process events
-		long processingDuration = System.currentTimeMillis() - timestamp;
-		assertTrue("UI Thread frozen for " + processingDuration + "ms", processingDuration < LongRunningBarContentAssistProcessor.DELAY);		
-
-		if (!completionShell.isDisposed()) {
-			completionShell.close();
-		}
 	}
 
 	private Table findCompletionSelectionControl(Widget control) {
@@ -148,4 +174,10 @@
 		return null;
 	}
 
+	@After
+	public void closeShell() {
+		if (this.completionShell != null && !completionShell.isDisposed()) {
+			completionShell.close();
+		}
+	}
 }
diff --git a/org.eclipse.ui.genericeditor.tests/src/org/eclipse/ui/genericeditor/tests/contributions/BarContentAssistProcessor.java b/org.eclipse.ui.genericeditor.tests/src/org/eclipse/ui/genericeditor/tests/contributions/BarContentAssistProcessor.java
index 745f42d..f53da97 100644
--- a/org.eclipse.ui.genericeditor.tests/src/org/eclipse/ui/genericeditor/tests/contributions/BarContentAssistProcessor.java
+++ b/org.eclipse.ui.genericeditor.tests/src/org/eclipse/ui/genericeditor/tests/contributions/BarContentAssistProcessor.java
@@ -10,6 +10,7 @@
  *******************************************************************************/
 package org.eclipse.ui.genericeditor.tests.contributions;
 
+import org.eclipse.jface.text.BadLocationException;
 import org.eclipse.jface.text.ITextViewer;
 import org.eclipse.jface.text.contentassist.CompletionProposal;
 import org.eclipse.jface.text.contentassist.ICompletionProposal;
@@ -19,15 +20,30 @@
 
 public class BarContentAssistProcessor implements IContentAssistProcessor {
 
-	public static final String PROPOSAL = "s are good for a beer.";
+	public static final String PROPOSAL = "bars are good for a beer.";
+	private final String completeString;
+
+	public BarContentAssistProcessor() {
+		this(PROPOSAL);
+	}
+
+	public BarContentAssistProcessor(String completeString) {
+		this.completeString = completeString;
+	}
 
 	@Override
 	public ICompletionProposal[] computeCompletionProposals(ITextViewer viewer, int offset) {
-		String text = viewer.getDocument().get();
-		if (text.length() >= 3 && offset >= 3 && text.substring(offset - 3, offset).equals("bar")) {
-			String message = PROPOSAL;
-			CompletionProposal proposal = new CompletionProposal(message, offset, 0, message.length());
-			return new ICompletionProposal[] { proposal };
+		for (int offsetInProposal = Math.min(this.completeString.length(), viewer.getDocument().getLength()); offsetInProposal > 0; offsetInProposal--) {
+			String maybeMatchingString = this.completeString.substring(0, offsetInProposal);
+			try {
+				int lastIndex = offset - offsetInProposal + this.completeString.length();
+				if (offset >= offsetInProposal && viewer.getDocument().get(offset - offsetInProposal, maybeMatchingString.length()).equals(maybeMatchingString)) {
+					CompletionProposal proposal = new CompletionProposal(this.completeString.substring(offsetInProposal), offset, 0, lastIndex);
+					return new ICompletionProposal[] { proposal };
+				}
+			} catch (BadLocationException e) {
+				e.printStackTrace();
+			}
 		}
 		return new ICompletionProposal[0];
 	}
diff --git a/org.eclipse.ui.genericeditor.tests/src/org/eclipse/ui/genericeditor/tests/contributions/LongRunningBarContentAssistProcessor.java b/org.eclipse.ui.genericeditor.tests/src/org/eclipse/ui/genericeditor/tests/contributions/LongRunningBarContentAssistProcessor.java
index 6f504d7..ce0dc48 100644
--- a/org.eclipse.ui.genericeditor.tests/src/org/eclipse/ui/genericeditor/tests/contributions/LongRunningBarContentAssistProcessor.java
+++ b/org.eclipse.ui.genericeditor.tests/src/org/eclipse/ui/genericeditor/tests/contributions/LongRunningBarContentAssistProcessor.java
@@ -11,17 +11,17 @@
 package org.eclipse.ui.genericeditor.tests.contributions;
 
 import org.eclipse.jface.text.ITextViewer;
-import org.eclipse.jface.text.contentassist.CompletionProposal;
 import org.eclipse.jface.text.contentassist.ICompletionProposal;
-import org.eclipse.jface.text.contentassist.IContentAssistProcessor;
-import org.eclipse.jface.text.contentassist.IContextInformation;
-import org.eclipse.jface.text.contentassist.IContextInformationValidator;
 
-public class LongRunningBarContentAssistProcessor implements IContentAssistProcessor {
+public class LongRunningBarContentAssistProcessor extends BarContentAssistProcessor {
 
-	public static final String PROPOSAL = "s are also good for soft drink cocktails.";
+	public static final String PROPOSAL = "bars are also good for soft drink cocktails.";
 	public static final int DELAY = 2000;
 
+	public LongRunningBarContentAssistProcessor() {
+		super(PROPOSAL);
+	}
+
 	@Override
 	public ICompletionProposal[] computeCompletionProposals(ITextViewer viewer, int offset) {
 		try {
@@ -30,38 +30,6 @@
 			// TODO Auto-generated catch block
 			e.printStackTrace();
 		}
-		String text = viewer.getDocument().get();
-		if (text.length() >= 3 && offset >= 3 && text.substring(offset - 3, offset).equals("bar")) {
-			String message = PROPOSAL;
-			CompletionProposal proposal = new CompletionProposal(message, offset, 0, message.length());
-			return new ICompletionProposal[] { proposal };
-		}
-		return new ICompletionProposal[0];
+		return super.computeCompletionProposals(viewer, offset);
 	}
-
-	@Override
-	public IContextInformation[] computeContextInformation(ITextViewer viewer, int offset) {
-		return null;
-	}
-
-	@Override
-	public char[] getCompletionProposalAutoActivationCharacters() {
-		return null;
-	}
-
-	@Override
-	public char[] getContextInformationAutoActivationCharacters() {
-		return null;
-	}
-
-	@Override
-	public String getErrorMessage() {
-		return null;
-	}
-
-	@Override
-	public IContextInformationValidator getContextInformationValidator() {
-		return null;
-	}
-
 }