Skip to content

Commit 11c54a8

Browse files
committed
New feature: expand class constructors, with parameter autocompletion.
1 parent 26138ad commit 11c54a8

1 file changed

Lines changed: 108 additions & 46 deletions

File tree

‎src/main/java/org/scijava/plugins/scripteditor/jython/JythonAutoCompletions.java‎

Lines changed: 108 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030

3131
import java.io.File;
3232
import java.io.IOException;
33+
import java.lang.reflect.Constructor;
3334
import java.lang.reflect.Parameter;
3435
import java.nio.file.FileVisitOption;
3536
import java.nio.file.Files;
@@ -38,6 +39,7 @@
3839
import java.util.Collections;
3940
import java.util.Comparator;
4041
import java.util.List;
42+
import java.util.Map;
4143
import java.util.function.Function;
4244
import java.util.jar.JarEntry;
4345
import java.util.jar.JarFile;
@@ -59,7 +61,8 @@
5961
public class JythonAutoCompletions {
6062

6163
static private final Pattern assign = Pattern.compile("^([ \\t]*)(([a-zA-Z_][a-zA-Z0-9_ \\t,]*)[ \\t]+=[ \\t]+(.*))$"),
62-
nameToken = Pattern.compile("^(.*?[ \\t]+|)([a-zA-Z_][a-zA-Z0-9_]+)$"),
64+
nameToken = Pattern.compile("^(.*?[ \\t,\\[=\\(]+)([a-zA-Z_][a-zA-Z0-9_]+)$"),
65+
invocation = Pattern.compile("^(.*?[ \\t]+|)([a-zA-Z_][a-zA-Z0-9_]+\\()$"),
6366
dotNameToken = Pattern.compile("^(.*?[ \\t]+|)([a-zA-Z0-9_\\.\\[\\](){}]+)\\.([a-zA-Z0-9_]*)$"),
6467
endingCode = Pattern.compile("^([ \\t]*)[^#]*?(.*?)[ \\t]*:[ \\t]*(#.*|)[\\n]*$"),
6568
sysPathAppend = Pattern.compile("sys.path.append[ \\t]*[(][ \\t]*['\"](.*?)['\"][ \\t]*[)]"), // fragile to line breaks in e.g. .append
@@ -113,9 +116,9 @@ public List<Completion> completionsFor(final JythonAutocompletionProvider provid
113116

114117
// Query the lastLine to find out what needs autocompletion
115118

116-
// Preconditions 1: can't expand when ending with any of: "()[]{},; "
119+
// Preconditions 1: can't expand when ending with any of: "[]{},; "
117120
final char lastChar = lastLine.charAt(lastLine.length() -1);
118-
if (0 == lastLine.length() || "()[]{},; ".indexOf(lastChar) > -1)
121+
if (0 == lastLine.length() || "[]{},; ".indexOf(lastChar) > -1)
119122
return Collections.emptyList();
120123

121124
// Preconditions 2: codeWithoutLastLine has to be valid
@@ -221,10 +224,57 @@ public List<Completion> completionsFor(final JythonAutocompletionProvider provid
221224
}
222225

223226
final Matcher m1 = nameToken.matcher(lastLine);
227+
/*
224228
if (m1.find())
225229
return JythonScriptParser.parseAST(codeWithoutLastLine).getLast().findStartsWith(m1.group(2)).stream()
226230
.map(s -> new BasicCompletion(provider, (lastLine + s.substring(m1.group(2).length())).substring(crop)))
227231
.collect(Collectors.toList());
232+
*/
233+
234+
if (m1.find()) {
235+
final Scope scope = JythonScriptParser.parseAST(codeWithoutLastLine).getLast();
236+
// Handle argument suggestions for constructors and functions
237+
provider.setParameterChoicesProvider(new CustomParameterChoicesProvider(provider, scope));
238+
final Map<String, String> names = scope.findStartsWith2(m1.group(2));
239+
final ArrayList<Completion> completions = new ArrayList<>();
240+
for (final Map.Entry<String, String> e : names.entrySet()) {
241+
final String classname = e.getValue();
242+
// Add a constructor parameterized completion
243+
if (null != classname) {
244+
try {
245+
final Class<?> c = Class.forName(classname);
246+
for (final Constructor<?> constructor : c.getConstructors()) {
247+
completions.add(makeDotCompletion(
248+
crop > -1 && crop < m1.group(1).length() ? m1.group(1).substring(crop) : "",
249+
m1.group(2), new CompletionText(e.getKey(), c, constructor), provider));
250+
}
251+
} catch (ClassNotFoundException cnfe) {
252+
JythonDev.printTrace("Can't load class: " + classname);
253+
}
254+
}
255+
// Add the simple classname completion
256+
completions.add(new BasicCompletion(provider, (lastLine + e.getKey().substring(m1.group(2).length())).substring(crop)));
257+
}
258+
return completions;
259+
}
260+
261+
JythonDev.printTrace("invocation.matcher:");
262+
final Matcher m1c = invocation.matcher(lastLine);
263+
if (m1c.find()) {
264+
final String name = m1c.group(2).substring(0, m1c.group(2).length() -1);
265+
JythonDev.printTrace(" name: " + name);
266+
final Scope scope = JythonScriptParser.parseAST(codeWithoutLastLine).getLast();
267+
final DotAutocompletions da = scope.find(name, DotAutocompletions.EMPTY);
268+
provider.setParameterChoicesProvider(new CustomParameterChoicesProvider(provider, scope));
269+
JythonDev.print(da);
270+
if (da instanceof ConstructorAutocompletions) {
271+
JythonDev.printTrace("da is a ConstructorAutocompletions");
272+
return da.get().stream()
273+
.map(ct -> makeDotCompletion("", name, ct, provider)) // TODO pre is wrong: see below for hints
274+
.collect(Collectors.toList());
275+
}
276+
// TODO and functions
277+
}
228278

229279
final Matcher m2 = dotNameToken.matcher(lastLine);
230280
if (m2.find()) {
@@ -264,49 +314,12 @@ public List<Completion> completionsFor(final JythonAutocompletionProvider provid
264314
final String lowerCaseSeed = seed.toLowerCase();
265315

266316
// Depends on scope
267-
provider.setParameterChoicesProvider(new ParameterChoicesProvider() {
268-
@Override
269-
public List<Completion> getParameterChoices(JTextComponent tc,
270-
org.fife.ui.autocomplete.ParameterizedCompletion.Parameter param) {
271-
final Object typeObj = param.getTypeObject();
272-
Class<?> clazz = null;
273-
if (null != typeObj && typeObj instanceof Class) {
274-
clazz = (Class<?>)typeObj;
275-
JythonDev.printTrace("class: " + clazz + ", " + clazz.getCanonicalName());
276-
}
277-
switch (param.getType()) {
278-
case "int": clazz = Integer.class; break;
279-
case "long": clazz = Long.class; break;
280-
case "float": clazz = Float.class; break;
281-
case "double": clazz = Double.class; break;
282-
case "boolean": clazz = Boolean.class; break;
283-
case "char": clazz = Character.class; break;
284-
case "short": clazz = Short.class; break;
285-
case "byte": clazz = Byte.class; break;
286-
}
287-
if (null != clazz) try {
288-
JythonDev.printTrace("type is: " + param.getType());
289-
JythonDev.printTrace("class is: " + clazz.getCanonicalName());
290-
final List<Completion> bc = scope
291-
.findVarsByType(param.getType(), clazz)
292-
.map(varName -> new BasicCompletion(provider, varName))
293-
.collect(Collectors.toList());
294-
for (int i=0; i<bc.size(); ++i) {
295-
((BasicCompletion)bc.get(i)).setRelevance(bc.size() - i); // ensure sorted from innermost scope to outer, rather than alphabetical
296-
}
297-
JythonDev.printTrace("found bc: " + bc.size());
298-
return bc;
299-
} catch (Exception e) {
300-
JythonDev.printError(e);
301-
}
302-
return Collections.emptyList();
303-
}
304-
});
317+
provider.setParameterChoicesProvider(new CustomParameterChoicesProvider(provider, scope));
305318

306319
List<Completion> list = da.get().stream()
307320
.filter(s -> s.getReplacementText().toLowerCase().contains(lowerCaseSeed))
308321
//.map(s -> s.getCompletion(provider, pre + s.getReplacementText(), s.getReplacementText().startsWith(seed) ? 1 : 0))
309-
.map(s -> makeDotCompletion(pre, lowerCaseSeed, scope, s, provider))
322+
.map(s -> makeDotCompletion(pre, lowerCaseSeed, s, provider))
310323
.collect(Collectors.toList());
311324
sortCompletions(list, seed);
312325
return list;
@@ -315,13 +328,15 @@ public List<Completion> getParameterChoices(JTextComponent tc,
315328
return Collections.emptyList();
316329
}
317330

318-
private static Completion makeDotCompletion(final String pre, final String seed, final Scope scope, final CompletionText ct, final CompletionProvider provider) {
319-
List<Parameter> ps = ct.getMethodArgs();
320-
if (null != ps && !ps.isEmpty()) {
331+
protected static Completion makeDotCompletion(final String pre, final String seed, final CompletionText ct, final CompletionProvider provider) {
332+
final List<Parameter> ps = ct.getMethodArgs();
333+
if (null != ps && null != ct.getReturnType()) { // ) && !ps.isEmpty()) {
321334
JythonDev.printTrace("using FunctionCompletion: ps is " + ps.stream().map(Parameter::toString).collect(Collectors.joining(", ", "[", "]")));
335+
String text = ct.getReplacementText();
336+
if (text.endsWith("()")) text = text.substring(0, text.length() -2); // remove trailing parentheses
322337
final FunctionCompletion fc = new CustomFunctionCompletion(
323338
provider,
324-
pre + ct.getReplacementText().substring(0, ct.getReplacementText().length() -2), // remove trailing parentheses
339+
pre + text,
325340
ct.getReturnType());
326341
fc.setReturnValueDescription(ct.getReturnType());
327342
fc.setShortDescription(ct.getSummary()); // CompletionText.getDescription returns null
@@ -338,6 +353,53 @@ private static Completion makeDotCompletion(final String pre, final String seed,
338353
return ct.getCompletion(provider, pre + ct.getReplacementText(), ct.getReplacementText().startsWith(seed) ? 1 : 0);
339354
}
340355

356+
private final class CustomParameterChoicesProvider implements ParameterChoicesProvider {
357+
private final JythonAutocompletionProvider provider;
358+
private final Scope scope;
359+
360+
private CustomParameterChoicesProvider(JythonAutocompletionProvider provider, Scope scope) {
361+
this.provider = provider;
362+
this.scope = scope;
363+
}
364+
365+
@Override
366+
public List<Completion> getParameterChoices(JTextComponent tc,
367+
org.fife.ui.autocomplete.ParameterizedCompletion.Parameter param) {
368+
final Object typeObj = param.getTypeObject();
369+
Class<?> clazz = null;
370+
if (null != typeObj && typeObj instanceof Class) {
371+
clazz = (Class<?>)typeObj;
372+
JythonDev.printTrace("class: " + clazz + ", " + clazz.getCanonicalName());
373+
}
374+
switch (param.getType()) {
375+
case "int": clazz = Integer.class; break;
376+
case "long": clazz = Long.class; break;
377+
case "float": clazz = Float.class; break;
378+
case "double": clazz = Double.class; break;
379+
case "boolean": clazz = Boolean.class; break;
380+
case "char": clazz = Character.class; break;
381+
case "short": clazz = Short.class; break;
382+
case "byte": clazz = Byte.class; break;
383+
}
384+
if (null != clazz) try {
385+
JythonDev.printTrace("type is: " + param.getType());
386+
JythonDev.printTrace("class is: " + clazz.getCanonicalName());
387+
final List<Completion> bc = scope
388+
.findVarsByType(param.getType(), clazz)
389+
.map(varName -> new BasicCompletion(provider, varName))
390+
.collect(Collectors.toList());
391+
for (int i=0; i<bc.size(); ++i) {
392+
((BasicCompletion)bc.get(i)).setRelevance(bc.size() - i); // ensure sorted from innermost scope to outer, rather than alphabetical
393+
}
394+
JythonDev.printTrace("found bc: " + bc.size());
395+
return bc;
396+
} catch (Exception e) {
397+
JythonDev.printError(e);
398+
}
399+
return Collections.emptyList();
400+
}
401+
}
402+
341403

342404
@SuppressWarnings("unused")
343405
private static String removeLastOptionalDot(final String s) {

0 commit comments

Comments
 (0)