c22575755bfcf769c329d2cdee79d4a952865f49
[idea/community.git] / platform / xdebugger-impl / src / com / intellij / xdebugger / impl / breakpoints / XLineBreakpointImpl.java
1 /*
2  * Copyright 2000-2009 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 package com.intellij.xdebugger.impl.breakpoints;
17
18 import com.intellij.openapi.actionSystem.*;
19 import com.intellij.openapi.editor.Document;
20 import com.intellij.openapi.editor.colors.EditorColorsManager;
21 import com.intellij.openapi.editor.colors.EditorColorsScheme;
22 import com.intellij.openapi.editor.ex.MarkupModelEx;
23 import com.intellij.openapi.editor.markup.GutterIconRenderer;
24 import com.intellij.openapi.editor.markup.RangeHighlighter;
25 import com.intellij.openapi.editor.markup.TextAttributes;
26 import com.intellij.openapi.editor.markup.GutterDraggableObject;
27 import com.intellij.openapi.fileEditor.FileDocumentManager;
28 import com.intellij.openapi.vfs.VirtualFile;
29 import com.intellij.openapi.vfs.VirtualFileManager;
30 import com.intellij.openapi.vfs.VfsUtil;
31 import com.intellij.openapi.vfs.LocalFileSystem;
32 import com.intellij.openapi.util.io.FileUtil;
33 import com.intellij.openapi.application.ReadAction;
34 import com.intellij.openapi.application.Result;
35 import com.intellij.util.xmlb.annotations.Tag;
36 import com.intellij.util.StringBuilderSpinAllocator;
37 import com.intellij.xdebugger.*;
38 import com.intellij.xdebugger.breakpoints.*;
39 import com.intellij.xdebugger.impl.actions.ViewBreakpointsAction;
40 import com.intellij.xdebugger.impl.XSourcePositionImpl;
41 import com.intellij.xdebugger.impl.XDebugSessionImpl;
42 import com.intellij.xdebugger.ui.DebuggerColors;
43 import org.jetbrains.annotations.NotNull;
44 import org.jetbrains.annotations.Nullable;
45 import org.jetbrains.annotations.NonNls;
46
47 import javax.swing.*;
48 import java.awt.*;
49 import java.awt.dnd.DragSource;
50
51 /**
52  * @author nik
53  */
54 public class XLineBreakpointImpl<P extends XBreakpointProperties> extends XBreakpointBase<XLineBreakpoint<P>, P, XLineBreakpointImpl.LineBreakpointState<P>> implements XLineBreakpoint<P> {
55   private @Nullable RangeHighlighter myHighlighter;
56   private final XLineBreakpointType<P> myType;
57   private Icon myIcon;
58   private XSourcePosition mySourcePosition;
59   @NonNls private static final String BR_NBSP = "<br>&nbsp;";
60   private boolean myDisposed;
61
62   public XLineBreakpointImpl(final XLineBreakpointType<P> type, XBreakpointManagerImpl breakpointManager, String url, int line, final @Nullable P properties) {
63     super(type, breakpointManager, properties, new LineBreakpointState<P>(true, type.getId(), url, line));
64     myType = type;
65   }
66
67   private XLineBreakpointImpl(final XLineBreakpointType<P> type, XBreakpointManagerImpl breakpointManager, final LineBreakpointState<P> breakpointState) {
68     super(type, breakpointManager, breakpointState);
69     myType = type;
70   }
71
72   public void updateUI() {
73     if (myDisposed) return;
74
75     Document document = getDocument();
76     if (document == null) return;
77
78     EditorColorsScheme scheme = EditorColorsManager.getInstance().getGlobalScheme();
79     TextAttributes attributes = scheme.getAttributes(DebuggerColors.BREAKPOINT_ATTRIBUTES);
80
81     removeHighlighter();
82     MarkupModelEx markupModel = (MarkupModelEx)document.getMarkupModel(getProject());
83     RangeHighlighter highlighter = markupModel.addPersistentLineHighlighter(getLine(), DebuggerColors.BREAKPOINT_HIGHLIGHTER_LAYER,
84                                                                             attributes);
85     if (highlighter != null) {
86       updateIcon();
87       setupGutterRenderer(highlighter);
88     }
89     myHighlighter = highlighter;
90   }
91
92   @Nullable
93   public Document getDocument() {
94     VirtualFile file = getFile();
95     if (file == null) return null;
96     return FileDocumentManager.getInstance().getDocument(file);
97   }
98
99   @Nullable
100   private VirtualFile getFile() {
101     return VirtualFileManager.getInstance().findFileByUrl(getFileUrl());
102   }
103
104   private void setupGutterRenderer(final RangeHighlighter highlighter) {
105     highlighter.setGutterIconRenderer(new BreakpointGutterIconRenderer());
106   }
107
108   @NotNull
109   public XLineBreakpointType<P> getType() {
110     return myType;
111   }
112
113   private void updateIcon() {
114     myIcon = calculateIcon();
115   }
116
117   @NotNull
118   private Icon calculateIcon() {
119     if (!isEnabled()) {
120       return myType.getDisabledIcon();
121     }
122
123     XDebugSessionImpl session = getBreakpointManager().getDebuggerManager().getCurrentSession();
124     if (session == null) {
125       if (getBreakpointManager().getDependentBreakpointManager().getMasterBreakpoint(this) != null) {
126         return myType.getDisabledDependentIcon();
127       }
128     }
129     else {
130       if (session.areBreakpointsMuted()) {
131         return myType.getDisabledIcon();
132       }
133       if (session.isDisabledSlaveBreakpoint(this)) {
134         return myType.getDisabledDependentIcon();
135       }
136       CustomizedBreakpointPresentation presentation = session.getBreakpointPresentation(this);
137       if (presentation != null) {
138         Icon icon = presentation.getIcon();
139         if (icon != null) {
140           return icon;                  
141         }
142       }
143     }
144     return myType.getEnabledIcon();
145   }
146
147   @Nullable
148   private CustomizedBreakpointPresentation getCustomizedPresentation() {
149     final XDebugSessionImpl currentSession = getBreakpointManager().getDebuggerManager().getCurrentSession();
150     return currentSession != null ? currentSession.getBreakpointPresentation(this) : null;
151   }
152
153   public int getLine() {
154     return myState.getLine();
155   }
156
157   public String getFileUrl() {
158     return myState.getFileUrl();
159   }
160
161   public String getPresentableFilePath() {
162     String url = getFileUrl();
163     if (url != null && LocalFileSystem.PROTOCOL.equals(VirtualFileManager.extractProtocol(url))) {
164       return FileUtil.toSystemDependentName(VfsUtil.urlToPath(url));
165     }
166     return url != null ? url : "";
167   }
168
169   @Nullable
170   public RangeHighlighter getHighlighter() {
171     return myHighlighter;
172   }
173
174   public XSourcePosition getSourcePosition() {
175     if (mySourcePosition == null) {
176       new ReadAction() {
177         protected void run(final Result result) {
178           mySourcePosition = XSourcePositionImpl.create(getFile(), getLine());
179         }
180       }.execute();
181     }
182     return mySourcePosition;
183   }
184
185   public boolean isValid() {
186     return myHighlighter != null && myHighlighter.isValid();
187   }
188
189   public void dispose() {
190     removeHighlighter();
191     myDisposed = true;
192   }
193
194   private void removeHighlighter() {
195     if (myHighlighter != null) {
196       myHighlighter.getDocument().getMarkupModel(getProject()).removeHighlighter(myHighlighter);
197       myHighlighter = null;
198     }
199   }
200
201   private Icon getIcon() {
202     if (myIcon == null) {
203       updateIcon();
204     }
205     return myIcon;
206   }
207
208   public String getDescription() {
209     @NonNls StringBuilder builder = StringBuilderSpinAllocator.alloc();
210     try {
211       builder.append("<html><body>");
212       builder.append(myType.getDisplayText(this));
213
214       String errorMessage = getErrorMessage();
215       if (errorMessage != null) {
216         builder.append(BR_NBSP);
217         builder.append("<font color=\"red\">");
218         builder.append(errorMessage);
219         builder.append("</font>");
220       }
221
222       SuspendPolicy suspendPolicy = getSuspendPolicy();
223       if (suspendPolicy == SuspendPolicy.THREAD) {
224         builder.append(BR_NBSP).append(XDebuggerBundle.message("xbreakpoint.tooltip.suspend.policy.thread"));
225       }
226       else if (suspendPolicy == SuspendPolicy.NONE) {
227         builder.append(BR_NBSP).append(XDebuggerBundle.message("xbreakpoint.tooltip.suspend.policy.none"));
228       }
229
230       String condition = getCondition();
231       if (condition != null) {
232         builder.append(BR_NBSP);
233         builder.append(XDebuggerBundle.message("xbreakpoint.tooltip.condition"));
234         builder.append("&nbsp;");
235         builder.append(condition);
236       }
237
238       if (isLogMessage()) {
239         builder.append(BR_NBSP).append(XDebuggerBundle.message("xbreakpoint.tooltip.log.message"));
240       }
241       String logExpression = getLogExpression();
242       if (logExpression != null) {
243         builder.append(BR_NBSP);
244         builder.append(XDebuggerBundle.message("xbreakpoint.tooltip.log.expression"));
245         builder.append("&nbsp;");
246         builder.append(logExpression);
247       }
248
249       XBreakpoint<?> masterBreakpoint = getBreakpointManager().getDependentBreakpointManager().getMasterBreakpoint(this);
250       if (masterBreakpoint != null) {
251         builder.append(BR_NBSP);
252         String str = XDebuggerBundle.message("xbreakpoint.tooltip.depends.on");
253         builder.append(str);
254         builder.append("&nbsp;");
255         builder.append(XBreakpointUtil.getDisplayText(masterBreakpoint));
256       }
257
258       builder.append("</body><html");
259       return builder.toString();
260     }
261     finally {
262       StringBuilderSpinAllocator.dispose(builder);
263     }
264   }
265
266   private boolean canMoveTo(int line) {
267     final VirtualFile file = getFile();
268     return file != null && myType.canPutAt(file, line, getProject());
269   }
270
271   @Nullable
272   private String getErrorMessage() {
273     CustomizedBreakpointPresentation presentation = getCustomizedPresentation();
274     return presentation != null ? presentation.getErrorMessage() : null;
275   }
276
277   public void updatePosition() {
278     if (myHighlighter != null && myHighlighter.isValid()) {
279       Document document = myHighlighter.getDocument();
280       setLine(document.getLineNumber(myHighlighter.getStartOffset()));
281     }
282   }
283
284   private void setLine(final int line) {
285     if (getLine() != line) {
286       myState.setLine(line);
287       mySourcePosition = null;
288       fireBreakpointChanged();
289     }
290   }
291
292   @Tag("line-breakpoint")
293   public static class LineBreakpointState<P extends XBreakpointProperties> extends XBreakpointBase.BreakpointState<XLineBreakpoint<P>, P, XLineBreakpointType<P>> {
294     private String myFileUrl;
295     private int myLine;
296
297     public LineBreakpointState() {
298     }
299
300     public LineBreakpointState(final boolean enabled, final String typeId, final String fileUrl, final int line) {
301       super(enabled, typeId);
302       myFileUrl = fileUrl;
303       myLine = line;
304     }
305
306     @Tag("url")
307     public String getFileUrl() {
308       return myFileUrl;
309     }
310
311     public void setFileUrl(final String fileUrl) {
312       myFileUrl = fileUrl;
313     }
314
315     @Tag("line")
316     public int getLine() {
317       return myLine;
318     }
319
320     public void setLine(final int line) {
321       myLine = line;
322     }
323
324     public XBreakpointBase<XLineBreakpoint<P>,P, ?> createBreakpoint(@NotNull final XLineBreakpointType<P> type, @NotNull XBreakpointManagerImpl breakpointManager) {
325       return new XLineBreakpointImpl<P>(type, breakpointManager, this);
326     }
327   }
328
329   private class BreakpointGutterIconRenderer extends GutterIconRenderer {
330     @NotNull
331     public Icon getIcon() {
332       return XLineBreakpointImpl.this.getIcon();
333     }
334
335     @Nullable
336     public AnAction getClickAction() {
337       return new MyRemoveBreakpointAction();
338     }
339
340     @Nullable
341     public AnAction getMiddleButtonClickAction() {
342       return new MyToggleBreakpointAction();
343     }
344
345     @Nullable
346     public ActionGroup getPopupMenuActions() {
347       DefaultActionGroup group = new DefaultActionGroup();
348       group.add(new MyRemoveBreakpointAction());
349       group.add(new MyToggleBreakpointAction());
350       for (AnAction action : myType.getAdditionalPopupMenuActions(XLineBreakpointImpl.this, XDebuggerManager.getInstance(getProject()).getCurrentSession())) {
351         group.add(action);
352       }
353       group.add(new Separator());
354       group.add(new ViewBreakpointsAction(XDebuggerBundle.message("xdebugger.view.breakpoint.properties.action"), XLineBreakpointImpl.this));
355       return group;
356     }
357
358     @Nullable
359     public String getTooltipText() {
360       return getDescription();
361     }
362
363     @Override
364     public GutterDraggableObject getDraggableObject() {
365       return new GutterDraggableObject() {
366         public void removeSelf() {
367         }
368
369         public boolean copy(int line) {
370           if (canMoveTo(line)) {
371             setLine(line);
372             return true;
373           }
374           return false;
375         }
376
377         public Cursor getCursor(int line) {
378           return canMoveTo(line) ? DragSource.DefaultMoveDrop : DragSource.DefaultMoveNoDrop;
379         }
380       };
381     }
382   }
383
384   private class MyRemoveBreakpointAction extends AnAction {
385     private MyRemoveBreakpointAction() {
386       super(XDebuggerBundle.message("xdebugger.remove.line.breakpoint.action.text"));
387     }
388
389     public void actionPerformed(final AnActionEvent e) {
390       XDebuggerUtil.getInstance().removeBreakpoint(getProject(), XLineBreakpointImpl.this);
391     }
392   }
393
394   private class MyToggleBreakpointAction extends AnAction {
395     private MyToggleBreakpointAction() {
396       super(isEnabled() ? XDebuggerBundle.message("xdebugger.disable.breakpoint.action.text") : XDebuggerBundle.message("xdebugger.enable.breakpoint.action.text"));
397     }
398
399     public void actionPerformed(final AnActionEvent e) {
400       setEnabled(!isEnabled());
401     }
402   }
403
404 }