2 * Copyright 2000-2009 JetBrains s.r.o.
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
16 package com
.intellij
.openapi
.editor
.impl
;
18 import com
.intellij
.openapi
.application
.ex
.ApplicationManagerEx
;
19 import com
.intellij
.openapi
.diagnostic
.Logger
;
20 import com
.intellij
.openapi
.editor
.CaretModel
;
21 import com
.intellij
.openapi
.editor
.FoldRegion
;
22 import com
.intellij
.openapi
.editor
.RangeMarker
;
23 import com
.intellij
.openapi
.editor
.colors
.EditorColors
;
24 import com
.intellij
.openapi
.editor
.ex
.DocumentEx
;
25 import com
.intellij
.openapi
.editor
.ex
.EditorEx
;
26 import com
.intellij
.openapi
.editor
.ex
.FoldingModelEx
;
27 import com
.intellij
.openapi
.editor
.ex
.MarkupModelEx
;
28 import com
.intellij
.openapi
.editor
.highlighter
.HighlighterIterator
;
29 import com
.intellij
.openapi
.editor
.markup
.EffectType
;
30 import com
.intellij
.openapi
.editor
.markup
.HighlighterLayer
;
31 import com
.intellij
.openapi
.editor
.markup
.HighlighterTargetArea
;
32 import com
.intellij
.openapi
.editor
.markup
.TextAttributes
;
33 import com
.intellij
.util
.containers
.ContainerUtil
;
34 import org
.jetbrains
.annotations
.Nullable
;
37 import java
.util
.ArrayList
;
38 import java
.util
.Comparator
;
39 import java
.util
.List
;
41 @SuppressWarnings({"ForLoopReplaceableByForEach"}) // Way too many garbage in AbrstractList.iterator() produced otherwise.
42 public final class IterationState
{
43 private static final Logger LOG
= Logger
.getInstance("#com.intellij.openapi.editor.impl.IterationState");
44 private final TextAttributes myMergedAttributes
= new TextAttributes();
46 private final HighlighterIterator myHighlighterIterator
;
47 private final List
<RangeHighlighterImpl
> myViewHighlighters
;
48 private int myCurrentViewHighlighterIdx
;
50 private final List
<RangeHighlighterImpl
> myDocumentHighlighters
;
51 private int myCurrentDocHighlighterIdx
;
53 private int myStartOffset
;
54 private int myEndOffset
;
55 private final int myEnd
;
57 private final int mySelectionStart
;
58 private final int mySelectionEnd
;
60 private ArrayList
<RangeHighlighterImpl
> myCurrentHighlighters
;
62 private RangeHighlighterImpl myNextViewHighlighter
= null;
63 private RangeHighlighterImpl myNextDocumentHighlighter
= null;
65 private final FoldingModelEx myFoldingModel
;
67 private final boolean hasSelection
;
68 private FoldRegion myCurrentFold
= null;
69 private final TextAttributes myFoldTextAttributes
;
70 private final TextAttributes mySelectionAttributes
;
71 private final TextAttributes myCaretRowAttributes
;
72 private final Color myDefaultBackground
;
73 private final Color myDefaultForeground
;
74 private final int myCaretRowStart
;
75 private final int myCaretRowEnd
;
76 private final ArrayList
<TextAttributes
> myCachedAttributesList
;
77 private final DocumentEx myDocument
;
78 private final EditorEx myEditor
;
79 private final Color myReadOnlyColor
;
81 public IterationState(EditorEx editor
, int start
, boolean useCaretAndSelection
) {
82 ApplicationManagerEx
.getApplicationEx().assertIsDispatchThread(editor
.getComponent());
83 myDocument
= (DocumentEx
)editor
.getDocument();
84 myStartOffset
= start
;
85 myEnd
= editor
.getDocument().getTextLength();
88 LOG
.assertTrue(myStartOffset
<= myEnd
);
89 myHighlighterIterator
= editor
.getHighlighter().createIterator(start
);
91 HighlighterList editorList
= ((MarkupModelEx
)editor
.getMarkupModel()).getHighlighterList();
93 int longestViewHighlighterLength
= editorList
== null ?
0 : editorList
.getLongestHighlighterLength();
94 myViewHighlighters
= editorList
== null ?
null : editorList
.getSortedHighlighters();
96 final MarkupModelEx docMarkup
= (MarkupModelEx
)editor
.getDocument().getMarkupModel(editor
.getProject());
98 final HighlighterList docList
= docMarkup
.getHighlighterList();
99 myDocumentHighlighters
= docList
!= null
100 ? docList
.getSortedHighlighters()
101 : new ArrayList
<RangeHighlighterImpl
>();
103 int longestDocHighlighterLength
= docList
!= null
104 ? docList
.getLongestHighlighterLength()
107 hasSelection
= useCaretAndSelection
&& editor
.getSelectionModel().hasSelection();
108 mySelectionStart
= hasSelection ? editor
.getSelectionModel().getSelectionStart() : -1;
109 mySelectionEnd
= hasSelection ? editor
.getSelectionModel().getSelectionEnd() : -1;
111 myFoldingModel
= (FoldingModelEx
)editor
.getFoldingModel();
112 myFoldTextAttributes
= myFoldingModel
.getPlaceholderAttributes();
113 mySelectionAttributes
= editor
.getSelectionModel().getTextAttributes();
115 myReadOnlyColor
= myEditor
.getColorsScheme().getColor(EditorColors
.READONLY_FRAGMENT_BACKGROUND_COLOR
);
117 CaretModel caretModel
= editor
.getCaretModel();
118 myCaretRowAttributes
= editor
.isRendererMode() ?
null : caretModel
.getTextAttributes();
119 myDefaultBackground
= editor
.getColorsScheme().getDefaultBackground();
120 myDefaultForeground
= editor
.getColorsScheme().getDefaultForeground();
122 myCurrentHighlighters
= new ArrayList
<RangeHighlighterImpl
>();
124 myCurrentViewHighlighterIdx
= initHighlighterIterator(start
, myViewHighlighters
, longestViewHighlighterLength
);
125 while (myCurrentViewHighlighterIdx
< myViewHighlighters
.size()) {
126 myNextViewHighlighter
= myViewHighlighters
.get(myCurrentViewHighlighterIdx
);
127 if (!skipHighlighter(myNextViewHighlighter
)) break;
128 myCurrentViewHighlighterIdx
++;
130 if (myCurrentViewHighlighterIdx
== myViewHighlighters
.size()) myNextViewHighlighter
= null;
132 myCurrentDocHighlighterIdx
= initHighlighterIterator(start
, myDocumentHighlighters
, longestDocHighlighterLength
);
133 myNextDocumentHighlighter
= null;
134 while (myCurrentDocHighlighterIdx
< myDocumentHighlighters
.size()) {
135 myNextDocumentHighlighter
= myDocumentHighlighters
.get(myCurrentDocHighlighterIdx
);
136 if (!skipHighlighter(myNextDocumentHighlighter
)) break;
137 myCurrentDocHighlighterIdx
++;
139 if (myCurrentDocHighlighterIdx
== myDocumentHighlighters
.size()) myNextDocumentHighlighter
= null;
141 advanceSegmentHighlighters();
143 myCaretRowStart
= caretModel
.getVisualLineStart();
144 myCaretRowEnd
= caretModel
.getVisualLineEnd();
146 myEndOffset
= Math
.min(getHighlighterEnd(myStartOffset
), getSelectionEnd(myStartOffset
));
147 myEndOffset
= Math
.min(myEndOffset
, getSegmentHighlightersEnd());
148 myEndOffset
= Math
.min(myEndOffset
, getFoldRangesEnd(myStartOffset
));
149 myEndOffset
= Math
.min(myEndOffset
, getCaretEnd(myStartOffset
));
150 myEndOffset
= Math
.min(myEndOffset
, getGuardedBlockEnd(myStartOffset
));
152 myCurrentFold
= myFoldingModel
.getCollapsedRegionAtOffset(myStartOffset
);
153 if (myCurrentFold
!= null) {
154 myEndOffset
= myCurrentFold
.getEndOffset();
157 myCachedAttributesList
= new ArrayList
<TextAttributes
>(5);
162 private int initHighlighterIterator(int start
, List
<RangeHighlighterImpl
> sortedHighlighters
, int longestHighlighterLength
) {
164 int high
= sortedHighlighters
.size();
165 int search
= myDocument
.getLineStartOffset(myDocument
.getLineNumber(start
)) -
166 longestHighlighterLength
- 1;
170 int mid
= (low
+ high
) / 2;
171 while (mid
> 0 && !sortedHighlighters
.get(mid
).isValid()) mid
--;
172 if (mid
< low
+ 1) break;
173 RangeHighlighterImpl midHighlighter
= sortedHighlighters
.get(mid
);
174 if (midHighlighter
.getStartOffset() < search
) {
183 for (int i
= low
== high ? low
: 0; i
< sortedHighlighters
.size(); i
++) {
184 RangeHighlighterImpl rangeHighlighter
= sortedHighlighters
.get(i
);
185 if (!skipHighlighter(rangeHighlighter
) &&
186 rangeHighlighter
.getAffectedAreaEndOffset() >= start
) {
190 return sortedHighlighters
.size();
193 private boolean skipHighlighter(RangeHighlighterImpl highlighter
) {
194 if (!highlighter
.isValid() || highlighter
.isAfterEndOfLine() || highlighter
.getTextAttributes() == null) return true;
195 final FoldRegion region
= myFoldingModel
.getCollapsedRegionAtOffset(highlighter
.getAffectedAreaStartOffset());
196 if (region
!= null && region
== myFoldingModel
.getCollapsedRegionAtOffset(highlighter
.getAffectedAreaEndOffset())) return true;
197 return !highlighter
.getEditorFilter().avaliableIn(myEditor
);
200 public void advance() {
201 myStartOffset
= myEndOffset
;
202 advanceSegmentHighlighters();
204 myCurrentFold
= myFoldingModel
.fetchOutermost(myStartOffset
);
205 if (myCurrentFold
!= null) {
206 myEndOffset
= myCurrentFold
.getEndOffset();
209 myEndOffset
= Math
.min(getHighlighterEnd(myStartOffset
), getSelectionEnd(myStartOffset
));
210 myEndOffset
= Math
.min(myEndOffset
, getSegmentHighlightersEnd());
211 myEndOffset
= Math
.min(myEndOffset
, getFoldRangesEnd(myStartOffset
));
212 myEndOffset
= Math
.min(myEndOffset
, getCaretEnd(myStartOffset
));
213 myEndOffset
= Math
.min(myEndOffset
, getGuardedBlockEnd(myStartOffset
));
219 private int getHighlighterEnd(int start
) {
220 while (!myHighlighterIterator
.atEnd()) {
221 int end
= myHighlighterIterator
.getEnd();
225 myHighlighterIterator
.advance();
230 private int getCaretEnd(int start
) {
231 if (myCaretRowStart
> start
) {
232 return myCaretRowStart
;
235 if (myCaretRowEnd
> start
) {
236 return myCaretRowEnd
;
242 private int getGuardedBlockEnd(int start
) {
243 List
<RangeMarker
> blocks
= myDocument
.getGuardedBlocks();
245 for (int i
= 0; i
< blocks
.size(); i
++) {
246 RangeMarker block
= blocks
.get(i
);
247 if (block
.getStartOffset() > start
) {
248 min
= Math
.min(min
, block
.getStartOffset());
250 else if (block
.getEndOffset() > start
) {
251 min
= Math
.min(min
, block
.getEndOffset());
257 private int getSelectionEnd(int start
) {
261 if (mySelectionStart
> start
) {
262 return mySelectionStart
;
264 if (mySelectionEnd
> start
) {
265 return mySelectionEnd
;
270 private void advanceSegmentHighlighters() {
271 if (myNextDocumentHighlighter
!= null) {
272 if (myNextDocumentHighlighter
.getAffectedAreaStartOffset() <= myStartOffset
) {
273 myCurrentHighlighters
.add(myNextDocumentHighlighter
);
274 myNextDocumentHighlighter
= null;
278 if (myNextViewHighlighter
!= null) {
279 if (myNextViewHighlighter
.getAffectedAreaStartOffset() <= myStartOffset
) {
280 myCurrentHighlighters
.add(myNextViewHighlighter
);
281 myNextViewHighlighter
= null;
286 RangeHighlighterImpl highlighter
;
288 final int docHighlightersSize
= myDocumentHighlighters
.size();
289 while (myNextDocumentHighlighter
== null && myCurrentDocHighlighterIdx
< docHighlightersSize
) {
290 highlighter
= myDocumentHighlighters
.get(myCurrentDocHighlighterIdx
++);
291 if (!skipHighlighter(highlighter
)) {
292 if (highlighter
.getAffectedAreaStartOffset() > myStartOffset
) {
293 myNextDocumentHighlighter
= highlighter
;
297 myCurrentHighlighters
.add(highlighter
);
302 final int viewHighlightersSize
= myViewHighlighters
.size();
303 while (myNextViewHighlighter
== null && myCurrentViewHighlighterIdx
< viewHighlightersSize
) {
304 highlighter
= myViewHighlighters
.get(myCurrentViewHighlighterIdx
++);
305 if (!skipHighlighter(highlighter
)) {
306 if (highlighter
.getAffectedAreaStartOffset() > myStartOffset
) {
307 myNextViewHighlighter
= highlighter
;
311 myCurrentHighlighters
.add(highlighter
);
316 if (myCurrentHighlighters
.size() == 1) {
318 if (myCurrentHighlighters
.get(0).getAffectedAreaEndOffset() <= myStartOffset
) {
319 myCurrentHighlighters
= new ArrayList
<RangeHighlighterImpl
>();
322 else if (!myCurrentHighlighters
.isEmpty()) {
323 ArrayList
<RangeHighlighterImpl
> copy
= new ArrayList
<RangeHighlighterImpl
>(myCurrentHighlighters
.size());
324 for (int i
= 0; i
< myCurrentHighlighters
.size(); i
++) {
325 highlighter
= myCurrentHighlighters
.get(i
);
326 if (highlighter
.getAffectedAreaEndOffset() > myStartOffset
) {
327 copy
.add(highlighter
);
330 myCurrentHighlighters
= copy
;
334 private int getFoldRangesEnd(int startOffset
) {
336 FoldRegion
[] topLevelCollapsed
= myFoldingModel
.fetchTopLevel();
337 if (topLevelCollapsed
!= null) {
338 for (int i
= myFoldingModel
.getLastCollapsedRegionBefore(startOffset
) + 1;
339 i
>= 0 && i
< topLevelCollapsed
.length
;
341 FoldRegion range
= topLevelCollapsed
[i
];
342 if (!range
.isValid()) continue;
344 int rangeEnd
= range
.getStartOffset();
345 if (rangeEnd
> startOffset
) {
346 if (rangeEnd
< end
) {
359 private int getSegmentHighlightersEnd() {
362 for (RangeHighlighterImpl highlighter
: myCurrentHighlighters
) {
363 if (highlighter
.getAffectedAreaEndOffset() < end
) {
364 end
= highlighter
.getAffectedAreaEndOffset();
368 if (myNextDocumentHighlighter
!= null && myNextDocumentHighlighter
.getAffectedAreaStartOffset() < end
) {
369 end
= myNextDocumentHighlighter
.getAffectedAreaStartOffset();
372 if (myNextViewHighlighter
!= null && myNextViewHighlighter
.getAffectedAreaStartOffset() < end
) {
373 end
= myNextViewHighlighter
.getAffectedAreaStartOffset();
379 private void reinit() {
380 if (myHighlighterIterator
.atEnd()) {
384 boolean isInSelection
= hasSelection
&& myStartOffset
>= mySelectionStart
&& myStartOffset
< mySelectionEnd
;
385 boolean isInCaretRow
= myStartOffset
>= myCaretRowStart
&& myStartOffset
< myCaretRowEnd
;
386 boolean isInGuardedBlock
= myDocument
.getOffsetGuard(myStartOffset
) != null;
388 TextAttributes syntax
= myHighlighterIterator
.getTextAttributes();
390 TextAttributes selection
= isInSelection ? mySelectionAttributes
: null;
391 TextAttributes caret
= isInCaretRow ? myCaretRowAttributes
: null;
392 TextAttributes fold
= myCurrentFold
!= null ? myFoldTextAttributes
: null;
393 TextAttributes guard
= isInGuardedBlock
394 ?
new TextAttributes(null, myReadOnlyColor
, null, EffectType
.BOXED
, Font
.PLAIN
)
397 final int size
= myCurrentHighlighters
.size();
399 ContainerUtil
.quickSort(myCurrentHighlighters
, LayerComparator
.INSTANCE
);
402 for (int i
= 0; i
< size
; i
++) {
403 RangeHighlighterImpl highlighter
= myCurrentHighlighters
.get(i
);
404 if (highlighter
.getTextAttributes() == TextAttributes
.ERASE_MARKER
) {
409 myCachedAttributesList
.clear();
411 for (int i
= 0; i
< size
; i
++) {
412 RangeHighlighterImpl highlighter
= myCurrentHighlighters
.get(i
);
413 if (selection
!= null && highlighter
.getLayer() < HighlighterLayer
.SELECTION
) {
414 myCachedAttributesList
.add(selection
);
418 if (syntax
!= null && highlighter
.getLayer() < HighlighterLayer
.SYNTAX
) {
420 myCachedAttributesList
.add(fold
);
424 myCachedAttributesList
.add(syntax
);
428 if (guard
!= null && highlighter
.getLayer() < HighlighterLayer
.GUARDED_BLOCKS
) {
429 myCachedAttributesList
.add(guard
);
433 if (caret
!= null && highlighter
.getLayer() < HighlighterLayer
.CARET_ROW
) {
434 myCachedAttributesList
.add(caret
);
438 TextAttributes textAttributes
= highlighter
.getTextAttributes();
439 if (textAttributes
!= null) {
440 myCachedAttributesList
.add(textAttributes
);
444 if (selection
!= null) myCachedAttributesList
.add(selection
);
445 if (fold
!= null) myCachedAttributesList
.add(fold
);
446 if (guard
!= null) myCachedAttributesList
.add(guard
);
447 if (syntax
!= null) myCachedAttributesList
.add(syntax
);
448 if (caret
!= null) myCachedAttributesList
.add(caret
);
451 Color back
= isInGuardedBlock ? myReadOnlyColor
: null;
453 EffectType effectType
= null;
456 for (int i
= 0; i
< myCachedAttributesList
.size(); i
++) {
457 TextAttributes attrs
= myCachedAttributesList
.get(i
);
460 fore
= ifDiffers(attrs
.getForegroundColor(), myDefaultForeground
);
464 back
= ifDiffers(attrs
.getBackgroundColor(), myDefaultBackground
);
467 if (fontType
== Font
.PLAIN
) {
468 fontType
= attrs
.getFontType();
471 if (effect
== null) {
472 effect
= attrs
.getEffectColor();
473 effectType
= attrs
.getEffectType();
477 if (fore
== null) fore
= myDefaultForeground
;
478 if (back
== null) back
= myDefaultBackground
;
479 if (effectType
== null) effectType
= EffectType
.BOXED
;
481 myMergedAttributes
.setForegroundColor(fore
);
482 myMergedAttributes
.setBackgroundColor(back
);
483 myMergedAttributes
.setFontType(fontType
);
484 myMergedAttributes
.setEffectColor(effect
);
485 myMergedAttributes
.setEffectType(effectType
);
489 private static Color
ifDiffers(final Color c1
, final Color c2
) {
490 return c1
== c2 ?
null : c1
;
493 public boolean atEnd() {
494 return myStartOffset
>= myEnd
;
498 public int getStartOffset() {
499 return myStartOffset
;
502 public int getEndOffset() {
506 public TextAttributes
getMergedAttributes() {
507 return myMergedAttributes
;
510 public FoldRegion
getCurrentFold() {
511 return myCurrentFold
;
515 public Color
getPastFileEndBackground() {
516 boolean isInCaretRow
= myEditor
.getCaretModel().getLogicalPosition().line
>= myDocument
.getLineCount() - 1;
518 Color caret
= isInCaretRow
&& myCaretRowAttributes
!= null ? myCaretRowAttributes
.getBackgroundColor() : null;
520 ContainerUtil
.quickSort(myCurrentHighlighters
, LayerComparator
.INSTANCE
);
522 for (int i
= 0; i
< myCurrentHighlighters
.size(); i
++) {
523 RangeHighlighterImpl highlighter
= myCurrentHighlighters
.get(i
);
524 if (caret
!= null && highlighter
.getLayer() < HighlighterLayer
.CARET_ROW
) {
528 if (highlighter
.getTargetArea() != HighlighterTargetArea
.LINES_IN_RANGE
529 || myDocument
.getLineNumber(highlighter
.getEndOffset()) < myDocument
.getLineCount() - 1) {
533 TextAttributes textAttributes
= highlighter
.getTextAttributes();
534 if (textAttributes
!= null) {
535 Color backgroundColor
= textAttributes
.getBackgroundColor();
536 if (backgroundColor
!= null) return backgroundColor
;
543 private static class LayerComparator
implements Comparator
<RangeHighlighterImpl
> {
544 private static final LayerComparator INSTANCE
= new LayerComparator();
545 public int compare(RangeHighlighterImpl o1
, RangeHighlighterImpl o2
) {
546 int layerDiff
= o2
.getLayer() - o1
.getLayer();
547 if (layerDiff
!= 0) {
550 // prefer more specific region
551 int o1Length
= o1
.getEndOffset() - o1
.getStartOffset();
552 int o2Length
= o2
.getEndOffset() - o2
.getStartOffset();
553 return o1Length
- o2Length
;