ConsoleIOContext.java revision 3767:b265444e51db
1167857Simp/* 2167857Simp * Copyright (c) 2015, 2016, Oracle and/or its affiliates. All rights reserved. 3167857Simp * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4167857Simp * 5167857Simp * This code is free software; you can redistribute it and/or modify it 6167857Simp * under the terms of the GNU General Public License version 2 only, as 7167857Simp * published by the Free Software Foundation. Oracle designates this 8167857Simp * particular file as subject to the "Classpath" exception as provided 9167857Simp * by Oracle in the LICENSE file that accompanied this code. 10167857Simp * 11167857Simp * This code is distributed in the hope that it will be useful, but WITHOUT 12167857Simp * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13167857Simp * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14167857Simp * version 2 for more details (a copy is included in the LICENSE file that 15167857Simp * accompanied this code). 16167857Simp * 17167857Simp * You should have received a copy of the GNU General Public License version 18167857Simp * 2 along with this work; if not, write to the Free Software Foundation, 19167857Simp * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20167857Simp * 21167857Simp * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22167857Simp * or visit www.oracle.com if you need additional information or have any 23167857Simp * questions. 24167857Simp */ 25167857Simp 26167857Simppackage jdk.internal.jshell.tool; 27167857Simp 28167857Simpimport jdk.jshell.SourceCodeAnalysis.Documentation; 29167857Simpimport jdk.jshell.SourceCodeAnalysis.QualifiedNames; 30289727Sianimport jdk.jshell.SourceCodeAnalysis.Suggestion; 31289727Sian 32289727Sianimport java.io.IOException; 33167857Simpimport java.io.InputStream; 34167857Simpimport java.io.InterruptedIOException; 35167857Simpimport java.io.PrintStream; 36167857Simpimport java.io.UncheckedIOException; 37167857Simpimport java.util.ArrayList; 38167857Simpimport java.util.Arrays; 39167857Simpimport java.util.Collection; 40181305Sjhbimport java.util.Collections; 41346548Sianimport java.util.HashMap; 42167857Simpimport java.util.Iterator; 43167857Simpimport java.util.List; 44289727Sianimport java.util.Locale; 45289727Sianimport java.util.Map; 46289727Sianimport java.util.Objects; 47289727Sianimport java.util.Optional; 48289727Sianimport java.util.function.Function; 49289727Sianimport java.util.prefs.BackingStoreException; 50167857Simpimport java.util.stream.Collectors; 51167857Simpimport java.util.stream.Stream; 52167857Simp 53167857Simpimport jdk.internal.shellsupport.doc.JavadocFormatter; 54167857Simpimport jdk.internal.jline.NoInterruptUnixTerminal; 55289727Sianimport jdk.internal.jline.Terminal; 56289727Sianimport jdk.internal.jline.TerminalFactory; 57289727Sianimport jdk.internal.jline.TerminalSupport; 58289727Sianimport jdk.internal.jline.WindowsTerminal; 59289727Sianimport jdk.internal.jline.console.ConsoleReader; 60289727Sianimport jdk.internal.jline.console.KeyMap; 61167857Simpimport jdk.internal.jline.console.UserInterruptException; 62289727Sianimport jdk.internal.jline.console.completer.Completer; 63167857Simpimport jdk.internal.jline.console.history.History; 64167857Simpimport jdk.internal.jline.console.history.MemoryHistory; 65289727Sianimport jdk.internal.jline.extra.EditingHistory; 66167857Simpimport jdk.internal.jshell.tool.StopDetectingInputStream.State; 67289727Sianimport jdk.internal.misc.Signal; 68167857Simpimport jdk.internal.misc.Signal.Handler; 69289727Sian 70167857Simpclass ConsoleIOContext extends IOContext { 71167857Simp 72167857Simp private static final String HISTORY_LINE_PREFIX = "HISTORY_LINE_"; 73289727Sian 74289727Sian final JShellTool repl; 75289727Sian final StopDetectingInputStream input; 76289727Sian final ConsoleReader in; 77289727Sian final EditingHistory history; 78289727Sian final MemoryHistory userInputHistory = new MemoryHistory(); 79289727Sian 80289727Sian String prefix = ""; 81289727Sian 82289727Sian ConsoleIOContext(JShellTool repl, InputStream cmdin, PrintStream cmdout) throws Exception { 83289727Sian this.repl = repl; 84289727Sian this.input = new StopDetectingInputStream(() -> repl.state.stop(), ex -> repl.hard("Error on input: %s", ex)); 85289727Sian Terminal term; 86289727Sian if (System.getProperty("test.jdk") != null) { 87289727Sian term = new TestTerminal(input); 88289727Sian } else if (System.getProperty("os.name").toLowerCase(Locale.US).contains(TerminalFactory.WINDOWS)) { 89289727Sian term = new JShellWindowsTerminal(input); 90289727Sian } else { 91289727Sian term = new JShellUnixTerminal(input); 92289727Sian } 93289727Sian term.init(); 94289727Sian in = new ConsoleReader(cmdin, cmdout, term); 95289727Sian in.setExpandEvents(false); 96289727Sian in.setHandleUserInterrupt(true); 97289727Sian List<String> persistenHistory = Stream.of(repl.prefs.keys()) 98289727Sian .filter(key -> key.startsWith(HISTORY_LINE_PREFIX)) 99289727Sian .sorted() 100289727Sian .map(key -> repl.prefs.get(key, null)) 101289727Sian .collect(Collectors.toList()); 102289727Sian in.setHistory(history = new EditingHistory(in, persistenHistory) { 103289727Sian @Override protected boolean isComplete(CharSequence input) { 104289727Sian return repl.analysis.analyzeCompletion(input.toString()).completeness().isComplete(); 105289727Sian } 106289727Sian }); 107289727Sian in.setBellEnabled(true); 108289727Sian in.setCopyPasteDetection(true); 109289727Sian in.addCompleter(new Completer() { 110289727Sian private String lastTest; 111167857Simp private int lastCursor; 112167857Simp private boolean allowSmart = false; 113167857Simp @Override public int complete(String test, int cursor, List<CharSequence> result) { 114167857Simp int[] anchor = new int[] {-1}; 115167857Simp List<Suggestion> suggestions; 116167857Simp if (prefix.isEmpty() && test.trim().startsWith("/")) { 117167857Simp suggestions = repl.commandCompletionSuggestions(test, cursor, anchor); 118167857Simp } else { 119167857Simp int prefixLength = prefix.length(); 120167857Simp suggestions = repl.analysis.completionSuggestions(prefix + test, cursor + prefixLength, anchor); 121167857Simp anchor[0] -= prefixLength; 122181305Sjhb } 123167857Simp if (!Objects.equals(lastTest, test) || lastCursor != cursor) 124167857Simp allowSmart = true; 125167857Simp 126167857Simp boolean smart = allowSmart && 127167857Simp suggestions.stream() 128167857Simp .anyMatch(s -> s.matchesType()); 129289727Sian 130167857Simp lastTest = test; 131167857Simp lastCursor = cursor; 132167857Simp allowSmart = !allowSmart; 133289727Sian 134289083Sian suggestions.stream() 135289727Sian .filter(s -> !smart || s.matchesType()) 136289727Sian .map(s -> s.continuation()) 137289727Sian .forEach(result::add); 138289727Sian 139289727Sian boolean onlySmart = suggestions.stream() 140289727Sian .allMatch(s -> s.matchesType()); 141289727Sian 142289727Sian if (smart && !onlySmart) { 143289727Sian Optional<String> prefix = 144289727Sian suggestions.stream() 145289727Sian .map(s -> s.continuation()) 146289727Sian .reduce(ConsoleIOContext::commonPrefix); 147289727Sian 148289727Sian String prefixStr = prefix.orElse("").substring(cursor - anchor[0]); 149289727Sian try { 150289727Sian in.putString(prefixStr); 151289727Sian cursor += prefixStr.length(); 152289727Sian } catch (IOException ex) { 153289727Sian throw new IllegalStateException(ex); 154289727Sian } 155289727Sian result.add(repl.messageFormat("jshell.console.see.more")); 156289727Sian return cursor; //anchor should not be used. 157289727Sian } 158289727Sian 159289727Sian if (result.isEmpty()) { 160289727Sian try { 161289727Sian //provide "empty completion" feedback 162289727Sian //XXX: this only works correctly when there is only one Completer: 163289727Sian in.beep(); 164289727Sian } catch (IOException ex) { 165289727Sian throw new UncheckedIOException(ex); 166167857Simp } 167186833Snwhitehorn } 168167857Simp 169167857Simp return anchor[0]; 170289727Sian } 171289727Sian }); 172289727Sian bind(DOCUMENTATION_SHORTCUT, (Runnable) () -> documentation(repl)); 173289727Sian for (FixComputer computer : FIX_COMPUTERS) { 174289727Sian for (String shortcuts : SHORTCUT_FIXES) { 175289727Sian bind(shortcuts + computer.shortcut, (Runnable) () -> fixes(computer)); 176289727Sian } 177289727Sian } 178289727Sian try { 179289727Sian Signal.handle(new Signal("CONT"), new Handler() { 180289727Sian @Override public void handle(Signal sig) { 181289727Sian try { 182289727Sian in.getTerminal().reset(); 183289727Sian in.redrawLine(); 184167857Simp in.flush(); 185167857Simp } catch (Exception ex) { 186167857Simp ex.printStackTrace(); 187167857Simp } 188346548Sian } 189346548Sian }); 190167857Simp } catch (IllegalArgumentException ignored) { 191289727Sian //the CONT signal does not exist on this platform 192181325Sstas } 193289727Sian } 194289727Sian 195289727Sian @Override 196289727Sian public String readLine(String prompt, String prefix) throws IOException, InputInterruptedException { 197289727Sian this.prefix = prefix; 198289727Sian try { 199289727Sian return in.readLine(prompt); 200167857Simp } catch (UserInterruptException ex) { 201289727Sian throw (InputInterruptedException) new InputInterruptedException().initCause(ex); 202167857Simp } 203167857Simp } 204167857Simp 205167857Simp @Override 206289727Sian public boolean interactiveOutput() { 207167857Simp return true; 208167857Simp } 209346548Sian 210346548Sian @Override 211346548Sian public Iterable<String> currentSessionHistory() { 212346548Sian return history.currentSessionEntries(); 213346548Sian } 214346548Sian 215346548Sian @Override 216346548Sian public void close() throws IOException { 217346548Sian //save history: 218346548Sian try { 219289727Sian for (String key : repl.prefs.keys()) { 220167857Simp if (key.startsWith(HISTORY_LINE_PREFIX)) 221167857Simp repl.prefs.remove(key); 222323931Sian } 223323931Sian Collection<? extends String> savedHistory = history.save(); 224323931Sian if (!savedHistory.isEmpty()) { 225323931Sian int len = (int) Math.ceil(Math.log10(savedHistory.size()+1)); 226323931Sian String format = HISTORY_LINE_PREFIX + "%0" + len + "d"; 227323931Sian int index = 0; 228323931Sian for (String historyLine : savedHistory) { 229323931Sian repl.prefs.put(String.format(format, index++), historyLine); 230323931Sian } 231167857Simp } 232167857Simp } catch (BackingStoreException ex) { 233167857Simp throw new IllegalStateException(ex); 234323931Sian } 235167857Simp in.shutdown(); 236323931Sian try { 237323931Sian in.getTerminal().restore(); 238323931Sian } catch (Exception ex) { 239323931Sian throw new IOException(ex); 240289083Sian } 241167857Simp input.shutdown(); 242167857Simp } 243167857Simp 244167857Simp private void bind(String shortcut, Object action) { 245167857Simp KeyMap km = in.getKeys(); 246323931Sian for (int i = 0; i < shortcut.length(); i++) { 247167857Simp Object value = km.getBound(Character.toString(shortcut.charAt(i))); 248323931Sian if (value instanceof KeyMap) { 249323931Sian km = (KeyMap) value; 250167857Simp } else { 251167857Simp km.bind(shortcut.substring(i), action); 252167857Simp } 253167857Simp } 254167857Simp } 255167857Simp 256167857Simp private static final String DOCUMENTATION_SHORTCUT = "\033\133\132"; //Shift-TAB 257167857Simp private static final String[] SHORTCUT_FIXES = { 258167857Simp "\033\015", //Alt-Enter (Linux) 259167857Simp "\033\012", //Alt-Enter (Linux) 260167857Simp "\033\133\061\067\176", //F6/Alt-F1 (Mac) 261167857Simp "\u001BO3P" //Alt-F1 (Linux) 262167857Simp }; 263167857Simp 264167857Simp private String lastDocumentationBuffer; 265167857Simp private int lastDocumentationCursor = (-1); 266167857Simp 267167857Simp private void documentation(JShellTool repl) { 268167857Simp String buffer = in.getCursorBuffer().buffer.toString(); 269167857Simp int cursor = in.getCursorBuffer().cursor; 270167857Simp boolean firstInvocation = !buffer.equals(lastDocumentationBuffer) || cursor != lastDocumentationCursor; 271167857Simp lastDocumentationBuffer = buffer; 272167857Simp lastDocumentationCursor = cursor; 273167857Simp List<String> doc; 274167857Simp String seeMore; 275167857Simp Terminal term = in.getTerminal(); 276289727Sian if (prefix.isEmpty() && buffer.trim().startsWith("/")) { 277167857Simp doc = Arrays.asList(repl.commandDocumentation(buffer, cursor, firstInvocation)); 278167857Simp seeMore = "jshell.console.see.help"; 279167857Simp } else { 280167857Simp JavadocFormatter formatter = new JavadocFormatter(term.getWidth(), 281167857Simp term.isAnsiSupported()); 282167857Simp Function<Documentation, String> convertor; 283167857Simp if (firstInvocation) { 284167857Simp convertor = d -> d.signature(); 285167857Simp } else { 286167857Simp convertor = d -> formatter.formatJavadoc(d.signature(), d.javadoc()) + 287167857Simp (d.javadoc() == null ? repl.messageFormat("jshell.console.no.javadoc") 288167857Simp : ""); 289167857Simp } 290167857Simp doc = repl.analysis.documentation(prefix + buffer, cursor + prefix.length(), !firstInvocation) 291167857Simp .stream() 292167857Simp .map(convertor) 293167857Simp .collect(Collectors.toList()); 294167857Simp seeMore = "jshell.console.see.javadoc"; 295289727Sian } 296289105Sian 297289105Sian try { 298167857Simp if (doc != null && !doc.isEmpty()) { 299289105Sian if (firstInvocation) { 300167857Simp in.println(); 301167857Simp in.println(doc.stream().collect(Collectors.joining("\n"))); 302167857Simp in.println(repl.messageFormat(seeMore)); 303167857Simp in.redrawLine(); 304167857Simp in.flush(); 305167857Simp } else { 306167857Simp in.println(); 307167857Simp 308167857Simp int height = term.getHeight(); 309167857Simp String lastNote = ""; 310167857Simp 311167857Simp PRINT_DOC: for (Iterator<String> docIt = doc.iterator(); docIt.hasNext(); ) { 312167857Simp String currentDoc = docIt.next(); 313167857Simp String[] lines = currentDoc.split("\n"); 314167857Simp int firstLine = 0; 315167857Simp 316167857Simp PRINT_PAGE: while (true) { 317167857Simp int toPrint = height - 1; 318167857Simp 319167857Simp while (toPrint > 0 && firstLine < lines.length) { 320167857Simp in.println(lines[firstLine++]); 321167857Simp toPrint--; 322167857Simp } 323167857Simp 324167857Simp if (firstLine >= lines.length) { 325167857Simp break; 326167857Simp } 327167857Simp 328167857Simp lastNote = repl.getResourceString("jshell.console.see.next.page"); 329167857Simp in.print(lastNote + ConsoleReader.RESET_LINE); 330167857Simp in.flush(); 331289118Sian 332167857Simp while (true) { 333167857Simp int r = in.readCharacter(); 334167857Simp 335167857Simp switch (r) { 336167857Simp case ' ': continue PRINT_PAGE; 337167857Simp case 'q': 338167857Simp case 3: 339167857Simp break PRINT_DOC; 340167857Simp default: 341167857Simp in.beep(); 342167857Simp break; 343167857Simp } 344167857Simp } 345167857Simp } 346167857Simp 347167857Simp if (docIt.hasNext()) { 348167857Simp lastNote = repl.getResourceString("jshell.console.see.next.javadoc"); 349167857Simp in.print(lastNote + ConsoleReader.RESET_LINE); 350167857Simp in.flush(); 351167857Simp 352167857Simp while (true) { 353167857Simp int r = in.readCharacter(); 354167857Simp 355289727Sian switch (r) { 356289105Sian case ' ': continue PRINT_DOC; 357289105Sian case 'q': 358167857Simp case 3: 359289105Sian break PRINT_DOC; 360289083Sian default: 361167857Simp in.beep(); 362167857Simp break; 363289083Sian } 364289727Sian } 365289727Sian } 366167857Simp } 367289105Sian //clear the "press space" line: 368289105Sian in.getCursorBuffer().buffer.replace(0, buffer.length(), lastNote); 369289083Sian in.getCursorBuffer().cursor = 0; 370289105Sian in.killLine(); 371167857Simp in.getCursorBuffer().buffer.append(buffer); 372167857Simp in.getCursorBuffer().cursor = cursor; 373167857Simp in.redrawLine(); 374167857Simp in.flush(); 375167857Simp } 376167857Simp } else { 377167857Simp in.beep(); 378323931Sian } 379167857Simp } catch (IOException ex) { 380246128Ssbz throw new IllegalStateException(ex); 381167857Simp } 382167857Simp } 383167857Simp 384167857Simp private static String commonPrefix(String str1, String str2) { 385167857Simp for (int i = 0; i < str2.length(); i++) { 386167857Simp if (!str1.startsWith(str2.substring(0, i + 1))) { 387167857Simp return str2.substring(0, i); 388167857Simp } 389167857Simp } 390167857Simp 391167857Simp return str2; 392167857Simp } 393 394 @Override 395 public boolean terminalEditorRunning() { 396 Terminal terminal = in.getTerminal(); 397 if (terminal instanceof JShellUnixTerminal) 398 return ((JShellUnixTerminal) terminal).isRaw(); 399 return false; 400 } 401 402 @Override 403 public void suspend() { 404 try { 405 in.getTerminal().restore(); 406 } catch (Exception ex) { 407 throw new IllegalStateException(ex); 408 } 409 } 410 411 @Override 412 public void resume() { 413 try { 414 in.getTerminal().init(); 415 } catch (Exception ex) { 416 throw new IllegalStateException(ex); 417 } 418 } 419 420 public void beforeUserCode() { 421 synchronized (this) { 422 inputBytes = null; 423 } 424 input.setState(State.BUFFER); 425 } 426 427 public void afterUserCode() { 428 input.setState(State.WAIT); 429 } 430 431 @Override 432 public void replaceLastHistoryEntry(String source) { 433 history.fullHistoryReplace(source); 434 } 435 436 //compute possible options/Fixes based on the selected FixComputer, present them to the user, 437 //and perform the selected one: 438 private void fixes(FixComputer computer) { 439 String input = prefix + in.getCursorBuffer().toString(); 440 int cursor = prefix.length() + in.getCursorBuffer().cursor; 441 FixResult candidates = computer.compute(repl, input, cursor); 442 443 try { 444 final boolean printError = candidates.error != null && !candidates.error.isEmpty(); 445 if (printError) { 446 in.println(candidates.error); 447 } 448 if (candidates.fixes.isEmpty()) { 449 in.beep(); 450 if (printError) { 451 in.redrawLine(); 452 in.flush(); 453 } 454 } else if (candidates.fixes.size() == 1 && !computer.showMenu) { 455 if (printError) { 456 in.redrawLine(); 457 in.flush(); 458 } 459 candidates.fixes.get(0).perform(in); 460 } else { 461 List<Fix> fixes = new ArrayList<>(candidates.fixes); 462 fixes.add(0, new Fix() { 463 @Override 464 public String displayName() { 465 return repl.messageFormat("jshell.console.do.nothing"); 466 } 467 468 @Override 469 public void perform(ConsoleReader in) throws IOException { 470 in.redrawLine(); 471 } 472 }); 473 474 Map<Character, Fix> char2Fix = new HashMap<>(); 475 in.println(); 476 for (int i = 0; i < fixes.size(); i++) { 477 Fix fix = fixes.get(i); 478 char2Fix.put((char) ('0' + i), fix); 479 in.println("" + i + ": " + fixes.get(i).displayName()); 480 } 481 in.print(repl.messageFormat("jshell.console.choice")); 482 in.flush(); 483 int read; 484 485 read = in.readCharacter(); 486 487 Fix fix = char2Fix.get((char) read); 488 489 if (fix == null) { 490 in.beep(); 491 fix = fixes.get(0); 492 } 493 494 in.println(); 495 496 fix.perform(in); 497 498 in.flush(); 499 } 500 } catch (IOException ex) { 501 ex.printStackTrace(); 502 } 503 } 504 505 private byte[] inputBytes; 506 private int inputBytesPointer; 507 508 @Override 509 public synchronized int readUserInput() throws IOException { 510 while (inputBytes == null || inputBytes.length <= inputBytesPointer) { 511 boolean prevHandleUserInterrupt = in.getHandleUserInterrupt(); 512 History prevHistory = in.getHistory(); 513 514 try { 515 input.setState(State.WAIT); 516 in.setHandleUserInterrupt(true); 517 in.setHistory(userInputHistory); 518 inputBytes = (in.readLine("") + System.getProperty("line.separator")).getBytes(); 519 inputBytesPointer = 0; 520 } catch (UserInterruptException ex) { 521 throw new InterruptedIOException(); 522 } finally { 523 in.setHistory(prevHistory); 524 in.setHandleUserInterrupt(prevHandleUserInterrupt); 525 input.setState(State.BUFFER); 526 } 527 } 528 return inputBytes[inputBytesPointer++]; 529 } 530 531 /** 532 * A possible action which the user can choose to perform. 533 */ 534 public interface Fix { 535 /** 536 * A name that should be shown to the user. 537 */ 538 public String displayName(); 539 /** 540 * Perform the given action. 541 */ 542 public void perform(ConsoleReader in) throws IOException; 543 } 544 545 /** 546 * A factory for {@link Fix}es. 547 */ 548 public abstract static class FixComputer { 549 private final char shortcut; 550 private final boolean showMenu; 551 552 /** 553 * Construct a new FixComputer. {@code shortcut} defines the key which should trigger this FixComputer. 554 * If {@code showMenu} is {@code false}, and this computer returns exactly one {@code Fix}, 555 * no options will be show to the user, and the given {@code Fix} will be performed. 556 */ 557 public FixComputer(char shortcut, boolean showMenu) { 558 this.shortcut = shortcut; 559 this.showMenu = showMenu; 560 } 561 562 /** 563 * Compute possible actions for the given code. 564 */ 565 public abstract FixResult compute(JShellTool repl, String code, int cursor); 566 } 567 568 /** 569 * A list of {@code Fix}es with a possible error that should be shown to the user. 570 */ 571 public static class FixResult { 572 public final List<Fix> fixes; 573 public final String error; 574 575 public FixResult(List<Fix> fixes, String error) { 576 this.fixes = fixes; 577 this.error = error; 578 } 579 } 580 581 private static final FixComputer[] FIX_COMPUTERS = new FixComputer[] { 582 new FixComputer('v', false) { //compute "Introduce variable" Fix: 583 private void performToVar(ConsoleReader in, String type) throws IOException { 584 in.redrawLine(); 585 in.setCursorPosition(0); 586 in.putString(type + " = "); 587 in.setCursorPosition(in.getCursorBuffer().cursor - 3); 588 in.flush(); 589 } 590 591 @Override 592 public FixResult compute(JShellTool repl, String code, int cursor) { 593 String type = repl.analysis.analyzeType(code, cursor); 594 if (type == null) { 595 return new FixResult(Collections.emptyList(), null); 596 } 597 List<Fix> fixes = new ArrayList<>(); 598 fixes.add(new Fix() { 599 @Override 600 public String displayName() { 601 return repl.messageFormat("jshell.console.create.variable"); 602 } 603 604 @Override 605 public void perform(ConsoleReader in) throws IOException { 606 performToVar(in, type); 607 } 608 }); 609 int idx = type.lastIndexOf("."); 610 if (idx > 0) { 611 String stype = type.substring(idx + 1); 612 QualifiedNames res = repl.analysis.listQualifiedNames(stype, stype.length()); 613 if (res.isUpToDate() && res.getNames().contains(type) 614 && !res.isResolvable()) { 615 fixes.add(new Fix() { 616 @Override 617 public String displayName() { 618 return "import: " + type + ". " + 619 repl.messageFormat("jshell.console.create.variable"); 620 } 621 622 @Override 623 public void perform(ConsoleReader in) throws IOException { 624 repl.state.eval("import " + type + ";"); 625 in.println("Imported: " + type); 626 performToVar(in, stype); 627 } 628 }); 629 } 630 } 631 return new FixResult(fixes, null); 632 } 633 }, 634 new FixComputer('i', true) { //compute "Add import" Fixes: 635 @Override 636 public FixResult compute(JShellTool repl, String code, int cursor) { 637 QualifiedNames res = repl.analysis.listQualifiedNames(code, cursor); 638 List<Fix> fixes = new ArrayList<>(); 639 for (String fqn : res.getNames()) { 640 fixes.add(new Fix() { 641 @Override 642 public String displayName() { 643 return "import: " + fqn; 644 } 645 646 @Override 647 public void perform(ConsoleReader in) throws IOException { 648 repl.state.eval("import " + fqn + ";"); 649 in.println("Imported: " + fqn); 650 in.redrawLine(); 651 } 652 }); 653 } 654 if (res.isResolvable()) { 655 return new FixResult(Collections.emptyList(), 656 repl.messageFormat("jshell.console.resolvable")); 657 } else { 658 String error = ""; 659 if (fixes.isEmpty()) { 660 error = repl.messageFormat("jshell.console.no.candidate"); 661 } 662 if (!res.isUpToDate()) { 663 error += repl.messageFormat("jshell.console.incomplete"); 664 } 665 return new FixResult(fixes, error); 666 } 667 } 668 } 669 }; 670 671 private static final class JShellUnixTerminal extends NoInterruptUnixTerminal { 672 673 private final StopDetectingInputStream input; 674 675 public JShellUnixTerminal(StopDetectingInputStream input) throws Exception { 676 this.input = input; 677 } 678 679 public boolean isRaw() { 680 try { 681 return getSettings().get("-a").contains("-icanon"); 682 } catch (IOException | InterruptedException ex) { 683 return false; 684 } 685 } 686 687 @Override 688 public InputStream wrapInIfNeeded(InputStream in) throws IOException { 689 return input.setInputStream(super.wrapInIfNeeded(in)); 690 } 691 692 @Override 693 public void disableInterruptCharacter() { 694 } 695 696 @Override 697 public void enableInterruptCharacter() { 698 } 699 700 } 701 702 private static final class JShellWindowsTerminal extends WindowsTerminal { 703 704 private final StopDetectingInputStream input; 705 706 public JShellWindowsTerminal(StopDetectingInputStream input) throws Exception { 707 this.input = input; 708 } 709 710 @Override 711 public void init() throws Exception { 712 super.init(); 713 setAnsiSupported(false); 714 } 715 716 @Override 717 public InputStream wrapInIfNeeded(InputStream in) throws IOException { 718 return input.setInputStream(super.wrapInIfNeeded(in)); 719 } 720 721 } 722 723 private static final class TestTerminal extends TerminalSupport { 724 725 private final StopDetectingInputStream input; 726 727 public TestTerminal(StopDetectingInputStream input) throws Exception { 728 super(true); 729 setAnsiSupported(false); 730 setEchoEnabled(true); 731 this.input = input; 732 } 733 734 @Override 735 public InputStream wrapInIfNeeded(InputStream in) throws IOException { 736 return input.setInputStream(super.wrapInIfNeeded(in)); 737 } 738 739 } 740} 741