IDEA-51739
[fedora-idea.git] / platform / platform-impl / src / com / intellij / openapi / editor / impl / IterationState.java
blobf5e8a8c8cef80a513d0bc24aef3d1a3e896ed0b7
1 /*
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;
36 import java.awt.*;
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();
86 myEditor = editor;
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()
105 : 0;
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);
159 reinit();
162 private int initHighlighterIterator(int start, List<RangeHighlighterImpl> sortedHighlighters, int longestHighlighterLength) {
163 int low = 0;
164 int high = sortedHighlighters.size();
165 int search = myDocument.getLineStartOffset(myDocument.getLineNumber(start)) -
166 longestHighlighterLength - 1;
168 if (search > 0) {
169 while (low < high) {
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) {
175 low = mid + 1;
177 else {
178 high = mid - 1;
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) {
187 return i;
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();
208 else {
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));
216 reinit();
219 private int getHighlighterEnd(int start) {
220 while (!myHighlighterIterator.atEnd()) {
221 int end = myHighlighterIterator.getEnd();
222 if (end > start) {
223 return end;
225 myHighlighterIterator.advance();
227 return myEnd;
230 private int getCaretEnd(int start) {
231 if (myCaretRowStart > start) {
232 return myCaretRowStart;
235 if (myCaretRowEnd > start) {
236 return myCaretRowEnd;
239 return myEnd;
242 private int getGuardedBlockEnd(int start) {
243 List<RangeMarker> blocks = myDocument.getGuardedBlocks();
244 int min = myEnd;
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());
254 return min;
257 private int getSelectionEnd(int start) {
258 if (!hasSelection) {
259 return myEnd;
261 if (mySelectionStart > start) {
262 return mySelectionStart;
264 if (mySelectionEnd > start) {
265 return mySelectionEnd;
267 return myEnd;
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;
294 break;
296 else {
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;
308 break;
310 else {
311 myCurrentHighlighters.add(highlighter);
316 if (myCurrentHighlighters.size() == 1) {
317 //Optimization
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) {
335 int end = myEnd;
336 FoldRegion[] topLevelCollapsed = myFoldingModel.fetchTopLevel();
337 if (topLevelCollapsed != null) {
338 for (int i = myFoldingModel.getLastCollapsedRegionBefore(startOffset) + 1;
339 i >= 0 && i < topLevelCollapsed.length;
340 i++) {
341 FoldRegion range = topLevelCollapsed[i];
342 if (!range.isValid()) continue;
344 int rangeEnd = range.getStartOffset();
345 if (rangeEnd > startOffset) {
346 if (rangeEnd < end) {
347 end = rangeEnd;
349 else {
350 break;
356 return end;
359 private int getSegmentHighlightersEnd() {
360 int end = myEnd;
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();
376 return end;
379 private void reinit() {
380 if (myHighlighterIterator.atEnd()) {
381 return;
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)
395 : null;
397 final int size = myCurrentHighlighters.size();
398 if (size > 1) {
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) {
405 syntax = null;
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);
415 selection = null;
418 if (syntax != null && highlighter.getLayer() < HighlighterLayer.SYNTAX) {
419 if (fold != null) {
420 myCachedAttributesList.add(fold);
421 fold = null;
424 myCachedAttributesList.add(syntax);
425 syntax = null;
428 if (guard != null && highlighter.getLayer() < HighlighterLayer.GUARDED_BLOCKS) {
429 myCachedAttributesList.add(guard);
430 guard = null;
433 if (caret != null && highlighter.getLayer() < HighlighterLayer.CARET_ROW) {
434 myCachedAttributesList.add(caret);
435 caret = null;
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);
450 Color fore = null;
451 Color back = isInGuardedBlock ? myReadOnlyColor : null;
452 Color effect = null;
453 EffectType effectType = null;
454 int fontType = 0;
456 for (int i = 0; i < myCachedAttributesList.size(); i++) {
457 TextAttributes attrs = myCachedAttributesList.get(i);
459 if (fore == null) {
460 fore = ifDiffers(attrs.getForegroundColor(), myDefaultForeground);
463 if (back == null) {
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);
488 @Nullable
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() {
503 return myEndOffset;
506 public TextAttributes getMergedAttributes() {
507 return myMergedAttributes;
510 public FoldRegion getCurrentFold() {
511 return myCurrentFold;
514 @Nullable
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) {
525 return caret;
528 if (highlighter.getTargetArea() != HighlighterTargetArea.LINES_IN_RANGE
529 || myDocument.getLineNumber(highlighter.getEndOffset()) < myDocument.getLineCount() - 1) {
530 continue;
533 TextAttributes textAttributes = highlighter.getTextAttributes();
534 if (textAttributes != null) {
535 Color backgroundColor = textAttributes.getBackgroundColor();
536 if (backgroundColor != null) return backgroundColor;
540 return caret;
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) {
548 return layerDiff;
550 // prefer more specific region
551 int o1Length = o1.getEndOffset() - o1.getStartOffset();
552 int o2Length = o2.getEndOffset() - o2.getStartOffset();
553 return o1Length - o2Length;