Skip to content

Commit 161ea84

Browse files
committed
Extend error tracking to more languages and make console searchable
This should cover all but IJM (which anyway is not really supported) and 'R'. Awkwardly, traces from the renjin engine contain little detail so there is not much we can do there. 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 161ea84

File tree

4 files changed

+200
-48
lines changed

4 files changed

+200
-48
lines changed

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

Lines changed: 102 additions & 33 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,6 +170,11 @@ 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) {
@@ -179,13 +185,16 @@ private void parse(final String errorLog) {
179185
abort("Code selection unknown: Erros are not highlighted in the Editor");
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+
189198
errorLines = new TreeSet<>();
190199
final StringTokenizer tokenizer = new StringTokenizer(errorLog, "\n");
191200
if (isJava) {
@@ -206,34 +215,65 @@ private void parse(final String errorLog) {
206215
}
207216

208217
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) {
218+
219+
if ( // Elimination of some false positives. TODO: Make this Regex
220+
lineText.indexOf(":classloader:") > -1 // ruby
221+
|| lineText.indexOf(".org.python.") > -1 // python
222+
|| lineText.indexOf(".codehaus.groovy.") > -1 // groovy
223+
|| lineText.indexOf(".tools.nsc.") > -1 // scala
224+
|| lineText.indexOf("at bsh.") > -1 // beanshel
225+
|| lineText.indexOf("$Recompilation$") > -1 // javascript
226+
) {//
214227
return;
215228
}
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-
}
229+
230+
final int extensionIdx = extensionIdx(lineText);
231+
final int lineIdx = lineText.toLowerCase().indexOf("line");
232+
if (lineIdx < 0 && extensionIdx < 0 && filenameIdx(lineText) < 0)
233+
return;
234+
235+
extractLineIndicesFromFilteredTextLines(lineText, errorLines);
236+
}
237+
238+
private void extractLineIndicesFromFilteredTextLines(final String lineText, final Collection<Integer> errorLines) {
239+
// System.out.println("Section being matched: " + lineText);
240+
final Pattern pattern = Pattern.compile(":(\\d+)|line\\D*(\\d+)", Pattern.CASE_INSENSITIVE);
241+
final Matcher matcher = pattern.matcher(lineText);
242+
243+
if (matcher.find()) {
244+
try {
245+
final String firstGroup = matcher.group(1);
246+
final String lastGroup = matcher.group(matcher.groupCount());
247+
final String group = (firstGroup == null) ? lastGroup : firstGroup;
248+
// System.out.println("firstGroup: " + firstGroup);
249+
// System.out.println("lastGroup: " + lastGroup);
250+
251+
final int lineNumber = Integer.valueOf(group.trim());
252+
if (lineNumber > 0)
253+
errorLines.add(lineNumber - 1 + lineOffset); // store 0-based indices
254+
} catch (final NumberFormatException e) {
255+
e.printStackTrace();
233256
}
234257
}
235258
}
236259

260+
private int extensionIdx(final String line) {
261+
int dotIndex = -1;
262+
for (final String extension : editorPane.getCurrentLanguage().getExtensions()) {
263+
dotIndex = line.indexOf("." + extension);
264+
if (dotIndex > -1)
265+
return dotIndex;
266+
}
267+
return -1;
268+
}
269+
270+
private int filenameIdx(final String line) {
271+
int index = line.indexOf(editorPane.getFileName());
272+
if (index == -1)
273+
index = (line.indexOf(" Script")); // unsaved file, etc.
274+
return index;
275+
}
276+
237277
private void parseJava(final String filename, final String line, final Collection<Integer> errorLines) {
238278
int colon = line.indexOf(filename);
239279
if (colon <= 0)
@@ -256,7 +296,7 @@ private void parseJava(final String filename, final String line, final Collectio
256296
private void abort(final String msg) {
257297
if (writer != null) {
258298
String finalMsg = "[WARNING] " + msg + "\n";
259-
finalMsg += "[WARNING] Error line(s) below may not match line numbers in the editor\n";
299+
finalMsg += "[WARNING] Error line(s) below may not match line numbers in the editor\n";
260300
writer.textArea.insert(finalMsg, lengthOfJTextAreaWriter);
261301
}
262302
errorLines = null;
@@ -289,21 +329,34 @@ public ParseResult parse(final RSyntaxDocument doc, final String style) {
289329
if (isEnabled() && !SyntaxConstants.SYNTAX_STYLE_NONE.equals(style)) {
290330
errorLines.forEach(line -> {
291331
result.addNotice(new ErrorNotice(this, line));
292-
if (highlightAbnoxiously) {
332+
});
333+
if (highlightAbnoxiously) {
334+
final Color c = highlightColor();
335+
errorLines.forEach(line -> {
293336
try {
294-
editorPane.addLineHighlight(line, COLOR);
337+
editorPane.addLineHighlight(line, c);
295338
} catch (final BadLocationException ignored) {
296339
// do nothing
297340
}
298-
}
299-
});
341+
});
342+
}
300343
}
301344
return result;
302345

303346
}
304347

305348
}
306349

350+
private Color highlightColor() {
351+
// https://stackoverflow.com/a/29576746
352+
final Color c1 = editorPane.getCurrentLineHighlightColor();
353+
final Color c2 = (editorPane.getBackground() == null) ? COLOR : editorPane.getBackground();
354+
final int r = (int) Math.sqrt( (Math.pow(c1.getRed(), 2) + Math.pow(c2.getRed(), 2)) / 2);
355+
final int g = (int) Math.sqrt( (Math.pow(c1.getGreen(), 2) + Math.pow(c2.getGreen(), 2)) / 2);
356+
final int b = (int) Math.sqrt( (Math.pow(c1.getBlue(), 2) + Math.pow(c2.getGreen(), 2)) / 2);
357+
return new Color(r, g, b, c1.getAlpha());
358+
}
359+
307360
class ErrorNotice extends DefaultParserNotice {
308361
public ErrorNotice(final Parser parser, final int line) {
309362
super(parser, "Run Error: Line " + (line + 1), line);
@@ -314,4 +367,20 @@ public ErrorNotice(final Parser parser, final int line) {
314367

315368
}
316369

370+
public static void main(final String[] args) throws Exception {
371+
// poor man's test for REGEX filtering
372+
final String groovy = " at Script1.run(Script1.groovy:51)";
373+
final String python = "File \"New_.py\", line 51, in <module>";
374+
final String ruby = "<main> at Batch_Convert.rb:51";
375+
final String scala = " at line number 51 at column number 18";
376+
final String beanshell = "or class name: Systesm : at Line: 51 : in file: ";
377+
final String javascript = " at jdk.nashorn.internal.scripts.Script$15$Greeting.:program(Greeting.js:51)";
378+
Arrays.asList(groovy, python, ruby, scala, beanshell, javascript).forEach(lang -> {
379+
final ErrorParser parser = new ErrorParser(new EditorPane());
380+
final TreeSet<Integer> errorLines = new TreeSet<>();
381+
parser.extractLineIndicesFromFilteredTextLines(lang, errorLines);
382+
assert (errorLines.first() == 50);
383+
System.out.println((errorLines.first() == 50) + ": <<" + lang + ">> ");
384+
});
385+
}
317386
}

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)