IDEA-125285, EA-645852: AppCode exception on start up +review
[idea/community.git] / platform / lang-impl / src / com / intellij / codeInsight / highlighting / BraceHighlightingHandler.java
1 /*
2  * Copyright 2000-2014 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
17 /*
18  * Created by IntelliJ IDEA.
19  * User: mike
20  * Date: Sep 27, 2002
21  * Time: 3:10:17 PM
22  * To change template for new class use 
23  * Code Style | Class Templates options (Tools | IDE Options).
24  */
25 package com.intellij.codeInsight.highlighting;
26
27 import com.intellij.codeInsight.CodeInsightSettings;
28 import com.intellij.codeInsight.hint.EditorFragmentComponent;
29 import com.intellij.injected.editor.EditorWindow;
30 import com.intellij.openapi.application.ApplicationManager;
31 import com.intellij.openapi.application.ModalityState;
32 import com.intellij.openapi.application.ex.ApplicationManagerEx;
33 import com.intellij.openapi.editor.Document;
34 import com.intellij.openapi.editor.Editor;
35 import com.intellij.openapi.editor.LogicalPosition;
36 import com.intellij.openapi.editor.colors.CodeInsightColors;
37 import com.intellij.openapi.editor.colors.EditorColorsScheme;
38 import com.intellij.openapi.editor.ex.DocumentEx;
39 import com.intellij.openapi.editor.ex.EditorEx;
40 import com.intellij.openapi.editor.ex.MarkupModelEx;
41 import com.intellij.openapi.editor.highlighter.EditorHighlighter;
42 import com.intellij.openapi.editor.highlighter.HighlighterIterator;
43 import com.intellij.openapi.editor.markup.*;
44 import com.intellij.openapi.fileEditor.FileEditorManager;
45 import com.intellij.openapi.fileTypes.FileType;
46 import com.intellij.openapi.fileTypes.FileTypes;
47 import com.intellij.openapi.project.DumbAwareRunnable;
48 import com.intellij.openapi.project.Project;
49 import com.intellij.openapi.util.Key;
50 import com.intellij.openapi.util.TextRange;
51 import com.intellij.psi.*;
52 import com.intellij.psi.impl.source.tree.injected.InjectedLanguageUtil;
53 import com.intellij.psi.tree.IElementType;
54 import com.intellij.psi.util.PsiUtilBase;
55 import com.intellij.psi.util.PsiUtilCore;
56 import com.intellij.ui.ColorUtil;
57 import com.intellij.util.Alarm;
58 import com.intellij.util.Processor;
59 import com.intellij.util.containers.WeakHashMap;
60 import com.intellij.util.text.CharArrayUtil;
61 import com.intellij.util.ui.UIUtil;
62 import org.jetbrains.annotations.NotNull;
63 import org.jetbrains.annotations.Nullable;
64
65 import java.awt.*;
66 import java.util.ArrayList;
67 import java.util.Collections;
68 import java.util.List;
69 import java.util.Set;
70
71 public class BraceHighlightingHandler {
72   private static final Key<List<RangeHighlighter>> BRACE_HIGHLIGHTERS_IN_EDITOR_VIEW_KEY = Key.create("BraceHighlighter.BRACE_HIGHLIGHTERS_IN_EDITOR_VIEW_KEY");
73   private static final Key<RangeHighlighter> LINE_MARKER_IN_EDITOR_KEY = Key.create("BraceHighlighter.LINE_MARKER_IN_EDITOR_KEY");
74
75   /**
76    * Holds weak references to the editors that are being processed at non-EDT.
77    * <p/>
78    * Is intended to be used to avoid submitting unnecessary new processing request from EDT, i.e. it's assumed that the collection
79    * is accessed from the single thread (EDT).
80    */
81   private static final Set<Editor> PROCESSED_EDITORS = Collections.newSetFromMap(new WeakHashMap<Editor, Boolean>());
82
83   @NotNull private final Project myProject;
84   @NotNull private final Editor myEditor;
85   private final Alarm myAlarm;
86
87   private final DocumentEx myDocument;
88   private final PsiFile myPsiFile;
89   private final CodeInsightSettings myCodeInsightSettings;
90
91   private BraceHighlightingHandler(@NotNull Project project, @NotNull Editor editor, @NotNull Alarm alarm, PsiFile psiFile) {
92     myProject = project;
93
94     myEditor = editor;
95     myAlarm = alarm;
96     myDocument = (DocumentEx)myEditor.getDocument();
97
98     myPsiFile = psiFile;
99     myCodeInsightSettings = CodeInsightSettings.getInstance();
100     // myFileType = myPsiFile == null ? null : myPsiFile.getFileType();
101   }
102
103   static void lookForInjectedAndMatchBracesInOtherThread(@NotNull final Editor editor,
104                                                          @NotNull final Alarm alarm,
105                                                          @NotNull final Processor<BraceHighlightingHandler> processor) {
106     ApplicationManagerEx.getApplicationEx().assertIsDispatchThread(editor.getComponent());
107     final Project project = editor.getProject();
108     if (project == null || project.isDisposed()) return;
109     if (!PROCESSED_EDITORS.add(editor)) {
110       // Skip processing if that is not really necessary.
111       // Assuming to be in EDT here.
112       return;
113     }
114     final int offset = editor.getCaretModel().getOffset();
115     final PsiFile psiFile = PsiUtilBase.getPsiFileInEditor(editor, project);
116     ApplicationManager.getApplication().executeOnPooledThread(new Runnable() {
117       @Override
118       public void run() {
119         if (!ApplicationManagerEx.getApplicationEx().tryRunReadAction(new Runnable() {
120           @Override
121           public void run() {
122             final PsiFile injected;
123             try {
124               injected = psiFile == null ||
125                          psiFile instanceof PsiCompiledElement ||
126                          psiFile instanceof PsiBinaryFile ||
127                          isReallyDisposed(editor, project)
128                          ? null : getInjectedFileIfAny(editor, project, offset, psiFile, alarm);
129             }
130             catch (RuntimeException e) {
131               // Reset processing flag in case of unexpected exception.
132               ApplicationManager.getApplication().invokeLater(new DumbAwareRunnable() {
133                 @Override
134                 public void run() {
135                   PROCESSED_EDITORS.remove(editor);
136                 }
137               });
138               throw e;
139             }
140             ApplicationManager.getApplication().invokeLater(new DumbAwareRunnable() {
141               @Override
142               public void run() {
143                 try {
144                   if (!isReallyDisposed(editor, project)) {
145                     Editor newEditor = InjectedLanguageUtil.getInjectedEditorForInjectedFile(editor, injected);
146                     BraceHighlightingHandler handler = new BraceHighlightingHandler(project, newEditor, alarm, injected);
147                     processor.process(handler);
148                   }
149                 }
150                 finally {
151                   PROCESSED_EDITORS.remove(editor);
152                 }
153               }
154             }, ModalityState.stateForComponent(editor.getComponent()));
155           }
156         })) {
157           // write action is queued in AWT. restart after it's finished
158           ApplicationManager.getApplication().invokeLater(new Runnable() {
159             @Override
160             public void run() {
161               PROCESSED_EDITORS.remove(editor);
162               lookForInjectedAndMatchBracesInOtherThread(editor, alarm, processor);
163             }
164           }, ModalityState.stateForComponent(editor.getComponent()));
165         }
166       }
167     });
168   }
169
170   private static boolean isReallyDisposed(@NotNull Editor editor, @NotNull Project project) {
171     Project editorProject = editor.getProject();
172     return editorProject == null ||
173            editorProject.isDisposed() || project.isDisposed() || !editor.getComponent().isShowing() || editor.isViewer();
174   }
175
176   @NotNull
177   private static PsiFile getInjectedFileIfAny(@NotNull final Editor editor,
178                                               @NotNull final Project project,
179                                               int offset,
180                                               @NotNull PsiFile psiFile,
181                                               @NotNull final Alarm alarm) {
182     Document document = editor.getDocument();
183     // when document is committed, try to highlight braces in injected lang - it's fast
184     if (PsiDocumentManager.getInstance(project).isCommitted(document)) {
185       final PsiElement injectedElement = InjectedLanguageUtil.findInjectedElementNoCommit(psiFile, offset);
186       if (injectedElement != null /*&& !(injectedElement instanceof PsiWhiteSpace)*/) {
187         final PsiFile injected = injectedElement.getContainingFile();
188         if (injected != null) {
189           return injected;
190         }
191       }
192     }
193     else {
194       PsiDocumentManager.getInstance(project).performForCommittedDocument(document, new Runnable() {
195         @Override
196         public void run() {
197           if (!project.isDisposed() && !editor.isDisposed()) {
198             BraceHighlighter.updateBraces(editor, alarm);
199           }
200         }
201       });
202     }
203     return psiFile;
204   }
205
206   void updateBraces() {
207     ApplicationManager.getApplication().assertIsDispatchThread();
208
209     if (myPsiFile == null || !myPsiFile.isValid()) return;
210
211     clearBraceHighlighters();
212
213     if (!myCodeInsightSettings.HIGHLIGHT_BRACES) return;
214
215     if (myEditor.getSelectionModel().hasSelection()) return;
216     
217     if (myEditor.getSoftWrapModel().isInsideOrBeforeSoftWrap(myEditor.getCaretModel().getVisualPosition())) return;
218
219     int offset = myEditor.getCaretModel().getOffset();
220     final CharSequence chars = myEditor.getDocument().getCharsSequence();
221
222     //if (myEditor.offsetToLogicalPosition(offset).column != myEditor.getCaretModel().getLogicalPosition().column) {
223     //  // we are in virtual space
224     //  final int caretLineNumber = myEditor.getCaretModel().getLogicalPosition().line;
225     //  if (caretLineNumber >= myDocument.getLineCount()) return;
226     //  offset = myDocument.getLineEndOffset(caretLineNumber) + myDocument.getLineSeparatorLength(caretLineNumber);
227     //}
228
229     final int originalOffset = offset;
230
231     HighlighterIterator iterator = getEditorHighlighter().createIterator(offset);
232     FileType fileType = PsiUtilBase.getPsiFileAtOffset(myPsiFile, offset).getFileType();
233
234     if (iterator.atEnd()) {
235       offset--;
236     }
237     else if (BraceMatchingUtil.isRBraceToken(iterator, chars, fileType)) {
238       offset--;
239     }
240     else if (!BraceMatchingUtil.isLBraceToken(iterator, chars, fileType)) {
241       offset--;
242
243       if (offset >= 0) {
244         final HighlighterIterator i = getEditorHighlighter().createIterator(offset);
245         if (!BraceMatchingUtil.isRBraceToken(i, chars, getFileTypeByIterator(i))) offset++;
246       }
247     }
248
249     if (offset < 0) {
250       removeLineMarkers();
251       return;
252     }
253
254     iterator = getEditorHighlighter().createIterator(offset);
255     fileType = getFileTypeByIterator(iterator);
256
257     myAlarm.cancelAllRequests();
258
259     if (BraceMatchingUtil.isLBraceToken(iterator, chars, fileType) ||
260         BraceMatchingUtil.isRBraceToken(iterator, chars, fileType)) {
261       doHighlight(offset, originalOffset, fileType);
262     }
263     else if (offset > 0 && offset < chars.length()) {
264       // There is a possible case that there are paired braces nearby the caret position and the document contains only white
265       // space symbols between them. We want to highlight such braces as well.
266       // Example: 
267       //     public void test() { <caret>
268       //     }
269       char c = chars.charAt(offset);
270       boolean searchForward = c != '\n';
271
272       // Try to find matched brace backwards.
273       if (offset >= originalOffset && (c == ' ' || c == '\t' || c == '\n')) {
274         int backwardNonWsOffset = CharArrayUtil.shiftBackward(chars, offset - 1, "\t ");
275         if (backwardNonWsOffset >= 0) {
276           iterator = getEditorHighlighter().createIterator(backwardNonWsOffset);
277           FileType newFileType = getFileTypeByIterator(iterator);
278           if (BraceMatchingUtil.isLBraceToken(iterator, chars, newFileType) ||
279               BraceMatchingUtil.isRBraceToken(iterator, chars, newFileType)) {
280             offset = backwardNonWsOffset;
281             searchForward = false;
282             doHighlight(backwardNonWsOffset, originalOffset, newFileType);
283           }
284         }
285       }
286
287       // Try to find matched brace forward.
288       if (searchForward) {
289         int forwardOffset = CharArrayUtil.shiftForward(chars, offset, "\t ");
290         if (forwardOffset > offset || c == ' ' || c == '\t') {
291           iterator = getEditorHighlighter().createIterator(forwardOffset);
292           FileType newFileType = getFileTypeByIterator(iterator);
293           if (BraceMatchingUtil.isLBraceToken(iterator, chars, newFileType) ||
294               BraceMatchingUtil.isRBraceToken(iterator, chars, newFileType)) {
295             offset = forwardOffset;
296             doHighlight(forwardOffset, originalOffset, newFileType);
297           }
298         }
299       }
300     }
301
302     //highlight scope
303     if (!myCodeInsightSettings.HIGHLIGHT_SCOPE) {
304       removeLineMarkers();
305       return;
306     }
307
308     final int _offset = offset;
309     final FileType _fileType = fileType;
310     myAlarm.addRequest(new Runnable() {
311       @Override
312       public void run() {
313         if (!myProject.isDisposed() && !myEditor.isDisposed()) {
314           highlightScope(_offset, _fileType);
315         }
316       }
317     }, 300);
318   }
319
320   @NotNull
321   private FileType getFileTypeByIterator(@NotNull HighlighterIterator iterator) {
322     return PsiUtilBase.getPsiFileAtOffset(myPsiFile, iterator.getStart()).getFileType();
323   }
324
325   @NotNull
326   private FileType getFileTypeByOffset(int offset) {
327     return PsiUtilBase.getPsiFileAtOffset(myPsiFile, offset).getFileType();
328   }
329
330   @NotNull
331   private EditorHighlighter getEditorHighlighter() {
332     return ((EditorEx)myEditor).getHighlighter();
333   }
334
335   private void highlightScope(int offset, @NotNull FileType fileType) {
336     if (myEditor.getFoldingModel().isOffsetCollapsed(offset)) return;
337     if (myEditor.getDocument().getTextLength() <= offset) return;
338
339     HighlighterIterator iterator = getEditorHighlighter().createIterator(offset);
340     final CharSequence chars = myDocument.getCharsSequence();
341
342     if (!BraceMatchingUtil.isStructuralBraceToken(fileType, iterator, chars)) {
343 //      if (BraceMatchingUtil.isRBraceTokenToHighlight(myFileType, iterator) || BraceMatchingUtil.isLBraceTokenToHighlight(myFileType, iterator)) return;
344     }
345     else {
346       if (BraceMatchingUtil.isRBraceToken(iterator, chars, fileType) ||
347           BraceMatchingUtil.isLBraceToken(iterator, chars, fileType)) return;
348     }
349
350     if (!BraceMatchingUtil.findStructuralLeftBrace(fileType, iterator, chars)) {
351       removeLineMarkers();
352       return;
353     }
354
355     highlightLeftBrace(iterator, true, fileType);
356   }
357
358   private void doHighlight(int offset, int originalOffset, @NotNull FileType fileType) {
359     if (myEditor.getFoldingModel().isOffsetCollapsed(offset)) return;
360
361     HighlighterIterator iterator = getEditorHighlighter().createIterator(offset);
362     final CharSequence chars = myDocument.getCharsSequence();
363
364     if (BraceMatchingUtil.isLBraceToken(iterator, chars, fileType)) {
365       IElementType tokenType = iterator.getTokenType();
366
367       iterator.advance();
368       if (!iterator.atEnd() && BraceMatchingUtil.isRBraceToken(iterator, chars, fileType)) {
369         if (BraceMatchingUtil.isPairBraces(tokenType, iterator.getTokenType(), fileType) &&
370             originalOffset == iterator.getStart()) return;
371       }
372
373       iterator.retreat();
374       highlightLeftBrace(iterator, false, fileType);
375
376       if (offset > 0) {
377         iterator = getEditorHighlighter().createIterator(offset - 1);
378         if (BraceMatchingUtil.isRBraceToken(iterator, chars, fileType)) {
379           highlightRightBrace(iterator, fileType);
380         }
381       }
382     }
383     else if (BraceMatchingUtil.isRBraceToken(iterator, chars, fileType)) {
384       highlightRightBrace(iterator, fileType);
385     }
386   }
387
388   private void highlightRightBrace(@NotNull HighlighterIterator iterator, @NotNull FileType fileType) {
389     TextRange brace1 = TextRange.create(iterator.getStart(), iterator.getEnd());
390
391     boolean matched = BraceMatchingUtil.matchBrace(myDocument.getCharsSequence(), fileType, iterator, false);
392
393     TextRange brace2 = iterator.atEnd() ? null : TextRange.create(iterator.getStart(), iterator.getEnd());
394
395     highlightBraces(brace2, brace1, matched, false, fileType);
396   }
397
398   private void highlightLeftBrace(@NotNull HighlighterIterator iterator, boolean scopeHighlighting, @NotNull FileType fileType) {
399     TextRange brace1Start = TextRange.create(iterator.getStart(), iterator.getEnd());
400     boolean matched = BraceMatchingUtil.matchBrace(myDocument.getCharsSequence(), fileType, iterator, true);
401
402     TextRange brace2End = iterator.atEnd() ? null : TextRange.create(iterator.getStart(), iterator.getEnd());
403
404     highlightBraces(brace1Start, brace2End, matched, scopeHighlighting, fileType);
405   }
406
407   private void highlightBraces(@Nullable TextRange lBrace, @Nullable TextRange rBrace, boolean matched, boolean scopeHighlighting, @NotNull FileType fileType) {
408     if (!matched && fileType == FileTypes.PLAIN_TEXT) {
409       return;
410     }
411
412     EditorColorsScheme scheme = myEditor.getColorsScheme();
413     final TextAttributes attributes =
414       matched ? scheme.getAttributes(CodeInsightColors.MATCHED_BRACE_ATTRIBUTES)
415               : scheme.getAttributes(CodeInsightColors.UNMATCHED_BRACE_ATTRIBUTES);
416
417     if (rBrace != null && !scopeHighlighting) {
418       highlightBrace(rBrace, matched);
419     }
420
421     if (lBrace != null && !scopeHighlighting) {
422       highlightBrace(lBrace, matched);
423     }
424
425     FileEditorManager fileEditorManager = FileEditorManager.getInstance(myProject); // null in default project
426     if (fileEditorManager == null || !myEditor.equals(fileEditorManager.getSelectedTextEditor())) {
427       return;
428     }
429
430     if (lBrace != null && rBrace !=null) {
431       final int startLine = myEditor.offsetToLogicalPosition(lBrace.getStartOffset()).line;
432       final int endLine = myEditor.offsetToLogicalPosition(rBrace.getEndOffset()).line;
433       if (endLine - startLine > 0) {
434         final Runnable runnable = new Runnable() {
435           @Override
436           public void run() {
437             if (myProject.isDisposed() || myEditor.isDisposed()) return;
438             Color color = attributes.getBackgroundColor();
439             if (color == null) return;
440             color = UIUtil.isUnderDarcula() ? ColorUtil.shift(color, 1.1d) : color.darker();
441             lineMarkFragment(startLine, endLine, color);
442           }
443         };
444
445         if (!scopeHighlighting) {
446           myAlarm.addRequest(runnable, 300);
447         }
448         else {
449           runnable.run();
450         }
451       }
452       else {
453         if (!myCodeInsightSettings.HIGHLIGHT_SCOPE) {
454           removeLineMarkers();
455         }
456       }
457
458       if (!scopeHighlighting) {
459         showScopeHint(lBrace.getStartOffset(), lBrace.getEndOffset());
460       }
461     }
462     else {
463       if (!myCodeInsightSettings.HIGHLIGHT_SCOPE) {
464         removeLineMarkers();
465       }
466     }
467   }
468
469   private void highlightBrace(@NotNull TextRange braceRange, boolean matched) {
470     EditorColorsScheme scheme = myEditor.getColorsScheme();
471     final TextAttributes attributes =
472         matched ? scheme.getAttributes(CodeInsightColors.MATCHED_BRACE_ATTRIBUTES)
473         : scheme.getAttributes(CodeInsightColors.UNMATCHED_BRACE_ATTRIBUTES);
474
475
476     RangeHighlighter rbraceHighlighter =
477         myEditor.getMarkupModel().addRangeHighlighter(
478           braceRange.getStartOffset(), braceRange.getEndOffset(), HighlighterLayer.LAST + 1, attributes, HighlighterTargetArea.EXACT_RANGE);
479     rbraceHighlighter.setGreedyToLeft(false);
480     rbraceHighlighter.setGreedyToRight(false);
481     registerHighlighter(rbraceHighlighter);
482   }
483
484   private void registerHighlighter(@NotNull RangeHighlighter highlighter) {
485     getHighlightersList().add(highlighter);
486   }
487
488   @NotNull
489   private List<RangeHighlighter> getHighlightersList() {
490     // braces are highlighted across the whole editor, not in each injected editor separately
491     Editor editor = myEditor instanceof EditorWindow ? ((EditorWindow)myEditor).getDelegate() : myEditor;
492     List<RangeHighlighter> highlighters = editor.getUserData(BRACE_HIGHLIGHTERS_IN_EDITOR_VIEW_KEY);
493     if (highlighters == null) {
494       highlighters = new ArrayList<RangeHighlighter>();
495       editor.putUserData(BRACE_HIGHLIGHTERS_IN_EDITOR_VIEW_KEY, highlighters);
496     }
497     return highlighters;
498   }
499
500   private void showScopeHint(final int lbraceStart, final int lbraceEnd) {
501     LogicalPosition bracePosition = myEditor.offsetToLogicalPosition(lbraceStart);
502     Point braceLocation = myEditor.logicalPositionToXY(bracePosition);
503     final int y = braceLocation.y;
504     myAlarm.addRequest(
505         new Runnable() {
506           @Override
507           public void run() {
508             if (!myEditor.getComponent().isShowing()) return;
509             Rectangle viewRect = myEditor.getScrollingModel().getVisibleArea();
510             if (y < viewRect.y) {
511               int start = lbraceStart;
512               if (!(myPsiFile instanceof PsiPlainTextFile) && myPsiFile.isValid()) {
513                 PsiDocumentManager.getInstance(myProject).commitAllDocuments();
514                 start = BraceMatchingUtil.getBraceMatcher(getFileTypeByOffset(lbraceStart), PsiUtilCore
515                   .getLanguageAtOffset(myPsiFile, lbraceStart)).getCodeConstructStart(myPsiFile, lbraceStart);
516               }
517               TextRange range = new TextRange(start, lbraceEnd);
518               int line1 = myDocument.getLineNumber(range.getStartOffset());
519               int line2 = myDocument.getLineNumber(range.getEndOffset());
520               line1 = Math.max(line1, line2 - 5);
521               range = new TextRange(myDocument.getLineStartOffset(line1), range.getEndOffset());
522               EditorFragmentComponent.showEditorFragmentHint(myEditor, range, true, true);
523             }
524           }
525         },
526         300, ModalityState.stateForComponent(myEditor.getComponent()));
527   }
528
529   void clearBraceHighlighters() {
530     List<RangeHighlighter> highlighters = getHighlightersList();
531     for (final RangeHighlighter highlighter : highlighters) {
532       highlighter.dispose();
533     }
534     highlighters.clear();
535   }
536
537   private void lineMarkFragment(int startLine, int endLine, @NotNull Color color) {
538     removeLineMarkers();
539
540     if (startLine >= endLine || endLine >= myDocument.getLineCount()) return;
541
542     int startOffset = myDocument.getLineStartOffset(startLine);
543     int endOffset = myDocument.getLineStartOffset(endLine);
544
545     RangeHighlighter highlighter = myEditor.getMarkupModel().addRangeHighlighter(startOffset, endOffset, 0, null, HighlighterTargetArea.LINES_IN_RANGE);
546     highlighter.setLineMarkerRenderer(new MyLineMarkerRenderer(color));
547     myEditor.putUserData(LINE_MARKER_IN_EDITOR_KEY, highlighter);
548   }
549
550   private void removeLineMarkers() {
551     ApplicationManager.getApplication().assertIsDispatchThread();
552     RangeHighlighter marker = myEditor.getUserData(LINE_MARKER_IN_EDITOR_KEY);
553     if (marker != null && ((MarkupModelEx)myEditor.getMarkupModel()).containsHighlighter(marker)) {
554       marker.dispose();
555     }
556     myEditor.putUserData(LINE_MARKER_IN_EDITOR_KEY, null);
557   }
558
559   private static class MyLineMarkerRenderer implements LineMarkerRenderer {
560     private static final int DEEPNESS = 2;
561     private static final int THICKNESS = 2;
562     private final Color myColor;
563
564     private MyLineMarkerRenderer(@NotNull Color color) {
565       myColor = color;
566     }
567
568     @Override
569     public void paint(Editor editor, Graphics g, Rectangle r) {
570       int height = r.height + editor.getLineHeight();
571       g.setColor(myColor);
572       g.fillRect(r.x, r.y, THICKNESS, height);
573       g.fillRect(r.x + THICKNESS, r.y, DEEPNESS, THICKNESS);
574       g.fillRect(r.x + THICKNESS, r.y + height - THICKNESS, DEEPNESS, THICKNESS);
575     }
576   }
577 }