Skip to main content
aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSopot Cela2016-09-26 10:16:25 +0000
committerDani Megert2016-09-26 10:30:36 +0000
commit5bcb7ac5045743f9ec17895a9a12318dd90ea4cb (patch)
treeea2d98cee208a040d2ee43fef458d3775a5dc90d /org.eclipse.ui.genericeditor/src
parent8871738e5b595e7715499161b04980d9f16df820 (diff)
downloadeclipse.platform.text-5bcb7ac5045743f9ec17895a9a12318dd90ea4cb.tar.gz
eclipse.platform.text-5bcb7ac5045743f9ec17895a9a12318dd90ea4cb.tar.xz
eclipse.platform.text-5bcb7ac5045743f9ec17895a9a12318dd90ea4cb.zip
Bug 497871 - Generic and extensible text editor
This change creates a new extensible text editor, with extension points for: * contentAssist * hover * syntax highlighting Some unit tests show examples of extensions. You can load an IDE with the suggested change (including org.eclipse.ui.editors.tests) and get a syntax highlighter, a hover support and some content assist on the plain text editor. Bug: 497871 Bug: 496117 Bug: 496115 Bug: 496300 Signed-off-by: Mickael Istria <mistria@redhat.com> Signed-off-by: Sopot Cela <scela@redhat.com> Change-Id: I2eec71e4620364aa11c500ec07e54c693863cf44
Diffstat (limited to 'org.eclipse.ui.genericeditor/src')
-rw-r--r--org.eclipse.ui.genericeditor/src/org/eclipse/ui/internal/genericeditor/CompositeContentAssistProcessor.java93
-rw-r--r--org.eclipse.ui.genericeditor/src/org/eclipse/ui/internal/genericeditor/ContentAssistProcessorRegistry.java195
-rw-r--r--org.eclipse.ui.genericeditor/src/org/eclipse/ui/internal/genericeditor/ExtensionBasedTextEditor.java37
-rw-r--r--org.eclipse.ui.genericeditor/src/org/eclipse/ui/internal/genericeditor/ExtensionBasedTextViewerConfiguration.java98
-rw-r--r--org.eclipse.ui.genericeditor/src/org/eclipse/ui/internal/genericeditor/GenericEditorPlugin.java82
-rw-r--r--org.eclipse.ui.genericeditor/src/org/eclipse/ui/internal/genericeditor/PresentationReconcilerRegistry.java149
-rw-r--r--org.eclipse.ui.genericeditor/src/org/eclipse/ui/internal/genericeditor/TextHoverRegistry.java111
7 files changed, 765 insertions, 0 deletions
diff --git a/org.eclipse.ui.genericeditor/src/org/eclipse/ui/internal/genericeditor/CompositeContentAssistProcessor.java b/org.eclipse.ui.genericeditor/src/org/eclipse/ui/internal/genericeditor/CompositeContentAssistProcessor.java
new file mode 100644
index 000000000..d4d64acee
--- /dev/null
+++ b/org.eclipse.ui.genericeditor/src/org/eclipse/ui/internal/genericeditor/CompositeContentAssistProcessor.java
@@ -0,0 +1,93 @@
+/*******************************************************************************
+ * Copyright (c) 2016 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.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import org.eclipse.jface.text.ITextViewer;
+import org.eclipse.jface.text.contentassist.ICompletionProposal;
+import org.eclipse.jface.text.contentassist.IContentAssistProcessor;
+import org.eclipse.jface.text.contentassist.IContextInformation;
+import org.eclipse.jface.text.contentassist.IContextInformationValidator;
+
+/**
+ * A content assist processor that delegates all content assist
+ * operations to children provided in constructor and aggregates
+ * the results.
+ *
+ * @since 1.0
+ */
+public class CompositeContentAssistProcessor implements IContentAssistProcessor {
+
+ private List<IContentAssistProcessor> fContentAssistProcessors;
+
+ /**
+ * Constructor
+ * @param contentAssistProcessors the children that will actually populate the output
+ * of this content assist processor.
+ */
+ public CompositeContentAssistProcessor(List<IContentAssistProcessor> contentAssistProcessors) {
+ fContentAssistProcessors= contentAssistProcessors;
+ }
+
+ @Override
+ public ICompletionProposal[] computeCompletionProposals(ITextViewer viewer, int offset) {
+ List<ICompletionProposal> res = new ArrayList<>();
+ for (IContentAssistProcessor processor : this.fContentAssistProcessors) {
+ res.addAll(Arrays.asList(processor.computeCompletionProposals(viewer, offset)));
+ }
+ return res.toArray(new ICompletionProposal[res.size()]);
+ }
+
+ @Override
+ public IContextInformation[] computeContextInformation(ITextViewer viewer, int offset) {
+ List<IContextInformation> res = new ArrayList<>();
+ for (IContentAssistProcessor processor : this.fContentAssistProcessors) {
+ res.addAll(Arrays.asList(processor.computeContextInformation(viewer, offset)));
+ }
+ return res.toArray(new IContextInformation[res.size()]);
+ }
+
+ @Override
+ public char[] getCompletionProposalAutoActivationCharacters() {
+ return null;
+ }
+
+ @Override
+ public char[] getContextInformationAutoActivationCharacters() {
+ return null;
+ }
+
+ @Override
+ public String getErrorMessage() {
+ StringBuilder res = new StringBuilder();
+ for (IContentAssistProcessor processor : this.fContentAssistProcessors) {
+ String errorMessage = processor.getErrorMessage();
+ if (errorMessage != null) {
+ res.append(errorMessage);
+ res.append('\n');
+ }
+ }
+ if (res.length() == 0) {
+ return null;
+ } else {
+ return res.toString();
+ }
+ }
+
+ @Override
+ public IContextInformationValidator getContextInformationValidator() {
+ return null;
+ }
+
+} \ No newline at end of file
diff --git a/org.eclipse.ui.genericeditor/src/org/eclipse/ui/internal/genericeditor/ContentAssistProcessorRegistry.java b/org.eclipse.ui.genericeditor/src/org/eclipse/ui/internal/genericeditor/ContentAssistProcessorRegistry.java
new file mode 100644
index 000000000..a81cecbf8
--- /dev/null
+++ b/org.eclipse.ui.genericeditor/src/org/eclipse/ui/internal/genericeditor/ContentAssistProcessorRegistry.java
@@ -0,0 +1,195 @@
+/*******************************************************************************
+ * Copyright (c) 2016 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.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IConfigurationElement;
+import org.eclipse.core.runtime.IRegistryChangeEvent;
+import org.eclipse.core.runtime.IRegistryChangeListener;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.Platform;
+import org.eclipse.core.runtime.Status;
+import org.eclipse.core.runtime.content.IContentType;
+import org.eclipse.core.runtime.content.IContentTypeManager;
+import org.eclipse.jface.text.ITextViewer;
+import org.eclipse.jface.text.contentassist.ICompletionProposal;
+import org.eclipse.jface.text.contentassist.IContentAssistProcessor;
+import org.eclipse.jface.text.contentassist.IContextInformation;
+import org.eclipse.jface.text.contentassist.IContextInformationValidator;
+import org.eclipse.jface.text.source.ISourceViewer;
+import org.eclipse.ui.IEditorInput;
+import org.eclipse.ui.PlatformUI;
+
+/**
+ * A registry of content assist processors provided by extension <code>org.eclipse.ui.genericeditor.contentAssistProcessors</code>.
+ * Those extensions are specific to a given {@link IContentType}.
+ *
+ * @since 1.0
+ */
+public class ContentAssistProcessorRegistry {
+
+ private static final String EXTENSION_POINT_ID = GenericEditorPlugin.BUNDLE_ID + ".contentAssistProcessors"; //$NON-NLS-1$
+
+ /**
+ * This class wraps and proxies an {@link IContentAssistProcessor} provided through extensions
+ * and loads it lazily when it can contribute to the editor, then delegates all operations to
+ * actual processor.
+ * When the contribution cannot contribute to the editor, this wrapper will return neutral values
+ * that don't affect editor behavior.
+ */
+ private static class ContentAssistProcessorExtension implements IContentAssistProcessor {
+ private static final String CONTENT_TYPE_ATTRIBUTE = "contentType"; //$NON-NLS-1$
+ private static final String CLASS_ATTRIBUTE = "class"; //$NON-NLS-1$
+
+ private IConfigurationElement extension;
+ private IContentType targetContentType;
+
+ private IContentAssistProcessor delegate;
+
+ private ContentAssistProcessorExtension(IConfigurationElement element) throws Exception {
+ this.extension = element;
+ this.targetContentType = Platform.getContentTypeManager().getContentType(element.getAttribute(CONTENT_TYPE_ATTRIBUTE));
+ }
+
+ private IContentAssistProcessor getDelegate() {
+ if (this.delegate == null) {
+ try {
+ this.delegate = (IContentAssistProcessor) extension.createExecutableExtension(CLASS_ATTRIBUTE);
+ } catch (CoreException e) {
+ e.printStackTrace();
+ }
+ }
+ return delegate;
+ }
+
+ /**
+ * @return whether the referenced contribution should contribute to the current editor.
+ */
+ public boolean isActive() {
+ IEditorInput input = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage().getActiveEditor().getEditorInput();
+ IContentTypeManager contentTypeManager= Platform.getContentTypeManager();
+ for (IContentType currentContentType : contentTypeManager.findContentTypesFor(input.getName())) {
+ if (currentContentType.isKindOf(targetContentType)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public ICompletionProposal[] computeCompletionProposals(ITextViewer viewer, int offset) {
+ if (isActive()) {
+ return getDelegate().computeCompletionProposals(viewer, offset);
+ }
+ return new ICompletionProposal[0];
+ }
+
+ @Override
+ public IContextInformation[] computeContextInformation(ITextViewer viewer, int offset) {
+ if (isActive()) {
+ return getDelegate().computeContextInformation(viewer, offset);
+ }
+ return new IContextInformation[0];
+ }
+
+ @Override
+ public char[] getCompletionProposalAutoActivationCharacters() {
+ if (isActive()) {
+ return getDelegate().getCompletionProposalAutoActivationCharacters();
+ }
+ return null;
+ }
+
+ @Override
+ public char[] getContextInformationAutoActivationCharacters() {
+ if (isActive()) {
+ return getDelegate().getContextInformationAutoActivationCharacters();
+ }
+ return null;
+ }
+
+ @Override
+ public String getErrorMessage() {
+ if (isActive()) {
+ return getDelegate().getErrorMessage();
+ }
+ return null;
+ }
+
+ @Override
+ public IContextInformationValidator getContextInformationValidator() {
+ if (isActive()) {
+ return getDelegate().getContextInformationValidator();
+ }
+ return null;
+ }
+ }
+
+ private Map<IConfigurationElement, ContentAssistProcessorExtension> extensions = new HashMap<>();
+ private boolean outOfSync = true;
+
+ /**
+ * Creates the registry and binds it to the extension point.
+ */
+ public ContentAssistProcessorRegistry() {
+ Platform.getExtensionRegistry().addRegistryChangeListener(new IRegistryChangeListener() {
+ @Override
+ public void registryChanged(IRegistryChangeEvent event) {
+ outOfSync = true;
+ }
+ }, EXTENSION_POINT_ID);
+ }
+
+ /**
+ * Get the contributed {@link IContentAssistProcessor}s that are relevant to hook on source viewer according
+ * to document content types.
+ * @param sourceViewer the source viewer we're hooking completion to.
+ * @param contentTypes the content types of the document we're editing.
+ * @return the list of {@link IContentAssistProcessor} contributed for at least one of the content types.
+ */
+ public List<IContentAssistProcessor> getContentAssistProcessors(ISourceViewer sourceViewer, Set<IContentType> contentTypes) {
+ if (this.outOfSync) {
+ sync();
+ }
+ List<IContentAssistProcessor> res = new ArrayList<>();
+ for (ContentAssistProcessorExtension ext : this.extensions.values()) {
+ if (contentTypes.contains(ext.targetContentType)) {
+ res.add(ext);
+ }
+ }
+ return res;
+ }
+
+ private void sync() {
+ Set<IConfigurationElement> toRemoveExtensions = new HashSet<>(this.extensions.keySet());
+ for (IConfigurationElement extension : Platform.getExtensionRegistry().getConfigurationElementsFor(EXTENSION_POINT_ID)) {
+ toRemoveExtensions.remove(extension);
+ if (!this.extensions.containsKey(extension)) {
+ try {
+ this.extensions.put(extension, new ContentAssistProcessorExtension(extension));
+ } catch (Exception ex) {
+ GenericEditorPlugin.getDefault().getLog().log(new Status(IStatus.ERROR, GenericEditorPlugin.BUNDLE_ID, ex.getMessage(), ex));
+ }
+ }
+ }
+ for (IConfigurationElement toRemove : toRemoveExtensions) {
+ this.extensions.remove(toRemove);
+ }
+ this.outOfSync = false;
+ }
+}
diff --git a/org.eclipse.ui.genericeditor/src/org/eclipse/ui/internal/genericeditor/ExtensionBasedTextEditor.java b/org.eclipse.ui.genericeditor/src/org/eclipse/ui/internal/genericeditor/ExtensionBasedTextEditor.java
new file mode 100644
index 000000000..467be5318
--- /dev/null
+++ b/org.eclipse.ui.genericeditor/src/org/eclipse/ui/internal/genericeditor/ExtensionBasedTextEditor.java
@@ -0,0 +1,37 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2016 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:
+ * Sopot Cela, Mickael Istria (Red Hat Inc.) - initial implementation
+ *******************************************************************************/
+package org.eclipse.ui.internal.genericeditor;
+
+import org.eclipse.ui.editors.text.TextEditor;
+
+/**
+ * A generic code editor that is aimed at being extended by contributions. Behavior
+ * is supposed to be added via extensions, not by inheritance.
+ *
+ * @since 1.0
+ */
+public class ExtensionBasedTextEditor extends TextEditor {
+
+ private static final String CONTEXT_ID = "org.eclipse.ui.genericeditor.genericEditorContext"; //$NON-NLS-1$
+
+ /**
+ *
+ */
+ public ExtensionBasedTextEditor() {
+ setSourceViewerConfiguration(new ExtensionBasedTextViewerConfiguration(this, getPreferenceStore()));
+ }
+
+ @Override
+ protected void setKeyBindingScopes(String[] scopes) {
+ super.setKeyBindingScopes(new String[] { CONTEXT_ID });
+ }
+
+}
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
new file mode 100644
index 000000000..d8d2b21c4
--- /dev/null
+++ b/org.eclipse.ui.genericeditor/src/org/eclipse/ui/internal/genericeditor/ExtensionBasedTextViewerConfiguration.java
@@ -0,0 +1,98 @@
+/*******************************************************************************
+ * Copyright (c) 2016 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:
+ * Sopot Cela, Mickael Istria (Red Hat Inc.) - initial implementation
+ *******************************************************************************/
+package org.eclipse.ui.internal.genericeditor;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import org.eclipse.core.runtime.Platform;
+import org.eclipse.core.runtime.content.IContentType;
+import org.eclipse.jface.preference.IPreferenceStore;
+import org.eclipse.jface.text.AbstractReusableInformationControlCreator;
+import org.eclipse.jface.text.DefaultInformationControl;
+import org.eclipse.jface.text.IDocument;
+import org.eclipse.jface.text.IInformationControl;
+import org.eclipse.jface.text.ITextHover;
+import org.eclipse.jface.text.contentassist.ContentAssistant;
+import org.eclipse.jface.text.contentassist.IContentAssistProcessor;
+import org.eclipse.jface.text.contentassist.IContentAssistant;
+import org.eclipse.jface.text.presentation.IPresentationReconciler;
+import org.eclipse.jface.text.source.ISourceViewer;
+import org.eclipse.swt.widgets.Shell;
+import org.eclipse.ui.IEditorPart;
+import org.eclipse.ui.editors.text.TextSourceViewerConfiguration;
+
+/**
+ * The configuration of the {@link ExtensionBasedTextEditor}. It registers the proxy composite
+ * for hover, completion, syntax highlighting, and then those proxy take care of resolving to
+ * the right extensions on-demand.
+ *
+ * @since 1.0
+ */
+public final class ExtensionBasedTextViewerConfiguration extends TextSourceViewerConfiguration {
+
+ private IEditorPart editor;
+ private Set<IContentType> contentTypes;
+
+ /**
+ *
+ * @param editor the editor we're creating.
+ * @param preferenceStore the preference store.
+ */
+ public ExtensionBasedTextViewerConfiguration(IEditorPart editor, IPreferenceStore preferenceStore) {
+ super(preferenceStore);
+ this.editor = editor;
+ }
+
+ private Set<IContentType> getContentTypes() {
+ if (this.contentTypes == null) {
+ this.contentTypes = new HashSet<>(Arrays.asList(Platform.getContentTypeManager().findContentTypesFor(editor.getEditorInput().getName())));
+ }
+ return this.contentTypes;
+ }
+
+ @Override
+ public ITextHover getTextHover(ISourceViewer sourceViewer, String contentType) {
+ TextHoverRegistry registry= GenericEditorPlugin.getDefault().getHoverRegistry();
+ return registry.getAvailableHover(sourceViewer, getContentTypes());
+ }
+
+ @Override
+ public IContentAssistant getContentAssistant(ISourceViewer sourceViewer) {
+ ContentAssistProcessorRegistry registry= GenericEditorPlugin.getDefault().getContentAssistProcessorRegistry();
+ IContentAssistProcessor processor = new CompositeContentAssistProcessor(registry.getContentAssistProcessors(sourceViewer, getContentTypes()));
+ ContentAssistant res= new ContentAssistant();
+ res.setContextInformationPopupOrientation(ContentAssistant.CONTEXT_INFO_BELOW);
+ res.setProposalPopupOrientation(ContentAssistant.PROPOSAL_REMOVE);
+ res.enableColoredLabels(true);
+ res.setContentAssistProcessor(processor, IDocument.DEFAULT_CONTENT_TYPE);
+ res.setInformationControlCreator(new AbstractReusableInformationControlCreator() {
+ @Override
+ protected IInformationControl doCreateInformationControl(Shell parent) {
+ return new DefaultInformationControl(parent);
+ }
+ });
+ return res;
+ }
+
+ @Override
+ public IPresentationReconciler getPresentationReconciler(ISourceViewer sourceViewer) {
+ PresentationReconcilerRegistry registry = GenericEditorPlugin.getDefault().getPresentationReconcilerRegistry();
+ List<IPresentationReconciler> reconciliers = registry.getPresentationReconcilers(sourceViewer, getContentTypes());
+ if (!reconciliers.isEmpty()) {
+ return reconciliers.get(0);
+ }
+ return null;
+ }
+
+}
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
new file mode 100644
index 000000000..e5f33d470
--- /dev/null
+++ b/org.eclipse.ui.genericeditor/src/org/eclipse/ui/internal/genericeditor/GenericEditorPlugin.java
@@ -0,0 +1,82 @@
+/*******************************************************************************
+ * Copyright (c) 2016 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:
+ * Sopot Cela, Mickael Istria (Red Hat Inc.) - initial implementation
+ *******************************************************************************/
+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.plugin.AbstractUIPlugin;
+import org.osgi.framework.BundleContext;
+
+/**
+ * Generic editor plugin activator and singletons.
+ *
+ * @since 1.0
+ */
+public class GenericEditorPlugin extends AbstractUIPlugin {
+
+ public static final String BUNDLE_ID = "org.eclipse.ui.genericeditor"; //$NON-NLS-1$
+
+ private static GenericEditorPlugin INSTANCE;
+
+ private TextHoverRegistry textHoversRegistry;
+ private ContentAssistProcessorRegistry contentAssistProcessorsRegistry;
+ private PresentationReconcilerRegistry presentationReconcilierRegistry;
+
+ @Override
+ public void start(BundleContext context) throws Exception{
+ INSTANCE = this;
+ super.start(context);
+ }
+
+ @Override
+ public void stop(BundleContext context) throws Exception {
+ super.stop(context);
+ INSTANCE = null;
+ }
+
+ public static GenericEditorPlugin getDefault() {
+ return INSTANCE;
+ }
+
+ /**
+ * @return the registry allowing to access contributed {@link ITextHover}s.
+ * @since 1.0
+ */
+ public synchronized TextHoverRegistry getHoverRegistry() {
+ if (this.textHoversRegistry == null) {
+ this.textHoversRegistry = new TextHoverRegistry(getPreferenceStore());
+ }
+ return this.textHoversRegistry;
+ }
+
+ /**
+ * @return the registry allowing to access contributed {@link IContentAssistProcessor}s.
+ * @since 1.0
+ */
+ public synchronized ContentAssistProcessorRegistry getContentAssistProcessorRegistry() {
+ if (this.contentAssistProcessorsRegistry == null) {
+ this.contentAssistProcessorsRegistry = new ContentAssistProcessorRegistry();
+ }
+ return this.contentAssistProcessorsRegistry;
+ }
+
+ /**
+ * @return the registry allowing to access contributed {@link IPresentationReconciler}s.
+ * @since 1.0
+ */
+ public synchronized PresentationReconcilerRegistry getPresentationReconcilerRegistry() {
+ if (this.presentationReconcilierRegistry == null) {
+ this.presentationReconcilierRegistry = new PresentationReconcilerRegistry();
+ }
+ return this.presentationReconcilierRegistry;
+ }
+}
diff --git a/org.eclipse.ui.genericeditor/src/org/eclipse/ui/internal/genericeditor/PresentationReconcilerRegistry.java b/org.eclipse.ui.genericeditor/src/org/eclipse/ui/internal/genericeditor/PresentationReconcilerRegistry.java
new file mode 100644
index 000000000..b973b816c
--- /dev/null
+++ b/org.eclipse.ui.genericeditor/src/org/eclipse/ui/internal/genericeditor/PresentationReconcilerRegistry.java
@@ -0,0 +1,149 @@
+/*******************************************************************************
+ * Copyright (c) 2016 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:
+ * Sopot Cela, Mickael Istria (Red Hat Inc.) - initial implementation
+ *******************************************************************************/
+package org.eclipse.ui.internal.genericeditor;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IConfigurationElement;
+import org.eclipse.core.runtime.IRegistryChangeEvent;
+import org.eclipse.core.runtime.IRegistryChangeListener;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.Platform;
+import org.eclipse.core.runtime.Status;
+import org.eclipse.core.runtime.content.IContentType;
+import org.eclipse.jface.text.ITextViewer;
+import org.eclipse.jface.text.presentation.IPresentationDamager;
+import org.eclipse.jface.text.presentation.IPresentationReconciler;
+import org.eclipse.jface.text.presentation.IPresentationRepairer;
+import org.eclipse.jface.text.source.ISourceViewer;
+
+/**
+ * A registry of presentation reconciliers provided by extension <code>org.eclipse.ui.genericeditor.presentationReconcilers</code>.
+ * Those extensions are specific to a given {@link IContentType}.
+ *
+ * @since 1.0
+ */
+public class PresentationReconcilerRegistry {
+
+ private static final String EXTENSION_POINT_ID = GenericEditorPlugin.BUNDLE_ID + ".presentationReconcilers"; //$NON-NLS-1$
+
+ /**
+ * This class wraps and proxies an {@link IPresentationReconcilier} provided through extensions
+ * and loads it lazily when it can contribute to the editor, then delegates all operations to
+ * actual reconcilier.
+ */
+ private static class PresentationReconcilerExtension implements IPresentationReconciler {
+ private static final String CLASS_ATTRIBUTE = "class"; //$NON-NLS-1$
+ private static final String CONTENT_TYPE_ATTRIBUTE = "contentType"; //$NON-NLS-1$
+
+ private IConfigurationElement extension;
+ private IContentType targetContentType;
+
+ private IPresentationReconciler delegate;
+
+ private PresentationReconcilerExtension(IConfigurationElement element) throws Exception {
+ this.extension = element;
+ this.targetContentType = Platform.getContentTypeManager().getContentType(element.getAttribute(CONTENT_TYPE_ATTRIBUTE));
+ }
+
+ private IPresentationReconciler getDelegate() {
+ if (this.delegate == null) {
+ try {
+ this.delegate = (IPresentationReconciler) extension.createExecutableExtension(CLASS_ATTRIBUTE);
+ } catch (CoreException e) {
+ e.printStackTrace();
+ }
+ }
+ return delegate;
+ }
+
+ @Override
+ public void install(ITextViewer viewer) {
+ getDelegate().install(viewer);
+ }
+
+ @Override
+ public void uninstall() {
+ getDelegate().uninstall();
+ }
+
+ @Override
+ public IPresentationDamager getDamager(String contentType) {
+ return getDelegate().getDamager(contentType);
+
+ }
+
+ @Override
+ public IPresentationRepairer getRepairer(String contentType) {
+ return getDelegate().getRepairer(contentType);
+ }
+
+ }
+ private Map<IConfigurationElement, PresentationReconcilerExtension> extensions = new HashMap<>();
+ private boolean outOfSync = true;
+
+ /**
+ * Creates the registry and binds it to the extension point.
+ */
+ public PresentationReconcilerRegistry() {
+ Platform.getExtensionRegistry().addRegistryChangeListener(new IRegistryChangeListener() {
+ @Override
+ public void registryChanged(IRegistryChangeEvent event) {
+ outOfSync = true;
+ }
+ }, EXTENSION_POINT_ID);
+ }
+
+ /**
+ * Get the contributed {@link IPresentationReconciliers}s that are relevant to hook on source viewer according
+ * to document content types.
+ * @param sourceViewer the source viewer we're hooking completion to.
+ * @param contentTypes the content types of the document we're editing.
+ * @return the list of {@link IPresentationReconciler} contributed for at least one of the content types.
+ */
+ public List<IPresentationReconciler> getPresentationReconcilers(ISourceViewer sourceViewer, Set<IContentType> contentTypes) {
+ if (this.outOfSync) {
+ sync();
+ }
+ List<IPresentationReconciler> res = new ArrayList<>();
+ for (PresentationReconcilerExtension ext : this.extensions.values()) {
+ if (contentTypes.contains(ext.targetContentType)) {
+ res.add(ext);
+ }
+ }
+ return res;
+ }
+
+ private void sync() {
+ Set<IConfigurationElement> toRemoveExtensions = new HashSet<>(this.extensions.keySet());
+ for (IConfigurationElement extension : Platform.getExtensionRegistry().getConfigurationElementsFor(EXTENSION_POINT_ID)) {
+ toRemoveExtensions.remove(extension);
+ if (!this.extensions.containsKey(extension)) {
+ try {
+ this.extensions.put(extension, new PresentationReconcilerExtension(extension));
+ } catch (Exception ex) {
+ GenericEditorPlugin.getDefault().getLog().log(new Status(IStatus.ERROR, GenericEditorPlugin.BUNDLE_ID, ex.getMessage(), ex));
+ }
+ }
+ }
+ for (IConfigurationElement toRemove : toRemoveExtensions) {
+ this.extensions.remove(toRemove);
+ }
+ this.outOfSync = false;
+ }
+
+}
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/TextHoverRegistry.java
new file mode 100644
index 000000000..88e4825f6
--- /dev/null
+++ b/org.eclipse.ui.genericeditor/src/org/eclipse/ui/internal/genericeditor/TextHoverRegistry.java
@@ -0,0 +1,111 @@
+/*******************************************************************************
+ * Copyright (c) 2016 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.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IConfigurationElement;
+import org.eclipse.core.runtime.IRegistryChangeEvent;
+import org.eclipse.core.runtime.IRegistryChangeListener;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.Platform;
+import org.eclipse.core.runtime.Status;
+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;
+
+/**
+ * Text hover registry that manages the detectors
+ * contributed by the <code>org.eclipse.ui.workbench.texteditor.hoverProvider</code> extension point for
+ * targets contributed by the <code>org.eclipse.ui.workbench.texteditor.hyperlinkDetectorTargets</code> extension point.
+ *
+ * @since 1.0
+ */
+public final class TextHoverRegistry {
+
+ private static final String EXTENSION_POINT_ID = GenericEditorPlugin.BUNDLE_ID + ".hoverProviders"; //$NON-NLS-1$
+
+ private Map<IConfigurationElement, TextHoverExtension> extensions = new HashMap<>();
+ private boolean outOfSync = true;
+
+ private static class TextHoverExtension {
+ private static final String CONTENT_TYPE_ATTRIBUTE = "contentType"; //$NON-NLS-1$
+ private static final String CLASS_ATTRIBUTE = "class"; //$NON-NLS-1$
+
+ private IConfigurationElement extension;
+ private IContentType targetContentType;
+ private ITextHover delegate;
+
+ public TextHoverExtension(IConfigurationElement extension) throws Exception {
+ this.extension = extension;
+ this.targetContentType = Platform.getContentTypeManager().getContentType(extension.getAttribute(CONTENT_TYPE_ATTRIBUTE));
+ }
+
+ private ITextHover getDelegate() {
+ if (this.delegate == null) {
+ try {
+ this.delegate = (ITextHover) extension.createExecutableExtension(CLASS_ATTRIBUTE);
+ } catch (CoreException e) {
+ e.printStackTrace();
+ }
+ }
+ return delegate;
+ }
+
+ }
+
+ public TextHoverRegistry(IPreferenceStore preferenceStore) {
+ Platform.getExtensionRegistry().addRegistryChangeListener(new IRegistryChangeListener() {
+ @Override
+ public void registryChanged(IRegistryChangeEvent event) {
+ outOfSync = true;
+ }
+ }, EXTENSION_POINT_ID);
+ }
+
+ public ITextHover getAvailableHover(ISourceViewer sourceViewer, Set<IContentType> contentTypes) {
+ if (this.outOfSync) {
+ sync();
+ }
+ // TODO rather that returning the 1st active hover, consider
+ // supporting compound/aggregated hovers.
+ for (TextHoverExtension ext : this.extensions.values()) {
+ if (contentTypes.contains(ext.targetContentType)) {
+ return ext.getDelegate();
+ }
+ }
+ return null;
+ }
+
+ private void sync() {
+ Set<IConfigurationElement> toRemoveExtensions = new HashSet<>(this.extensions.keySet());
+ for (IConfigurationElement extension : Platform.getExtensionRegistry().getConfigurationElementsFor(EXTENSION_POINT_ID)) {
+ toRemoveExtensions.remove(extension);
+ if (!this.extensions.containsKey(extension)) {
+ try {
+ this.extensions.put(extension, new TextHoverExtension(extension));
+ } catch (Exception ex) {
+ GenericEditorPlugin.getDefault().getLog().log(new Status(IStatus.ERROR, GenericEditorPlugin.BUNDLE_ID, ex.getMessage(), ex));
+ }
+ }
+ }
+ for (IConfigurationElement toRemove : toRemoveExtensions) {
+ this.extensions.remove(toRemove);
+ }
+ this.outOfSync = false;
+ }
+
+}

Back to the top