Skip to main content
aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--org.eclipse.egit.ui.test/src/org/eclipse/egit/ui/test/commit/DiffRegionFormatterTest.java1
-rw-r--r--org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/UIText.java3
-rw-r--r--org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/commit/DiffDocument.java38
-rw-r--r--org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/commit/DiffEditorPage.java57
-rw-r--r--org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/commit/DiffRegionFormatter.java163
-rw-r--r--org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/commit/DiffViewer.java30
-rw-r--r--org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/commit/ILogicalLineNumberProvider.java32
-rw-r--r--org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/commit/LogicalLineNumberProvider.java57
-rw-r--r--org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/commit/LogicalLineNumberRulerColumn.java72
-rw-r--r--org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/commit/OldNewLogicalLineNumberRulerColumn.java234
-rw-r--r--org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/uitext.properties1
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

Back to the top