/*******************************************************************************
* Copyright (c) 2011, 2016 GitHub Inc. and others.
* 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
*
* Contributors:
* Kevin Sawicki (GitHub Inc.) - initial API and implementation
* Tobias Pfeifer (SAP AG) - customizable font and color for the first header line - https://bugs.eclipse.org/397723
* Thomas Wolf <thomas.wolf@paranor.ch> - add hyperlinks, and use JFace syntax coloring
*******************************************************************************/
package org.eclipse.egit.ui.internal.commit;
import static org.eclipse.egit.ui.UIPreferences.THEME_DiffAddBackgroundColor;
import static org.eclipse.egit.ui.UIPreferences.THEME_DiffAddForegroundColor;
import static org.eclipse.egit.ui.UIPreferences.THEME_DiffHeadlineBackgroundColor;
import static org.eclipse.egit.ui.UIPreferences.THEME_DiffHeadlineFont;
import static org.eclipse.egit.ui.UIPreferences.THEME_DiffHeadlineForegroundColor;
import static org.eclipse.egit.ui.UIPreferences.THEME_DiffHunkBackgroundColor;
import static org.eclipse.egit.ui.UIPreferences.THEME_DiffHunkForegroundColor;
import static org.eclipse.egit.ui.UIPreferences.THEME_DiffRemoveBackgroundColor;
import static org.eclipse.egit.ui.UIPreferences.THEME_DiffRemoveForegroundColor;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Supplier;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.eclipse.compare.ITypedElement;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.Path;
import org.eclipse.egit.core.internal.util.ResourceUtil;
import org.eclipse.egit.ui.Activator;
import org.eclipse.egit.ui.internal.CompareUtils;
import org.eclipse.egit.ui.internal.EgitUiEditorUtils;
import org.eclipse.egit.ui.internal.UIText;
import org.eclipse.egit.ui.internal.commit.DiffRegionFormatter.DiffRegion;
import org.eclipse.egit.ui.internal.commit.DiffRegionFormatter.FileDiffRegion;
import org.eclipse.egit.ui.internal.dialogs.HyperlinkSourceViewer;
import org.eclipse.egit.ui.internal.history.FileDiff;
import org.eclipse.egit.ui.internal.revision.GitCompareFileRevisionEditorInput;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.jface.preference.PreferenceConverter;
import org.eclipse.jface.resource.ColorDescriptor;
import org.eclipse.jface.resource.ColorRegistry;
import org.eclipse.jface.resource.DeviceResourceManager;
import org.eclipse.jface.resource.FontRegistry;
import org.eclipse.jface.resource.JFaceResources;
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.ITextViewer;
import org.eclipse.jface.text.ITypedRegion;
import org.eclipse.jface.text.Region;
import org.eclipse.jface.text.TextAttribute;
import org.eclipse.jface.text.TextUtilities;
import org.eclipse.jface.text.hyperlink.AbstractHyperlinkDetector;
import org.eclipse.jface.text.hyperlink.IHyperlink;
import org.eclipse.jface.text.hyperlink.IHyperlinkDetector;
import org.eclipse.jface.text.hyperlink.IHyperlinkDetectorExtension2;
import org.eclipse.jface.text.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.ITokenScanner;
import org.eclipse.jface.text.rules.Token;
import org.eclipse.jface.text.source.IOverviewRuler;
import org.eclipse.jface.text.source.ISourceViewer;
import org.eclipse.jface.text.source.IVerticalRuler;
import org.eclipse.jface.text.source.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;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.osgi.util.NLS;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.StyledText;
import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.swt.events.DisposeListener;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Layout;
import org.eclipse.team.core.history.IFileRevision;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.IWorkbenchPage;
import org.eclipse.ui.IWorkbenchWindow;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.editors.text.EditorsUI;
import org.eclipse.ui.texteditor.AbstractTextEditor;
import org.eclipse.ui.themes.IThemeManager;
/**
* Source viewer to display one or more file differences using standard editor
* colors and fonts preferences. Should be used together with a
* {@link DiffDocument} to get proper coloring and hyperlink support.
*/
public class DiffViewer extends HyperlinkSourceViewer {
private final DeviceResourceManager colors = new DeviceResourceManager(
PlatformUI.getWorkbench().getDisplay());
private final Map<String, IToken> tokens = new HashMap<>();
private final Map<String, Color> backgroundColors = new HashMap<>();
private IPropertyChangeListener themeListener = new IPropertyChangeListener() {
@Override
public void propertyChange(PropertyChangeEvent event) {
String property = event.getProperty();
if (IThemeManager.CHANGE_CURRENT_THEME.equals(property)
|| THEME_DiffAddBackgroundColor.equals(property)
|| THEME_DiffAddForegroundColor.equals(property)
|| THEME_DiffHunkBackgroundColor.equals(property)
|| THEME_DiffHunkForegroundColor.equals(property)
|| THEME_DiffHeadlineBackgroundColor.equals(property)
|| THEME_DiffHeadlineForegroundColor.equals(property)
|| THEME_DiffHeadlineFont.equals(property)
|| THEME_DiffRemoveBackgroundColor.equals(property)
|| THEME_DiffRemoveForegroundColor.equals(property)) {
refreshDiffStyles();
invalidateTextPresentation();
}
}
};
private IPropertyChangeListener editorPrefListener = new IPropertyChangeListener() {
@Override
public void propertyChange(PropertyChangeEvent event) {
styleViewer();
}
};
/**
* A configuration to use with a {@link DiffViewer}, setting up the syntax
* coloring for a diff and adding the {@link IHyperlinkDetector} for the
* links.
*/
public static class Configuration
extends HyperlinkSourceViewer.Configuration {
/**
* Creates a new {@link Configuration} connected to the given
* {@link IPreferenceStore}.
*
* @param preferenceStore
* to connect to
*/
public Configuration(IPreferenceStore preferenceStore) {
super(preferenceStore);
}
@Override
public int getHyperlinkStateMask(ISourceViewer sourceViewer) {
return SWT.NONE;
}
@Override
protected IHyperlinkDetector[] internalGetHyperlinkDetectors(
ISourceViewer sourceViewer) {
Assert.isTrue(sourceViewer instanceof DiffViewer);
IHyperlinkDetector[] result = { new HyperlinkDetector() };
return result;
}
@Override
public String[] getConfiguredContentTypes(ISourceViewer sourceViewer) {
Assert.isTrue(sourceViewer instanceof DiffViewer);
DiffViewer viewer = (DiffViewer) sourceViewer;
return viewer.tokens.keySet()
.toArray(new String[viewer.tokens.size()]);
}
@Override
public IPresentationReconciler getPresentationReconciler(
ISourceViewer sourceViewer) {
Assert.isTrue(sourceViewer instanceof DiffViewer);
DiffViewer viewer = (DiffViewer) sourceViewer;
PresentationReconciler reconciler = new PresentationReconciler();
reconciler.setDocumentPartitioning(
getConfiguredDocumentPartitioning(viewer));
for (String contentType : viewer.tokens.keySet()) {
DefaultDamagerRepairer damagerRepairer = new DefaultDamagerRepairer(
new SingleTokenScanner(
() -> viewer.tokens.get(contentType)));
reconciler.setDamager(damagerRepairer, contentType);
reconciler.setRepairer(damagerRepairer, contentType);
}
return reconciler;
}
}
/**
* Creates a new {@link DiffViewer}.
*
* @param parent
* to contain the viewer
* @param ruler
* for the viewer (left side)
* @param styles
* for the viewer
*/
public DiffViewer(Composite parent, IVerticalRuler ruler, int styles) {
this(parent, ruler, null, false, styles);
}
/**
* Creates a new {@link DiffViewer}.
*
* @param parent
* to contain the viewer
* @param ruler
* for the viewer (left side)
* @param overviewRuler
* ruler for overview annotations
* @param showsAnnotationOverview
* whether to show overview annotations
* @param styles
* for the viewer
*/
public DiffViewer(Composite parent, IVerticalRuler ruler,
IOverviewRuler overviewRuler, boolean showsAnnotationOverview,
int styles) {
super(parent, ruler, overviewRuler, showsAnnotationOverview, styles);
getTextWidget().setAlwaysShowScrollBars(false);
setEditable(false);
setDocument(new Document());
initListeners();
getControl().addDisposeListener(new DisposeListener() {
@Override
public void widgetDisposed(DisposeEvent e) {
EditorsUI.getPreferenceStore()
.removePropertyChangeListener(editorPrefListener);
PlatformUI.getWorkbench().getThemeManager()
.removePropertyChangeListener(themeListener);
colors.dispose();
}
});
refreshDiffStyles();
styleViewer();
}
@Override
public void configure(SourceViewerConfiguration config) {
Assert.isTrue(config instanceof Configuration);
super.configure(config);
}
@Override
public void refresh() {
// Don't lose the annotation model, if there is one!
// (The super implementation ignores it.)
setDocument(getDocument(), getAnnotationModel());
}
@Override
protected Layout createLayout() {
return new FixedRulerLayout(GAP_SIZE_1);
}
private class FixedRulerLayout extends RulerLayout {
public FixedRulerLayout(int gap) {
super(gap);
}
@Override
protected void layout(Composite composite, boolean flushCache) {
Rectangle bounds = composite.getBounds();
if (bounds.width == 0 || bounds.height == 0) {
// The overview ruler is laid out wrongly in the DiffEditorPage:
// it ends up with a negative y-coordinate. This seems to be
// caused by layout attempts while the page is not visible,
// which cache that bogus negative offset in RulerLayout, which
// will re-use it even when the viewer is laid out again when
// the page is visible. So don't layout if the containing
// composite has no extent.
return;
}
super.layout(composite, flushCache);
}
}
private void refreshDiffStyles() {
ColorRegistry col = PlatformUI.getWorkbench().getThemeManager()
.getCurrentTheme().getColorRegistry();
FontRegistry reg = PlatformUI.getWorkbench().getThemeManager()
.getCurrentTheme().getFontRegistry();
// We do the foreground via syntax coloring and the background via a
// line background listener. If we did the background also via the
// TextAttributes, this would take precedence over the line background
// resulting in strange display if the current line is highlighted:
// that highlighting would appear only beyond the end of the actual
// text content (i.e., beyond the end-of-line), while actual text
// would still get the background from the attribute.
tokens.put(IDocument.DEFAULT_CONTENT_TYPE, new Token(null));
tokens.put(DiffDocument.HEADLINE_CONTENT_TYPE,
new Token(new TextAttribute(
col.get(THEME_DiffHeadlineForegroundColor), null,
SWT.NORMAL, reg.get(THEME_DiffHeadlineFont))));
tokens.put(DiffDocument.HUNK_CONTENT_TYPE, new Token(
new TextAttribute(col.get(THEME_DiffHunkForegroundColor))));
tokens.put(DiffDocument.ADDED_CONTENT_TYPE, new Token(
new TextAttribute(col.get(THEME_DiffAddForegroundColor))));
tokens.put(DiffDocument.REMOVED_CONTENT_TYPE, new Token(
new TextAttribute(col.get(THEME_DiffRemoveForegroundColor))));
backgroundColors.put(DiffDocument.HEADLINE_CONTENT_TYPE,
col.get(THEME_DiffHeadlineBackgroundColor));
backgroundColors.put(DiffDocument.HUNK_CONTENT_TYPE,
col.get(THEME_DiffHunkBackgroundColor));
backgroundColors.put(DiffDocument.ADDED_CONTENT_TYPE,
col.get(THEME_DiffAddBackgroundColor));
backgroundColors.put(DiffDocument.REMOVED_CONTENT_TYPE,
col.get(THEME_DiffRemoveBackgroundColor));
}
private void initListeners() {
PlatformUI.getWorkbench().getThemeManager()
.addPropertyChangeListener(this.themeListener);
EditorsUI.getPreferenceStore().addPropertyChangeListener(
this.editorPrefListener);
getTextWidget().addLineBackgroundListener((event) -> {
IDocument document = getDocument();
if (document instanceof DiffDocument) {
try {
// We are in SWT land here: we get widget offsets.
int modelOffset = widgetOffset2ModelOffset(
event.lineOffset);
ITypedRegion partition = ((DiffDocument) document)
.getPartition(modelOffset);
if (partition != null) {
Color color = backgroundColors.get(partition.getType());
if (color != null) {
event.lineBackground = color;
}
}
} catch (BadLocationException e) {
// Ignore
}
}
});
}
private ColorDescriptor createEditorColorDescriptor(String key) {
return ColorDescriptor.createFrom(PreferenceConverter.getColor(
EditorsUI.getPreferenceStore(), key));
}
private Color getEditorColor(String key) {
return (Color) colors.get(createEditorColorDescriptor(key));
}
private void styleViewer() {
IPreferenceStore store = EditorsUI.getPreferenceStore();
Color foreground = null;
if (!store
.getBoolean(AbstractTextEditor.PREFERENCE_COLOR_FOREGROUND_SYSTEM_DEFAULT))
foreground = getEditorColor(AbstractTextEditor.PREFERENCE_COLOR_FOREGROUND);
Color background = null;
if (!store
.getBoolean(AbstractTextEditor.PREFERENCE_COLOR_BACKGROUND_SYSTEM_DEFAULT))
background = getEditorColor(AbstractTextEditor.PREFERENCE_COLOR_BACKGROUND);
Color selectionForeground = null;
if (!store
.getBoolean(AbstractTextEditor.PREFERENCE_COLOR_SELECTION_FOREGROUND_SYSTEM_DEFAULT))
selectionForeground = getEditorColor(AbstractTextEditor.PREFERENCE_COLOR_SELECTION_FOREGROUND);
Color selectionBackground = null;
if (!store
.getBoolean(AbstractTextEditor.PREFERENCE_COLOR_SELECTION_BACKGROUND_SYSTEM_DEFAULT))
selectionBackground = getEditorColor(AbstractTextEditor.PREFERENCE_COLOR_SELECTION_BACKGROUND);
StyledText text = getTextWidget();
text.setForeground(foreground);
text.setBackground(background);
text.setSelectionForeground(selectionForeground);
text.setSelectionBackground(selectionBackground);
text.setFont(JFaceResources.getFont(JFaceResources.TEXT_FONT));
}
private static class SingleTokenScanner implements ITokenScanner {
private final Supplier<IToken> token;
private int currentOffset;
private int end;
private int tokenStart;
public SingleTokenScanner(Supplier<IToken> supplier) {
this.token = supplier;
}
@Override
public void setRange(IDocument document, int offset, int length) {
currentOffset = offset;
end = offset + length;
tokenStart = -1;
}
@Override
public IToken nextToken() {
tokenStart = currentOffset;
if (currentOffset < end) {
currentOffset = end;
return token.get();
}
return Token.EOF;
}
@Override
public int getTokenOffset() {
return tokenStart;
}
@Override
public int getTokenLength() {
return currentOffset - tokenStart;
}
}
private static class HyperlinkDetector extends AbstractHyperlinkDetector
implements IHyperlinkDetectorExtension2 {
private final Pattern HUNK_LINE_PATTERN = Pattern
.compile("@@ ([-+]?(\\d+),\\d+) ([-+]?(\\d+),\\d+) @@"); //$NON-NLS-1$
@Override
public IHyperlink[] detectHyperlinks(ITextViewer textViewer,
IRegion region, boolean canShowMultipleHyperlinks) {
IDocument document = textViewer.getDocument();
if (!(document instanceof DiffDocument)
|| document.getLength() == 0) {
return null;
}
DiffDocument diffDocument = (DiffDocument) document;
DiffRegion[] regions = diffDocument.getRegions();
FileDiffRegion[] fileRegions = diffDocument.getFileRegions();
if (regions == null || regions.length == 0 || fileRegions == null
|| fileRegions.length == 0) {
return null;
}
int start = region.getOffset();
int end = region.getOffset() + region.getLength();
DiffRegion key = new DiffRegion(start, 0);
int i = Arrays.binarySearch(regions, key, (a, b) -> {
if (a.getOffset() > b.getOffset() + b.getLength()) {
return 1;
}
if (a.getOffset() + a.getLength() < b.getOffset()) {
return -1;
}
return 0;
});
List<IHyperlink> links = new ArrayList<>();
FileDiffRegion fileRange = null;
for (; i >= 0 && i < regions.length; i++) {
DiffRegion range = regions[i];
if (range.getOffset() >= end) {
break;
}
if (range.getOffset() + range.getLength() <= start) {
continue;
}
// Range overlaps region
switch (range.getType()) {
case HEADLINE:
fileRange = findFileRange(diffDocument, fileRange,
range.getOffset());
if (fileRange != null) {
DiffEntry.ChangeType change = fileRange.getDiff()
.getChange();
switch (change) {
case ADD:
case DELETE:
break;
default:
if (getString(document, range.getOffset(),
range.getLength()).startsWith("diff")) { //$NON-NLS-1$
// "diff" is at the beginning
IRegion linkRegion = new Region(
range.getOffset(), 4);
if (TextUtilities.overlaps(region,
linkRegion)) {
links.add(new CompareLink(linkRegion,
fileRange, -1));
}
}
break;
}
}
break;
case HEADER:
fileRange = findFileRange(diffDocument, fileRange,
range.getOffset());
if (fileRange != null) {
String line = getString(document, range.getOffset(),
range.getLength());
createHeaderLinks((DiffDocument) document, region,
fileRange, range, line, DiffEntry.Side.OLD,
links);
createHeaderLinks((DiffDocument) document, region,
fileRange, range, line, DiffEntry.Side.NEW,
links);
}
break;
case HUNK:
fileRange = findFileRange(diffDocument, fileRange,
range.getOffset());
if (fileRange != null) {
String line = getString(document, range.getOffset(),
range.getLength());
Matcher m = HUNK_LINE_PATTERN.matcher(line);
if (m.find()) {
int lineOffset = getContextLines(document, range,
i + 1 < regions.length ? regions[i + 1]
: null);
createHunkLinks(region, fileRange, range, m,
lineOffset, links);
}
}
break;
default:
break;
}
}
if (links.isEmpty()) {
return null;
}
return links.toArray(new IHyperlink[links.size()]);
}
private String getString(IDocument document, int offset, int length) {
try {
return document.get(offset, length);
} catch (BadLocationException e) {
return ""; //$NON-NLS-1$
}
}
private int getContextLines(IDocument document, DiffRegion hunk,
DiffRegion next) {
if (next != null) {
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;
default:
break;
}
} catch (BadLocationException e) {
// Ignore
}
}
return 0;
}
private FileDiffRegion findFileRange(DiffDocument document,
FileDiffRegion candidate, int offset) {
if (candidate != null && TextUtilities.overlaps(candidate,
new Region(offset, 0))) {
return candidate;
}
return document.findFileRegion(offset);
}
private void createHeaderLinks(DiffDocument document, IRegion region,
FileDiffRegion fileRange, DiffRegion range, String line,
@NonNull DiffEntry.Side side, List<IHyperlink> links) {
Pattern p = document.getPathPattern(side);
if (p == null) {
return;
}
DiffEntry.ChangeType change = fileRange.getDiff().getChange();
switch (side) {
case OLD:
if (change == DiffEntry.ChangeType.ADD) {
return;
}
break;
default:
if (change == DiffEntry.ChangeType.DELETE) {
return;
}
break;
}
Matcher m = p.matcher(line);
if (m.find()) {
IRegion linkRegion = new Region(range.getOffset() + m.start(),
m.end() - m.start());
if (TextUtilities.overlaps(region, linkRegion)) {
if (side == DiffEntry.Side.NEW) {
File file = new Path(fileRange.getRepository()
.getWorkTree().getAbsolutePath()).append(
fileRange.getDiff().getNewPath())
.toFile();
if (file.exists()) {
links.add(new FileLink(linkRegion, file, -1));
}
}
links.add(new OpenLink(linkRegion, fileRange, side, -1));
}
}
}
private void createHunkLinks(IRegion region, FileDiffRegion fileRange,
DiffRegion range, Matcher m, int lineOffset,
List<IHyperlink> links) {
DiffEntry.ChangeType change = fileRange.getDiff().getChange();
if (change != DiffEntry.ChangeType.ADD) {
IRegion linkRegion = new Region(range.getOffset() + m.start(1),
m.end(1) - m.start(1));
if (TextUtilities.overlaps(linkRegion, region)) {
int lineNo = Integer.parseInt(m.group(2)) - 1 + lineOffset;
if (change != DiffEntry.ChangeType.DELETE) {
links.add(
new CompareLink(linkRegion, fileRange, lineNo));
}
links.add(new OpenLink(linkRegion, fileRange,
DiffEntry.Side.OLD, lineNo));
}
}
if (change != DiffEntry.ChangeType.DELETE) {
IRegion linkRegion = new Region(range.getOffset() + m.start(3),
m.end(3) - m.start(3));
if (TextUtilities.overlaps(linkRegion, region)) {
int lineNo = Integer.parseInt(m.group(4)) - 1 + lineOffset;
if (change != DiffEntry.ChangeType.ADD) {
links.add(
new CompareLink(linkRegion, fileRange, lineNo));
}
File file = new Path(fileRange.getRepository().getWorkTree()
.getAbsolutePath())
.append(fileRange.getDiff().getNewPath())
.toFile();
if (file.exists()) {
links.add(new FileLink(linkRegion, file, lineNo));
}
links.add(new OpenLink(linkRegion, fileRange,
DiffEntry.Side.NEW, lineNo));
}
}
}
@Override
public int getStateMask() {
return -1;
}
}
private static abstract class RevealLink implements IHyperlink {
private final IRegion region;
protected final int lineNo;
protected RevealLink(IRegion region, int lineNo) {
this.region = region;
this.lineNo = lineNo;
}
@Override
public IRegion getHyperlinkRegion() {
return region;
}
@Override
public String getTypeLabel() {
return null;
}
}
private static class FileLink extends RevealLink {
private final File file;
public FileLink(IRegion region, File file, int lineNo) {
super(region, lineNo);
this.file = file;
}
@Override
public String getHyperlinkText() {
return UIText.DiffViewer_OpenWorkingTreeLinkLabel;
}
@Override
public void open() {
openFileInEditor(file, lineNo);
}
}
private static class CompareLink extends RevealLink {
protected final Repository repository;
protected final FileDiff fileDiff;
public CompareLink(IRegion region, FileDiffRegion fileRange,
int lineNo) {
super(region, lineNo);
this.repository = fileRange.getRepository();
this.fileDiff = fileRange.getDiff();
}
@Override
public String getHyperlinkText() {
return UIText.DiffViewer_OpenComparisonLinkLabel;
}
@Override
public void open() {
// No way to selectAndReveal a line or a diff node in a
// CompareEditor?
showTwoWayFileDiff(repository, fileDiff);
}
}
private static class OpenLink extends CompareLink {
private final DiffEntry.Side side;
public OpenLink(IRegion region, FileDiffRegion fileRange,
DiffEntry.Side side, int lineNo) {
super(region, fileRange, lineNo);
this.side = side;
}
@Override
public String getHyperlinkText() {
switch (side) {
case OLD:
return UIText.DiffViewer_OpenPreviousLinkLabel;
default:
return UIText.DiffViewer_OpenInEditorLinkLabel;
}
}
@Override
public void open() {
openInEditor(repository, fileDiff, side, lineNo);
}
}
/**
* Opens the file, if it exists, in an editor.
*
* @param file
* to open
* @param lineNoToReveal
* if >= 0, select and reveals the given line
*/
public static void openFileInEditor(File file, int lineNoToReveal) {
if (!file.exists()) {
Activator.showError(
NLS.bind(UIText.DiffViewer_FileDoesNotExist,
file.getPath()),
null);
return;
}
IWorkbenchPage page = PlatformUI.getWorkbench()
.getActiveWorkbenchWindow().getActivePage();
IEditorPart editor = EgitUiEditorUtils.openEditor(file, page);
EgitUiEditorUtils.revealLine(editor, lineNoToReveal);
}
/**
* Opens either the new or the old version of a {@link FileDiff} in an
* editor.
*
* @param repository
* the {@link FileDiff} belongs to
* @param d
* the {@link FileDiff}
* @param side
* to show
* @param lineNoToReveal
* if >= 0, select and reveals the given line
*/
public static void openInEditor(Repository repository, FileDiff d,
DiffEntry.Side side, int lineNoToReveal) {
ObjectId[] blobs = d.getBlobs();
switch (side) {
case OLD:
openInEditor(repository, d.getOldPath(), d.getCommit().getParent(0),
blobs[0], lineNoToReveal);
break;
default:
openInEditor(repository, d.getNewPath(), d.getCommit(),
blobs[blobs.length - 1], lineNoToReveal);
break;
}
}
private static void openInEditor(Repository repository, String path,
RevCommit commit, ObjectId blob, int reveal) {
try {
IFileRevision rev = CompareUtils.getFileRevision(path, commit,
repository, blob);
if (rev == null) {
String message = NLS.bind(
UIText.DiffViewer_notContainedInCommit, path,
commit.getName());
Activator.showError(message, null);
return;
}
IWorkbenchWindow window = PlatformUI.getWorkbench()
.getActiveWorkbenchWindow();
IWorkbenchPage page = window.getActivePage();
IEditorPart editor = EgitUiEditorUtils.openEditor(page, rev,
new NullProgressMonitor());
EgitUiEditorUtils.revealLine(editor, reveal);
} catch (IOException | CoreException e) {
Activator.handleError(UIText.GitHistoryPage_openFailed, e, true);
}
}
/**
* Shows a two-way diff between the old and new versions of a
* {@link FileDiff} in a compare editor.
*
* @param repository
* the {@link FileDiff} belongs to
* @param d
* the {@link FileDiff} to show
*/
public static void showTwoWayFileDiff(Repository repository, FileDiff d) {
String np = d.getNewPath();
String op = d.getOldPath();
RevCommit c = d.getCommit();
ObjectId[] blobs = d.getBlobs();
// extract commits
final RevCommit oldCommit;
final ObjectId oldObjectId;
if (!d.getChange().equals(ChangeType.ADD)) {
oldCommit = c.getParent(0);
oldObjectId = blobs[0];
} else {
// Initial import
oldCommit = null;
oldObjectId = null;
}
final RevCommit newCommit;
final ObjectId newObjectId;
if (d.getChange().equals(ChangeType.DELETE)) {
newCommit = null;
newObjectId = null;
} else {
newCommit = c;
newObjectId = blobs[blobs.length - 1];
}
IWorkbenchWindow window = PlatformUI.getWorkbench()
.getActiveWorkbenchWindow();
IWorkbenchPage page = window.getActivePage();
if (oldCommit != null && newCommit != null && repository != null) {
IFile file = np != null
? ResourceUtil.getFileForLocation(repository, np, false)
: null;
try {
if (file != null) {
CompareUtils.compare(file, repository, np, op,
newCommit.getName(), oldCommit.getName(), false,
page);
} else {
IPath location = new Path(
repository.getWorkTree().getAbsolutePath())
.append(np);
CompareUtils.compare(location, repository,
newCommit.getName(), oldCommit.getName(), false,
page);
}
} catch (IOException e) {
Activator.handleError(UIText.GitHistoryPage_openFailed, e,
true);
}
return;
}
// still happens on initial commits
final ITypedElement oldSide = createTypedElement(repository, op,
oldCommit, oldObjectId);
final ITypedElement newSide = createTypedElement(repository, np,
newCommit, newObjectId);
CompareUtils.openInCompare(page,
new GitCompareFileRevisionEditorInput(newSide, oldSide, null));
}
private static ITypedElement createTypedElement(Repository repository,
String path, final RevCommit commit, final ObjectId objectId) {
if (null != commit) {
return CompareUtils.getFileRevisionTypedElement(path, commit,
repository, objectId);
} else {
return new GitCompareFileRevisionEditorInput.EmptyTypedElement(""); //$NON-NLS-1$
}
}
}