diff: add annotate action to diff viewers
[idea/community.git] / platform / diff-impl / src / com / intellij / diff / tools / fragmented / UnifiedEditorHighlighter.java
1 /*
2  * Copyright 2000-2015 JetBrains s.r.o.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.intellij.diff.tools.fragmented;
17
18 import com.intellij.openapi.application.ApplicationManager;
19 import com.intellij.openapi.diagnostic.Logger;
20 import com.intellij.openapi.editor.Document;
21 import com.intellij.openapi.editor.colors.EditorColorsScheme;
22 import com.intellij.openapi.editor.event.DocumentEvent;
23 import com.intellij.openapi.editor.highlighter.EditorHighlighter;
24 import com.intellij.openapi.editor.highlighter.HighlighterClient;
25 import com.intellij.openapi.editor.highlighter.HighlighterIterator;
26 import com.intellij.openapi.editor.markup.TextAttributes;
27 import com.intellij.openapi.util.Comparing;
28 import com.intellij.openapi.util.TextRange;
29 import com.intellij.psi.tree.IElementType;
30 import org.jetbrains.annotations.NotNull;
31
32 import java.util.ArrayList;
33 import java.util.Collections;
34 import java.util.Comparator;
35 import java.util.List;
36
37 class UnifiedEditorHighlighter implements EditorHighlighter {
38   public static final Logger LOG = UnifiedDiffViewer.LOG;
39
40   @NotNull private final Document myDocument;
41   @NotNull private final List<Element> myPieces;
42
43   public UnifiedEditorHighlighter(@NotNull Document document,
44                                   @NotNull EditorHighlighter highlighter1,
45                                   @NotNull EditorHighlighter highlighter2,
46                                   @NotNull List<HighlightRange> ranges,
47                                   int textLength) {
48     myDocument = document;
49     myPieces = new ArrayList<Element>();
50     init(highlighter1.createIterator(0), highlighter2.createIterator(0), ranges, textLength);
51   }
52
53   private void init(@NotNull HighlighterIterator it1,
54                     @NotNull HighlighterIterator it2,
55                     @NotNull List<HighlightRange> ranges,
56                     int textLength) {
57     ApplicationManager.getApplication().assertReadAccessAllowed();
58
59     int offset = 0;
60
61     for (HighlightRange range : ranges) {
62       TextRange base = range.getBase();
63       TextRange changed = range.getChanged();
64
65       if (base.isEmpty()) continue;
66
67       if (base.getStartOffset() > offset) {
68         addElement(new Element(offset, base.getStartOffset(), null, TextAttributes.ERASE_MARKER));
69         offset = base.getStartOffset();
70       }
71
72       HighlighterIterator it = range.getSide().select(it1, it2);
73       while (!it.atEnd() && changed.getStartOffset() >= it.getEnd()) {
74         it.advance();
75       }
76
77       if (it.atEnd()) {
78         LOG.error("Unexpected end of highlighter");
79         break;
80       }
81
82       if (changed.getEndOffset() <= it.getStart()) {
83         continue;
84       }
85
86       while (true) {
87         int relativeStart = Math.max(it.getStart() - changed.getStartOffset(), 0);
88         int relativeEnd = Math.min(it.getEnd() - changed.getStartOffset(), changed.getLength() + 1);
89
90         addElement(new Element(offset + relativeStart,
91                                offset + relativeEnd,
92                                it.getTokenType(),
93                                it.getTextAttributes()));
94
95         if (changed.getEndOffset() <= it.getEnd()) {
96           offset += changed.getLength();
97           break;
98         }
99
100         it.advance();
101         if (it.atEnd()) {
102           LOG.error("Unexpected end of highlighter");
103           break;
104         }
105       }
106     }
107
108     if (offset < textLength) {
109       addElement(new Element(offset, textLength, null, TextAttributes.ERASE_MARKER));
110     }
111   }
112
113   private void addElement(@NotNull Element element) {
114     boolean merged = false;
115     if (!myPieces.isEmpty()) {
116       Element oldElement = myPieces.get(myPieces.size() - 1);
117       if (oldElement.getEnd() >= element.getStart() &&
118           Comparing.equal(oldElement.getAttributes(), element.getAttributes()) &&
119           Comparing.equal(oldElement.getElementType(), element.getElementType())) {
120         merged = true;
121         myPieces.remove(myPieces.size() - 1);
122         myPieces.add(new Element(oldElement.getStart(),
123                                  element.getEnd(),
124                                  element.getElementType(),
125                                  element.getAttributes()));
126       }
127     }
128     if (!merged) {
129       myPieces.add(element);
130     }
131   }
132
133   @NotNull
134   @Override
135   public HighlighterIterator createIterator(int startOffset) {
136     int index = Collections.binarySearch(myPieces, new Element(startOffset, 0, null, null), new Comparator<Element>() {
137       @Override
138       public int compare(Element o1, Element o2) {
139         return o1.getStart() - o2.getStart();
140       }
141     });
142     // index: (-insertion point - 1), where insertionPoint is the index of the first element greater than the key
143     // and we need index of the first element that is less or equal (floorElement)
144     if (index < 0) index = Math.max(-index - 2, 0);
145     return new ProxyIterator(myDocument, index, myPieces);
146   }
147
148   @Override
149   public void setColorScheme(@NotNull EditorColorsScheme scheme) {
150   }
151
152   @Override
153   public void setEditor(@NotNull HighlighterClient editor) {
154   }
155
156   @Override
157   public void setText(@NotNull CharSequence text) {
158   }
159
160   @Override
161   public void beforeDocumentChange(DocumentEvent event) {
162   }
163
164   @Override
165   public void documentChanged(DocumentEvent event) {
166   }
167
168   private static class ProxyIterator implements HighlighterIterator {
169     private final Document myDocument;
170     private int myIdx;
171     private final List<Element> myPieces;
172
173     private ProxyIterator(@NotNull Document document, int idx, @NotNull List<Element> pieces) {
174       myDocument = document;
175       myIdx = idx;
176       myPieces = pieces;
177     }
178
179     @Override
180     public TextAttributes getTextAttributes() {
181       return myPieces.get(myIdx).getAttributes();
182     }
183
184     @Override
185     public int getStart() {
186       return myPieces.get(myIdx).getStart();
187     }
188
189     @Override
190     public int getEnd() {
191       return myPieces.get(myIdx).getEnd();
192     }
193
194     @Override
195     public IElementType getTokenType() {
196       return myPieces.get(myIdx).myElementType;
197     }
198
199     @Override
200     public void advance() {
201       if (myIdx < myPieces.size()) {
202         myIdx++;
203       }
204     }
205
206     @Override
207     public void retreat() {
208       if (myIdx > -1) {
209         myIdx--;
210       }
211     }
212
213     @Override
214     public boolean atEnd() {
215       return myIdx < 0 || myIdx >= myPieces.size();
216     }
217
218     @Override
219     public Document getDocument() {
220       return myDocument;
221     }
222   }
223
224   private static class Element {
225     private final int myStart;
226     private final int myEnd;
227     private final IElementType myElementType;
228     private final TextAttributes myAttributes;
229
230     private Element(int start, int end, IElementType elementType, TextAttributes attributes) {
231       myStart = start;
232       myEnd = end;
233       myElementType = elementType;
234       myAttributes = attributes;
235     }
236
237     public int getStart() {
238       return myStart;
239     }
240
241     public int getEnd() {
242       return myEnd;
243     }
244
245     public IElementType getElementType() {
246       return myElementType;
247     }
248
249     public TextAttributes getAttributes() {
250       return myAttributes;
251     }
252   }
253 }