diff options
6 files changed, 439 insertions, 251 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 4de8b1ad1c..51e49cf31b 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 @@ -101,7 +101,7 @@ public class HyperlinkTokenScannerTest { IDocument testDocument = new Document(text); when(viewer.getDocument()).thenReturn(testDocument); HyperlinkTokenScanner scanner = new HyperlinkTokenScanner(detectors, - viewer); + viewer, null); scanner.setRangeAndColor(testDocument, offset, length, null); IToken token = null; char[] found = new char[text.length()]; @@ -110,15 +110,13 @@ public class HyperlinkTokenScannerTest { int tokenOffset = scanner.getTokenOffset(); int tokenLength = scanner.getTokenLength(); char ch = 'x'; - if (token == HyperlinkTokenScanner.DEFAULT) { + Object data = token.getData(); + if (data == null) { ch = 'D'; - } else { - Object data = token.getData(); - if (data instanceof TextAttribute) { - int style = ((TextAttribute) data).getStyle(); - if ((style & TextAttribute.UNDERLINE) != 0) { - ch = 'H'; - } + } else if (data instanceof TextAttribute) { + int style = ((TextAttribute) data).getStyle(); + if ((style & TextAttribute.UNDERLINE) != 0) { + ch = 'H'; } } Arrays.fill(found, tokenOffset, tokenOffset + tokenLength, ch); 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 5a754b8231..91517a5a6f 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 @@ -30,13 +30,6 @@ import org.eclipse.swt.graphics.Color; */ public class HyperlinkTokenScanner implements ITokenScanner { - /** The {@link IToken} returned for all non-hyperlink content. */ - protected static final IToken DEFAULT = new Token(null); - - private int endOfRange; - - private int currentOffset; - private int tokenStart; private IToken hyperlinkToken; @@ -45,6 +38,18 @@ public class HyperlinkTokenScanner implements ITokenScanner { private final IHyperlinkDetector[] hyperlinkDetectors; + /** The current offset in the document. */ + protected int currentOffset; + + /** The end of the range to tokenize. */ + protected int endOfRange; + + /** The {@link IDocument} the current scan operates on. */ + protected IDocument document; + + /** The {@link IToken} to use for default content. */ + protected final IToken defaultToken; + /** * Creates a new instance that uses the given hyperlink detector and viewer. * @@ -55,8 +60,25 @@ public class HyperlinkTokenScanner implements ITokenScanner { */ public HyperlinkTokenScanner(IHyperlinkDetector[] hyperlinkDetectors, ISourceViewer viewer) { + this(hyperlinkDetectors, viewer, null); + } + + /** + * Creates a new instance that uses the given hyperlink detector and viewer. + * + * @param hyperlinkDetectors + * the {@link IHyperlinkDetector}s to use + * @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, + ISourceViewer viewer, @Nullable TextAttribute defaultAttribute) { this.hyperlinkDetectors = hyperlinkDetectors; this.viewer = viewer; + this.defaultToken = new Token(defaultAttribute); } @Override @@ -84,8 +106,13 @@ public class HyperlinkTokenScanner implements ITokenScanner { } } } - currentOffset++; - return DEFAULT; + int actualOffset = currentOffset; + IToken token = scanToken(); + if (token != null && actualOffset < currentOffset) { + return token; + } + currentOffset = actualOffset + 1; + return defaultToken; } @Override @@ -116,10 +143,22 @@ public class HyperlinkTokenScanner implements ITokenScanner { protected void setRangeAndColor(@NonNull IDocument document, int offset, int length, @Nullable Color color) { Assert.isTrue(document == viewer.getDocument()); + this.document = document; this.endOfRange = offset + length; this.currentOffset = offset; this.tokenStart = -1; this.hyperlinkToken = new Token( new TextAttribute(color, null, TextAttribute.UNDERLINE)); } + + /** + * Invoked if there is no hyperlink at the current position; may check for + * additional tokens. If a token is found, must advance currentOffset and + * return the token. + * + * @return the {@link IToken}, or {@code null} if none. + */ + protected IToken scanToken() { + return null; + } }
\ No newline at end of file diff --git a/org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/history/CommitInfoBuilder.java b/org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/history/CommitInfoBuilder.java index b259d627be..f991f3d543 100644 --- a/org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/history/CommitInfoBuilder.java +++ b/org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/history/CommitInfoBuilder.java @@ -4,6 +4,7 @@ * Copyright (C) 2011, Mathias Kinzler <mathias.kinzler@sap.com> * Copyright (C) 2011, Jens Baumgart <jens.baumgart@sap.com> * Copyright (C) 2011, Stefan Lay <stefan.lay@sap.com> + * 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 @@ -16,6 +17,7 @@ package org.eclipse.egit.ui.internal.history; import java.io.IOException; import java.text.DateFormat; import java.text.SimpleDateFormat; +import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; import java.util.List; @@ -30,7 +32,9 @@ import org.eclipse.egit.ui.Activator; import org.eclipse.egit.ui.UIPreferences; import org.eclipse.egit.ui.internal.UIText; import org.eclipse.egit.ui.internal.history.CommitMessageViewer.ObjectLink; +import org.eclipse.egit.ui.internal.history.FormatJob.FormatResult; import org.eclipse.egit.ui.internal.trace.GitTraceLocation; +import org.eclipse.jface.text.Region; import org.eclipse.jgit.errors.IncorrectObjectTypeException; import org.eclipse.jgit.errors.MissingObjectException; import org.eclipse.jgit.lib.Constants; @@ -44,9 +48,6 @@ import org.eclipse.jgit.revwalk.RevObject; import org.eclipse.jgit.revwalk.RevWalk; import org.eclipse.jgit.revwalk.RevWalkUtils; import org.eclipse.osgi.util.NLS; -import org.eclipse.swt.SWT; -import org.eclipse.swt.custom.StyleRange; -import org.eclipse.swt.graphics.Color; /** * Class to build and format commit info in History View @@ -61,16 +62,15 @@ public class CommitInfoBuilder { private final DateFormat fmt = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); //$NON-NLS-1$ + private static final Pattern FOOTER_PATTERN = Pattern + .compile("(?:\n(?:[A-Z](?:[A-Za-z]+-)*[A-Za-z]+:[^\n]*))+$"); //$NON-NLS-1$ + private PlotCommit<?> commit; private final Repository db; private final boolean fill; - private Color linkColor; - - private Color darkGrey; - private final Collection<Ref> allRefs; /** @@ -88,26 +88,14 @@ public class CommitInfoBuilder { } /** - * set colors for formatting + * Retrieves and formats the commit info. * - * @param linkColor - * @param darkGrey - */ - public void setColors(Color linkColor, Color darkGrey) { - this.linkColor = linkColor; - this.darkGrey = darkGrey; - } - - /** - * Format the commit info - * - * @param styles styles for text formatting * @param monitor + * for progress reporting and cancellation * @return formatted commit info * @throws IOException */ - public String format(final List<StyleRange> styles, - IProgressMonitor monitor) throws IOException { + public FormatResult format(IProgressMonitor monitor) throws IOException { boolean trace = GitTraceLocation.HISTORYVIEW.isActive(); if (trace) GitTraceLocation.getTrace().traceEntry( @@ -116,6 +104,7 @@ public class CommitInfoBuilder { final StringBuilder d = new StringBuilder(); final PersonIdent author = commit.getAuthorIdent(); final PersonIdent committer = commit.getCommitterIdent(); + List<ObjectLink> hyperlinks = new ArrayList<>(); d.append(UIText.CommitMessageViewer_commit); d.append(SPACE); d.append(commit.getId().name()); @@ -148,7 +137,7 @@ public class CommitInfoBuilder { p.parseBody(); d.append(UIText.CommitMessageViewer_parent); d.append(": "); //$NON-NLS-1$ - addLink(d, styles, p); + addLink(d, hyperlinks, p); d.append(" ("); //$NON-NLS-1$ d.append(p.getShortMessage()); d.append(")"); //$NON-NLS-1$ @@ -160,7 +149,7 @@ public class CommitInfoBuilder { p.parseBody(); d.append(UIText.CommitMessageViewer_child); d.append(": "); //$NON-NLS-1$ - addLink(d, styles, p); + addLink(d, hyperlinks, p); d.append(" ("); //$NON-NLS-1$ d.append(p.getShortMessage()); d.append(")"); //$NON-NLS-1$ @@ -179,7 +168,7 @@ public class CommitInfoBuilder { Ref head = i.next(); RevCommit p; p = rw.parseCommit(head.getObjectId()); - addLink(d, formatHeadRef(head), styles, p); + addLink(d, formatHeadRef(head), hyperlinks, p); if (i.hasNext()) { if (count++ <= MAXBRANCHES) { d.append(", "); //$NON-NLS-1$ @@ -214,7 +203,7 @@ public class CommitInfoBuilder { d.append(": "); //$NON-NLS-1$ RevCommit p = rw.parseCommit(followingTag .getObjectId()); - addLink(d, formatTagRef(followingTag), styles, p); + addLink(d, formatTagRef(followingTag), hyperlinks, p); d.append(LF); } } catch (IOException e) { @@ -229,7 +218,7 @@ public class CommitInfoBuilder { d.append(": "); //$NON-NLS-1$ RevCommit p = rw.parseCommit(precedingTag .getObjectId()); - addLink(d, formatTagRef(precedingTag), styles, p); + addLink(d, formatTagRef(precedingTag), hyperlinks, p); d.append(LF); } } catch (IOException e) { @@ -237,50 +226,47 @@ public class CommitInfoBuilder { } } - makeGrayText(d, styles); d.append(LF); - String msg = commit.getFullMessage(); - Pattern p = Pattern.compile("\n([A-Z](?:[A-Za-z]+-)+by: [^\n]+)"); //$NON-NLS-1$ - if (fill) { - Matcher spm = p.matcher(msg); - if (spm.find()) { - String subMsg = msg.substring(0, spm.end()); - msg = subMsg.replaceAll("([\\w.,; \t])\n(\\w)", "$1 $2") //$NON-NLS-1$ //$NON-NLS-2$ - + msg.substring(spm.end()); + int headerEnd = d.length(); + String msg = commit.getFullMessage().trim(); + // Find start of footer: + Matcher spm = FOOTER_PATTERN.matcher(msg); + int footerStart = -1; + if (spm.find()) { + if (fill) { + String footer = msg.substring(spm.start()); + msg = msg.substring(0, spm.start()); + msg = msg.replaceAll("([\\w.,; \t])\n(\\w)", "$1 $2") //$NON-NLS-1$ //$NON-NLS-2$ + + footer; + footerStart = headerEnd + msg.length() - footer.length(); + } else { + footerStart = headerEnd + spm.start(); } + } else if (fill) { + msg = msg.replaceAll("([\\w.,; \t])\n(\\w)", "$1 $2"); //$NON-NLS-1$ //$NON-NLS-2$ } - int h0 = d.length(); + d.append(msg); if (!msg.endsWith(LF)) d.append(LF); - Matcher matcher = p.matcher(msg); - while (matcher.find()) { - styles.add(new StyleRange(h0 + matcher.start(), matcher.end() - - matcher.start(), null, null, SWT.ITALIC)); - } - if (trace) GitTraceLocation.getTrace().traceExit( GitTraceLocation.HISTORYVIEW.getLocation()); - return d.toString(); + return new FormatResult(d.toString(), hyperlinks, headerEnd, + footerStart >= 0 ? footerStart : d.length()); } - private void addLink(final StringBuilder d, String linkLabel, - final List<StyleRange> styles, final RevCommit to) { - final ObjectLink sr = new ObjectLink(); - sr.targetCommit = to; - sr.foreground = linkColor; - sr.underline = true; - sr.start = d.length(); + private void addLink(StringBuilder d, String linkLabel, + Collection<ObjectLink> hyperlinks, RevCommit to) { + hyperlinks.add( + new ObjectLink(to, new Region(d.length(), linkLabel.length()))); d.append(linkLabel); - sr.length = d.length() - sr.start; - styles.add(sr); } - private void addLink(final StringBuilder d, final List<StyleRange> styles, - final RevCommit to) { - addLink(d, to.getId().name(), styles, to); + private void addLink(StringBuilder d, Collection<ObjectLink> hyperlinks, + RevCommit to) { + addLink(d, to.getId().name(), hyperlinks, to); } /** @@ -318,28 +304,6 @@ public class CommitInfoBuilder { return name; } - private void makeGrayText(StringBuilder d, List<StyleRange> styles) { - int p0 = 0; - for (int i = 0; i < styles.size(); ++i) { - StyleRange r = styles.get(i); - if (p0 < r.start) { - StyleRange nr = new StyleRange(p0, r.start - p0, darkGrey, - null); - styles.add(i, nr); - p0 = r.start; - } else { - if (r.foreground == null) - r.foreground = darkGrey; - p0 = r.start + r.length; - } - } - if (d.length() - 1 > p0) { - StyleRange nr = new StyleRange(p0, d.length() - p0, darkGrey, - null); - styles.add(nr); - } - } - private String getTagsString() { StringBuilder sb = new StringBuilder(); Map<String, Ref> tagsMap = db.getTags(); 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 de3769d81e..fe4d7ea412 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 @@ -6,6 +6,7 @@ * Copyright (C) 2011, Stefan Lay <stefan.lay@sap.com> * Copyright (C) 2014, Marc-Andre Laperle <marc-andre.laperle@ericsson.com> * Copyright (C) 2015, IBM Corporation (Dani Megert <daniel_megert@ch.ibm.com>) + * 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 @@ -34,10 +35,21 @@ import org.eclipse.jface.action.IAction; import org.eclipse.jface.action.MenuManager; import org.eclipse.jface.preference.IPersistentPreferenceStore; import org.eclipse.jface.preference.IPreferenceStore; +import org.eclipse.jface.preference.JFacePreferences; import org.eclipse.jface.text.DefaultTextDoubleClickStrategy; import org.eclipse.jface.text.Document; import org.eclipse.jface.text.IDocument; +import org.eclipse.jface.text.IDocumentPartitioner; +import org.eclipse.jface.text.IRegion; import org.eclipse.jface.text.ITextOperationTarget; +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.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; @@ -51,37 +63,21 @@ import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.revplot.PlotCommit; import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.swt.SWT; -import org.eclipse.swt.custom.StyleRange; import org.eclipse.swt.custom.StyledText; import org.eclipse.swt.events.FocusEvent; import org.eclipse.swt.events.FocusListener; -import org.eclipse.swt.events.MouseAdapter; -import org.eclipse.swt.events.MouseEvent; -import org.eclipse.swt.graphics.Color; -import org.eclipse.swt.graphics.Cursor; -import org.eclipse.swt.graphics.Point; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Control; -import org.eclipse.swt.widgets.Event; -import org.eclipse.swt.widgets.Listener; import org.eclipse.ui.IWorkbenchPartSite; -import org.eclipse.ui.PlatformUI; import org.eclipse.ui.actions.ActionFactory; import org.eclipse.ui.part.IPageSite; import org.eclipse.ui.progress.IWorkbenchSiteProgressService; class CommitMessageViewer extends SourceViewer { - private static final Color SYS_LINKCOLOR = PlatformUI.getWorkbench() - .getDisplay().getSystemColor(SWT.COLOR_BLUE); + static final String HEADER_CONTENT_TYPE = "__egit_commit_msg_header"; //$NON-NLS-1$ - private static final Color SYS_DARKGRAY = PlatformUI.getWorkbench() - .getDisplay().getSystemColor(SWT.COLOR_DARK_GRAY); - - private static final Cursor SYS_LINK_CURSOR = PlatformUI.getWorkbench() - .getDisplay().getSystemCursor(SWT.CURSOR_HAND); - - private final Cursor sys_normalCursor; + static final String FOOTER_CONTENT_TYPE = "__egit_commit_msg_footer"; //$NON-NLS-1$ // notified when clicking on a link in the message (branch, commit...) private final ListenerList navListeners = new ListenerList(); @@ -89,6 +85,9 @@ class CommitMessageViewer extends SourceViewer { // listener to detect changes in the wrap and fill preferences private final IPropertyChangeListener listener; + // Listener to react on syntax coloring preferences changes + private final IPropertyChangeListener syntaxColoringListener; + // the current repository private Repository db; @@ -121,35 +120,6 @@ class CommitMessageViewer extends SourceViewer { final StyledText t = getTextWidget(); t.setFont(UIUtils.getFont(UIPreferences.THEME_CommitMessageFont)); - sys_normalCursor = t.getCursor(); - - // set the cursor when hovering over a link - t.addListener(SWT.MouseMove, new Listener() { - @Override - public void handleEvent(final Event e) { - StyleRange styleRange = getStyleRange(e.x, e.y); - if (styleRange != null && styleRange.underline) - t.setCursor(SYS_LINK_CURSOR); - else - t.setCursor(sys_normalCursor); - } - }); - // react on link click - t.addMouseListener(new MouseAdapter() { - @Override - public void mouseDown(final MouseEvent e) { - // only process the hyper link if it was a primary mouse click - if (e.button != 1) - return; - - final StyleRange r = getStyleRange(e.x, e.y); - if (r instanceof ObjectLink) { - final RevCommit c = ((ObjectLink) r).targetCommit; - for (final Object l : navListeners.getListeners()) - ((CommitNavigationListener) l).showCommit(c); - } - } - }); setTextDoubleClickStrategy(new DefaultTextDoubleClickStrategy(), IDocument.DEFAULT_CONTENT_TYPE); activatePlugins(); @@ -172,12 +142,24 @@ class CommitMessageViewer extends SourceViewer { } } }; - IPreferenceStore store = Activator.getDefault().getPreferenceStore(); store.addPropertyChangeListener(listener); fill = store .getBoolean(UIPreferences.RESOURCEHISTORY_SHOW_COMMENT_FILL); + // React on changes in the JFace color preferences + syntaxColoringListener = new IPropertyChangeListener() { + @Override + public void propertyChange(PropertyChangeEvent event) { + if (JFacePreferences.HYPERLINK_COLOR + .equals(event.getProperty())) { + format(); + } + } + }; + JFacePreferences.getPreferenceStore() + .addPropertyChangeListener(syntaxColoringListener); + // global action handlers for select all and copy final IAction selectAll = new Action() { @Override @@ -300,6 +282,8 @@ class CommitMessageViewer extends SourceViewer { } Activator.getDefault().getPreferenceStore() .removePropertyChangeListener(listener); + JFacePreferences.getPreferenceStore() + .removePropertyChangeListener(syntaxColoringListener); if (refsChangedListener != null) refsChangedListener.remove(); refsChangedListener = null; @@ -387,8 +371,7 @@ class CommitMessageViewer extends SourceViewer { if (siteService == null) return; FormatJob.FormatRequest formatRequest = new FormatJob.FormatRequest( - getRepository(), commit, fill, SYS_LINKCOLOR, SYS_DARKGRAY, - allRefs); + getRepository(), commit, fill, allRefs); formatJob = new FormatJob(formatRequest); addDoneListenerToFormatJob(); siteService.schedule(formatJob, 0 /* now */, true /* @@ -402,68 +385,196 @@ class CommitMessageViewer extends SourceViewer { if (!UIUtils.isUsable(text)) return; - setDocument(new Document(formatResult.getCommitInfo())); + setDocument(new CommitDocument(formatResult)); + } + + static class ObjectLink { + private final RevCommit target; + private final IRegion region; + + public ObjectLink(RevCommit target, IRegion region) { + this.target = target; + this.region = region; + } + + public IRegion getRegion() { + return region; + } - // Set style ranges from format job. We know that they are already - // ordered and don't overlap. - text.setStyleRanges(formatResult.getStyleRange()); + public RevCommit getTarget() { + return target; + } - // Apply additional styles. If we combined them with the above style - // ranges and set them all at once, we would have to manually remove - // overlapping ones. - UIUtils.applyHyperlinkDetectorStyleRanges(CommitMessageViewer.this, - fHyperlinkDetectors); } - static final class ObjectLink extends StyleRange { - RevCommit targetCommit; + private class ObjectHyperlink implements IHyperlink { + + ObjectLink link; + + public ObjectHyperlink(ObjectLink link) { + this.link = link; + } + + @Override + public IRegion getHyperlinkRegion() { + return link.getRegion(); + } @Override - public boolean similarTo(final StyleRange style) { - if (!(style instanceof ObjectLink)) - return false; - if (targetCommit != ((ObjectLink) style).targetCommit) - return false; - return super.similarTo(style); + public String getTypeLabel() { + return null; } @Override - public boolean equals(Object object) { - return super.equals(object) - && targetCommit.equals(((ObjectLink) object).targetCommit); + public String getHyperlinkText() { + return link.getTarget().name(); } @Override - public int hashCode() { - return super.hashCode() ^ targetCommit.hashCode(); + public void open() { + for (final Object l : navListeners.getListeners()) + ((CommitNavigationListener) l).showCommit(link.getTarget()); } + } - private void setFill(boolean fill) { - this.fill = fill; - format(); + private class CommitDocument extends Document { + + private final List<IHyperlink> hyperlinks; + + private final int headerEnd; + + private final int footerStart; + + public CommitDocument(FormatResult format) { + super(format.getCommitInfo()); + headerEnd = format.getHeaderEnd(); + footerStart = format.getFooterStart(); + List<ObjectLink> knownLinks = format.getKnownLinks(); + hyperlinks = new ArrayList<>(knownLinks.size()); + for (ObjectLink o : knownLinks) { + hyperlinks.add(new ObjectHyperlink(o)); + } + IDocumentPartitioner partitioner = new FastPartitioner( + new CommitPartitionTokenScanner(), + new String[] { IDocument.DEFAULT_CONTENT_TYPE, + HEADER_CONTENT_TYPE, FOOTER_CONTENT_TYPE }); + partitioner.connect(this); + this.setDocumentPartitioner(partitioner); + } + + public List<IHyperlink> getKnownHyperlinks() { + return hyperlinks; + } + + public int getHeaderEnd() { + return headerEnd; + } + + public int getFooterStart() { + return footerStart; + } } - /** - * Get style range at x/y coordinates - * - * @param x - * @param y - * @return style range, will be null when no style range exists at given - * coordinates - */ - private StyleRange getStyleRange(final int x, final int y) { - final StyledText t = getTextWidget(); - final int offset; - try { - offset = t.getOffsetAtLocation(new Point(x, y)); - } catch (IllegalArgumentException e) { - return null; + private static class CommitPartitionTokenScanner + implements IPartitionTokenScanner { + + private static final IToken HEADER = new Token(HEADER_CONTENT_TYPE); + + private static final IToken BODY = new Token( + IDocument.DEFAULT_CONTENT_TYPE); + + private static final IToken FOOTER = new Token(FOOTER_CONTENT_TYPE); + + private int headerEnd; + + private int footerStart; + + private int currentOffset; + + private int end; + + private int tokenStart; + + @Override + public void setRange(IDocument document, int offset, int length) { + if (document instanceof CommitDocument) { + CommitDocument d = (CommitDocument) document; + headerEnd = d.getHeaderEnd(); + footerStart = d.getFooterStart(); + } else { + headerEnd = 0; + footerStart = document.getLength(); + } + currentOffset = offset; + end = offset + length; + tokenStart = -1; + } + + @Override + public IToken nextToken() { + if (currentOffset < end) { + tokenStart = currentOffset; + if (currentOffset < headerEnd) { + currentOffset = Math.min(headerEnd, end); + return HEADER; + } else if (currentOffset < footerStart) { + currentOffset = Math.min(footerStart, end); + return BODY; + } else { + currentOffset = end; + return FOOTER; + } + } + return Token.EOF; + } + + @Override + public int getTokenOffset() { + return tokenStart; + } + + @Override + public int getTokenLength() { + return currentOffset - tokenStart; + } + + @Override + public void setPartialRange(IDocument document, int offset, int length, + String contentType, int partitionOffset) { + setRange(document, offset, length); } - if (offset < t.getCharCount()) - return t.getStyleRangeAtOffset(offset); - else + + } + + static class KnownHyperlinksDetector implements IHyperlinkDetector { + + @Override + public IHyperlink[] detectHyperlinks(ITextViewer textViewer, + IRegion region, boolean canShowMultipleHyperlinks) { + IDocument document = textViewer.getDocument(); + if (document instanceof CommitDocument) { + List<IHyperlink> knownLinks = ((CommitDocument) document) + .getKnownHyperlinks(); + List<IHyperlink> result = new ArrayList<>(); + for (IHyperlink link : knownLinks) { + IRegion linkRegion = link.getHyperlinkRegion(); + if (TextUtilities.overlaps(linkRegion, region)) { + result.add(link); + } + } + if (!result.isEmpty()) { + return result.toArray(new IHyperlink[result.size()]); + } + } return null; + } + + } + + private void setFill(boolean fill) { + this.fill = fill; + format(); } } diff --git a/org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/history/FormatJob.java b/org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/history/FormatJob.java index b12d38416f..b93b1a8924 100644 --- a/org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/history/FormatJob.java +++ b/org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/history/FormatJob.java @@ -1,6 +1,7 @@ /******************************************************************************* * Copyright (C) 2011, Jens Baumgart <jens.baumgart@sap.com> * Copyright (C) 2011, Stefan Lay <stefan.lay@sap.com> + * 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 @@ -10,10 +11,7 @@ package org.eclipse.egit.ui.internal.history; import java.io.IOException; -import java.util.ArrayList; -import java.util.Arrays; import java.util.Collection; -import java.util.Comparator; import java.util.List; import org.eclipse.core.runtime.IProgressMonitor; @@ -23,11 +21,10 @@ import org.eclipse.core.runtime.jobs.Job; import org.eclipse.egit.ui.Activator; import org.eclipse.egit.ui.JobFamilies; import org.eclipse.egit.ui.internal.UIText; +import org.eclipse.egit.ui.internal.history.CommitMessageViewer.ObjectLink; import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.revplot.PlotCommit; -import org.eclipse.swt.custom.StyleRange; -import org.eclipse.swt.graphics.Color; class FormatJob extends Job { @@ -57,8 +54,7 @@ class FormatJob extends Job { protected IStatus run(IProgressMonitor monitor) { if(monitor.isCanceled()) return Status.CANCEL_STATUS; - final List<StyleRange> styles = new ArrayList<StyleRange>(); - final String commitInfo; + final FormatResult commitInfo; CommitInfoBuilder builder; try { synchronized(lock) { @@ -67,47 +63,21 @@ class FormatJob extends Job { builder = new CommitInfoBuilder(formatRequest.getRepository(), commit, formatRequest.isFill(), formatRequest.getAllRefs()); - builder.setColors(formatRequest.getLinkColor(), - formatRequest.getDarkGrey()); } - commitInfo = builder.format(styles, monitor); + commitInfo = builder.format(monitor); } catch (IOException e) { return Activator.createErrorStatus(e.getMessage(), e); } - final StyleRange[] arr = new StyleRange[styles.size()]; - styles.toArray(arr); - Arrays.sort(arr, new Comparator<StyleRange>() { - @Override - public int compare(StyleRange o1, StyleRange o2) { - return o1.start - o2.start; - } - }); if(monitor.isCanceled()) return Status.CANCEL_STATUS; synchronized(lock) { - formatResult = new FormatResult(commitInfo, arr); + formatResult = commitInfo; } return Status.OK_STATUS; } static class FormatRequest { - public Color getLinkColor() { - return linkColor; - } - - public void setLinkColor(Color linkColor) { - this.linkColor = linkColor; - } - - public Color getDarkGrey() { - return darkGrey; - } - - public void setDarkGrey(Color darkGrey) { - this.darkGrey = darkGrey; - } - public Collection<Ref> getAllRefs() { return allRefs; } @@ -122,20 +92,13 @@ class FormatJob extends Job { private boolean fill; - private Color linkColor; - - private Color darkGrey; - private Collection<Ref> allRefs; - FormatRequest(Repository repository, PlotCommit<?> commit, - boolean fill, Color linkColor, Color darkGrey, + FormatRequest(Repository repository, PlotCommit<?> commit, boolean fill, Collection<Ref> allRefs) { this.repository = repository; this.commit = commit; this.fill = fill; - this.linkColor = linkColor; - this.darkGrey = darkGrey; this.allRefs = allRefs; } @@ -154,20 +117,36 @@ class FormatJob extends Job { } static class FormatResult{ - String commitInfo; - StyleRange[] styleRange; + private final String commitInfo; + + private final List<ObjectLink> knownLinks; + + private final int headerEnd; + + private final int footerStart; - FormatResult(String commmitInfo, StyleRange[] styleRange) { + FormatResult(String commmitInfo, List<ObjectLink> links, + int headerEnd, int footerStart) { this.commitInfo = commmitInfo; - this.styleRange = styleRange; + this.knownLinks = links; + this.headerEnd = headerEnd; + this.footerStart = footerStart; } public String getCommitInfo() { return commitInfo; } - public StyleRange[] getStyleRange() { - return styleRange; + public List<ObjectLink> getKnownLinks() { + return knownLinks; + } + + public int getHeaderEnd() { + return headerEnd; + } + + public int getFooterStart() { + return footerStart; } } 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 a553da0d65..9ca47acbab 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 @@ -8,6 +8,7 @@ * Copyright (C) 2012-2013 Robin Stocker <robin@nibor.org> * Copyright (C) 2012, François Rey <eclipse.org_@_francois_._rey_._name> * Copyright (C) 2015, IBM Corporation (Dani Megert <daniel_megert@ch.ibm.com>) + * 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 @@ -23,6 +24,8 @@ import java.util.HashSet; import java.util.List; import java.util.Map.Entry; import java.util.Set; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import org.eclipse.core.resources.IResource; import org.eclipse.core.resources.IWorkspaceRoot; @@ -46,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.HyperlinkTokenScanner; import org.eclipse.egit.ui.internal.repository.tree.AdditionalRefNode; import org.eclipse.egit.ui.internal.repository.tree.FileNode; import org.eclipse.egit.ui.internal.repository.tree.FolderNode; @@ -69,13 +73,19 @@ import org.eclipse.jface.resource.ImageDescriptor; import org.eclipse.jface.resource.JFaceResources; import org.eclipse.jface.resource.LocalResourceManager; import org.eclipse.jface.resource.ResourceManager; +import org.eclipse.jface.text.BadLocationException; import org.eclipse.jface.text.Document; import org.eclipse.jface.text.IDocument; +import org.eclipse.jface.text.IRegion; import org.eclipse.jface.text.ITextListener; +import org.eclipse.jface.text.TextAttribute; import org.eclipse.jface.text.TextEvent; import org.eclipse.jface.text.hyperlink.IHyperlinkDetector; -import org.eclipse.jface.text.hyperlink.IHyperlinkPresenter; -import org.eclipse.jface.text.hyperlink.MultipleHyperlinkPresenter; +import org.eclipse.jface.text.presentation.IPresentationReconciler; +import org.eclipse.jface.text.presentation.PresentationReconciler; +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.util.IPropertyChangeListener; import org.eclipse.jface.util.PropertyChangeEvent; @@ -902,23 +912,62 @@ public class GitHistoryPage extends HistoryPage implements RefsChangedListener, } @Override - public IHyperlinkPresenter getHyperlinkPresenter( + public IHyperlinkDetector[] getHyperlinkDetectors( ISourceViewer sourceViewer) { - return new MultipleHyperlinkPresenter(PlatformUI.getWorkbench() - .getDisplay().getSystemColor(SWT.COLOR_BLUE).getRGB()) { - - @Override - public void hideHyperlinks() { - // We want links to always show. - } + IHyperlinkDetector[] registered = getRegisteredHyperlinkDetectors( + 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; + } - }; + @Override + public String[] getConfiguredContentTypes( + ISourceViewer sourceViewer) { + return new String[] { IDocument.DEFAULT_CONTENT_TYPE, + CommitMessageViewer.HEADER_CONTENT_TYPE, + CommitMessageViewer.FOOTER_CONTENT_TYPE }; } @Override - public IHyperlinkDetector[] getHyperlinkDetectors(ISourceViewer sourceViewer) { - return getRegisteredHyperlinkDetectors(sourceViewer); + public IPresentationReconciler getPresentationReconciler( + ISourceViewer viewer) { + PresentationReconciler reconciler = new PresentationReconciler(); + reconciler.setDocumentPartitioning( + getConfiguredDocumentPartitioning(commentViewer)); + DefaultDamagerRepairer hyperlinkDamagerRepairer = new DefaultDamagerRepairer( + new HyperlinkTokenScanner( + getHyperlinkDetectors(commentViewer), + commentViewer)); + reconciler.setDamager(hyperlinkDamagerRepairer, + IDocument.DEFAULT_CONTENT_TYPE); + reconciler.setRepairer(hyperlinkDamagerRepairer, + IDocument.DEFAULT_CONTENT_TYPE); + TextAttribute headerDefault = new TextAttribute( + PlatformUI.getWorkbench().getDisplay() + .getSystemColor(SWT.COLOR_DARK_GRAY)); + DefaultDamagerRepairer headerDamagerRepairer = new DefaultDamagerRepairer( + new HyperlinkTokenScanner( + getHyperlinkDetectors(commentViewer), + commentViewer, headerDefault)); + reconciler.setDamager(headerDamagerRepairer, + CommitMessageViewer.HEADER_CONTENT_TYPE); + reconciler.setRepairer(headerDamagerRepairer, + CommitMessageViewer.HEADER_CONTENT_TYPE); + DefaultDamagerRepairer footerDamagerRepairer = new DefaultDamagerRepairer( + new FooterTokenScanner( + getHyperlinkDetectors(commentViewer), + commentViewer)); + reconciler.setDamager(footerDamagerRepairer, + CommitMessageViewer.FOOTER_CONTENT_TYPE); + reconciler.setRepairer(footerDamagerRepairer, + CommitMessageViewer.FOOTER_CONTENT_TYPE); + return reconciler; } + }; commentViewer.configure(configuration); @@ -2346,4 +2395,52 @@ public class GitHistoryPage extends HistoryPage implements RefsChangedListener, public String getRenamedPath(String path, ObjectId commit) { return renameTracker.getPath(commit, path); } + + private static class FooterTokenScanner extends HyperlinkTokenScanner { + + private static final Pattern ITALIC_LINE = Pattern + .compile("^[A-Z](?:[A-Za-z]+-)+by: "); //$NON-NLS-1$ + + private final IToken italicToken; + + public FooterTokenScanner(IHyperlinkDetector[] hyperlinkDetectors, + ISourceViewer viewer) { + super(hyperlinkDetectors, viewer); + Object defaults = defaultToken.getData(); + TextAttribute italic; + if (defaults instanceof TextAttribute) { + TextAttribute defaultAttribute = (TextAttribute) defaults; + int style = defaultAttribute.getStyle() ^ SWT.ITALIC; + italic = new TextAttribute(defaultAttribute.getForeground(), + defaultAttribute.getBackground(), style, + defaultAttribute.getFont()); + } else { + italic = new TextAttribute(null, null, SWT.ITALIC); + } + italicToken = new Token(italic); + } + + @Override + protected IToken scanToken() { + // If we're at a "Signed-off-by" or similar footer line, make it + // italic. + try { + IRegion region = document + .getLineInformationOfOffset(currentOffset); + if (currentOffset == region.getOffset()) { + String line = document.get(currentOffset, + region.getLength()); + Matcher m = ITALIC_LINE.matcher(line); + if (m.find()) { + currentOffset = Math.min(endOfRange, + currentOffset + region.getLength()); + return italicToken; + } + } + } catch (BadLocationException e) { + // Ignore and return null below. + } + return null; + } + } } |