Skip to content

Commit 156d16b

Browse files
committed
New class AutocompletionProvider to provide completions for class, package
and fields and methods of imported classes.
1 parent e1b1f12 commit 156d16b

File tree

1 file changed

+161
-0
lines changed

1 file changed

+161
-0
lines changed
Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
package org.scijava.ui.swing.script;
2+
3+
import java.lang.reflect.Field;
4+
import java.lang.reflect.Method;
5+
import java.lang.reflect.Modifier;
6+
import java.util.ArrayList;
7+
import java.util.Collections;
8+
import java.util.List;
9+
import java.util.regex.Matcher;
10+
import java.util.regex.Pattern;
11+
import java.util.stream.Collectors;
12+
import java.util.stream.Stream;
13+
14+
import javax.swing.text.JTextComponent;
15+
16+
import org.fife.ui.autocomplete.BasicCompletion;
17+
import org.fife.ui.autocomplete.Completion;
18+
import org.fife.ui.autocomplete.DefaultCompletionProvider;
19+
import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea;
20+
21+
public class AutocompletionProvider extends DefaultCompletionProvider {
22+
23+
private final RSyntaxTextArea text_area;
24+
25+
public AutocompletionProvider(final RSyntaxTextArea text_area) {
26+
this.text_area = text_area;
27+
new Thread(new Runnable() {
28+
@Override
29+
public void run() {
30+
ClassUtil.ensureCache();
31+
}
32+
}).start();
33+
}
34+
35+
/**
36+
* Override parent implementation to allow letters, digits, the period and a space, to be able to match e.g.:
37+
*
38+
* "from "
39+
* "from ij"
40+
* "from ij.Im"
41+
* etc.
42+
*
43+
* @param c
44+
*/
45+
@Override
46+
public boolean isValidChar(final char c) {
47+
return Character.isLetterOrDigit(c) || '.' == c || ' ' == c;
48+
}
49+
50+
static private final Pattern
51+
fromImport = Pattern.compile("^(from[ \\t]+)([a-zA-Z][a-zA-Z0-9._]*)$"),
52+
fastImport = Pattern.compile("^(from[ \\t]+)([a-zA-Z][a-zA-Z0-9._]*)[ \\t]+$"),
53+
importStatement = Pattern.compile("^((from[ \\t]+([a-zA-Z0-9._]+)[ \\t]+|[ \\t]*)import(Class\\(|[ \\t]+))([a-zA-Z0-9_., \\t]*)$"),
54+
simpleClassName = Pattern.compile("^(.*[ \\t]+|)([A-Z_][a-zA-Z0-9_]+)$"),
55+
staticMethodOrField = Pattern.compile("^((.*[ \\t]+|)([A-Z_][a-zA-Z0-9_]*)\\.)([a-zA-Z0-9_]*)$");
56+
57+
private final List<Completion> asCompletionList(final Stream<String> stream, final String pre) {
58+
return stream
59+
.map((s) -> new BasicCompletion(AutocompletionProvider.this, pre + s))
60+
.collect(Collectors.toList());
61+
}
62+
63+
@Override
64+
public List<Completion> getCompletionsImpl(final JTextComponent comp) {
65+
// don't block
66+
if (!ClassUtil.isCacheReady()) return Collections.emptyList();
67+
68+
final String text = this.getAlreadyEnteredText(comp);
69+
70+
// E.g. "from ij" to expand to a package name like ij or ij.gui or ij.plugin
71+
final Matcher m1 = fromImport.matcher(text);
72+
if (m1.find())
73+
return asCompletionList(ClassUtil.findPackageNamesStartingWith(m1.group(2)), m1.group(1));
74+
75+
final Matcher m1f = fastImport.matcher(text);
76+
if (m1f.find())
77+
return asCompletionList(ClassUtil.findClassNamesForPackage(m1f.group(2)).map(s -> s.substring(m1f.group(2).length() + 1)),
78+
m1f.group(0) + "import ");
79+
80+
// E.g. "from ij.gui import Roi, Po" to expand to PolygonRoi, PointRoi for Jython
81+
// or e.g. "importClass(Package.ij" to expand to a fully qualified class name for Javascript
82+
final Matcher m2 = importStatement.matcher(text);
83+
if (m2.find()) {
84+
String packageName = m2.group(3),
85+
className = m2.group(5); // incomplete or empty, or multiple separated by commas with the last one incomplete or empty
86+
87+
System.out.println("m2 matches className: " + className);
88+
final String[] bycomma = className.split(",");
89+
String precomma = "";
90+
if (bycomma.length > 1) {
91+
className = bycomma[bycomma.length -1].trim(); // last one
92+
for (int i=0; i<bycomma.length -1; ++i)
93+
precomma += bycomma[0] + ", ";
94+
}
95+
Stream<String> stream;
96+
if (className.length() > 0)
97+
stream = ClassUtil.findClassNamesStartingWith(null == packageName ? className : packageName + "." + className);
98+
else
99+
stream = ClassUtil.findClassNamesForPackage(packageName);
100+
if (!m2.group(4).equals("Class(Package"))
101+
stream = stream.map((s) -> s.substring(Math.max(0, s.lastIndexOf('.') + 1))); // simple class name for Jython
102+
return asCompletionList(stream, m2.group(1) + precomma);
103+
}
104+
105+
final Matcher m3 = simpleClassName.matcher(text);
106+
if (m3.find())
107+
return asCompletionList(ClassUtil.findSimpleClassNamesStartingWith(m3.group(2)).stream(), m3.group(1));
108+
109+
final Matcher m4 = staticMethodOrField.matcher(text);
110+
if (m4.find()) {
111+
try {
112+
final String simpleClassName = m4.group(3), // expected complete, e.g. ImagePlus
113+
methodOrFieldSeed = m4.group(4).toLowerCase(); // incomplete: e.g. "GR", a string to search for in the class declared fields or methods
114+
// Scan the script, parse the imports, find first one matching
115+
String packageName = null;
116+
lines: for (final String line: text_area.getText().split("\n")) {
117+
System.out.println(line);
118+
final String[] comma = line.split(",");
119+
final Matcher m = importStatement.matcher(comma[0]);
120+
if (m.find()) {
121+
final String first = m.group(5);
122+
if (m.group(4).equals("Class(Package")) {
123+
// Javascript import
124+
final int lastdot = Math.max(0, first.lastIndexOf('.'));
125+
if (simpleClassName.equals(first.substring(lastdot + 1))) {
126+
packageName = first.substring(0, lastdot);
127+
break lines;
128+
}
129+
} else {
130+
// Jython import
131+
comma[0] = first;
132+
for (int i=0; i<comma.length; ++i)
133+
if (simpleClassName.equals(comma[i].trim())) {
134+
packageName = m.group(3);
135+
break lines;
136+
}
137+
}
138+
}
139+
}
140+
System.out.println("package name: " + packageName);
141+
if (null != packageName) {
142+
final Class<?> c = Class.forName(packageName + "." + simpleClassName);
143+
final ArrayList<String> matches = new ArrayList<>();
144+
for (final Field f: c.getFields()) {
145+
if (Modifier.isStatic(f.getModifiers()) && f.getName().toLowerCase().startsWith(methodOrFieldSeed))
146+
matches.add(f.getName());
147+
}
148+
for (final Method m: c.getMethods()) {
149+
if (Modifier.isStatic(m.getModifiers()) && m.getName().toLowerCase().startsWith(methodOrFieldSeed))
150+
matches.add(m.getName() + "(");
151+
}
152+
return asCompletionList(matches.stream(), m4.group(1));
153+
}
154+
} catch (Exception e) {
155+
e.printStackTrace();
156+
}
157+
}
158+
159+
return Collections.emptyList();
160+
}
161+
}

0 commit comments

Comments
 (0)