diff options
author | Lucas Bullen | 2017-09-08 14:05:20 +0000 |
---|---|---|
committer | Mickael Istria | 2017-09-15 07:28:29 +0000 |
commit | 436cf72b5d16b64ce88662e468a4839ed8dd99e9 (patch) | |
tree | 223533de2fcc8463e5c74d8831c5f170a2e624f6 | |
parent | 4c519144c9f59cb4c5b40bc9b375d94fdf9fd285 (diff) | |
download | eclipse.platform.text-436cf72b5d16b64ce88662e468a4839ed8dd99e9.tar.gz eclipse.platform.text-436cf72b5d16b64ce88662e468a4839ed8dd99e9.tar.xz eclipse.platform.text-436cf72b5d16b64ce88662e468a4839ed8dd99e9.zip |
Bug 521484 - Async content-assist can freeze UI Thread
Added async proposal filtering to avoid defaulting to sync proposal
retrieval
Change-Id: Ibdc3767359e7fe6f4ff8d52d8cd895e1ddea04ae
Signed-off-by: Lucas Bullen <lbullen@redhat.com>
Also-By: Mickael Istria <mistria@redhat.com>
6 files changed, 87 insertions, 8 deletions
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 1dafdf5fd46..61a1164e20b 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,6 +32,7 @@ import org.eclipse.swt.widgets.Display; import org.eclipse.jface.contentassist.IContentAssistSubjectControl; import org.eclipse.jface.text.BadLocationException; +import org.eclipse.jface.text.DocumentEvent; import org.eclipse.jface.text.IDocument; import org.eclipse.jface.text.ITextViewer; import org.eclipse.jface.text.TextUtilities; @@ -215,7 +216,37 @@ class AsyncCompletionProposalPopup extends CompletionProposalPopup { return getErrorMessage(); } - + + @Override + List<ICompletionProposal> computeProposals(int offset) { + fProposalShell.dispose(); + showProposals(true); + return fComputedProposals; + } + + @Override + void createProposalSelector() { + super.createProposalSelector(); + fProposalShell.addDisposeListener(e -> hide()); + } + + @Override + protected List<ICompletionProposal> computeFilteredProposals(int offset, DocumentEvent event) { + if(fComputedProposals.size() > 0 && fComputedProposals.get(0) instanceof ComputingProposal) { + Set<CompletableFuture<List<ICompletionProposal>>> remaining = Collections.synchronizedSet(new HashSet<>(fFutures)); + for (CompletableFuture<List<ICompletionProposal>> future : fFutures) { + future.thenRun(() -> { + remaining.removeIf(CompletableFuture::isDone); + if (remaining.isEmpty()) { + filterProposals(); + } + }); + } + return fComputedProposals; + } + return super.computeFilteredProposals(offset, event); + } + @Override public void hide() { super.hide(); diff --git a/org.eclipse.jface.text/src/org/eclipse/jface/text/contentassist/CompletionProposalPopup.java b/org.eclipse.jface.text/src/org/eclipse/jface/text/contentassist/CompletionProposalPopup.java index c47118d8b1b..93fa19be112 100644 --- a/org.eclipse.jface.text/src/org/eclipse/jface/text/contentassist/CompletionProposalPopup.java +++ b/org.eclipse.jface.text/src/org/eclipse/jface/text/contentassist/CompletionProposalPopup.java @@ -552,7 +552,7 @@ class CompletionProposalPopup implements IContentAssistListener { * @param offset the offset * @return the completion proposals available at this offset, never null */ - private List<ICompletionProposal> computeProposals(int offset) { + List<ICompletionProposal> computeProposals(int offset) { ICompletionProposal[] completionProposals; if (fContentAssistSubjectControl != null) { completionProposals= fContentAssistant.computeCompletionProposals(fContentAssistSubjectControl, offset); @@ -1460,7 +1460,7 @@ class CompletionProposalPopup implements IContentAssistListener { * Filters the displayed proposal based on the given cursor position and the * offset of the original invocation of the content assistant. */ - private void filterProposals() { + void filterProposals() { if (!fIsFilterPending) { fIsFilterPending= true; Control control= fContentAssistSubjectControlAdapter.getControl(); @@ -1477,7 +1477,7 @@ class CompletionProposalPopup implements IContentAssistListener { * @return the set of filtered proposals * @since 3.0 */ - private List<ICompletionProposal> computeFilteredProposals(int offset, DocumentEvent event) { + List<ICompletionProposal> computeFilteredProposals(int offset, DocumentEvent event) { if (offset == fInvocationOffset && event == null) { fIsFilteredSubset= false; diff --git a/org.eclipse.ui.genericeditor.tests/plugin.xml b/org.eclipse.ui.genericeditor.tests/plugin.xml index c7ce8e9b8de..ea8d49a05c5 100644 --- a/org.eclipse.ui.genericeditor.tests/plugin.xml +++ b/org.eclipse.ui.genericeditor.tests/plugin.xml @@ -20,7 +20,7 @@ </contentAssistProcessor> <contentAssistProcessor class="org.eclipse.ui.genericeditor.tests.contributions.LongRunningBarContentAssistProcessor" - contentType="org.eclipse.ui.genericeditor.tests.content-type"> + contentType="org.eclipse.ui.genericeditor.tests.specialized-content-type"> </contentAssistProcessor> </extension> <extension 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 97b5ef1b370..7ed46a86486 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 @@ -19,8 +19,13 @@ import java.util.Set; import org.junit.Test; +import org.eclipse.swt.SWT; +import org.eclipse.swt.custom.ST; +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; @@ -80,12 +85,55 @@ public class CompletionTest extends AbstratGenericEditorTest { 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()); - + // 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(); } + @Test + public void testCompletionFreeze_bug521484() throws Exception { + Set<Shell> beforeShell = new HashSet<>(Arrays.asList(Display.getDefault().getShells())); + 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); + // 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); + 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$ + // Some processors are long running, moving cursor can cause freeze (bug 521484) + // asynchronous + 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) { if (control instanceof Table) { return (Table)control; 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 7f6a1485797..745f42dc3bf 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 @@ -24,7 +24,7 @@ public class BarContentAssistProcessor implements IContentAssistProcessor { @Override public ICompletionProposal[] computeCompletionProposals(ITextViewer viewer, int offset) { String text = viewer.getDocument().get(); - if (text.length() >= 3 && text.substring(offset - 3, offset).equals("bar")) { + 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 }; 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 b902524379d..6f504d78053 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 @@ -31,7 +31,7 @@ public class LongRunningBarContentAssistProcessor implements IContentAssistProce e.printStackTrace(); } String text = viewer.getDocument().get(); - if (text.length() >= 3 && text.substring(offset - 3, offset).equals("bar")) { + 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 }; |