terminal: support custom keyboard shortcuts for Split Right/Split Down (IDEA-237048)
[idea/community.git] / platform / diff-impl / src / com / intellij / diff / tools / fragmented / UnifiedEditorHighlighter.java
1 // Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
2 package com.intellij.diff.tools.fragmented;
3
4 import com.intellij.openapi.application.ApplicationManager;
5 import com.intellij.openapi.diagnostic.Logger;
6 import com.intellij.openapi.editor.Document;
7 import com.intellij.openapi.editor.highlighter.EditorHighlighter;
8 import com.intellij.openapi.editor.highlighter.HighlighterClient;
9 import com.intellij.openapi.editor.highlighter.HighlighterIterator;
10 import com.intellij.openapi.editor.markup.TextAttributes;
11 import com.intellij.openapi.progress.ProgressManager;
12 import com.intellij.openapi.util.Comparing;
13 import com.intellij.openapi.util.TextRange;
14 import com.intellij.psi.tree.IElementType;
15 import org.jetbrains.annotations.NotNull;
16
17 import java.util.ArrayList;
18 import java.util.Collections;
19 import java.util.Comparator;
20 import java.util.List;
21
22 class UnifiedEditorHighlighter implements EditorHighlighter {
23   private static final Logger LOG = Logger.getInstance(UnifiedEditorHighlighter.class);
24
25   @NotNull private final Document myDocument;
26   @NotNull private final List<Element> myPieces;
27
28   UnifiedEditorHighlighter(@NotNull Document document,
29                                   @NotNull EditorHighlighter highlighter1,
30                                   @NotNull EditorHighlighter highlighter2,
31                                   @NotNull List<HighlightRange> ranges,
32                                   int textLength) {
33     myDocument = document;
34     myPieces = new ArrayList<>();
35     init(highlighter1.createIterator(0), highlighter2.createIterator(0), ranges, textLength);
36   }
37
38   private void init(@NotNull HighlighterIterator it1,
39                     @NotNull HighlighterIterator it2,
40                     @NotNull List<HighlightRange> ranges,
41                     int textLength) {
42     ApplicationManager.getApplication().assertReadAccessAllowed();
43
44     int i = 0;
45     int offset = 0;
46
47     for (HighlightRange range : ranges) {
48       TextRange base = range.getBase();
49       TextRange changed = range.getChanged();
50
51       if (base.isEmpty()) continue;
52
53       if (base.getStartOffset() > offset) {
54         addElement(new Element(offset, base.getStartOffset(), null, TextAttributes.ERASE_MARKER));
55         offset = base.getStartOffset();
56       }
57
58       HighlighterIterator it = range.getSide().select(it1, it2);
59       while (!it.atEnd() && changed.getStartOffset() >= it.getEnd()) {
60         if (i++ % 1024 == 0) ProgressManager.checkCanceled();
61         it.advance();
62       }
63
64       if (it.atEnd()) {
65         LOG.error("Unexpected end of highlighter");
66         break;
67       }
68
69       if (changed.getEndOffset() <= it.getStart()) {
70         continue;
71       }
72
73       while (true) {
74         int relativeStart = Math.max(it.getStart() - changed.getStartOffset(), 0);
75         int relativeEnd = Math.min(it.getEnd() - changed.getStartOffset(), changed.getLength() + 1);
76
77         addElement(new Element(offset + relativeStart,
78                                offset + relativeEnd,
79                                it.getTokenType(),
80                                it.getTextAttributes()));
81
82         if (changed.getEndOffset() <= it.getEnd()) {
83           offset += changed.getLength();
84           break;
85         }
86
87         if (i++ % 1024 == 0) ProgressManager.checkCanceled();
88         it.advance();
89         if (it.atEnd()) {
90           LOG.error("Unexpected end of highlighter");
91           break;
92         }
93       }
94     }
95
96     if (offset < textLength) {
97       addElement(new Element(offset, textLength, null, TextAttributes.ERASE_MARKER));
98     }
99   }
100
101   private void addElement(@NotNull Element element) {
102     boolean merged = false;
103     if (!myPieces.isEmpty()) {
104       Element oldElement = myPieces.get(myPieces.size() - 1);
105       if (oldElement.getEnd() >= element.getStart() &&
106           Comparing.equal(oldElement.getAttributes(), element.getAttributes()) &&
107           Comparing.equal(oldElement.getElementType(), element.getElementType())) {
108         merged = true;
109         myPieces.remove(myPieces.size() - 1);
110         myPieces.add(new Element(oldElement.getStart(),
111                                  element.getEnd(),
112                                  element.getElementType(),
113                                  element.getAttributes()));
114       }
115     }
116     if (!merged) {
117       myPieces.add(element);
118     }
119   }
120
121   @NotNull
122   @Override
123   public HighlighterIterator createIterator(int startOffset) {
124     int index = Collections.binarySearch(myPieces, new Element(startOffset, 0, null, TextAttributes.ERASE_MARKER), Comparator.comparingInt(Element::getStart));
125     // index: (-insertion point - 1), where insertionPoint is the index of the first element greater than the key
126     // and we need index of the first element that is less or equal (floorElement)
127     if (index < 0) index = Math.max(-index - 2, 0);
128     return new ProxyIterator(myDocument, index, myPieces);
129   }
130
131   @Override
132   public void setEditor(@NotNull HighlighterClient editor) {
133   }
134
135   private static final class ProxyIterator implements HighlighterIterator {
136     @NotNull
137     private final Document myDocument;
138     private int myIdx;
139     private final List<Element> myPieces;
140
141     private ProxyIterator(@NotNull Document document, int idx, @NotNull List<Element> pieces) {
142       myDocument = document;
143       myIdx = idx;
144       myPieces = pieces;
145     }
146
147     @Override
148     public TextAttributes getTextAttributes() {
149       return myPieces.get(myIdx).getAttributes();
150     }
151
152     @Override
153     public int getStart() {
154       return myPieces.get(myIdx).getStart();
155     }
156
157     @Override
158     public int getEnd() {
159       return myPieces.get(myIdx).getEnd();
160     }
161
162     @Override
163     public IElementType getTokenType() {
164       return myPieces.get(myIdx).myElementType;
165     }
166
167     @Override
168     public void advance() {
169       if (myIdx < myPieces.size()) {
170         myIdx++;
171       }
172     }
173
174     @Override
175     public void retreat() {
176       if (myIdx > -1) {
177         myIdx--;
178       }
179     }
180
181     @Override
182     public boolean atEnd() {
183       return myIdx < 0 || myIdx >= myPieces.size();
184     }
185
186     @NotNull
187     @Override
188     public Document getDocument() {
189       return myDocument;
190     }
191   }
192
193   private static final class Element {
194     private final int myStart;
195     private final int myEnd;
196     private final IElementType myElementType;
197     private final TextAttributes myAttributes;
198
199     private Element(int start, int end, IElementType elementType, @NotNull TextAttributes attributes) {
200       myStart = start;
201       myEnd = end;
202       myElementType = elementType;
203       myAttributes = attributes;
204     }
205
206     int getStart() {
207       return myStart;
208     }
209
210     int getEnd() {
211       return myEnd;
212     }
213
214     IElementType getElementType() {
215       return myElementType;
216     }
217
218     @NotNull TextAttributes getAttributes() {
219       return myAttributes;
220     }
221   }
222 }