1 /*
2  * ConsoleError.java
3  *
4  * Copyright (C) 2021 by RStudio, PBC
5  *
6  * Unless you have received this program directly from RStudio pursuant
7  * to the terms of a commercial license agreement with RStudio, then
8  * this program is licensed to you under the terms of version 3 of the
9  * GNU Affero General Public License. This program is distributed WITHOUT
10  * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
11  * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
12  * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
13  *
14  */
15 
16 package org.rstudio.studio.client.common.debugging.ui;
17 
18 import com.google.gwt.dom.client.Element;
19 import com.google.gwt.dom.client.Node;
20 import com.google.gwt.dom.client.NodeList;
21 import com.google.gwt.dom.client.Style;
22 import org.rstudio.core.client.VirtualConsole;
23 import org.rstudio.studio.client.RStudioGinjector;
24 import org.rstudio.studio.client.common.debugging.model.UnhandledError;
25 
26 import com.google.gwt.core.client.GWT;
27 import com.google.gwt.uibinder.client.UiBinder;
28 import com.google.gwt.uibinder.client.UiField;
29 import com.google.gwt.user.client.DOM;
30 import com.google.gwt.user.client.Event;
31 import com.google.gwt.user.client.EventListener;
32 import com.google.gwt.user.client.ui.Anchor;
33 import com.google.gwt.user.client.ui.Composite;
34 import com.google.gwt.user.client.ui.HTML;
35 import com.google.gwt.user.client.ui.HTMLPanel;
36 import com.google.gwt.user.client.ui.Image;
37 import com.google.gwt.user.client.ui.Widget;
38 
39 public class ConsoleError extends Composite
40 {
41    private static ConsoleErrorUiBinder uiBinder = GWT.create(ConsoleErrorUiBinder.class);
42 
43    interface ConsoleErrorUiBinder extends UiBinder<Widget, ConsoleError>
44    {
45    }
46 
47    public interface Observer
48    {
onErrorBoxResize()49       void onErrorBoxResize();
runCommandWithDebug(String command)50       void runCommandWithDebug(String command);
51    }
52 
53    // Because we are adding interactive elements to the VirtualScroller the GWT bindings are lost.
54    // We need to programmatically find the elements and manipulate them through regular JS functions.
ConsoleError(UnhandledError err, String errorClass, Observer observer, String command)55    public ConsoleError(UnhandledError err,
56                        String errorClass,
57                        Observer observer,
58                        String command)
59    {
60       observer_ = observer;
61       command_ = command;
62 
63       initWidget(uiBinder.createAndBindUi(this));
64 
65       VirtualConsole vc = RStudioGinjector.INSTANCE.getVirtualConsoleFactory().create(errorMessage.getElement());
66       vc.submit(err.getErrorMessage().trim());
67       errorMessage.addStyleName(errorClass);
68 
69       EventListener onConsoleErrorClick = event ->
70       {
71          if (DOM.eventGetType(event) == Event.ONCLICK)
72          {
73             Element target = Element.as(event.getEventTarget());
74             if (target == null)
75                return;
76 
77             if (target.hasClassName("show_traceback_text") || target.hasClassName("show_traceback_image"))
78             {
79                setTracebackVisible(!showingTraceback_);
80                observer_.onErrorBoxResize();
81             }
82             else if (target.hasClassName("rerun_text") || target.hasClassName("rerun_image"))
83             {
84                observer_.onErrorBoxResize();
85                observer_.runCommandWithDebug(command_);
86             }
87          }
88       };
89 
90       rerunText.setVisible(command_ != null);
91       rerunImage.setVisible(command_ != null);
92 
93       DOM.sinkEvents(this.getElement(), Event.ONCLICK);
94       DOM.setEventListener(this.getElement(), onConsoleErrorClick);
95 
96       for (int i = err.getErrorFrames().length() - 1; i >= 0; i--)
97       {
98          ConsoleErrorFrame frame = new ConsoleErrorFrame(i + 1,
99                err.getErrorFrames().get(i));
100          framePanel.add(frame);
101       }
102    }
103 
setTracebackVisible(boolean visible)104    public void setTracebackVisible(boolean visible)
105    {
106       showingTraceback_ = visible;
107 
108       NodeList<Node> children = this.getElement().getChildNodes();
109       for (int i = 0; i < children.getLength(); i++)
110       {
111          Node n = children.getItem(i);
112          if (n.getNodeType() != Node.ELEMENT_NODE)
113             continue;
114 
115          Element child = Element.as(children.getItem(i));
116          if (child.hasClassName("show_traceback_text")) {
117             if (showingTraceback_)
118                child.setInnerText("Hide Traceback");
119             else
120                child.setInnerText("Show Traceback");
121          }
122          else if (child.hasClassName("stack_trace"))
123          {
124             if (showingTraceback_)
125                child.getStyle().setDisplay(Style.Display.BLOCK);
126             else
127                child.getStyle().setDisplay(Style.Display.NONE);
128          }
129       }
130    }
131 
132    @UiField Anchor showTracebackText;
133    @UiField Image showTracebackImage;
134    @UiField Anchor rerunText;
135    @UiField Image rerunImage;
136    @UiField HTMLPanel framePanel;
137    @UiField HTML errorMessage;
138 
139    private Observer observer_;
140    private boolean showingTraceback_ = false;
141    private String command_;
142 }
143