| /* |
| * Copyright 2000-2014 JetBrains s.r.o. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| /* |
| * Created by IntelliJ IDEA. |
| * User: mike |
| * Date: Sep 27, 2002 |
| * Time: 3:10:17 PM |
| * To change template for new class use |
| * Code Style | Class Templates options (Tools | IDE Options). |
| */ |
| package com.intellij.codeInsight.highlighting; |
| |
| import com.intellij.codeInsight.CodeInsightSettings; |
| import com.intellij.codeInsight.hint.EditorFragmentComponent; |
| import com.intellij.injected.editor.EditorWindow; |
| import com.intellij.openapi.application.ApplicationManager; |
| import com.intellij.openapi.application.ModalityState; |
| import com.intellij.openapi.application.ex.ApplicationManagerEx; |
| import com.intellij.openapi.editor.Document; |
| import com.intellij.openapi.editor.Editor; |
| import com.intellij.openapi.editor.LogicalPosition; |
| import com.intellij.openapi.editor.colors.CodeInsightColors; |
| import com.intellij.openapi.editor.colors.EditorColorsScheme; |
| import com.intellij.openapi.editor.ex.DocumentEx; |
| import com.intellij.openapi.editor.ex.EditorEx; |
| import com.intellij.openapi.editor.ex.MarkupModelEx; |
| import com.intellij.openapi.editor.highlighter.EditorHighlighter; |
| import com.intellij.openapi.editor.highlighter.HighlighterIterator; |
| import com.intellij.openapi.editor.markup.*; |
| import com.intellij.openapi.fileEditor.FileEditorManager; |
| import com.intellij.openapi.fileTypes.FileType; |
| import com.intellij.openapi.fileTypes.FileTypes; |
| import com.intellij.openapi.project.DumbAwareRunnable; |
| import com.intellij.openapi.project.Project; |
| import com.intellij.openapi.util.Key; |
| import com.intellij.openapi.util.TextRange; |
| import com.intellij.psi.*; |
| import com.intellij.psi.impl.source.tree.injected.InjectedLanguageUtil; |
| import com.intellij.psi.tree.IElementType; |
| import com.intellij.psi.util.PsiUtilBase; |
| import com.intellij.psi.util.PsiUtilCore; |
| import com.intellij.ui.ColorUtil; |
| import com.intellij.util.Alarm; |
| import com.intellij.util.Processor; |
| import com.intellij.util.containers.WeakHashMap; |
| import com.intellij.util.text.CharArrayUtil; |
| import com.intellij.util.ui.UIUtil; |
| import org.jetbrains.annotations.NotNull; |
| import org.jetbrains.annotations.Nullable; |
| |
| import java.awt.*; |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.List; |
| import java.util.Set; |
| |
| public class BraceHighlightingHandler { |
| private static final Key<List<RangeHighlighter>> BRACE_HIGHLIGHTERS_IN_EDITOR_VIEW_KEY = Key.create("BraceHighlighter.BRACE_HIGHLIGHTERS_IN_EDITOR_VIEW_KEY"); |
| private static final Key<RangeHighlighter> LINE_MARKER_IN_EDITOR_KEY = Key.create("BraceHighlighter.LINE_MARKER_IN_EDITOR_KEY"); |
| |
| /** |
| * Holds weak references to the editors that are being processed at non-EDT. |
| * <p/> |
| * Is intended to be used to avoid submitting unnecessary new processing request from EDT, i.e. it's assumed that the collection |
| * is accessed from the single thread (EDT). |
| */ |
| private static final Set<Editor> PROCESSED_EDITORS = Collections.newSetFromMap(new WeakHashMap<Editor, Boolean>()); |
| |
| @NotNull private final Project myProject; |
| @NotNull private final Editor myEditor; |
| private final Alarm myAlarm; |
| |
| private final DocumentEx myDocument; |
| private final PsiFile myPsiFile; |
| private final CodeInsightSettings myCodeInsightSettings; |
| |
| private BraceHighlightingHandler(@NotNull Project project, @NotNull Editor editor, @NotNull Alarm alarm, PsiFile psiFile) { |
| myProject = project; |
| |
| myEditor = editor; |
| myAlarm = alarm; |
| myDocument = (DocumentEx)myEditor.getDocument(); |
| |
| myPsiFile = psiFile; |
| myCodeInsightSettings = CodeInsightSettings.getInstance(); |
| } |
| |
| static void lookForInjectedAndMatchBracesInOtherThread(@NotNull final Editor editor, |
| @NotNull final Alarm alarm, |
| @NotNull final Processor<BraceHighlightingHandler> processor) { |
| ApplicationManagerEx.getApplicationEx().assertIsDispatchThread(); |
| if (!isValidEditor(editor)) return; |
| if (!PROCESSED_EDITORS.add(editor)) { |
| // Skip processing if that is not really necessary. |
| // Assuming to be in EDT here. |
| return; |
| } |
| final int offset = editor.getCaretModel().getOffset(); |
| final Project project = editor.getProject(); |
| final PsiFile psiFile = PsiUtilBase.getPsiFileInEditor(editor, project); |
| if (!isValidFile(psiFile)) return; |
| ApplicationManager.getApplication().executeOnPooledThread(new Runnable() { |
| @Override |
| public void run() { |
| if (!ApplicationManagerEx.getApplicationEx().tryRunReadAction(new Runnable() { |
| @Override |
| public void run() { |
| final PsiFile injected; |
| try { |
| injected = psiFile == null || |
| psiFile instanceof PsiCompiledElement || |
| psiFile instanceof PsiBinaryFile || |
| !isValidEditor(editor) || |
| !isValidFile(psiFile) |
| ? null : getInjectedFileIfAny(editor, project, offset, psiFile, alarm); |
| } |
| catch (RuntimeException e) { |
| // Reset processing flag in case of unexpected exception. |
| ApplicationManager.getApplication().invokeLater(new DumbAwareRunnable() { |
| @Override |
| public void run() { |
| PROCESSED_EDITORS.remove(editor); |
| } |
| }); |
| throw e; |
| } |
| ApplicationManager.getApplication().invokeLater(new DumbAwareRunnable() { |
| @Override |
| public void run() { |
| try { |
| if (isValidEditor(editor) && isValidFile(injected)) { |
| Editor newEditor = InjectedLanguageUtil.getInjectedEditorForInjectedFile(editor, injected); |
| BraceHighlightingHandler handler = new BraceHighlightingHandler(project, newEditor, alarm, injected); |
| processor.process(handler); |
| } |
| } |
| finally { |
| PROCESSED_EDITORS.remove(editor); |
| } |
| } |
| }, ModalityState.stateForComponent(editor.getComponent())); |
| } |
| })) { |
| // write action is queued in AWT. restart after it's finished |
| ApplicationManager.getApplication().invokeLater(new Runnable() { |
| @Override |
| public void run() { |
| PROCESSED_EDITORS.remove(editor); |
| lookForInjectedAndMatchBracesInOtherThread(editor, alarm, processor); |
| } |
| }, ModalityState.stateForComponent(editor.getComponent())); |
| } |
| } |
| }); |
| } |
| |
| private static boolean isValidFile(PsiFile file) { |
| return file != null && file.isValid() && !file.getProject().isDisposed(); |
| } |
| |
| private static boolean isValidEditor(@NotNull Editor editor) { |
| Project editorProject = editor.getProject(); |
| return editorProject != null && !editorProject.isDisposed() && !editor.isDisposed() && editor.getComponent().isShowing() && !editor.isViewer(); |
| } |
| |
| @NotNull |
| private static PsiFile getInjectedFileIfAny(@NotNull final Editor editor, |
| @NotNull final Project project, |
| int offset, |
| @NotNull PsiFile psiFile, |
| @NotNull final Alarm alarm) { |
| Document document = editor.getDocument(); |
| // when document is committed, try to highlight braces in injected lang - it's fast |
| if (PsiDocumentManager.getInstance(project).isCommitted(document)) { |
| final PsiElement injectedElement = InjectedLanguageUtil.findInjectedElementNoCommit(psiFile, offset); |
| if (injectedElement != null /*&& !(injectedElement instanceof PsiWhiteSpace)*/) { |
| final PsiFile injected = injectedElement.getContainingFile(); |
| if (injected != null) { |
| return injected; |
| } |
| } |
| } |
| else { |
| PsiDocumentManager.getInstance(project).performForCommittedDocument(document, new Runnable() { |
| @Override |
| public void run() { |
| if (!project.isDisposed() && !editor.isDisposed()) { |
| BraceHighlighter.updateBraces(editor, alarm); |
| } |
| } |
| }); |
| } |
| return psiFile; |
| } |
| |
| void updateBraces() { |
| ApplicationManager.getApplication().assertIsDispatchThread(); |
| |
| if (myPsiFile == null || !myPsiFile.isValid()) return; |
| |
| clearBraceHighlighters(); |
| |
| if (!myCodeInsightSettings.HIGHLIGHT_BRACES) return; |
| |
| if (myEditor.getSelectionModel().hasSelection()) return; |
| |
| if (myEditor.getSoftWrapModel().isInsideOrBeforeSoftWrap(myEditor.getCaretModel().getVisualPosition())) return; |
| |
| int offset = myEditor.getCaretModel().getOffset(); |
| final CharSequence chars = myEditor.getDocument().getCharsSequence(); |
| |
| //if (myEditor.offsetToLogicalPosition(offset).column != myEditor.getCaretModel().getLogicalPosition().column) { |
| // // we are in virtual space |
| // final int caretLineNumber = myEditor.getCaretModel().getLogicalPosition().line; |
| // if (caretLineNumber >= myDocument.getLineCount()) return; |
| // offset = myDocument.getLineEndOffset(caretLineNumber) + myDocument.getLineSeparatorLength(caretLineNumber); |
| //} |
| |
| final int originalOffset = offset; |
| |
| HighlighterIterator iterator = getEditorHighlighter().createIterator(offset); |
| FileType fileType = PsiUtilBase.getPsiFileAtOffset(myPsiFile, offset).getFileType(); |
| |
| if (iterator.atEnd()) { |
| offset--; |
| } |
| else if (BraceMatchingUtil.isRBraceToken(iterator, chars, fileType)) { |
| offset--; |
| } |
| else if (!BraceMatchingUtil.isLBraceToken(iterator, chars, fileType)) { |
| offset--; |
| |
| if (offset >= 0) { |
| final HighlighterIterator i = getEditorHighlighter().createIterator(offset); |
| if (!BraceMatchingUtil.isRBraceToken(i, chars, getFileTypeByIterator(i))) offset++; |
| } |
| } |
| |
| if (offset < 0) { |
| removeLineMarkers(); |
| return; |
| } |
| |
| iterator = getEditorHighlighter().createIterator(offset); |
| fileType = getFileTypeByIterator(iterator); |
| |
| myAlarm.cancelAllRequests(); |
| |
| if (BraceMatchingUtil.isLBraceToken(iterator, chars, fileType) || |
| BraceMatchingUtil.isRBraceToken(iterator, chars, fileType)) { |
| doHighlight(offset, originalOffset, fileType); |
| } |
| else if (offset > 0 && offset < chars.length()) { |
| // There is a possible case that there are paired braces nearby the caret position and the document contains only white |
| // space symbols between them. We want to highlight such braces as well. |
| // Example: |
| // public void test() { <caret> |
| // } |
| char c = chars.charAt(offset); |
| boolean searchForward = c != '\n'; |
| |
| // Try to find matched brace backwards. |
| if (offset >= originalOffset && (c == ' ' || c == '\t' || c == '\n')) { |
| int backwardNonWsOffset = CharArrayUtil.shiftBackward(chars, offset - 1, "\t "); |
| if (backwardNonWsOffset >= 0) { |
| iterator = getEditorHighlighter().createIterator(backwardNonWsOffset); |
| FileType newFileType = getFileTypeByIterator(iterator); |
| if (BraceMatchingUtil.isLBraceToken(iterator, chars, newFileType) || |
| BraceMatchingUtil.isRBraceToken(iterator, chars, newFileType)) { |
| offset = backwardNonWsOffset; |
| searchForward = false; |
| doHighlight(backwardNonWsOffset, originalOffset, newFileType); |
| } |
| } |
| } |
| |
| // Try to find matched brace forward. |
| if (searchForward) { |
| int forwardOffset = CharArrayUtil.shiftForward(chars, offset, "\t "); |
| if (forwardOffset > offset || c == ' ' || c == '\t') { |
| iterator = getEditorHighlighter().createIterator(forwardOffset); |
| FileType newFileType = getFileTypeByIterator(iterator); |
| if (BraceMatchingUtil.isLBraceToken(iterator, chars, newFileType) || |
| BraceMatchingUtil.isRBraceToken(iterator, chars, newFileType)) { |
| offset = forwardOffset; |
| doHighlight(forwardOffset, originalOffset, newFileType); |
| } |
| } |
| } |
| } |
| |
| //highlight scope |
| if (!myCodeInsightSettings.HIGHLIGHT_SCOPE) { |
| removeLineMarkers(); |
| return; |
| } |
| |
| final int _offset = offset; |
| final FileType _fileType = fileType; |
| myAlarm.addRequest(new Runnable() { |
| @Override |
| public void run() { |
| if (!myProject.isDisposed() && !myEditor.isDisposed()) { |
| highlightScope(_offset, _fileType); |
| } |
| } |
| }, 300); |
| } |
| |
| @NotNull |
| private FileType getFileTypeByIterator(@NotNull HighlighterIterator iterator) { |
| return PsiUtilBase.getPsiFileAtOffset(myPsiFile, iterator.getStart()).getFileType(); |
| } |
| |
| @NotNull |
| private FileType getFileTypeByOffset(int offset) { |
| return PsiUtilBase.getPsiFileAtOffset(myPsiFile, offset).getFileType(); |
| } |
| |
| @NotNull |
| private EditorHighlighter getEditorHighlighter() { |
| return ((EditorEx)myEditor).getHighlighter(); |
| } |
| |
| private void highlightScope(int offset, @NotNull FileType fileType) { |
| if (myEditor.getFoldingModel().isOffsetCollapsed(offset)) return; |
| if (myEditor.getDocument().getTextLength() <= offset) return; |
| |
| HighlighterIterator iterator = getEditorHighlighter().createIterator(offset); |
| final CharSequence chars = myDocument.getCharsSequence(); |
| |
| if (!BraceMatchingUtil.isStructuralBraceToken(fileType, iterator, chars)) { |
| // if (BraceMatchingUtil.isRBraceTokenToHighlight(myFileType, iterator) || BraceMatchingUtil.isLBraceTokenToHighlight(myFileType, iterator)) return; |
| } |
| else { |
| if (BraceMatchingUtil.isRBraceToken(iterator, chars, fileType) || |
| BraceMatchingUtil.isLBraceToken(iterator, chars, fileType)) return; |
| } |
| |
| if (!BraceMatchingUtil.findStructuralLeftBrace(fileType, iterator, chars)) { |
| removeLineMarkers(); |
| return; |
| } |
| |
| highlightLeftBrace(iterator, true, fileType); |
| } |
| |
| private void doHighlight(int offset, int originalOffset, @NotNull FileType fileType) { |
| if (myEditor.getFoldingModel().isOffsetCollapsed(offset)) return; |
| |
| HighlighterIterator iterator = getEditorHighlighter().createIterator(offset); |
| final CharSequence chars = myDocument.getCharsSequence(); |
| |
| if (BraceMatchingUtil.isLBraceToken(iterator, chars, fileType)) { |
| IElementType tokenType = iterator.getTokenType(); |
| |
| iterator.advance(); |
| if (!iterator.atEnd() && BraceMatchingUtil.isRBraceToken(iterator, chars, fileType)) { |
| if (BraceMatchingUtil.isPairBraces(tokenType, iterator.getTokenType(), fileType) && |
| originalOffset == iterator.getStart()) return; |
| } |
| |
| iterator.retreat(); |
| highlightLeftBrace(iterator, false, fileType); |
| |
| if (offset > 0) { |
| iterator = getEditorHighlighter().createIterator(offset - 1); |
| if (BraceMatchingUtil.isRBraceToken(iterator, chars, fileType)) { |
| highlightRightBrace(iterator, fileType); |
| } |
| } |
| } |
| else if (BraceMatchingUtil.isRBraceToken(iterator, chars, fileType)) { |
| highlightRightBrace(iterator, fileType); |
| } |
| } |
| |
| private void highlightRightBrace(@NotNull HighlighterIterator iterator, @NotNull FileType fileType) { |
| TextRange brace1 = TextRange.create(iterator.getStart(), iterator.getEnd()); |
| |
| boolean matched = BraceMatchingUtil.matchBrace(myDocument.getCharsSequence(), fileType, iterator, false); |
| |
| TextRange brace2 = iterator.atEnd() ? null : TextRange.create(iterator.getStart(), iterator.getEnd()); |
| |
| highlightBraces(brace2, brace1, matched, false, fileType); |
| } |
| |
| private void highlightLeftBrace(@NotNull HighlighterIterator iterator, boolean scopeHighlighting, @NotNull FileType fileType) { |
| TextRange brace1Start = TextRange.create(iterator.getStart(), iterator.getEnd()); |
| boolean matched = BraceMatchingUtil.matchBrace(myDocument.getCharsSequence(), fileType, iterator, true); |
| |
| TextRange brace2End = iterator.atEnd() ? null : TextRange.create(iterator.getStart(), iterator.getEnd()); |
| |
| highlightBraces(brace1Start, brace2End, matched, scopeHighlighting, fileType); |
| } |
| |
| private void highlightBraces(@Nullable TextRange lBrace, @Nullable TextRange rBrace, boolean matched, boolean scopeHighlighting, @NotNull FileType fileType) { |
| if (!matched && fileType == FileTypes.PLAIN_TEXT) { |
| return; |
| } |
| |
| EditorColorsScheme scheme = myEditor.getColorsScheme(); |
| final TextAttributes attributes = |
| matched ? scheme.getAttributes(CodeInsightColors.MATCHED_BRACE_ATTRIBUTES) |
| : scheme.getAttributes(CodeInsightColors.UNMATCHED_BRACE_ATTRIBUTES); |
| |
| if (rBrace != null && !scopeHighlighting) { |
| highlightBrace(rBrace, matched); |
| } |
| |
| if (lBrace != null && !scopeHighlighting) { |
| highlightBrace(lBrace, matched); |
| } |
| |
| FileEditorManager fileEditorManager = FileEditorManager.getInstance(myProject); // null in default project |
| if (fileEditorManager == null || !myEditor.equals(fileEditorManager.getSelectedTextEditor())) { |
| return; |
| } |
| |
| if (lBrace != null && rBrace !=null) { |
| final int startLine = myEditor.offsetToLogicalPosition(lBrace.getStartOffset()).line; |
| final int endLine = myEditor.offsetToLogicalPosition(rBrace.getEndOffset()).line; |
| if (endLine - startLine > 0) { |
| final Runnable runnable = new Runnable() { |
| @Override |
| public void run() { |
| if (myProject.isDisposed() || myEditor.isDisposed()) return; |
| Color color = attributes.getBackgroundColor(); |
| if (color == null) return; |
| color = UIUtil.isUnderDarcula() ? ColorUtil.shift(color, 1.1d) : color.darker(); |
| lineMarkFragment(startLine, endLine, color); |
| } |
| }; |
| |
| if (!scopeHighlighting) { |
| myAlarm.addRequest(runnable, 300); |
| } |
| else { |
| runnable.run(); |
| } |
| } |
| else { |
| if (!myCodeInsightSettings.HIGHLIGHT_SCOPE) { |
| removeLineMarkers(); |
| } |
| } |
| |
| if (!scopeHighlighting) { |
| showScopeHint(lBrace.getStartOffset(), lBrace.getEndOffset()); |
| } |
| } |
| else { |
| if (!myCodeInsightSettings.HIGHLIGHT_SCOPE) { |
| removeLineMarkers(); |
| } |
| } |
| } |
| |
| private void highlightBrace(@NotNull TextRange braceRange, boolean matched) { |
| EditorColorsScheme scheme = myEditor.getColorsScheme(); |
| final TextAttributes attributes = |
| matched ? scheme.getAttributes(CodeInsightColors.MATCHED_BRACE_ATTRIBUTES) |
| : scheme.getAttributes(CodeInsightColors.UNMATCHED_BRACE_ATTRIBUTES); |
| |
| |
| RangeHighlighter rbraceHighlighter = |
| myEditor.getMarkupModel().addRangeHighlighter( |
| braceRange.getStartOffset(), braceRange.getEndOffset(), HighlighterLayer.LAST + 1, attributes, HighlighterTargetArea.EXACT_RANGE); |
| rbraceHighlighter.setGreedyToLeft(false); |
| rbraceHighlighter.setGreedyToRight(false); |
| registerHighlighter(rbraceHighlighter); |
| } |
| |
| private void registerHighlighter(@NotNull RangeHighlighter highlighter) { |
| getHighlightersList().add(highlighter); |
| } |
| |
| @NotNull |
| private List<RangeHighlighter> getHighlightersList() { |
| // braces are highlighted across the whole editor, not in each injected editor separately |
| Editor editor = myEditor instanceof EditorWindow ? ((EditorWindow)myEditor).getDelegate() : myEditor; |
| List<RangeHighlighter> highlighters = editor.getUserData(BRACE_HIGHLIGHTERS_IN_EDITOR_VIEW_KEY); |
| if (highlighters == null) { |
| highlighters = new ArrayList<RangeHighlighter>(); |
| editor.putUserData(BRACE_HIGHLIGHTERS_IN_EDITOR_VIEW_KEY, highlighters); |
| } |
| return highlighters; |
| } |
| |
| private void showScopeHint(final int lbraceStart, final int lbraceEnd) { |
| LogicalPosition bracePosition = myEditor.offsetToLogicalPosition(lbraceStart); |
| Point braceLocation = myEditor.logicalPositionToXY(bracePosition); |
| final int y = braceLocation.y; |
| myAlarm.addRequest( |
| new Runnable() { |
| @Override |
| public void run() { |
| if (!myEditor.getComponent().isShowing()) return; |
| Rectangle viewRect = myEditor.getScrollingModel().getVisibleArea(); |
| if (y < viewRect.y) { |
| int start = lbraceStart; |
| if (!(myPsiFile instanceof PsiPlainTextFile) && myPsiFile.isValid()) { |
| PsiDocumentManager.getInstance(myProject).commitAllDocuments(); |
| start = BraceMatchingUtil.getBraceMatcher(getFileTypeByOffset(lbraceStart), PsiUtilCore |
| .getLanguageAtOffset(myPsiFile, lbraceStart)).getCodeConstructStart(myPsiFile, lbraceStart); |
| } |
| TextRange range = new TextRange(start, lbraceEnd); |
| int line1 = myDocument.getLineNumber(range.getStartOffset()); |
| int line2 = myDocument.getLineNumber(range.getEndOffset()); |
| line1 = Math.max(line1, line2 - 5); |
| range = new TextRange(myDocument.getLineStartOffset(line1), range.getEndOffset()); |
| EditorFragmentComponent.showEditorFragmentHint(myEditor, range, true, true); |
| } |
| } |
| }, |
| 300, ModalityState.stateForComponent(myEditor.getComponent())); |
| } |
| |
| void clearBraceHighlighters() { |
| List<RangeHighlighter> highlighters = getHighlightersList(); |
| for (final RangeHighlighter highlighter : highlighters) { |
| highlighter.dispose(); |
| } |
| highlighters.clear(); |
| } |
| |
| private void lineMarkFragment(int startLine, int endLine, @NotNull Color color) { |
| removeLineMarkers(); |
| |
| if (startLine >= endLine || endLine >= myDocument.getLineCount()) return; |
| |
| int startOffset = myDocument.getLineStartOffset(startLine); |
| int endOffset = myDocument.getLineStartOffset(endLine); |
| |
| RangeHighlighter highlighter = myEditor.getMarkupModel().addRangeHighlighter(startOffset, endOffset, 0, null, HighlighterTargetArea.LINES_IN_RANGE); |
| highlighter.setLineMarkerRenderer(new MyLineMarkerRenderer(color)); |
| myEditor.putUserData(LINE_MARKER_IN_EDITOR_KEY, highlighter); |
| } |
| |
| private void removeLineMarkers() { |
| ApplicationManager.getApplication().assertIsDispatchThread(); |
| RangeHighlighter marker = myEditor.getUserData(LINE_MARKER_IN_EDITOR_KEY); |
| if (marker != null && ((MarkupModelEx)myEditor.getMarkupModel()).containsHighlighter(marker)) { |
| marker.dispose(); |
| } |
| myEditor.putUserData(LINE_MARKER_IN_EDITOR_KEY, null); |
| } |
| |
| private static class MyLineMarkerRenderer implements LineMarkerRenderer { |
| private static final int DEEPNESS = 2; |
| private static final int THICKNESS = 2; |
| private final Color myColor; |
| |
| private MyLineMarkerRenderer(@NotNull Color color) { |
| myColor = color; |
| } |
| |
| @Override |
| public void paint(Editor editor, Graphics g, Rectangle r) { |
| int height = r.height + editor.getLineHeight(); |
| g.setColor(myColor); |
| g.fillRect(r.x, r.y, THICKNESS, height); |
| g.fillRect(r.x + THICKNESS, r.y, DEEPNESS, THICKNESS); |
| g.fillRect(r.x + THICKNESS, r.y + height - THICKNESS, DEEPNESS, THICKNESS); |
| } |
| } |
| } |