/* * 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.io.IOException; import java.io.InputStream; import java.io.PrintStream; import java.io.UncheckedIOException; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.ListIterator; import java.util.Objects; import java.util.Optional; import java.util.Set; import java.util.function.Supplier; import java.util.prefs.BackingStoreException; import java.util.prefs.Preferences; import jdk.internal.jline.NoInterruptUnixTerminal; import jdk.internal.jline.Terminal; import jdk.internal.jline.TerminalFactory; import jdk.internal.jline.TerminalFactory.Flavor; import jdk.internal.jline.WindowsTerminal; 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.console.history.History; import jdk.internal.jline.console.history.History.Entry; import jdk.internal.jline.console.history.MemoryHistory; class Console implements AutoCloseable { private final ConsoleReader in; private final EditingHistory history; Console(InputStream cmdin, PrintStream cmdout, Preferences prefs) throws IOException { in = new ConsoleReader(cmdin, cmdout); in.setExpandEvents(false); in.setHandleUserInterrupt(true); in.setHistory(history = new EditingHistory(prefs)); Runtime.getRuntime().addShutdownHook(new Thread(()->close())); } String readLine(String prompt) throws IOException { return in.readLine(prompt); } @Override public void close() { history.save(); } public static 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 (isComplete(complete.toString())) { currentLine.span[1] = fullSize; //TODO: +1? currentDelegate = fullHistory; } fullHistory.add(line); } protected boolean isComplete(String input) { return true; // FIXME } @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 entries(int index) { return currentDelegate.entries(index); } @Override public ListIterator entries() { return currentDelegate.entries(); } @Override public Iterator 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 snippetsStart = new HashSet<>(); for (String start : prefs.get(HISTORY_SNIPPET_START, "").split(";")) { if (start.isEmpty()) continue; snippetsStart.add(Integer.parseInt(start)); } List keys = new ArrayList<>(Arrays.asList(prefs.keys())); Collections.sort(keys); 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 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 currentSessionEntries() { List result = new ArrayList<>(); for (Entry e : fullHistory) { if (!(e.value() instanceof PersistentEntryMarker)) { result.add(e.value().toString()); } } return result; } 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 {} } }