8133549: Generalize jshell's EditingHistory
authorjlahoda
Wed, 18 May 2016 21:00:43 +0200
changeset 38521 f2fe39ab9256
parent 38520 17e72b872ffd
child 38522 c4fdb181cd64
8133549: Generalize jshell's EditingHistory Summary: EditingHistory moved to jdk.internal.le Reviewed-by: rfield
langtools/src/jdk.jshell/share/classes/jdk/internal/jshell/tool/ConsoleIOContext.java
langtools/src/jdk.jshell/share/classes/jdk/internal/jshell/tool/EditingHistory.java
langtools/test/jdk/jshell/HistoryTest.java
--- a/langtools/src/jdk.jshell/share/classes/jdk/internal/jshell/tool/ConsoleIOContext.java	Wed May 18 11:35:10 2016 -0700
+++ b/langtools/src/jdk.jshell/share/classes/jdk/internal/jshell/tool/ConsoleIOContext.java	Wed May 18 21:00:43 2016 +0200
@@ -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.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.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.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 @@
         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 @@
             }
         });
         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 @@
 
     @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 @@
         }
     }
 
-    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 @@
     }
 
     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)
--- a/langtools/src/jdk.jshell/share/classes/jdk/internal/jshell/tool/EditingHistory.java	Wed May 18 11:35:10 2016 -0700
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -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 {}
-}
-
--- a/langtools/test/jdk/jshell/HistoryTest.java	Wed May 18 11:35:10 2016 -0700
+++ b/langtools/test/jdk/jshell/HistoryTest.java	Wed May 18 21:00:43 2016 +0200
@@ -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 @@
         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 {}
-
-    }
-
 }