Skip to main content
aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMatthias Sohn2015-09-20 19:13:28 -0400
committerGerrit Code Review @ Eclipse.org2015-09-20 19:13:30 -0400
commit96beea9ac75b1e732213bd55e2638d155fe272f8 (patch)
tree55583d5d99725441091dfe136b5a98ed4e051ed2
parent122267c0af9a61d9ecd9d498133debd7c26d0da5 (diff)
parent0c7f0492b88a5ab38de368e691ba00107a6fa137 (diff)
downloadegit-96beea9ac75b1e732213bd55e2638d155fe272f8.tar.gz
egit-96beea9ac75b1e732213bd55e2638d155fe272f8.tar.xz
egit-96beea9ac75b1e732213bd55e2638d155fe272f8.zip
Merge "Obey hyperlink preferences in SpellcheckableMessageArea"
-rw-r--r--org.eclipse.egit.ui.test/src/org/eclipse/egit/ui/internal/dialogs/HyperlinkTokenScannerTest.java21
-rw-r--r--org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/dialogs/HyperlinkSourceViewer.java425
-rw-r--r--org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/dialogs/HyperlinkTokenScanner.java109
-rw-r--r--org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/dialogs/SpellcheckableMessageArea.java30
-rw-r--r--org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/history/CommitMessageViewer.java13
-rw-r--r--org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/history/GitHistoryPage.java41
6 files changed, 570 insertions, 69 deletions
diff --git a/org.eclipse.egit.ui.test/src/org/eclipse/egit/ui/internal/dialogs/HyperlinkTokenScannerTest.java b/org.eclipse.egit.ui.test/src/org/eclipse/egit/ui/internal/dialogs/HyperlinkTokenScannerTest.java
index 51e49cf31..2b1eece69 100644
--- a/org.eclipse.egit.ui.test/src/org/eclipse/egit/ui/internal/dialogs/HyperlinkTokenScannerTest.java
+++ b/org.eclipse.egit.ui.test/src/org/eclipse/egit/ui/internal/dialogs/HyperlinkTokenScannerTest.java
@@ -13,6 +13,7 @@ import static org.mockito.Mockito.when;
import java.util.Arrays;
+import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.jface.text.Document;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.TextAttribute;
@@ -20,6 +21,8 @@ import org.eclipse.jface.text.hyperlink.IHyperlinkDetector;
import org.eclipse.jface.text.hyperlink.URLHyperlinkDetector;
import org.eclipse.jface.text.rules.IToken;
import org.eclipse.jface.text.source.ISourceViewer;
+import org.eclipse.jface.text.source.SourceViewerConfiguration;
+import org.eclipse.ui.texteditor.AbstractTextEditor;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
@@ -31,6 +34,12 @@ public class HyperlinkTokenScannerTest {
@Mock
private ISourceViewer viewer;
+ @Mock
+ private SourceViewerConfiguration configuration;
+
+ @Mock
+ private IPreferenceStore preferenceStore;
+
private IHyperlinkDetector[] detectors = new IHyperlinkDetector[] {
new URLHyperlinkDetector() };
@@ -94,14 +103,22 @@ public class HyperlinkTokenScannerTest {
assertTokens(testString, 15, 14, expected);
}
+ @SuppressWarnings("boxing")
private void assertTokens(String text, int offset, int length,
String expected) {
assertEquals("Test definition problem: 'expected' length mismatch",
text.length(), expected.length());
IDocument testDocument = new Document(text);
when(viewer.getDocument()).thenReturn(testDocument);
- HyperlinkTokenScanner scanner = new HyperlinkTokenScanner(detectors,
- viewer, null);
+ when(configuration.getHyperlinkDetectors(viewer)).thenReturn(detectors);
+ when(preferenceStore
+ .getBoolean(AbstractTextEditor.PREFERENCE_HYPERLINKS_ENABLED))
+ .thenReturn(true);
+ when(preferenceStore.getBoolean(
+ "org.eclipse.ui.internal.editors.text.URLHyperlinkDetector"))
+ .thenReturn(false);
+ HyperlinkTokenScanner scanner = new HyperlinkTokenScanner(configuration,
+ viewer, preferenceStore, null);
scanner.setRangeAndColor(testDocument, offset, length, null);
IToken token = null;
char[] found = new char[text.length()];
diff --git a/org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/dialogs/HyperlinkSourceViewer.java b/org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/dialogs/HyperlinkSourceViewer.java
new file mode 100644
index 000000000..6b3d74627
--- /dev/null
+++ b/org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/dialogs/HyperlinkSourceViewer.java
@@ -0,0 +1,425 @@
+/*******************************************************************************
+ * Copyright (C) 2015 Thomas Wolf <thomas.wolf@paranor.ch>.
+ *
+ * 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
+ *******************************************************************************/
+package org.eclipse.egit.ui.internal.dialogs;
+
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.StringTokenizer;
+
+import org.eclipse.jdt.annotation.Nullable;
+import org.eclipse.jface.action.Action;
+import org.eclipse.jface.preference.IPreferenceStore;
+import org.eclipse.jface.text.IRegion;
+import org.eclipse.jface.text.ITextViewer;
+import org.eclipse.jface.text.hyperlink.IHyperlink;
+import org.eclipse.jface.text.hyperlink.IHyperlinkDetector;
+import org.eclipse.jface.text.hyperlink.IHyperlinkDetectorExtension;
+import org.eclipse.jface.text.hyperlink.IHyperlinkDetectorExtension2;
+import org.eclipse.jface.text.source.IOverviewRuler;
+import org.eclipse.jface.text.source.ISourceViewer;
+import org.eclipse.jface.text.source.IVerticalRuler;
+import org.eclipse.jface.text.source.SourceViewer;
+import org.eclipse.jface.text.source.SourceViewerConfiguration;
+import org.eclipse.jface.util.IPropertyChangeListener;
+import org.eclipse.jface.util.PropertyChangeEvent;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.ui.editors.text.EditorsUI;
+import org.eclipse.ui.editors.text.TextSourceViewerConfiguration;
+import org.eclipse.ui.texteditor.AbstractTextEditor;
+import org.eclipse.ui.texteditor.HyperlinkDetectorDescriptor;
+
+/**
+ * A {@link SourceViewer} that automatically reacts to changes in the
+ * hyperlinking preferences.
+ */
+public class HyperlinkSourceViewer extends SourceViewer {
+ // The default SourceViewer doesn't do this and instead AbstractTextEditor
+ // has code that does all that. For our uses,it is much more convenient if
+ // the viewer itself handles this.
+
+ private Configuration configuration;
+
+ private Set<String> preferenceKeysForEnablement;
+
+ private Set<String> preferenceKeysForActivation;
+
+ private IPropertyChangeListener hyperlinkChangeListener;
+
+ /**
+ * Constructs a new source viewer. The vertical ruler is initially visible.
+ * The viewer has not yet been initialized with a source viewer
+ * configuration.
+ *
+ * @param parent
+ * the parent of the viewer's control
+ * @param ruler
+ * the vertical ruler used by this source viewer
+ * @param styles
+ * the SWT style bits for the viewer's control,
+ */
+ public HyperlinkSourceViewer(Composite parent, IVerticalRuler ruler,
+ int styles) {
+ super(parent, ruler, styles);
+ }
+
+ /**
+ * Constructs a new source viewer. The vertical ruler is initially visible.
+ * The overview ruler visibility is controlled by the value of
+ * <code>showAnnotationsOverview</code>. The viewer has not yet been
+ * initialized with a source viewer configuration.
+ *
+ * @param parent
+ * the parent of the viewer's control
+ * @param verticalRuler
+ * the vertical ruler used by this source viewer
+ * @param overviewRuler
+ * the overview ruler
+ * @param showAnnotationsOverview
+ * {@code true} if the overview ruler should be visible,
+ * {@code false} otherwise
+ * @param styles
+ * the SWT style bits for the viewer's control,
+ */
+ public HyperlinkSourceViewer(Composite parent, IVerticalRuler verticalRuler,
+ IOverviewRuler overviewRuler, boolean showAnnotationsOverview,
+ int styles) {
+ super(parent, verticalRuler, overviewRuler, showAnnotationsOverview,
+ styles);
+ }
+
+ @Override
+ public void configure(SourceViewerConfiguration config) {
+ super.configure(config);
+ if (config instanceof Configuration) {
+ configuration = (Configuration) config;
+ configurePreferenceKeys();
+ // Install a listener
+ hyperlinkChangeListener = new IPropertyChangeListener() {
+ @Override
+ public void propertyChange(PropertyChangeEvent event) {
+ String property = event.getProperty();
+ if (preferenceKeysForEnablement.contains(property)) {
+ resetHyperlinkDetectors();
+ final Control control = getControl();
+ if (control != null && !control.isDisposed()) {
+ control.getDisplay().asyncExec(new Runnable() {
+ @Override
+ public void run() {
+ if (!control.isDisposed()) {
+ refresh();
+ }
+ }
+ });
+ }
+ } else if (preferenceKeysForActivation.contains(property)) {
+ resetHyperlinkDetectors();
+ }
+ }
+ };
+ EditorsUI.getPreferenceStore()
+ .addPropertyChangeListener(hyperlinkChangeListener);
+ } else {
+ configuration = null;
+ hyperlinkChangeListener = null;
+ }
+ }
+
+ private void configurePreferenceKeys() {
+ preferenceKeysForEnablement = new HashSet<>();
+ preferenceKeysForActivation = new HashSet<>();
+ // Global settings (master switch)
+ preferenceKeysForEnablement
+ .add(AbstractTextEditor.PREFERENCE_HYPERLINKS_ENABLED);
+ preferenceKeysForActivation
+ .add(AbstractTextEditor.PREFERENCE_HYPERLINK_KEY_MODIFIER);
+ // All applicable individual hyperlink detectors settings.
+ Map targets = configuration.getHyperlinkDetectorTargets(this);
+ for (HyperlinkDetectorDescriptor desc : EditorsUI
+ .getHyperlinkDetectorRegistry()
+ .getHyperlinkDetectorDescriptors()) {
+ if (targets.keySet().contains(desc.getTargetId())) {
+ preferenceKeysForEnablement.add(desc.getId());
+ preferenceKeysForActivation.add(desc.getId()
+ + HyperlinkDetectorDescriptor.STATE_MASK_POSTFIX);
+ }
+ }
+ }
+
+ private void resetHyperlinkDetectors() {
+ IHyperlinkDetector[] detectors = configuration
+ .getHyperlinkDetectors(this);
+ int stateMask = configuration.getHyperlinkStateMask(this);
+ setHyperlinkDetectors(detectors, stateMask);
+ }
+
+ @Override
+ public void unconfigure() {
+ super.unconfigure();
+ if (hyperlinkChangeListener != null) {
+ EditorsUI.getPreferenceStore()
+ .removePropertyChangeListener(hyperlinkChangeListener);
+ }
+ preferenceKeysForEnablement = null;
+ preferenceKeysForActivation = null;
+ }
+
+ /**
+ * A {@link TextSourceViewerConfiguration} for use in conjunction with
+ * {@link HyperlinkSourceViewer}. Includes support for opening hyperlinks on
+ * the configured modifier key, or also when no key is pressed (i.e., on
+ * {@link SWT#NONE}) if
+ * {@link TextSourceViewerConfiguration#getHyperlinkStateMask(ISourceViewer)
+ * getHyperlinkStateMask(ISourceViewer)} returns {@link SWT#NONE}
+ */
+ public static class Configuration extends TextSourceViewerConfiguration {
+
+ /**
+ * Creates a new configuration.
+ */
+ public Configuration() {
+ super();
+ }
+
+ /**
+ * Creates a new configuration and initializes it with the given
+ * preference store.
+ *
+ * @param preferenceStore
+ * the preference store used to initialize this configuration
+ */
+ public Configuration(IPreferenceStore preferenceStore) {
+ super(preferenceStore);
+ }
+
+ /**
+ * Returns the hyperlink detectors which are to be used to detect
+ * hyperlinks in the given source viewer. This implementation uses
+ * {@link #internalGetHyperlinkDetectors(ISourceViewer)} to get the
+ * hyperlink detectors.
+ * <p>
+ * Sets up the hyperlink detetctors such that they are active on both
+ * {@link SWT#NONE} and on the configured modifier key combination if
+ * the viewer is configured to open hyperlinks on direct click, i.e., if
+ * {@link TextSourceViewerConfiguration#getHyperlinkStateMask(ISourceViewer)
+ * getHyperlinkStateMask(ISourceViewer)} returns {@link SWT#NONE}.
+ * </p>
+ *
+ * @param sourceViewer
+ * the {@link ISourceViewer} to be configured by this
+ * configuration
+ * @return an array with {@link IHyperlinkDetector}s or {@code null} if
+ * no hyperlink support should be installed
+ */
+ @Override
+ public final IHyperlinkDetector[] getHyperlinkDetectors(
+ ISourceViewer sourceViewer) {
+ IHyperlinkDetector[] detectors = internalGetHyperlinkDetectors(
+ sourceViewer);
+ if (detectors != null && detectors.length > 0
+ && getHyperlinkStateMask(sourceViewer) == SWT.NONE) {
+ // Duplicate them all with a detector that is active on SWT.NONE
+ int defaultMask = getConfiguredDefaultMask();
+ IHyperlinkDetector[] newDetectors = new IHyperlinkDetector[detectors.length
+ * 2];
+ int j = 0;
+ for (IHyperlinkDetector original : detectors) {
+ if (original instanceof IHyperlinkDetectorExtension2) {
+ int mask = ((IHyperlinkDetectorExtension2) original)
+ .getStateMask();
+ if (mask == -1) {
+ newDetectors[j++] = new FixedMaskHyperlinkDetector(
+ original, defaultMask);
+ if (defaultMask != SWT.NONE) {
+ newDetectors[j++] = new NoMaskHyperlinkDetector(
+ original);
+ }
+ } else {
+ newDetectors[j++] = original;
+ if (mask != SWT.NONE) {
+ newDetectors[j++] = new NoMaskHyperlinkDetector(
+ original);
+ }
+ }
+ } else {
+ newDetectors[j++] = original;
+ }
+ }
+ IHyperlinkDetector[] result = new IHyperlinkDetector[j];
+ System.arraycopy(newDetectors, 0, result, 0, j);
+ return result;
+ }
+ return detectors;
+ }
+
+ /**
+ * Collects the {@link IHyperlinkDetector}s for the given
+ * {@link ISourceViewer}.
+ *
+ * @param sourceViewer
+ * to get the detectors for
+ * @return the hyperlink detectors, or {@code null} if none shall be
+ * installed
+ * @see TextSourceViewerConfiguration#getHyperlinkDetectors(ISourceViewer)
+ */
+ protected @Nullable IHyperlinkDetector[] internalGetHyperlinkDetectors(
+ ISourceViewer sourceViewer) {
+ return super.getHyperlinkDetectors(sourceViewer);
+ }
+
+ @Override
+ protected Map getHyperlinkDetectorTargets(ISourceViewer sourceViewer) {
+ // Just so that we have visibility on this in the enclosing class.
+ return super.getHyperlinkDetectorTargets(sourceViewer);
+ }
+
+ }
+
+ private static abstract class InternalHyperlinkDetector
+ implements IHyperlinkDetector, IHyperlinkDetectorExtension,
+ IHyperlinkDetectorExtension2 {
+
+ protected IHyperlinkDetector delegate;
+
+ protected InternalHyperlinkDetector(IHyperlinkDetector delegate) {
+ this.delegate = delegate;
+ }
+
+ @Override
+ public final IHyperlink[] detectHyperlinks(ITextViewer textViewer,
+ IRegion region, boolean canShowMultipleHyperlinks) {
+ return delegate.detectHyperlinks(textViewer, region,
+ canShowMultipleHyperlinks);
+ }
+
+ @Override
+ public final void dispose() {
+ if (delegate instanceof IHyperlinkDetectorExtension) {
+ ((IHyperlinkDetectorExtension) delegate).dispose();
+ }
+ }
+ }
+
+ private static class FixedMaskHyperlinkDetector
+ extends InternalHyperlinkDetector {
+
+ private final int mask;
+
+ protected FixedMaskHyperlinkDetector(IHyperlinkDetector delegate,
+ int mask) {
+ super(delegate);
+ this.mask = mask;
+ }
+
+ @Override
+ public int getStateMask() {
+ return mask;
+ }
+ }
+
+ /**
+ * An {@link IHyperlinkDetector} that activates when no modifier key is
+ * pressed. It's protected so that the {@link HyperlinkTokenScanner} can see
+ * it and can avoid calling those for syntax coloring since they are
+ * duplicates of some other hyperlink detector anyway.
+ */
+ protected static class NoMaskHyperlinkDetector
+ extends FixedMaskHyperlinkDetector {
+
+ private NoMaskHyperlinkDetector(IHyperlinkDetector delegate) {
+ // Private to allow instantiation only within this compilation unit
+ super(delegate, SWT.NONE);
+ }
+
+ }
+
+ private static int getConfiguredDefaultMask() {
+ int mask = computeStateMask(EditorsUI.getPreferenceStore().getString(
+ AbstractTextEditor.PREFERENCE_HYPERLINK_KEY_MODIFIER));
+ if (mask == -1) {
+ // Fallback
+ mask = EditorsUI.getPreferenceStore().getInt(
+ AbstractTextEditor.PREFERENCE_HYPERLINK_KEY_MODIFIER_MASK);
+ }
+ return mask;
+ }
+
+ // The preference for
+ // AbstractTextEditor.PREFERENCE_HYPERLINK_KEY_MODIFIER_MASK is not
+ // recomputed when the user changes preferences. Therefore, we have to
+ // use the AbstractTextEditor.PREFERENCE_HYPERLINK_KEY_MODIFIER and
+ // translate that to a state mask explicitly. Code below copied from
+ // org.eclipse.ui.internal.editors.text.HyperlinkDetectorsConfigurationBlock.
+
+ /**
+ * Maps the localized modifier name to a code in the same manner as
+ * {@link org.eclipse.jface.action.Action#findModifier
+ * Action.findModifier()}.
+ *
+ * @param modifierName
+ * the modifier name
+ * @return the SWT modifier bit, or {@code 0} if no match was found
+ */
+ private static final int findLocalizedModifier(String modifierName) {
+ if (modifierName == null) {
+ return 0;
+ }
+
+ if (modifierName
+ .equalsIgnoreCase(Action.findModifierString(SWT.CTRL))) {
+ return SWT.CTRL;
+ }
+ if (modifierName
+ .equalsIgnoreCase(Action.findModifierString(SWT.SHIFT))) {
+ return SWT.SHIFT;
+ }
+ if (modifierName.equalsIgnoreCase(Action.findModifierString(SWT.ALT))) {
+ return SWT.ALT;
+ }
+ if (modifierName
+ .equalsIgnoreCase(Action.findModifierString(SWT.COMMAND))) {
+ return SWT.COMMAND;
+ }
+
+ return 0;
+ }
+
+ /**
+ * Computes the state mask for the given modifier string.
+ *
+ * @param modifiers
+ * the string with the modifiers, separated by '+', '-', ';', ','
+ * or '.'
+ * @return the state mask or {@code -1} if the input is invalid
+ */
+ private static final int computeStateMask(String modifiers) {
+ if (modifiers == null) {
+ return -1;
+ }
+
+ if (modifiers.length() == 0) {
+ return SWT.NONE;
+ }
+
+ int stateMask = 0;
+ StringTokenizer modifierTokenizer = new StringTokenizer(modifiers,
+ ",;.:+-* "); //$NON-NLS-1$
+ while (modifierTokenizer.hasMoreTokens()) {
+ int modifier = findLocalizedModifier(modifierTokenizer.nextToken());
+ if (modifier == 0 || (stateMask & modifier) == modifier) {
+ return -1;
+ }
+ stateMask = stateMask | modifier;
+ }
+ return stateMask;
+ }
+
+}
diff --git a/org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/dialogs/HyperlinkTokenScanner.java b/org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/dialogs/HyperlinkTokenScanner.java
index e18d8c632..67738d61b 100644
--- a/org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/dialogs/HyperlinkTokenScanner.java
+++ b/org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/dialogs/HyperlinkTokenScanner.java
@@ -17,6 +17,7 @@ import java.util.List;
import org.eclipse.core.runtime.Assert;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.Nullable;
+import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.jface.resource.JFaceColors;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.IDocument;
@@ -31,7 +32,10 @@ import org.eclipse.jface.text.rules.IToken;
import org.eclipse.jface.text.rules.ITokenScanner;
import org.eclipse.jface.text.rules.Token;
import org.eclipse.jface.text.source.ISourceViewer;
+import org.eclipse.jface.text.source.SourceViewerConfiguration;
import org.eclipse.swt.graphics.Color;
+import org.eclipse.ui.editors.text.EditorsUI;
+import org.eclipse.ui.texteditor.AbstractTextEditor;
/**
* A simple {@link ITokenScanner} that recognizes hyperlinks using
@@ -39,15 +43,24 @@ import org.eclipse.swt.graphics.Color;
*/
public class HyperlinkTokenScanner implements ITokenScanner {
+ private static final String URL_HYPERLINK_DETECTOR_KEY = "org.eclipse.ui.internal.editors.text.URLHyperlinkDetector"; //$NON-NLS-1$
+
private int tokenStart;
private int lastLineStart;
private IToken hyperlinkToken;
+ private IHyperlinkDetector[] hyperlinkDetectors;
+
private final ISourceViewer viewer;
- private final IHyperlinkDetector[] hyperlinkDetectors;
+ private final SourceViewerConfiguration configuration;
+
+ /**
+ * The preference store to use to look up hyperlinking-related preferences.
+ */
+ private final IPreferenceStore preferenceStore;
/**
* Caches all hyperlinks on a line to avoid calling the hyperlink detectors
@@ -70,49 +83,61 @@ public class HyperlinkTokenScanner implements ITokenScanner {
/**
* Creates a new instance that uses the given hyperlink detector and viewer.
*
- * @param hyperlinkDetectors
- * the {@link IHyperlinkDetector}s to use
+ * @param configuration
+ * the {@link SourceViewerConfiguration}s to get the
+ * {@link IHyperlinkDetector}s from
* @param viewer
* the {@link ISourceViewer} to operate in
*/
- public HyperlinkTokenScanner(IHyperlinkDetector[] hyperlinkDetectors,
+ public HyperlinkTokenScanner(SourceViewerConfiguration configuration,
ISourceViewer viewer) {
- this(hyperlinkDetectors, viewer, null);
+ this(configuration, viewer, null);
}
/**
* Creates a new instance that uses the given hyperlink detector and viewer.
*
- * @param hyperlinkDetectors
- * the {@link IHyperlinkDetector}s to use
+ * @param configuration
+ * the {@link SourceViewerConfiguration}s to get the
+ * {@link IHyperlinkDetector}s from
* @param viewer
* the {@link ISourceViewer} to operate in
* @param defaultAttribute
* the {@link TextAttribute} to use for the default token; may be
* {@code null} to use the default style of the viewer
*/
- public HyperlinkTokenScanner(IHyperlinkDetector[] hyperlinkDetectors,
+ public HyperlinkTokenScanner(SourceViewerConfiguration configuration,
ISourceViewer viewer, @Nullable TextAttribute defaultAttribute) {
- IHyperlinkDetector[] allDetectors;
- if (hyperlinkDetectors == null || hyperlinkDetectors.length == 0) {
- allDetectors = new IHyperlinkDetector[0];
- } else {
- allDetectors = new IHyperlinkDetector[hyperlinkDetectors.length
- + 1];
- System.arraycopy(hyperlinkDetectors, 0, allDetectors, 0,
- hyperlinkDetectors.length);
- // URLHyperlinkDetector can only detect hyperlinks at the start of
- // the range. We need one that can detect all hyperlinks in a given
- // region.
- allDetectors[hyperlinkDetectors.length] = new MultiURLHyperlinkDetector();
- }
- this.hyperlinkDetectors = allDetectors;
+ this(configuration, viewer, null, defaultAttribute);
+ }
+
+ /**
+ * Creates a new instance that uses the given hyperlink detector and viewer.
+ *
+ * @param configuration
+ * the {@link SourceViewerConfiguration}s to get the
+ * {@link IHyperlinkDetector}s from
+ * @param viewer
+ * the {@link ISourceViewer} to operate in
+ * @param preferenceStore
+ * to use to look up preferences related to hyperlinking
+ * @param defaultAttribute
+ * the {@link TextAttribute} to use for the default token; may be
+ * {@code null} to use the default style of the viewer
+ */
+ protected HyperlinkTokenScanner(SourceViewerConfiguration configuration,
+ ISourceViewer viewer, @Nullable IPreferenceStore preferenceStore,
+ @Nullable TextAttribute defaultAttribute) {
this.viewer = viewer;
this.defaultToken = new Token(defaultAttribute);
+ this.configuration = configuration;
+ this.preferenceStore = preferenceStore == null
+ ? EditorsUI.getPreferenceStore() : preferenceStore;
}
@Override
public void setRange(IDocument document, int offset, int length) {
+ Assert.isNotNull(document);
setRangeAndColor(document, offset, length, JFaceColors
.getHyperlinkText(viewer.getTextWidget().getDisplay()));
}
@@ -124,7 +149,7 @@ public class HyperlinkTokenScanner implements ITokenScanner {
hyperlinksOnLine.clear();
return Token.EOF;
}
- if (hyperlinkDetectors.length > 0) {
+ if (hyperlinkDetectors != null && hyperlinkDetectors.length > 0) {
try {
IRegion currentLine = document
.getLineInformationOfOffset(currentOffset);
@@ -132,10 +157,16 @@ public class HyperlinkTokenScanner implements ITokenScanner {
// Compute all hyperlinks in the line
hyperlinksOnLine.clear();
for (IHyperlinkDetector hyperlinkDetector : hyperlinkDetectors) {
- IHyperlink[] newLinks = hyperlinkDetector
- .detectHyperlinks(viewer, currentLine, false);
- if (newLinks != null && newLinks.length > 0) {
- Collections.addAll(hyperlinksOnLine, newLinks);
+ // The NoMaskHyperlinkDetectors can be skipped; if there
+ // are any, they're only duplicates of others to ensure
+ // hyperlinks open also if no modifier key is active.
+ if (!(hyperlinkDetector instanceof HyperlinkSourceViewer.NoMaskHyperlinkDetector)) {
+ IHyperlink[] newLinks = hyperlinkDetector
+ .detectHyperlinks(viewer, currentLine,
+ false);
+ if (newLinks != null && newLinks.length > 0) {
+ Collections.addAll(hyperlinksOnLine, newLinks);
+ }
}
}
// Sort them by offset, and with increasing length
@@ -231,6 +262,7 @@ public class HyperlinkTokenScanner implements ITokenScanner {
this.tokenStart = -1;
this.hyperlinkToken = new Token(
new TextAttribute(color, null, TextAttribute.UNDERLINE));
+ this.hyperlinkDetectors = getHyperlinkDetectors();
}
/**
@@ -244,6 +276,29 @@ public class HyperlinkTokenScanner implements ITokenScanner {
return null;
}
+ private @NonNull IHyperlinkDetector[] getHyperlinkDetectors() {
+ IHyperlinkDetector[] allDetectors;
+ IHyperlinkDetector[] configuredDetectors = configuration
+ .getHyperlinkDetectors(viewer);
+ if (configuredDetectors == null || configuredDetectors.length == 0) {
+ allDetectors = new IHyperlinkDetector[0];
+ } else if (preferenceStore.getBoolean(URL_HYPERLINK_DETECTOR_KEY)
+ || !preferenceStore.getBoolean(
+ AbstractTextEditor.PREFERENCE_HYPERLINKS_ENABLED)) {
+ allDetectors = configuredDetectors;
+ } else {
+ allDetectors = new IHyperlinkDetector[configuredDetectors.length
+ + 1];
+ System.arraycopy(configuredDetectors, 0, allDetectors, 0,
+ configuredDetectors.length);
+ // URLHyperlinkDetector can only detect hyperlinks at the start of
+ // the range. We need one that can detect all hyperlinks in a given
+ // region.
+ allDetectors[configuredDetectors.length] = new MultiURLHyperlinkDetector();
+ }
+ return allDetectors;
+ }
+
/**
* A {@link URLHyperlinkDetector} that returns all hyperlinks in a region.
* <p>
diff --git a/org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/dialogs/SpellcheckableMessageArea.java b/org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/dialogs/SpellcheckableMessageArea.java
index 079915f25..12b6494be 100644
--- a/org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/dialogs/SpellcheckableMessageArea.java
+++ b/org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/dialogs/SpellcheckableMessageArea.java
@@ -52,7 +52,6 @@ import org.eclipse.jface.text.TextEvent;
import org.eclipse.jface.text.WhitespaceCharacterPainter;
import org.eclipse.jface.text.contentassist.ICompletionProposal;
import org.eclipse.jface.text.contentassist.IContentAssistant;
-import org.eclipse.jface.text.hyperlink.IHyperlinkDetector;
import org.eclipse.jface.text.presentation.IPresentationReconciler;
import org.eclipse.jface.text.presentation.PresentationReconciler;
import org.eclipse.jface.text.quickassist.IQuickAssistInvocationContext;
@@ -267,8 +266,8 @@ public class SpellcheckableMessageArea extends Composite {
setLayout(new FillLayout());
AnnotationModel annotationModel = new AnnotationModel();
- sourceViewer = new SourceViewer(this, null, null, true, SWT.MULTI
- | SWT.V_SCROLL | SWT.WRAP);
+ sourceViewer = new HyperlinkSourceViewer(this, null, null, true,
+ SWT.MULTI | SWT.V_SCROLL | SWT.WRAP);
getTextWidget().setAlwaysShowScrollBars(false);
getTextWidget().setFont(UIUtils
.getFont(UIPreferences.THEME_CommitMessageEditorFont));
@@ -318,7 +317,6 @@ public class SpellcheckableMessageArea extends Composite {
});
}
}
-
};
JFacePreferences.getPreferenceStore()
.addPropertyChangeListener(syntaxColoringChangeListener);
@@ -329,13 +327,15 @@ public class SpellcheckableMessageArea extends Composite {
Document document = new Document(initialText);
- configuration = new TextSourceViewerConfiguration(
- EditorsUI
- .getPreferenceStore()) {
+ configuration = new HyperlinkSourceViewer.Configuration(
+ EditorsUI.getPreferenceStore()) {
@Override
public int getHyperlinkStateMask(ISourceViewer targetViewer) {
- return SWT.NONE;
+ if (!targetViewer.isEditable()) {
+ return SWT.NONE;
+ }
+ return super.getHyperlinkStateMask(targetViewer);
}
@Override
@@ -344,12 +344,6 @@ public class SpellcheckableMessageArea extends Composite {
}
@Override
- public IHyperlinkDetector[] getHyperlinkDetectors(
- ISourceViewer targetViewer) {
- return getRegisteredHyperlinkDetectors(sourceViewer);
- }
-
- @Override
public IReconciler getReconciler(ISourceViewer viewer) {
if (!isEditable(viewer))
return null;
@@ -372,11 +366,9 @@ public class SpellcheckableMessageArea extends Composite {
ISourceViewer viewer) {
PresentationReconciler reconciler = new PresentationReconciler();
reconciler.setDocumentPartitioning(
- getConfiguredDocumentPartitioning(sourceViewer));
+ getConfiguredDocumentPartitioning(viewer));
DefaultDamagerRepairer hyperlinkDamagerRepairer = new DefaultDamagerRepairer(
- new HyperlinkTokenScanner(
- getHyperlinkDetectors(sourceViewer),
- sourceViewer));
+ new HyperlinkTokenScanner(this, viewer));
reconciler.setDamager(hyperlinkDamagerRepairer,
IDocument.DEFAULT_CONTENT_TYPE);
reconciler.setRepairer(hyperlinkDamagerRepairer,
@@ -964,7 +956,7 @@ public class SpellcheckableMessageArea extends Composite {
* @return map of targets
*/
protected Map<String, IAdaptable> getHyperlinkTargets() {
- return Collections.singletonMap("org.eclipse.ui.DefaultTextEditor", //$NON-NLS-1$
+ return Collections.singletonMap(EditorsUI.DEFAULT_TEXT_EDITOR_ID,
getDefaultTarget());
}
diff --git a/org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/history/CommitMessageViewer.java b/org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/history/CommitMessageViewer.java
index d3c1283e4..6084756c4 100644
--- a/org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/history/CommitMessageViewer.java
+++ b/org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/history/CommitMessageViewer.java
@@ -29,6 +29,7 @@ import org.eclipse.egit.ui.UIUtils;
import org.eclipse.egit.ui.internal.CommonUtils;
import org.eclipse.egit.ui.internal.UIText;
import org.eclipse.egit.ui.internal.actions.BooleanPrefAction;
+import org.eclipse.egit.ui.internal.dialogs.HyperlinkSourceViewer;
import org.eclipse.egit.ui.internal.history.FormatJob.FormatResult;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.action.IAction;
@@ -46,11 +47,11 @@ import org.eclipse.jface.text.ITextViewer;
import org.eclipse.jface.text.TextUtilities;
import org.eclipse.jface.text.hyperlink.IHyperlink;
import org.eclipse.jface.text.hyperlink.IHyperlinkDetector;
+import org.eclipse.jface.text.hyperlink.IHyperlinkDetectorExtension2;
import org.eclipse.jface.text.rules.FastPartitioner;
import org.eclipse.jface.text.rules.IPartitionTokenScanner;
import org.eclipse.jface.text.rules.IToken;
import org.eclipse.jface.text.rules.Token;
-import org.eclipse.jface.text.source.SourceViewer;
import org.eclipse.jface.util.IPropertyChangeListener;
import org.eclipse.jface.util.PropertyChangeEvent;
import org.eclipse.jgit.events.ListenerHandle;
@@ -72,7 +73,7 @@ import org.eclipse.ui.actions.ActionFactory;
import org.eclipse.ui.part.IPageSite;
import org.eclipse.ui.progress.IWorkbenchSiteProgressService;
-class CommitMessageViewer extends SourceViewer {
+class CommitMessageViewer extends HyperlinkSourceViewer {
static final String HEADER_CONTENT_TYPE = "__egit_commit_msg_header"; //$NON-NLS-1$
@@ -537,7 +538,8 @@ class CommitMessageViewer extends SourceViewer {
}
- static class KnownHyperlinksDetector implements IHyperlinkDetector {
+ static class KnownHyperlinksDetector
+ implements IHyperlinkDetector, IHyperlinkDetectorExtension2 {
@Override
public IHyperlink[] detectHyperlinks(ITextViewer textViewer,
@@ -560,6 +562,11 @@ class CommitMessageViewer extends SourceViewer {
return null;
}
+ @Override
+ public int getStateMask() {
+ return -1;
+ }
+
}
private void setFill(boolean fill) {
diff --git a/org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/history/GitHistoryPage.java b/org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/history/GitHistoryPage.java
index 81df7372e..5a3372675 100644
--- a/org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/history/GitHistoryPage.java
+++ b/org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/history/GitHistoryPage.java
@@ -49,6 +49,7 @@ import org.eclipse.egit.ui.internal.UIIcons;
import org.eclipse.egit.ui.internal.UIText;
import org.eclipse.egit.ui.internal.commit.DiffStyleRangeFormatter;
import org.eclipse.egit.ui.internal.commit.DiffViewer;
+import org.eclipse.egit.ui.internal.dialogs.HyperlinkSourceViewer;
import org.eclipse.egit.ui.internal.dialogs.HyperlinkTokenScanner;
import org.eclipse.egit.ui.internal.repository.tree.AdditionalRefNode;
import org.eclipse.egit.ui.internal.repository.tree.FileNode;
@@ -87,6 +88,7 @@ import org.eclipse.jface.text.rules.DefaultDamagerRepairer;
import org.eclipse.jface.text.rules.IToken;
import org.eclipse.jface.text.rules.Token;
import org.eclipse.jface.text.source.ISourceViewer;
+import org.eclipse.jface.text.source.SourceViewerConfiguration;
import org.eclipse.jface.util.IPropertyChangeListener;
import org.eclipse.jface.util.PropertyChangeEvent;
import org.eclipse.jface.viewers.ISelection;
@@ -151,7 +153,6 @@ import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.actions.ActionFactory.IWorkbenchAction;
import org.eclipse.ui.dialogs.PreferencesUtil;
import org.eclipse.ui.editors.text.EditorsUI;
-import org.eclipse.ui.editors.text.TextSourceViewerConfiguration;
import org.eclipse.ui.part.IShowInSource;
import org.eclipse.ui.part.IShowInTargetList;
import org.eclipse.ui.part.ShowInContext;
@@ -903,7 +904,7 @@ public class GitHistoryPage extends HistoryPage implements RefsChangedListener,
.getBackground());
- TextSourceViewerConfiguration configuration = new TextSourceViewerConfiguration(
+ HyperlinkSourceViewer.Configuration configuration = new HyperlinkSourceViewer.Configuration(
EditorsUI.getPreferenceStore()) {
@Override
@@ -912,16 +913,23 @@ public class GitHistoryPage extends HistoryPage implements RefsChangedListener,
}
@Override
- public IHyperlinkDetector[] getHyperlinkDetectors(
+ protected IHyperlinkDetector[] internalGetHyperlinkDetectors(
ISourceViewer sourceViewer) {
- IHyperlinkDetector[] registered = getRegisteredHyperlinkDetectors(
+ IHyperlinkDetector[] registered = super.internalGetHyperlinkDetectors(
sourceViewer);
- // Add our special detector for commit hyperlinks.
- IHyperlinkDetector[] result = new IHyperlinkDetector[registered.length
- + 1];
- System.arraycopy(registered, 0, result, 0, registered.length);
- result[registered.length] = new CommitMessageViewer.KnownHyperlinksDetector();
- return result;
+ // Always add our special detector for commit hyperlinks; we
+ // want those to show always.
+ if (registered == null) {
+ return new IHyperlinkDetector[] {
+ new CommitMessageViewer.KnownHyperlinksDetector() };
+ } else {
+ IHyperlinkDetector[] result = new IHyperlinkDetector[registered.length
+ + 1];
+ System.arraycopy(registered, 0, result, 0,
+ registered.length);
+ result[registered.length] = new CommitMessageViewer.KnownHyperlinksDetector();
+ return result;
+ }
}
@Override
@@ -939,8 +947,7 @@ public class GitHistoryPage extends HistoryPage implements RefsChangedListener,
reconciler.setDocumentPartitioning(
getConfiguredDocumentPartitioning(viewer));
DefaultDamagerRepairer hyperlinkDamagerRepairer = new DefaultDamagerRepairer(
- new HyperlinkTokenScanner(getHyperlinkDetectors(viewer),
- viewer));
+ new HyperlinkTokenScanner(this, viewer));
reconciler.setDamager(hyperlinkDamagerRepairer,
IDocument.DEFAULT_CONTENT_TYPE);
reconciler.setRepairer(hyperlinkDamagerRepairer,
@@ -949,15 +956,13 @@ public class GitHistoryPage extends HistoryPage implements RefsChangedListener,
PlatformUI.getWorkbench().getDisplay()
.getSystemColor(SWT.COLOR_DARK_GRAY));
DefaultDamagerRepairer headerDamagerRepairer = new DefaultDamagerRepairer(
- new HyperlinkTokenScanner(getHyperlinkDetectors(viewer),
- viewer, headerDefault));
+ new HyperlinkTokenScanner(this, viewer, headerDefault));
reconciler.setDamager(headerDamagerRepairer,
CommitMessageViewer.HEADER_CONTENT_TYPE);
reconciler.setRepairer(headerDamagerRepairer,
CommitMessageViewer.HEADER_CONTENT_TYPE);
DefaultDamagerRepairer footerDamagerRepairer = new DefaultDamagerRepairer(
- new FooterTokenScanner(getHyperlinkDetectors(viewer),
- viewer));
+ new FooterTokenScanner(this, viewer));
reconciler.setDamager(footerDamagerRepairer,
CommitMessageViewer.FOOTER_CONTENT_TYPE);
reconciler.setRepairer(footerDamagerRepairer,
@@ -2400,9 +2405,9 @@ public class GitHistoryPage extends HistoryPage implements RefsChangedListener,
private final IToken italicToken;
- public FooterTokenScanner(IHyperlinkDetector[] hyperlinkDetectors,
+ public FooterTokenScanner(SourceViewerConfiguration configuration,
ISourceViewer viewer) {
- super(hyperlinkDetectors, viewer);
+ super(configuration, viewer);
Object defaults = defaultToken.getData();
TextAttribute italic;
if (defaults instanceof TextAttribute) {

Back to the top