8000 Implement tracking of runtime errors in Errorstrip · scijava/script-editor@1ebf352 · GitHub
[go: up one dir, main page]

Skip to content

Commit 1ebf352

Browse files
committed
Implement tracking of runtime errors in Errorstrip
When execution of a script fails, this makes it so that the culprit code reported in the exception gets bookmarked in the ErrorStrip. This also enables Next/Previous Error commands to work with all the languages (up-to-now they would only work for Java!?) Not yet tested thoroughly with all the languages
1 parent 6c8cefa commit 1ebf352

File tree

4 files changed

+356
-8
lines changed

4 files changed

+356
-8
lines changed

src/main/java/org/scijava/ui/swing/script/EditorPane.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,8 @@ public class EditorPane extends RSyntaxTextArea implements DocumentListener {
134134
private boolean autoCompletionJavaFallback;
135135
private boolean autoCompletionWithoutKey;
136136
private String supportStatus;
137+
private final ErrorParser errorHighlighter;
138+
137139

138140
@Parameter
139141
Context context;
@@ -155,6 +157,8 @@ public class EditorPane extends RSyntaxTextArea implements DocumentListener {
155157
*/
156158
public EditorPane() {
157159

160+
errorHighlighter= new ErrorParser(this);
161+
158162
// set sensible defaults
159163
setAntiAliasingEnabled(true);
160164
setAutoIndentEnabled(true);
@@ -1089,6 +1093,10 @@ private void openLinkInBrowser(String link) {
10891093
}
10901094
}
10911095

1096+
ErrorParser getErrorHighlighter() {
1097+
return errorHighlighter;
1098+
}
1099+
10921100
static class CamelCaseAction extends RecordableTextAction {
10931101
private static final long serialVersionUID = 1L;
10941102

Lines changed: 314 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,314 @@
1+
/*
2+
* #%L
3+
* Script Editor and Interpreter for SciJava script languages.
4+
* %%
5+
* Copyright (C) 2009 - 2022 SciJava developers.
6+
* %%
7+
* Redistribution and use in source and binary forms, with or without
8+
* modification, are permitted provided that the following conditions are met:
9+
*
10+
* 1. Redistributions of source code must retain the above copyright notice,
11+
* this list of conditions and the following disclaimer.
12+
* 2. Redistributions in binary form must reproduce the above copyright notice,
13+
* this list of conditions and the following disclaimer in the documentation
14+
* and/or other materials provided with the distribution.
15+
*
16+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
17+
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18+
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19+
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
20+
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
21+
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
22+
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
23+
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
24+
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
25+
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
26+
* POSSIBILITY OF SUCH DAMAGE.
27+
* #L%
28+
*/
29+
30+
package org.scijava.ui.swing.script;
31+
32+
import java.awt.Color;
33+
import java.awt.Rectangle;
34+
import java.io.PrintWriter;
35+
import java.io.StringWriter;
36+
import java.util.Collection;
37+
import java.util.StringTokenizer;
38+
import java.util.TreeSet;
39+
import java.util.regex.Matcher;
40+
import java.util.regex.Pattern;
41+
42+
import javax.swing.UIManager;
43+
import javax.swing.text.BadLocationException;
44+
import javax.swing.text.Element;
45+
46+
import org.fife.ui.rsyntaxtextarea.RSyntaxDocument;
47+
import org.fife.ui.rsyntaxtextarea.SyntaxConstants;
48+
import org.fife.ui.rsyntaxtextarea.parser.AbstractParser;
49+
import org.fife.ui.rsyntaxtextarea.parser.DefaultParseResult;
50+
import org.fife.ui.rsyntaxtextarea.parser.DefaultParserNotice;
51+
import org.fife.ui.rsyntaxtextarea.parser.ParseResult;
52+
import org.fife.ui.rsyntaxtextarea.parser.Parser;
53+
import org.scijava.script.ScriptLanguage;
54+
55+
public class ErrorParser {
56+
57+
/* Color for ErrorStrip marks + line highlights. May not work for all themes */
58+
private static final Color COLOR = new Color(255, 99, 71, 128);
59+
/* 0-base indices of Editor's lines that have errored */
60+
private TreeSet<Integer> errorLines;
61+
/* When running selected code errored lines map to Editor through this offset */
62+
private int lineOffset;
63+
private boolean enabled;
64+
private final EditorPane editorPane;
65+
private JTextAreaWriter writer;
66+
private int lengthOfJTextAreaWriter;
67+
private ErrorStripNotifyingParser notifyingParser;
68+
69+
public ErrorParser(final EditorPane editorPane) {
70+
this.editorPane = editorPane;
71+
lineOffset = 0;
72+
}
73+
74+
public void gotoPreviousError() {
75+
if (!isCaretMovable()) {
76+
try {
77+
final int caretLine = editorPane.getCaretLineNumber();
78+
gotoLine(errorLines.lower(caretLine));
79+
} catch (final NullPointerException ignored) {
80+
gotoLine(errorLines.last());
81+
}
82+
}
83+
}
84+
85+
public void gotoNextError() {
86+
if (isCaretMovable()) {
87+
try {
88+
final int caretLine = editorPane.getCaretLineNumber();
89+
gotoLine(errorLines.higher(caretLine));
90+
} catch (final NullPointerException ignored) {
91+
gotoLine(errorLines.first());
92+
}
93+
}
94+
}
95+
96+
public void reset() {
97+
if (notifyingParser != null) {
98+
editorPane.removeParser(notifyingParser);
99+
if (notifyingParser.highlightAbnoxiously)
100+
editorPane.removeAllLineHighlights();
101+
}
102+
}
103+
104+
public void setEnabled(final boolean enabled) {
105+
this.enabled = enabled;
106+
}
107+
108+
public void setLineOffset(final int zeroBasedlineOffset) {
109+
this.lineOffset = zeroBasedlineOffset;
110+
}
111+
112+
public void setSelectedCodeExecution(final boolean selectedExecution) {
113+
if (selectedExecution) {
114+
final int p0 = Math.min(editorPane.getCaret().getDot(), editorPane.getCaret().getMark());
115+
final int p1 = Math.max(editorPane.getCaret().getDot(), editorPane.getCaret().getMark());
116+
if (p0 != p1) {
117+
try {
118+
lineOffset = editorPane.getLineOfOffset(p0);
119+
} catch (final BadLocationException ignored) {
120+
lineOffset = -1;
121+
}
122+
} else {
123+
lineOffset = -1;
124+
}
125+
} else {
126+
lineOffset = 0;
127+
}
128+
}
129+
130+
public void setWriter(final JTextAreaWriter writer) {
131+
this.writer = writer;
132+
lengthOfJTextAreaWriter = writer.textArea.getText().length();
133+
}
134+
135+
public void parse() {
136+
if (writer == null)
137+
throw new IllegalArgumentException("Writer is null");
138+
parse(writer.textArea.getText().substring(lengthOfJTextAreaWriter));
139+
}
140+
141+
public void parse(final Throwable t) {
142+
if (!enabled)
143+
return;
144+
final StringWriter sw = new StringWriter();
145+
final PrintWriter pw = new PrintWriter(sw);
146+
t.printStackTrace(pw);
147+
parse(sw.toString());
148+
}
149+
150+
private boolean isCaretMovable() {
151+
if (errorLines == null || errorLines.isEmpty() || !editorPane.isEditable() || !editorPane.isEnabled()) {
152+
UIManager.getLookAndFeel().provideErrorFeedback(editorPane);
153+
return false;
154+
}
155+
return true;
156+
}
157+
158+
private void gotoLine(final int lineNumber) {
159+
try {
160+
editorPane.setCaretPosition(editorPane.getLineStartOffset(lineNumber));
161+
// ensure line is visible. Probably not needed!?
162+
final Rectangle rect = editorPane.modelToView(editorPane.getCaretPosition());
163+
editorPane.scrollRectToVisible(rect);
164+
} catch (final BadLocationException ignored) {
165+
// do nothing
166+
} finally {
167+
editorPane.requestFocusInWindow();
168+
}
169+
}
170+
171+
private void parse(final String errorLog) {
172+
// Do nothing if disabled, or if only selected text was evaluated in the
173+
// script but we don't know where in the document such selection occurred
174+
if (!enabled) {
175+
abort("When Auto-imports are active errors are not tractable in the Editor");
176+
return;
177+
}
178+
if (lineOffset == -1) {
179+
abort("Code selection unknown: Erros are not tractable in the Editor");
180+
return;
181+
}
182+
final ScriptLanguage lang = editorPane.getCurrentLanguage();
183+
if (lang == null)
184+
return;
185+
final boolean isJava = lang.getLanguageName().equals("Java");
186+
final String fileName = editorPane.getFileName();
187+
if (isJava && fileName == null)
188+
return;
189+
errorLines = new TreeSet<>();
190+
final StringTokenizer tokenizer = new StringTokenizer(errorLog, "\n");
191+
if (isJava) {
192+
while (tokenizer.hasMoreTokens()) {
193+
parseJava(fileName, tokenizer.nextToken(), errorLines);
194+
}
195+
} else {
196+
while (tokenizer.hasMoreTokens()) {
197+
parseNonJava(tokenizer.nextToken(), errorLines);
198+
}
199+
}
200+
if (!errorLines.isEmpty()) {
201+
notifyingParser = new ErrorStripNotifyingParser();
202+
editorPane.addParser(notifyingParser);
203+
editorPane.forceReparsing(notifyingParser);
204+
gotoLine(errorLines.first());
205+
}
206+
}
207+
208+
private void parseNonJava(final String lineText, final Collection<Integer> errorLines) {
209+
// Rationale: line errors of interpretative scripts will mention both the
210+
// extension
211+
// of the script and the term "line "
212+
final int tokenIndex = lineText.indexOf("line ");
213+
if (tokenIndex < 0) {
214+
return;
215+
}
216+
// System.out.println("Parsing candidate: " + lineText);
217+
for (final String extension : editorPane.getCurrentLanguage().getExtensions()) {
218+
final int dotIndex = lineText.indexOf("." + extension);
219+
if (dotIndex < 0)
220+
continue;
221+
final Pattern pattern = Pattern.compile("\\d+");
222+
// System.out.println("section being matched: " + lineText.substring(tokenIndex));
223+
final Matcher matcher = pattern.matcher(lineText.substring(tokenIndex));
224+
if (matcher.find()) {
225+
try {
226+
final int lineNumber = Integer.valueOf(matcher.group());
227+
if (lineNumber > 0)
228+
errorLines.add(lineNumber - 1 + lineOffset); // store 0-based indices
229+
// System.out.println("line No (zero-based): " + (lineNumber - 1));
230+
} catch (final NumberFormatException e) {
231+
// ignore
232+
}
233+
}
234+
}
235+
}
236+
237+
private void parseJava(final String filename, final String line, final Collection<Integer> errorLines) {
238+
int colon = line.indexOf(filename);
239+
if (colon <= 0)
240+
return;
241+
// System.out.println("Parsing candidate: " + line);
242+
colon += filename.length();
243+
final int next = line.indexOf(':', colon + 1);
244+
if (next < colon + 2)
245+
return;
246+
try {
247+
final int lineNumber = Integer.parseInt(line.substring(colon + 1, next));
248+
if (lineNumber > 0)
249+
errorLines.add(lineNumber - 1 + lineOffset); // store 0-based indices
250+
// System.out.println("line Np: " + (lineNumber - 1));
251+
} catch (final NumberFormatException e) {
252+
// ignore
253+
}
254+
}
255+
256+
private void abort(final String msg) {
257+
if (writer != null)
258+
writer.textArea.insert("[WARNING] " + msg + "\n", lengthOfJTextAreaWriter);
259+
errorLines = null;
260+
}
261+
262+
/**
263+
* This is just so that we can register errorLines in the Editor's
264+
* {@link org.fife.ui.rsyntaxtextarea.ErrorStrip}
265+
*/
266+
class ErrorStripNotifyingParser extends AbstractParser {
267+
268+
private final DefaultParseResult result;
269+
private final boolean highlightAbnoxiously = true;
270+
271+
public ErrorStripNotifyingParser() {
272+
result = new DefaultParseResult(this);
273+
}
274+
275+
@Override
276+
public boolean isEnabled() {
277+
return enabled && errorLines != null;
278+
}
279+
280+
@Override
281+
public ParseResult parse(final RSyntaxDocument doc, final String style) {
282+
final Element root = doc.getDefaultRootElement();
283+
final int lineCount = root.getElementCount();
284+
result.clearNotices();
285+
result.setParsedLines(0, lineCount - 1);
286+
if (isEnabled() && !SyntaxConstants.SYNTAX_STYLE_NONE.equals(style)) {
287+
errorLines.forEach(line -> {
288+
result.addNotice(new ErrorNotice(this, line));
289+
if (highlightAbnoxiously) {
290+
try {
291+
editorPane.addLineHighlight(line, COLOR);
292+
} catch (final BadLocationException ignored) {
293+
// do nothing
294+
}
295+
}
296+
});
297+
}
298+
return result;
299+
300+
}
301+
302+
}
303+
304+
class ErrorNotice extends DefaultParserNotice {
305+
public ErrorNotice(final Parser parser, final int line) {
306+
super(parser, "Run Error: Line " + (line + 1), line);
307+
setColor(COLOR);
308+
setLevel(Level.ERROR);
309+
setShowInEditor(true);
310+
}
311+
312+
}
313+
314+
}

0 commit comments

Comments
 (0)
0