Bug 541714: [SourceEditor] Revise InfoHover / editor hovers
- Let run InfoHover.getHoverInfo always in non-UI thread
supporting also longer running tasks (progress monitor)
- Add use of nullable annotations
Change-Id: Ic6016bc34ca1930c5a10016599153374bd06189c
diff --git a/ltk/org.eclipse.statet.ltk.ui/META-INF/MANIFEST.MF b/ltk/org.eclipse.statet.ltk.ui/META-INF/MANIFEST.MF
index 3a1bf98..b52636c 100644
--- a/ltk/org.eclipse.statet.ltk.ui/META-INF/MANIFEST.MF
+++ b/ltk/org.eclipse.statet.ltk.ui/META-INF/MANIFEST.MF
@@ -35,6 +35,7 @@
org.eclipse.statet.ecommons.resources.core;version="4.0.0",
org.eclipse.statet.jcommons.collections;version="4.0.0",
org.eclipse.statet.jcommons.lang;version="4.0.0",
+ org.eclipse.statet.jcommons.status;version="4.0.0",
org.eclipse.statet.jcommons.text.core;version="4.0.0"
Export-Package: org.eclipse.statet.ecommons.templates,
org.eclipse.statet.ecommons.text.ui,
diff --git a/ltk/org.eclipse.statet.ltk.ui/src/org/eclipse/statet/ltk/ui/sourceediting/EditorInformationProvider.java b/ltk/org.eclipse.statet.ltk.ui/src/org/eclipse/statet/ltk/ui/sourceediting/EditorInformationProvider.java
index ccc460f..0e6ac9d 100644
--- a/ltk/org.eclipse.statet.ltk.ui/src/org/eclipse/statet/ltk/ui/sourceediting/EditorInformationProvider.java
+++ b/ltk/org.eclipse.statet.ltk.ui/src/org/eclipse/statet/ltk/ui/sourceediting/EditorInformationProvider.java
@@ -14,10 +14,14 @@
package org.eclipse.statet.ltk.ui.sourceediting;
+import java.lang.reflect.InvocationTargetException;
+import java.util.concurrent.atomic.AtomicReference;
+
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.Status;
-import org.eclipse.core.runtime.SubMonitor;
+import org.eclipse.jface.operation.IRunnableWithProgress;
import org.eclipse.jface.text.IInformationControlCreator;
import org.eclipse.jface.text.IRegion;
import org.eclipse.jface.text.ITextViewer;
@@ -25,27 +29,39 @@
import org.eclipse.jface.text.information.IInformationProvider;
import org.eclipse.jface.text.information.IInformationProviderExtension;
import org.eclipse.jface.text.information.IInformationProviderExtension2;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.ui.progress.IProgressService;
import org.eclipse.ui.statushandlers.StatusManager;
+import org.eclipse.statet.jcommons.collections.ImList;
+import org.eclipse.statet.jcommons.lang.NonNullByDefault;
+import org.eclipse.statet.jcommons.lang.Nullable;
+import org.eclipse.statet.jcommons.status.InterruptNullProgressMonitor;
+import org.eclipse.statet.jcommons.status.ProgressMonitor;
+import org.eclipse.statet.jcommons.status.StatusException;
+
+import org.eclipse.statet.ecommons.runtime.core.util.StatusUtils;
import org.eclipse.statet.ecommons.text.core.util.TextUtils;
import org.eclipse.statet.internal.ltk.ui.LTKUIPlugin;
import org.eclipse.statet.ltk.ui.sourceediting.assist.AssistInvocationContext;
-import org.eclipse.statet.ltk.ui.sourceediting.assist.IInfoHover;
+import org.eclipse.statet.ltk.ui.sourceediting.assist.InfoHover;
+@NonNullByDefault
public abstract class EditorInformationProvider
implements IInformationProvider, IInformationProviderExtension, IInformationProviderExtension2 {
private final ISourceEditor editor;
- private final IInfoHover[] hovers;
+ private final ImList<? extends InfoHover> hovers;
- private IInfoHover bestHover;
+ private volatile @Nullable InfoHover bestHover;
- public EditorInformationProvider(final ISourceEditor editor, final IInfoHover[] hovers) {
+ public EditorInformationProvider(final ISourceEditor editor,
+ final ImList<? extends InfoHover> hovers) {
this.editor= editor;
this.hovers= hovers;
}
@@ -56,14 +72,13 @@
}
@Override
- public String getInformation(final ITextViewer textViewer, final IRegion region) {
+ public @Nullable String getInformation(final ITextViewer textViewer, final IRegion region) {
return null;
}
@Override
- public Object getInformation2(final ITextViewer textViewer, final IRegion region) {
+ public @Nullable Object getInformation2(final ITextViewer textViewer, final IRegion region) {
this.bestHover= null;
- final SubMonitor m= SubMonitor.convert(null);
try {
final String contentType= (region instanceof TypedRegion) ?
((TypedRegion) region).getType() :
@@ -71,14 +86,42 @@
this.editor.getDocumentContentInfo(), region.getOffset(),
region.getLength() == 0 );
- final AssistInvocationContext context= createContext(region, contentType, m);
+ final AssistInvocationContext context= createContext(region, contentType,
+ new NullProgressMonitor() );
if (context != null) {
- for (int i= 0; i < this.hovers.length; i++) {
- final Object info= this.hovers[i].getHoverInfo(context);
- if (info != null) {
- this.bestHover= this.hovers[i];
- return info;
+ try {
+ final AtomicReference<Object> info= new AtomicReference<>();
+ if (Display.getCurrent() != null) {
+ try {
+ final IProgressService progressService= this.editor.getServiceLocator().getService(IProgressService.class);
+ progressService.run(true, true, new IRunnableWithProgress() {
+ @Override
+ public void run(final IProgressMonitor monitor)
+ throws InvocationTargetException, InterruptedException {
+ final ProgressMonitor m= StatusUtils.convert(monitor);
+ try {
+ getInfo0(context, info, m);
+ }
+ catch (final StatusException e) {
+ throw new InvocationTargetException(e);
+ }
+ }
+ });
+ }
+ catch (final InvocationTargetException e) {
+ throw (StatusException) e.getCause();
+ }
}
+ else {
+ getInfo0(context, info, new InterruptNullProgressMonitor());
+ }
+ return info.get();
+ }
+ catch (final InterruptedException e) {
+ return null;
+ }
+ catch (final StatusException e) {
+ return null;
}
}
}
@@ -89,15 +132,42 @@
return null;
}
+ protected abstract @Nullable AssistInvocationContext createContext(IRegion region, String contentType,
+ IProgressMonitor monitor );
+
+ private void getInfo0(final AssistInvocationContext context,
+ final AtomicReference<Object> infoValue,
+ final ProgressMonitor m) throws StatusException {
+ for (int i= 0; i < this.hovers.size(); i++) {
+ m.setWorkRemaining(this.hovers.size() - i);
+ if (m.isCanceled()) {
+ return;
+ }
+ try {
+ final InfoHover hover= this.hovers.get(i);
+ final Object info= hover.getHoverInfo(context, m.newSubMonitor(1));
+ if (info != null) {
+ this.bestHover= hover;
+ infoValue.set(info);
+ return;
+ }
+ }
+ catch (final StatusException e) {
+ if (e.getStatus().getSeverity() == Status.CANCEL) {
+ return;
+ }
+ }
+ }
+ }
+
+
@Override
- public IInformationControlCreator getInformationPresenterControlCreator() {
- if (this.bestHover != null) {
- return this.bestHover.getHoverControlCreator();
+ public @Nullable IInformationControlCreator getInformationPresenterControlCreator() {
+ final InfoHover hover= this.bestHover;
+ if (hover != null) {
+ return hover.getHoverControlCreator();
}
return null;
}
- protected abstract AssistInvocationContext createContext(IRegion region, String contentType,
- IProgressMonitor monitor );
-
}
diff --git a/ltk/org.eclipse.statet.ltk.ui/src/org/eclipse/statet/ltk/ui/sourceediting/EditorTextInfoHoverProxy.java b/ltk/org.eclipse.statet.ltk.ui/src/org/eclipse/statet/ltk/ui/sourceediting/EditorTextInfoHoverProxy.java
index 69c9813..fbf5a91 100644
--- a/ltk/org.eclipse.statet.ltk.ui/src/org/eclipse/statet/ltk/ui/sourceediting/EditorTextInfoHoverProxy.java
+++ b/ltk/org.eclipse.statet.ltk.ui/src/org/eclipse/statet/ltk/ui/sourceediting/EditorTextInfoHoverProxy.java
@@ -14,6 +14,10 @@
package org.eclipse.statet.ltk.ui.sourceediting;
+import static org.eclipse.statet.jcommons.lang.ObjectUtils.nonNullAssert;
+
+import java.util.concurrent.atomic.AtomicReference;
+
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.NullProgressMonitor;
@@ -28,28 +32,35 @@
import org.eclipse.osgi.util.NLS;
import org.eclipse.ui.statushandlers.StatusManager;
+import org.eclipse.statet.jcommons.collections.ImCollections;
+import org.eclipse.statet.jcommons.lang.NonNullByDefault;
+import org.eclipse.statet.jcommons.lang.Nullable;
+import org.eclipse.statet.jcommons.status.InterruptNullProgressMonitor;
+import org.eclipse.statet.jcommons.status.ProgressMonitor;
+import org.eclipse.statet.jcommons.status.StatusException;
+
import org.eclipse.statet.ecommons.text.core.util.TextUtils;
import org.eclipse.statet.internal.ltk.ui.LTKUIPlugin;
import org.eclipse.statet.ltk.ui.sourceediting.assist.AssistInvocationContext;
import org.eclipse.statet.ltk.ui.sourceediting.assist.CombinedHover;
-import org.eclipse.statet.ltk.ui.sourceediting.assist.IInfoHover;
+import org.eclipse.statet.ltk.ui.sourceediting.assist.InfoHover;
import org.eclipse.statet.ltk.ui.sourceediting.assist.InfoHoverDescriptor;
import org.eclipse.statet.ltk.ui.sourceediting.assist.InfoHoverRegistry.EffectiveHovers;
/**
- * Wraps an LTK {@link IInfoHover} to an editor text hover.
+ * Wraps an LTK {@link InfoHover} to an editor text hover.
*/
+@NonNullByDefault
public abstract class EditorTextInfoHoverProxy implements ITextHover, ITextHoverExtension, ITextHoverExtension2 {
private final InfoHoverDescriptor descriptor;
-
- private IInfoHover hover;
-
private final SourceEditorViewerConfiguration sourceEditorConfig;
+ private volatile @Nullable InfoHover hover;
+
public EditorTextInfoHoverProxy(final InfoHoverDescriptor descriptor,
final SourceEditorViewerConfiguration config) {
@@ -62,34 +73,44 @@
return this.sourceEditorConfig.getSourceEditor();
}
+ @SuppressWarnings("null")
protected boolean ensureHover() {
- if (this.hover == null) {
- this.hover= this.descriptor.createHover();
- if (this.hover instanceof CombinedHover) {
- final EffectiveHovers effectiveHovers= this.sourceEditorConfig.getConfiguredInfoHovers();
- if (effectiveHovers != null) {
- ((CombinedHover) this.hover).setHovers(effectiveHovers.getDescriptorsForCombined());
- }
- else {
- this.hover= null;
+ InfoHover hover= this.hover;
+ if (hover == null) {
+ synchronized (this) {
+ hover= this.hover;
+ if (hover == null) {
+ hover= createHover();
+ this.hover= hover;
}
}
}
- return (this.hover != null);
+ return (hover != null);
+ }
+
+ private @Nullable InfoHover createHover() {
+ final InfoHover hover= this.descriptor.createHover();
+ if (hover instanceof CombinedHover) {
+ final EffectiveHovers effectiveHovers= this.sourceEditorConfig.getConfiguredInfoHovers();
+ ((CombinedHover) hover).setHovers((effectiveHovers != null) ?
+ effectiveHovers.getDescriptorsForCombined() :
+ ImCollections.emptyList() );
+ }
+ return hover;
}
@Override
- public IRegion getHoverRegion(final ITextViewer textViewer, final int offset) {
+ public @Nullable IRegion getHoverRegion(final ITextViewer textViewer, final int offset) {
return null;
}
@Override
- public String getHoverInfo(final ITextViewer textViewer, final IRegion hoverRegion) {
+ public @Nullable String getHoverInfo(final ITextViewer textViewer, final IRegion hoverRegion) {
return null;
}
@Override
- public Object getHoverInfo2(final ITextViewer textViewer, final IRegion hoverRegion) {
+ public @Nullable Object getHoverInfo2(final ITextViewer textViewer, final IRegion hoverRegion) {
final ISourceEditor editor= getEditor();
if (editor != null && ensureHover()) {
try {
@@ -102,7 +123,14 @@
final AssistInvocationContext context= createContext(hoverRegion, contentType,
new NullProgressMonitor() );
if (context != null) {
- return this.hover.getHoverInfo(context);
+ try {
+ final AtomicReference<Object> info= new AtomicReference<>();
+ getInfo0(context, info, new InterruptNullProgressMonitor());
+ return info.get();
+ }
+ catch (final StatusException e) {
+ return null;
+ }
}
}
catch (final Exception e) {
@@ -114,13 +142,36 @@
return null;
}
- protected abstract AssistInvocationContext createContext(IRegion region, String contentType,
+ protected abstract @Nullable AssistInvocationContext createContext(IRegion region, String contentType,
IProgressMonitor monitor );
+ private void getInfo0(final AssistInvocationContext context,
+ final AtomicReference<Object> infoValue,
+ final ProgressMonitor m) throws StatusException {
+ try {
+ if (m.isCanceled()) {
+ return;
+ }
+
+ final InfoHover hover= nonNullAssert(this.hover);
+ final Object info= hover.getHoverInfo(context, m);
+ if (info != null) {
+ infoValue.set(info);
+ }
+ }
+ catch (final StatusException e) {
+ if (e.getStatus().getSeverity() == Status.CANCEL) {
+ return;
+ }
+ }
+ }
+
+
@Override
- public IInformationControlCreator getHoverControlCreator() {
- if (ensureHover()) {
- return this.hover.getHoverControlCreator();
+ public @Nullable IInformationControlCreator getHoverControlCreator() {
+ final InfoHover hover= this.hover;
+ if (hover != null) {
+ return hover.getHoverControlCreator();
}
return null;
}
diff --git a/ltk/org.eclipse.statet.ltk.ui/src/org/eclipse/statet/ltk/ui/sourceediting/assist/CombinedHover.java b/ltk/org.eclipse.statet.ltk.ui/src/org/eclipse/statet/ltk/ui/sourceediting/assist/CombinedHover.java
index 2b2fa73..6fe41be 100644
--- a/ltk/org.eclipse.statet.ltk.ui/src/org/eclipse/statet/ltk/ui/sourceediting/assist/CombinedHover.java
+++ b/ltk/org.eclipse.statet.ltk.ui/src/org/eclipse/statet/ltk/ui/sourceediting/assist/CombinedHover.java
@@ -19,46 +19,69 @@
import org.eclipse.jface.text.IInformationControlCreator;
+import org.eclipse.statet.jcommons.lang.NonNullByDefault;
+import org.eclipse.statet.jcommons.lang.Nullable;
+import org.eclipse.statet.jcommons.status.ProgressMonitor;
+import org.eclipse.statet.jcommons.status.StatusException;
+import org.eclipse.statet.jcommons.status.Statuses;
+
/**
* Special information hover showing the first match of the specified hover types.
*/
-public class CombinedHover implements IInfoHover {
+@NonNullByDefault
+public class CombinedHover implements InfoHover {
private List<InfoHoverDescriptor> descriptors;
- private List<IInfoHover> instantiatedHovers;
+ private List<InfoHover> instantiatedHovers;
- private IInfoHover bestHover;
+ private volatile @Nullable InfoHover bestHover;
public CombinedHover() {
}
- public void setHovers(final List<InfoHoverDescriptor> descriptors) {
+ public synchronized void setHovers(final List<InfoHoverDescriptor> descriptors) {
this.descriptors= descriptors;
this.instantiatedHovers= new ArrayList<>(descriptors.size());
}
@Override
- public Object getHoverInfo(final AssistInvocationContext context) {
+ public @Nullable Object getHoverInfo(final AssistInvocationContext context,
+ final ProgressMonitor m) throws StatusException {
this.bestHover= null;
- if (this.descriptors == null) {
+ final List<InfoHoverDescriptor> descriptors;
+ final List<InfoHover> instantiatedHovers;
+ synchronized (this) {
+ descriptors= this.descriptors;
+ instantiatedHovers= this.instantiatedHovers;
+ }
+ if (descriptors == null) {
return null;
}
- for (int i= 0; i < this.descriptors.size(); i++) {
- if (i == this.instantiatedHovers.size()) {
- this.instantiatedHovers.add(this.descriptors.get(i).createHover());
+ for (int i= 0; i < descriptors.size(); i++) {
+ m.setWorkRemaining(descriptors.size() - i);
+ if (m.isCanceled()) {
+ throw new StatusException(Statuses.CANCEL_STATUS);
}
- final IInfoHover hover= this.instantiatedHovers.get(i);
+
+ if (i == instantiatedHovers.size()) {
+ instantiatedHovers.add(descriptors.get(i).createHover());
+ }
+ final InfoHover hover= instantiatedHovers.get(i);
if (hover != null) {
- final Object info= hover.getHoverInfo(context);
- if (info != null) {
- this.bestHover= hover;
- return info;
+ try {
+ final Object info= hover.getHoverInfo(context, m.newSubMonitor(1));
+ if (info != null) {
+ this.bestHover= hover;
+ return info;
+ }
+ }
+ catch (final StatusException e) {
}
}
}
@@ -66,9 +89,10 @@
}
@Override
- public IInformationControlCreator getHoverControlCreator() {
- if (this.bestHover != null) {
- return this.bestHover.getHoverControlCreator();
+ public @Nullable IInformationControlCreator getHoverControlCreator() {
+ final InfoHover hover= this.bestHover;
+ if (hover != null) {
+ return hover.getHoverControlCreator();
}
return null;
}
diff --git a/ltk/org.eclipse.statet.ltk.ui/src/org/eclipse/statet/ltk/ui/sourceediting/assist/IInfoHover.java b/ltk/org.eclipse.statet.ltk.ui/src/org/eclipse/statet/ltk/ui/sourceediting/assist/InfoHover.java
similarity index 76%
rename from ltk/org.eclipse.statet.ltk.ui/src/org/eclipse/statet/ltk/ui/sourceediting/assist/IInfoHover.java
rename to ltk/org.eclipse.statet.ltk.ui/src/org/eclipse/statet/ltk/ui/sourceediting/assist/InfoHover.java
index 08c14b2..de25dbd 100644
--- a/ltk/org.eclipse.statet.ltk.ui/src/org/eclipse/statet/ltk/ui/sourceediting/assist/IInfoHover.java
+++ b/ltk/org.eclipse.statet.ltk.ui/src/org/eclipse/statet/ltk/ui/sourceediting/assist/InfoHover.java
@@ -18,6 +18,11 @@
import org.eclipse.jface.text.ITextHoverExtension;
import org.eclipse.jface.text.ITextHoverExtension2;
+import org.eclipse.statet.jcommons.lang.NonNullByDefault;
+import org.eclipse.statet.jcommons.lang.Nullable;
+import org.eclipse.statet.jcommons.status.ProgressMonitor;
+import org.eclipse.statet.jcommons.status.StatusException;
+
import org.eclipse.statet.ecommons.ui.util.InformationDispatchHandler;
@@ -27,7 +32,8 @@
* Intend to be implemented by client and registered to the extension point
* <code>org.eclipse.statet.ltk.advancedInfoHover</code>.
*/
-public interface IInfoHover {
+@NonNullByDefault
+public interface InfoHover {
int MODE_TOOLTIP= InformationDispatchHandler.MODE_TOOLTIP;
@@ -37,13 +43,15 @@
/**
+ * @param m optional progress monitor
* @see ITextHoverExtension2#getHoverInfo2(org.eclipse.jface.text.ITextViewer, org.eclipse.jface.text.IRegion)
*/
- Object getHoverInfo(AssistInvocationContext context);
+ @Nullable Object getHoverInfo(AssistInvocationContext context,
+ ProgressMonitor m) throws StatusException;
/**
* @see ITextHoverExtension#getHoverControlCreator()
*/
- IInformationControlCreator getHoverControlCreator();
+ @Nullable IInformationControlCreator getHoverControlCreator();
}
diff --git a/ltk/org.eclipse.statet.ltk.ui/src/org/eclipse/statet/ltk/ui/sourceediting/assist/InfoHoverDescriptor.java b/ltk/org.eclipse.statet.ltk.ui/src/org/eclipse/statet/ltk/ui/sourceediting/assist/InfoHoverDescriptor.java
index 6dc6249..1ced077 100644
--- a/ltk/org.eclipse.statet.ltk.ui/src/org/eclipse/statet/ltk/ui/sourceediting/assist/InfoHoverDescriptor.java
+++ b/ltk/org.eclipse.statet.ltk.ui/src/org/eclipse/statet/ltk/ui/sourceediting/assist/InfoHoverDescriptor.java
@@ -20,6 +20,9 @@
import org.eclipse.core.runtime.Status;
import org.eclipse.ui.statushandlers.StatusManager;
+import org.eclipse.statet.jcommons.lang.NonNullByDefault;
+import org.eclipse.statet.jcommons.lang.Nullable;
+
import org.eclipse.statet.internal.ltk.ui.AdvancedExtensionsInternal;
import org.eclipse.statet.internal.ltk.ui.LTKUIPlugin;
@@ -27,6 +30,7 @@
/**
* Describes an configured information hover type.
*/
+@NonNullByDefault
public class InfoHoverDescriptor {
@@ -69,16 +73,16 @@
*
* @return the text hover
*/
- public IInfoHover createHover() {
+ public @Nullable InfoHover createHover() {
try {
- return (IInfoHover) this.configurationElement.createExecutableExtension(
+ return (InfoHover) this.configurationElement.createExecutableExtension(
AdvancedExtensionsInternal.CONFIG_CLASS_ATTRIBUTE_NAME);
}
catch (final CoreException e) {
StatusManager.getManager().handle(new Status(IStatus.ERROR, LTKUIPlugin.BUNDLE_ID, 0,
"Could not create text hover '" + this.name + "'.", e ));
+ return null;
}
- return null;
}
/**
@@ -106,7 +110,7 @@
}
@Override
- public boolean equals(final Object obj) {
+ public boolean equals(final @Nullable Object obj) {
return (obj != null && getClass().equals(obj.getClass())
&& this.id.equals(((InfoHoverDescriptor) obj).getId()) );
}