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