8185426: Jshell crashing on autocompletion
Properly canceling completion on <tab> if needed. Reviewed-by: rfield
This commit is contained in:
parent
701dfdd390
commit
698882e467
@ -55,7 +55,6 @@ import jdk.internal.jline.TerminalSupport;
|
|||||||
import jdk.internal.jline.WindowsTerminal;
|
import jdk.internal.jline.WindowsTerminal;
|
||||||
import jdk.internal.jline.console.ConsoleReader;
|
import jdk.internal.jline.console.ConsoleReader;
|
||||||
import jdk.internal.jline.console.KeyMap;
|
import jdk.internal.jline.console.KeyMap;
|
||||||
import jdk.internal.jline.console.Operation;
|
|
||||||
import jdk.internal.jline.console.UserInterruptException;
|
import jdk.internal.jline.console.UserInterruptException;
|
||||||
import jdk.internal.jline.console.history.History;
|
import jdk.internal.jline.console.history.History;
|
||||||
import jdk.internal.jline.console.history.MemoryHistory;
|
import jdk.internal.jline.console.history.MemoryHistory;
|
||||||
@ -94,14 +93,14 @@ class ConsoleIOContext extends IOContext {
|
|||||||
term = new JShellUnixTerminal(input);
|
term = new JShellUnixTerminal(input);
|
||||||
}
|
}
|
||||||
term.init();
|
term.init();
|
||||||
List<CompletionTask> completionTODO = new ArrayList<>();
|
CompletionState completionState = new CompletionState();
|
||||||
in = new ConsoleReader(cmdin, cmdout, term) {
|
in = new ConsoleReader(cmdin, cmdout, term) {
|
||||||
@Override public KeyMap getKeys() {
|
@Override public KeyMap getKeys() {
|
||||||
return new CheckCompletionKeyMap(super.getKeys(), completionTODO);
|
return new CheckCompletionKeyMap(super.getKeys(), completionState);
|
||||||
}
|
}
|
||||||
@Override
|
@Override
|
||||||
protected boolean complete() throws IOException {
|
protected boolean complete() throws IOException {
|
||||||
return ConsoleIOContext.this.complete(completionTODO);
|
return ConsoleIOContext.this.complete(completionState);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
in.setExpandEvents(false);
|
in.setExpandEvents(false);
|
||||||
@ -201,15 +200,19 @@ class ConsoleIOContext extends IOContext {
|
|||||||
private static final String LINE_SEPARATORS2 = LINE_SEPARATOR + LINE_SEPARATOR;
|
private static final String LINE_SEPARATORS2 = LINE_SEPARATOR + LINE_SEPARATOR;
|
||||||
|
|
||||||
@SuppressWarnings("fallthrough")
|
@SuppressWarnings("fallthrough")
|
||||||
private boolean complete(List<CompletionTask> todo) {
|
private boolean complete(CompletionState completionState) {
|
||||||
//The completion has multiple states (invoked by subsequent presses of <tab>).
|
//The completion has multiple states (invoked by subsequent presses of <tab>).
|
||||||
//On the first invocation in a given sequence, all steps are precomputed
|
//On the first invocation in a given sequence, all steps are precomputed
|
||||||
//and placed into the todo list. The todo list is then followed on both the first
|
//and placed into the todo list (completionState.todo). The todo list is
|
||||||
//and subsequent <tab> presses:
|
//then followed on both the first and subsequent completion invocations:
|
||||||
try {
|
try {
|
||||||
String text = in.getCursorBuffer().toString();
|
String text = in.getCursorBuffer().toString();
|
||||||
int cursor = in.getCursorBuffer().cursor;
|
int cursor = in.getCursorBuffer().cursor;
|
||||||
if (todo.isEmpty()) {
|
|
||||||
|
List<CompletionTask> todo = completionState.todo;
|
||||||
|
|
||||||
|
if (todo.isEmpty() || completionState.actionCount != 1) {
|
||||||
|
ConsoleIOContextTestSupport.willComputeCompletion();
|
||||||
int[] anchor = new int[] {-1};
|
int[] anchor = new int[] {-1};
|
||||||
List<Suggestion> suggestions;
|
List<Suggestion> suggestions;
|
||||||
List<String> doc;
|
List<String> doc;
|
||||||
@ -237,6 +240,8 @@ class ConsoleIOContext extends IOContext {
|
|||||||
CompletionTask ordinaryCompletion = new OrdinaryCompletionTask(suggestions, anchor[0], !command && !doc.isEmpty(), hasSmart);
|
CompletionTask ordinaryCompletion = new OrdinaryCompletionTask(suggestions, anchor[0], !command && !doc.isEmpty(), hasSmart);
|
||||||
CompletionTask allCompletion = new AllSuggestionsCompletionTask(suggestions, anchor[0]);
|
CompletionTask allCompletion = new AllSuggestionsCompletionTask(suggestions, anchor[0]);
|
||||||
|
|
||||||
|
todo = new ArrayList<>();
|
||||||
|
|
||||||
//the main decission tree:
|
//the main decission tree:
|
||||||
if (command) {
|
if (command) {
|
||||||
CompletionTask shortDocumentation = new CommandSynopsisTask(doc);
|
CompletionTask shortDocumentation = new CommandSynopsisTask(doc);
|
||||||
@ -310,6 +315,9 @@ class ConsoleIOContext extends IOContext {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
completionState.actionCount = 0;
|
||||||
|
completionState.todo = todo;
|
||||||
|
|
||||||
if (repaint) {
|
if (repaint) {
|
||||||
in.redrawLine();
|
in.redrawLine();
|
||||||
in.flush();
|
in.flush();
|
||||||
@ -1203,12 +1211,12 @@ class ConsoleIOContext extends IOContext {
|
|||||||
private static final class CheckCompletionKeyMap extends KeyMap {
|
private static final class CheckCompletionKeyMap extends KeyMap {
|
||||||
|
|
||||||
private final KeyMap del;
|
private final KeyMap del;
|
||||||
private final List<CompletionTask> completionTODO;
|
private final CompletionState completionState;
|
||||||
|
|
||||||
public CheckCompletionKeyMap(KeyMap del, List<CompletionTask> completionTODO) {
|
public CheckCompletionKeyMap(KeyMap del, CompletionState completionState) {
|
||||||
super(del.getName(), del.isViKeyMap());
|
super(del.getName(), del.isViKeyMap());
|
||||||
this.del = del;
|
this.del = del;
|
||||||
this.completionTODO = completionTODO;
|
this.completionState = completionState;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -1233,13 +1241,9 @@ class ConsoleIOContext extends IOContext {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Object getBound(CharSequence keySeq) {
|
public Object getBound(CharSequence keySeq) {
|
||||||
Object res = del.getBound(keySeq);
|
this.completionState.actionCount++;
|
||||||
|
|
||||||
if (res != Operation.COMPLETE) {
|
return del.getBound(keySeq);
|
||||||
completionTODO.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
return res;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -1252,4 +1256,12 @@ class ConsoleIOContext extends IOContext {
|
|||||||
return "check: " + del.toString();
|
return "check: " + del.toString();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static final class CompletionState {
|
||||||
|
/**The number of actions since the last completion invocation. Will be 1 when completion is
|
||||||
|
* invoked immediately after the last completion invocation.*/
|
||||||
|
public int actionCount;
|
||||||
|
/**Precomputed completion actions. Should only be reused if actionCount == 1.*/
|
||||||
|
public List<CompletionTask> todo = Collections.emptyList();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
@ -0,0 +1,41 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2017, 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.internal.jshell.tool;
|
||||||
|
|
||||||
|
/**A support class to the ConsoleIOContext, containing callbacks called
|
||||||
|
* on important points in ConsoleIOContext.
|
||||||
|
*/
|
||||||
|
public abstract class ConsoleIOContextTestSupport {
|
||||||
|
|
||||||
|
public static ConsoleIOContextTestSupport IMPL;
|
||||||
|
|
||||||
|
public static void willComputeCompletion() {
|
||||||
|
if (IMPL != null)
|
||||||
|
IMPL.willComputeCompletionCallback();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract void willComputeCompletionCallback();
|
||||||
|
|
||||||
|
}
|
@ -23,10 +23,11 @@
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @test
|
* @test
|
||||||
* @bug 8177076
|
* @bug 8177076 8185426
|
||||||
* @modules
|
* @modules
|
||||||
* jdk.compiler/com.sun.tools.javac.api
|
* jdk.compiler/com.sun.tools.javac.api
|
||||||
* jdk.compiler/com.sun.tools.javac.main
|
* jdk.compiler/com.sun.tools.javac.main
|
||||||
|
* jdk.jshell/jdk.internal.jshell.tool
|
||||||
* jdk.jshell/jdk.internal.jshell.tool.resources:open
|
* jdk.jshell/jdk.internal.jshell.tool.resources:open
|
||||||
* jdk.jshell/jdk.jshell:open
|
* jdk.jshell/jdk.jshell:open
|
||||||
* @library /tools/lib
|
* @library /tools/lib
|
||||||
@ -42,10 +43,12 @@ import java.nio.file.Files;
|
|||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.nio.file.Paths;
|
import java.nio.file.Paths;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
import java.util.concurrent.CountDownLatch;
|
||||||
import java.util.jar.JarEntry;
|
import java.util.jar.JarEntry;
|
||||||
import java.util.jar.JarOutputStream;
|
import java.util.jar.JarOutputStream;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
import jdk.internal.jshell.tool.ConsoleIOContextTestSupport;
|
||||||
import org.testng.annotations.Test;
|
import org.testng.annotations.Test;
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -191,6 +194,39 @@ public class ToolTabSnippetTest extends UITesting {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void testCleaningCompletionTODO() throws Exception {
|
||||||
|
doRunTest((inputSink, out) -> {
|
||||||
|
CountDownLatch testCompleteComputationStarted = new CountDownLatch(1);
|
||||||
|
CountDownLatch testCompleteComputationContinue = new CountDownLatch(1);
|
||||||
|
ConsoleIOContextTestSupport.IMPL = new ConsoleIOContextTestSupport() {
|
||||||
|
@Override
|
||||||
|
protected void willComputeCompletionCallback() {
|
||||||
|
if (testCompleteComputationStarted != null) {
|
||||||
|
testCompleteComputationStarted.countDown();
|
||||||
|
}
|
||||||
|
if (testCompleteComputationContinue != null) {
|
||||||
|
try {
|
||||||
|
testCompleteComputationContinue.await();
|
||||||
|
} catch (InterruptedException ex) {
|
||||||
|
throw new IllegalStateException(ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
//-> <tab>
|
||||||
|
inputSink.write("\011");
|
||||||
|
testCompleteComputationStarted.await();
|
||||||
|
//-> <tab><tab>
|
||||||
|
inputSink.write("\011\011");
|
||||||
|
testCompleteComputationContinue.countDown();
|
||||||
|
waitOutput(out, "\u0005");
|
||||||
|
//-> <tab>
|
||||||
|
inputSink.write("\011");
|
||||||
|
waitOutput(out, "\u0005");
|
||||||
|
ConsoleIOContextTestSupport.IMPL = null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private Path prepareZip() {
|
private Path prepareZip() {
|
||||||
String clazz1 =
|
String clazz1 =
|
||||||
"package jshelltest;\n" +
|
"package jshelltest;\n" +
|
||||||
|
Loading…
Reference in New Issue
Block a user