diff options
author | Mickael Istria | 2019-07-03 13:45:50 +0000 |
---|---|---|
committer | Jeff Johnston | 2020-01-06 19:09:56 +0000 |
commit | c80a0ed5358fd6caf3f92e129276abba94329c19 (patch) | |
tree | 6b88e5525863017d9fe1635cefbc516d1a9f9e91 | |
parent | 192cfdece48b10c4d7193d82af0d9ba703e39502 (diff) | |
download | eclipse.jdt.ui-c80a0ed5358fd6caf3f92e129276abba94329c19.tar.gz eclipse.jdt.ui-c80a0ed5358fd6caf3f92e129276abba94329c19.tar.xz eclipse.jdt.ui-c80a0ed5358fd6caf3f92e129276abba94329c19.zip |
Bug 531061 - Asynchronous/non-blocking Java completion
Change-Id: I122f94304b3be477fd26df180cbdbcb0098508d8
Signed-off-by: Mickael Istria <mistria@redhat.com>
13 files changed, 412 insertions, 78 deletions
diff --git a/org.eclipse.jdt.text.tests/plugin.xml b/org.eclipse.jdt.text.tests/plugin.xml index 8931f92480..49a78dccb8 100644 --- a/org.eclipse.jdt.text.tests/plugin.xml +++ b/org.eclipse.jdt.text.tests/plugin.xml @@ -36,5 +36,15 @@ type="java.lang.Object"> </propertyTester> </extension> - + <extension + point="org.eclipse.jdt.ui.javaCompletionProposalComputer" + id="LongCompletionProposalComputer"> + <javaCompletionProposalComputer + class="org.eclipse.jdt.text.tests.contentassist.LongCompletionProposalComputer" + categoryId="org.eclipse.jdt.ui.javaAllProposalCategory" + requiresUIThread="false"> + <partition type="__dftl_partition_content_type"/> + <partition type="__java_string"/> + </javaCompletionProposalComputer> + </extension> </plugin> diff --git a/org.eclipse.jdt.text.tests/src/org/eclipse/jdt/text/tests/contentassist/CheckUIThreadReactivityThread.java b/org.eclipse.jdt.text.tests/src/org/eclipse/jdt/text/tests/contentassist/CheckUIThreadReactivityThread.java new file mode 100644 index 0000000000..cc14e27238 --- /dev/null +++ b/org.eclipse.jdt.text.tests/src/org/eclipse/jdt/text/tests/contentassist/CheckUIThreadReactivityThread.java @@ -0,0 +1,53 @@ +/******************************************************************************* + * Copyright (c) 2019 Red Hat Inc., and others. + * + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package org.eclipse.jdt.text.tests.contentassist; + +import org.eclipse.swt.widgets.Display; + +/** + * This thread polls the display every 50milliseconds with a null UI operation + * request and computes how long it take for display to process the request. + * The time to handle request is time when display is busy doing other work, so + * it's actually a UI Freeze. + * + * This could be moved to SWT or other common place where we need to check + * UI Freezes and reused form there, + */ +public class CheckUIThreadReactivityThread extends Thread { + + long pauseBetweenEachPing = 50; + final private Display fDisplay; + + private long maxDuration; + + public CheckUIThreadReactivityThread(Display display) { + fDisplay= display; + } + + @Override + public void run() { + while (!isInterrupted()) { + long duration = System.currentTimeMillis(); + fDisplay.syncExec(() -> {}); // do nothing, but in UI Thread + duration = System.currentTimeMillis() - duration; + maxDuration = Math.max(duration, maxDuration); + try { + sleep(pauseBetweenEachPing); + } catch (InterruptedException e) { + // nothing + } + } + } + + public long getMaxDuration() { + return maxDuration; + } +} diff --git a/org.eclipse.jdt.text.tests/src/org/eclipse/jdt/text/tests/contentassist/ContentAssistAndThreadsTest.java b/org.eclipse.jdt.text.tests/src/org/eclipse/jdt/text/tests/contentassist/ContentAssistAndThreadsTest.java new file mode 100644 index 0000000000..2fc1067f73 --- /dev/null +++ b/org.eclipse.jdt.text.tests/src/org/eclipse/jdt/text/tests/contentassist/ContentAssistAndThreadsTest.java @@ -0,0 +1,144 @@ +/******************************************************************************* + * Copyright (c) 2019 Red Hat Inc., and others. + * + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package org.eclipse.jdt.text.tests.contentassist; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Set; +import java.util.concurrent.atomic.AtomicReference; +import java.util.stream.Collectors; + +import org.junit.After; +import org.junit.Test; + +import org.eclipse.jdt.testplugin.JavaProjectHelper; + +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Display; +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.core.runtime.IStatus; +import org.eclipse.core.runtime.NullProgressMonitor; + +import org.eclipse.jface.text.contentassist.ContentAssistant; + +import org.eclipse.ui.texteditor.ContentAssistAction; +import org.eclipse.ui.texteditor.ITextEditorActionConstants; + +import org.eclipse.jdt.core.ICompilationUnit; +import org.eclipse.jdt.core.IJavaProject; +import org.eclipse.jdt.core.IPackageFragment; +import org.eclipse.jdt.core.IPackageFragmentRoot; + +import org.eclipse.jdt.ui.JavaUI; +import org.eclipse.jdt.ui.PreferenceConstants; +import org.eclipse.jdt.ui.text.IJavaPartitions; + +import org.eclipse.jdt.internal.ui.JavaPlugin; +import org.eclipse.jdt.internal.ui.javaeditor.JavaEditor; +import org.eclipse.jdt.internal.ui.text.java.JavaCompletionProcessor; + +public class ContentAssistAndThreadsTest extends AbstractCompletionTest { + + @After + public void resetPreference() { + JavaPlugin.getDefault().getPreferenceStore().setToDefault(PreferenceConstants.CODEASSIST_NONUITHREAD_COMPUTATION); + } + + @Test + public void testComputeCompletionInNonUIThread() throws Exception { + IJavaProject fJProject1= JavaProjectHelper.createJavaProject("TestProject1", "bin"); + JavaProjectHelper.addRTJar(fJProject1); + IPackageFragmentRoot sourceFolder= JavaProjectHelper.addSourceContainer(fJProject1, "src"); + IPackageFragment pack1= sourceFolder.createPackageFragment("test1", false, null); + ICompilationUnit cu= pack1.createCompilationUnit("Blah.java", "", true, new NullProgressMonitor()); + JavaEditor part= (JavaEditor) JavaUI.openInEditor(cu); + ContentAssistant assistant= new ContentAssistant(); + assistant.setDocumentPartitioning(IJavaPartitions.JAVA_PARTITIONING); + JavaCompletionProcessor javaProcessor= new JavaCompletionProcessor(part, assistant, getContentType()); + AtomicReference<Throwable> exception = new AtomicReference<>(); + List<IStatus> errors = new ArrayList<>(); + JavaPlugin.getDefault().getLog().addLogListener((status, plugin) -> { + if (status.getSeverity() >= IStatus.WARNING) { + errors.add(status); + } + }); + Thread thread = new Thread(() -> { + try { + javaProcessor.computeCompletionProposals(part.getViewer(), 0); + // a popup can be shown and block the thread in case of error + } catch (Exception e) { + exception.set(e); + } + }); + thread.start(); + thread.join(); + if (exception.get() != null) { + exception.get().printStackTrace(); + } + assertNull(exception.get()); + assertEquals(Collections.emptyList(), errors); + } + + @Test + public void testLongNonUIThreadContentAssistDoesntFreezeUI() throws Exception { + JavaPlugin.getDefault().getPreferenceStore().setValue(PreferenceConstants.CODEASSIST_NONUITHREAD_COMPUTATION, true); + IJavaProject fJProject1= JavaProjectHelper.createJavaProject("TestProject1", "bin"); + JavaProjectHelper.addRTJar(fJProject1); + IPackageFragmentRoot sourceFolder= JavaProjectHelper.addSourceContainer(fJProject1, "src"); + IPackageFragment pack1= sourceFolder.createPackageFragment("test1", false, null); + ICompilationUnit cu= pack1.createCompilationUnit("Blah.java", LongCompletionProposalComputer.CONTENT_TRIGGER_STRING, true, new NullProgressMonitor()); + JavaEditor part= (JavaEditor) JavaUI.openInEditor(cu); + final Set<Shell> beforeShells = Arrays.stream(part.getSite().getShell().getDisplay().getShells()).filter(Shell::isVisible).collect(Collectors.toSet()); + Display display= part.getViewer().getTextWidget().getDisplay(); + ContentAssistAction action = (ContentAssistAction) part.getAction(ITextEditorActionConstants.CONTENT_ASSIST); + action.update(); + CheckUIThreadReactivityThread thread = new CheckUIThreadReactivityThread(display); + thread.start(); + display.asyncExec(() -> action.run()); // mustn't be synchronous or CheckUIThreadReactivityTest can miss it. + try { + assertTrue("Missing completion proposal", new org.eclipse.jdt.text.tests.performance.DisplayHelper() { + @Override + protected boolean condition() { + Set<Shell> newShells = Arrays.stream(part.getSite().getShell().getDisplay().getShells()).filter(Shell::isVisible).collect(Collectors.toSet()); + newShells.removeAll(beforeShells); + if (!newShells.isEmpty()) { + Table completionTable = findCompletionSelectionControl(newShells.iterator().next()); + return Arrays.stream(completionTable.getItems()).map(TableItem::getText).anyMatch(LongCompletionProposalComputer.CONTENT_TRIGGER_STRING::equals); + } + return false; + } + }.waitForCondition(display, 3000)); + } finally { + thread.interrupt(); + } + assertTrue("UI was frozen for " + thread.getMaxDuration(), thread.getMaxDuration() < 1000); + } + + private Table findCompletionSelectionControl(Widget control) { + if (control instanceof Table) { + return (Table)control; + } else if (control instanceof Composite) { + for (Widget child : ((Composite)control).getChildren()) { + Table res = findCompletionSelectionControl(child); + if (res != null) { + return res; + } + } + } + return null; + } +} diff --git a/org.eclipse.jdt.text.tests/src/org/eclipse/jdt/text/tests/contentassist/LongCompletionProposalComputer.java b/org.eclipse.jdt.text.tests/src/org/eclipse/jdt/text/tests/contentassist/LongCompletionProposalComputer.java new file mode 100644 index 0000000000..1dacabe983 --- /dev/null +++ b/org.eclipse.jdt.text.tests/src/org/eclipse/jdt/text/tests/contentassist/LongCompletionProposalComputer.java @@ -0,0 +1,62 @@ +/******************************************************************************* + * Copyright (c) 2019 Red Hat Inc., and others. + * + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package org.eclipse.jdt.text.tests.contentassist; + +import java.util.Collections; +import java.util.List; + +import org.eclipse.core.runtime.IProgressMonitor; + +import org.eclipse.jface.text.contentassist.CompletionProposal; +import org.eclipse.jface.text.contentassist.ICompletionProposal; +import org.eclipse.jface.text.contentassist.IContextInformation; + +import org.eclipse.jdt.ui.text.java.ContentAssistInvocationContext; +import org.eclipse.jdt.ui.text.java.IJavaCompletionProposalComputer; + +import org.eclipse.jdt.internal.ui.JavaPlugin; + +public class LongCompletionProposalComputer implements IJavaCompletionProposalComputer { + + public static final String CONTENT_TRIGGER_STRING = "longCompletion"; + + @Override + public void sessionStarted() { + } + + @Override + public List<ICompletionProposal> computeCompletionProposals(ContentAssistInvocationContext context, IProgressMonitor monitor) { + if (context.getDocument().get().contains(CONTENT_TRIGGER_STRING)) { + try { + Thread.sleep(1000); + } catch (InterruptedException e) { + JavaPlugin.log(e); + } + return Collections.singletonList(new CompletionProposal(CONTENT_TRIGGER_STRING, 0, 0, 0, null, CONTENT_TRIGGER_STRING, null, null)); + } + return Collections.emptyList(); + } + + @Override + public List<IContextInformation> computeContextInformation(ContentAssistInvocationContext context, IProgressMonitor monitor) { + return Collections.emptyList(); + } + + @Override + public String getErrorMessage() { + return null; + } + + @Override + public void sessionEnded() { + } + +} diff --git a/org.eclipse.jdt.ui/META-INF/MANIFEST.MF b/org.eclipse.jdt.ui/META-INF/MANIFEST.MF index a20ac02cfb..80123f900f 100644 --- a/org.eclipse.jdt.ui/META-INF/MANIFEST.MF +++ b/org.eclipse.jdt.ui/META-INF/MANIFEST.MF @@ -3,7 +3,7 @@ Automatic-Module-Name: org.eclipse.jdt.ui Bundle-ManifestVersion: 2 Bundle-Name: %pluginName Bundle-SymbolicName: org.eclipse.jdt.ui; singleton:=true -Bundle-Version: 3.20.100.qualifier +Bundle-Version: 3.21.0.qualifier Bundle-Activator: org.eclipse.jdt.internal.ui.JavaPlugin Bundle-ActivationPolicy: lazy Bundle-Vendor: %providerName diff --git a/org.eclipse.jdt.ui/pom.xml b/org.eclipse.jdt.ui/pom.xml index 7d0bf4d14f..609886acbb 100644 --- a/org.eclipse.jdt.ui/pom.xml +++ b/org.eclipse.jdt.ui/pom.xml @@ -18,7 +18,7 @@ </parent> <groupId>org.eclipse.jdt</groupId> <artifactId>org.eclipse.jdt.ui</artifactId> - <version>3.20.100-SNAPSHOT</version> + <version>3.21.0-SNAPSHOT</version> <packaging>eclipse-plugin</packaging> <properties> <code.ignoredWarnings>-warn:-deprecation,unavoidableGenericProblems</code.ignoredWarnings> diff --git a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/preferences/CodeAssistAdvancedConfigurationBlock.java b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/preferences/CodeAssistAdvancedConfigurationBlock.java index 8f753a99fa..8909196293 100644 --- a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/preferences/CodeAssistAdvancedConfigurationBlock.java +++ b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/preferences/CodeAssistAdvancedConfigurationBlock.java @@ -20,6 +20,7 @@ import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.stream.Collectors; import org.eclipse.swt.SWT; import org.eclipse.swt.events.SelectionAdapter; @@ -53,9 +54,7 @@ import org.eclipse.jface.layout.PixelConverter; import org.eclipse.jface.resource.ImageDescriptor; import org.eclipse.jface.resource.JFaceResources; import org.eclipse.jface.viewers.ArrayContentProvider; -import org.eclipse.jface.viewers.CheckStateChangedEvent; import org.eclipse.jface.viewers.CheckboxTableViewer; -import org.eclipse.jface.viewers.ICheckStateListener; import org.eclipse.jface.viewers.IStructuredSelection; import org.eclipse.jface.viewers.ITableLabelProvider; import org.eclipse.jface.viewers.LabelProvider; @@ -92,12 +91,14 @@ final class CodeAssistAdvancedConfigurationBlock extends OptionsConfigurationBlo private static final Key PREF_EXCLUDED_CATEGORIES= getJDTUIKey(PreferenceConstants.CODEASSIST_EXCLUDED_CATEGORIES); private static final Key PREF_CATEGORY_ORDER= getJDTUIKey(PreferenceConstants.CODEASSIST_CATEGORY_ORDER); private static final Key PREF_CODEASSIST_TIMEOUT_FOR_PARAMETER_NAME_FROM_ATTACHED_JAVADOC= getJDTCoreKey(JavaCore.TIMEOUT_FOR_PARAMETER_NAME_FROM_ATTACHED_JAVADOC); + private static final Key PREF_CODEASSIST_NONUITHREAD_COMPUTATION = getJDTUIKey(PreferenceConstants.CODEASSIST_NONUITHREAD_COMPUTATION); private static Key[] getAllKeys() { return new Key[] { PREF_EXCLUDED_CATEGORIES, PREF_CATEGORY_ORDER, - PREF_CODEASSIST_TIMEOUT_FOR_PARAMETER_NAME_FROM_ATTACHED_JAVADOC + PREF_CODEASSIST_TIMEOUT_FOR_PARAMETER_NAME_FROM_ATTACHED_JAVADOC, + PREF_CODEASSIST_NONUITHREAD_COMPUTATION }; } @@ -108,8 +109,9 @@ final class CodeAssistAdvancedConfigurationBlock extends OptionsConfigurationBlo */ @Override public Image getColumnImage(Object element, int columnIndex) { - if (columnIndex == 0) + if (columnIndex == 0) { return ((ModelElement) element).getImage(); + } return null; } @@ -145,8 +147,9 @@ final class CodeAssistAdvancedConfigurationBlock extends OptionsConfigurationBlo */ @Override public Image getColumnImage(Object element, int columnIndex) { - if (columnIndex == 0) + if (columnIndex == 0) { return ((ModelElement) element).getImage(); + } return null; } @@ -220,8 +223,9 @@ final class CodeAssistAdvancedConfigurationBlock extends OptionsConfigurationBlo StringBuilder buf= new StringBuilder(); for (ModelElement item : fElements) { boolean included= changed == item ? isInDefaultCategory : item.isInDefaultCategory(); - if (!included) + if (!included) { buf.append(item.getId() + SEPARATOR); + } } String newValue= buf.toString(); @@ -257,8 +261,9 @@ final class CodeAssistAdvancedConfigurationBlock extends OptionsConfigurationBlo private int readOrderPreference(CompletionProposalCategory cat) { for (String sortOrderId : getTokens(getValue(PREF_CATEGORY_ORDER), SEPARATOR)) { String[] idAndRank= getTokens(sortOrderId, COLON); - if (idAndRank[0].equals(cat.getId())) + if (idAndRank[0].equals(cat.getId())) { return Integer.parseInt(idAndRank[1]); + } } return LIMIT - 1; } @@ -304,16 +309,18 @@ final class CodeAssistAdvancedConfigurationBlock extends OptionsConfigurationBlo return fPreferenceModel.readInclusionPreference(fCategory); } void setInDefaultCategory(boolean included) { - if (included != isInDefaultCategory()) + if (included != isInDefaultCategory()) { fPreferenceModel.writeInclusionPreference(this, included); + } } String getId() { return fCategory.getId(); } int getRank() { int rank= getInternalRank(); - if (rank > PreferenceModel.LIMIT) + if (rank > PreferenceModel.LIMIT) { return rank - PreferenceModel.LIMIT; + } return rank; } void moveUp() { @@ -330,8 +337,9 @@ final class CodeAssistAdvancedConfigurationBlock extends OptionsConfigurationBlo } void setSeparateCommand(boolean separate) { - if (separate != isSeparateCommand()) + if (separate != isSeparateCommand()) { fPreferenceModel.writeOrderPreference(this, separate); + } } void update() { @@ -387,6 +395,8 @@ final class CodeAssistAdvancedConfigurationBlock extends OptionsConfigurationBlo createFiller(composite, columns); createParameterTimeoutControl(composite, columns); + createFiller(composite, columns); + createNonUIThreadControl(composite, columns); updateControls(); if (fModel.elements.size() > 0) { @@ -400,13 +410,30 @@ final class CodeAssistAdvancedConfigurationBlock extends OptionsConfigurationBlo return scrolled; } + private void createNonUIThreadControl(Composite composite, int columns) { + PixelConverter pixelConverter= new PixelConverter(composite); + String str= PreferencesMessages.CodeAssistAdvancedConfigurationBlock_nonUIThread; + Button checkbox = addCheckBox(composite, str, PREF_CODEASSIST_NONUITHREAD_COMPUTATION, new String[] { Boolean.TRUE.toString(), Boolean.FALSE.toString() }, pixelConverter.convertWidthInCharsToPixels(7)); + checkbox.setLayoutData(new GridData(GridData.FILL, GridData.FILL, true, false, columns, 1)); + CompletionProposalComputerRegistry registry= CompletionProposalComputerRegistry.getDefault(); + if (registry.computingCompletionRequiresUIThread()) { + Label label = new Label(composite, SWT.NONE); + label.setText(Messages.format(PreferencesMessages.CodeAssistAdvancedConfigurationBlock_nonUIThread_computersRequiringUIThread, registry.getComputersRequiringUIThreadNames().map(name -> "* " + name).collect(Collectors.joining("\n")))); //$NON-NLS-1$ //$NON-NLS-2$ + GridData layoutData= new GridData(GridData.FILL, GridData.FILL, true, false, columns, 1); + layoutData.horizontalIndent = 30; + label.setLayoutData(layoutData); + label.setEnabled(false); + } + } + private void createDefaultLabel(Composite composite, int h_span) { final ICommandService commandSvc= PlatformUI.getWorkbench().getAdapter(ICommandService.class); final Command command= commandSvc.getCommand(ITextEditorActionDefinitionIds.CONTENT_ASSIST_PROPOSALS); ParameterizedCommand pCmd= new ParameterizedCommand(command, null); String key= getKeyboardShortcut(pCmd); - if (key == null) + if (key == null) { key= PreferencesMessages.CodeAssistAdvancedConfigurationBlock_no_shortcut; + } PixelConverter pixelConverter= new PixelConverter(composite); int width= pixelConverter.convertWidthInCharsToPixels(40); @@ -440,13 +467,10 @@ final class CodeAssistAdvancedConfigurationBlock extends OptionsConfigurationBlo keyColumn.setText(PreferencesMessages.CodeAssistAdvancedConfigurationBlock_default_table_keybinding_column_title); keyColumn.setResizable(false); - fDefaultViewer.addCheckStateListener(new ICheckStateListener() { - @Override - public void checkStateChanged(CheckStateChangedEvent event) { - boolean checked= event.getChecked(); - ModelElement element= (ModelElement) event.getElement(); - element.setInDefaultCategory(checked); - } + fDefaultViewer.addCheckStateListener(event -> { + boolean checked= event.getChecked(); + ModelElement element= (ModelElement) event.getElement(); + element.setInDefaultCategory(checked); }); fDefaultViewer.setContentProvider(ArrayContentProvider.getInstance()); @@ -460,9 +484,9 @@ final class CodeAssistAdvancedConfigurationBlock extends OptionsConfigurationBlo final int HEADER_MARGIN= 20; int minNameWidth= computeWidth(table, nameColumn.getText()) + HEADER_MARGIN; int minKeyWidth= computeWidth(table, keyColumn.getText()) + HEADER_MARGIN; - for (int i= 0; i < fModel.elements.size(); i++) { - minNameWidth= Math.max(minNameWidth, computeWidth(table, labelProvider.getColumnText(fModel.elements.get(i), 0)) + ICON_AND_CHECKBOX_WITH); - minKeyWidth= Math.max(minKeyWidth, computeWidth(table, labelProvider.getColumnText(fModel.elements.get(i), 1))); + for (ModelElement element : fModel.elements) { + minNameWidth= Math.max(minNameWidth, computeWidth(table, labelProvider.getColumnText(element, 0)) + ICON_AND_CHECKBOX_WITH); + minKeyWidth= Math.max(minKeyWidth, computeWidth(table, labelProvider.getColumnText(element, 1))); } nameColumn.setWidth(minNameWidth); @@ -530,19 +554,16 @@ final class CodeAssistAdvancedConfigurationBlock extends OptionsConfigurationBlo final int ICON_AND_CHECKBOX_WITH= 50; final int HEADER_MARGIN= 20; int minNameWidth= computeWidth(table, nameColumn.getText()) + HEADER_MARGIN; - for (int i= 0; i < fModel.elements.size(); i++) { - minNameWidth= Math.max(minNameWidth, computeWidth(table, labelProvider.getColumnText(fModel.elements.get(i), 0)) + ICON_AND_CHECKBOX_WITH); + for (ModelElement element : fModel.elements) { + minNameWidth= Math.max(minNameWidth, computeWidth(table, labelProvider.getColumnText(element, 0)) + ICON_AND_CHECKBOX_WITH); } nameColumn.setWidth(minNameWidth); - fSeparateViewer.addCheckStateListener(new ICheckStateListener() { - @Override - public void checkStateChanged(CheckStateChangedEvent event) { - boolean checked= event.getChecked(); - ModelElement element= (ModelElement) event.getElement(); - element.setSeparateCommand(checked); - } + fSeparateViewer.addCheckStateListener(event -> { + boolean checked= event.getChecked(); + ModelElement element= (ModelElement) event.getElement(); + element.setSeparateCommand(checked); }); table.addSelectionListener(new SelectionAdapter() { @@ -651,10 +672,12 @@ final class CodeAssistAdvancedConfigurationBlock extends OptionsConfigurationBlo List<ModelElement> separateChecked= new ArrayList<>(size); for (ModelElement element : fModel.elements) { - if (element.isInDefaultCategory()) + if (element.isInDefaultCategory()) { defaultChecked.add(element); - if (element.isSeparateCommand()) + } + if (element.isSeparateCommand()) { separateChecked.add(element); + } } fDefaultViewer.setCheckedElements(defaultChecked.toArray(new Object[defaultChecked.size()])); @@ -680,9 +703,9 @@ final class CodeAssistAdvancedConfigurationBlock extends OptionsConfigurationBlo protected void validateSettings(Key changedKey, String oldValue, String newValue) { if (changedKey == PREF_CODEASSIST_TIMEOUT_FOR_PARAMETER_NAME_FROM_ATTACHED_JAVADOC) { final StatusInfo status= new StatusInfo(); - if (newValue.length() == 0) + if (newValue.length() == 0) { status.setError(PreferencesMessages.CodeAssistAdvancedConfigurationBlock_parameterNameFromAttachedJavadoc_timeout_emptyInput); - else { + } else { try { int number= Integer.parseInt(newValue); int min= 0; @@ -716,8 +739,7 @@ final class CodeAssistAdvancedConfigurationBlock extends OptionsConfigurationBlo */ @Override public void dispose() { - for (Iterator<Image> it= fImages.values().iterator(); it.hasNext();) { - Image image= it.next(); + for (Image image : fImages.values()) { image.dispose(); } @@ -725,8 +747,9 @@ final class CodeAssistAdvancedConfigurationBlock extends OptionsConfigurationBlo } private int computeWidth(Control control, String name) { - if (name == null) + if (name == null) { return 0; + } GC gc= new GC(control); try { gc.setFont(JFaceResources.getDialogFont()); @@ -760,21 +783,24 @@ final class CodeAssistAdvancedConfigurationBlock extends OptionsConfigurationBlo fgLocalBindingManager.setBindings(bindingService.getBindings()); try { Scheme activeScheme= bindingService.getActiveScheme(); - if (activeScheme != null) + if (activeScheme != null) { fgLocalBindingManager.setActiveScheme(activeScheme); + } } catch (NotDefinedException e) { JavaPlugin.log(e); } TriggerSequence[] bindings= fgLocalBindingManager.getActiveBindingsDisregardingContextFor(command); - if (bindings.length > 0) + if (bindings.length > 0) { return bindings[0].format(); + } return null; } private Image getImage(ImageDescriptor imgDesc) { - if (imgDesc == null) + if (imgDesc == null) { return null; + } Image img= fImages.get(imgDesc); if (img == null) { diff --git a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/preferences/PreferencesMessages.java b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/preferences/PreferencesMessages.java index 20ea4a6a84..2dc67c88d5 100644 --- a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/preferences/PreferencesMessages.java +++ b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/preferences/PreferencesMessages.java @@ -65,6 +65,8 @@ public final class PreferencesMessages extends NLS { public static String CodeAssistAdvancedConfigurationBlock_parameterNameFromAttachedJavadoc_timeout_emptyInput; public static String CodeAssistAdvancedConfigurationBlock_parameterNameFromAttachedJavadoc_timeout_invalidInput; public static String CodeAssistAdvancedConfigurationBlock_parameterNameFromAttachedJavadoc_timeout_invalidRange; + public static String CodeAssistAdvancedConfigurationBlock_nonUIThread; + public static String CodeAssistAdvancedConfigurationBlock_nonUIThread_computersRequiringUIThread; public static String ImportOrganizePreferencePage_title; public static String ImportOrganizeConfigurationBlock_order_label; public static String ImportOrganizeConfigurationBlock_other_static; diff --git a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/preferences/PreferencesMessages.properties b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/preferences/PreferencesMessages.properties index 65522c1769..261b5953c8 100644 --- a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/preferences/PreferencesMessages.properties +++ b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/preferences/PreferencesMessages.properties @@ -950,6 +950,9 @@ CodeAssistAdvancedConfigurationBlock_separate_table_category_column_title=Separa CodeAssistAdvancedConfigurationBlock_Up=&Up CodeAssistAdvancedConfigurationBlock_Down=D&own CodeAssistAdvancedConfigurationBlock_parameterNameFromAttachedJavadoc_timeout=&Timeout for fetching a parameter name from attached Javadoc (ms): +CodeAssistAdvancedConfigurationBlock_nonUIThread=Do not block &UI Thread while computing completion proposals (does not affect open editors) +CodeAssistAdvancedConfigurationBlock_nonUIThread_computersRequiringUIThread=\u26A0\uFE0F This setting will be ignored because the following contributions require UI Thread:\n\ +{0} CodeAssistAdvancedConfigurationBlock_parameterNameFromAttachedJavadoc_timeout_emptyInput=Empty input. CodeAssistAdvancedConfigurationBlock_parameterNameFromAttachedJavadoc_timeout_invalidInput=''{0}'' is not a valid input. diff --git a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/text/java/CompletionProposalComputerRegistry.java b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/text/java/CompletionProposalComputerRegistry.java index 33dd482a47..a892d355b2 100644 --- a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/text/java/CompletionProposalComputerRegistry.java +++ b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/text/java/CompletionProposalComputerRegistry.java @@ -24,6 +24,7 @@ import java.util.List; import java.util.Map; import java.util.Set; import java.util.StringTokenizer; +import java.util.stream.Stream; import org.eclipse.swt.SWT; import org.eclipse.swt.events.SelectionAdapter; @@ -526,4 +527,8 @@ public final class CompletionProposalComputerRegistry { public boolean computingCompletionRequiresUIThread() { return fDescriptors.stream().anyMatch(CompletionProposalComputerDescriptor::requiresUIThread); } + + public Stream<String> getComputersRequiringUIThreadNames() { + return fDescriptors.stream().filter(CompletionProposalComputerDescriptor::requiresUIThread).map(CompletionProposalComputerDescriptor::getName); + } } diff --git a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/text/java/ContentAssistProcessor.java b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/text/java/ContentAssistProcessor.java index c07448d1ae..7199d2f09f 100644 --- a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/text/java/ContentAssistProcessor.java +++ b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/text/java/ContentAssistProcessor.java @@ -32,6 +32,7 @@ import org.eclipse.swt.layout.GridLayout; import org.eclipse.swt.widgets.Button; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Link; import org.eclipse.swt.widgets.Shell; @@ -110,8 +111,9 @@ public class ContentAssistProcessor implements IContentAssistProcessor { */ @Override public void assistSessionStarted(ContentAssistEvent event) { - if (event.processor != ContentAssistProcessor.this) + if (event.processor != ContentAssistProcessor.this) { return; + } fIterationGesture= getIterationGesture(); KeySequence binding= getIterationBinding(); @@ -145,7 +147,7 @@ public class ContentAssistProcessor implements IContentAssistProcessor { /** * Returns the categories that need to be notified when a session starts and ends. - * + * * @return the current categories * @since 3.8.1 */ @@ -155,14 +157,16 @@ public class ContentAssistProcessor implements IContentAssistProcessor { // Currently enabled categories for this session if (fCategoryIteration != null) { Iterator<List<CompletionProposalCategory>> it= fCategoryIteration.iterator(); - while (it.hasNext()) + while (it.hasNext()) { currentCategories.addAll(it.next()); + } } // Backwards compatibility: notify all categories which have no enablement expression for (CompletionProposalCategory cat : fCategories) { - if (cat.getEnablementExpression() == null) + if (cat.getEnablementExpression() == null) { currentCategories.add(cat); + } } return currentCategories; @@ -173,8 +177,9 @@ public class ContentAssistProcessor implements IContentAssistProcessor { */ @Override public void assistSessionEnded(ContentAssistEvent event) { - if (event.processor != ContentAssistProcessor.this) + if (event.processor != ContentAssistProcessor.this) { return; + } for (CompletionProposalCategory cat : getCategoriesToNotify()) { cat.sessionEnded(); @@ -222,14 +227,7 @@ public class ContentAssistProcessor implements IContentAssistProcessor { */ private static final String PREF_WARN_ABOUT_EMPTY_ASSIST_CATEGORY= "EmptyDefaultAssistCategory"; //$NON-NLS-1$ - private static final Comparator<CompletionProposalCategory> ORDER_COMPARATOR= new Comparator<CompletionProposalCategory>() { - - @Override - public int compare(CompletionProposalCategory d1, CompletionProposalCategory d2) { - return d1.getSortOrder() - d2.getSortOrder(); - } - - }; + private static final Comparator<CompletionProposalCategory> ORDER_COMPARATOR= (d1, d2) -> d1.getSortOrder() - d2.getSortOrder(); private final List<CompletionProposalCategory> fCategories; private final String fPartition; @@ -256,7 +254,7 @@ public class ContentAssistProcessor implements IContentAssistProcessor { * Flag indicating whether any completion engine associated with this processor requests * resorting of its proposals after filtering is triggered. Filtering is, e.g., triggered when a * user continues typing with an open completion window. - * + * * @since 3.8 */ private boolean fNeedsSortingAfterFiltering; @@ -292,10 +290,11 @@ public class ContentAssistProcessor implements IContentAssistProcessor { long collect= JavaPlugin.DEBUG_RESULT_COLLECTOR ? System.currentTimeMillis() : 0; monitor.subTask(JavaTextMessages.ContentAssistProcessor_sorting_proposals); - if (fNeedsSortingAfterFiltering) + if (fNeedsSortingAfterFiltering) { setContentAssistSorter(); - else + } else { proposals= sortProposals(proposals, monitor, context); + } fNumberOfComputedResults= proposals.size(); long filter= JavaPlugin.DEBUG_RESULT_COLLECTOR ? System.currentTimeMillis() : 0; @@ -334,11 +333,13 @@ public class ContentAssistProcessor implements IContentAssistProcessor { List<ICompletionProposal> computed= cat.computeCompletionProposals(context, fPartition, new SubProgressMonitor(monitor, 1)); proposals.addAll(computed); needsSortingAfterFiltering= needsSortingAfterFiltering || (cat.isSortingAfterFilteringNeeded() && !computed.isEmpty()); - if (fErrorMessage == null) + if (fErrorMessage == null) { fErrorMessage= cat.getErrorMessage(); + } } - if (fNeedsSortingAfterFiltering && !needsSortingAfterFiltering) + if (fNeedsSortingAfterFiltering && !needsSortingAfterFiltering) { fAssistant.setSorter(null); + } fNeedsSortingAfterFiltering= needsSortingAfterFiltering; return proposals; } @@ -388,8 +389,9 @@ public class ContentAssistProcessor implements IContentAssistProcessor { for (CompletionProposalCategory cat : providers) { List<IContextInformation> computed= cat.computeContextInformation(context, fPartition, new SubProgressMonitor(monitor, 1)); proposals.addAll(computed); - if (fErrorMessage == null) + if (fErrorMessage == null) { fErrorMessage= cat.getErrorMessage(); + } } return proposals; @@ -442,10 +444,12 @@ public class ContentAssistProcessor implements IContentAssistProcessor { */ @Override public String getErrorMessage() { - if (fErrorMessage != null) + if (fErrorMessage != null) { return fErrorMessage; - if (fNumberOfComputedResults > 0) + } + if (fNumberOfComputedResults > 0) { return null; + } return JavaUIMessages.JavaEditor_codeassist_noCompletions; } @@ -483,11 +487,17 @@ public class ContentAssistProcessor implements IContentAssistProcessor { } private List<CompletionProposalCategory> getCategories() { - if (fCategoryIteration == null) + if (fCategoryIteration == null) { return fCategories; + } int iteration= fRepetition % fCategoryIteration.size(); - fAssistant.setStatusMessage(createIterationMessage()); + String iterationMessage = createIterationMessage(); + if (Display.getCurrent() != null) { + fAssistant.setStatusMessage(iterationMessage); + } else { + Display.getDefault().asyncExec(() -> fAssistant.setStatusMessage(iterationMessage)); + } fAssistant.setEmptyMessage(createEmptyMessage()); fRepetition++; @@ -515,9 +525,10 @@ public class ContentAssistProcessor implements IContentAssistProcessor { List<CompletionProposalCategory> included= getDefaultCategoriesUnchecked(); if (fComputerRegistry.hasUninstalledComputers(fPartition, included)) { - if (informUserAboutEmptyDefaultCategory()) + if (informUserAboutEmptyDefaultCategory()) { // preferences were restored - recompute the default categories included= getDefaultCategoriesUnchecked(); + } fComputerRegistry.resetUnistalledComputers(); } @@ -527,15 +538,16 @@ public class ContentAssistProcessor implements IContentAssistProcessor { private List<CompletionProposalCategory> getDefaultCategoriesUnchecked() { List<CompletionProposalCategory> included= new ArrayList<>(); for (CompletionProposalCategory category : fCategories) { - if (checkDefaultEnablement(category)) + if (checkDefaultEnablement(category)) { included.add(category); + } } return included; } /** * Determine whether the category is enabled by default. - * + * * @param category the category to check * @return <code>true</code> if this category is enabled by default, <code>false</code> * otherwise @@ -620,8 +632,9 @@ public class ContentAssistProcessor implements IContentAssistProcessor { store.setToDefault(PreferenceConstants.CODEASSIST_CATEGORY_ORDER); store.setToDefault(PreferenceConstants.CODEASSIST_EXCLUDED_CATEGORIES); } - if (settingsId == returnValue) + if (settingsId == returnValue) { PreferencesUtil.createPreferenceDialogOn(shell, "org.eclipse.jdt.ui.preferences.CodeAssistPreferenceAdvanced", null, null).open(); //$NON-NLS-1$ + } fComputerRegistry.reload(); return true; } @@ -632,16 +645,17 @@ public class ContentAssistProcessor implements IContentAssistProcessor { private List<CompletionProposalCategory> getSeparateCategories() { ArrayList<CompletionProposalCategory> sorted= new ArrayList<>(); for (CompletionProposalCategory category : fCategories) { - if (checkSeparateEnablement(category)) + if (checkSeparateEnablement(category)) { sorted.add(category); + } } Collections.sort(sorted, ORDER_COMPARATOR); return sorted; } - + /** * Determine whether the category is enabled for separate use. - * + * * @param category the category to check * @return <code>true</code> if this category is enabled for separate use, <code>false</code> * otherwise @@ -661,8 +675,9 @@ public class ContentAssistProcessor implements IContentAssistProcessor { private String getCategoryLabel(int repetition) { int iteration= repetition % fCategoryIteration.size(); - if (iteration == 0) + if (iteration == 0) { return JavaTextMessages.ContentAssistProcessor_defaultProposalCategory; + } return toString(fCategoryIteration.get(iteration).get(0)); } @@ -680,14 +695,15 @@ public class ContentAssistProcessor implements IContentAssistProcessor { private KeySequence getIterationBinding() { final IBindingService bindingSvc= PlatformUI.getWorkbench().getAdapter(IBindingService.class); TriggerSequence binding= bindingSvc.getBestActiveBindingFor(ITextEditorActionDefinitionIds.CONTENT_ASSIST_PROPOSALS); - if (binding instanceof KeySequence) + if (binding instanceof KeySequence) { return (KeySequence) binding; + } return null; } /** * Sets the current proposal sorter into the content assistant. - * + * * @since 3.8 * @see ProposalSorterRegistry#getCurrentSorter() the sorter used if <code>true</code> */ diff --git a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/ui/PreferenceConstants.java b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/ui/PreferenceConstants.java index c786b78460..5b0857b5a3 100644 --- a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/ui/PreferenceConstants.java +++ b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/ui/PreferenceConstants.java @@ -2956,6 +2956,18 @@ public class PreferenceConstants { public final static String FORMATTER_COMMENT_FORMATHTML= "comment_format_html"; //$NON-NLS-1$ /** + * A named preference that controls whether completion processors should be called from the non-UI thread, + * when they declare ability to work from non-UI Thread. + * <p>Completion processors can declare whether they + * require UI Thread or not in their extension description, see {@link org.eclipse.jdt.internal.ui.text.java.CompletionProposalComputerDescriptor#requiresUIThread()}.</p> + * <p>Value is of type <code>Boolean</code></p> + * + * @since 3.21 + */ + public static final String CODEASSIST_NONUITHREAD_COMPUTATION= "content_assist_noUIThread_computation"; //$NON-NLS-1$ + + + /** * A named preference that controls if the Java code assist gets auto activated. * <p> * Value is of type <code>Boolean</code>. @@ -4087,6 +4099,7 @@ public class PreferenceConstants { store.setDefault(PreferenceConstants.CODEASSIST_AUTOACTIVATION_DELAY, 0); store.setDefault(PreferenceConstants.CODEASSIST_AUTOINSERT, true); store.setDefault(PreferenceConstants.CODEASSIST_DISABLE_COMPLETION_PROPOSAL_TRIGGER_CHARS, false); + store.setDefault(PreferenceConstants.CODEASSIST_NONUITHREAD_COMPUTATION, false); store.setDefault(PreferenceConstants.PREF_MIN_CHAIN_LENGTH, 2); store.setDefault(PreferenceConstants.PREF_MAX_CHAIN_LENGTH, 4); store.setDefault(PreferenceConstants.PREF_MAX_CHAINS, 20); diff --git a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/ui/text/JavaSourceViewerConfiguration.java b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/ui/text/JavaSourceViewerConfiguration.java index 20ef33bec7..a6154dd513 100644 --- a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/ui/text/JavaSourceViewerConfiguration.java +++ b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/ui/text/JavaSourceViewerConfiguration.java @@ -86,6 +86,7 @@ import org.eclipse.jdt.internal.ui.text.JavaReconciler; import org.eclipse.jdt.internal.ui.text.PreferencesAdapter; import org.eclipse.jdt.internal.ui.text.SingleTokenJavaScanner; import org.eclipse.jdt.internal.ui.text.correction.JavaCorrectionAssistant; +import org.eclipse.jdt.internal.ui.text.java.CompletionProposalComputerRegistry; import org.eclipse.jdt.internal.ui.text.java.ContentAssistProcessor; import org.eclipse.jdt.internal.ui.text.java.JavaAutoIndentStrategy; import org.eclipse.jdt.internal.ui.text.java.JavaCodeScanner; @@ -434,8 +435,7 @@ public class JavaSourceViewerConfiguration extends TextSourceViewerConfiguration public IContentAssistant getContentAssistant(ISourceViewer sourceViewer) { if (getEditor() != null) { - - ContentAssistant assistant= new ContentAssistant(); + ContentAssistant assistant= new ContentAssistant(fPreferenceStore.getBoolean(PreferenceConstants.CODEASSIST_NONUITHREAD_COMPUTATION) && !CompletionProposalComputerRegistry.getDefault().computingCompletionRequiresUIThread()); assistant.setDocumentPartitioning(getConfiguredDocumentPartitioning(sourceViewer)); assistant.setRestoreCompletionProposalSize(getSettings("completion_proposal_size")); //$NON-NLS-1$ |