Skip to content

Commit b1bc566

Browse files
committed
Statically add an AutoCompletionListener to the Script Editor.
1 parent c44281c commit b1bc566

1 file changed

Lines changed: 76 additions & 0 deletions

File tree

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
package sc.fiji.jython.autocompletion;
2+
3+
import java.util.Collections;
4+
import java.util.List;
5+
import java.util.regex.Matcher;
6+
import java.util.regex.Pattern;
7+
import java.util.stream.Collectors;
8+
9+
import org.fife.ui.autocomplete.BasicCompletion;
10+
import org.fife.ui.autocomplete.Completion;
11+
import org.fife.ui.autocomplete.CompletionProvider;
12+
import org.scijava.ui.swing.script.autocompletion.AutoCompletionListener;
13+
import org.scijava.ui.swing.script.autocompletion.JythonAutocompletionProvider;
14+
15+
public class JythonAutoCompletions implements AutoCompletionListener
16+
{
17+
static {
18+
JythonAutocompletionProvider.addAutoCompletionListener(new JythonAutoCompletions());
19+
}
20+
21+
static private final Pattern assign = Pattern.compile("^([ \\t]*)(([a-zA-Z_][a-zA-Z0-9_ \\t,]*)[ \\t]+=[ \\t]+(.*))$"),
22+
nameToken = Pattern.compile("^(.*?[ \\t]+|)([a-zA-Z_][a-zA-Z0-9_]+)$"),
23+
dotNameToken = Pattern.compile("^(.*?[ \\t]+|)([a-zA-Z0-9_\\.\\[\\](){}]+)\\.([a-zA-Z0-9_]*)$");
24+
25+
@Override
26+
public List<Completion> completionsFor(final CompletionProvider provider, final String codeWithoutLastLine, final String lastLine) {
27+
// Query the lastLine to find out what needs autocompletion
28+
29+
// Preconditions: can't expand when ending with any of: "()[]{},; "
30+
final char lastChar = lastLine.charAt(lastLine.length() -1);
31+
if (0 == lastLine.length() || "()[]{},; ".indexOf(lastChar) > -1)
32+
return Collections.emptyList();
33+
34+
// Two situations to autocomplete:
35+
// 1) a plain name: delimited with space (or none) to the left, and without parentheses.
36+
// 2) a method or field: none or some text after a period.
37+
38+
final Matcher m1 = nameToken.matcher(lastLine);
39+
if (m1.find())
40+
return JythonScriptParser.parseAST(codeWithoutLastLine).getLast().findStartsWith(m1.group(2)).stream()
41+
.map(s -> new BasicCompletion(provider, lastLine + s.substring(m1.group(2).length())))
42+
.collect(Collectors.toList());
43+
44+
final Matcher m2 = dotNameToken.matcher(lastLine);
45+
if (m2.find()) {
46+
final String seed = m2.group(3); // can be empty
47+
// Expand fields and methods of previous class
48+
// Assume code is correct up to the dot
49+
// Python has multiple assignment: find out the class of the last left var
50+
final String code,
51+
varName;
52+
final Matcher m3 = assign.matcher(lastLine);
53+
if (m3.find()) {
54+
// An assignment, e.g. "ip1, ip2 = imp1.getProcessor(), imp2."
55+
final String[] assignment = lastLine.split("=");
56+
final String[] names = assignment[0].split(",");
57+
varName = names[names.length -1].trim();
58+
code = codeWithoutLastLine + lastLine.substring(0, lastLine.length() -1); // without the ending dot
59+
} else {
60+
// Not an assignment, i.e. "imp.getImage()."
61+
// Find first non-whitespace char
62+
int start = 0;
63+
while (Character.isWhitespace(lastLine.charAt(start++)));
64+
varName = "____GRAB____"; // an injected var to capture the class
65+
code = codeWithoutLastLine + lastLine.substring(0, start) + varName + " = " + lastLine.substring(start);
66+
}
67+
final DotAutocompletions da = JythonScriptParser.parseAST(code).getLast().find(varName, DotAutocompletions.EMPTY);
68+
return da.get().stream()
69+
.filter(s -> s.startsWith(seed))
70+
.map(s -> new BasicCompletion(provider, lastLine + s, da.getClassname(), "From class " + da.getClassname()))
71+
.collect(Collectors.toList());
72+
}
73+
74+
return Collections.emptyList();
75+
}
76+
}

0 commit comments

Comments
 (0)