1 /*
   2  * Copyright (c) 2015, 2018, 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 
  26 package jdk.nashorn.tools.jjs;
  27 
  28 import java.io.File;
  29 import java.io.IOException;
  30 import java.io.InputStream;
  31 import java.io.PrintStream;
  32 import java.io.Writer;
  33 import java.nio.file.Files;
  34 import java.util.ArrayList;
  35 import java.util.List;
  36 import java.util.function.Function;
  37 import java.util.stream.Collectors;
  38 import java.util.stream.StreamSupport;
  39 
  40 import jdk.internal.org.jline.reader.Candidate;
  41 import jdk.internal.org.jline.reader.CompletingParsedLine;
  42 import jdk.internal.org.jline.reader.EOFError;
  43 import jdk.internal.org.jline.reader.History;
  44 import jdk.internal.org.jline.reader.LineReader;
  45 import jdk.internal.org.jline.reader.LineReader.Option;
  46 import jdk.internal.org.jline.reader.LineReaderBuilder;
  47 import jdk.internal.org.jline.reader.Parser;
  48 import jdk.internal.org.jline.reader.Parser.ParseContext;
  49 import jdk.internal.org.jline.reader.Widget;
  50 import jdk.internal.org.jline.reader.impl.LineReaderImpl;
  51 import jdk.internal.org.jline.reader.impl.completer.ArgumentCompleter.ArgumentLine;
  52 import jdk.internal.org.jline.terminal.Attributes.LocalFlag;
  53 import jdk.internal.org.jline.terminal.Terminal;
  54 
  55 class Console implements AutoCloseable {
  56     private static final String DOCUMENTATION_SHORTCUT = "\033\133\132"; //Shift-TAB
  57     private final LineReader in;
  58     private final File historyFile;
  59 
  60     Console(final InputStream cmdin, final PrintStream cmdout, final File historyFile,
  61             final NashornCompleter completer, final Function<String, String> docHelper) throws IOException {
  62         this.historyFile = historyFile;
  63 
  64         Parser parser = (line, cursor, context) -> {
  65             if (context == ParseContext.COMPLETE) {
  66                 List<Candidate> candidates = new ArrayList<>();
  67                 int anchor = completer.complete(line, cursor, candidates);
  68                 anchor = Math.min(anchor, line.length());
  69                 return new CompletionLine(line.substring(anchor), cursor, candidates);
  70             } else if (!completer.isComplete(line)) {
  71                 throw new EOFError(cursor, cursor, line);
  72             }
  73             return new ArgumentLine(line, cursor);
  74         };
  75         in = LineReaderBuilder.builder()
  76                               .option(Option.DISABLE_EVENT_EXPANSION, true)
  77                               .completer((in, line, candidates) -> candidates.addAll(((CompletionLine) line).candidates))
  78                               .parser(parser)
  79                               .build();
  80         if (historyFile.exists()) {
  81             StringBuilder line = new StringBuilder();
  82             for (String h : Files.readAllLines(historyFile.toPath())) {
  83                 if (line.length() > 0) {
  84                     line.append("\n");
  85                 }
  86                 line.append(h);
  87                 try {
  88                     parser.parse(line.toString(), line.length());
  89                     in.getHistory().add(line.toString());
  90                     line.delete(0, line.length());
  91                 } catch (EOFError e) {
  92                     //continue;
  93                 }
  94             }
  95             if (line.length() > 0) {
  96                 in.getHistory().add(line.toString());
  97             }
  98         }
  99         Runtime.getRuntime().addShutdownHook(new Thread((Runnable)this::saveHistory));
 100         bind(DOCUMENTATION_SHORTCUT, ()->showDocumentation(docHelper));
 101     }
 102 
 103     String readLine(final String prompt, final String continuationPrompt) throws IOException {
 104         in.setVariable(LineReader.SECONDARY_PROMPT_PATTERN, continuationPrompt);
 105         return in.readLine(prompt);
 106     }
 107 
 108     String readUserLine(final String prompt) throws IOException {
 109         Parser prevParser = in.getParser();
 110 
 111         try {
 112             ((LineReaderImpl) in).setParser((line, cursor, context) -> new ArgumentLine(line, cursor));
 113             return in.readLine(prompt);
 114         } finally {
 115             ((LineReaderImpl) in).setParser(prevParser);
 116         }
 117     }
 118 
 119     @Override
 120     public void close() {
 121         saveHistory();
 122     }
 123 
 124     private void saveHistory() {
 125         try (Writer out = Files.newBufferedWriter(historyFile.toPath())) {
 126             String lineSeparator = System.getProperty("line.separator");
 127 
 128             out.write(StreamSupport.stream(getHistory().spliterator(), false)
 129                                    .map(e -> e.line())
 130                                    .collect(Collectors.joining(lineSeparator)));
 131         } catch (final IOException exp) {}
 132     }
 133 
 134     History getHistory() {
 135         return in.getHistory();
 136     }
 137 
 138     boolean terminalEditorRunning() {
 139         Terminal terminal = in.getTerminal();
 140         return !terminal.getAttributes().getLocalFlag(LocalFlag.ICANON);
 141     }
 142 
 143     void suspend() {
 144     }
 145 
 146     void resume() {
 147     }
 148 
 149     private void bind(String shortcut, Widget action) {
 150         in.getKeyMaps().get(LineReader.MAIN).bind(action, shortcut);
 151     }
 152 
 153     private boolean showDocumentation(final Function<String, String> docHelper) {
 154         final String buffer = in.getBuffer().toString();
 155         final int cursor = in.getBuffer().cursor();
 156         final String doc = docHelper.apply(buffer.substring(0, cursor));
 157         if (doc != null) {
 158             in.getTerminal().writer().println();
 159             in.printAbove(doc);
 160             return true;
 161         } else {
 162             return false;
 163         }
 164     }
 165 
 166     private static final class CompletionLine extends ArgumentLine implements CompletingParsedLine {
 167         public final List<Candidate> candidates;
 168 
 169         public CompletionLine(String word, int cursor, List<Candidate> candidates) {
 170             super(word, cursor);
 171             this.candidates = candidates;
 172         }
 173 
 174         public CharSequence escape(CharSequence candidate, boolean complete) {
 175             return candidate;
 176         }
 177 
 178         public int rawWordCursor() {
 179             return word().length();
 180         }
 181 
 182         public int rawWordLength() {
 183             return word().length();
 184         }
 185     }
 186 }