/* * Copyright 2000-2016 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. */ package com.intellij.xdebugger.impl.ui; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.editor.Document; import com.intellij.openapi.editor.Editor; import com.intellij.openapi.editor.ScrollType; import com.intellij.openapi.editor.colors.EditorColorsManager; import com.intellij.openapi.editor.colors.EditorColorsScheme; import com.intellij.openapi.editor.impl.DocumentMarkupModel; import com.intellij.openapi.editor.impl.EditorImpl; import com.intellij.openapi.editor.markup.*; import com.intellij.openapi.fileEditor.FileEditor; import com.intellij.openapi.fileEditor.FileEditorManager; import com.intellij.openapi.fileEditor.OpenFileDescriptor; import com.intellij.openapi.fileEditor.TextEditor; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Key; import com.intellij.openapi.util.TextRange; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.ui.AppUIUtil; import com.intellij.util.DocumentUtil; import com.intellij.xdebugger.XSourcePosition; import com.intellij.xdebugger.impl.XDebuggerUtilImpl; import com.intellij.xdebugger.impl.XSourcePositionImpl; import com.intellij.xdebugger.impl.settings.XDebuggerSettingManagerImpl; import com.intellij.xdebugger.ui.DebuggerColors; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import javax.swing.*; import java.util.concurrent.atomic.AtomicBoolean; /** * @author nik */ public class ExecutionPointHighlighter { private final Project myProject; private RangeHighlighter myRangeHighlighter; private Editor myEditor; private XSourcePosition mySourcePosition; private OpenFileDescriptor myOpenFileDescriptor; private boolean myNotTopFrame; private GutterIconRenderer myGutterIconRenderer; private static final Key<Boolean> EXECUTION_POINT_HIGHLIGHTER_KEY = Key.create("EXECUTION_POINT_HIGHLIGHTER_KEY"); private final AtomicBoolean updateRequested = new AtomicBoolean(); public ExecutionPointHighlighter(@NotNull Project project) { myProject = project; // Update highlighter colors if global color schema was changed project.getMessageBus().connect().subscribe(EditorColorsManager.TOPIC, scheme -> update(false)); } public void show(final @NotNull XSourcePosition position, final boolean notTopFrame, @Nullable final GutterIconRenderer gutterIconRenderer) { updateRequested.set(false); AppUIUtil.invokeLaterIfProjectAlive(myProject, () -> { updateRequested.set(false); mySourcePosition = position; clearDescriptor(); myOpenFileDescriptor = XSourcePositionImpl.createOpenFileDescriptor(myProject, position); if (!XDebuggerSettingManagerImpl.getInstanceImpl().getGeneralSettings().isScrollToCenter()) { myOpenFileDescriptor.setScrollType(notTopFrame ? ScrollType.CENTER : ScrollType.MAKE_VISIBLE); } //see IDEA-125645 and IDEA-63459 //myOpenFileDescriptor.setUseCurrentWindow(true); myGutterIconRenderer = gutterIconRenderer; myNotTopFrame = notTopFrame; doShow(true); }); } public void hide() { AppUIUtil.invokeOnEdt(() -> { updateRequested.set(false); removeHighlighter(); clearDescriptor(); myEditor = null; myGutterIconRenderer = null; }); } private void clearDescriptor() { if (myOpenFileDescriptor != null) { myOpenFileDescriptor.dispose(); myOpenFileDescriptor = null; } } public void navigateTo() { if (myOpenFileDescriptor != null && myOpenFileDescriptor.getFile().isValid()) { myOpenFileDescriptor.navigateInEditor(myProject, true); } } @Nullable public VirtualFile getCurrentFile() { return myOpenFileDescriptor != null ? myOpenFileDescriptor.getFile() : null; } public void update(final boolean navigate) { if (updateRequested.compareAndSet(false, true)) { ApplicationManager.getApplication().invokeLater(() -> { if (updateRequested.compareAndSet(true, false)) { doShow(navigate); } }, myProject.getDisposed()); } } public void updateGutterIcon(@Nullable final GutterIconRenderer renderer) { AppUIUtil.invokeOnEdt(() -> { if (myRangeHighlighter != null && myGutterIconRenderer != null) { myRangeHighlighter.setGutterIconRenderer(renderer); } }); } private void doShow(boolean navigate) { ApplicationManager.getApplication().assertIsDispatchThread(); if (ApplicationManager.getApplication().isUnitTestMode()) return; removeHighlighter(); OpenFileDescriptor fileDescriptor = myOpenFileDescriptor; if (!navigate && myOpenFileDescriptor != null) { fileDescriptor = new OpenFileDescriptor(myProject, myOpenFileDescriptor.getFile()); } myEditor = null; if (fileDescriptor != null) { if (!navigate) { FileEditor editor = FileEditorManager.getInstance(fileDescriptor.getProject()).getSelectedEditor(fileDescriptor.getFile()); if (editor instanceof TextEditor) { myEditor = ((TextEditor)editor).getEditor(); } } if (myEditor == null) { myEditor = XDebuggerUtilImpl.createEditor(fileDescriptor); } } if (myEditor != null) { addHighlighter(); } } private void removeHighlighter() { if (myEditor != null) { adjustCounter(myEditor, -1); } //if (myNotTopFrame && myEditor != null) { // myEditor.getSelectionModel().removeSelection(); //} if (myRangeHighlighter != null) { myRangeHighlighter.dispose(); myRangeHighlighter = null; } } private void addHighlighter() { adjustCounter(myEditor, 1); int line = mySourcePosition.getLine(); Document document = myEditor.getDocument(); if (line < 0 || line >= document.getLineCount()) return; //if (myNotTopFrame) { // myEditor.getSelectionModel().setSelection(document.getLineStartOffset(line), document.getLineEndOffset(line) + document.getLineSeparatorLength(line)); // return; //} if (myRangeHighlighter != null) return; EditorColorsScheme scheme = EditorColorsManager.getInstance().getGlobalScheme(); TextAttributes attributes = myNotTopFrame ? scheme.getAttributes(DebuggerColors.NOT_TOP_FRAME_ATTRIBUTES) : scheme.getAttributes(DebuggerColors.EXECUTIONPOINT_ATTRIBUTES); MarkupModel markupModel = DocumentMarkupModel.forDocument(document, myProject, true); if (mySourcePosition instanceof HighlighterProvider) { TextRange range = ((HighlighterProvider)mySourcePosition).getHighlightRange(); if (range != null) { TextRange lineRange = DocumentUtil.getLineTextRange(document, line); range = range.intersection(lineRange); if (range != null && !range.isEmpty() && !range.equals(lineRange)) { myRangeHighlighter = markupModel.addRangeHighlighter(range.getStartOffset(), range.getEndOffset(), DebuggerColors.EXECUTION_LINE_HIGHLIGHTERLAYER, attributes, HighlighterTargetArea.EXACT_RANGE); } } } if (myRangeHighlighter == null) { myRangeHighlighter = markupModel.addLineHighlighter(line, DebuggerColors.EXECUTION_LINE_HIGHLIGHTERLAYER, attributes); } myRangeHighlighter.putUserData(EXECUTION_POINT_HIGHLIGHTER_KEY, true); myRangeHighlighter.setEditorFilter(MarkupEditorFilterFactory.createIsNotDiffFilter()); myRangeHighlighter.setGutterIconRenderer(myGutterIconRenderer); } public boolean isFullLineHighlighter() { ApplicationManager.getApplication().assertIsDispatchThread(); return myRangeHighlighter != null && myRangeHighlighter.getTargetArea() == HighlighterTargetArea.LINES_IN_RANGE; } private static void adjustCounter(@NotNull final Editor editor, final int increment) { if (ApplicationManager.getApplication().isUnitTestMode()) return; // need to always invoke later to maintain order of increment/decrement SwingUtilities.invokeLater(() -> { JComponent component = editor.getComponent(); Object o = component.getClientProperty(EditorImpl.IGNORE_MOUSE_TRACKING); Integer value = ((o instanceof Integer) ? (Integer)o : 0) + increment; component.putClientProperty(EditorImpl.IGNORE_MOUSE_TRACKING, value > 0 ? value : null); }); } public interface HighlighterProvider { @Nullable TextRange getHighlightRange(); } }