Fix "Compare with Previous" in history view and commit editor outline
[egit/eclipse.git] / org.eclipse.egit.ui / src / org / eclipse / egit / ui / internal / commit / DiffViewer.java
blobf758e60bd05554c1f3a27233f6a6f868da158f3a
1 /*******************************************************************************
2 * Copyright (c) 2011, 2016 GitHub Inc. and others.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the Eclipse Public License 2.0
5 * which accompanies this distribution, and is available at
6 * https://www.eclipse.org/legal/epl-2.0/
8 * SPDX-License-Identifier: EPL-2.0
10 * Contributors:
11 * Kevin Sawicki (GitHub Inc.) - initial API and implementation
12 * Tobias Pfeifer (SAP AG) - customizable font and color for the first header line - https://bugs.eclipse.org/397723
13 * Thomas Wolf <thomas.wolf@paranor.ch> - add hyperlinks, and use JFace syntax coloring
14 *******************************************************************************/
15 package org.eclipse.egit.ui.internal.commit;
17 import static org.eclipse.egit.ui.UIPreferences.THEME_DiffAddBackgroundColor;
18 import static org.eclipse.egit.ui.UIPreferences.THEME_DiffAddForegroundColor;
19 import static org.eclipse.egit.ui.UIPreferences.THEME_DiffHeadlineBackgroundColor;
20 import static org.eclipse.egit.ui.UIPreferences.THEME_DiffHeadlineFont;
21 import static org.eclipse.egit.ui.UIPreferences.THEME_DiffHeadlineForegroundColor;
22 import static org.eclipse.egit.ui.UIPreferences.THEME_DiffHunkBackgroundColor;
23 import static org.eclipse.egit.ui.UIPreferences.THEME_DiffHunkForegroundColor;
24 import static org.eclipse.egit.ui.UIPreferences.THEME_DiffRemoveBackgroundColor;
25 import static org.eclipse.egit.ui.UIPreferences.THEME_DiffRemoveForegroundColor;
27 import java.io.File;
28 import java.io.IOException;
29 import java.util.ArrayList;
30 import java.util.Arrays;
31 import java.util.HashMap;
32 import java.util.List;
33 import java.util.Map;
34 import java.util.function.Supplier;
35 import java.util.regex.Matcher;
36 import java.util.regex.Pattern;
38 import org.eclipse.compare.ITypedElement;
39 import org.eclipse.core.resources.IFile;
40 import org.eclipse.core.runtime.Assert;
41 import org.eclipse.core.runtime.CoreException;
42 import org.eclipse.core.runtime.NullProgressMonitor;
43 import org.eclipse.core.runtime.Path;
44 import org.eclipse.egit.core.internal.util.ResourceUtil;
45 import org.eclipse.egit.ui.Activator;
46 import org.eclipse.egit.ui.internal.CompareUtils;
47 import org.eclipse.egit.ui.internal.EgitUiEditorUtils;
48 import org.eclipse.egit.ui.internal.UIText;
49 import org.eclipse.egit.ui.internal.commit.DiffRegionFormatter.DiffRegion;
50 import org.eclipse.egit.ui.internal.commit.DiffRegionFormatter.FileDiffRegion;
51 import org.eclipse.egit.ui.internal.dialogs.HyperlinkSourceViewer;
52 import org.eclipse.egit.ui.internal.history.FileDiff;
53 import org.eclipse.egit.ui.internal.revision.GitCompareFileRevisionEditorInput;
54 import org.eclipse.jface.preference.IPreferenceStore;
55 import org.eclipse.jface.resource.ColorRegistry;
56 import org.eclipse.jface.resource.FontRegistry;
57 import org.eclipse.jface.resource.JFaceResources;
58 import org.eclipse.jface.text.BadLocationException;
59 import org.eclipse.jface.text.Document;
60 import org.eclipse.jface.text.IDocument;
61 import org.eclipse.jface.text.IRegion;
62 import org.eclipse.jface.text.ITextViewer;
63 import org.eclipse.jface.text.ITypedRegion;
64 import org.eclipse.jface.text.Region;
65 import org.eclipse.jface.text.TextAttribute;
66 import org.eclipse.jface.text.TextUtilities;
67 import org.eclipse.jface.text.hyperlink.AbstractHyperlinkDetector;
68 import org.eclipse.jface.text.hyperlink.IHyperlink;
69 import org.eclipse.jface.text.hyperlink.IHyperlinkDetector;
70 import org.eclipse.jface.text.hyperlink.IHyperlinkDetectorExtension2;
71 import org.eclipse.jface.text.presentation.IPresentationReconciler;
72 import org.eclipse.jface.text.presentation.PresentationReconciler;
73 import org.eclipse.jface.text.rules.DefaultDamagerRepairer;
74 import org.eclipse.jface.text.rules.IToken;
75 import org.eclipse.jface.text.rules.ITokenScanner;
76 import org.eclipse.jface.text.rules.Token;
77 import org.eclipse.jface.text.source.IOverviewRuler;
78 import org.eclipse.jface.text.source.ISourceViewer;
79 import org.eclipse.jface.text.source.IVerticalRuler;
80 import org.eclipse.jface.text.source.SourceViewerConfiguration;
81 import org.eclipse.jface.util.IPropertyChangeListener;
82 import org.eclipse.jface.util.PropertyChangeEvent;
83 import org.eclipse.jgit.annotations.NonNull;
84 import org.eclipse.jgit.diff.DiffEntry;
85 import org.eclipse.jgit.diff.DiffEntry.ChangeType;
86 import org.eclipse.jgit.lib.ObjectId;
87 import org.eclipse.jgit.lib.Repository;
88 import org.eclipse.jgit.revwalk.RevCommit;
89 import org.eclipse.osgi.util.NLS;
90 import org.eclipse.swt.SWT;
91 import org.eclipse.swt.graphics.Color;
92 import org.eclipse.swt.graphics.Rectangle;
93 import org.eclipse.swt.widgets.Composite;
94 import org.eclipse.swt.widgets.Layout;
95 import org.eclipse.team.core.history.IFileRevision;
96 import org.eclipse.ui.IEditorPart;
97 import org.eclipse.ui.IWorkbenchPage;
98 import org.eclipse.ui.IWorkbenchWindow;
99 import org.eclipse.ui.PlatformUI;
100 import org.eclipse.ui.themes.IThemeManager;
103 * Source viewer to display one or more file differences using standard editor
104 * colors and fonts preferences. Should be used together with a
105 * {@link DiffDocument} to get proper coloring and hyperlink support.
107 public class DiffViewer extends HyperlinkSourceViewer {
109 private final Map<String, IToken> tokens = new HashMap<>();
111 private final Map<String, Color> backgroundColors = new HashMap<>();
113 private IPropertyChangeListener themeListener = new IPropertyChangeListener() {
115 @Override
116 public void propertyChange(PropertyChangeEvent event) {
117 String property = event.getProperty();
118 if (IThemeManager.CHANGE_CURRENT_THEME.equals(property)
119 || THEME_DiffAddBackgroundColor.equals(property)
120 || THEME_DiffAddForegroundColor.equals(property)
121 || THEME_DiffHunkBackgroundColor.equals(property)
122 || THEME_DiffHunkForegroundColor.equals(property)
123 || THEME_DiffHeadlineBackgroundColor.equals(property)
124 || THEME_DiffHeadlineForegroundColor.equals(property)
125 || THEME_DiffHeadlineFont.equals(property)
126 || THEME_DiffRemoveBackgroundColor.equals(property)
127 || THEME_DiffRemoveForegroundColor.equals(property)) {
128 refreshDiffStyles();
129 invalidateTextPresentation();
135 * A configuration to use with a {@link DiffViewer}, setting up the syntax
136 * coloring for a diff and adding the {@link IHyperlinkDetector} for the
137 * links.
139 public static class Configuration
140 extends HyperlinkSourceViewer.Configuration {
143 * Creates a new {@link Configuration} connected to the given
144 * {@link IPreferenceStore}.
146 * @param preferenceStore
147 * to connect to
149 public Configuration(IPreferenceStore preferenceStore) {
150 super(preferenceStore);
153 @Override
154 public int getHyperlinkStateMask(ISourceViewer sourceViewer) {
155 return SWT.NONE;
158 @Override
159 protected IHyperlinkDetector[] internalGetHyperlinkDetectors(
160 ISourceViewer sourceViewer) {
161 Assert.isTrue(sourceViewer instanceof DiffViewer);
162 IHyperlinkDetector[] result = { new HyperlinkDetector() };
163 return result;
166 @Override
167 public String[] getConfiguredContentTypes(ISourceViewer sourceViewer) {
168 Assert.isTrue(sourceViewer instanceof DiffViewer);
169 DiffViewer viewer = (DiffViewer) sourceViewer;
170 return viewer.tokens.keySet()
171 .toArray(new String[viewer.tokens.size()]);
174 @Override
175 public IPresentationReconciler getPresentationReconciler(
176 ISourceViewer sourceViewer) {
177 Assert.isTrue(sourceViewer instanceof DiffViewer);
178 DiffViewer viewer = (DiffViewer) sourceViewer;
179 PresentationReconciler reconciler = new PresentationReconciler();
180 reconciler.setDocumentPartitioning(
181 getConfiguredDocumentPartitioning(viewer));
182 for (String contentType : viewer.tokens.keySet()) {
183 DefaultDamagerRepairer damagerRepairer = new DefaultDamagerRepairer(
184 new SingleTokenScanner(
185 () -> viewer.tokens.get(contentType)));
186 reconciler.setDamager(damagerRepairer, contentType);
187 reconciler.setRepairer(damagerRepairer, contentType);
189 return reconciler;
194 * Creates a new {@link DiffViewer}.
196 * @param parent
197 * to contain the viewer
198 * @param ruler
199 * for the viewer (left side)
200 * @param styles
201 * for the viewer
203 public DiffViewer(Composite parent, IVerticalRuler ruler, int styles) {
204 this(parent, ruler, null, false, styles);
208 * Creates a new {@link DiffViewer}.
210 * @param parent
211 * to contain the viewer
212 * @param ruler
213 * for the viewer (left side)
214 * @param overviewRuler
215 * ruler for overview annotations
216 * @param showsAnnotationOverview
217 * whether to show overview annotations
218 * @param styles
219 * for the viewer
221 public DiffViewer(Composite parent, IVerticalRuler ruler,
222 IOverviewRuler overviewRuler, boolean showsAnnotationOverview,
223 int styles) {
224 super(parent, ruler, overviewRuler, showsAnnotationOverview, styles);
225 getTextWidget().setAlwaysShowScrollBars(false);
226 setEditable(false);
227 setDocument(new Document());
228 initListeners();
229 getTextWidget()
230 .setFont(JFaceResources.getFont(JFaceResources.TEXT_FONT));
231 refreshDiffStyles();
234 @Override
235 protected void handleDispose() {
236 PlatformUI.getWorkbench().getThemeManager()
237 .removePropertyChangeListener(themeListener);
238 super.handleDispose();
241 @Override
242 public void configure(SourceViewerConfiguration config) {
243 Assert.isTrue(config instanceof Configuration);
244 super.configure(config);
247 @Override
248 protected Layout createLayout() {
249 return new FixedRulerLayout(GAP_SIZE_1);
252 private class FixedRulerLayout extends RulerLayout {
254 public FixedRulerLayout(int gap) {
255 super(gap);
258 @Override
259 protected void layout(Composite composite, boolean flushCache) {
260 Rectangle bounds = composite.getBounds();
261 if (bounds.width == 0 || bounds.height == 0) {
262 // The overview ruler is laid out wrongly in the DiffEditorPage:
263 // it ends up with a negative y-coordinate. This seems to be
264 // caused by layout attempts while the page is not visible,
265 // which cache that bogus negative offset in RulerLayout, which
266 // will re-use it even when the viewer is laid out again when
267 // the page is visible. So don't layout if the containing
268 // composite has no extent.
269 return;
271 super.layout(composite, flushCache);
275 private void refreshDiffStyles() {
276 ColorRegistry col = PlatformUI.getWorkbench().getThemeManager()
277 .getCurrentTheme().getColorRegistry();
278 FontRegistry reg = PlatformUI.getWorkbench().getThemeManager()
279 .getCurrentTheme().getFontRegistry();
280 // We do the foreground via syntax coloring and the background via a
281 // line background listener. If we did the background also via the
282 // TextAttributes, this would take precedence over the line background
283 // resulting in strange display if the current line is highlighted:
284 // that highlighting would appear only beyond the end of the actual
285 // text content (i.e., beyond the end-of-line), while actual text
286 // would still get the background from the attribute.
287 tokens.put(IDocument.DEFAULT_CONTENT_TYPE, new Token(null));
288 tokens.put(DiffDocument.HEADLINE_CONTENT_TYPE,
289 new Token(new TextAttribute(
290 col.get(THEME_DiffHeadlineForegroundColor), null,
291 SWT.NORMAL, reg.get(THEME_DiffHeadlineFont))));
292 tokens.put(DiffDocument.HUNK_CONTENT_TYPE, new Token(
293 new TextAttribute(col.get(THEME_DiffHunkForegroundColor))));
294 tokens.put(DiffDocument.ADDED_CONTENT_TYPE, new Token(
295 new TextAttribute(col.get(THEME_DiffAddForegroundColor))));
296 tokens.put(DiffDocument.REMOVED_CONTENT_TYPE, new Token(
297 new TextAttribute(col.get(THEME_DiffRemoveForegroundColor))));
298 backgroundColors.put(DiffDocument.HEADLINE_CONTENT_TYPE,
299 col.get(THEME_DiffHeadlineBackgroundColor));
300 backgroundColors.put(DiffDocument.HUNK_CONTENT_TYPE,
301 col.get(THEME_DiffHunkBackgroundColor));
302 backgroundColors.put(DiffDocument.ADDED_CONTENT_TYPE,
303 col.get(THEME_DiffAddBackgroundColor));
304 backgroundColors.put(DiffDocument.REMOVED_CONTENT_TYPE,
305 col.get(THEME_DiffRemoveBackgroundColor));
308 private void initListeners() {
309 PlatformUI.getWorkbench().getThemeManager()
310 .addPropertyChangeListener(this.themeListener);
311 getTextWidget().addLineBackgroundListener((event) -> {
312 IDocument document = getDocument();
313 if (document instanceof DiffDocument) {
314 try {
315 // We are in SWT land here: we get widget offsets.
316 int modelOffset = widgetOffset2ModelOffset(
317 event.lineOffset);
318 ITypedRegion partition = ((DiffDocument) document)
319 .getPartition(modelOffset);
320 if (partition != null) {
321 Color color = backgroundColors.get(partition.getType());
322 if (color != null) {
323 event.lineBackground = color;
326 } catch (BadLocationException e) {
327 // Ignore
333 @Override
334 protected void handleJFacePreferencesChange(PropertyChangeEvent event) {
335 if (JFaceResources.TEXT_FONT.equals(event.getProperty())) {
336 setFont(JFaceResources.getFont(JFaceResources.TEXT_FONT));
337 } else {
338 super.handleJFacePreferencesChange(event);
342 private static class SingleTokenScanner implements ITokenScanner {
344 private final Supplier<IToken> token;
346 private int currentOffset;
348 private int end;
350 private int tokenStart;
352 public SingleTokenScanner(Supplier<IToken> supplier) {
353 this.token = supplier;
356 @Override
357 public void setRange(IDocument document, int offset, int length) {
358 currentOffset = offset;
359 end = offset + length;
360 tokenStart = -1;
363 @Override
364 public IToken nextToken() {
365 tokenStart = currentOffset;
366 if (currentOffset < end) {
367 currentOffset = end;
368 return token.get();
370 return Token.EOF;
373 @Override
374 public int getTokenOffset() {
375 return tokenStart;
378 @Override
379 public int getTokenLength() {
380 return currentOffset - tokenStart;
385 private static class HyperlinkDetector extends AbstractHyperlinkDetector
386 implements IHyperlinkDetectorExtension2 {
388 private final Pattern HUNK_LINE_PATTERN = Pattern
389 .compile("@@ ([-+]?(\\d+),\\d+) ([-+]?(\\d+),\\d+) @@"); //$NON-NLS-1$
391 @Override
392 public IHyperlink[] detectHyperlinks(ITextViewer textViewer,
393 IRegion region, boolean canShowMultipleHyperlinks) {
394 IDocument document = textViewer.getDocument();
395 if (!(document instanceof DiffDocument)
396 || document.getLength() == 0) {
397 return null;
399 DiffDocument diffDocument = (DiffDocument) document;
400 DiffRegion[] regions = diffDocument.getRegions();
401 FileDiffRegion[] fileRegions = diffDocument.getFileRegions();
402 if (regions == null || regions.length == 0 || fileRegions == null
403 || fileRegions.length == 0) {
404 return null;
406 int start = region.getOffset();
407 int end = region.getOffset() + region.getLength();
408 DiffRegion key = new DiffRegion(start, 0);
409 int i = Arrays.binarySearch(regions, key, (a, b) -> {
410 if (a.getOffset() > b.getOffset() + b.getLength()) {
411 return 1;
413 if (a.getOffset() + a.getLength() < b.getOffset()) {
414 return -1;
416 return 0;
418 List<IHyperlink> links = new ArrayList<>();
419 FileDiffRegion fileRange = null;
420 for (; i >= 0 && i < regions.length; i++) {
421 DiffRegion range = regions[i];
422 if (range.getOffset() >= end) {
423 break;
425 if (range.getOffset() + range.getLength() <= start) {
426 continue;
428 // Range overlaps region
429 switch (range.getType()) {
430 case HEADLINE:
431 fileRange = findFileRange(diffDocument, fileRange,
432 range.getOffset());
433 if (fileRange != null) {
434 DiffEntry.ChangeType change = fileRange.getDiff()
435 .getChange();
436 switch (change) {
437 case ADD:
438 case DELETE:
439 break;
440 default:
441 if (getString(document, range.getOffset(),
442 range.getLength()).startsWith("diff")) { //$NON-NLS-1$
443 // "diff" is at the beginning
444 IRegion linkRegion = new Region(
445 range.getOffset(), 4);
446 if (TextUtilities.overlaps(region,
447 linkRegion)) {
448 links.add(new CompareLink(linkRegion,
449 fileRange, -1));
452 break;
455 break;
456 case HEADER:
457 fileRange = findFileRange(diffDocument, fileRange,
458 range.getOffset());
459 if (fileRange != null) {
460 String line = getString(document, range.getOffset(),
461 range.getLength());
462 createHeaderLinks((DiffDocument) document, region,
463 fileRange, range, line, DiffEntry.Side.OLD,
464 links);
465 createHeaderLinks((DiffDocument) document, region,
466 fileRange, range, line, DiffEntry.Side.NEW,
467 links);
469 break;
470 case HUNK:
471 fileRange = findFileRange(diffDocument, fileRange,
472 range.getOffset());
473 if (fileRange != null) {
474 String line = getString(document, range.getOffset(),
475 range.getLength());
476 Matcher m = HUNK_LINE_PATTERN.matcher(line);
477 if (m.find()) {
478 int lineOffset = getContextLines(document, range,
479 i + 1 < regions.length ? regions[i + 1]
480 : null);
481 createHunkLinks(region, fileRange, range, m,
482 lineOffset, links);
485 break;
486 default:
487 break;
490 if (links.isEmpty()) {
491 return null;
493 return links.toArray(new IHyperlink[links.size()]);
496 private String getString(IDocument document, int offset, int length) {
497 try {
498 return document.get(offset, length);
499 } catch (BadLocationException e) {
500 return ""; //$NON-NLS-1$
504 private int getContextLines(IDocument document, DiffRegion hunk,
505 DiffRegion next) {
506 if (next != null) {
507 try {
508 switch (next.getType()) {
509 case CONTEXT:
510 int nofLines = document.getNumberOfLines(
511 next.getOffset(), next.getLength());
512 return nofLines - 1;
513 case ADD:
514 case REMOVE:
515 int hunkLine = document
516 .getLineOfOffset(hunk.getOffset());
517 int diffLine = document
518 .getLineOfOffset(next.getOffset());
519 return diffLine - hunkLine - 1;
520 default:
521 break;
523 } catch (BadLocationException e) {
524 // Ignore
527 return 0;
530 private FileDiffRegion findFileRange(DiffDocument document,
531 FileDiffRegion candidate, int offset) {
532 if (candidate != null && TextUtilities.overlaps(candidate,
533 new Region(offset, 0))) {
534 return candidate;
536 return document.findFileRegion(offset);
539 private void createHeaderLinks(DiffDocument document, IRegion region,
540 FileDiffRegion fileRange, DiffRegion range, String line,
541 @NonNull DiffEntry.Side side, List<IHyperlink> links) {
542 Pattern p = document.getPathPattern(side);
543 if (p == null) {
544 return;
546 DiffEntry.ChangeType change = fileRange.getDiff().getChange();
547 switch (side) {
548 case OLD:
549 if (change == DiffEntry.ChangeType.ADD) {
550 return;
552 break;
553 default:
554 if (change == DiffEntry.ChangeType.DELETE) {
555 return;
557 break;
560 Matcher m = p.matcher(line);
561 if (m.find()) {
562 IRegion linkRegion = new Region(range.getOffset() + m.start(),
563 m.end() - m.start());
564 if (TextUtilities.overlaps(region, linkRegion)) {
565 if (side == DiffEntry.Side.NEW) {
566 File file = new Path(fileRange.getRepository()
567 .getWorkTree().getAbsolutePath()).append(
568 fileRange.getDiff().getNewPath())
569 .toFile();
570 if (file.exists()) {
571 links.add(new FileLink(linkRegion, file, -1));
574 links.add(new OpenLink(linkRegion, fileRange, side, -1));
579 private void createHunkLinks(IRegion region, FileDiffRegion fileRange,
580 DiffRegion range, Matcher m, int lineOffset,
581 List<IHyperlink> links) {
582 DiffEntry.ChangeType change = fileRange.getDiff().getChange();
583 if (change != DiffEntry.ChangeType.ADD) {
584 IRegion linkRegion = new Region(range.getOffset() + m.start(1),
585 m.end(1) - m.start(1));
586 if (TextUtilities.overlaps(linkRegion, region)) {
587 int lineNo = Integer.parseInt(m.group(2)) - 1 + lineOffset;
588 if (change != DiffEntry.ChangeType.DELETE) {
589 links.add(
590 new CompareLink(linkRegion, fileRange, lineNo));
592 links.add(new OpenLink(linkRegion, fileRange,
593 DiffEntry.Side.OLD, lineNo));
596 if (change != DiffEntry.ChangeType.DELETE) {
597 IRegion linkRegion = new Region(range.getOffset() + m.start(3),
598 m.end(3) - m.start(3));
599 if (TextUtilities.overlaps(linkRegion, region)) {
600 int lineNo = Integer.parseInt(m.group(4)) - 1 + lineOffset;
601 if (change != DiffEntry.ChangeType.ADD) {
602 links.add(
603 new CompareLink(linkRegion, fileRange, lineNo));
605 File file = new Path(fileRange.getRepository().getWorkTree()
606 .getAbsolutePath())
607 .append(fileRange.getDiff().getNewPath())
608 .toFile();
609 if (file.exists()) {
610 links.add(new FileLink(linkRegion, file, lineNo));
612 links.add(new OpenLink(linkRegion, fileRange,
613 DiffEntry.Side.NEW, lineNo));
618 @Override
619 public int getStateMask() {
620 return -1;
624 private static abstract class RevealLink implements IHyperlink {
626 private final IRegion region;
628 protected final int lineNo;
630 protected RevealLink(IRegion region, int lineNo) {
631 this.region = region;
632 this.lineNo = lineNo;
635 @Override
636 public IRegion getHyperlinkRegion() {
637 return region;
640 @Override
641 public String getTypeLabel() {
642 return null;
647 private static class FileLink extends RevealLink {
649 private final File file;
651 public FileLink(IRegion region, File file, int lineNo) {
652 super(region, lineNo);
653 this.file = file;
656 @Override
657 public String getHyperlinkText() {
658 return UIText.DiffViewer_OpenWorkingTreeLinkLabel;
661 @Override
662 public void open() {
663 openFileInEditor(file, lineNo);
668 private static class CompareLink extends RevealLink {
670 protected final Repository repository;
672 protected final FileDiff fileDiff;
674 public CompareLink(IRegion region, FileDiffRegion fileRange,
675 int lineNo) {
676 super(region, lineNo);
677 this.repository = fileRange.getRepository();
678 this.fileDiff = fileRange.getDiff();
681 @Override
682 public String getHyperlinkText() {
683 return UIText.DiffViewer_OpenComparisonLinkLabel;
686 @Override
687 public void open() {
688 // No way to selectAndReveal a line or a diff node in a
689 // CompareEditor?
690 showTwoWayFileDiff(repository, fileDiff);
695 private static class OpenLink extends CompareLink {
697 private final DiffEntry.Side side;
699 public OpenLink(IRegion region, FileDiffRegion fileRange,
700 DiffEntry.Side side, int lineNo) {
701 super(region, fileRange, lineNo);
702 this.side = side;
705 @Override
706 public String getHyperlinkText() {
707 switch (side) {
708 case OLD:
709 return UIText.DiffViewer_OpenPreviousLinkLabel;
710 default:
711 return UIText.DiffViewer_OpenInEditorLinkLabel;
715 @Override
716 public void open() {
717 openInEditor(repository, fileDiff, side, lineNo);
723 * Opens the file, if it exists, in an editor.
725 * @param file
726 * to open
727 * @param lineNoToReveal
728 * if >= 0, select and reveals the given line
730 public static void openFileInEditor(File file, int lineNoToReveal) {
731 if (!file.exists()) {
732 Activator.showError(
733 NLS.bind(UIText.DiffViewer_FileDoesNotExist,
734 file.getPath()),
735 null);
736 return;
738 IWorkbenchPage page = PlatformUI.getWorkbench()
739 .getActiveWorkbenchWindow().getActivePage();
740 IEditorPart editor = EgitUiEditorUtils.openEditor(file, page);
741 EgitUiEditorUtils.revealLine(editor, lineNoToReveal);
745 * Opens either the new or the old version of a {@link FileDiff} in an
746 * editor.
748 * @param repository
749 * the {@link FileDiff} belongs to
750 * @param d
751 * the {@link FileDiff}
752 * @param side
753 * to show
754 * @param lineNoToReveal
755 * if >= 0, select and reveals the given line
757 public static void openInEditor(Repository repository, FileDiff d,
758 DiffEntry.Side side, int lineNoToReveal) {
759 ObjectId[] blobs = d.getBlobs();
760 switch (side) {
761 case OLD:
762 openInEditor(repository, d.getOldPath(), d.getCommit().getParent(0),
763 blobs[0], lineNoToReveal);
764 break;
765 default:
766 openInEditor(repository, d.getNewPath(), d.getCommit(),
767 blobs[blobs.length - 1], lineNoToReveal);
768 break;
772 private static void openInEditor(Repository repository, String path,
773 RevCommit commit, ObjectId blob, int reveal) {
774 try {
775 IFileRevision rev = CompareUtils.getFileRevision(path, commit,
776 repository, blob);
777 if (rev == null) {
778 String message = NLS.bind(
779 UIText.DiffViewer_notContainedInCommit, path,
780 commit.getName());
781 Activator.showError(message, null);
782 return;
784 IWorkbenchWindow window = PlatformUI.getWorkbench()
785 .getActiveWorkbenchWindow();
786 IWorkbenchPage page = window.getActivePage();
787 IEditorPart editor = EgitUiEditorUtils.openEditor(page, rev,
788 new NullProgressMonitor());
789 EgitUiEditorUtils.revealLine(editor, reveal);
790 } catch (IOException | CoreException e) {
791 Activator.handleError(UIText.GitHistoryPage_openFailed, e, true);
796 * Shows a two-way diff between the old and new versions of a
797 * {@link FileDiff} in a compare editor.
799 * @param repository
800 * the {@link FileDiff} belongs to
801 * @param d
802 * the {@link FileDiff} to show
804 public static void showTwoWayFileDiff(Repository repository, FileDiff d) {
805 String np = d.getNewPath();
806 String op = d.getOldPath();
807 RevCommit c = d.getCommit();
808 ObjectId[] blobs = d.getBlobs();
810 // extract commits
811 final RevCommit oldCommit;
812 final ObjectId oldObjectId;
813 if (!d.getChange().equals(ChangeType.ADD)) {
814 oldCommit = c.getParent(0);
815 oldObjectId = blobs[0];
816 } else {
817 // Initial import
818 oldCommit = null;
819 oldObjectId = null;
822 final RevCommit newCommit;
823 final ObjectId newObjectId;
824 if (d.getChange().equals(ChangeType.DELETE)) {
825 newCommit = null;
826 newObjectId = null;
827 } else {
828 newCommit = c;
829 newObjectId = blobs[blobs.length - 1];
831 IWorkbenchWindow window = PlatformUI.getWorkbench()
832 .getActiveWorkbenchWindow();
833 IWorkbenchPage page = window.getActivePage();
834 if (oldCommit != null && newCommit != null && repository != null) {
835 IFile file = np != null
836 ? ResourceUtil.getFileForLocation(repository, np, false)
837 : null;
838 try {
839 if (file != null) {
840 CompareUtils.compare(file, repository, np, op,
841 newCommit.getName(), oldCommit.getName(), false,
842 page);
843 } else {
844 CompareUtils.compareBetween(repository, np, op,
845 newCommit.getName(), oldCommit.getName(),
846 page);
848 } catch (IOException e) {
849 Activator.handleError(UIText.GitHistoryPage_openFailed, e,
850 true);
852 return;
855 // still happens on initial commits
856 final ITypedElement oldSide = createTypedElement(repository, op,
857 oldCommit, oldObjectId);
858 final ITypedElement newSide = createTypedElement(repository, np,
859 newCommit, newObjectId);
860 CompareUtils.openInCompare(page,
861 new GitCompareFileRevisionEditorInput(newSide, oldSide, null));
864 private static ITypedElement createTypedElement(Repository repository,
865 String path, final RevCommit commit, final ObjectId objectId) {
866 if (null != commit) {
867 return CompareUtils.getFileRevisionTypedElement(path, commit,
868 repository, objectId);
869 } else {
870 return new GitCompareFileRevisionEditorInput.EmptyTypedElement(""); //$NON-NLS-1$