mirror of
https://github.com/openjdk/jdk.git
synced 2026-02-23 16:55:09 +00:00
8133652: Implement tab-completion for member select expressions
Reviewed-by: jlaskey, attila
This commit is contained in:
parent
67e6d1bad0
commit
a45bb1ba66
@ -36,6 +36,7 @@ import java.util.List;
|
||||
import java.util.prefs.BackingStoreException;
|
||||
import java.util.prefs.Preferences;
|
||||
import jdk.internal.jline.console.ConsoleReader;
|
||||
import jdk.internal.jline.console.completer.Completer;
|
||||
import jdk.internal.jline.console.history.History.Entry;
|
||||
import jdk.internal.jline.console.history.MemoryHistory;
|
||||
|
||||
@ -43,15 +44,18 @@ class Console implements AutoCloseable {
|
||||
private final ConsoleReader in;
|
||||
private final PersistentHistory history;
|
||||
|
||||
Console(InputStream cmdin, PrintStream cmdout, Preferences prefs) throws IOException {
|
||||
Console(final InputStream cmdin, final PrintStream cmdout, final Preferences prefs,
|
||||
final Completer completer) throws IOException {
|
||||
in = new ConsoleReader(cmdin, cmdout);
|
||||
in.setExpandEvents(false);
|
||||
in.setHandleUserInterrupt(true);
|
||||
in.setBellEnabled(true);
|
||||
in.setHistory(history = new PersistentHistory(prefs));
|
||||
in.addCompleter(completer);
|
||||
Runtime.getRuntime().addShutdownHook(new Thread(()->close()));
|
||||
}
|
||||
|
||||
String readLine(String prompt) throws IOException {
|
||||
String readLine(final String prompt) throws IOException {
|
||||
return in.readLine(prompt);
|
||||
}
|
||||
|
||||
@ -65,7 +69,7 @@ class Console implements AutoCloseable {
|
||||
|
||||
private final Preferences prefs;
|
||||
|
||||
protected PersistentHistory(Preferences prefs) {
|
||||
protected PersistentHistory(final Preferences prefs) {
|
||||
this.prefs = prefs;
|
||||
load();
|
||||
}
|
||||
@ -74,7 +78,7 @@ class Console implements AutoCloseable {
|
||||
|
||||
public final void load() {
|
||||
try {
|
||||
List<String> keys = new ArrayList<>(Arrays.asList(prefs.keys()));
|
||||
final List<String> keys = new ArrayList<>(Arrays.asList(prefs.keys()));
|
||||
Collections.sort(keys);
|
||||
for (String key : keys) {
|
||||
if (!key.startsWith(HISTORY_LINE_PREFIX))
|
||||
|
||||
@ -31,14 +31,32 @@ import java.io.InputStreamReader;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.io.PrintWriter;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.prefs.Preferences;
|
||||
import jdk.nashorn.api.tree.AssignmentTree;
|
||||
import jdk.nashorn.api.tree.BinaryTree;
|
||||
import jdk.nashorn.api.tree.CompilationUnitTree;
|
||||
import jdk.nashorn.api.tree.CompoundAssignmentTree;
|
||||
import jdk.nashorn.api.tree.ConditionalExpressionTree;
|
||||
import jdk.nashorn.api.tree.ExpressionTree;
|
||||
import jdk.nashorn.api.tree.ExpressionStatementTree;
|
||||
import jdk.nashorn.api.tree.InstanceOfTree;
|
||||
import jdk.nashorn.api.tree.MemberSelectTree;
|
||||
import jdk.nashorn.api.tree.SimpleTreeVisitorES5_1;
|
||||
import jdk.nashorn.api.tree.Tree;
|
||||
import jdk.nashorn.api.tree.UnaryTree;
|
||||
import jdk.nashorn.api.tree.Parser;
|
||||
import jdk.nashorn.api.scripting.NashornException;
|
||||
import jdk.nashorn.internal.objects.Global;
|
||||
import jdk.nashorn.internal.runtime.Context;
|
||||
import jdk.nashorn.internal.runtime.ErrorManager;
|
||||
import jdk.nashorn.internal.runtime.JSType;
|
||||
import jdk.nashorn.internal.runtime.ScriptEnvironment;
|
||||
import jdk.nashorn.internal.runtime.ScriptObject;
|
||||
import jdk.nashorn.internal.runtime.ScriptRuntime;
|
||||
import jdk.nashorn.tools.Shell;
|
||||
import jdk.internal.jline.console.completer.Completer;
|
||||
import jdk.internal.jline.console.UserInterruptException;
|
||||
|
||||
/**
|
||||
@ -96,8 +114,72 @@ public final class Main extends Shell {
|
||||
final PrintWriter err = context.getErr();
|
||||
final Global oldGlobal = Context.getGlobal();
|
||||
final boolean globalChanged = (oldGlobal != global);
|
||||
final Parser parser = Parser.create();
|
||||
|
||||
try (final Console in = new Console(System.in, System.out, PREFS)) {
|
||||
// simple source "tab completer" for nashorn
|
||||
final Completer completer = new Completer() {
|
||||
@Override
|
||||
public int complete(final String test, final int cursor, final List<CharSequence> result) {
|
||||
// check that cursor is at the end of test string. Do not complete in the middle!
|
||||
if (cursor != test.length()) {
|
||||
return cursor;
|
||||
}
|
||||
|
||||
// if it has a ".", then assume it is a member selection expression
|
||||
final int idx = test.lastIndexOf('.');
|
||||
if (idx == -1) {
|
||||
return cursor;
|
||||
}
|
||||
|
||||
// stuff before the last "."
|
||||
final String exprBeforeDot = test.substring(0, idx);
|
||||
|
||||
// Make sure that completed code will have a member expression! Adding ".x" as a
|
||||
// random property/field name selected to make it possible to be a proper member select
|
||||
final ExpressionTree topExpr = getTopLevelExpression(parser, exprBeforeDot + ".x");
|
||||
if (topExpr == null) {
|
||||
// did not parse to be a top level expression, no suggestions!
|
||||
return cursor;
|
||||
}
|
||||
|
||||
|
||||
// Find 'right most' member select expression's start position
|
||||
final int startPosition = (int) getStartOfMemberSelect(topExpr);
|
||||
if (startPosition == -1) {
|
||||
// not a member expression that we can handle for completion
|
||||
return cursor;
|
||||
}
|
||||
|
||||
// The part of the right most member select expression before the "."
|
||||
final String objExpr = test.substring(startPosition, idx);
|
||||
|
||||
// try to evaluate the object expression part as a script
|
||||
Object obj = null;
|
||||
try {
|
||||
obj = context.eval(global, objExpr, global, "<suggestions>");
|
||||
} catch (Exception ignored) {
|
||||
// throw the exception - this is during tab-completion
|
||||
}
|
||||
|
||||
if (obj != null && obj != ScriptRuntime.UNDEFINED) {
|
||||
// where is the last dot? Is there a partial property name specified?
|
||||
final String prefix = test.substring(idx + 1);
|
||||
if (prefix.isEmpty()) {
|
||||
// no user specified "prefix". List all properties of the object
|
||||
result.addAll(PropertiesHelper.getProperties(obj));
|
||||
return cursor;
|
||||
} else {
|
||||
// list of properties matching the user specified prefix
|
||||
result.addAll(PropertiesHelper.getProperties(obj, prefix));
|
||||
return idx + 1;
|
||||
}
|
||||
}
|
||||
|
||||
return cursor;
|
||||
}
|
||||
};
|
||||
|
||||
try (final Console in = new Console(System.in, System.out, PREFS, completer)) {
|
||||
if (globalChanged) {
|
||||
Context.setGlobal(global);
|
||||
}
|
||||
@ -147,4 +229,66 @@ public final class Main extends Shell {
|
||||
|
||||
return SUCCESS;
|
||||
}
|
||||
|
||||
// returns ExpressionTree if the given code parses to a top level expression.
|
||||
// Or else returns null.
|
||||
private ExpressionTree getTopLevelExpression(final Parser parser, final String code) {
|
||||
try {
|
||||
final CompilationUnitTree cut = parser.parse("<code>", code, null);
|
||||
final List<? extends Tree> stats = cut.getSourceElements();
|
||||
if (stats.size() == 1) {
|
||||
final Tree stat = stats.get(0);
|
||||
if (stat instanceof ExpressionStatementTree) {
|
||||
return ((ExpressionStatementTree)stat).getExpression();
|
||||
}
|
||||
}
|
||||
} catch (final NashornException ignored) {
|
||||
// ignore any parser error. This is for completion anyway!
|
||||
// And user will get that error later when the expression is evaluated.
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
private long getStartOfMemberSelect(final ExpressionTree expr) {
|
||||
if (expr instanceof MemberSelectTree) {
|
||||
return ((MemberSelectTree)expr).getStartPosition();
|
||||
}
|
||||
|
||||
final Tree rightMostExpr = expr.accept(new SimpleTreeVisitorES5_1<Tree, Void>() {
|
||||
@Override
|
||||
public Tree visitAssignment(final AssignmentTree at, final Void v) {
|
||||
return at.getExpression();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Tree visitCompoundAssignment(final CompoundAssignmentTree cat, final Void v) {
|
||||
return cat.getExpression();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Tree visitConditionalExpression(final ConditionalExpressionTree cet, final Void v) {
|
||||
return cet.getFalseExpression();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Tree visitBinary(final BinaryTree bt, final Void v) {
|
||||
return bt.getRightOperand();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Tree visitInstanceOf(final InstanceOfTree it, final Void v) {
|
||||
return it.getType();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Tree visitUnary(final UnaryTree ut, final Void v) {
|
||||
return ut.getExpression();
|
||||
}
|
||||
}, null);
|
||||
|
||||
return (rightMostExpr instanceof MemberSelectTree)?
|
||||
rightMostExpr.getStartPosition() : -1L;
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,104 @@
|
||||
/*
|
||||
* Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
package jdk.nashorn.tools.jjs;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.WeakHashMap;
|
||||
import java.util.stream.Collectors;
|
||||
import jdk.nashorn.internal.runtime.JSType;
|
||||
import jdk.nashorn.internal.runtime.PropertyMap;
|
||||
import jdk.nashorn.internal.runtime.ScriptObject;
|
||||
import jdk.nashorn.internal.runtime.ScriptRuntime;
|
||||
import jdk.nashorn.internal.objects.NativeJava;
|
||||
|
||||
/*
|
||||
* A helper class to get properties of a given object for source code completion.
|
||||
*/
|
||||
final class PropertiesHelper {
|
||||
private PropertiesHelper() {}
|
||||
|
||||
// cached properties list
|
||||
private static final WeakHashMap<Object, List<String>> propsCache = new WeakHashMap<>();
|
||||
|
||||
// returns the list of properties of the given object
|
||||
static List<String> getProperties(final Object obj) {
|
||||
assert obj != null && obj != ScriptRuntime.UNDEFINED;
|
||||
|
||||
if (JSType.isPrimitive(obj)) {
|
||||
return getProperties(JSType.toScriptObject(obj));
|
||||
}
|
||||
|
||||
if (obj instanceof ScriptObject) {
|
||||
final ScriptObject sobj = (ScriptObject)obj;
|
||||
final PropertyMap pmap = sobj.getMap();
|
||||
if (propsCache.containsKey(pmap)) {
|
||||
return propsCache.get(pmap);
|
||||
}
|
||||
final String[] keys = sobj.getAllKeys();
|
||||
List<String> props = Arrays.asList(keys);
|
||||
props = props.stream()
|
||||
.filter(s -> Character.isJavaIdentifierStart(s.charAt(0)))
|
||||
.collect(Collectors.toList());
|
||||
Collections.sort(props);
|
||||
// cache properties against the PropertyMap
|
||||
propsCache.put(pmap, props);
|
||||
return props;
|
||||
}
|
||||
|
||||
if (NativeJava.isType(ScriptRuntime.UNDEFINED, obj)) {
|
||||
if (propsCache.containsKey(obj)) {
|
||||
return propsCache.get(obj);
|
||||
}
|
||||
final List<String> props = NativeJava.getProperties(obj);
|
||||
Collections.sort(props);
|
||||
// cache properties against the StaticClass representing the class
|
||||
propsCache.put(obj, props);
|
||||
return props;
|
||||
}
|
||||
|
||||
final Class<?> clazz = obj.getClass();
|
||||
if (propsCache.containsKey(clazz)) {
|
||||
return propsCache.get(clazz);
|
||||
}
|
||||
|
||||
final List<String> props = NativeJava.getProperties(obj);
|
||||
Collections.sort(props);
|
||||
// cache properties against the Class object
|
||||
propsCache.put(clazz, props);
|
||||
return props;
|
||||
}
|
||||
|
||||
// returns the list of properties of the given object that start with the given prefix
|
||||
static List<String> getProperties(final Object obj, final String prefix) {
|
||||
assert prefix != null && !prefix.isEmpty();
|
||||
return getProperties(obj).stream()
|
||||
.filter(s -> s.startsWith(prefix))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
@ -30,11 +30,14 @@ import static jdk.nashorn.internal.runtime.ScriptRuntime.UNDEFINED;
|
||||
|
||||
import java.lang.invoke.MethodHandles;
|
||||
import java.lang.reflect.Array;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Deque;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Queue;
|
||||
import jdk.internal.dynalink.beans.BeansLinker;
|
||||
import jdk.internal.dynalink.beans.StaticClass;
|
||||
import jdk.internal.dynalink.support.TypeUtilities;
|
||||
import jdk.nashorn.api.scripting.JSObject;
|
||||
@ -443,6 +446,47 @@ public final class NativeJava {
|
||||
throw typeError("cant.convert.to.javascript.array", objArray.getClass().getName());
|
||||
}
|
||||
|
||||
/**
|
||||
* Return properties of the given object. Properties also include "method names".
|
||||
* This is meant for source code completion in interactive shells or editors.
|
||||
*
|
||||
* @param object the object whose properties are returned.
|
||||
* @return list of properties
|
||||
*/
|
||||
public static List<String> getProperties(final Object object) {
|
||||
if (object instanceof StaticClass) {
|
||||
// static properties of the given class
|
||||
final Class<?> clazz = ((StaticClass)object).getRepresentedClass();
|
||||
final ArrayList<String> props = new ArrayList<>();
|
||||
try {
|
||||
Bootstrap.checkReflectionAccess(clazz, true);
|
||||
// Usually writable properties are a subset as 'write-only' properties are rare
|
||||
props.addAll(BeansLinker.getReadableStaticPropertyNames(clazz));
|
||||
props.addAll(BeansLinker.getStaticMethodNames(clazz));
|
||||
} catch (Exception ignored) {}
|
||||
return props;
|
||||
} else if (object instanceof JSObject) {
|
||||
final JSObject jsObj = ((JSObject)object);
|
||||
final ArrayList<String> props = new ArrayList<>();
|
||||
props.addAll(jsObj.keySet());
|
||||
return props;
|
||||
} else if (object != null && object != UNDEFINED) {
|
||||
// instance properties of the given object
|
||||
final Class<?> clazz = object.getClass();
|
||||
final ArrayList<String> props = new ArrayList<>();
|
||||
try {
|
||||
Bootstrap.checkReflectionAccess(clazz, false);
|
||||
// Usually writable properties are a subset as 'write-only' properties are rare
|
||||
props.addAll(BeansLinker.getReadableInstancePropertyNames(clazz));
|
||||
props.addAll(BeansLinker.getInstanceMethodNames(clazz));
|
||||
} catch (Exception ignored) {}
|
||||
return props;
|
||||
}
|
||||
|
||||
// don't know about that object
|
||||
return Collections.<String>emptyList();
|
||||
}
|
||||
|
||||
private static int[] copyArray(final byte[] in) {
|
||||
final int[] out = new int[in.length];
|
||||
for(int i = 0; i < in.length; ++i) {
|
||||
|
||||
@ -1339,6 +1339,21 @@ public abstract class ScriptObject implements PropertyAccess, Cloneable {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* return an array of all property keys - all inherited, non-enumerable included.
|
||||
* This is meant for source code completion by interactive shells or editors.
|
||||
*
|
||||
* @return Array of keys, order of properties is undefined.
|
||||
*/
|
||||
public String[] getAllKeys() {
|
||||
final Set<String> keys = new HashSet<>();
|
||||
final Set<String> nonEnumerable = new HashSet<>();
|
||||
for (ScriptObject self = this; self != null; self = self.getProto()) {
|
||||
keys.addAll(Arrays.asList(self.getOwnKeys(true, nonEnumerable)));
|
||||
}
|
||||
return keys.toArray(new String[keys.size()]);
|
||||
}
|
||||
|
||||
/**
|
||||
* return an array of own property keys associated with the object.
|
||||
*
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user