1/* 2 * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26package jdk.nashorn.tools.jjs; 27 28import java.awt.event.ActionListener; 29import java.io.File; 30import java.io.IOException; 31import java.io.InputStream; 32import java.io.PrintStream; 33import java.io.Writer; 34import java.nio.file.Files; 35import java.util.function.Function; 36import java.util.stream.Collectors; 37import jdk.internal.jline.NoInterruptUnixTerminal; 38import jdk.internal.jline.Terminal; 39import jdk.internal.jline.TerminalFactory; 40import jdk.internal.jline.TerminalFactory.Flavor; 41import jdk.internal.jline.WindowsTerminal; 42import jdk.internal.jline.console.ConsoleReader; 43import jdk.internal.jline.console.KeyMap; 44import jdk.internal.jline.extra.EditingHistory; 45import jdk.internal.misc.Signal; 46import jdk.internal.misc.Signal.Handler; 47 48class Console implements AutoCloseable { 49 private static final String DOCUMENTATION_SHORTCUT = "\033\133\132"; //Shift-TAB 50 private final ConsoleReader in; 51 private final File historyFile; 52 53 Console(final InputStream cmdin, final PrintStream cmdout, final File historyFile, 54 final NashornCompleter completer, final Function<String, String> docHelper) throws IOException { 55 this.historyFile = historyFile; 56 57 TerminalFactory.registerFlavor(Flavor.WINDOWS, isCygwin()? JJSUnixTerminal::new : JJSWindowsTerminal::new); 58 TerminalFactory.registerFlavor(Flavor.UNIX, JJSUnixTerminal::new); 59 in = new ConsoleReader(cmdin, cmdout); 60 in.setExpandEvents(false); 61 in.setHandleUserInterrupt(true); 62 in.setBellEnabled(true); 63 in.setCopyPasteDetection(true); 64 final Iterable<String> existingHistory = historyFile.exists() ? Files.readAllLines(historyFile.toPath()) : null; 65 in.setHistory(new EditingHistory(in, existingHistory) { 66 @Override protected boolean isComplete(CharSequence input) { 67 return completer.isComplete(input.toString()); 68 } 69 }); 70 in.addCompleter(completer); 71 Runtime.getRuntime().addShutdownHook(new Thread((Runnable)this::saveHistory)); 72 bind(DOCUMENTATION_SHORTCUT, (ActionListener)evt -> showDocumentation(docHelper)); 73 try { 74 Signal.handle(new Signal("CONT"), new Handler() { 75 @Override public void handle(Signal sig) { 76 try { 77 in.getTerminal().reset(); 78 in.redrawLine(); 79 in.flush(); 80 } catch (Exception ex) { 81 ex.printStackTrace(); 82 } 83 } 84 }); 85 } catch (IllegalArgumentException ignored) { 86 //the CONT signal does not exist on this platform 87 } 88 } 89 90 String readLine(final String prompt) throws IOException { 91 return in.readLine(prompt); 92 } 93 94 @Override 95 public void close() { 96 saveHistory(); 97 } 98 99 private void saveHistory() { 100 try (Writer out = Files.newBufferedWriter(historyFile.toPath())) { 101 String lineSeparator = System.getProperty("line.separator"); 102 103 out.write(getHistory().save() 104 .stream() 105 .collect(Collectors.joining(lineSeparator))); 106 } catch (final IOException exp) {} 107 } 108 109 EditingHistory getHistory() { 110 return (EditingHistory) in.getHistory(); 111 } 112 113 boolean terminalEditorRunning() { 114 Terminal terminal = in.getTerminal(); 115 if (terminal instanceof JJSUnixTerminal) { 116 return ((JJSUnixTerminal) terminal).isRaw(); 117 } 118 return false; 119 } 120 121 void suspend() { 122 try { 123 in.getTerminal().restore(); 124 } catch (Exception ex) { 125 throw new IllegalStateException(ex); 126 } 127 } 128 129 void resume() { 130 try { 131 in.getTerminal().init(); 132 } catch (Exception ex) { 133 throw new IllegalStateException(ex); 134 } 135 } 136 137 static final class JJSUnixTerminal extends NoInterruptUnixTerminal { 138 JJSUnixTerminal() throws Exception { 139 } 140 141 boolean isRaw() { 142 try { 143 return getSettings().get("-a").contains("-icanon"); 144 } catch (IOException | InterruptedException ex) { 145 return false; 146 } 147 } 148 149 @Override 150 public void disableInterruptCharacter() { 151 } 152 153 @Override 154 public void enableInterruptCharacter() { 155 } 156 } 157 158 static final class JJSWindowsTerminal extends WindowsTerminal { 159 public JJSWindowsTerminal() throws Exception { 160 } 161 162 @Override 163 public void init() throws Exception { 164 super.init(); 165 setAnsiSupported(false); 166 } 167 } 168 169 private static boolean isCygwin() { 170 return System.getenv("SHELL") != null; 171 } 172 173 private void bind(String shortcut, Object action) { 174 KeyMap km = in.getKeys(); 175 for (int i = 0; i < shortcut.length(); i++) { 176 final Object value = km.getBound(Character.toString(shortcut.charAt(i))); 177 if (value instanceof KeyMap) { 178 km = (KeyMap) value; 179 } else { 180 km.bind(shortcut.substring(i), action); 181 } 182 } 183 } 184 185 private void showDocumentation(final Function<String, String> docHelper) { 186 final String buffer = in.getCursorBuffer().buffer.toString(); 187 final int cursor = in.getCursorBuffer().cursor; 188 final String doc = docHelper.apply(buffer.substring(0, cursor)); 189 try { 190 if (doc != null) { 191 in.println(); 192 in.println(doc); 193 in.redrawLine(); 194 in.flush(); 195 } else { 196 in.beep(); 197 } 198 } catch (IOException ex) { 199 throw new IllegalStateException(ex); 200 } 201 } 202} 203