diff options
author | Matthias Sohn | 2015-09-20 23:13:28 +0000 |
---|---|---|
committer | Gerrit Code Review @ Eclipse.org | 2015-09-20 23:13:30 +0000 |
commit | 96beea9ac75b1e732213bd55e2638d155fe272f8 (patch) | |
tree | 55583d5d99725441091dfe136b5a98ed4e051ed2 | |
parent | 122267c0af9a61d9ecd9d498133debd7c26d0da5 (diff) | |
parent | 0c7f0492b88a5ab38de368e691ba00107a6fa137 (diff) | |
download | egit-96beea9ac75b1e732213bd55e2638d155fe272f8.tar.gz egit-96beea9ac75b1e732213bd55e2638d155fe272f8.tar.xz egit-96beea9ac75b1e732213bd55e2638d155fe272f8.zip |
Merge "Obey hyperlink preferences in SpellcheckableMessageArea"
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 51e49cf31b..2b1eece69f 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 0000000000..6b3d746276 --- /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 e18d8c632a..67738d61b6 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 079915f257..12b6494be8 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 d3c1283e4e..6084756c4a 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 81df7372eb..5a33726755 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) { |