diff options
19 files changed, 553 insertions, 143 deletions
diff --git a/org.eclipse.jface.text/META-INF/MANIFEST.MF b/org.eclipse.jface.text/META-INF/MANIFEST.MF index 79bfc1dc281..dff65ab29c5 100644 --- a/org.eclipse.jface.text/META-INF/MANIFEST.MF +++ b/org.eclipse.jface.text/META-INF/MANIFEST.MF @@ -2,7 +2,7 @@ Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-Name: %pluginName Bundle-SymbolicName: org.eclipse.jface.text -Bundle-Version: 3.12.100.qualifier +Bundle-Version: 3.13.0.qualifier Bundle-Vendor: %providerName Bundle-Localization: plugin Export-Package: diff --git a/org.eclipse.jface.text/pom.xml b/org.eclipse.jface.text/pom.xml index 67c700590b4..2ea44349300 100644 --- a/org.eclipse.jface.text/pom.xml +++ b/org.eclipse.jface.text/pom.xml @@ -18,6 +18,6 @@ </parent> <groupId>org.eclipse.jface</groupId> <artifactId>org.eclipse.jface.text</artifactId> - <version>3.12.100-SNAPSHOT</version> + <version>3.13.0-SNAPSHOT</version> <packaging>eclipse-plugin</packaging> </project> diff --git a/org.eclipse.jface.text/src/org/eclipse/jface/text/AbstractInformationControl.java b/org.eclipse.jface.text/src/org/eclipse/jface/text/AbstractInformationControl.java index 374ad6108f5..317084573a3 100644 --- a/org.eclipse.jface.text/src/org/eclipse/jface/text/AbstractInformationControl.java +++ b/org.eclipse.jface.text/src/org/eclipse/jface/text/AbstractInformationControl.java @@ -434,8 +434,9 @@ public abstract class AbstractInformationControl implements IInformationControl, * The shell of the popup window. * * @return the shell used for the popup window + * @since 3.13 */ - protected final Shell getShell() { + public final Shell getShell() { return fShell; } diff --git a/org.eclipse.ui.genericeditor.tests/plugin.xml b/org.eclipse.ui.genericeditor.tests/plugin.xml index d2c3f4580ae..c7ce8e9b8de 100644 --- a/org.eclipse.ui.genericeditor.tests/plugin.xml +++ b/org.eclipse.ui.genericeditor.tests/plugin.xml @@ -26,9 +26,13 @@ <extension point="org.eclipse.ui.genericeditor.hoverProviders"> <hoverProvider - class="org.eclipse.ui.genericeditor.tests.contributions.MagicHoverProvider" + class="org.eclipse.ui.genericeditor.tests.contributions.AlrightyHoverProvider" contentType="org.eclipse.ui.genericeditor.tests.content-type"> </hoverProvider> + <hoverProvider + class="org.eclipse.ui.genericeditor.tests.contributions.HelloHoverProvider" + contentType="org.eclipse.ui.genericeditor.tests.specialized-content-type-bartxt"> + </hoverProvider> </extension> <extension point="org.eclipse.ui.genericeditor.reconcilers"> @@ -36,11 +40,11 @@ class="org.eclipse.ui.genericeditor.tests.contributions.TheReconcilerFirst" contentType="org.eclipse.core.runtime.text"> </reconciler> - <reconciler - class="org.eclipse.ui.genericeditor.tests.contributions.TheReconcilerSecond" - contentType="org.eclipse.ui.genericeditor.tests.reconciled-content-type"> - </reconciler> - </extension> + <reconciler + class="org.eclipse.ui.genericeditor.tests.contributions.TheReconcilerSecond" + contentType="org.eclipse.ui.genericeditor.tests.specialized-content-type-bartxt"> + </reconciler> + </extension> <extension point="org.eclipse.ui.genericeditor.presentationReconcilers"> <presentationReconciler @@ -80,10 +84,10 @@ priority="normal"> </content-type> <content-type - base-type="org.eclipse.core.runtime.text" + base-type="org.eclipse.ui.genericeditor.tests.content-type" file-names="bar.txt" - id="org.eclipse.ui.genericeditor.tests.reconciled-content-type" - name="Generic Editor Reconciler Test content-type" + id="org.eclipse.ui.genericeditor.tests.specialized-content-type-bartxt" + name="Specialized Generic Editor content-type for 'bar.txt'" priority="normal"> </content-type> </extension> diff --git a/org.eclipse.ui.genericeditor.tests/src/org/eclipse/ui/genericeditor/tests/AbstratGenericEditorTest.java b/org.eclipse.ui.genericeditor.tests/src/org/eclipse/ui/genericeditor/tests/AbstratGenericEditorTest.java index 6d12a6f548a..2d31f0ad00e 100644 --- a/org.eclipse.ui.genericeditor.tests/src/org/eclipse/ui/genericeditor/tests/AbstratGenericEditorTest.java +++ b/org.eclipse.ui.genericeditor.tests/src/org/eclipse/ui/genericeditor/tests/AbstratGenericEditorTest.java @@ -17,6 +17,8 @@ import org.junit.Before; import org.eclipse.swt.widgets.Display; +import org.eclipse.core.runtime.NullProgressMonitor; + import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.ResourcesPlugin; @@ -47,17 +49,42 @@ public class AbstratGenericEditorTest { project = ResourcesPlugin.getWorkspace().getRoot().getProject(getClass().getName() + System.currentTimeMillis()); project.create(null); project.open(null); - file = project.getFile("foo.txt"); - file.create(new ByteArrayInputStream("bar 'bar'".getBytes()), true, null); - editor = (ExtensionBasedTextEditor) PlatformUI.getWorkbench().getActiveWorkbenchWindow() - .getActivePage().openEditor(new FileEditorInput(this.file), "org.eclipse.ui.genericeditor.GenericEditor"); + createAndOpenFile("foo.txt", "bar 'bar'"); } + + /** + * Creates a new file in the project, opens it, and associate that file with the test state + * @param name name of the file in the project + * @param contents content of the file + * @throws Exception ex + * @since 1.1 + */ + protected void createAndOpenFile(String name, String contents) throws Exception { + this.file = project.getFile(name); + this.file.create(new ByteArrayInputStream(contents.getBytes()), true, null); + this.editor = (ExtensionBasedTextEditor) PlatformUI.getWorkbench().getActiveWorkbenchWindow() + .getActivePage().openEditor(new FileEditorInput(this.file), "org.eclipse.ui.genericeditor.GenericEditor"); + } - @After - public void tearDown() throws Exception { + /** + * Closes editor and delete file. Keeps project open. + * @throws Exception ex + * @since 1.1 + */ + protected void cleanFileAndEditor() throws Exception { + if (editor != null) { + editor.close(false); + editor = null; + } if (file != null) { - file.delete(true, null); + file.delete(true, new NullProgressMonitor()); + file = null; } + } + + @After + public void tearDown() throws Exception { + cleanFileAndEditor(); if (project != null) { project.delete(true, null); } diff --git a/org.eclipse.ui.genericeditor.tests/src/org/eclipse/ui/genericeditor/tests/HoverTest.java b/org.eclipse.ui.genericeditor.tests/src/org/eclipse/ui/genericeditor/tests/HoverTest.java index a92fea66910..e234e340a9b 100644 --- a/org.eclipse.ui.genericeditor.tests/src/org/eclipse/ui/genericeditor/tests/HoverTest.java +++ b/org.eclipse.ui.genericeditor.tests/src/org/eclipse/ui/genericeditor/tests/HoverTest.java @@ -10,13 +10,14 @@ *******************************************************************************/ package org.eclipse.ui.genericeditor.tests; -import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import java.util.Collections; +import java.util.Map; import org.junit.Assume; import org.junit.Before; @@ -46,7 +47,8 @@ import org.eclipse.jface.text.ITextViewer; import org.eclipse.jface.text.TextViewer; import org.eclipse.jface.text.tests.util.DisplayHelper; -import org.eclipse.ui.genericeditor.tests.contributions.MagicHoverProvider; +import org.eclipse.ui.genericeditor.tests.contributions.AlrightyHoverProvider; +import org.eclipse.ui.genericeditor.tests.contributions.HelloHoverProvider; import org.eclipse.ui.genericeditor.tests.contributions.MarkerResolutionGenerator; import org.eclipse.ui.workbench.texteditor.tests.ScreenshotTest; @@ -67,9 +69,23 @@ public class HoverTest extends AbstratGenericEditorTest { } @Test - public void testHover() throws Exception { + public void testSingleHover() throws Exception { Shell shell = getHoverShell(triggerCompletionAndRetrieveInformationControlManager()); - assertNotNull(findControl(shell, StyledText.class, MagicHoverProvider.LABEL)); + assertNotNull(findControl(shell, StyledText.class, AlrightyHoverProvider.LABEL)); + assertNull(findControl(shell, StyledText.class, HelloHoverProvider.LABEL)); + } + + /** + * @throws Exception ex + * @since 1.1 + */ + @Test + public void testMultipleHover() throws Exception { + cleanFileAndEditor(); + createAndOpenFile("bar.txt", "Hi"); + Shell shell = getHoverShell(triggerCompletionAndRetrieveInformationControlManager()); + assertNotNull(findControl(shell, StyledText.class, AlrightyHoverProvider.LABEL)); + assertNotNull(findControl(shell, StyledText.class, HelloHoverProvider.LABEL)); } @Test @@ -85,10 +101,17 @@ public class HoverTest extends AbstratGenericEditorTest { marker.setAttribute(IMarker.MESSAGE, problemMessage); marker.setAttribute(MarkerResolutionGenerator.FIXME, true); AbstractInformationControlManager manager = triggerCompletionAndRetrieveInformationControlManager(); - assertEquals(Collections.singletonList(marker), getHoverData(manager)); + Object hoverData = getHoverData(manager); + assertTrue(hoverData instanceof Map); + assertTrue(((Map<?,?>)hoverData).containsValue(Collections.singletonList(marker))); + assertTrue(((Map<?,?>)hoverData).containsValue(AlrightyHoverProvider.LABEL)); + assertFalse(((Map<?,?>)hoverData).containsValue(HelloHoverProvider.LABEL)); // check dialog content Shell shell= getHoverShell(manager); assertNotNull(findControl(shell, Label.class, marker.getAttribute(IMarker.MESSAGE, "NONE"))); + assertNotNull(findControl(shell, StyledText.class, AlrightyHoverProvider.LABEL)); + assertNull(findControl(shell, StyledText.class, HelloHoverProvider.LABEL)); + // check quick-fix works Link link = findControl(shell, Link.class, MarkerResolutionGenerator.FIXME); assertNotNull(link); Event event = new Event(); @@ -126,6 +149,12 @@ public class HoverTest extends AbstratGenericEditorTest { fail(); } Shell shell = (Shell)new Accessor(control[0], AbstractInformationControl.class).get("fShell"); + new DisplayHelper() { + @Override + protected boolean condition() { + return shell.isVisible(); + } + }.waitForCondition(this.editor.getSite().getShell().getDisplay(), 2000); assertTrue(shell.isVisible()); return shell; } diff --git a/org.eclipse.ui.genericeditor.tests/src/org/eclipse/ui/genericeditor/tests/contributions/MagicHoverProvider.java b/org.eclipse.ui.genericeditor.tests/src/org/eclipse/ui/genericeditor/tests/contributions/AlrightyHoverProvider.java index 0bba76cf9d4..726036e6dc4 100644 --- a/org.eclipse.ui.genericeditor.tests/src/org/eclipse/ui/genericeditor/tests/contributions/MagicHoverProvider.java +++ b/org.eclipse.ui.genericeditor.tests/src/org/eclipse/ui/genericeditor/tests/contributions/AlrightyHoverProvider.java @@ -16,7 +16,7 @@ import org.eclipse.jface.text.ITextHoverExtension2; import org.eclipse.jface.text.ITextViewer; import org.eclipse.jface.text.Region; -public class MagicHoverProvider implements ITextHover,ITextHoverExtension2 { +public class AlrightyHoverProvider implements ITextHover,ITextHoverExtension2 { public static final String LABEL= "Alrighty!"; diff --git a/org.eclipse.ui.genericeditor.tests/src/org/eclipse/ui/genericeditor/tests/contributions/HelloHoverProvider.java b/org.eclipse.ui.genericeditor.tests/src/org/eclipse/ui/genericeditor/tests/contributions/HelloHoverProvider.java new file mode 100644 index 00000000000..12f53196eda --- /dev/null +++ b/org.eclipse.ui.genericeditor.tests/src/org/eclipse/ui/genericeditor/tests/contributions/HelloHoverProvider.java @@ -0,0 +1,38 @@ +/******************************************************************************* + * Copyright (c) 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 + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Mickael Istria (Red Hat Inc.) + *******************************************************************************/ +package org.eclipse.ui.genericeditor.tests.contributions; + +import org.eclipse.jface.text.IRegion; +import org.eclipse.jface.text.ITextHover; +import org.eclipse.jface.text.ITextHoverExtension2; +import org.eclipse.jface.text.ITextViewer; +import org.eclipse.jface.text.Region; + +public class HelloHoverProvider implements ITextHover, ITextHoverExtension2 { + + public static final String LABEL= "Hello!"; + + @Deprecated + @Override + public String getHoverInfo(ITextViewer textViewer, IRegion hoverRegion) { + return null; + } + + @Override + public IRegion getHoverRegion(ITextViewer textViewer, int offset) { + return new Region(0, textViewer.getTextWidget().getText().length()); + } + + @Override + public Object getHoverInfo2(ITextViewer textViewer, IRegion hoverRegion) { + return LABEL; + } +} diff --git a/org.eclipse.ui.genericeditor/META-INF/MANIFEST.MF b/org.eclipse.ui.genericeditor/META-INF/MANIFEST.MF index c52eaf1ba91..2d611f8ed1b 100644 --- a/org.eclipse.ui.genericeditor/META-INF/MANIFEST.MF +++ b/org.eclipse.ui.genericeditor/META-INF/MANIFEST.MF @@ -8,7 +8,7 @@ Bundle-RequiredExecutionEnvironment: JavaSE-1.8 Require-Bundle: org.eclipse.ui.workbench.texteditor;bundle-version="3.10.0", org.eclipse.ui.editors;bundle-version="3.10.0", org.eclipse.text;bundle-version="3.6.0", - org.eclipse.jface.text;bundle-version="3.12.0", + org.eclipse.jface.text;bundle-version="3.13.0", org.eclipse.core.runtime;bundle-version="3.12.0", org.eclipse.ui.workbench;bundle-version="3.109.0", org.eclipse.jface;bundle-version="3.12.0", diff --git a/org.eclipse.ui.genericeditor/src/org/eclipse/ui/internal/genericeditor/CompositeTextHover.java b/org.eclipse.ui.genericeditor/src/org/eclipse/ui/internal/genericeditor/CompositeTextHover.java deleted file mode 100644 index 89944fb780b..00000000000 --- a/org.eclipse.ui.genericeditor/src/org/eclipse/ui/internal/genericeditor/CompositeTextHover.java +++ /dev/null @@ -1,90 +0,0 @@ -/******************************************************************************* - * 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 - * http://www.eclipse.org/legal/epl-v10.html - * - * Contributors: - * - Mickael Istria (Red Hat Inc.) - *******************************************************************************/ -package org.eclipse.ui.internal.genericeditor; - -import java.util.List; - -import org.eclipse.core.runtime.Assert; -import org.eclipse.jface.text.IInformationControlCreator; -import org.eclipse.jface.text.IRegion; -import org.eclipse.jface.text.ITextHover; -import org.eclipse.jface.text.ITextHoverExtension; -import org.eclipse.jface.text.ITextHoverExtension2; -import org.eclipse.jface.text.ITextViewer; - -/** - * A text hover that delegates its operations to children - * provided in constructor and returns the first interesting result. - * - * @since 1.0 - */ -public class CompositeTextHover implements ITextHover, ITextHoverExtension, ITextHoverExtension2 { - - private List<ITextHover> hoversToConsider; - private ITextHover currentHover = null; - - public CompositeTextHover(List<ITextHover> hoversToConsider) { - Assert.isNotNull(hoversToConsider); - this.hoversToConsider = hoversToConsider; - } - - @Override - public Object getHoverInfo2(ITextViewer textViewer, IRegion hoverRegion) { - for (ITextHover hover : this.hoversToConsider) { - Object res = null; - if (hover instanceof ITextHoverExtension2) { - res = ((ITextHoverExtension2)hover).getHoverInfo2(textViewer, hoverRegion); - } else { - res = hover.getHoverInfo(textViewer, hoverRegion); - } - if (res != null) { - currentHover = hover; - return res; - } - } - return null; - } - - @Override - public IInformationControlCreator getHoverControlCreator() { - ITextHover hover = this.currentHover; - if (hover != null) { - if (hover instanceof ITextHoverExtension) { - return ((ITextHoverExtension)hover).getHoverControlCreator(); - } - } - return null; - } - - @Override - public String getHoverInfo(ITextViewer textViewer, IRegion hoverRegion) { - for (ITextHover hover : this.hoversToConsider) { - String res = hover.getHoverInfo(textViewer, hoverRegion); - if (res != null) { - currentHover = hover; - return res; - } - } - return null; - } - - @Override - public IRegion getHoverRegion(ITextViewer textViewer, int offset) { - for (ITextHover hover : this.hoversToConsider) { - IRegion res = hover.getHoverRegion(textViewer, offset); - if (res != null) { - return res; - } - } - return null; - } - -} diff --git a/org.eclipse.ui.genericeditor/src/org/eclipse/ui/internal/genericeditor/ExtensionBasedTextViewerConfiguration.java b/org.eclipse.ui.genericeditor/src/org/eclipse/ui/internal/genericeditor/ExtensionBasedTextViewerConfiguration.java index ab43608f518..4e0461fe0b1 100644 --- a/org.eclipse.ui.genericeditor/src/org/eclipse/ui/internal/genericeditor/ExtensionBasedTextViewerConfiguration.java +++ b/org.eclipse.ui.genericeditor/src/org/eclipse/ui/internal/genericeditor/ExtensionBasedTextViewerConfiguration.java @@ -42,6 +42,7 @@ import org.eclipse.ui.IEditorPart; import org.eclipse.ui.IPropertyListener; import org.eclipse.ui.editors.text.TextSourceViewerConfiguration; import org.eclipse.ui.internal.editors.text.EditorsPlugin; +import org.eclipse.ui.internal.genericeditor.hover.CompositeTextHover; import org.eclipse.ui.internal.genericeditor.markers.MarkerResoltionQuickAssistProcessor; import org.eclipse.ui.texteditor.ITextEditor; import org.eclipse.ui.texteditor.spelling.SpellingCorrectionProcessor; @@ -99,8 +100,14 @@ public final class ExtensionBasedTextViewerConfiguration extends TextSourceViewe @Override public ITextHover getTextHover(ISourceViewer sourceViewer, String contentType) { - TextHoverRegistry registry= GenericEditorPlugin.getDefault().getHoverRegistry(); - return registry.getAvailableHover(sourceViewer, getContentTypes()); + List<ITextHover> hovers = GenericEditorPlugin.getDefault().getHoverRegistry().getAvailableHovers(sourceViewer, getContentTypes()); + if (hovers == null || hovers.isEmpty()) { + return null; + } else if (hovers.size() == 1) { + return hovers.get(0); + } else { + return new CompositeTextHover(hovers); + } } @Override diff --git a/org.eclipse.ui.genericeditor/src/org/eclipse/ui/internal/genericeditor/GenericEditorPlugin.java b/org.eclipse.ui.genericeditor/src/org/eclipse/ui/internal/genericeditor/GenericEditorPlugin.java index 8bdf7a4cf52..cbceac42bcc 100644 --- a/org.eclipse.ui.genericeditor/src/org/eclipse/ui/internal/genericeditor/GenericEditorPlugin.java +++ b/org.eclipse.ui.genericeditor/src/org/eclipse/ui/internal/genericeditor/GenericEditorPlugin.java @@ -14,6 +14,7 @@ package org.eclipse.ui.internal.genericeditor; import org.eclipse.jface.text.ITextHover; import org.eclipse.jface.text.contentassist.IContentAssistProcessor; import org.eclipse.jface.text.presentation.IPresentationReconciler; +import org.eclipse.ui.internal.genericeditor.hover.TextHoverRegistry; import org.eclipse.ui.plugin.AbstractUIPlugin; import org.osgi.framework.BundleContext; diff --git a/org.eclipse.ui.genericeditor/src/org/eclipse/ui/internal/genericeditor/hover/CompositeInformationControl.java b/org.eclipse.ui.genericeditor/src/org/eclipse/ui/internal/genericeditor/hover/CompositeInformationControl.java new file mode 100644 index 00000000000..a63088f2fb4 --- /dev/null +++ b/org.eclipse.ui.genericeditor/src/org/eclipse/ui/internal/genericeditor/hover/CompositeInformationControl.java @@ -0,0 +1,156 @@ +/******************************************************************************* + * Copyright (c) 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 + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * - Mickael Istria (Red Hat Inc.) + *******************************************************************************/ +package org.eclipse.ui.internal.genericeditor.hover; + +import java.util.Arrays; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + +import org.eclipse.core.runtime.Assert; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Status; +import org.eclipse.jface.text.AbstractInformationControl; +import org.eclipse.jface.text.IInformationControl; +import org.eclipse.jface.text.IInformationControlCreator; +import org.eclipse.jface.text.IInformationControlExtension; +import org.eclipse.jface.text.IInformationControlExtension2; +import org.eclipse.jface.text.IInformationControlExtension5; +import org.eclipse.jface.text.ITextHover; +import org.eclipse.swt.SWT; +import org.eclipse.swt.graphics.Point; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Shell; +import org.eclipse.ui.internal.genericeditor.GenericEditorPlugin; + +public class CompositeInformationControl extends AbstractInformationControl implements IInformationControlExtension2 { + + final LinkedHashMap<ITextHover, IInformationControlCreator> creators; + LinkedHashMap<ITextHover, IInformationControl> controls; + + public CompositeInformationControl(Shell parentShell, LinkedHashMap<ITextHover, IInformationControlCreator> creators) { + super(parentShell, true); // TODO check best constructor + Assert.isLegal(creators.size() > 1, "Do not compose a unique hover"); //$NON-NLS-1$ + this.creators = creators; + create(); + } + + @Override + public boolean hasContents() { + for (IInformationControl control : controls.values()) { + if (control instanceof IInformationControlExtension) { + if (((IInformationControlExtension)control).hasContents()) { + return true; + } + } else { + return true; + } + } + return false; + } + + @Override + public void setInput(Object input) { + @SuppressWarnings("unchecked") + Map<ITextHover, Object> inputs = (Map<ITextHover, Object>)input; + for (Entry<ITextHover, Object> entry : inputs.entrySet()) { + IInformationControl informationControl = controls.get(entry.getKey()); + if (informationControl != null) { + if (informationControl instanceof IInformationControlExtension2) { + ((IInformationControlExtension2)informationControl).setInput(entry.getValue()); + } else { + informationControl.setInformation(entry.getValue().toString()); + } + } + } + } + + @Override + public void createContent(Composite parent) { + this.controls = new LinkedHashMap<>(); // TODO maybe use canReuse or canReplace + GridLayout layout = new GridLayout(1, false); + parent.setLayout(layout); + boolean firstControl = true; + for (Entry<ITextHover, IInformationControlCreator> hoverControlCreator : this.creators.entrySet()) { + IInformationControl informationControl = hoverControlCreator.getValue().createInformationControl(parent.getShell()); + if (informationControl instanceof AbstractInformationControl) { + List<Control> children = Arrays.asList(((AbstractInformationControl)informationControl).getShell().getChildren()); + children.remove(parent); + if (children.size() == 0 ) { + continue; + } + for (Control control : children) { + control.setParent(parent); + control.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); + } + if (!firstControl) { + ((GridData)children.get(0).getLayoutData()).verticalIndent = 15; + } + controls.put(hoverControlCreator.getKey(), informationControl); + firstControl = false; + } else { + GenericEditorPlugin.getDefault().getLog().log(new Status(IStatus.WARNING, GenericEditorPlugin.BUNDLE_ID, + "Only text hovers producing an AbstractInformationControl can be aggregated; got a " + informationControl.getClass().getSimpleName())); //$NON-NLS-1$ + informationControl.dispose(); + } + } + } + + @Override + public void dispose() { + controls.values().forEach(IInformationControl::dispose); + controls.clear(); + super.dispose(); + } + + @Override + public IInformationControlCreator getInformationPresenterControlCreator() { + if (controls.isEmpty()) { + return null; + } else if (controls.size() == 1) { + IInformationControl control = controls.values().iterator().next(); + if (control instanceof IInformationControlExtension5) { + return ((IInformationControlExtension5)control).getInformationPresenterControlCreator(); + } + } else { + LinkedHashMap<ITextHover, IInformationControlCreator> presenterCreators = new LinkedHashMap<>(); + boolean allNull = true; + for (Entry<ITextHover, IInformationControl> hover : this.controls.entrySet()) { + IInformationControlCreator creator = null; + if (hover.getValue() instanceof IInformationControlExtension5) + creator = ((IInformationControlExtension5)hover.getValue()).getInformationPresenterControlCreator(); + if (creator == null) { + creator = this.creators.get(hover.getKey()); + } else { + allNull = false; + } + if (creator != null) { + presenterCreators.put(hover.getKey(), creator); + } + } + if (allNull) { + return null; + } + return new CompositeInformationControlCreator(presenterCreators); + } + return null; + } + + @Override + public Point computeSizeHint() { + return getShell().computeSize(SWT.DEFAULT, SWT.DEFAULT, true); + } + +} diff --git a/org.eclipse.ui.genericeditor/src/org/eclipse/ui/internal/genericeditor/hover/CompositeInformationControlCreator.java b/org.eclipse.ui.genericeditor/src/org/eclipse/ui/internal/genericeditor/hover/CompositeInformationControlCreator.java new file mode 100644 index 00000000000..ddeee67056c --- /dev/null +++ b/org.eclipse.ui.genericeditor/src/org/eclipse/ui/internal/genericeditor/hover/CompositeInformationControlCreator.java @@ -0,0 +1,120 @@ +/******************************************************************************* + * Copyright (c) 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 + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * - Mickael Istria (Red Hat Inc.) + *******************************************************************************/ +package org.eclipse.ui.internal.genericeditor.hover; + +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map.Entry; + +import org.eclipse.jface.text.AbstractReusableInformationControlCreator; +import org.eclipse.jface.text.DefaultInformationControl; +import org.eclipse.jface.text.IInformationControl; +import org.eclipse.jface.text.IInformationControlCreator; +import org.eclipse.jface.text.IInformationControlCreatorExtension; +import org.eclipse.jface.text.ITextHover; +import org.eclipse.jface.text.ITextHoverExtension; +import org.eclipse.swt.widgets.Shell; + +public class CompositeInformationControlCreator + implements IInformationControlCreator, IInformationControlCreatorExtension { + + private final LinkedHashMap<ITextHover, IInformationControlCreator> creators; + + public CompositeInformationControlCreator(List<ITextHover> hovers) { + this.creators = new LinkedHashMap<>(); + for (ITextHover hover : hovers) { + creators.put(hover, getInformationControlCreator(hover)); + } + } + + public CompositeInformationControlCreator(LinkedHashMap<ITextHover, IInformationControlCreator> creators) { + this.creators = creators; + } + + private static IInformationControlCreator getInformationControlCreator(ITextHover hover) { + IInformationControlCreator controlCreator = null; + if (hover instanceof ITextHoverExtension) { + controlCreator = ((ITextHoverExtension)hover).getHoverControlCreator(); + } + if (controlCreator == null) { + controlCreator = new AbstractReusableInformationControlCreator() { + @Override + protected IInformationControl doCreateInformationControl(Shell parent) { + return new DefaultInformationControl(parent, true); + } + }; + } + return controlCreator; + } + + + @Override + public boolean canReuse(IInformationControl control) { + if (control.getClass() != CompositeInformationControl.class) { + return false; + } + CompositeInformationControl other = (CompositeInformationControl)control; + if (!other.creators.equals(this.creators)) { + return false; + } + Iterator<Entry<ITextHover, IInformationControlCreator>> thisIterator = this.creators.entrySet().iterator(); + Iterator<Entry<ITextHover, IInformationControl>> otherIterator = other.controls.entrySet().iterator(); + do { + Entry<ITextHover, IInformationControlCreator> thisEntry = thisIterator.next(); + Entry<ITextHover, IInformationControl> otherEntry = otherIterator.next(); + if (!thisEntry.getKey().equals(otherEntry.getKey())) { + return false; + } + if (!(thisEntry.getValue() instanceof IInformationControlCreatorExtension)) { + return false; + } + if (!((IInformationControlCreatorExtension)thisEntry.getValue()).canReuse(otherEntry.getValue())) { + return false; + } + } while (thisIterator.hasNext()); + return true; + } + + @Override + public boolean canReplace(IInformationControlCreator creator) { + if (creator.getClass() != this.getClass()) { + return false; + } + CompositeInformationControlCreator other = (CompositeInformationControlCreator)creator; + if (other.creators.size() != this.creators.size()) { + return false; + } + Iterator<Entry<ITextHover, IInformationControlCreator>> thisIterator = this.creators.entrySet().iterator(); + Iterator<Entry<ITextHover, IInformationControlCreator>> otherIterator = other.creators.entrySet().iterator(); + do { + Entry<ITextHover, IInformationControlCreator> thisEntry = thisIterator.next(); + Entry<ITextHover, IInformationControlCreator> otherEntry = otherIterator.next(); + if (!thisEntry.getKey().equals(otherEntry.getKey())) { + return false; + } + if (!(thisEntry.getValue() instanceof IInformationControlCreatorExtension)) { + return false; + } + if (!((IInformationControlCreatorExtension)thisEntry.getValue()).canReplace(otherEntry.getValue())) { + return false; + } + } while (thisIterator.hasNext()); + return true; + } + + + @Override + public IInformationControl createInformationControl(Shell parent) { + return new CompositeInformationControl(parent, this.creators); + } + +} diff --git a/org.eclipse.ui.genericeditor/src/org/eclipse/ui/internal/genericeditor/hover/CompositeTextHover.java b/org.eclipse.ui.genericeditor/src/org/eclipse/ui/internal/genericeditor/hover/CompositeTextHover.java new file mode 100644 index 00000000000..82e2cb271a9 --- /dev/null +++ b/org.eclipse.ui.genericeditor/src/org/eclipse/ui/internal/genericeditor/hover/CompositeTextHover.java @@ -0,0 +1,113 @@ +/******************************************************************************* + * 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 + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * - Mickael Istria (Red Hat Inc.) + *******************************************************************************/ +package org.eclipse.ui.internal.genericeditor.hover; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; + +import org.eclipse.core.runtime.Assert; +import org.eclipse.jface.text.IInformationControlCreator; +import org.eclipse.jface.text.IRegion; +import org.eclipse.jface.text.ITextHover; +import org.eclipse.jface.text.ITextHoverExtension; +import org.eclipse.jface.text.ITextHoverExtension2; +import org.eclipse.jface.text.ITextViewer; +import org.eclipse.jface.text.Region; + +/** + * A text hover that delegates its operations to children + * provided in constructor and returns the first interesting result. + * + * @since 1.0 + */ +public class CompositeTextHover implements ITextHover, ITextHoverExtension, ITextHoverExtension2 { + + private final List<ITextHover> allHovers; + private LinkedHashMap<ITextHover, IRegion> regions = null; + private LinkedHashMap<ITextHover, Object> currentHovers = null; + + public CompositeTextHover(List<ITextHover> hoversToConsider) { + Assert.isNotNull(hoversToConsider); + Assert.isLegal(hoversToConsider.size() > 1, "Do not compose a single hover."); //$NON-NLS-1$ + this.allHovers = Collections.unmodifiableList(hoversToConsider); + } + + @Override + public Object getHoverInfo2(ITextViewer textViewer, IRegion requestRegion) { + this.currentHovers = new LinkedHashMap<>(); + for (ITextHover hover : this.allHovers) { + IRegion currentRegion = this.regions.get(hover); + if (currentRegion == null) { + continue; + } + Object res = hover instanceof ITextHoverExtension2 ? + ((ITextHoverExtension2)hover).getHoverInfo2(textViewer, currentRegion) : + hover.getHoverInfo(textViewer, currentRegion); + if (res != null) { + this.currentHovers.put(hover, res); + } + } + if (this.currentHovers.isEmpty()) { + return null; + } else if (this.currentHovers.size() == 1) { + return this.currentHovers.values().iterator().next(); + } else { + return this.currentHovers; + } + } + + @Override + public IInformationControlCreator getHoverControlCreator() { + if (this.currentHovers == null || this.currentHovers.isEmpty()) { + return null; + } else if (currentHovers.size() == 1) { + ITextHover hover = this.currentHovers.keySet().iterator().next(); + return hover instanceof ITextHoverExtension ? + ((ITextHoverExtension)hover).getHoverControlCreator(): + null; + } else { + return new CompositeInformationControlCreator(new ArrayList<>(this.currentHovers.keySet())); + } + } + + @Override + public String getHoverInfo(ITextViewer textViewer, IRegion hoverRegion) { + return this.allHovers.stream() + .map(hover -> hover.getHoverInfo(textViewer, this.regions.get(hover))) + .filter(Objects::nonNull) + .collect(Collectors.joining("\n")); //$NON-NLS-1$ + } + + @Override + public IRegion getHoverRegion(ITextViewer textViewer, int offset) { + this.regions = new LinkedHashMap<>(); + IRegion res = null; + for (ITextHover hover : this.allHovers) { + IRegion region = hover.getHoverRegion(textViewer, offset); + if (region != null) { + this.regions.put(hover, region); + if (res == null) { + res = region; + } else { + int startOffset = Math.max(res.getOffset(), region.getOffset()); + int endOffset = Math.min(res.getOffset() + res.getLength(), region.getOffset() + region.getLength()); + res = new Region(startOffset, endOffset - startOffset); + } + } + } + return res; + } + +} diff --git a/org.eclipse.ui.genericeditor/src/org/eclipse/ui/internal/genericeditor/OrderedExtensionComparator.java b/org.eclipse.ui.genericeditor/src/org/eclipse/ui/internal/genericeditor/hover/OrderedExtensionComparator.java index 0ae0bbaca75..9cf32222951 100644 --- a/org.eclipse.ui.genericeditor/src/org/eclipse/ui/internal/genericeditor/OrderedExtensionComparator.java +++ b/org.eclipse.ui.genericeditor/src/org/eclipse/ui/internal/genericeditor/hover/OrderedExtensionComparator.java @@ -8,7 +8,7 @@ * Contributors: * Mickael Istria (Red Hat Inc.) - initial implementation *******************************************************************************/ -package org.eclipse.ui.internal.genericeditor; +package org.eclipse.ui.internal.genericeditor.hover; import java.util.Collection; import java.util.Comparator; @@ -17,12 +17,12 @@ import java.util.function.Function; import java.util.stream.Collectors; import org.eclipse.core.runtime.Assert; -import org.eclipse.ui.internal.genericeditor.TextHoverRegistry.TextHoverExtension; +import org.eclipse.ui.internal.genericeditor.hover.TextHoverRegistry.TextHoverExtension; /** * A comparator that allows to sort elements according to their relative * placement (isBefore and isAfter) - * + *TODO: generalizing to any kind of extension supporting isBefore and isAfter */ class OrderedExtensionComparator implements Comparator<TextHoverExtension> { diff --git a/org.eclipse.ui.genericeditor/src/org/eclipse/ui/internal/genericeditor/TextHoverRegistry.java b/org.eclipse.ui.genericeditor/src/org/eclipse/ui/internal/genericeditor/hover/TextHoverRegistry.java index 374c066eccc..f68bbf74deb 100644 --- a/org.eclipse.ui.genericeditor/src/org/eclipse/ui/internal/genericeditor/TextHoverRegistry.java +++ b/org.eclipse.ui.genericeditor/src/org/eclipse/ui/internal/genericeditor/hover/TextHoverRegistry.java @@ -8,7 +8,7 @@ * Contributors: * - Mickael Istria (Red Hat Inc.) *******************************************************************************/ -package org.eclipse.ui.internal.genericeditor; +package org.eclipse.ui.internal.genericeditor.hover; import java.util.HashMap; import java.util.HashSet; @@ -30,6 +30,8 @@ import org.eclipse.core.runtime.content.IContentType; import org.eclipse.jface.preference.IPreferenceStore; import org.eclipse.jface.text.ITextHover; import org.eclipse.jface.text.source.ISourceViewer; +import org.eclipse.ui.internal.genericeditor.GenericContentTypeRelatedExtension; +import org.eclipse.ui.internal.genericeditor.GenericEditorPlugin; /** * Text hover registry that manages the detectors @@ -90,19 +92,15 @@ public final class TextHoverRegistry { }, EXTENSION_POINT_ID); } - public ITextHover getAvailableHover(ISourceViewer sourceViewer, Set<IContentType> contentTypes) { + public List<ITextHover> getAvailableHovers(ISourceViewer sourceViewer, Set<IContentType> contentTypes) { if (this.outOfSync) { sync(); } - List<ITextHover> hoversToConsider = this.extensions.stream() + return this.extensions.stream() .filter(ext -> contentTypes.contains(ext.targetContentType)) // don't sort in the stream as the initial structure is already sorted by isAfter/isBefore .map(GenericContentTypeRelatedExtension<ITextHover>::createDelegate) .collect(Collectors.toList()); - if (!hoversToConsider.isEmpty()) { - return new CompositeTextHover(hoversToConsider); - } - return null; } private void sync() { diff --git a/org.eclipse.ui.genericeditor/src/org/eclipse/ui/internal/genericeditor/markers/MarkerHoverControlCreator.java b/org.eclipse.ui.genericeditor/src/org/eclipse/ui/internal/genericeditor/markers/MarkerHoverControlCreator.java index 8d81994f070..55e9d0bf663 100644 --- a/org.eclipse.ui.genericeditor/src/org/eclipse/ui/internal/genericeditor/markers/MarkerHoverControlCreator.java +++ b/org.eclipse.ui.genericeditor/src/org/eclipse/ui/internal/genericeditor/markers/MarkerHoverControlCreator.java @@ -17,19 +17,29 @@ import org.eclipse.swt.widgets.Shell; public class MarkerHoverControlCreator implements IInformationControlCreator, IInformationControlCreatorExtension { + private final boolean showAffordanceString; + + public MarkerHoverControlCreator() { + this(true); + } + + public MarkerHoverControlCreator(boolean showAffordanceString) { + this.showAffordanceString = showAffordanceString; + } + @Override public boolean canReuse(IInformationControl control) { - return false; + return control instanceof MarkerInformationControl; } @Override public boolean canReplace(IInformationControlCreator creator) { - return false; + return creator instanceof MarkerHoverControlCreator; } @Override public IInformationControl createInformationControl(Shell parent) { - return new MarkerInformationControl(parent,this); + return new MarkerInformationControl(parent, showAffordanceString); } } diff --git a/org.eclipse.ui.genericeditor/src/org/eclipse/ui/internal/genericeditor/markers/MarkerInformationControl.java b/org.eclipse.ui.genericeditor/src/org/eclipse/ui/internal/genericeditor/markers/MarkerInformationControl.java index 98bf713d93b..db93b4a9669 100644 --- a/org.eclipse.ui.genericeditor/src/org/eclipse/ui/internal/genericeditor/markers/MarkerInformationControl.java +++ b/org.eclipse.ui.genericeditor/src/org/eclipse/ui/internal/genericeditor/markers/MarkerInformationControl.java @@ -11,6 +11,7 @@ package org.eclipse.ui.internal.genericeditor.markers; import java.util.Collection; +import java.util.LinkedHashMap; import java.util.List; import org.eclipse.core.resources.IMarker; @@ -19,7 +20,6 @@ import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Status; import org.eclipse.core.runtime.jobs.Job; import org.eclipse.jface.text.AbstractInformationControl; -import org.eclipse.jface.text.AbstractReusableInformationControlCreator; import org.eclipse.jface.text.IInformationControl; import org.eclipse.jface.text.IInformationControlCreator; import org.eclipse.jface.text.IInformationControlExtension; @@ -46,11 +46,10 @@ import org.eclipse.ui.ide.IDE.SharedImages; public class MarkerInformationControl extends AbstractInformationControl implements IInformationControl, IInformationControlExtension, IInformationControlExtension2 { - private IInformationControlCreator creator; + private final LinkedHashMap<IMarker, Composite> composites = new LinkedHashMap<>(); - public MarkerInformationControl(Shell parentShell, IInformationControlCreator creator) { - super(parentShell, EditorsUI.getTooltipAffordanceString()); - this.creator = creator; + public MarkerInformationControl(Shell parentShell, boolean showAffordanceString) { + super(parentShell, showAffordanceString ? EditorsUI.getTooltipAffordanceString() : null); create(); } @@ -84,9 +83,11 @@ public class MarkerInformationControl extends AbstractInformationControl impleme @SuppressWarnings("unchecked") @Override public void setInput(Object input) { + this.composites.values().forEach(Composite::dispose); this.markers = (List<IMarker>)input; for (IMarker marker : this.markers) { Composite markerComposite = new Composite(parent, SWT.NONE); + this.composites.put(marker, markerComposite); GridLayout gridLayout = new GridLayout(1, false); gridLayout.verticalSpacing = 0; markerComposite.setLayout(gridLayout); @@ -135,17 +136,12 @@ public class MarkerInformationControl extends AbstractInformationControl impleme }); } } - parent.pack(true); + parent.layout(true); } @Override public IInformationControlCreator getInformationPresenterControlCreator() { - return new AbstractReusableInformationControlCreator() { - @Override - protected IInformationControl doCreateInformationControl(Shell parent) { - return creator.createInformationControl(parent); - } - }; + return new MarkerHoverControlCreator(false); } @Override |