Have icon for "reset" entry in reflog
[egit/eclipse.git] / org.eclipse.egit.ui / src / org / eclipse / egit / ui / internal / commit / DiffDocument.java
blob7234437ec8dd25eb9bb5de9f5d93e89a8a58cab2
1 /*******************************************************************************
2 * Copyright (C) 2016, Thomas Wolf <thomas.wolf@paranor.ch>
4 * All rights reserved. This program and the accompanying materials
5 * are made available under the terms of the Eclipse Public License 2.0
6 * which accompanies this distribution, and is available at
7 * https://www.eclipse.org/legal/epl-2.0/
9 * SPDX-License-Identifier: EPL-2.0
10 *******************************************************************************/
11 package org.eclipse.egit.ui.internal.commit;
13 import java.util.Arrays;
14 import java.util.regex.Pattern;
16 import org.eclipse.core.runtime.Assert;
17 import org.eclipse.egit.ui.internal.commit.DiffRegionFormatter.DiffRegion;
18 import org.eclipse.egit.ui.internal.commit.DiffRegionFormatter.FileDiffRegion;
19 import org.eclipse.egit.ui.internal.history.FileDiff;
20 import org.eclipse.jface.text.BadLocationException;
21 import org.eclipse.jface.text.Document;
22 import org.eclipse.jface.text.IDocument;
23 import org.eclipse.jface.text.IDocumentPartitioner;
24 import org.eclipse.jface.text.Region;
25 import org.eclipse.jface.text.TextUtilities;
26 import org.eclipse.jface.text.rules.FastPartitioner;
27 import org.eclipse.jface.text.rules.IPartitionTokenScanner;
28 import org.eclipse.jface.text.rules.IToken;
29 import org.eclipse.jface.text.rules.Token;
30 import org.eclipse.jgit.annotations.NonNull;
31 import org.eclipse.jgit.diff.DiffEntry;
32 import org.eclipse.jgit.lib.Repository;
34 /**
35 * A {@link Document} specialized for displaying unified diffs generated by a
36 * {@link DiffRegionFormatter}. Intended usage is to create a DiffDocument, let
37 * a DiffRegionFormatter generate into it, and then
38 * {@link #connect(DiffRegionFormatter) connect()} the formatter. This will
39 * partition the document into regions for file headlines, hunks, and added or
40 * removed lines.
42 public class DiffDocument extends Document {
44 static final String HEADLINE_CONTENT_TYPE = "_egit_diff_headline"; //$NON-NLS-1$
46 static final String HUNK_CONTENT_TYPE = "_egit_diff_hunk"; //$NON-NLS-1$
48 static final String ADDED_CONTENT_TYPE = "_egit_diff_added"; //$NON-NLS-1$
50 static final String REMOVED_CONTENT_TYPE = "_egit_diff_removed"; //$NON-NLS-1$
52 private DiffRegion[] regions;
54 private FileDiffRegion[] fileRegions;
56 private Pattern newPathPattern;
58 private Pattern oldPathPattern;
60 private FileDiff defaultFileDiff;
62 private int[] maximumLineNumbers;
64 /**
65 * Creates a new {@link DiffDocument}.
67 public DiffDocument() {
68 super();
71 /**
72 * Creates a new {@link DiffDocument} with initial text.
74 * @param text
75 * to set on the document
77 public DiffDocument(String text) {
78 super(text);
81 /**
82 * Sets up the document to use information from the given
83 * {@link DiffRegionFormatter} for partitioning the document into
84 * partitions for file headlines, hunk headers, and added or removed lines.
85 * It is assumed that the given formatter has been used to generate content
86 * into the document.
88 * @param formatter
89 * to obtain information from
91 public void connect(DiffRegionFormatter formatter) {
92 regions = formatter.getRegions();
93 fileRegions = formatter.getFileRegions();
94 if (fileRegions == null || fileRegions.length == 0) {
95 FileDiff implicitFileDiff = defaultFileDiff;
96 if (implicitFileDiff != null) {
97 fileRegions = new FileDiffRegion[] {
98 new FileDiffRegion(implicitFileDiff, 0, getLength()) };
101 newPathPattern = Pattern.compile(
102 Pattern.quote(formatter.getNewPrefix()) + "\\S+"); //$NON-NLS-1$
103 oldPathPattern = Pattern.compile(
104 Pattern.quote(formatter.getOldPrefix()) + "\\S+"); //$NON-NLS-1$
105 maximumLineNumbers = formatter.getMaximumLineNumbers();
106 // Connect a new partitioner.
107 IDocumentPartitioner partitioner = new FastPartitioner(
108 new DiffPartitionTokenScanner(),
109 new String[] { IDocument.DEFAULT_CONTENT_TYPE,
110 HEADLINE_CONTENT_TYPE, HUNK_CONTENT_TYPE,
111 ADDED_CONTENT_TYPE, REMOVED_CONTENT_TYPE });
112 IDocumentPartitioner oldPartitioner = getDocumentPartitioner();
113 if (oldPartitioner != null) {
114 oldPartitioner.disconnect();
116 partitioner.connect(this);
117 setDocumentPartitioner(partitioner);
121 * Provide default settings about the {@link Repository} and
122 * {@link FileDiff}, to be used in the absence of explicit information from
123 * a connected {@link DiffRegionFormatter}. Useful if the document is
124 * used for only individual edits from a file.
126 * @param fileDiff
127 * to use if none set explicitly
129 public void setDefault(@NonNull FileDiff fileDiff) {
130 defaultFileDiff = fileDiff;
133 DiffRegion[] getRegions() {
134 return regions;
137 FileDiffRegion[] getFileRegions() {
138 return fileRegions;
141 int getMaximumLineNumber(@NonNull DiffEntry.Side side) {
142 if (maximumLineNumbers == null) {
143 return DiffRegion.NO_LINE;
145 if (DiffEntry.Side.OLD.equals(side)) {
146 return maximumLineNumbers[0];
148 return maximumLineNumbers[1];
151 private int findRegionIndex(int offset) {
152 DiffRegion key = new DiffRegion(offset, 0);
153 return Arrays.binarySearch(regions, key, (a, b) -> {
154 if (!TextUtilities.overlaps(a, b)) {
155 return a.getOffset() - b.getOffset();
157 return 0;
161 DiffRegion findRegion(int offset) {
162 int i = findRegionIndex(offset);
163 return i >= 0 ? regions[i] : null;
166 FileDiffRegion findFileRegion(int offset) {
167 Region key = new Region(offset, 0);
168 int i = Arrays.binarySearch(fileRegions, key, (a, b) -> {
169 if (!TextUtilities.overlaps(a, b)) {
170 return a.getOffset() - b.getOffset();
172 return 0;
174 return i >= 0 ? fileRegions[i] : null;
177 int getLogicalLine(int physicalLine, @NonNull DiffEntry.Side side) {
178 int offset;
179 try {
180 offset = getLineOffset(physicalLine);
181 DiffRegion region = findRegion(offset);
182 if (region == null) {
183 return DiffRegion.NO_LINE;
185 int logicalStart = region.getLine(side);
186 if (logicalStart == DiffRegion.NO_LINE) {
187 return DiffRegion.NO_LINE;
189 int physicalStart = getLineOfOffset(region.getOffset());
190 return logicalStart + (physicalLine - physicalStart);
191 } catch (BadLocationException e) {
192 return DiffRegion.NO_LINE;
196 Pattern getPathPattern(@NonNull DiffEntry.Side side) {
197 switch (side) {
198 case OLD:
199 return oldPathPattern;
200 default:
201 return newPathPattern;
205 private class DiffPartitionTokenScanner implements IPartitionTokenScanner {
207 private final Token HEADLINE_TOKEN = new Token(HEADLINE_CONTENT_TYPE);
209 private final Token HUNK_TOKEN = new Token(HUNK_CONTENT_TYPE);
211 private final Token ADDED_TOKEN = new Token(ADDED_CONTENT_TYPE);
213 private final Token DELETED_TOKEN = new Token(REMOVED_CONTENT_TYPE);
215 private final Token OTHER_TOKEN = new Token(
216 IDocument.DEFAULT_CONTENT_TYPE);
218 private int currentOffset;
220 private int end;
222 private int tokenStart;
224 private int currIdx;
226 @Override
227 public void setRange(IDocument document, int offset, int length) {
228 Assert.isLegal(document == DiffDocument.this);
229 currentOffset = offset;
230 end = offset + length;
231 tokenStart = -1;
234 @Override
235 public IToken nextToken() {
236 if (tokenStart < 0) {
237 currIdx = findRegionIndex(currentOffset);
238 if (currIdx < 0) {
239 currIdx = -(currIdx + 1);
242 tokenStart = currentOffset;
243 if (currentOffset < end) {
244 if (currIdx >= DiffDocument.this.regions.length) {
245 currentOffset = end;
246 return OTHER_TOKEN;
248 if (currentOffset < DiffDocument.this.regions[currIdx]
249 .getOffset()) {
250 currentOffset = DiffDocument.this.regions[currIdx]
251 .getOffset();
252 return OTHER_TOKEN;
254 // We're in range[currIdx]. Typically at the beginning, but if
255 // called via setPartialRange, we may also be somewhere in the
256 // middle.
257 currentOffset += DiffDocument.this.regions[currIdx].getLength()
258 - (currentOffset
259 - DiffDocument.this.regions[currIdx]
260 .getOffset());
261 switch (DiffDocument.this.regions[currIdx++].getType()) {
262 case HEADLINE:
263 return HEADLINE_TOKEN;
264 case HUNK:
265 return HUNK_TOKEN;
266 case ADD:
267 return ADDED_TOKEN;
268 case REMOVE:
269 return DELETED_TOKEN;
270 default:
271 return OTHER_TOKEN;
274 return Token.EOF;
277 @Override
278 public int getTokenOffset() {
279 return tokenStart;
282 @Override
283 public int getTokenLength() {
284 return currentOffset - tokenStart;
287 @Override
288 public void setPartialRange(IDocument document, int offset, int length,
289 String contentType, int partitionOffset) {
290 setRange(document, offset, length);