Have icon for "reset" entry in reflog
[egit/eclipse.git] / org.eclipse.egit.ui / src / org / eclipse / egit / ui / internal / commit / DiffRegionFormatter.java
blobf1eae81d400f9ae632a5500a05c7975824c20ddd
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 *******************************************************************************/
14 package org.eclipse.egit.ui.internal.commit;
16 import java.io.ByteArrayOutputStream;
17 import java.io.IOException;
18 import java.io.OutputStream;
19 import java.util.ArrayList;
20 import java.util.List;
22 import org.eclipse.egit.core.internal.CompareCoreUtils;
23 import org.eclipse.egit.ui.internal.UIText;
24 import org.eclipse.egit.ui.internal.commit.DiffRegionFormatter.DiffRegion.Type;
25 import org.eclipse.egit.ui.internal.history.FileDiff;
26 import org.eclipse.jface.text.BadLocationException;
27 import org.eclipse.jface.text.IDocument;
28 import org.eclipse.jface.text.Region;
29 import org.eclipse.jgit.annotations.NonNull;
30 import org.eclipse.jgit.diff.DiffEntry;
31 import org.eclipse.jgit.diff.DiffEntry.ChangeType;
32 import org.eclipse.jgit.diff.DiffFormatter;
33 import org.eclipse.jgit.diff.EditList;
34 import org.eclipse.jgit.diff.RawText;
35 import org.eclipse.jgit.lib.Repository;
36 import org.eclipse.osgi.util.NLS;
38 /**
39 * Diff region formatter class that builds up a list of
40 * {@link DiffRegion} instances as each {@link FileDiff} is being written to
41 * an {@link IDocument}.
43 public class DiffRegionFormatter extends DiffFormatter {
45 /**
46 * A text {@link Region} describing an interesting region in a unified diff.
48 public static class DiffRegion extends Region {
50 /** Constant {@value} indicating that no line number exists. */
51 public static final int NO_LINE = -1;
53 /**
54 * The type of a {@link DiffRegion}.
56 public enum Type {
58 /** Added line. */
59 ADD,
61 /** Removed line. */
62 REMOVE,
64 /** Hunk line. */
65 HUNK,
67 /** Headline. */
68 HEADLINE,
70 /** Header (after HEADLINE). */
71 HEADER,
73 /** A context line in a hunk. */
74 CONTEXT,
76 /** Other line. */
77 OTHER,
81 private final @NonNull Type type;
83 private final int aLine;
85 private final int bLine;
87 /**
88 * @param offset
89 * @param length
91 public DiffRegion(int offset, int length) {
92 this(offset, length, NO_LINE, NO_LINE, Type.OTHER);
95 /**
96 * @param offset
97 * @param length
98 * @param aLine
99 * @param bLine
100 * @param type
102 public DiffRegion(int offset, int length, int aLine, int bLine,
103 @NonNull Type type) {
104 super(offset, length);
105 this.type = type;
106 this.aLine = aLine;
107 this.bLine = bLine;
111 * @return the {@link Type} of the region
113 public @NonNull Type getType() {
114 return type;
118 * Returns the first logical line number of the region.
120 * @param side
121 * to get the line number of
122 * @return the line number; -1 indicates that the range has no line
123 * number for the given side.
125 public int getLine(@NonNull DiffEntry.Side side) {
126 if (DiffEntry.Side.NEW.equals(side)) {
127 return bLine;
129 return aLine;
132 @Override
133 public boolean equals(Object object) {
134 return super.equals(object);
137 @Override
138 public int hashCode() {
139 return super.hashCode();
144 * Region giving access to the {@link FileDiff} that generated the content.
146 public static class FileDiffRegion extends Region {
148 private final @NonNull FileDiff diff;
151 * Creates a new {@link FileDiffRegion}.
153 * @param fileDiff
154 * the range belongs to
155 * @param start
156 * of the range
157 * @param length
158 * of the range
160 public FileDiffRegion(@NonNull FileDiff fileDiff,
161 int start, int length) {
162 super(start, length);
163 this.diff = fileDiff;
167 * Retrieves the {@link FileDiff}.
169 * @return the {@link FileDiff}
171 @NonNull
172 public FileDiff getDiff() {
173 return diff;
176 @Override
177 public boolean equals(Object object) {
178 return super.equals(object);
181 @Override
182 public int hashCode() {
183 return super.hashCode();
186 @Override
187 public String toString() {
188 return "[FileDiffRange " + diff.getPath() //$NON-NLS-1$
189 + ' ' + super.toString() + ']';
193 private static class DocumentOutputStream extends OutputStream {
195 private String charset;
197 private IDocument document;
199 private int offset;
201 private StringBuilder lineBuffer = new StringBuilder();
203 public DocumentOutputStream(IDocument document, int offset) {
204 this.document = document;
205 this.offset = offset;
208 private void write(String content) throws IOException {
209 try {
210 this.document.replace(this.offset, 0, content);
211 this.offset += content.length();
212 } catch (BadLocationException e) {
213 throw new IOException(e.getMessage());
217 @Override
218 public void write(byte[] b, int off, int len) throws IOException {
219 if (charset == null)
220 lineBuffer.append(new String(b, off, len, "UTF-8")); //$NON-NLS-1$
221 else
222 lineBuffer.append(new String(b, off, len, charset));
225 @Override
226 public void write(byte[] b) throws IOException {
227 write(b, 0, b.length);
230 @Override
231 public void write(int b) throws IOException {
232 write(new byte[] { (byte) b });
235 @Override
236 public void flush() throws IOException {
237 flushLine();
240 protected void flushLine() throws IOException {
241 if (lineBuffer.length() > 0) {
242 write(lineBuffer.toString());
243 lineBuffer.setLength(0);
248 private DocumentOutputStream stream;
250 private List<DiffRegion> regions = new ArrayList<>();
252 private List<FileDiffRegion> fileRegions = new ArrayList<>();
254 private final int maxLines;
256 private int linesWritten;
258 private int lastNewLine;
260 private int[] maximumLineNumbers = new int[] { DiffRegion.NO_LINE,
261 DiffRegion.NO_LINE };
264 * @param document
265 * @param offset
267 public DiffRegionFormatter(IDocument document, int offset) {
268 this(document, offset, -1);
272 * @param document
274 public DiffRegionFormatter(IDocument document) {
275 this(document, document.getLength(), -1);
279 * @param document
280 * @param offset
281 * @param maxLines
283 public DiffRegionFormatter(IDocument document, int offset,
284 int maxLines) {
285 super(new DocumentOutputStream(document, offset));
286 this.stream = (DocumentOutputStream) getOutputStream();
287 this.maxLines = maxLines;
288 this.lastNewLine = DiffRegion.NO_LINE;
292 * Write diff
294 * @param diff
295 * @return this formatter
296 * @throws IOException
298 public DiffRegionFormatter write(FileDiff diff)
299 throws IOException {
300 Repository repository = diff.getRepository();
301 this.stream.charset = CompareCoreUtils.getResourceEncoding(repository,
302 diff.getPath());
303 int start = stream.offset;
304 diff.outputDiff(null, repository, this, true);
305 flush();
306 fileRegions.add(new FileDiffRegion(diff, start, stream.offset - start));
307 return this;
311 * Get diff regions, sorted by offset
313 * @return non-null but possibly empty array
315 public DiffRegion[] getRegions() {
316 return this.regions.toArray(new DiffRegion[this.regions.size()]);
320 * Gets the file diff regions, sorted by offset.
322 * @return the regions; non-null but possibly empty
324 public FileDiffRegion[] getFileRegions() {
325 return this.fileRegions
326 .toArray(new FileDiffRegion[this.fileRegions.size()]);
330 * Retrieves the maximum line numbers for hunk lines.
332 * @return an array with two elements, index 0 being the maximum old line
333 * number and index 1 the maximum new line number
335 public int[] getMaximumLineNumbers() {
336 return maximumLineNumbers.clone();
340 * Create and add a new {@link DiffRegion} without line number information,
341 * coalescing it with the previous region,if any, if that has the same type
342 * and the two regions are adjacent.
344 * @param type
345 * the {@link Type}
346 * @param start
347 * start offset
348 * @param end
349 * end offset
350 * @return added range
352 protected DiffRegion addRegion(@NonNull Type type, int start, int end) {
353 return addRegion(type, start, end, DiffRegion.NO_LINE,
354 DiffRegion.NO_LINE);
358 * Create and add a new {@link DiffRegion}, coalescing it with the previous
359 * region,if any, if that has the same type and the two regions are
360 * adjacent.
362 * @param type
363 * the {@link Type}
364 * @param start
365 * start offset
366 * @param end
367 * end offset
368 * @param aLine
369 * line number in the old version, or {@link DiffRegion#NO_LINE}
370 * @param bLine
371 * line number in the new version, or {@link DiffRegion#NO_LINE}
372 * @return added range
374 protected DiffRegion addRegion(@NonNull Type type, int start, int end,
375 int aLine, int bLine) {
376 maximumLineNumbers[0] = Math.max(aLine, maximumLineNumbers[0]);
377 maximumLineNumbers[1] = Math.max(bLine, maximumLineNumbers[1]);
378 if (bLine != DiffRegion.NO_LINE) {
379 lastNewLine = bLine;
381 if (!regions.isEmpty()) {
382 DiffRegion last = regions.get(regions.size() - 1);
383 if (last.getType().equals(type)
384 && start == last.getOffset() + last.getLength()) {
385 regions.remove(regions.size() - 1);
386 start = last.getOffset();
387 aLine = last.getLine(DiffEntry.Side.OLD);
388 bLine = last.getLine(DiffEntry.Side.NEW);
391 DiffRegion range = new DiffRegion(start, end - start, aLine, bLine,
392 type);
393 regions.add(range);
394 return range;
397 @Override
398 protected void writeHunkHeader(int aStartLine, int aEndLine,
399 int bStartLine, int bEndLine) throws IOException {
400 int start = stream.offset;
401 if (!regions.isEmpty()) {
402 DiffRegion last = regions.get(regions.size() - 1);
403 int lastEnd = last.getOffset() + last.getLength();
404 if (last.getType().equals(Type.HEADLINE) && lastEnd < start) {
405 addRegion(Type.HEADER, lastEnd, start);
408 super.writeHunkHeader(aStartLine, aEndLine, bStartLine, bEndLine);
409 stream.flushLine();
410 addRegion(Type.HUNK, start, stream.offset);
411 lastNewLine = bStartLine - 1;
414 @Override
415 protected void writeLine(char prefix, RawText text, int cur)
416 throws IOException {
417 if (maxLines > 0 && linesWritten > maxLines) {
418 if (linesWritten == maxLines + 1) {
419 int start = stream.offset;
420 stream.flushLine();
421 stream.write(
422 NLS.bind(UIText.DiffStyleRangeFormatter_diffTruncated,
423 Integer.valueOf(maxLines)));
424 stream.write("\n"); //$NON-NLS-1$
425 addRegion(Type.HEADLINE, start, stream.offset);
426 linesWritten++;
428 return;
431 int start = stream.offset;
432 super.writeLine(prefix, text, cur);
433 stream.flushLine();
434 if (prefix == ' ') {
435 addRegion(Type.CONTEXT, start, stream.offset, cur, ++lastNewLine);
436 } else if (prefix == '+') {
437 addRegion(Type.ADD, start, stream.offset, DiffRegion.NO_LINE, cur);
439 } else {
440 addRegion(Type.REMOVE, start, stream.offset, cur,
441 DiffRegion.NO_LINE);
443 linesWritten++;
447 * @see org.eclipse.jgit.diff.DiffFormatter#formatGitDiffFirstHeaderLine(ByteArrayOutputStream
448 * o, ChangeType type, String oldPath, String newPath)
450 @Override
451 protected void formatGitDiffFirstHeaderLine(ByteArrayOutputStream o,
452 final ChangeType type, final String oldPath, final String newPath)
453 throws IOException {
454 stream.flushLine();
455 int offset = stream.offset;
456 int start = o.size();
457 super.formatGitDiffFirstHeaderLine(o, type, oldPath, newPath);
458 int end = o.size();
459 addRegion(Type.HEADLINE, offset + start, offset + end);
462 @Override
463 public void format(final EditList edits, final RawText a, final RawText b)
464 throws IOException {
465 // Flush header before formatting of edits begin
466 stream.flushLine();
467 super.format(edits, a, b);