Console.java revision 1701:7ab7fc00b147
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;
45
46class Console implements AutoCloseable {
47    private static final String DOCUMENTATION_SHORTCUT = "\033\133\132"; //Shift-TAB
48    private final ConsoleReader in;
49    private final File historyFile;
50
51    Console(final InputStream cmdin, final PrintStream cmdout, final File historyFile,
52            final NashornCompleter completer, final Function<String, String> docHelper) throws IOException {
53        this.historyFile = historyFile;
54
55        TerminalFactory.registerFlavor(Flavor.WINDOWS, isCygwin()? JJSUnixTerminal::new : JJSWindowsTerminal::new);
56        TerminalFactory.registerFlavor(Flavor.UNIX, JJSUnixTerminal::new);
57        in = new ConsoleReader(cmdin, cmdout);
58        in.setExpandEvents(false);
59        in.setHandleUserInterrupt(true);
60        in.setBellEnabled(true);
61        in.setCopyPasteDetection(true);
62        in.setHistory(new EditingHistory(in, Files.readAllLines(historyFile.toPath())) {
63            @Override protected boolean isComplete(CharSequence input) {
64                return completer.isComplete(input.toString());
65            }
66        });
67        in.addCompleter(completer);
68        Runtime.getRuntime().addShutdownHook(new Thread((Runnable)this::saveHistory));
69        bind(DOCUMENTATION_SHORTCUT, (ActionListener)evt -> showDocumentation(docHelper));
70    }
71
72    String readLine(final String prompt) throws IOException {
73        return in.readLine(prompt);
74    }
75
76    @Override
77    public void close() {
78        saveHistory();
79    }
80
81    private void saveHistory() {
82        try (Writer out = Files.newBufferedWriter(historyFile.toPath())) {
83            String lineSeparator = System.getProperty("line.separator");
84
85            out.write(getHistory().save()
86                                  .stream()
87                                  .collect(Collectors.joining(lineSeparator)));
88        } catch (final IOException exp) {}
89    }
90
91    EditingHistory getHistory() {
92        return (EditingHistory) in.getHistory();
93    }
94
95    boolean terminalEditorRunning() {
96        Terminal terminal = in.getTerminal();
97        if (terminal instanceof JJSUnixTerminal) {
98            return ((JJSUnixTerminal) terminal).isRaw();
99        }
100        return false;
101    }
102
103    void suspend() {
104        try {
105            in.getTerminal().restore();
106        } catch (Exception ex) {
107            throw new IllegalStateException(ex);
108        }
109    }
110
111    void resume() {
112        try {
113            in.getTerminal().init();
114        } catch (Exception ex) {
115            throw new IllegalStateException(ex);
116        }
117    }
118
119    static final class JJSUnixTerminal extends NoInterruptUnixTerminal {
120        JJSUnixTerminal() throws Exception {
121        }
122
123        boolean isRaw() {
124            try {
125                return getSettings().get("-a").contains("-icanon");
126            } catch (IOException | InterruptedException ex) {
127                return false;
128            }
129        }
130
131        @Override
132        public void disableInterruptCharacter() {
133        }
134
135        @Override
136        public void enableInterruptCharacter() {
137        }
138    }
139
140    static final class JJSWindowsTerminal extends WindowsTerminal {
141        public JJSWindowsTerminal() throws Exception {
142        }
143
144        @Override
145        public void init() throws Exception {
146            super.init();
147            setAnsiSupported(false);
148        }
149    }
150
151    private static boolean isCygwin() {
152        return System.getenv("SHELL") != null;
153    }
154
155    private void bind(String shortcut, Object action) {
156        KeyMap km = in.getKeys();
157        for (int i = 0; i < shortcut.length(); i++) {
158            final Object value = km.getBound(Character.toString(shortcut.charAt(i)));
159            if (value instanceof KeyMap) {
160                km = (KeyMap) value;
161            } else {
162                km.bind(shortcut.substring(i), action);
163            }
164        }
165    }
166
167    private void showDocumentation(final Function<String, String> docHelper) {
168        final String buffer = in.getCursorBuffer().buffer.toString();
169        final int cursor = in.getCursorBuffer().cursor;
170        final String doc = docHelper.apply(buffer.substring(0, cursor));
171        try {
172            if (doc != null) {
173                in.println();
174                in.println(doc);
175                in.redrawLine();
176                in.flush();
177            } else {
178                in.beep();
179            }
180        } catch (IOException ex) {
181            throw new IllegalStateException(ex);
182        }
183    }
184}
185