mirror of
https://github.com/openjdk/jdk.git
synced 2026-01-28 12:09:14 +00:00
8133549: Generalize jshell's EditingHistory
EditingHistory moved to jdk.internal.le Reviewed-by: rfield
This commit is contained in:
parent
3d6e1e8abc
commit
d75bcbe358
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2015, 2016, 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
|
||||
@ -25,7 +25,6 @@
|
||||
|
||||
package jdk.internal.jshell.tool;
|
||||
|
||||
import jdk.jshell.SourceCodeAnalysis.CompletionInfo;
|
||||
import jdk.jshell.SourceCodeAnalysis.QualifiedNames;
|
||||
import jdk.jshell.SourceCodeAnalysis.Suggestion;
|
||||
|
||||
@ -36,6 +35,7 @@ import java.io.PrintStream;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
@ -44,6 +44,9 @@ import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.prefs.BackingStoreException;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import jdk.internal.jline.NoInterruptUnixTerminal;
|
||||
import jdk.internal.jline.Terminal;
|
||||
@ -54,10 +57,13 @@ import jdk.internal.jline.console.ConsoleReader;
|
||||
import jdk.internal.jline.console.KeyMap;
|
||||
import jdk.internal.jline.console.UserInterruptException;
|
||||
import jdk.internal.jline.console.completer.Completer;
|
||||
import jdk.internal.jline.extra.EditingHistory;
|
||||
import jdk.internal.jshell.tool.StopDetectingInputStream.State;
|
||||
|
||||
class ConsoleIOContext extends IOContext {
|
||||
|
||||
private static final String HISTORY_LINE_PREFIX = "HISTORY_LINE_";
|
||||
|
||||
final JShellTool repl;
|
||||
final StopDetectingInputStream input;
|
||||
final ConsoleReader in;
|
||||
@ -80,9 +86,14 @@ class ConsoleIOContext extends IOContext {
|
||||
in = new ConsoleReader(cmdin, cmdout, term);
|
||||
in.setExpandEvents(false);
|
||||
in.setHandleUserInterrupt(true);
|
||||
in.setHistory(history = new EditingHistory(repl.prefs) {
|
||||
@Override protected CompletionInfo analyzeCompletion(String input) {
|
||||
return repl.analysis.analyzeCompletion(input);
|
||||
List<String> persistenHistory = Stream.of(repl.prefs.keys())
|
||||
.filter(key -> key.startsWith(HISTORY_LINE_PREFIX))
|
||||
.sorted()
|
||||
.map(key -> repl.prefs.get(key, null))
|
||||
.collect(Collectors.toList());
|
||||
in.setHistory(history = new EditingHistory(in, persistenHistory) {
|
||||
@Override protected boolean isComplete(CharSequence input) {
|
||||
return repl.analysis.analyzeCompletion(input.toString()).completeness.isComplete;
|
||||
}
|
||||
});
|
||||
in.setBellEnabled(true);
|
||||
@ -150,8 +161,6 @@ class ConsoleIOContext extends IOContext {
|
||||
}
|
||||
});
|
||||
bind(DOCUMENTATION_SHORTCUT, (ActionListener) evt -> documentation(repl));
|
||||
bind(CTRL_UP, (ActionListener) evt -> moveHistoryToSnippet(((EditingHistory) in.getHistory())::previousSnippet));
|
||||
bind(CTRL_DOWN, (ActionListener) evt -> moveHistoryToSnippet(((EditingHistory) in.getHistory())::nextSnippet));
|
||||
for (FixComputer computer : FIX_COMPUTERS) {
|
||||
for (String shortcuts : SHORTCUT_FIXES) {
|
||||
bind(shortcuts + computer.shortcut, (ActionListener) evt -> fixes(computer));
|
||||
@ -181,7 +190,24 @@ class ConsoleIOContext extends IOContext {
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
history.save();
|
||||
//save history:
|
||||
try {
|
||||
for (String key : repl.prefs.keys()) {
|
||||
if (key.startsWith(HISTORY_LINE_PREFIX))
|
||||
repl.prefs.remove(key);
|
||||
}
|
||||
Collection<? extends String> savedHistory = history.save();
|
||||
if (!savedHistory.isEmpty()) {
|
||||
int len = (int) Math.ceil(Math.log10(savedHistory.size()+1));
|
||||
String format = HISTORY_LINE_PREFIX + "%0" + len + "d";
|
||||
int index = 0;
|
||||
for (String historyLine : savedHistory) {
|
||||
repl.prefs.put(String.format(format, index++), historyLine);
|
||||
}
|
||||
}
|
||||
} catch (BackingStoreException ex) {
|
||||
throw new IllegalStateException(ex);
|
||||
}
|
||||
in.shutdown();
|
||||
try {
|
||||
in.getTerminal().restore();
|
||||
@ -190,30 +216,6 @@ class ConsoleIOContext extends IOContext {
|
||||
}
|
||||
}
|
||||
|
||||
private void moveHistoryToSnippet(Supplier<Boolean> action) {
|
||||
if (!action.get()) {
|
||||
try {
|
||||
in.beep();
|
||||
} catch (IOException ex) {
|
||||
throw new IllegalStateException(ex);
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
//could use:
|
||||
//in.resetPromptLine(in.getPrompt(), in.getHistory().current().toString(), -1);
|
||||
//but that would mean more re-writing on the screen, (and prints an additional
|
||||
//empty line), so using setBuffer directly:
|
||||
Method setBuffer = in.getClass().getDeclaredMethod("setBuffer", String.class);
|
||||
|
||||
setBuffer.setAccessible(true);
|
||||
setBuffer.invoke(in, in.getHistory().current().toString());
|
||||
in.flush();
|
||||
} catch (ReflectiveOperationException | IOException ex) {
|
||||
throw new IllegalStateException(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void bind(String shortcut, Object action) {
|
||||
KeyMap km = in.getKeys();
|
||||
for (int i = 0; i < shortcut.length(); i++) {
|
||||
@ -227,8 +229,6 @@ class ConsoleIOContext extends IOContext {
|
||||
}
|
||||
|
||||
private static final String DOCUMENTATION_SHORTCUT = "\033\133\132"; //Shift-TAB
|
||||
private static final String CTRL_UP = "\033\133\061\073\065\101"; //Ctrl-UP
|
||||
private static final String CTRL_DOWN = "\033\133\061\073\065\102"; //Ctrl-DOWN
|
||||
private static final String[] SHORTCUT_FIXES = {
|
||||
"\033\015", //Alt-Enter (Linux)
|
||||
"\033\133\061\067\176", //F6/Alt-F1 (Mac)
|
||||
|
||||
@ -1,382 +0,0 @@
|
||||
/*
|
||||
* 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.internal.jshell.tool;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.ListIterator;
|
||||
import java.util.Set;
|
||||
import java.util.prefs.BackingStoreException;
|
||||
import java.util.prefs.Preferences;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import jdk.internal.jline.console.history.History;
|
||||
import jdk.internal.jline.console.history.History.Entry;
|
||||
import jdk.internal.jline.console.history.MemoryHistory;
|
||||
import jdk.jshell.SourceCodeAnalysis.CompletionInfo;
|
||||
|
||||
/*Public for tests (HistoryTest).
|
||||
*/
|
||||
public abstract class EditingHistory implements History {
|
||||
|
||||
private final Preferences prefs;
|
||||
private final History fullHistory;
|
||||
private History currentDelegate;
|
||||
|
||||
protected EditingHistory(Preferences prefs) {
|
||||
this.prefs = prefs;
|
||||
this.fullHistory = new MemoryHistory();
|
||||
this.currentDelegate = fullHistory;
|
||||
load();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
return currentDelegate.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEmpty() {
|
||||
return currentDelegate.isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int index() {
|
||||
return currentDelegate.index();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
if (currentDelegate != fullHistory)
|
||||
throw new IllegalStateException("narrowed");
|
||||
currentDelegate.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public CharSequence get(int index) {
|
||||
return currentDelegate.get(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void add(CharSequence line) {
|
||||
NarrowingHistoryLine currentLine = null;
|
||||
int origIndex = fullHistory.index();
|
||||
int fullSize;
|
||||
try {
|
||||
fullHistory.moveToEnd();
|
||||
fullSize = fullHistory.index();
|
||||
if (currentDelegate == fullHistory) {
|
||||
if (origIndex < fullHistory.index()) {
|
||||
for (Entry entry : fullHistory) {
|
||||
if (!(entry.value() instanceof NarrowingHistoryLine))
|
||||
continue;
|
||||
int[] cluster = ((NarrowingHistoryLine) entry.value()).span;
|
||||
if (cluster[0] == origIndex && cluster[1] > cluster[0]) {
|
||||
currentDelegate = new MemoryHistory();
|
||||
for (int i = cluster[0]; i <= cluster[1]; i++) {
|
||||
currentDelegate.add(fullHistory.get(i));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
fullHistory.moveToEnd();
|
||||
while (fullHistory.previous()) {
|
||||
CharSequence c = fullHistory.current();
|
||||
if (c instanceof NarrowingHistoryLine) {
|
||||
currentLine = (NarrowingHistoryLine) c;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
fullHistory.moveTo(origIndex);
|
||||
}
|
||||
if (currentLine == null || currentLine.span[1] != (-1)) {
|
||||
line = currentLine = new NarrowingHistoryLine(line, fullSize);
|
||||
}
|
||||
StringBuilder complete = new StringBuilder();
|
||||
for (int i = currentLine.span[0]; i < fullSize; i++) {
|
||||
complete.append(fullHistory.get(i));
|
||||
}
|
||||
complete.append(line);
|
||||
if (analyzeCompletion(complete.toString()).completeness.isComplete) {
|
||||
currentLine.span[1] = fullSize; //TODO: +1?
|
||||
currentDelegate = fullHistory;
|
||||
}
|
||||
fullHistory.add(line);
|
||||
}
|
||||
|
||||
protected abstract CompletionInfo analyzeCompletion(String input);
|
||||
|
||||
@Override
|
||||
public void set(int index, CharSequence item) {
|
||||
if (currentDelegate != fullHistory)
|
||||
throw new IllegalStateException("narrowed");
|
||||
currentDelegate.set(index, item);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CharSequence remove(int i) {
|
||||
if (currentDelegate != fullHistory)
|
||||
throw new IllegalStateException("narrowed");
|
||||
return currentDelegate.remove(i);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CharSequence removeFirst() {
|
||||
if (currentDelegate != fullHistory)
|
||||
throw new IllegalStateException("narrowed");
|
||||
return currentDelegate.removeFirst();
|
||||
}
|
||||
|
||||
@Override
|
||||
public CharSequence removeLast() {
|
||||
if (currentDelegate != fullHistory)
|
||||
throw new IllegalStateException("narrowed");
|
||||
return currentDelegate.removeLast();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void replace(CharSequence item) {
|
||||
if (currentDelegate != fullHistory)
|
||||
throw new IllegalStateException("narrowed");
|
||||
currentDelegate.replace(item);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ListIterator<Entry> entries(int index) {
|
||||
return currentDelegate.entries(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ListIterator<Entry> entries() {
|
||||
return currentDelegate.entries();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<Entry> iterator() {
|
||||
return currentDelegate.iterator();
|
||||
}
|
||||
|
||||
@Override
|
||||
public CharSequence current() {
|
||||
return currentDelegate.current();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean previous() {
|
||||
return currentDelegate.previous();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean next() {
|
||||
return currentDelegate.next();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean moveToFirst() {
|
||||
return currentDelegate.moveToFirst();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean moveToLast() {
|
||||
return currentDelegate.moveToLast();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean moveTo(int index) {
|
||||
return currentDelegate.moveTo(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void moveToEnd() {
|
||||
currentDelegate.moveToEnd();
|
||||
}
|
||||
|
||||
public boolean previousSnippet() {
|
||||
for (int i = index() - 1; i >= 0; i--) {
|
||||
if (get(i) instanceof NarrowingHistoryLine) {
|
||||
moveTo(i);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean nextSnippet() {
|
||||
for (int i = index() + 1; i < size(); i++) {
|
||||
if (get(i) instanceof NarrowingHistoryLine) {
|
||||
moveTo(i);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (index() < size()) {
|
||||
moveToEnd();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static final String HISTORY_LINE_PREFIX = "HISTORY_LINE_";
|
||||
private static final String HISTORY_SNIPPET_START = "HISTORY_SNIPPET";
|
||||
|
||||
public final void load() {
|
||||
try {
|
||||
Set<Integer> snippetsStart = new HashSet<>();
|
||||
for (String start : prefs.get(HISTORY_SNIPPET_START, "").split(";")) {
|
||||
if (!start.isEmpty())
|
||||
snippetsStart.add(Integer.parseInt(start));
|
||||
}
|
||||
List<String> keys = Stream.of(prefs.keys()).sorted().collect(Collectors.toList());
|
||||
NarrowingHistoryLine currentHistoryLine = null;
|
||||
int currentLine = 0;
|
||||
for (String key : keys) {
|
||||
if (!key.startsWith(HISTORY_LINE_PREFIX))
|
||||
continue;
|
||||
CharSequence line = prefs.get(key, "");
|
||||
if (snippetsStart.contains(currentLine)) {
|
||||
class PersistentNarrowingHistoryLine extends NarrowingHistoryLine implements PersistentEntryMarker {
|
||||
public PersistentNarrowingHistoryLine(CharSequence delegate, int start) {
|
||||
super(delegate, start);
|
||||
}
|
||||
}
|
||||
line = currentHistoryLine = new PersistentNarrowingHistoryLine(line, currentLine);
|
||||
} else {
|
||||
class PersistentLine implements CharSequence, PersistentEntryMarker {
|
||||
private final CharSequence delegate;
|
||||
public PersistentLine(CharSequence delegate) {
|
||||
this.delegate = delegate;
|
||||
}
|
||||
@Override public int length() {
|
||||
return delegate.length();
|
||||
}
|
||||
@Override public char charAt(int index) {
|
||||
return delegate.charAt(index);
|
||||
}
|
||||
@Override public CharSequence subSequence(int start, int end) {
|
||||
return delegate.subSequence(start, end);
|
||||
}
|
||||
@Override public String toString() {
|
||||
return delegate.toString();
|
||||
}
|
||||
}
|
||||
line = new PersistentLine(line);
|
||||
}
|
||||
if (currentHistoryLine != null)
|
||||
currentHistoryLine.span[1] = currentLine;
|
||||
currentLine++;
|
||||
fullHistory.add(line);
|
||||
}
|
||||
currentLine = 0;
|
||||
} catch (BackingStoreException ex) {
|
||||
throw new IllegalStateException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
public void save() {
|
||||
try {
|
||||
for (String key : prefs.keys()) {
|
||||
if (key.startsWith(HISTORY_LINE_PREFIX))
|
||||
prefs.remove(key);
|
||||
}
|
||||
Iterator<Entry> entries = fullHistory.iterator();
|
||||
if (entries.hasNext()) {
|
||||
int len = (int) Math.ceil(Math.log10(fullHistory.size()+1));
|
||||
String format = HISTORY_LINE_PREFIX + "%0" + len + "d";
|
||||
StringBuilder snippetStarts = new StringBuilder();
|
||||
String snippetStartDelimiter = "";
|
||||
while (entries.hasNext()) {
|
||||
Entry entry = entries.next();
|
||||
prefs.put(String.format(format, entry.index()), entry.value().toString());
|
||||
if (entry.value() instanceof NarrowingHistoryLine) {
|
||||
snippetStarts.append(snippetStartDelimiter);
|
||||
snippetStarts.append(entry.index());
|
||||
snippetStartDelimiter = ";";
|
||||
}
|
||||
}
|
||||
prefs.put(HISTORY_SNIPPET_START, snippetStarts.toString());
|
||||
}
|
||||
} catch (BackingStoreException ex) {
|
||||
throw new IllegalStateException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
public List<String> currentSessionEntries() {
|
||||
List<String> result = new ArrayList<>();
|
||||
|
||||
for (Entry e : fullHistory) {
|
||||
if (!(e.value() instanceof PersistentEntryMarker)) {
|
||||
result.add(e.value().toString());
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void fullHistoryReplace(String source) {
|
||||
fullHistory.replace(source);
|
||||
}
|
||||
|
||||
private class NarrowingHistoryLine implements CharSequence {
|
||||
private final CharSequence delegate;
|
||||
private final int[] span;
|
||||
|
||||
public NarrowingHistoryLine(CharSequence delegate, int start) {
|
||||
this.delegate = delegate;
|
||||
this.span = new int[] {start, -1};
|
||||
}
|
||||
|
||||
@Override
|
||||
public int length() {
|
||||
return delegate.length();
|
||||
}
|
||||
|
||||
@Override
|
||||
public char charAt(int index) {
|
||||
return delegate.charAt(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CharSequence subSequence(int start, int end) {
|
||||
return delegate.subSequence(start, end);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return delegate.toString();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private interface PersistentEntryMarker {}
|
||||
}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2015, 2016, 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
|
||||
@ -24,152 +24,64 @@
|
||||
/*
|
||||
* @test
|
||||
* @summary Test Completion
|
||||
* @modules jdk.jshell/jdk.internal.jshell.tool
|
||||
* jdk.internal.le/jdk.internal.jline.console.history
|
||||
* @modules jdk.internal.le/jdk.internal.jline.extra
|
||||
* jdk.jshell/jdk.internal.jshell.tool
|
||||
* @build HistoryTest
|
||||
* @run testng HistoryTest
|
||||
*/
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.PrintStream;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.prefs.AbstractPreferences;
|
||||
import java.util.prefs.BackingStoreException;
|
||||
import jdk.internal.jline.console.history.MemoryHistory;
|
||||
|
||||
import jdk.jshell.JShell;
|
||||
import jdk.jshell.SourceCodeAnalysis;
|
||||
import jdk.jshell.SourceCodeAnalysis.CompletionInfo;
|
||||
import java.lang.reflect.Field;
|
||||
import jdk.internal.jline.extra.EditingHistory;
|
||||
import org.testng.annotations.Test;
|
||||
import jdk.internal.jshell.tool.EditingHistory;
|
||||
|
||||
import static org.testng.Assert.*;
|
||||
|
||||
@Test
|
||||
public class HistoryTest {
|
||||
public class HistoryTest extends ReplToolTesting {
|
||||
|
||||
public void testHistory() {
|
||||
JShell eval = JShell.builder()
|
||||
.in(new ByteArrayInputStream(new byte[0]))
|
||||
.out(new PrintStream(new ByteArrayOutputStream()))
|
||||
.err(new PrintStream(new ByteArrayOutputStream()))
|
||||
.build();
|
||||
SourceCodeAnalysis analysis = eval.sourceCodeAnalysis();
|
||||
MemoryPreferences prefs = new MemoryPreferences(null, "");
|
||||
EditingHistory history = new EditingHistory(prefs) {
|
||||
@Override protected CompletionInfo analyzeCompletion(String input) {
|
||||
return analysis.analyzeCompletion(input);
|
||||
}
|
||||
};
|
||||
history.add("void test() {");
|
||||
history.add(" System.err.println(1);");
|
||||
history.add("}");
|
||||
history.add("/exit");
|
||||
|
||||
previousAndAssert(history, "/exit");
|
||||
|
||||
history.previous(); history.previous(); history.previous();
|
||||
|
||||
history.add("void test() { /*changed*/");
|
||||
|
||||
previousAndAssert(history, "}");
|
||||
previousAndAssert(history, " System.err.println(1);");
|
||||
previousAndAssert(history, "void test() {");
|
||||
|
||||
assertFalse(history.previous());
|
||||
|
||||
nextAndAssert(history, " System.err.println(1);");
|
||||
nextAndAssert(history, "}");
|
||||
nextAndAssert(history, "");
|
||||
|
||||
history.add(" System.err.println(2);");
|
||||
history.add("} /*changed*/");
|
||||
|
||||
assertEquals(history.size(), 7);
|
||||
|
||||
history.save();
|
||||
|
||||
history = new EditingHistory(prefs) {
|
||||
@Override protected CompletionInfo analyzeCompletion(String input) {
|
||||
return analysis.analyzeCompletion(input);
|
||||
}
|
||||
};
|
||||
|
||||
previousSnippetAndAssert(history, "void test() { /*changed*/");
|
||||
previousSnippetAndAssert(history, "/exit");
|
||||
previousSnippetAndAssert(history, "void test() {");
|
||||
|
||||
assertFalse(history.previousSnippet());
|
||||
|
||||
nextSnippetAndAssert(history, "/exit");
|
||||
nextSnippetAndAssert(history, "void test() { /*changed*/");
|
||||
nextSnippetAndAssert(history, "");
|
||||
|
||||
assertFalse(history.nextSnippet());
|
||||
|
||||
history.add("{");
|
||||
history.add("}");
|
||||
|
||||
history.save();
|
||||
|
||||
history = new EditingHistory(prefs) {
|
||||
@Override protected CompletionInfo analyzeCompletion(String input) {
|
||||
return analysis.analyzeCompletion(input);
|
||||
}
|
||||
};
|
||||
|
||||
previousSnippetAndAssert(history, "{");
|
||||
previousSnippetAndAssert(history, "void test() { /*changed*/");
|
||||
previousSnippetAndAssert(history, "/exit");
|
||||
previousSnippetAndAssert(history, "void test() {");
|
||||
|
||||
while (history.next());
|
||||
|
||||
history.add("/*current1*/");
|
||||
history.add("/*current2*/");
|
||||
history.add("/*current3*/");
|
||||
|
||||
assertEquals(history.currentSessionEntries(), Arrays.asList("/*current1*/", "/*current2*/", "/*current3*/"));
|
||||
|
||||
history.remove(0);
|
||||
|
||||
assertEquals(history.currentSessionEntries(), Arrays.asList("/*current1*/", "/*current2*/", "/*current3*/"));
|
||||
|
||||
while (history.size() > 2)
|
||||
history.remove(0);
|
||||
|
||||
assertEquals(history.currentSessionEntries(), Arrays.asList("/*current2*/", "/*current3*/"));
|
||||
|
||||
for (int i = 0; i < MemoryHistory.DEFAULT_MAX_SIZE * 2; i++) {
|
||||
history.add("/exit");
|
||||
}
|
||||
|
||||
history.add("void test() { /*after full*/");
|
||||
history.add(" System.err.println(1);");
|
||||
history.add("}");
|
||||
|
||||
previousSnippetAndAssert(history, "void test() { /*after full*/");
|
||||
test(
|
||||
a -> {if (!a) setCommandInput("void test() {\n");},
|
||||
a -> {if (!a) setCommandInput(" System.err.println(1);\n");},
|
||||
a -> {if (!a) setCommandInput(" System.err.println(1);\n");},
|
||||
a -> {assertCommand(a, "} //test", "| created method test()");},
|
||||
a -> {
|
||||
if (!a) {
|
||||
try {
|
||||
previousAndAssert(getHistory(), "} //test");
|
||||
previousSnippetAndAssert(getHistory(), "void test() {");
|
||||
} catch (Exception ex) {
|
||||
throw new IllegalStateException(ex);
|
||||
}
|
||||
}
|
||||
assertCommand(a, "int dummy;", "dummy ==> 0");
|
||||
});
|
||||
test(
|
||||
a -> {if (!a) setCommandInput("void test2() {\n");},
|
||||
a -> {assertCommand(a, "} //test2", "| created method test2()");},
|
||||
a -> {
|
||||
if (!a) {
|
||||
try {
|
||||
previousAndAssert(getHistory(), "} //test2");
|
||||
previousSnippetAndAssert(getHistory(), "void test2() {");
|
||||
previousSnippetAndAssert(getHistory(), "/debug 0"); //added by test framework
|
||||
previousSnippetAndAssert(getHistory(), "/exit");
|
||||
previousSnippetAndAssert(getHistory(), "int dummy;");
|
||||
previousSnippetAndAssert(getHistory(), "void test() {");
|
||||
} catch (Exception ex) {
|
||||
throw new IllegalStateException(ex);
|
||||
}
|
||||
}
|
||||
assertCommand(a, "int dummy;", "dummy ==> 0");
|
||||
});
|
||||
}
|
||||
|
||||
public void testSaveOneHistory() {
|
||||
JShell eval = JShell.builder()
|
||||
.in(new ByteArrayInputStream(new byte[0]))
|
||||
.out(new PrintStream(new ByteArrayOutputStream()))
|
||||
.err(new PrintStream(new ByteArrayOutputStream()))
|
||||
.build();
|
||||
SourceCodeAnalysis analysis = eval.sourceCodeAnalysis();
|
||||
MemoryPreferences prefs = new MemoryPreferences(null, "");
|
||||
EditingHistory history = new EditingHistory(prefs) {
|
||||
@Override protected CompletionInfo analyzeCompletion(String input) {
|
||||
return analysis.analyzeCompletion(input);
|
||||
}
|
||||
};
|
||||
|
||||
history.add("first");
|
||||
history.save();
|
||||
private EditingHistory getHistory() throws Exception {
|
||||
Field input = repl.getClass().getDeclaredField("input");
|
||||
input.setAccessible(true);
|
||||
Object console = input.get(repl);
|
||||
Field history = console.getClass().getDeclaredField("history");
|
||||
history.setAccessible(true);
|
||||
return (EditingHistory) history.get(console);
|
||||
}
|
||||
|
||||
private void previousAndAssert(EditingHistory history, String expected) {
|
||||
@ -177,71 +89,9 @@ public class HistoryTest {
|
||||
assertEquals(history.current().toString(), expected);
|
||||
}
|
||||
|
||||
private void nextAndAssert(EditingHistory history, String expected) {
|
||||
assertTrue(history.next());
|
||||
assertEquals(history.current().toString(), expected);
|
||||
}
|
||||
|
||||
private void previousSnippetAndAssert(EditingHistory history, String expected) {
|
||||
assertTrue(history.previousSnippet());
|
||||
assertEquals(history.current().toString(), expected);
|
||||
}
|
||||
|
||||
private void nextSnippetAndAssert(EditingHistory history, String expected) {
|
||||
assertTrue(history.nextSnippet());
|
||||
assertEquals(history.current().toString(), expected);
|
||||
}
|
||||
|
||||
private static final class MemoryPreferences extends AbstractPreferences {
|
||||
|
||||
private final Map<String, String> key2Value = new HashMap<>();
|
||||
private final Map<String, MemoryPreferences> key2SubNode = new HashMap<>();
|
||||
|
||||
public MemoryPreferences(AbstractPreferences parent, String name) {
|
||||
super(parent, name);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void putSpi(String key, String value) {
|
||||
key2Value.put(key, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getSpi(String key) {
|
||||
return key2Value.get(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void removeSpi(String key) {
|
||||
key2Value.remove(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void removeNodeSpi() throws BackingStoreException {
|
||||
((MemoryPreferences) parent()).key2SubNode.remove(name());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String[] keysSpi() throws BackingStoreException {
|
||||
return key2Value.keySet().toArray(new String[key2Value.size()]);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String[] childrenNamesSpi() throws BackingStoreException {
|
||||
return key2SubNode.keySet().toArray(new String[key2SubNode.size()]);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected AbstractPreferences childSpi(String name) {
|
||||
return key2SubNode.computeIfAbsent(name, n -> new MemoryPreferences(this, n));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void syncSpi() throws BackingStoreException {}
|
||||
|
||||
@Override
|
||||
protected void flushSpi() throws BackingStoreException {}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user