8000 Extend error tracking to more languages and make console searchable · scijava/script-editor@a2ae17a · GitHub
[go: up one dir, main page]

Skip to content

Commit a2ae17a

Browse files
committed
Extend error tracking to more languages and make console searchable
This should cover all languages except for IJM and R (awkwardly, traces from the renjin engine contain little details) This also adds commands to highlight, delete, and search contents of the output/error panel. While at it, make some effort to make highlighted colors work with any theme
1 parent 676b370 commit a2ae17a

File tree

4 files changed

+228
-52
lines changed

4 files changed

+228
-52
lines changed

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

Lines changed: 114 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
import java.awt.Rectangle;
3434
import java.io.PrintWriter;
3535
import java.io.StringWriter;
36+
import java.util.Arrays;
3637
import java.util.Collection;
3738
import java.util.StringTokenizer;
3839
import java.util.TreeSet;
@@ -54,8 +55,8 @@
5455

5556
public class ErrorParser {
5657

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);
58+
/* Color for ErrorStrip marks and fallback taint for line highlights */
59+
private static final Color COLOR = Color.RED;
5960
/* 0-base indices of Editor's lines that have errored */
6061
private TreeSet<Integer> errorLines;
6162
/* When running selected code errored lines map to Editor through this offset */
@@ -169,23 +170,34 @@ private void gotoLine(final int lineNumber) {
169170
}
170171

171172
private void parse(final String errorLog) {
173+
174+
final ScriptLanguage lang = editorPane.getCurrentLanguage();
175+
if (lang == null)
176+
return;
177+
172178
// Do nothing if disabled, or if only selected text was evaluated in the
173179
// script but we don't know where in the document such selection occurred
174180
if (!enabled) {
175-
abort("Execution errors are not highlighted when auto-imports are active");
181+
abort("Execution errors are not highlighted when auto-imports are active", true);
176182
return;
177183
}
178184
if (lineOffset == -1) {
179-
abort("Code selection unknown: Erros are not highlighted in the Editor");
185+
abort("Code selection unknown: Erros are not highlighted in the Editor", true);
180186
return;
181187
}
182-
final ScriptLanguage lang = editorPane.getCurrentLanguage();
183-
if (lang == null)
184-
return;
185-
final boolean isJava = lang.getLanguageName().equals("Java");
188+
189+
final boolean isJava = "Java".equals(lang.getLanguageName());
186190
final String fileName = editorPane.getFileName();
187191
if (isJava && fileName == null)
188192
return;
193+
194+
// HACK scala code seems to always be pre-pended by some 10 lines of code(!?).
195+
if ("Scala".equals(lang.getLanguageName()))
196+
lineOffset += 10;
197+
// HACK and R by one (!?)
198+
else if ("R".equals(lang.getLanguageName()))
199+
lineOffset += 1;
200+
189201
errorLines = new TreeSet<>();
190202
final StringTokenizer tokenizer = new StringTokenizer(errorLog, "\n");
191203
if (isJava) {
@@ -197,7 +209,9 @@ private void parse(final String errorLog) {
197209
parseNonJava(tokenizer.nextToken(), errorLines);
198210
}
199211
}
200-
if (!errorLines.isEmpty()) {
212+
if (errorLines.isEmpty() && "IJ1 Macro".equals(lang.getLanguageName())) {
213+
abort("Execution errors handled by the Macro Interpreter. Use the Interpreter's Debug option for error tracking", false);
214+
} else if (!errorLines.isEmpty()) {
201215
notifyingParser = new ErrorStripNotifyingParser();
202216
editorPane.addParser(notifyingParser);
203217
editorPane.forceReparsing(notifyingParser);
@@ -206,34 +220,66 @@ private void parse(final String errorLog) {
206220
}
207221

208222
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) {
223+
224+
if ( // Elimination of some false positives. TODO: Make this Regex
225+
lineText.indexOf(":classloader:") > -1 // ruby
226+
|| lineText.indexOf(".org.python.") > -1 // python
227+
|| lineText.indexOf(".codehaus.groovy.") > -1 // groovy
228+
|| lineText.indexOf(".tools.nsc.") > -1 // scala
229+
|| lineText.indexOf("at bsh.") > -1 // beanshel
230+
|| lineText.indexOf("$Recompilation$") > -1 // javascript
231+
|| lineText.indexOf("at org.renjin.") > -1 // R: The only thing useful in traces are Syntax errors!?
232+
) {//
214233
return;
215234
}
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-
}
235+
236+
final int extensionIdx = extensionIdx(lineText);
237+
final int lineIdx = lineText.toLowerCase().indexOf("line");
238+
if (lineIdx < 0 && extensionIdx < 0 && filenameIdx(lineText) < 0)
239+
return;
240+
241+
extractLineIndicesFromFilteredTextLines(lineText, errorLines);
242+
}
243+
244+
private void extractLineIndicesFromFilteredTextLines(final String lineText, final Collection<Integer> errorLines) {
245+
// System.out.println(" Section being matched: " + lineText);
246+
final Pattern pattern = Pattern.compile(":(\\d+)|line\\D*(\\d+)", Pattern.CASE_INSENSITIVE);
247+
final Matcher matcher = pattern.matcher(lineText);
248+
249+
if (matcher.find()) {
250+
try {
251+
final String firstGroup = matcher.group(1);
252+
final String lastGroup = matcher.group(matcher.groupCount());
253+
final String group = (firstGroup == null) ? lastGroup : firstGroup;
254+
// System.out.println(" firstGroup: " + firstGroup);
255+
// System.out.println(" lastGroup: " + lastGroup);
256+
257+
final int lineNumber = Integer.valueOf(group.trim());
258+
if (lineNumber > 0)
259+
errorLines.add(lineNumber - 1 + lineOffset); // store 0-based indices
260+
} catch (final NumberFormatException e) {
261+
e.printStackTrace();
233262
}
234263
}
235264
}
236265

266+
private int extensionIdx(final String line) {
267+
int dotIndex = -1;
268+
for (final String extension : editorPane.getCurrentLanguage().getExtensions()) {
269+
dotIndex = line.indexOf("." + extension);
270+
if (dotIndex > -1)
271+
return dotIndex;
272+
}
273+
return -1;
274+
}
275+
276+
private int filenameIdx(final String line) {
277+
int index = line.indexOf(editorPane.getFileName());
278+
if (index == -1)
279+
index = (line.indexOf(" Script")); // unsaved file, etc.
280+
return index;
281+
}
282+
237283
private void parseJava(final String filename, final String line, final Collection<Integer> errorLines) {
238284
int colon = line.indexOf(filename);
239285
if (colon <= 0)
@@ -253,10 +299,11 @@ private void parseJava(final String filename, final String line, final Collectio
253299
}
254300
}
255301

256-
private void abort(final String msg) {
302+
private void abort(final String msg, final boolean offsetNotice) {
257303
if (writer != null) {
258304
String finalMsg = "[WARNING] " + msg + "\n";
259-
finalMsg += "[WARNING] Error line(s) below may not match line numbers in the editor\n";
305+
if (offsetNotice)
306+
finalMsg += "[WARNING] Error line(s) below may not match line numbers in the editor\n";
260307
writer.textArea.insert(finalMsg, lengthOfJTextAreaWriter);
261308
}
262309
errorLines = null;
@@ -289,21 +336,34 @@ public ParseResult parse(final RSyntaxDocument doc, final String style) {
289336
if (isEnabled() && !SyntaxConstants.SYNTAX_STYLE_NONE.equals(style)) {
290337
errorLines.forEach(line -> {
291338
result.addNotice(new ErrorNotice(this, line));
292-
if (highlightAbnoxiously) {
339+
});
340+
if (highlightAbnoxiously) {
341+
final Color c = highlightColor();
342+
errorLines.forEach(line -> {
293343
try {
294-
editorPane.addLineHighlight(line, COLOR);
344+
editorPane.addLineHighlight(line, c);
295345
} catch (final BadLocationException ignored) {
296346
// do nothing
297347
}
298-
}
299-
});
348+
});
349+
}
300350
}
301351
return result;
302352

303353
}
304354

305355
}
306356

357+
private Color highlightColor() {
358+
// https://stackoverflow.com/a/29576746
359+
final Color c1 = editorPane.getCurrentLineHighlightColor();
360+
final Color c2 = (editorPane.getBackground() == null) ? COLOR : editorPane.getBackground();
361+
final int r = (int) Math.sqrt( (Math.pow(c1.getRed(), 2) + Math.pow(c2.getRed(), 2)) / 2);
362+
final int g = (int) Math.sqrt( (Math.pow(c1.getGreen(), 2) + Math.pow(c2.getGreen(), 2)) / 2);
363+
final int b = (int) Math.sqrt( (Math.pow(c1.getBlue(), 2) + Math.pow(c2.getGreen(), 2)) / 2);
364+
return new Color(r, g, b, c1.getAlpha());
365+
}
366+
307367
class ErrorNotice extends DefaultParserNotice {
308368
public ErrorNotice(final Parser parser, final int line) {
309369
super(parser, "Run Error: Line " + (line + 1), line);
@@ -314,4 +374,21 @@ public ErrorNotice(final Parser parser, final int line) {
314374

315375
}
316376

377+
public static void main(final String[] args) throws Exception {
378+
// poor man's test for REGEX filtering
379+
final String groovy = " at Script1.run(Script1.groovy:51)";
380+
final String python = "File \"New_.py\", line 51, in <module>";
381+
final String ruby = "<main> at Batch_Convert.rb:51";
382+
final String scala = " at line number 51 at column number 18";
383+
final String beanshell = "or class name: Dummy : at Line: 51 : in file: ";
384+
final String javascript = " at jdk.nashorn.internal.scripts.Script$15$Greeting.:program(Greeting.js:51)";
385+
final String r = "org.renjin.parser.ParseException: Syntax error at line 51 char 2: syntax error, unexpected ',', expecting '\\n' or ';'";
386+
Arrays.asList(groovy, python, ruby, scala, beanshell, javascript, r).forEach(lang -> {
387+
final ErrorParser parser = new ErrorParser(new EditorPane());
388+
final TreeSet<Integer> errorLines = new TreeSet<>();
389+
parser.extractLineIndicesFromFilteredTextLines(lang, errorLines);
390+
assert (errorLines.first() == 50);
391+
System.out.println((errorLines.first() == 50) + ": " + lang);
392+
});
393+
}
317394
}

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

Lines changed: 40 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
import javax.swing.JLabel;
4545
import javax.swing.JOptionPane;
4646
import javax.swing.JPanel;
47+
import javax.swing.JTextArea;
4748
import javax.swing.JTextField;
4849
import javax.swing.WindowConstants;
4950

@@ -63,6 +64,7 @@ public class FindAndReplaceDialog extends JDialog implements ActionListener {
6364
JLabel replaceLabel;
6465
JCheckBox matchCase, wholeWord, markAll, regex, forward;
6566
JButton findNext, replace, replaceAll, cancel;
67+
private boolean restrictToConsole;
6668

6769
public FindAndReplaceDialog(final TextEditor editor) {
6870
super(editor);
@@ -131,20 +133,32 @@ public void keyPressed(final KeyEvent e) {
131133
replaceField.addKeyListener(listener);
132134
}
133135

134-
protected RSyntaxTextArea getTextArea() {
135-
return textEditor.getTextArea();
136+
private JTextArea getSearchArea() {
137+
if (restrictToConsole) {
138+
if (textEditor.getTab().showingErrors)
139+
return textEditor.getErrorScreen();
140+
else
141+
return textEditor.getTab().getScreen();
142+
}
143+
return getTextArea();
144+
}
145+
146+
final public RSyntaxTextArea getTextArea() {
147+
return textEditor.getEditorPane();
136148
}
137149

138150
@Override
139151
public void show(final boolean replace) {
140-
setTitle(replace ? "Find/Replace" : "Find");
152+
if (replace && restrictToConsole)
153+
throw new IllegalArgumentException("replace is not compatible with restrictToConsole");
154+
updateTitle();
141155
replaceLabel.setEnabled(replace);
142156
replaceField.setEnabled(replace);
143157
replaceField.setBackground(replace ? searchField.getBackground()
144158
: getRootPane().getBackground());
145159
this.replace.setEnabled(replace);
146160
replaceAll.setEnabled(replace);
147-
161+
markAll.setEnabled(!restrictToConsole);
148162
searchField.selectAll();
149163
replaceField.selectAll();
150164
getRootPane().setDefaultButton(findNext);
@@ -210,9 +224,24 @@ public boolean searchOrReplace(final boolean replace) {
210224
return searchOrReplace(replace, forward.isSelected());
211225
}
212226

227+
public void setRestrictToConsole(final boolean restrict) {
228+
restrictToConsole = restrict;
229+
markAll.setEnabled(!restrict);
230+
updateTitle();
231+
}
232+
233+
private void updateTitle() {
234+
String title = "Find";
235+
if (isReplace())
236+
title +="/Replace";
237+
if (restrictToConsole)
238+
title += " in Console";
239+
setTitle(title);
240+
}
241+
213242
public boolean searchOrReplace(final boolean replace, final boolean forward) {
214243
if (searchOrReplaceFromHere(replace, forward)) return true;
215-
final RSyntaxTextArea textArea = getTextArea();
244+
final JTextArea textArea = getSearchArea();
216245
final int caret = textArea.getCaretPosition();
217246
textArea.setCaretPosition(forward ? 0 : textArea.getDocument().getLength());
218247
if (searchOrReplaceFromHere(replace, forward)) return true;
@@ -233,20 +262,22 @@ protected SearchContext getSearchContext(final boolean forward) {
233262
context.setMatchCase(matchCase.isSelected());
234263
context.setWholeWord(wholeWord.isSelected());
235264
context.setRegularExpression(regex.isSelected());
265+
context.setMarkAll(markAll.isSelected() && !restrictToConsole);
236266
return context;
237267
}
238268

239269
protected boolean searchOrReplaceFromHere(final boolean replace,
240270
final boolean forward)
241271
{
242-
final RSyntaxTextArea textArea = getTextArea();
243272
final SearchContext context = getSearchContext(forward);
244-
return (replace ? SearchEngine.replace(textArea, context) : SearchEngine
245-
.find(textArea, context)).wasFound();
273+
if (replace) {
274+
return SearchEngine.replace(getTextArea(), context).wasFound();
275+
}
276+
return SearchEngine.find(getSearchArea(), context).wasFound();
246277
}
247278

248279
public boolean isReplace() {
249-
return replace.isEnabled();
280+
return (restrictToConsole) ? false : replace.isEnabled();
250281
}
251282

252283
/**

0 commit comments

Comments
 (0)
0