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