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
;
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
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
;
65 * Creates a new {@link DiffDocument}.
67 public DiffDocument() {
72 * Creates a new {@link DiffDocument} with initial text.
75 * to set on the document
77 public DiffDocument(String text
) {
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
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.
127 * to use if none set explicitly
129 public void setDefault(@NonNull FileDiff fileDiff
) {
130 defaultFileDiff
= fileDiff
;
133 DiffRegion
[] getRegions() {
137 FileDiffRegion
[] getFileRegions() {
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();
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();
174 return i
>= 0 ? fileRegions
[i
] : null;
177 int getLogicalLine(int physicalLine
, @NonNull DiffEntry
.Side side
) {
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
) {
199 return oldPathPattern
;
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
;
222 private int tokenStart
;
227 public void setRange(IDocument document
, int offset
, int length
) {
228 Assert
.isLegal(document
== DiffDocument
.this);
229 currentOffset
= offset
;
230 end
= offset
+ length
;
235 public IToken
nextToken() {
236 if (tokenStart
< 0) {
237 currIdx
= findRegionIndex(currentOffset
);
239 currIdx
= -(currIdx
+ 1);
242 tokenStart
= currentOffset
;
243 if (currentOffset
< end
) {
244 if (currIdx
>= DiffDocument
.this.regions
.length
) {
248 if (currentOffset
< DiffDocument
.this.regions
[currIdx
]
250 currentOffset
= DiffDocument
.this.regions
[currIdx
]
254 // We're in range[currIdx]. Typically at the beginning, but if
255 // called via setPartialRange, we may also be somewhere in the
257 currentOffset
+= DiffDocument
.this.regions
[currIdx
].getLength()
259 - DiffDocument
.this.regions
[currIdx
]
261 switch (DiffDocument
.this.regions
[currIdx
++].getType()) {
263 return HEADLINE_TOKEN
;
269 return DELETED_TOKEN
;
278 public int getTokenOffset() {
283 public int getTokenLength() {
284 return currentOffset
- tokenStart
;
288 public void setPartialRange(IDocument document
, int offset
, int length
,
289 String contentType
, int partitionOffset
) {
290 setRange(document
, offset
, length
);