Skip to content

Commit

Permalink
GroovyEngine: tab-completion for chained methods
Browse files Browse the repository at this point in the history
  • Loading branch information
mattirn committed Jun 15, 2020
1 parent 88cf67d commit 7d61914
Showing 1 changed file with 118 additions and 25 deletions.
143 changes: 118 additions & 25 deletions groovy/src/main/java/org/jline/script/GroovyEngine.java
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,11 @@ public enum Format {JSON, GROOVY, NONE};
private Map<String,String> imports = new HashMap<>();
private Map<String,String> methods = new HashMap<>();
private Map<String,Class<?>> nameClass = new HashMap<>();
private Cloner objectCloner = new ObjectCloner();

public interface Cloner {
Object clone(Object obj);
}

public GroovyEngine() {
this.sharedData = new Binding();
Expand Down Expand Up @@ -323,13 +328,21 @@ public Map<String,Object> toMap(Object obj) {
return Utils.toMap(obj);
}

public void setObjectCloner(Cloner objectCloner) {
this.objectCloner = objectCloner;
}

public Cloner getObjectCloner() {
return objectCloner;
}

private Completer compileCompleter() {
List<Completer> completers = new ArrayList<>();
completers.add(new ArgumentCompleter(new StringsCompleter("while", "class", "for", "print", "println"), NullCompleter.INSTANCE));
completers.add(new ArgumentCompleter(new StringsCompleter("def"), new StringsCompleter(methods::keySet), NullCompleter.INSTANCE));
completers.add(new ArgumentCompleter(new StringsCompleter("import")
, new PackageCompleter(CandidateType.PACKAGE), NullCompleter.INSTANCE));
completers.add(new MethodCompleter());
completers.add(new MethodCompleter(this));
return new AggregateCompleter(completers);
}

Expand Down Expand Up @@ -496,23 +509,37 @@ public void complete(LineReader reader, ParsedLine commandLine, List<Candidate>
}

private class MethodCompleter implements Completer {
private GroovyEngine groovyEngine;

public MethodCompleter(){}
public MethodCompleter(GroovyEngine engine){
this.groovyEngine = engine;
}

@Override
public void complete(LineReader reader, ParsedLine commandLine, List<Candidate> candidates) {
assert commandLine != null;
assert candidates != null;
String wordbuffer = commandLine.word();
String buffer = commandLine.line().substring(0, commandLine.cursor());
if (commandLine.wordIndex() > 0 && !commandLine.words().get(0).matches("(new|\\w+=new)")) {
Brackets brackets = new Brackets(buffer);
if (commandLine.wordIndex() > 0 && !commandLine.words().get(0).matches("(new|\\w+=new)") && brackets.numberOfRounds() == 0) {
return;
}
Brackets brackets = new Brackets(buffer);
if (brackets.openCurly() || brackets.openRound() || brackets.numberOfCurlies() > 0 || brackets.numberOfRounds() > 0) {
if (brackets.openCurly() || brackets.openRound() || brackets.numberOfCurlies() > 0) {
return;
}
if (commandLine.wordIndex() == 1 && commandLine.words().get(0).matches("(new|\\w+=new)")) {
if (brackets.numberOfRounds() > 0) {
int varsep = buffer.lastIndexOf('.');
int eqsep = buffer.indexOf('=');
if (varsep > 0 && varsep > eqsep) {
Class<?> clazz = evaluateClass(buffer.substring(eqsep + 1, varsep));
int vs = wordbuffer.lastIndexOf('.');
int es = wordbuffer.indexOf('=');
String curBuf = wordbuffer.substring(es + 1, vs + 1);
String hint = wordbuffer.substring(vs + 1);
doMethodCandidates(candidates, clazz, curBuf, hint);
}
} else if (commandLine.wordIndex() == 1 && commandLine.words().get(0).matches("(new|\\w+=new)")) {
if (wordbuffer.matches("[a-z]+.*")) {
int idx = wordbuffer.lastIndexOf('.');
if (idx > 0 && wordbuffer.substring(idx + 1).matches("[A-Z]+.*")) {
Expand Down Expand Up @@ -546,32 +573,22 @@ public void complete(LineReader reader, ParsedLine commandLine, List<Candidate>
String p = wordbuffer.substring(varsep + 1);
if (nameClass.containsKey(var)) {
if (firstMethod) {
Helpers.doCandidates(candidates
, Helpers.getStaticMethods(nameClass.get(var))
, curBuf, p, CandidateType.METHOD);
Helpers.doCandidates(candidates
, Helpers.getStaticFields(nameClass.get(var))
, curBuf, p, CandidateType.OTHER);
doStaticMethodCandidates(candidates, nameClass.get(var), curBuf, p);
} else {
Class<?> clazz = evaluateClass(wordbuffer.substring(0, varsep));
doMethodCandidates(candidates, clazz, curBuf, p);
}
} else if (hasVariable(var)) {
if (firstMethod) {
Helpers.doCandidates(candidates
, Helpers.getMethods(get(var).getClass())
, curBuf, p, CandidateType.METHOD);
Helpers.doCandidates(candidates
, Helpers.getFields(get(var).getClass())
, curBuf, p, CandidateType.OTHER);
doMethodCandidates(candidates, get(var).getClass(), curBuf, p);
} else {
Class<?> clazz = evaluateClass(wordbuffer.substring(0, varsep));
doMethodCandidates(candidates, clazz, curBuf, p);
}
} else {
try {
param = wordbuffer.substring(eqsep + 1, varsep);
Class<?> clazz = Class.forName(param);
Helpers.doCandidates(candidates
, Helpers.getStaticMethods(clazz)
, curBuf, p, CandidateType.METHOD);
Helpers.doCandidates(candidates
, Helpers.getStaticFields(clazz)
, curBuf, p, CandidateType.OTHER);
doStaticMethodCandidates(candidates, Class.forName(param), curBuf, p);
} catch (Exception e) {
param = wordbuffer.substring(eqsep + 1, varsep + 1);
Helpers.doCandidates(candidates
Expand All @@ -583,6 +600,26 @@ public void complete(LineReader reader, ParsedLine commandLine, List<Candidate>
}
}

private Class<?> evaluateClass(String objectStatement) {
return new Inspector(groovyEngine).evaluateClass(objectStatement);
}

private void doMethodCandidates(List<Candidate> candidates, Class<?> clazz, String curBuf, String hint) {
if (clazz == null) {
return;
}
Helpers.doCandidates(candidates, Helpers.getMethods(clazz), curBuf, hint, CandidateType.METHOD);
Helpers.doCandidates(candidates, Helpers.getFields(clazz), curBuf, hint, CandidateType.OTHER);
}

private void doStaticMethodCandidates(List<Candidate> candidates, Class<?> clazz, String curBuf, String hint) {
if (clazz == null) {
return;
}
Helpers.doCandidates(candidates, Helpers.getStaticMethods(clazz), curBuf, hint, CandidateType.METHOD);
Helpers.doCandidates(candidates, Helpers.getStaticFields(clazz), curBuf, hint, CandidateType.OTHER);
}

private Set<String> retrieveConstructors() {
Set<String> out = new HashSet<>();
for (Map.Entry<String, Class<?>> entry : nameClass.entrySet()) {
Expand All @@ -608,6 +645,62 @@ private Set<String> retrieveClassesWithStaticMethods() {
}
}

private static class Inspector {
private GroovyShell shell;
protected Binding sharedData = new Binding();
private Map<String,String> imports = new HashMap<>();

public Inspector(GroovyEngine groovyEngine) {
imports.putAll(groovyEngine.imports);
for (Map.Entry<String, Object> entry : groovyEngine.find().entrySet()) {
Object obj = entry.getValue() instanceof Closure ? entry.getValue()
: groovyEngine.getObjectCloner().clone(entry.getValue());
sharedData.setVariable(entry.getKey(), obj);
}
shell = new GroovyShell(sharedData);
}

public Class<?> evaluateClass(String objectStatement) {
try {
return execute(objectStatement).getClass();
} catch (Exception e) {

}
return null;
}

private Object execute(String statement) {
String e = "";
for (Map.Entry<String, String> entry : imports.entrySet()) {
e += entry.getValue()+"\n";
}
e += statement;
return shell.evaluate(e);
}
}

public static class ObjectCloner implements Cloner {

public ObjectCloner() {

}

/**
* Shallow copy of the object using java Cloneable clone() method.
*/
public Object clone(Object obj) {
Object out = null;
try {
Class<?> clazz = obj.getClass();
Method clone = clazz.getDeclaredMethod("clone");
out = clone.invoke(obj);
} catch (Exception e) {
out = obj;
}
return out;
}
}

private static class Brackets {
char[] quote = {'"', '\''};
int quoteId = -1;
Expand Down

0 comments on commit 7d61914

Please sign in to comment.