diff options
11 files changed, 622 insertions, 66 deletions
diff --git a/org.eclipse.egit.ui.test/src/org/eclipse/egit/ui/test/commit/DiffRegionFormatterTest.java b/org.eclipse.egit.ui.test/src/org/eclipse/egit/ui/test/commit/DiffRegionFormatterTest.java index 2d9f25b0e0..07b0036d1e 100644 --- a/org.eclipse.egit.ui.test/src/org/eclipse/egit/ui/test/commit/DiffRegionFormatterTest.java +++ b/org.eclipse.egit.ui.test/src/org/eclipse/egit/ui/test/commit/DiffRegionFormatterTest.java @@ -65,7 +65,6 @@ public class DiffRegionFormatterTest extends LocalRepositoryTestCase { assertTrue(regions.length > 0); for (DiffRegion region : regions) { assertNotNull(region); - assertNotNull(region.diffType); assertTrue(region.getOffset() >= 0); assertTrue(region.getLength() >= 0); assertTrue(region.getOffset() < document.getLength()); diff --git a/org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/UIText.java b/org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/UIText.java index baa8b72c0f..e0ff828d13 100644 --- a/org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/UIText.java +++ b/org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/UIText.java @@ -3655,6 +3655,9 @@ public class UIText extends NLS { public static String DiffEditorPage_Title; /** */ + public static String DiffEditorPage_ToggleLineNumbers; + + /** */ public static String DiscardChangesAction_confirmActionTitle; /** */ diff --git a/org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/commit/DiffDocument.java b/org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/commit/DiffDocument.java index 2a0790d26e..c28820d908 100644 --- a/org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/commit/DiffDocument.java +++ b/org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/commit/DiffDocument.java @@ -15,6 +15,7 @@ import org.eclipse.core.runtime.Assert; import org.eclipse.egit.ui.internal.commit.DiffRegionFormatter.DiffRegion; import org.eclipse.egit.ui.internal.commit.DiffRegionFormatter.FileDiffRegion; import org.eclipse.egit.ui.internal.history.FileDiff; +import org.eclipse.jface.text.BadLocationException; import org.eclipse.jface.text.Document; import org.eclipse.jface.text.IDocument; import org.eclipse.jface.text.IDocumentPartitioner; @@ -23,6 +24,7 @@ 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.jgit.annotations.NonNull; import org.eclipse.jgit.diff.DiffEntry; import org.eclipse.jgit.lib.Repository; @@ -56,6 +58,8 @@ public class DiffDocument extends Document { private FileDiff defaultFileDiff; + private int[] maximumLineNumbers; + /** * Creates a new {@link DiffDocument}. */ @@ -95,6 +99,7 @@ public class DiffDocument extends Document { Pattern.quote(formatter.getNewPrefix()) + "\\S+"); //$NON-NLS-1$ oldPathPattern = Pattern.compile( Pattern.quote(formatter.getOldPrefix()) + "\\S+"); //$NON-NLS-1$ + maximumLineNumbers = formatter.getMaximumLineNumbers(); // Connect a new partitioner. IDocumentPartitioner partitioner = new FastPartitioner( new DiffPartitionTokenScanner(), @@ -133,6 +138,16 @@ public class DiffDocument extends Document { return fileRegions; } + int getMaximumLineNumber(@NonNull DiffEntry.Side side) { + if (maximumLineNumbers == null) { + return DiffRegion.NO_LINE; + } + if (DiffEntry.Side.OLD.equals(side)) { + return maximumLineNumbers[0]; + } + return maximumLineNumbers[1]; + } + private int findRegionIndex(int offset) { DiffRegion key = new DiffRegion(offset, 0); return Arrays.binarySearch(regions, key, (a, b) -> { @@ -159,7 +174,26 @@ public class DiffDocument extends Document { return i >= 0 ? fileRegions[i] : null; } - Pattern getPathPattern(DiffEntry.Side side) { + int getLogicalLine(int physicalLine, @NonNull DiffEntry.Side side) { + int offset; + try { + offset = getLineOffset(physicalLine); + DiffRegion region = findRegion(offset); + if (region == null) { + return DiffRegion.NO_LINE; + } + int logicalStart = region.getLine(side); + if (logicalStart == DiffRegion.NO_LINE) { + return DiffRegion.NO_LINE; + } + int physicalStart = getLineOfOffset(region.getOffset()); + return logicalStart + (physicalLine - physicalStart); + } catch (BadLocationException e) { + return DiffRegion.NO_LINE; + } + } + + Pattern getPathPattern(@NonNull DiffEntry.Side side) { switch (side) { case OLD: return oldPathPattern; @@ -224,7 +258,7 @@ public class DiffDocument extends Document { - (currentOffset - DiffDocument.this.regions[currIdx] .getOffset()); - switch (DiffDocument.this.regions[currIdx++].diffType) { + switch (DiffDocument.this.regions[currIdx++].getType()) { case HEADLINE: return HEADLINE_TOKEN; case HUNK: diff --git a/org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/commit/DiffEditorPage.java b/org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/commit/DiffEditorPage.java index 4a556ebcf9..cee94a3afe 100644 --- a/org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/commit/DiffEditorPage.java +++ b/org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/commit/DiffEditorPage.java @@ -35,6 +35,10 @@ import org.eclipse.egit.ui.internal.commit.DiffRegionFormatter.DiffRegion; import org.eclipse.egit.ui.internal.commit.DiffRegionFormatter.FileDiffRegion; import org.eclipse.egit.ui.internal.history.FileDiff; import org.eclipse.egit.ui.internal.repository.RepositoriesView; +import org.eclipse.jface.action.Action; +import org.eclipse.jface.action.ActionContributionItem; +import org.eclipse.jface.action.IAction; +import org.eclipse.jface.action.IContributionItem; import org.eclipse.jface.action.IMenuManager; import org.eclipse.jface.operation.IRunnableContext; import org.eclipse.jface.preference.IPreferenceStore; @@ -51,6 +55,7 @@ import org.eclipse.jface.text.source.IAnnotationModel; import org.eclipse.jface.text.source.IAnnotationModelExtension; import org.eclipse.jface.text.source.ISourceViewer; import org.eclipse.jface.text.source.IVerticalRuler; +import org.eclipse.jface.text.source.IVerticalRulerColumn; import org.eclipse.jface.text.source.projection.ProjectionAnnotation; import org.eclipse.jface.text.source.projection.ProjectionSupport; import org.eclipse.jface.text.source.projection.ProjectionViewer; @@ -75,6 +80,7 @@ import org.eclipse.ui.part.IShowInSource; import org.eclipse.ui.part.IShowInTargetList; import org.eclipse.ui.part.ShowInContext; import org.eclipse.ui.progress.UIJob; +import org.eclipse.ui.texteditor.AbstractDecoratedTextEditorPreferenceConstants; import org.eclipse.ui.texteditor.AbstractDocumentProvider; import org.eclipse.ui.texteditor.ChainedPreferenceStore; import org.eclipse.ui.texteditor.IDocumentProvider; @@ -115,6 +121,10 @@ public class DiffEditorPage extends TextEditor private FileDiffRegion currentFileDiffRange; + private OldNewLogicalLineNumberRulerColumn lineNumberColumn; + + private boolean plainLineNumbers = false; + /** * Creates a new {@link DiffEditorPage} with the given id and title, which * is shown on the page's tab. @@ -202,7 +212,7 @@ public class DiffEditorPage extends TextEditor ProjectionSupport projector = new ProjectionSupport(viewer, getAnnotationAccess(), getSharedColors()); projector.install(); - viewer.getTextWidget().addCaretListener((event) -> { + viewer.getTextWidget().addCaretListener(event -> { if (outlinePage != null) { FileDiffRegion region = getFileDiffRange(event.caretOffset); if (region != null && !region.equals(currentFileDiffRange)) { @@ -217,6 +227,14 @@ public class DiffEditorPage extends TextEditor } @Override + protected IVerticalRulerColumn createLineNumberRulerColumn() { + lineNumberColumn = new OldNewLogicalLineNumberRulerColumn( + plainLineNumbers); + initializeLineNumberRulerColumn(lineNumberColumn); + return lineNumberColumn; + } + + @Override protected void initializeEditor() { super.initializeEditor(); overviewStore = new ThemePreferenceStore(); @@ -282,6 +300,39 @@ public class DiffEditorPage extends TextEditor menu.remove(ITextEditorActionConstants.SHIFT_LEFT); } + @Override + protected void rulerContextMenuAboutToShow(IMenuManager menu) { + super.rulerContextMenuAboutToShow(menu); + // AbstractDecoratedTextEditor's menu presumes a + // LineNumberChangeRulerColumn, which we don't have. + IContributionItem showLineNumbers = menu + .find(ITextEditorActionConstants.LINENUMBERS_TOGGLE); + boolean isShowingLineNumbers = EditorsUI.getPreferenceStore() + .getBoolean( + AbstractDecoratedTextEditorPreferenceConstants.EDITOR_LINE_NUMBER_RULER); + if (showLineNumbers instanceof ActionContributionItem) { + ((ActionContributionItem) showLineNumbers).getAction() + .setChecked(isShowingLineNumbers); + } + if (isShowingLineNumbers) { + // Add an action to toggle between physical and logical line numbers + boolean plain = lineNumberColumn.isPlain(); + IAction togglePlain = new Action( + UIText.DiffEditorPage_ToggleLineNumbers, + IAction.AS_CHECK_BOX) { + + @Override + public void run() { + plainLineNumbers = !plain; + lineNumberColumn.setPlain(!plain); + } + }; + togglePlain.setChecked(!plain); + menu.appendToGroup(ITextEditorActionConstants.GROUP_RULERS, + togglePlain); + } + } + // FormPage specifics: @Override @@ -430,11 +481,11 @@ public class DiffEditorPage extends TextEditor } Map<Annotation, Position> newAnnotations = new HashMap<>(); for (DiffRegion region : diffs) { - if (DiffRegion.Type.ADD.equals(region.diffType)) { + if (DiffRegion.Type.ADD.equals(region.getType())) { newAnnotations.put( new Annotation(ADD_ANNOTATION_TYPE, true, null), new Position(region.getOffset(), region.getLength())); - } else if (DiffRegion.Type.REMOVE.equals(region.diffType)) { + } else if (DiffRegion.Type.REMOVE.equals(region.getType())) { newAnnotations.put( new Annotation(REMOVE_ANNOTATION_TYPE, true, null), new Position(region.getOffset(), region.getLength())); diff --git a/org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/commit/DiffRegionFormatter.java b/org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/commit/DiffRegionFormatter.java index 9f4118335b..2feef3f827 100644 --- a/org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/commit/DiffRegionFormatter.java +++ b/org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/commit/DiffRegionFormatter.java @@ -24,6 +24,8 @@ import org.eclipse.egit.ui.internal.history.FileDiff; import org.eclipse.jface.text.BadLocationException; import org.eclipse.jface.text.IDocument; import org.eclipse.jface.text.Region; +import org.eclipse.jgit.annotations.NonNull; +import org.eclipse.jgit.diff.DiffEntry; import org.eclipse.jgit.diff.DiffEntry.ChangeType; import org.eclipse.jgit.diff.DiffFormatter; import org.eclipse.jgit.diff.EditList; @@ -39,68 +41,90 @@ import org.eclipse.osgi.util.NLS; public class DiffRegionFormatter extends DiffFormatter { /** - * Diff style range + * A text {@link Region} describing an interesting region in a unified diff. */ public static class DiffRegion extends Region { + /** Constant {@value} indicating that no line number exists. */ + public static final int NO_LINE = -1; + /** - * Diff type + * The type of a {@link DiffRegion}. */ public enum Type { - /** - * Added line - */ + /** Added line. */ ADD, - /** - * Removed line - */ + /** Removed line. */ REMOVE, - /** - * Hunk line - */ + /** Hunk line. */ HUNK, - /** - * Headline - */ + /** Headline. */ HEADLINE, - /** - * Header (after HEADLINE) - */ + /** Header (after HEADLINE). */ HEADER, - /** - * Other line - */ + /** A context line in a hunk. */ + CONTEXT, + + /** Other line. */ OTHER, } - /** - * Diff type - */ - public Type diffType = Type.OTHER; + private final @NonNull Type type; + + private final int aLine; + + private final int bLine; /** * @param offset * @param length */ public DiffRegion(int offset, int length) { - super(offset, length); + this(offset, length, NO_LINE, NO_LINE, Type.OTHER); } /** * @param offset * @param length + * @param aLine + * @param bLine * @param type */ - public DiffRegion(int offset, int length, Type type) { + public DiffRegion(int offset, int length, int aLine, int bLine, + @NonNull Type type) { super(offset, length); - this.diffType = type; + this.type = type; + this.aLine = aLine; + this.bLine = bLine; + } + + /** + * @return the {@link Type} of the region + */ + public @NonNull Type getType() { + return type; + } + + /** + * Returns the first logical line number of the region. + * + * @param side + * to get the line number of + * @return the line number; -1 indicates that the range has no line + * number for the given side. + */ + public int getLine(@NonNull DiffEntry.Side side) { + if (DiffEntry.Side.NEW.equals(side)) { + return bLine; + } + return aLine; } @Override @@ -243,6 +267,11 @@ public class DiffRegionFormatter extends DiffFormatter { private int linesWritten; + private int lastNewLine; + + private int[] maximumLineNumbers = new int[] { DiffRegion.NO_LINE, + DiffRegion.NO_LINE }; + /** * @param document * @param offset @@ -268,6 +297,7 @@ public class DiffRegionFormatter extends DiffFormatter { super(new DocumentOutputStream(document, offset)); this.stream = (DocumentOutputStream) getOutputStream(); this.maxLines = maxLines; + this.lastNewLine = DiffRegion.NO_LINE; } /** @@ -311,7 +341,37 @@ public class DiffRegionFormatter extends DiffFormatter { } /** - * Create and add diff style range + * Retrieves the maximum line numbers for hunk lines. + * + * @return an array with two elements, index 0 being the maximum old line + * number and index 1 the maximum new line number + */ + public int[] getMaximumLineNumbers() { + return maximumLineNumbers.clone(); + } + + /** + * Create and add a new {@link DiffRegion} without line number information, + * coalescing it with the previous region,if any, if that has the same type + * and the two regions are adjacent. + * + * @param type + * the {@link Type} + * @param start + * start offset + * @param end + * end offset + * @return added range + */ + protected DiffRegion addRegion(@NonNull Type type, int start, int end) { + return addRegion(type, start, end, DiffRegion.NO_LINE, + DiffRegion.NO_LINE); + } + + /** + * Create and add a new {@link DiffRegion}, coalescing it with the previous + * region,if any, if that has the same type and the two regions are + * adjacent. * * @param type * the {@link Type} @@ -319,26 +379,35 @@ public class DiffRegionFormatter extends DiffFormatter { * start offset * @param end * end offset + * @param aLine + * line number in the old version, or {@link DiffRegion#NO_LINE} + * @param bLine + * line number in the new version, or {@link DiffRegion#NO_LINE} * @return added range */ - protected DiffRegion addRegion(Type type, int start, int end) { + protected DiffRegion addRegion(@NonNull Type type, int start, int end, + int aLine, int bLine) { + maximumLineNumbers[0] = Math.max(aLine, maximumLineNumbers[0]); + maximumLineNumbers[1] = Math.max(bLine, maximumLineNumbers[1]); + if (bLine != DiffRegion.NO_LINE) { + lastNewLine = bLine; + } if (!regions.isEmpty()) { DiffRegion last = regions.get(regions.size() - 1); - if (last.diffType.equals(type) + if (last.getType().equals(type) && start == last.getOffset() + last.getLength()) { regions.remove(regions.size() - 1); start = last.getOffset(); + aLine = last.getLine(DiffEntry.Side.OLD); + bLine = last.getLine(DiffEntry.Side.NEW); } } - DiffRegion range = new DiffRegion(start, end - start, type); + DiffRegion range = new DiffRegion(start, end - start, aLine, bLine, + type); regions.add(range); return range; } - /** - * @see org.eclipse.jgit.diff.DiffFormatter#writeHunkHeader(int, int, int, - * int) - */ @Override protected void writeHunkHeader(int aStartLine, int aEndLine, int bStartLine, int bEndLine) throws IOException { @@ -346,19 +415,16 @@ public class DiffRegionFormatter extends DiffFormatter { if (!regions.isEmpty()) { DiffRegion last = regions.get(regions.size() - 1); int lastEnd = last.getOffset() + last.getLength(); - if (last.diffType == Type.HEADLINE && lastEnd < start) { + if (last.getType().equals(Type.HEADLINE) && lastEnd < start) { addRegion(Type.HEADER, lastEnd, start); } } super.writeHunkHeader(aStartLine, aEndLine, bStartLine, bEndLine); stream.flushLine(); addRegion(Type.HUNK, start, stream.offset); + lastNewLine = bStartLine - 1; } - /** - * @see org.eclipse.jgit.diff.DiffFormatter#writeLine(char, - * org.eclipse.jgit.diff.RawText, int) - */ @Override protected void writeLine(char prefix, RawText text, int cur) throws IOException { @@ -375,15 +441,18 @@ public class DiffRegionFormatter extends DiffFormatter { } return; } + + int start = stream.offset; + super.writeLine(prefix, text, cur); + stream.flushLine(); if (prefix == ' ') { - super.writeLine(prefix, text, cur); - stream.flushLine(); + addRegion(Type.CONTEXT, start, stream.offset, cur, ++lastNewLine); + } else if (prefix == '+') { + addRegion(Type.ADD, start, stream.offset, DiffRegion.NO_LINE, cur); + } else { - Type type = prefix == '+' ? Type.ADD : Type.REMOVE; - int start = stream.offset; - super.writeLine(prefix, text, cur); - stream.flushLine(); - addRegion(type, start, stream.offset); + addRegion(Type.REMOVE, start, stream.offset, cur, + DiffRegion.NO_LINE); } linesWritten++; } diff --git a/org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/commit/DiffViewer.java b/org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/commit/DiffViewer.java index a20a06b57d..0d2ec9794a 100644 --- a/org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/commit/DiffViewer.java +++ b/org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/commit/DiffViewer.java @@ -82,6 +82,7 @@ import org.eclipse.jface.text.source.IVerticalRuler; import org.eclipse.jface.text.source.SourceViewerConfiguration; import org.eclipse.jface.util.IPropertyChangeListener; import org.eclipse.jface.util.PropertyChangeEvent; +import org.eclipse.jgit.annotations.NonNull; import org.eclipse.jgit.diff.DiffEntry; import org.eclipse.jgit.diff.DiffEntry.ChangeType; import org.eclipse.jgit.lib.ObjectId; @@ -486,7 +487,7 @@ public class DiffViewer extends HyperlinkSourceViewer { continue; } // Range overlaps region - switch (range.diffType) { + switch (range.getType()) { case HEADLINE: fileRange = findFileRange(diffDocument, fileRange, range.getOffset()); @@ -564,21 +565,24 @@ public class DiffViewer extends HyperlinkSourceViewer { private int getContextLines(IDocument document, DiffRegion hunk, DiffRegion next) { if (next != null) { - switch (next.diffType) { - case ADD: - case REMOVE: - try { - int diffLine = document - .getLineOfOffset(next.getOffset()); + try { + switch (next.getType()) { + case CONTEXT: + int nofLines = document.getNumberOfLines( + next.getOffset(), next.getLength()); + return nofLines - 1; + case ADD: + case REMOVE: int hunkLine = document .getLineOfOffset(hunk.getOffset()); + int diffLine = document + .getLineOfOffset(next.getOffset()); return diffLine - hunkLine - 1; - } catch (BadLocationException e) { - // Ignore + default: + break; } - break; - default: - break; + } catch (BadLocationException e) { + // Ignore } } return 0; @@ -595,7 +599,7 @@ public class DiffViewer extends HyperlinkSourceViewer { private void createHeaderLinks(DiffDocument document, IRegion region, FileDiffRegion fileRange, DiffRegion range, String line, - DiffEntry.Side side, List<IHyperlink> links) { + @NonNull DiffEntry.Side side, List<IHyperlink> links) { Pattern p = document.getPathPattern(side); if (p == null) { return; diff --git a/org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/commit/ILogicalLineNumberProvider.java b/org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/commit/ILogicalLineNumberProvider.java new file mode 100644 index 0000000000..65c478f129 --- /dev/null +++ b/org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/commit/ILogicalLineNumberProvider.java @@ -0,0 +1,32 @@ +/******************************************************************************* + * Copyright (C) 2016, 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.commit; + +/** + * Something that can translate physical to logical line numbers. + */ +public interface ILogicalLineNumberProvider { + + /** + * Translates a physical line number to a logical one. + * + * @param lineNumber + * of the physical line + * @return the logical line number, or -1 if none + */ + int getLogicalLine(int lineNumber); + + /** + * Determines the largest line number this + * {@link ILogicalLineNumberProvider} will return. + * + * @return the maximum line number, or -1 if unknown + */ + int getMaximum(); +} diff --git a/org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/commit/LogicalLineNumberProvider.java b/org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/commit/LogicalLineNumberProvider.java new file mode 100644 index 0000000000..0851e95059 --- /dev/null +++ b/org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/commit/LogicalLineNumberProvider.java @@ -0,0 +1,57 @@ +/******************************************************************************* + * Copyright (C) 2016, 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.commit; + +import org.eclipse.jface.text.IDocument; +import org.eclipse.jface.text.ITextViewer; +import org.eclipse.jgit.annotations.NonNull; +import org.eclipse.jgit.diff.DiffEntry; + +/** + * An {@link ILogicalLineNumberProvider} that uses a viewer's + * {@link DiffDocument} to translate from physical to logical line numbers. + */ +public class LogicalLineNumberProvider implements ILogicalLineNumberProvider { + + private final @NonNull DiffEntry.Side side; + + private final @NonNull ITextViewer viewer; + + /** + * Creates a new {@link LogicalLineNumberProvider}. + * + * @param side + * of the diff to report line numbers for + * @param viewer + * to get the document from + */ + public LogicalLineNumberProvider(@NonNull DiffEntry.Side side, + @NonNull ITextViewer viewer) { + this.side = side; + this.viewer = viewer; + } + + @Override + public int getLogicalLine(int lineNumber) { + IDocument document = viewer.getDocument(); + if (document instanceof DiffDocument) { + return ((DiffDocument) document).getLogicalLine(lineNumber, side); + } + return lineNumber; + } + + @Override + public int getMaximum() { + IDocument document = viewer.getDocument(); + if (document instanceof DiffDocument) { + return ((DiffDocument) document).getMaximumLineNumber(side); + } + return -1; + } +} diff --git a/org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/commit/LogicalLineNumberRulerColumn.java b/org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/commit/LogicalLineNumberRulerColumn.java new file mode 100644 index 0000000000..deccdfafdd --- /dev/null +++ b/org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/commit/LogicalLineNumberRulerColumn.java @@ -0,0 +1,72 @@ +/******************************************************************************* + * Copyright (C) 2016, 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.commit; + +import org.eclipse.core.runtime.Assert; +import org.eclipse.jface.text.ITextViewer; +import org.eclipse.jface.text.source.LineNumberRulerColumn; +import org.eclipse.jgit.annotations.NonNull; +import org.eclipse.jgit.diff.DiffEntry; + +/** + * A {@link LineNumberRulerColumn} that uses an + * {@link ILogicalLineNumberProvider} to determine the line numbers to show. + */ +public class LogicalLineNumberRulerColumn extends LineNumberRulerColumn { + + private ILogicalLineNumberProvider logicalLineNumberProvider; + + private final @NonNull DiffEntry.Side side; + + /** + * @param side + */ + public LogicalLineNumberRulerColumn(@NonNull DiffEntry.Side side) { + this.side = side; + } + + /** + * @return the {@link DiffEntry} side + */ + protected @NonNull DiffEntry.Side getSide() { + return side; + } + + /** + * Retrieves the {@link ILogicalLineNumberProvider} to use. This + * implementation returns a {@link LogicalLineNumberProvider}. + * + * @return the {@link ILogicalLineNumberProvider} + */ + protected ILogicalLineNumberProvider getLogicalLineNumberProvider() { + if (logicalLineNumberProvider == null) { + ITextViewer viewer = getParentRuler().getTextViewer(); + Assert.isNotNull(viewer); + logicalLineNumberProvider = new LogicalLineNumberProvider(getSide(), + viewer); + } + return logicalLineNumberProvider; + } + + @Override + protected String createDisplayString(int line) { + int logicalLine = getLogicalLineNumberProvider().getLogicalLine(line); + return logicalLine < 0 ? "" : Integer.toString(logicalLine + 1); //$NON-NLS-1$ + } + + @Override + protected int computeNumberOfDigits() { + int max = getLogicalLineNumberProvider().getMaximum(); + int digits = 2; + while (max > Math.pow(10, digits) - 1) { + ++digits; + } + return digits; + } +} diff --git a/org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/commit/OldNewLogicalLineNumberRulerColumn.java b/org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/commit/OldNewLogicalLineNumberRulerColumn.java new file mode 100644 index 0000000000..a7c68fce78 --- /dev/null +++ b/org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/commit/OldNewLogicalLineNumberRulerColumn.java @@ -0,0 +1,234 @@ +/******************************************************************************* + * Copyright (C) 2016, 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.commit; + +import org.eclipse.jface.resource.JFaceResources; +import org.eclipse.jface.resource.LocalResourceManager; +import org.eclipse.jface.resource.ResourceManager; +import org.eclipse.jface.text.source.CompositeRuler; +import org.eclipse.jface.text.source.IAnnotationModel; +import org.eclipse.jface.text.source.LineNumberRulerColumn; +import org.eclipse.jgit.diff.DiffEntry; +import org.eclipse.swt.graphics.Color; +import org.eclipse.swt.graphics.Font; +import org.eclipse.swt.graphics.GC; +import org.eclipse.swt.graphics.Rectangle; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Display; +import org.eclipse.ui.themes.ColorUtil; + +/** + * A {@link LineNumberRulerColumn} specialized for a viewer showing a + * {@link DiffDocument}: it can display logical line numbers corresponding to + * the shown diff hunks (both old and new), or alternatively the physical line + * numbers of the unified diff itself. + */ +public class OldNewLogicalLineNumberRulerColumn extends LineNumberRulerColumn { + + /** Container for assembling the actual line number columns. */ + private final CompositeRuler composite = new CompositeRuler(0); + // Must not have any gap, otherwise mouse wheel scrolling won't work when + // the mouse cursor happens to be exactly on the gap. + + /** Standard physical line numbers for plain display. */ + private final LineNumberRulerColumn plainLines = new LineNumberRulerColumn(); + + /** + * Ruler for the old line numbers; draws a vertical line on its right edge + * in order to get a visual separation of old and new line numbers. + * <p> + * Note that the rulers are always on the left side of the viewer, even in + * an RTL layout. They are also always ordered as given by their indices in + * a CompositeRuler -- there is no logic anywhere that would flip the order + * when the layout direction is changed. Thus drawing the line always on the + * right edge of the left ruler is correct in all cases. + * </p> + */ + private final LineNumberRulerColumn oldLines = new LogicalLineNumberRulerColumn( + DiffEntry.Side.OLD) { + + private ResourceManager resourceManager; + + private Color lineColor; + + @Override + public int getWidth() { + // Add space for the line plus one empty pixel on each side of the + // line. + return super.getWidth() + 3; + } + + @Override + protected void paintLine(int line, int y, int lineHeight, GC gc, + Display display) { + super.paintLine(line, y, lineHeight, gc, display); + // Draw a vertical line to separate old numbers from the new ones. + int x = super.getWidth() + 1; + if (lineColor == null) { + if (resourceManager == null) { + resourceManager = new LocalResourceManager( + JFaceResources.getResources()); + } + lineColor = resourceManager.createColor( + ColorUtil.blend(gc.getForeground().getRGB(), + gc.getBackground().getRGB(), 60)); + } + Color foreground = gc.getForeground(); + // Needs to redraw the whole line each time, otherwise it'll have + // gaps when word-wrapping is on. There doesn't seem to be any hook + // available to hook into drawing after all line numbers have been + // drawn. + Rectangle bounds = super.getControl().getBounds(); + gc.setForeground(lineColor); + gc.drawLine(x, 0, x, bounds.height); + gc.setForeground(foreground); + } + + @Override + public void setForeground(Color foreground) { + lineColor = null; + super.setForeground(foreground); + } + + @Override + public void setBackground(Color background) { + lineColor = null; + super.setBackground(background); + } + + @Override + protected void handleDispose() { + super.handleDispose(); + if (resourceManager != null) { + resourceManager.dispose(); + resourceManager = null; + } + lineColor = null; + } + }; + + /** Ruler for the new line numbers. */ + private final LineNumberRulerColumn newLines = new LogicalLineNumberRulerColumn( + DiffEntry.Side.NEW); + + /** + * Current display mode. If {@code true}, showing only physical line + * numbers, otherwise showing both old and new logical line numbers. + */ + private boolean plain; + + /** + * Creates a new {@link OldNewLogicalLineNumberRulerColumn} showing both old + * and new logical line numbers. + */ + public OldNewLogicalLineNumberRulerColumn() { + this(false); + } + + /** + * Creates a new {@link OldNewLogicalLineNumberRulerColumn}. + * + * @param plain + * {@code true} to show physical line numbers only, or + * {@code false} to show both old and new logical line numbers. + */ + public OldNewLogicalLineNumberRulerColumn(boolean plain) { + this.plain = plain; + if (!plain) { + composite.addDecorator(0, oldLines); + composite.addDecorator(1, newLines); + } else { + composite.addDecorator(0, plainLines); + } + } + + @Override + public void setModel(IAnnotationModel model) { + composite.setModel(model); + } + + @Override + public void redraw() { + composite.immediateUpdate(); + } + + @Override + public Control createControl(CompositeRuler parentRuler, + Composite parentControl) { + return composite.createControl(parentControl, + parentRuler.getTextViewer()); + } + + @Override + public Control getControl() { + return composite.getControl(); + } + + @Override + public int getWidth() { + return composite.getWidth(); + } + + @Override + public void setFont(Font font) { + plainLines.setFont(font); + oldLines.setFont(font); + newLines.setFont(font); + } + + @Override + public void setForeground(Color foreground) { + super.setForeground(foreground); + plainLines.setForeground(foreground); + oldLines.setForeground(foreground); + newLines.setForeground(foreground); + } + + @Override + public void setBackground(Color background) { + super.setBackground(background); + plainLines.setBackground(background); + oldLines.setBackground(background); + newLines.setBackground(background); + } + + /** + * Tells whether the column is currently plain, showing only physical line + * numbers. + * + * @return {@code true} if only physical line numbers are shown; + * {@code false} if old and new logical line numbers are shown + */ + public boolean isPlain() { + return plain; + } + + /** + * Switches the mode of the {@link OldNewLogicalLineNumberRulerColumn}. + * + * @param plain + * {@code true} to show only physical line numbers; {@code false} + * to show old and new logical line numbers. + */ + public void setPlain(boolean plain) { + if (this.plain != plain) { + this.plain = plain; + if (!plain) { + composite.removeDecorator(plainLines); + composite.addDecorator(0, oldLines); + composite.addDecorator(1, newLines); + } else { + composite.removeDecorator(oldLines); + composite.removeDecorator(newLines); + composite.addDecorator(0, plainLines); + } + } + } +} diff --git a/org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/uitext.properties b/org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/uitext.properties index 065b9b54d5..65907b14fc 100644 --- a/org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/uitext.properties +++ b/org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/uitext.properties @@ -1291,6 +1291,7 @@ DialogsPreferencePage_ShowCloneFailedDialog=Clone failed error DiffEditorPage_TaskGeneratingDiff=Generating diff DiffEditorPage_TaskUpdatingViewer=Updating diff viewer DiffEditorPage_Title=Diff +DiffEditorPage_ToggleLineNumbers=Use Old/New &Line Numbers DiscardChangesAction_confirmActionTitle=Discard Local Changes DiscardChangesAction_confirmActionMessage=This will discard all local changes for the selected resources. Untracked files will be ignored.{0}\n\nAre you sure you want to do this? DiscardChangesAction_discardChanges=Discard Changes |