diff options
author | Lucas Bullen | 2017-10-04 16:04:38 +0000 |
---|---|---|
committer | Lucas Bullen | 2017-11-07 13:43:48 +0000 |
commit | aaab23b17c73ac1bdd89b5c499e158b9c3275fec (patch) | |
tree | f49e5be7ca53b0d507d79700e551c4c64a766eed | |
parent | a0ab17a0ec67369d0762d905ae9f170594b41f07 (diff) | |
download | eclipse.platform.text-I20171107-2000.tar.gz eclipse.platform.text-I20171107-2000.tar.xz eclipse.platform.text-I20171107-2000.zip |
Bug 521382 - [generic editor] Default highlighter for generic editorI20171107-2000
Added extension point for highlight reconcilers, when one is added the
default is turned off
Change-Id: Ibbe8d3df69ba5d364c027bd8c8f07aa7b4e71902
Signed-off-by: Lucas Bullen <lbullen@redhat.com>
16 files changed, 816 insertions, 28 deletions
diff --git a/org.eclipse.ui.genericeditor.examples/plugin.xml b/org.eclipse.ui.genericeditor.examples/plugin.xml index a4fd6f752d5..97238a81750 100644 --- a/org.eclipse.ui.genericeditor.examples/plugin.xml +++ b/org.eclipse.ui.genericeditor.examples/plugin.xml @@ -10,6 +10,7 @@ <!-- Contributors: --> <!-- Sopot Cela & Mickael Istria (Red Hat Inc). -initial implementation --> <!-- Lucas Bullen (Red Hat Inc.) - Bug 508829 custom reconciler support --> +<!-- - Bug 521382 default highlighter --> <!-- ====================================================================== --> <plugin> <extension @@ -76,4 +77,11 @@ contentType="org.eclipse.ui.genericeditor.examples.dotproject"> </autoEditStrategy> </extension> + <extension + point="org.eclipse.ui.genericeditor.highlightReconcilers"> + <highlightReconciler + class="org.eclipse.ui.genericeditor.examples.dotproject.HighlightReconciler" + contentType="org.eclipse.ui.genericeditor.examples.dotproject"> + </highlightReconciler> + </extension> </plugin> diff --git a/org.eclipse.ui.genericeditor.examples/src/org/eclipse/ui/genericeditor/examples/dotproject/HighlightReconciler.java b/org.eclipse.ui.genericeditor.examples/src/org/eclipse/ui/genericeditor/examples/dotproject/HighlightReconciler.java new file mode 100644 index 00000000000..dd520c273a6 --- /dev/null +++ b/org.eclipse.ui.genericeditor.examples/src/org/eclipse/ui/genericeditor/examples/dotproject/HighlightReconciler.java @@ -0,0 +1,37 @@ +/******************************************************************************* + * 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: + * - Lucas Bullen (Red Hat Inc.) + *******************************************************************************/ +package org.eclipse.ui.genericeditor.examples.dotproject; + +import org.eclipse.jface.text.IDocument; +import org.eclipse.jface.text.ITextViewer; +import org.eclipse.jface.text.reconciler.Reconciler; + +public class HighlightReconciler extends Reconciler { + + private HighlightStrategy fStrategy; + + public HighlightReconciler() { + fStrategy = new HighlightStrategy(); + this.setReconcilingStrategy(fStrategy, IDocument.DEFAULT_CONTENT_TYPE); + } + + @Override + public void install(ITextViewer textViewer) { + super.install(textViewer); + fStrategy.install(textViewer); + } + + @Override + public void uninstall() { + super.uninstall(); + fStrategy.uninstall(); + } +}
\ No newline at end of file diff --git a/org.eclipse.ui.genericeditor.examples/src/org/eclipse/ui/genericeditor/examples/dotproject/HighlightStrategy.java b/org.eclipse.ui.genericeditor.examples/src/org/eclipse/ui/genericeditor/examples/dotproject/HighlightStrategy.java new file mode 100644 index 00000000000..58d1b9060a2 --- /dev/null +++ b/org.eclipse.ui.genericeditor.examples/src/org/eclipse/ui/genericeditor/examples/dotproject/HighlightStrategy.java @@ -0,0 +1,209 @@ +/******************************************************************************* + * 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: + * - Lucas Bullen (Red Hat Inc.) + *******************************************************************************/ +package org.eclipse.ui.genericeditor.examples.dotproject; + +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.Map.Entry; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.preferences.IEclipsePreferences; +import org.eclipse.core.runtime.preferences.IEclipsePreferences.IPreferenceChangeListener; +import org.eclipse.core.runtime.preferences.IEclipsePreferences.PreferenceChangeEvent; +import org.eclipse.core.runtime.preferences.InstanceScope; +import org.eclipse.jface.text.IDocument; +import org.eclipse.jface.text.IRegion; +import org.eclipse.jface.text.ISynchronizable; +import org.eclipse.jface.text.ITextViewer; +import org.eclipse.jface.text.ITextViewerExtension5; +import org.eclipse.jface.text.Position; +import org.eclipse.jface.text.reconciler.DirtyRegion; +import org.eclipse.jface.text.reconciler.IReconcilingStrategy; +import org.eclipse.jface.text.reconciler.IReconcilingStrategyExtension; +import org.eclipse.jface.text.source.Annotation; +import org.eclipse.jface.text.source.IAnnotationModel; +import org.eclipse.jface.text.source.IAnnotationModelExtension; +import org.eclipse.jface.text.source.ISourceViewer; +import org.eclipse.swt.custom.CaretEvent; +import org.eclipse.swt.custom.CaretListener; + +/** +* +* This Reconciler Strategy is an example for how to override the default highlight strategy. +* It will highlight closing and opening tag names that match the current word the user is on. +* +*/ +public class HighlightStrategy implements IReconcilingStrategy, IReconcilingStrategyExtension, CaretListener, IPreferenceChangeListener { + + private static final String ANNOTATION_TYPE = "org.eclipse.ui.genericeditor.text"; //$NON-NLS-1$ + public static final String TOGGLE_HIGHLIGHT_PREFERENCE = "org.eclipse.ui.genericeditor.togglehighlight"; //$NON-NLS-1$ + public static final String GENERIC_EDITOR_BUNDLE_ID = "org.eclipse.ui.genericeditor"; //$NON-NLS-1$ + + private boolean enabled; + private ISourceViewer sourceViewer; + private IDocument document; + + private static final String TAG_NAME_REGEX = "<\\/?\\s*([a-zA-Z]+)"; //$NON-NLS-1$ + private static final Pattern TAG_NAME_PATTERN = Pattern.compile(TAG_NAME_REGEX); + + private Annotation[] fOccurrenceAnnotations = null; + + private void applyHighlights(int offset) { + if (sourceViewer == null || !enabled) { + return; + } + String text = document.get(); + offset = ((ITextViewerExtension5)sourceViewer).widgetOffset2ModelOffset(offset); + + int wordStartOffset = Math.max(text.lastIndexOf('/',offset),text.lastIndexOf('<',offset))+1; + int wordEndOffset = findEndingOffset(text, offset); + if(wordEndOffset <= wordStartOffset || wordEndOffset == -1 || wordStartOffset == -1) + return; + String word = text.substring(wordStartOffset, wordEndOffset); + if(word.indexOf('>') != -1 || word.indexOf('<') != -1) { + removeOccurrenceAnnotations(); + return; + } + + Matcher m = TAG_NAME_PATTERN.matcher(text); + Map<Annotation, Position> annotationMap = new HashMap<>(); + while(m.find()) { + if(m.group(1).equals(word)) { + annotationMap.put(new Annotation(ANNOTATION_TYPE, false, null), + new Position(m.start(1), m.end(1) - m.start(1))); + } + } + + if(annotationMap.size() < 2) { + removeOccurrenceAnnotations(); + return; + } + + IAnnotationModel annotationModel = sourceViewer.getAnnotationModel(); + synchronized (getLockObject(annotationModel)) { + if (annotationModel instanceof IAnnotationModelExtension) { + ((IAnnotationModelExtension) annotationModel).replaceAnnotations(fOccurrenceAnnotations, annotationMap); + } else { + removeOccurrenceAnnotations(); + Iterator<Entry<Annotation, Position>> iter = annotationMap.entrySet().iterator(); + while (iter.hasNext()) { + Entry<Annotation, Position> mapEntry = iter.next(); + annotationModel.addAnnotation(mapEntry.getKey(), mapEntry.getValue()); + } + } + fOccurrenceAnnotations = annotationMap.keySet().toArray(new Annotation[annotationMap.keySet().size()]); + } + } + + private static int findEndingOffset(String text, int offset) { + String substring = text.substring(offset); + String[] split = substring.split("[^a-zA-Z0-9]+");//$NON-NLS-1$ + if(split.length == 0) { + return -1; + } + return offset + split[0].length(); + } + + public void install(ITextViewer viewer) { + if (!(viewer instanceof ISourceViewer)) { + return; + } + IEclipsePreferences preferences = InstanceScope.INSTANCE.getNode(GENERIC_EDITOR_BUNDLE_ID); + preferences.addPreferenceChangeListener(this); + this.enabled = preferences.getBoolean(TOGGLE_HIGHLIGHT_PREFERENCE, true); + this.sourceViewer = (ISourceViewer) viewer; + this.sourceViewer.getTextWidget().addCaretListener(this); + } + + public void uninstall() { + if (sourceViewer != null) { + sourceViewer.getTextWidget().removeCaretListener(this); + } + IEclipsePreferences preferences = InstanceScope.INSTANCE.getNode(GENERIC_EDITOR_BUNDLE_ID); + preferences.removePreferenceChangeListener(this); + } + + @Override + public void preferenceChange(PreferenceChangeEvent event) { + if (event.getKey().equals(TOGGLE_HIGHLIGHT_PREFERENCE)) { + this.enabled = Boolean.parseBoolean(event.getNewValue().toString()); + if (enabled) { + initialReconcile(); + } else { + removeOccurrenceAnnotations(); + } + } + } + + @Override + public void caretMoved(CaretEvent event) { + applyHighlights(event.caretOffset); + } + + @Override + public void initialReconcile() { + if (sourceViewer != null) { + sourceViewer.getTextWidget().getDisplay().asyncExec(() -> { + if (sourceViewer != null) { + applyHighlights(sourceViewer.getTextWidget().getCaretOffset()); + } + }); + } + } + + void removeOccurrenceAnnotations() { + IAnnotationModel annotationModel = sourceViewer.getAnnotationModel(); + if (annotationModel == null || fOccurrenceAnnotations == null) + return; + + synchronized (getLockObject(annotationModel)) { + if (annotationModel instanceof IAnnotationModelExtension) { + ((IAnnotationModelExtension) annotationModel).replaceAnnotations(fOccurrenceAnnotations, null); + } else { + for (Annotation fOccurrenceAnnotation : fOccurrenceAnnotations) + annotationModel.removeAnnotation(fOccurrenceAnnotation); + } + fOccurrenceAnnotations = null; + } + } + + private static Object getLockObject(IAnnotationModel annotationModel) { + if (annotationModel instanceof ISynchronizable) { + Object lock = ((ISynchronizable) annotationModel).getLockObject(); + if (lock != null) + return lock; + } + return annotationModel; + } + + @Override + public void setDocument(IDocument document) { + this.document = document; + } + + @Override + public void reconcile(DirtyRegion dirtyRegion, IRegion subRegion) { + // Do nothing + } + + @Override + public void reconcile(IRegion partition) { + // Do nothing + } + + @Override + public void setProgressMonitor(IProgressMonitor monitor) { + // Not used + } +} diff --git a/org.eclipse.ui.genericeditor/icons/full/dtool16/mark_occurrences.png b/org.eclipse.ui.genericeditor/icons/full/dtool16/mark_occurrences.png Binary files differnew file mode 100644 index 00000000000..84d023606b2 --- /dev/null +++ b/org.eclipse.ui.genericeditor/icons/full/dtool16/mark_occurrences.png diff --git a/org.eclipse.ui.genericeditor/icons/full/dtool16/mark_occurrences@2x.png b/org.eclipse.ui.genericeditor/icons/full/dtool16/mark_occurrences@2x.png Binary files differnew file mode 100644 index 00000000000..11b09b05236 --- /dev/null +++ b/org.eclipse.ui.genericeditor/icons/full/dtool16/mark_occurrences@2x.png diff --git a/org.eclipse.ui.genericeditor/icons/full/etool16/mark_occurrences.png b/org.eclipse.ui.genericeditor/icons/full/etool16/mark_occurrences.png Binary files differnew file mode 100644 index 00000000000..8bb2361071f --- /dev/null +++ b/org.eclipse.ui.genericeditor/icons/full/etool16/mark_occurrences.png diff --git a/org.eclipse.ui.genericeditor/icons/full/etool16/mark_occurrences@2x.png b/org.eclipse.ui.genericeditor/icons/full/etool16/mark_occurrences@2x.png Binary files differnew file mode 100644 index 00000000000..ca74b2bb88e --- /dev/null +++ b/org.eclipse.ui.genericeditor/icons/full/etool16/mark_occurrences@2x.png diff --git a/org.eclipse.ui.genericeditor/plugin.properties b/org.eclipse.ui.genericeditor/plugin.properties index 7670e1e42b0..4905b4f6779 100644 --- a/org.eclipse.ui.genericeditor/plugin.properties +++ b/org.eclipse.ui.genericeditor/plugin.properties @@ -8,6 +8,7 @@ # Contributors: # Sopot Cela & Mickael Istria (Red Hat Inc.) - initial implementation # Lucas Bullen (Red Hat Inc.) - Bug 508829 custom reconciler support +# - Bug 521382 default highlighter ############################################################################### genericEditor_name=Generic Text Editor ExtPoint.reconciliers=Reconciliers @@ -15,10 +16,13 @@ ExtPoint.presentationReconciliers=Presentation Reconciliers ExtPoint.hoverProviders= Hover Providers ExtPoint.contentAssistProcessors=Content Assist Providers ExtPoint.autoEditStrategies=Auto Edit Strategies +ExtPoint.highlightReconcilers=Highlight Reconcilers openDeclarationCommand_name=Open Declaration context_name=in Generic Code Editor context_description=When editing in the Generic Code Editor findReferencesCommand_name=Find References findReferencesCommand_description=Find other code items referencing the current selected item. Bundle-Vendor=Eclipse.org -Bundle-Name=Generic and Extensible Text Editor
\ No newline at end of file +Bundle-Name=Generic and Extensible Text Editor +command.toggle.highlight.label = Toggle Mark Occurences +command.toggle.highlight.name = Toggle Highlight
\ No newline at end of file diff --git a/org.eclipse.ui.genericeditor/plugin.xml b/org.eclipse.ui.genericeditor/plugin.xml index 28e7a1dd96c..f7098c91743 100644 --- a/org.eclipse.ui.genericeditor/plugin.xml +++ b/org.eclipse.ui.genericeditor/plugin.xml @@ -10,6 +10,7 @@ <!-- Contributors: --> <!-- Sopot Cela & Mickael Istria (Red Hat Inc). -initial implementation --> <!-- Lucas Bullen (Red Hat Inc.) - Bug 508829 custom reconciler support --> +<!-- - Bug 521382 default highlighter --> <!-- ====================================================================== --> <plugin> <extension-point id="reconcilers" name="%ExtPoint.reconciliers" schema="schema/reconcilers.exsd"/> @@ -17,6 +18,7 @@ <extension-point id="contentAssistProcessors" name="%ExtPoint.contentAssistProcessors" schema="schema/contentAssistProcessors.exsd"/> <extension-point id="hoverProviders" name="%ExtPoint.hoverProviders" schema="schema/hoverProviders.exsd"/> <extension-point id="autoEditStrategies" name="%ExtPoint.autoEditStrategies" schema="schema/autoEditStrategies.exsd"/> + <extension-point id="highlightReconcilers" name="%ExtPoint.highlightReconcilers" schema="schema/highlightReconcilers.exsd"/> <extension point="org.eclipse.ui.editors"> <editor @@ -38,6 +40,14 @@ id="org.eclipse.ui.genericeditor.findReferences" name="%findReferencesCommand_name"> </command> + <command + id="org.eclipse.ui.genericeditor.togglehighlight" + name="%command.toggle.highlight.name"> + <state + class="org.eclipse.ui.handlers.RegistryToggleState:true" + id="org.eclipse.ui.commands.toggleState"> + </state> + </command> </extension> <extension point="org.eclipse.ui.contexts"> @@ -73,6 +83,23 @@ style="push"> </command> </menuContribution> + <menuContribution + allPopups="false" + locationURI="toolbar:org.eclipse.ui.genericeditor.GenericEditor"> + <command + commandId="org.eclipse.ui.genericeditor.togglehighlight" + label="%command.toggle.highlight.label" + style="toggle"> + </command> + </menuContribution> + </extension> + <extension + point="org.eclipse.ui.handlers"> + <handler + commandId="org.eclipse.ui.genericeditor.togglehighlight"> + <class + class="org.eclipse.ui.internal.genericeditor.ToggleHighlight"></class> + </handler> </extension> <extension point="org.eclipse.ui.genericeditor.hoverProviders"> @@ -90,5 +117,40 @@ id="org.eclipse.ui.genericeditor.annotationsHoverProvider"> </hoverProvider> </extension> - + <extension + point="org.eclipse.ui.commandImages"> + <image + commandId="org.eclipse.ui.genericeditor.togglehighlight" + disabledIcon="icons/full/dtool16/mark_occurrences.png" + icon="icons/full/etool16/mark_occurrences.png"> + </image> + </extension> + <extension point="org.eclipse.ui.editors.annotationTypes"> + <type name="org.eclipse.ui.genericeditor.text"></type> + </extension> + <extension point="org.eclipse.ui.editors.markerAnnotationSpecification"> + <specification annotationType="org.eclipse.ui.genericeditor.text" + label="Text Occurrence" + textPreferenceKey="TextOccurrenceIndication" textPreferenceValue="false" + highlightPreferenceKey="TextOccurrenceHighlighting" + highlightPreferenceValue="true" contributesToHeader="false" + overviewRulerPreferenceKey="TextOccurrenceIndicationInOverviewRuler" + overviewRulerPreferenceValue="true" + verticalRulerPreferenceKey="TextOccurrenceIndicationInVerticalRuler" + verticalRulerPreferenceValue="false" colorPreferenceKey="TextOccurrenceIndicationColor" + colorPreferenceValue="212,212,212" presentationLayer="4" + showInNextPrevDropdownToolbarAction="true" + textStylePreferenceKey="TextOccurrenceTextStyle" + textStylePreferenceValue="NONE"> + </specification> + </extension> + <extension + point="org.eclipse.e4.ui.css.swt.theme"> + <stylesheet + uri="resources/css/dark.css"> + <themeid + refid="org.eclipse.e4.ui.css.theme.e4_dark"> + </themeid> + </stylesheet> + </extension> </plugin> diff --git a/org.eclipse.ui.genericeditor/resources/css/dark.css b/org.eclipse.ui.genericeditor/resources/css/dark.css new file mode 100644 index 00000000000..34c46b58c9d --- /dev/null +++ b/org.eclipse.ui.genericeditor/resources/css/dark.css @@ -0,0 +1,16 @@ +/******************************************************************************* + * 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: + * Lucas Bullen (Red Hat Inc.) - initial implementation + *******************************************************************************/ + +IEclipsePreferences#org-eclipse-ui-editors:org-eclipse-ui-genericeditor { + preferences: + 'TextOccurrenceIndicationColor=27,98,145' +} + diff --git a/org.eclipse.ui.genericeditor/schema/highlightReconcilers.exsd b/org.eclipse.ui.genericeditor/schema/highlightReconcilers.exsd new file mode 100644 index 00000000000..25c6abf7249 --- /dev/null +++ b/org.eclipse.ui.genericeditor/schema/highlightReconcilers.exsd @@ -0,0 +1,116 @@ +<?xml version='1.0' encoding='UTF-8'?> +<!-- Schema file written by PDE --> +<schema targetNamespace="org.eclipse.ui.genericeditor" xmlns="http://www.w3.org/2001/XMLSchema"> +<annotation> + <appinfo> + <meta.schema plugin="org.eclipse.ui.genericeditor" id="highlightReconcilers" name="Highlight reconcilers"/> + </appinfo> + <documentation> + This extension point is used to contribute highlight reconcilers for controlling the highlighting on a file with a given content type. Reconcilers attached to this extension point that listen to the preference "org.eclipse.ui.genericeditor.togglehighlight" will be toggled with the "Toggle Mark Occurences" button. + </documentation> + </annotation> + + <include schemaLocation="schema://org.eclipse.core.expressions/schema/expressionLanguage.exsd"/> + + <element name="extension"> + <annotation> + <appinfo> + <meta.element /> + </appinfo> + </annotation> + <complexType> + <sequence minOccurs="1" maxOccurs="unbounded"> + <element ref="highlightReconciler"/> + </sequence> + <attribute name="point" type="string" use="required"> + <annotation> + <documentation> + a fully qualified identifier of the target extension point + </documentation> + </annotation> + </attribute> + <attribute name="id" type="string"> + <annotation> + <documentation> + an optional identifier of the extension instance + </documentation> + </annotation> + </attribute> + <attribute name="name" type="string"> + <annotation> + <documentation> + an optional name of the extension instance + </documentation> + <appinfo> + <meta.attribute translatable="true"/> + </appinfo> + </annotation> + </attribute> + </complexType> + </element> + + <element name="highlightReconciler"> + <complexType> + <attribute name="class" type="string" use="required"> + <annotation> + <documentation> + The fully qualified class name implementing the interface <code>org.eclipse.jface.text.reconciler.IReconciler</code> + </documentation> + <appinfo> + <meta.attribute kind="java" basedOn=":org.eclipse.jface.text.reconciler.IReconciler"/> + </appinfo> + </annotation> + </attribute> + <attribute name="contentType" type="string" use="required"> + <annotation> + <documentation> + The target content-type for this extension. Content-types are defined as extension to the org.eclipse.core.contenttype.contentTypes extension point. + </documentation> + <appinfo> + <meta.attribute kind="identifier" basedOn="org.eclipse.core.contenttype.contentTypes/content-type/@id"/> + </appinfo> + </annotation> + </attribute> + </complexType> + </element> + + <annotation> + <appinfo> + <meta.section type="since"/> + </appinfo> + <documentation> + 1.1 + </documentation> + </annotation> + + <annotation> + <appinfo> + <meta.section type="examples"/> + </appinfo> + <documentation> + Below is an example of how to use the Highlight Reconciler extension point: + +<pre> +<extension point="org.eclipse.ui.genericeditor.highlightReconcilers"> + <presentationReconciler + class="org.eclipse.ui.genericeditor.examples.TPHighlightReconcilers" + contentType="org.eclipse.pde.targetFile"> + </presentationReconciler> +</extension> +</pre> + </documentation> + </annotation> + + + + <annotation> + <appinfo> + <meta.section type="copyright"/> + </appinfo> + <documentation> + 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 <a href="http://www.eclipse.org/legal/epl-v10.html">http://www.eclipse.org/legal/epl-v10.html</a> + </documentation> + </annotation> + +</schema> diff --git a/org.eclipse.ui.genericeditor/src/org/eclipse/ui/internal/genericeditor/DefaultWordHighlightReconciler.java b/org.eclipse.ui.genericeditor/src/org/eclipse/ui/internal/genericeditor/DefaultWordHighlightReconciler.java new file mode 100644 index 00000000000..ecf027fc924 --- /dev/null +++ b/org.eclipse.ui.genericeditor/src/org/eclipse/ui/internal/genericeditor/DefaultWordHighlightReconciler.java @@ -0,0 +1,37 @@ +/******************************************************************************* + * 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: + * - Lucas Bullen (Red Hat Inc.) + *******************************************************************************/ +package org.eclipse.ui.internal.genericeditor; + +import org.eclipse.jface.text.IDocument; +import org.eclipse.jface.text.ITextViewer; +import org.eclipse.jface.text.reconciler.Reconciler; + +public class DefaultWordHighlightReconciler extends Reconciler { + + private DefaultWordHighlightStrategy fStrategy; + + public DefaultWordHighlightReconciler() { + fStrategy = new DefaultWordHighlightStrategy(); + this.setReconcilingStrategy(fStrategy, IDocument.DEFAULT_CONTENT_TYPE); + } + + @Override + public void install(ITextViewer textViewer) { + super.install(textViewer); + fStrategy.install(textViewer); + } + + @Override + public void uninstall() { + super.uninstall(); + fStrategy.uninstall(); + } +}
\ No newline at end of file diff --git a/org.eclipse.ui.genericeditor/src/org/eclipse/ui/internal/genericeditor/DefaultWordHighlightStrategy.java b/org.eclipse.ui.genericeditor/src/org/eclipse/ui/internal/genericeditor/DefaultWordHighlightStrategy.java new file mode 100644 index 00000000000..d1aacdfa889 --- /dev/null +++ b/org.eclipse.ui.genericeditor/src/org/eclipse/ui/internal/genericeditor/DefaultWordHighlightStrategy.java @@ -0,0 +1,225 @@ +/******************************************************************************* + * 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: + * - Lucas Bullen (Red Hat Inc.) + *******************************************************************************/ +package org.eclipse.ui.internal.genericeditor; + +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.Map.Entry; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.preferences.IEclipsePreferences; +import org.eclipse.core.runtime.preferences.IEclipsePreferences.IPreferenceChangeListener; +import org.eclipse.core.runtime.preferences.IEclipsePreferences.PreferenceChangeEvent; +import org.eclipse.core.runtime.preferences.InstanceScope; +import org.eclipse.jface.text.IDocument; +import org.eclipse.jface.text.IRegion; +import org.eclipse.jface.text.ISynchronizable; +import org.eclipse.jface.text.ITextViewer; +import org.eclipse.jface.text.ITextViewerExtension5; +import org.eclipse.jface.text.Position; +import org.eclipse.jface.text.reconciler.DirtyRegion; +import org.eclipse.jface.text.reconciler.IReconcilingStrategy; +import org.eclipse.jface.text.reconciler.IReconcilingStrategyExtension; +import org.eclipse.jface.text.source.Annotation; +import org.eclipse.jface.text.source.IAnnotationModel; +import org.eclipse.jface.text.source.IAnnotationModelExtension; +import org.eclipse.jface.text.source.ISourceViewer; +import org.eclipse.swt.custom.CaretEvent; +import org.eclipse.swt.custom.CaretListener; + +/** +* +* This Reconciler Strategy is a default stategy which will be present if +* no other highlightReconcilers are registered for a given content-type. It splits +* the text into 'words' (which are defined as anything in-between +* non-alphanumeric characters) and searches the document highlighting all like words. +* +* E.g. if your file contains "t^he dog in the bog" and you leave your caret at +* ^ you will get both instances of 'the' highlighted. +* +*/ +public class DefaultWordHighlightStrategy implements IReconcilingStrategy, IReconcilingStrategyExtension, CaretListener, IPreferenceChangeListener { + + private static final String ANNOTATION_TYPE = "org.eclipse.ui.genericeditor.text"; //$NON-NLS-1$ + + private boolean enabled; + private ISourceViewer sourceViewer; + private IDocument document; + + private static final String WORD_REGEXP = "[a-zA-Z]+"; //$NON-NLS-1$ + private static final Pattern WORD_PATTERN = Pattern.compile(WORD_REGEXP); + + private Annotation[] fOccurrenceAnnotations = null; + + private void applyHighlights(int offset) { + if (sourceViewer == null || !enabled) { + removeOccurrenceAnnotations(); + return; + } + + String text = document.get(); + offset = ((ITextViewerExtension5)sourceViewer).widgetOffset2ModelOffset(offset); + + int wordStartOffset = findStartingOffset(text, offset); + int wordEndOffset = findEndingOffset(text, offset); + if(wordEndOffset <= wordStartOffset || wordEndOffset == -1 || wordStartOffset == -1) { + removeOccurrenceAnnotations(); + return; + } + String word = text.substring(wordStartOffset, wordEndOffset); + + Matcher m = WORD_PATTERN.matcher(text); + Map<Annotation, Position> annotationMap = new HashMap<>(); + while(m.find()) { + if(m.group().equals(word)) { + annotationMap.put(new Annotation(ANNOTATION_TYPE, false, null), + new Position(m.start(), m.end() - m.start())); + } + } + + if(annotationMap.size() < 2) { + removeOccurrenceAnnotations(); + return; + } + + IAnnotationModel annotationModel = sourceViewer.getAnnotationModel(); + synchronized (getLockObject(annotationModel)) { + if (annotationModel instanceof IAnnotationModelExtension) { + ((IAnnotationModelExtension) annotationModel).replaceAnnotations(fOccurrenceAnnotations, annotationMap); + } else { + removeOccurrenceAnnotations(); + Iterator<Entry<Annotation, Position>> iter = annotationMap.entrySet().iterator(); + while (iter.hasNext()) { + Entry<Annotation, Position> mapEntry = iter.next(); + annotationModel.addAnnotation(mapEntry.getKey(), mapEntry.getValue()); + } + } + fOccurrenceAnnotations = annotationMap.keySet().toArray(new Annotation[annotationMap.keySet().size()]); + } + } + + private static int findStartingOffset(String text, int offset) { + final Pattern NON_ALPHANUMERIC_LAST_PATTERN = Pattern.compile("[^\\w](?!.*[^\\w])"); //$NON-NLS-1$ + String substring = text.substring(0, offset); + Matcher m = NON_ALPHANUMERIC_LAST_PATTERN.matcher(substring); + if(m.find()) { + return m.end(); + } + return -1; + } + + private static int findEndingOffset(String text, int offset) { + String substring = text.substring(offset); + String[] split = substring.split("[^a-zA-Z0-9]+");//$NON-NLS-1$ + if(split.length == 0) { + return -1; + } + return offset + split[0].length(); + } + + public void install(ITextViewer viewer) { + if (!(viewer instanceof ISourceViewer)) { + return; + } + IEclipsePreferences preferences = InstanceScope.INSTANCE.getNode(GenericEditorPlugin.BUNDLE_ID); + preferences.addPreferenceChangeListener(this); + this.enabled = preferences.getBoolean(ToggleHighlight.TOGGLE_HIGHLIGHT_PREFERENCE, true); + this.sourceViewer = (ISourceViewer) viewer; + this.sourceViewer.getTextWidget().addCaretListener(this); + } + + public void uninstall() { + if (sourceViewer != null) { + sourceViewer.getTextWidget().removeCaretListener(this); + } + IEclipsePreferences preferences = InstanceScope.INSTANCE.getNode(GenericEditorPlugin.BUNDLE_ID); + preferences.removePreferenceChangeListener(this); + } + + @Override + public void preferenceChange(PreferenceChangeEvent event) { + if (event.getKey().equals(ToggleHighlight.TOGGLE_HIGHLIGHT_PREFERENCE)) { + this.enabled = Boolean.parseBoolean(event.getNewValue().toString()); + if (enabled) { + initialReconcile(); + } else { + removeOccurrenceAnnotations(); + } + } + } + + @Override + public void caretMoved(CaretEvent event) { + applyHighlights(event.caretOffset); + } + + @Override + public void initialReconcile() { + if (sourceViewer != null) { + sourceViewer.getTextWidget().getDisplay().asyncExec(() -> { + if (sourceViewer != null) { + applyHighlights(sourceViewer.getTextWidget().getCaretOffset()); + } + }); + } + } + + void removeOccurrenceAnnotations() { + IAnnotationModel annotationModel = sourceViewer.getAnnotationModel(); + if (annotationModel == null || fOccurrenceAnnotations == null) { + return; + } + + synchronized (getLockObject(annotationModel)) { + if (annotationModel instanceof IAnnotationModelExtension) { + ((IAnnotationModelExtension) annotationModel).replaceAnnotations(fOccurrenceAnnotations, null); + } else { + for (Annotation fOccurrenceAnnotation : fOccurrenceAnnotations) { + annotationModel.removeAnnotation(fOccurrenceAnnotation); + } + } + fOccurrenceAnnotations = null; + } + } + + private static Object getLockObject(IAnnotationModel annotationModel) { + if (annotationModel instanceof ISynchronizable) { + Object lock = ((ISynchronizable) annotationModel).getLockObject(); + if (lock != null) { + return lock; + } + } + return annotationModel; + } + + @Override + public void setDocument(IDocument document) { + this.document = document; + } + + @Override + public void reconcile(DirtyRegion dirtyRegion, IRegion subRegion) { + // Do nothing + } + + @Override + public void reconcile(IRegion partition) { + // Do nothing + } + + @Override + public void setProgressMonitor(IProgressMonitor monitor) { + // Not used + } +} 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 4e0461fe0b1..be79f1f6f55 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 @@ -8,6 +8,7 @@ * Contributors: * Sopot Cela, Mickael Istria (Red Hat Inc.) - initial implementation * Lucas Bullen (Red Hat Inc.) - Bug 508829 custom reconciler support + * - Bug 521382 default highlight reconciler *******************************************************************************/ package org.eclipse.ui.internal.genericeditor; @@ -27,7 +28,6 @@ import org.eclipse.jface.text.IAutoEditStrategy; import org.eclipse.jface.text.IDocument; import org.eclipse.jface.text.IDocumentPartitioningListener; import org.eclipse.jface.text.IInformationControl; -import org.eclipse.jface.text.IInformationControlCreator; import org.eclipse.jface.text.ITextHover; import org.eclipse.jface.text.contentassist.ContentAssistant; import org.eclipse.jface.text.contentassist.IContentAssistProcessor; @@ -39,7 +39,6 @@ import org.eclipse.jface.text.reconciler.IReconciler; import org.eclipse.jface.text.source.ISourceViewer; import org.eclipse.swt.widgets.Shell; 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; @@ -72,12 +71,9 @@ public final class ExtensionBasedTextViewerConfiguration extends TextSourceViewe public ExtensionBasedTextViewerConfiguration(ITextEditor editor, IPreferenceStore preferenceStore) { super(preferenceStore); this.editor = editor; - this.editor.addPropertyListener(new IPropertyListener() { - @Override - public void propertyChanged(Object source, int propId) { - if (propId == IEditorPart.PROP_INPUT) { - watchDocument(editor.getDocumentProvider().getDocument(editor.getEditorInput())); - } + this.editor.addPropertyListener((source, propId) -> { + if (propId == IEditorPart.PROP_INPUT) { + watchDocument(editor.getDocumentProvider().getDocument(editor.getEditorInput())); } }); } @@ -185,12 +181,7 @@ public final class ExtensionBasedTextViewerConfiguration extends TextSourceViewe )); quickAssistAssistant.setQuickAssistProcessor(processor); quickAssistAssistant.setRestoreCompletionProposalSize(EditorsPlugin.getDefault().getDialogSettingsSection("quick_assist_proposal_size")); //$NON-NLS-1$ - quickAssistAssistant.setInformationControlCreator(new IInformationControlCreator() { - @Override - public IInformationControl createInformationControl(Shell parent) { - return new DefaultInformationControl(parent, EditorsPlugin.getAdditionalInfoAffordanceString()); - } - }); + quickAssistAssistant.setInformationControlCreator(parent -> new DefaultInformationControl(parent, EditorsPlugin.getAdditionalInfoAffordanceString())); return quickAssistAssistant; } @@ -198,12 +189,20 @@ public final class ExtensionBasedTextViewerConfiguration extends TextSourceViewe public IReconciler getReconciler(ISourceViewer sourceViewer) { ReconcilerRegistry registry = GenericEditorPlugin.getDefault().getReconcilerRegistry(); List<IReconciler> reconciliers = registry.getReconcilers(sourceViewer, getContentTypes()); + List<IReconciler> highlightReconciliers = registry.getHighlightReconcilers(sourceViewer, getContentTypes()); + + if(!highlightReconciliers.isEmpty()) { + reconciliers.addAll(highlightReconciliers); + }else { + reconciliers.add(new DefaultWordHighlightReconciler()); + } + if (!reconciliers.isEmpty()) { return new CompositeReconciler(reconciliers); } return null; } - + @Override public IAutoEditStrategy[] getAutoEditStrategies(ISourceViewer sourceViewer, String contentType) { AutoEditStrategyRegistry registry = GenericEditorPlugin.getDefault().getAutoEditStrategyRegistry(); @@ -213,5 +212,5 @@ public final class ExtensionBasedTextViewerConfiguration extends TextSourceViewe } return super.getAutoEditStrategies(sourceViewer, contentType); } - + } diff --git a/org.eclipse.ui.genericeditor/src/org/eclipse/ui/internal/genericeditor/ReconcilerRegistry.java b/org.eclipse.ui.genericeditor/src/org/eclipse/ui/internal/genericeditor/ReconcilerRegistry.java index 5d52e3cfd65..b31ecadbd17 100644 --- a/org.eclipse.ui.genericeditor/src/org/eclipse/ui/internal/genericeditor/ReconcilerRegistry.java +++ b/org.eclipse.ui.genericeditor/src/org/eclipse/ui/internal/genericeditor/ReconcilerRegistry.java @@ -18,8 +18,6 @@ import java.util.Set; import java.util.stream.Collectors; 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; @@ -28,33 +26,38 @@ import org.eclipse.jface.text.reconciler.IReconciler; import org.eclipse.jface.text.source.ISourceViewer; /** - * A registry of reconciliers provided by extension <code>org.eclipse.ui.genericeditor.reconcilers</code>. + * A registry of reconciliers provided by extensions <code>org.eclipse.ui.genericeditor.reconcilers</code> + * and <code>org.eclipse.ui.genericeditor.foldingReconcilers</code>. * Those extensions are specific to a given {@link IContentType}. - * + * * @since 1.1 */ public class ReconcilerRegistry { private static final String EXTENSION_POINT_ID = GenericEditorPlugin.BUNDLE_ID + ".reconcilers"; //$NON-NLS-1$ + private static final String HIGHLIGHT_EXTENSION_POINT_ID = GenericEditorPlugin.BUNDLE_ID + ".highlightReconcilers"; //$NON-NLS-1$ private Map<IConfigurationElement, GenericContentTypeRelatedExtension<IReconciler>> extensions = new HashMap<>(); + private Map<IConfigurationElement, GenericContentTypeRelatedExtension<IReconciler>> highlightExtensions = new HashMap<>(); private boolean outOfSync = true; + private boolean highlightOutOfSync = true; /** * Creates the registry and binds it to the extension point. */ public ReconcilerRegistry() { - Platform.getExtensionRegistry().addRegistryChangeListener(new IRegistryChangeListener() { - @Override - public void registryChanged(IRegistryChangeEvent event) { - outOfSync = true; - } + Platform.getExtensionRegistry().addRegistryChangeListener(event -> { + outOfSync = true; }, EXTENSION_POINT_ID); + + Platform.getExtensionRegistry().addRegistryChangeListener(event -> { + highlightOutOfSync = true; + }, HIGHLIGHT_EXTENSION_POINT_ID); } /** * Get the contributed {@link IReconciliers}s that are relevant to hook on source viewer according - * to document content types. + * 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 IReconciler} contributed for at least one of the content types, @@ -72,6 +75,26 @@ public class ReconcilerRegistry { return reconcilers; } + /** + * Get the contributed highlight {@link IReconciliers}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 highlight {@link IReconciler}s contributed for at least one of the content types, + * sorted by most generic content type to most specific. + */ + public List<IReconciler> getHighlightReconcilers(ISourceViewer sourceViewer, Set<IContentType> contentTypes) { + if (this.highlightOutOfSync) { + syncHighlight(); + } + List<IReconciler> highlightReconcilers = this.highlightExtensions.values().stream() + .filter(ext -> contentTypes.contains(ext.targetContentType)) + .sorted(new ContentTypeSpecializationComparator<IReconciler>().reversed()) + .map(GenericContentTypeRelatedExtension<IReconciler>::createDelegate) + .collect(Collectors.toList()); + return highlightReconcilers; + } + private void sync() { Set<IConfigurationElement> toRemoveExtensions = new HashSet<>(this.extensions.keySet()); for (IConfigurationElement extension : Platform.getExtensionRegistry().getConfigurationElementsFor(EXTENSION_POINT_ID)) { @@ -90,4 +113,22 @@ public class ReconcilerRegistry { this.outOfSync = false; } + private void syncHighlight() { + Set<IConfigurationElement> toRemoveExtensions = new HashSet<>(this.extensions.keySet()); + for (IConfigurationElement extension : Platform.getExtensionRegistry().getConfigurationElementsFor(HIGHLIGHT_EXTENSION_POINT_ID)) { + toRemoveExtensions.remove(extension); + if (!this.highlightExtensions.containsKey(extension)) { + try { + this.highlightExtensions.put(extension, new GenericContentTypeRelatedExtension<IReconciler>(extension)); + } catch (Exception ex) { + GenericEditorPlugin.getDefault().getLog().log(new Status(IStatus.ERROR, GenericEditorPlugin.BUNDLE_ID, ex.getMessage(), ex)); + } + } + } + for (IConfigurationElement toRemove : toRemoveExtensions) { + this.highlightExtensions.remove(toRemove); + } + this.highlightOutOfSync = false; + } + } diff --git a/org.eclipse.ui.genericeditor/src/org/eclipse/ui/internal/genericeditor/ToggleHighlight.java b/org.eclipse.ui.genericeditor/src/org/eclipse/ui/internal/genericeditor/ToggleHighlight.java new file mode 100644 index 00000000000..9e9cc8e77dc --- /dev/null +++ b/org.eclipse.ui.genericeditor/src/org/eclipse/ui/internal/genericeditor/ToggleHighlight.java @@ -0,0 +1,34 @@ +/******************************************************************************* + * 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: + * Lucas Bullen (Red Hat Inc.) - initial implementation + *******************************************************************************/ +package org.eclipse.ui.internal.genericeditor; + +import org.eclipse.core.commands.AbstractHandler; +import org.eclipse.core.commands.Command; +import org.eclipse.core.commands.ExecutionEvent; +import org.eclipse.core.commands.ExecutionException; +import org.eclipse.core.runtime.preferences.IEclipsePreferences; +import org.eclipse.core.runtime.preferences.InstanceScope; +import org.eclipse.ui.handlers.HandlerUtil; + +public class ToggleHighlight extends AbstractHandler { + + public static final String TOGGLE_HIGHLIGHT_PREFERENCE = "org.eclipse.ui.genericeditor.togglehighlight"; //$NON-NLS-1$ + + @Override + public Object execute(ExecutionEvent event) throws ExecutionException { + Command command = event.getCommand(); + boolean oldValue = HandlerUtil.toggleCommandState(command); + + IEclipsePreferences preferences = InstanceScope.INSTANCE.getNode(GenericEditorPlugin.BUNDLE_ID); + preferences.putBoolean(TOGGLE_HIGHLIGHT_PREFERENCE, !oldValue); + return null; + } +} |