| /* |
| * Copyright 2000-2009 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.debugger.ui; |
| |
| import com.intellij.debugger.DebuggerBundle; |
| import com.intellij.debugger.DebuggerInvocationUtil; |
| import com.intellij.debugger.DebuggerManagerEx; |
| import com.intellij.debugger.SourcePosition; |
| import com.intellij.debugger.engine.DebugProcessEvents; |
| import com.intellij.debugger.engine.DebugProcessImpl; |
| import com.intellij.debugger.engine.evaluation.EvaluateException; |
| import com.intellij.debugger.engine.events.DebuggerContextCommandImpl; |
| import com.intellij.debugger.impl.*; |
| import com.intellij.debugger.jdi.StackFrameProxyImpl; |
| import com.intellij.debugger.jdi.ThreadReferenceProxyImpl; |
| import com.intellij.debugger.ui.breakpoints.*; |
| import com.intellij.openapi.actionSystem.ActionGroup; |
| import com.intellij.openapi.actionSystem.AnAction; |
| import com.intellij.openapi.actionSystem.AnActionEvent; |
| import com.intellij.openapi.actionSystem.DefaultActionGroup; |
| import com.intellij.openapi.application.ApplicationManager; |
| import com.intellij.openapi.diagnostic.Logger; |
| import com.intellij.openapi.editor.Document; |
| import com.intellij.openapi.editor.Editor; |
| import com.intellij.openapi.editor.colors.EditorColorsManager; |
| import com.intellij.openapi.editor.colors.EditorColorsScheme; |
| import com.intellij.openapi.editor.ex.DocumentEx; |
| import com.intellij.openapi.editor.impl.EditorImpl; |
| import com.intellij.openapi.editor.markup.GutterIconRenderer; |
| import com.intellij.openapi.editor.markup.RangeHighlighter; |
| import com.intellij.openapi.project.Project; |
| import com.intellij.openapi.util.Comparing; |
| import com.intellij.openapi.util.Computable; |
| import com.intellij.openapi.util.Key; |
| import com.intellij.openapi.util.Pair; |
| import com.intellij.psi.PsiDocumentManager; |
| import com.intellij.psi.PsiFile; |
| import com.intellij.util.StringBuilderSpinAllocator; |
| import com.intellij.xdebugger.impl.actions.ViewBreakpointsAction; |
| import com.intellij.xdebugger.ui.DebuggerColors; |
| import com.sun.jdi.event.Event; |
| import com.sun.jdi.event.LocatableEvent; |
| import com.sun.jdi.event.MethodEntryEvent; |
| import org.jetbrains.annotations.NotNull; |
| |
| import javax.swing.*; |
| import java.util.ArrayList; |
| import java.util.Iterator; |
| import java.util.List; |
| |
| /** |
| * Created by IntelliJ IDEA. |
| * User: lex |
| * Date: Jul 9, 2003 |
| * Time: 6:24:35 PM |
| * To change this template use Options | File Templates. |
| */ |
| public class PositionHighlighter { |
| private static final Key<Boolean> HIGHLIGHTER_USERDATA_KEY = new Key<Boolean>("HIGHLIGHTER_USERDATA_KEY"); |
| private static final Logger LOG = Logger.getInstance("#com.intellij.debugger.ui.PositionHighlighter"); |
| private final Project myProject; |
| private DebuggerContextImpl myContext = DebuggerContextImpl.EMPTY_CONTEXT; |
| private SelectionDescription mySelectionDescription = null; |
| private ExecutionPointDescription myExecutionPointDescription = null; |
| |
| public PositionHighlighter(Project project, DebuggerStateManager stateManager) { |
| myProject = project; |
| |
| stateManager.addListener(new DebuggerContextListener() { |
| public void changeEvent(DebuggerContextImpl newContext, int event) { |
| myContext = newContext; |
| if (event != DebuggerSession.EVENT_REFRESH_VIEWS_ONLY && event != DebuggerSession.EVENT_THREADS_REFRESH) { |
| refresh(); |
| } |
| } |
| }); |
| } |
| |
| private void showLocationInEditor() { |
| myContext.getDebugProcess().getManagerThread().schedule(new ShowLocationCommand(myContext)); |
| } |
| |
| private void refresh() { |
| clearSelections(); |
| final DebuggerSession session = myContext.getDebuggerSession(); |
| if(session != null) { |
| switch(session.getState()) { |
| case DebuggerSession.STATE_PAUSED: |
| if(myContext.getFrameProxy() != null) { |
| showLocationInEditor(); |
| return; |
| } |
| break; |
| } |
| } |
| } |
| |
| protected static class ExecutionPointDescription extends SelectionDescription { |
| private RangeHighlighter myHighlighter; |
| private final int myLineIndex; |
| |
| protected ExecutionPointDescription(Editor editor, int lineIndex) { |
| super(editor); |
| myLineIndex = lineIndex; |
| } |
| |
| public void select() { |
| if(myIsActive) return; |
| myIsActive = true; |
| EditorColorsScheme scheme = EditorColorsManager.getInstance().getGlobalScheme(); |
| myHighlighter = myEditor.getMarkupModel().addLineHighlighter( |
| myLineIndex, |
| DebuggerColors.EXECUTION_LINE_HIGHLIGHTERLAYER, |
| scheme.getAttributes(DebuggerColors.EXECUTIONPOINT_ATTRIBUTES) |
| ); |
| adjustCounter(myEditor, 1); |
| myHighlighter.setErrorStripeTooltip(DebuggerBundle.message("position.highlighter.stripe.tooltip")); |
| myHighlighter.putUserData(HIGHLIGHTER_USERDATA_KEY, Boolean.TRUE); |
| } |
| |
| private static void adjustCounter(@NotNull Editor editor, int increment) { |
| 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 void remove() { |
| if(!myIsActive) return; |
| myIsActive = false; |
| adjustCounter(myEditor, -1); |
| if (myHighlighter != null) { |
| myHighlighter.dispose(); |
| myHighlighter = null; |
| } |
| } |
| |
| public RangeHighlighter getHighlighter() { |
| return myHighlighter; |
| } |
| } |
| |
| protected abstract static class SelectionDescription { |
| protected Editor myEditor; |
| protected boolean myIsActive; |
| |
| public SelectionDescription(Editor editor) { |
| myEditor = editor; |
| } |
| |
| public abstract void select(); |
| public abstract void remove(); |
| |
| public static ExecutionPointDescription createExecutionPoint(final Editor editor, |
| final int lineIndex) { |
| return new ExecutionPointDescription(editor, lineIndex); |
| } |
| |
| public static SelectionDescription createSelection(final Editor editor, final int lineIndex) { |
| return new SelectionDescription(editor) { |
| public void select() { |
| if(myIsActive) return; |
| myIsActive = true; |
| DocumentEx doc = (DocumentEx)editor.getDocument(); |
| editor.getSelectionModel().setSelection( |
| doc.getLineStartOffset(lineIndex), |
| doc.getLineEndOffset(lineIndex) + doc.getLineSeparatorLength(lineIndex) |
| ); |
| } |
| |
| public void remove() { |
| if(!myIsActive) return; |
| myIsActive = false; |
| myEditor.getSelectionModel().removeSelection(); |
| } |
| }; |
| } |
| } |
| |
| private void showSelection(SourcePosition position) { |
| Editor editor = getEditor(position); |
| if(editor == null) { |
| return; |
| } |
| if (mySelectionDescription != null) { |
| mySelectionDescription.remove(); |
| } |
| mySelectionDescription = SelectionDescription.createSelection(editor, position.getLine()); |
| mySelectionDescription.select(); |
| } |
| |
| private void showExecutionPoint(final SourcePosition position, List<Pair<Breakpoint, Event>> events) { |
| if (myExecutionPointDescription != null) { |
| myExecutionPointDescription.remove(); |
| } |
| int lineIndex = position.getLine(); |
| Editor editor = getEditor(position); |
| if(editor == null) { |
| return; |
| } |
| myExecutionPointDescription = SelectionDescription.createExecutionPoint(editor, lineIndex); |
| myExecutionPointDescription.select(); |
| |
| RangeHighlighter highlighter = myExecutionPointDescription.getHighlighter(); |
| |
| if(highlighter != null) { |
| final List<Pair<Breakpoint, Event>> eventsOutOfLine = new ArrayList<Pair<Breakpoint, Event>>(); |
| |
| for (final Pair<Breakpoint, Event> eventDescriptor : events) { |
| final Breakpoint breakpoint = eventDescriptor.getFirst(); |
| // filter breakpoints that do not match the event |
| if (breakpoint instanceof MethodBreakpoint) { |
| try { |
| if (!((MethodBreakpoint)breakpoint).matchesEvent((LocatableEvent)eventDescriptor.getSecond(), myContext.getDebugProcess())) { |
| continue; |
| } |
| } |
| catch (EvaluateException ignored) { |
| } |
| } |
| else if (breakpoint instanceof WildcardMethodBreakpoint) { |
| if (!((WildcardMethodBreakpoint)breakpoint).matchesEvent((LocatableEvent)eventDescriptor.getSecond())) { |
| continue; |
| } |
| } |
| |
| if (breakpoint instanceof BreakpointWithHighlighter) { |
| if (((BreakpointWithHighlighter)breakpoint).isVisible() && breakpoint.isValid()) { |
| breakpoint.reload(); |
| int bptLine = ((BreakpointWithHighlighter)breakpoint).getLineIndex(); |
| if (bptLine < 0 || bptLine != lineIndex) { |
| eventsOutOfLine.add(eventDescriptor); |
| } |
| } |
| } |
| else { |
| eventsOutOfLine.add(eventDescriptor); |
| } |
| } |
| |
| if(!eventsOutOfLine.isEmpty()) { |
| highlighter.setGutterIconRenderer(new MyGutterIconRenderer(eventsOutOfLine)); |
| } |
| } |
| } |
| |
| private Editor getEditor(SourcePosition position) { |
| final PsiFile psiFile = position.getFile(); |
| Document doc = PsiDocumentManager.getInstance(myProject).getDocument(psiFile); |
| if (!psiFile.isValid()) { |
| return null; |
| } |
| final int lineIndex = position.getLine(); |
| if (lineIndex < 0 || lineIndex > doc.getLineCount()) { |
| //LOG.assertTrue(false, "Incorrect lineIndex " + lineIndex + " in file " + psiFile.getName()); |
| return null; |
| } |
| return position.openEditor(false); |
| } |
| |
| private void clearSelections() { |
| if (mySelectionDescription != null || myExecutionPointDescription != null) { |
| ApplicationManager.getApplication().runReadAction(new Runnable() { |
| public void run() { |
| if (mySelectionDescription != null) { |
| mySelectionDescription.remove(); |
| mySelectionDescription = null; |
| } |
| if (myExecutionPointDescription != null) { |
| myExecutionPointDescription.remove(); |
| myExecutionPointDescription = null; |
| } |
| } |
| }); |
| } |
| } |
| |
| |
| public void updateContextPointDescription() { |
| if(myContext.getDebuggerSession() == null) return; |
| |
| showLocationInEditor(); |
| } |
| |
| private class ShowLocationCommand extends DebuggerContextCommandImpl { |
| private final DebuggerContextImpl myContext; |
| |
| public ShowLocationCommand(DebuggerContextImpl context) { |
| super(context); |
| myContext = context; |
| } |
| |
| public void threadAction() { |
| final SourcePosition contextPosition = myContext.getSourcePosition(); |
| if (contextPosition == null) { |
| return; |
| } |
| |
| boolean isExecutionPoint = false; |
| try { |
| StackFrameProxyImpl frameProxy = myContext.getFrameProxy(); |
| final ThreadReferenceProxyImpl thread = getSuspendContext().getThread(); |
| isExecutionPoint = thread != null && frameProxy != null && frameProxy.equals(thread.frame(0)); |
| } |
| catch(Throwable th) { |
| LOG.debug(th); |
| } |
| |
| final List<Pair<Breakpoint, Event>> events = DebuggerUtilsEx.getEventDescriptors(getSuspendContext()); |
| |
| final SourcePosition position = ApplicationManager.getApplication().runReadAction(new Computable<SourcePosition>() { |
| public SourcePosition compute() { |
| Document document = PsiDocumentManager.getInstance(myProject).getDocument(contextPosition.getFile()); |
| if(document != null) { |
| if(contextPosition.getLine() < 0 || contextPosition.getLine() >= document.getLineCount()) { |
| return SourcePosition.createFromLine(contextPosition.getFile(), 0); |
| } |
| } |
| return contextPosition; |
| } |
| }); |
| |
| if(isExecutionPoint) { |
| DebuggerInvocationUtil.swingInvokeLater(myProject, new Runnable() { |
| public void run() { |
| final SourcePosition highlightPosition = getHighlightPosition(events, position); |
| showExecutionPoint(highlightPosition, events); |
| } |
| }); |
| } |
| else { |
| DebuggerInvocationUtil.swingInvokeLater(myProject, new Runnable() { |
| public void run() { |
| showSelection(position); |
| } |
| }); |
| } |
| } |
| |
| private SourcePosition getHighlightPosition(final List<Pair<Breakpoint, Event>> events, SourcePosition position) { |
| for (Iterator<Pair<Breakpoint, Event>> iterator = events.iterator(); iterator.hasNext();) { |
| final Pair<Breakpoint, Event> eventDescriptor = iterator.next(); |
| final Breakpoint breakpoint = eventDescriptor.getFirst(); |
| if(breakpoint instanceof LineBreakpoint) { |
| breakpoint.reload(); |
| final SourcePosition breakPosition = ((BreakpointWithHighlighter)breakpoint).getSourcePosition(); |
| if(breakPosition != null && breakPosition.getLine() != position.getLine()) { |
| position = SourcePosition.createFromLine(position.getFile(), breakPosition.getLine()); |
| } |
| } |
| else if(breakpoint instanceof MethodBreakpoint) { |
| final MethodBreakpoint methodBreakpoint = (MethodBreakpoint)breakpoint; |
| methodBreakpoint.reload(); |
| final SourcePosition breakPosition = methodBreakpoint.getSourcePosition(); |
| final LocatableEvent event = (LocatableEvent)eventDescriptor.getSecond(); |
| if(breakPosition != null && breakPosition.getFile().equals(position.getFile()) && breakPosition.getLine() != position.getLine() && event instanceof MethodEntryEvent) { |
| try { |
| if (methodBreakpoint.matchesEvent(event, myContext.getDebugProcess())) { |
| position = SourcePosition.createFromLine(position.getFile(), breakPosition.getLine()); |
| } |
| } |
| catch (EvaluateException ignored) { |
| } |
| } |
| } |
| } |
| return position; |
| } |
| } |
| |
| private class MyGutterIconRenderer extends GutterIconRenderer { |
| private final List<Pair<Breakpoint, Event>> myEventsOutOfLine; |
| |
| public MyGutterIconRenderer(List<Pair<Breakpoint, Event>> eventsOutOfLine) { |
| myEventsOutOfLine = eventsOutOfLine; |
| } |
| |
| @NotNull |
| public Icon getIcon() { |
| return myEventsOutOfLine.get(0).getFirst().getIcon(); |
| } |
| |
| public String getTooltipText() { |
| DebugProcessImpl debugProcess = myContext.getDebugProcess(); |
| if (debugProcess == null) { |
| return null; |
| } |
| final StringBuilder buf = StringBuilderSpinAllocator.alloc(); |
| try { |
| //noinspection HardCodedStringLiteral |
| buf.append("<html><body>"); |
| for (Iterator<Pair<Breakpoint, Event>> iterator = myEventsOutOfLine.iterator(); iterator.hasNext();) { |
| Pair<Breakpoint, Event> eventDescriptor = iterator.next(); |
| buf.append(((DebugProcessEvents)debugProcess).getEventText(eventDescriptor)); |
| if(iterator.hasNext()) { |
| //noinspection HardCodedStringLiteral |
| buf.append("<br>"); |
| } |
| } |
| //noinspection HardCodedStringLiteral |
| buf.append("</body></html>"); |
| return buf.toString(); |
| } |
| finally { |
| StringBuilderSpinAllocator.dispose(buf); |
| } |
| } |
| |
| public ActionGroup getPopupMenuActions() { |
| DefaultActionGroup group = new DefaultActionGroup(); |
| for (Pair<Breakpoint, Event> eventDescriptor : myEventsOutOfLine) { |
| Breakpoint breakpoint = eventDescriptor.getFirst(); |
| AnAction viewBreakpointsAction = new ViewBreakpointsAction(breakpoint.getDisplayName(), breakpoint.getXBreakpoint()); |
| group.add(viewBreakpointsAction); |
| } |
| |
| return group; |
| } |
| |
| @Override |
| public AnAction getMiddleButtonClickAction() { |
| return new AnAction() { |
| @Override |
| public void actionPerformed(AnActionEvent e) { |
| if (myEventsOutOfLine.size() == 1) { |
| Breakpoint breakpoint = myEventsOutOfLine.get(0).getFirst(); |
| breakpoint.setEnabled(!breakpoint.isEnabled()); |
| DebuggerManagerEx.getInstanceEx(myProject).getBreakpointManager().fireBreakpointChanged(breakpoint); |
| } |
| } |
| }; |
| } |
| |
| @Override |
| public boolean equals(Object obj) { |
| return obj instanceof MyGutterIconRenderer && |
| Comparing.equal(getTooltipText(), ((MyGutterIconRenderer)obj).getTooltipText()) && |
| Comparing.equal(getIcon(), ((MyGutterIconRenderer)obj).getIcon()); |
| } |
| |
| @Override |
| public int hashCode() { |
| return getIcon().hashCode(); |
| } |
| } |
| } |