JShellTool.java revision 3827:44bdefe64114
1/* 2 * Copyright (c) 2014, 2016, 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.internal.jshell.tool; 27 28import java.io.BufferedWriter; 29import java.io.File; 30import java.io.FileNotFoundException; 31import java.io.FileReader; 32import java.io.IOException; 33import java.io.InputStream; 34import java.io.PrintStream; 35import java.io.Reader; 36import java.io.StringReader; 37import java.nio.charset.Charset; 38import java.nio.file.AccessDeniedException; 39import java.nio.file.FileSystems; 40import java.nio.file.Files; 41import java.nio.file.NoSuchFileException; 42import java.nio.file.Path; 43import java.nio.file.Paths; 44import java.text.MessageFormat; 45import java.util.ArrayList; 46import java.util.Arrays; 47import java.util.Collections; 48import java.util.HashSet; 49import java.util.Iterator; 50import java.util.LinkedHashMap; 51import java.util.LinkedHashSet; 52import java.util.List; 53import java.util.Locale; 54import java.util.Map; 55import java.util.Map.Entry; 56import java.util.Scanner; 57import java.util.Set; 58import java.util.function.Consumer; 59import java.util.function.Predicate; 60import java.util.prefs.Preferences; 61import java.util.regex.Matcher; 62import java.util.regex.Pattern; 63import java.util.stream.Collectors; 64import java.util.stream.Stream; 65import java.util.stream.StreamSupport; 66 67import jdk.internal.jshell.debug.InternalDebugControl; 68import jdk.internal.jshell.tool.IOContext.InputInterruptedException; 69import jdk.jshell.DeclarationSnippet; 70import jdk.jshell.Diag; 71import jdk.jshell.EvalException; 72import jdk.jshell.ExpressionSnippet; 73import jdk.jshell.ImportSnippet; 74import jdk.jshell.JShell; 75import jdk.jshell.JShell.Subscription; 76import jdk.jshell.MethodSnippet; 77import jdk.jshell.Snippet; 78import jdk.jshell.Snippet.Status; 79import jdk.jshell.SnippetEvent; 80import jdk.jshell.SourceCodeAnalysis; 81import jdk.jshell.SourceCodeAnalysis.CompletionInfo; 82import jdk.jshell.SourceCodeAnalysis.Suggestion; 83import jdk.jshell.TypeDeclSnippet; 84import jdk.jshell.UnresolvedReferenceException; 85import jdk.jshell.VarSnippet; 86 87import static java.nio.file.StandardOpenOption.CREATE; 88import static java.nio.file.StandardOpenOption.TRUNCATE_EXISTING; 89import static java.nio.file.StandardOpenOption.WRITE; 90import java.util.MissingResourceException; 91import java.util.Optional; 92import java.util.ResourceBundle; 93import java.util.ServiceLoader; 94import java.util.Spliterators; 95import java.util.function.Function; 96import java.util.function.Supplier; 97import jdk.internal.joptsimple.*; 98import jdk.internal.jshell.tool.Feedback.FormatAction; 99import jdk.internal.jshell.tool.Feedback.FormatCase; 100import jdk.internal.jshell.tool.Feedback.FormatErrors; 101import jdk.internal.jshell.tool.Feedback.FormatResolve; 102import jdk.internal.jshell.tool.Feedback.FormatUnresolved; 103import jdk.internal.jshell.tool.Feedback.FormatWhen; 104import jdk.internal.editor.spi.BuildInEditorProvider; 105import jdk.internal.editor.external.ExternalEditor; 106import static java.util.Arrays.asList; 107import static java.util.Arrays.stream; 108import static java.util.stream.Collectors.joining; 109import static java.util.stream.Collectors.toList; 110import static jdk.jshell.Snippet.SubKind.VAR_VALUE_SUBKIND; 111import static java.util.stream.Collectors.toMap; 112import static jdk.internal.jshell.debug.InternalDebugControl.DBG_COMPA; 113import static jdk.internal.jshell.debug.InternalDebugControl.DBG_DEP; 114import static jdk.internal.jshell.debug.InternalDebugControl.DBG_EVNT; 115import static jdk.internal.jshell.debug.InternalDebugControl.DBG_FMGR; 116import static jdk.internal.jshell.debug.InternalDebugControl.DBG_GEN; 117import static jdk.internal.jshell.tool.ContinuousCompletionProvider.STARTSWITH_MATCHER; 118 119/** 120 * Command line REPL tool for Java using the JShell API. 121 * @author Robert Field 122 */ 123public class JShellTool implements MessageHandler { 124 125 private static final String LINE_SEP = System.getProperty("line.separator"); 126 private static final Pattern LINEBREAK = Pattern.compile("\\R"); 127 private static final String RECORD_SEPARATOR = "\u241E"; 128 private static final String RB_NAME_PREFIX = "jdk.internal.jshell.tool.resources"; 129 private static final String VERSION_RB_NAME = RB_NAME_PREFIX + ".version"; 130 private static final String L10N_RB_NAME = RB_NAME_PREFIX + ".l10n"; 131 132 final InputStream cmdin; 133 final PrintStream cmdout; 134 final PrintStream cmderr; 135 final PrintStream console; 136 final InputStream userin; 137 final PrintStream userout; 138 final PrintStream usererr; 139 final Preferences prefs; 140 final Map<String, String> envvars; 141 final Locale locale; 142 143 final Feedback feedback = new Feedback(); 144 145 /** 146 * Simple constructor for the tool used by main. 147 * @param in command line input 148 * @param out command line output, feedback including errors, user System.out 149 * @param err start-up errors and debugging info, user System.err 150 */ 151 public JShellTool(InputStream in, PrintStream out, PrintStream err) { 152 this(in, out, err, out, null, out, err, 153 Preferences.userRoot().node("tool/JShell"), 154 System.getenv(), 155 Locale.getDefault()); 156 } 157 158 /** 159 * The complete constructor for the tool (used by test harnesses). 160 * @param cmdin command line input -- snippets and commands 161 * @param cmdout command line output, feedback including errors 162 * @param cmderr start-up errors and debugging info 163 * @param console console control interaction 164 * @param userin code execution input, or null to use IOContext 165 * @param userout code execution output -- System.out.printf("hi") 166 * @param usererr code execution error stream -- System.err.printf("Oops") 167 * @param prefs preferences to use 168 * @param envvars environment variable mapping to use 169 * @param locale locale to use 170 */ 171 public JShellTool(InputStream cmdin, PrintStream cmdout, PrintStream cmderr, 172 PrintStream console, 173 InputStream userin, PrintStream userout, PrintStream usererr, 174 Preferences prefs, Map<String, String> envvars, Locale locale) { 175 this.cmdin = cmdin; 176 this.cmdout = cmdout; 177 this.cmderr = cmderr; 178 this.console = console; 179 this.userin = userin != null ? userin : new InputStream() { 180 @Override 181 public int read() throws IOException { 182 return input.readUserInput(); 183 } 184 }; 185 this.userout = userout; 186 this.usererr = usererr; 187 this.prefs = prefs; 188 this.envvars = envvars; 189 this.locale = locale; 190 } 191 192 private ResourceBundle versionRB = null; 193 private ResourceBundle outputRB = null; 194 195 private IOContext input = null; 196 private boolean regenerateOnDeath = true; 197 private boolean live = false; 198 private boolean feedbackInitialized = false; 199 private String commandLineFeedbackMode = null; 200 private List<String> remoteVMOptions = new ArrayList<>(); 201 private List<String> compilerOptions = new ArrayList<>(); 202 203 SourceCodeAnalysis analysis; 204 JShell state = null; 205 Subscription shutdownSubscription = null; 206 207 static final EditorSetting BUILT_IN_EDITOR = new EditorSetting(null, false); 208 209 private boolean debug = false; 210 public boolean testPrompt = false; 211 private String cmdlineClasspath = null; 212 private String startup = null; 213 private EditorSetting editor = BUILT_IN_EDITOR; 214 215 private static final String[] EDITOR_ENV_VARS = new String[] { 216 "JSHELLEDITOR", "VISUAL", "EDITOR"}; 217 218 // Commands and snippets which should be replayed 219 private List<String> replayableHistory; 220 private List<String> replayableHistoryPrevious; 221 222 static final String STARTUP_KEY = "STARTUP"; 223 static final String EDITOR_KEY = "EDITOR"; 224 static final String FEEDBACK_KEY = "FEEDBACK"; 225 static final String MODE_KEY = "MODE"; 226 static final String REPLAY_RESTORE_KEY = "REPLAY_RESTORE"; 227 228 static final String DEFAULT_STARTUP = 229 "\n" + 230 "import java.util.*;\n" + 231 "import java.io.*;\n" + 232 "import java.math.*;\n" + 233 "import java.net.*;\n" + 234 "import java.util.concurrent.*;\n" + 235 "import java.util.prefs.*;\n" + 236 "import java.util.regex.*;\n" + 237 "void printf(String format, Object... args) { System.out.printf(format, args); }\n"; 238 239 // Tool id (tid) mapping: the three name spaces 240 NameSpace mainNamespace; 241 NameSpace startNamespace; 242 NameSpace errorNamespace; 243 244 // Tool id (tid) mapping: the current name spaces 245 NameSpace currentNameSpace; 246 247 Map<Snippet,SnippetInfo> mapSnippet; 248 249 /** 250 * Is the input/output currently interactive 251 * 252 * @return true if console 253 */ 254 boolean interactive() { 255 return input != null && input.interactiveOutput(); 256 } 257 258 void debug(String format, Object... args) { 259 if (debug) { 260 cmderr.printf(format + "\n", args); 261 } 262 } 263 264 /** 265 * Base output for command output -- no pre- or post-fix 266 * 267 * @param printf format 268 * @param printf args 269 */ 270 void rawout(String format, Object... args) { 271 cmdout.printf(format, args); 272 } 273 274 /** 275 * Must show command output 276 * 277 * @param format printf format 278 * @param args printf args 279 */ 280 @Override 281 public void hard(String format, Object... args) { 282 rawout(prefix(format), args); 283 } 284 285 /** 286 * Error command output 287 * 288 * @param format printf format 289 * @param args printf args 290 */ 291 void error(String format, Object... args) { 292 rawout(prefixError(format), args); 293 } 294 295 /** 296 * Should optional informative be displayed? 297 * @return true if they should be displayed 298 */ 299 @Override 300 public boolean showFluff() { 301 return feedback.shouldDisplayCommandFluff() && interactive(); 302 } 303 304 /** 305 * Optional output 306 * 307 * @param format printf format 308 * @param args printf args 309 */ 310 @Override 311 public void fluff(String format, Object... args) { 312 if (showFluff()) { 313 hard(format, args); 314 } 315 } 316 317 /** 318 * Resource bundle look-up 319 * 320 * @param key the resource key 321 */ 322 String getResourceString(String key) { 323 if (outputRB == null) { 324 try { 325 outputRB = ResourceBundle.getBundle(L10N_RB_NAME, locale); 326 } catch (MissingResourceException mre) { 327 error("Cannot find ResourceBundle: %s for locale: %s", L10N_RB_NAME, locale); 328 return ""; 329 } 330 } 331 String s; 332 try { 333 s = outputRB.getString(key); 334 } catch (MissingResourceException mre) { 335 error("Missing resource: %s in %s", key, L10N_RB_NAME); 336 return ""; 337 } 338 return s; 339 } 340 341 /** 342 * Add normal prefixing/postfixing to embedded newlines in a string, 343 * bracketing with normal prefix/postfix 344 * 345 * @param s the string to prefix 346 * @return the pre/post-fixed and bracketed string 347 */ 348 String prefix(String s) { 349 return prefix(s, feedback.getPre(), feedback.getPost()); 350 } 351 352 /** 353 * Add error prefixing/postfixing to embedded newlines in a string, 354 * bracketing with error prefix/postfix 355 * 356 * @param s the string to prefix 357 * @return the pre/post-fixed and bracketed string 358 */ 359 String prefixError(String s) { 360 return prefix(s, feedback.getErrorPre(), feedback.getErrorPost()); 361 } 362 363 /** 364 * Add prefixing/postfixing to embedded newlines in a string, 365 * bracketing with prefix/postfix 366 * 367 * @param s the string to prefix 368 * @param pre the string to prepend to each line 369 * @param post the string to append to each line (replacing newline) 370 * @return the pre/post-fixed and bracketed string 371 */ 372 String prefix(String s, String pre, String post) { 373 if (s == null) { 374 return ""; 375 } 376 String pp = s.replaceAll("\\R", post + pre); 377 return pre + pp + post; 378 } 379 380 /** 381 * Print using resource bundle look-up and adding prefix and postfix 382 * 383 * @param key the resource key 384 */ 385 void hardrb(String key) { 386 hard(getResourceString(key)); 387 } 388 389 /** 390 * Format using resource bundle look-up using MessageFormat 391 * 392 * @param key the resource key 393 * @param args 394 */ 395 String messageFormat(String key, Object... args) { 396 String rs = getResourceString(key); 397 return MessageFormat.format(rs, args); 398 } 399 400 /** 401 * Print using resource bundle look-up, MessageFormat, and add prefix and 402 * postfix 403 * 404 * @param key the resource key 405 * @param args 406 */ 407 @Override 408 public void hardmsg(String key, Object... args) { 409 hard(messageFormat(key, args)); 410 } 411 412 /** 413 * Print error using resource bundle look-up, MessageFormat, and add prefix 414 * and postfix 415 * 416 * @param key the resource key 417 * @param args 418 */ 419 @Override 420 public void errormsg(String key, Object... args) { 421 if (isRunningInteractive()) { 422 rawout(prefixError(messageFormat(key, args))); 423 } else { 424 startmsg(key, args); 425 } 426 } 427 428 /** 429 * Print command-line error using resource bundle look-up, MessageFormat 430 * 431 * @param key the resource key 432 * @param args 433 */ 434 void startmsg(String key, Object... args) { 435 cmderr.println(messageFormat(key, args)); 436 } 437 438 /** 439 * Print (fluff) using resource bundle look-up, MessageFormat, and add 440 * prefix and postfix 441 * 442 * @param key the resource key 443 * @param args 444 */ 445 @Override 446 public void fluffmsg(String key, Object... args) { 447 if (showFluff()) { 448 hardmsg(key, args); 449 } 450 } 451 452 <T> void hardPairs(Stream<T> stream, Function<T, String> a, Function<T, String> b) { 453 Map<String, String> a2b = stream.collect(toMap(a, b, 454 (m1, m2) -> m1, 455 LinkedHashMap::new)); 456 for (Entry<String, String> e : a2b.entrySet()) { 457 hard("%s", e.getKey()); 458 rawout(prefix(e.getValue(), feedback.getPre() + "\t", feedback.getPost())); 459 } 460 } 461 462 /** 463 * Trim whitespace off end of string 464 * 465 * @param s 466 * @return 467 */ 468 static String trimEnd(String s) { 469 int last = s.length() - 1; 470 int i = last; 471 while (i >= 0 && Character.isWhitespace(s.charAt(i))) { 472 --i; 473 } 474 if (i != last) { 475 return s.substring(0, i + 1); 476 } else { 477 return s; 478 } 479 } 480 481 /** 482 * Normal start entry point 483 * @param args 484 * @throws Exception 485 */ 486 public static void main(String[] args) throws Exception { 487 new JShellTool(System.in, System.out, System.err) 488 .start(args); 489 } 490 491 public void start(String[] args) throws Exception { 492 List<String> loadList = processCommandArgs(args); 493 if (loadList == null) { 494 // Abort 495 return; 496 } 497 try (IOContext in = new ConsoleIOContext(this, cmdin, console)) { 498 start(in, loadList); 499 } 500 } 501 502 private void start(IOContext in, List<String> loadList) { 503 // If startup hasn't been set by command line, set from retained/default 504 if (startup == null) { 505 startup = prefs.get(STARTUP_KEY, null); 506 if (startup == null) { 507 startup = DEFAULT_STARTUP; 508 } 509 } 510 511 configEditor(); 512 513 resetState(); // Initialize 514 515 // Read replay history from last jshell session into previous history 516 String prevReplay = prefs.get(REPLAY_RESTORE_KEY, null); 517 if (prevReplay != null) { 518 replayableHistoryPrevious = Arrays.asList(prevReplay.split(RECORD_SEPARATOR)); 519 } 520 521 for (String loadFile : loadList) { 522 runFile(loadFile, "jshell"); 523 } 524 525 if (regenerateOnDeath && feedback.shouldDisplayCommandFluff()) { 526 hardmsg("jshell.msg.welcome", version()); 527 } 528 529 try { 530 while (regenerateOnDeath) { 531 if (!live) { 532 resetState(); 533 } 534 run(in); 535 } 536 } finally { 537 closeState(); 538 } 539 } 540 541 private EditorSetting configEditor() { 542 // Read retained editor setting (if any) 543 editor = EditorSetting.fromPrefs(prefs); 544 if (editor != null) { 545 return editor; 546 } 547 // Try getting editor setting from OS environment variables 548 for (String envvar : EDITOR_ENV_VARS) { 549 String v = envvars.get(envvar); 550 if (v != null) { 551 return editor = new EditorSetting(v.split("\\s+"), false); 552 } 553 } 554 // Default to the built-in editor 555 return editor = BUILT_IN_EDITOR; 556 } 557 558 /** 559 * Process the command line arguments. 560 * Set options. 561 * @param args the command line arguments 562 * @return the list of files to be loaded 563 */ 564 private List<String> processCommandArgs(String[] args) { 565 OptionParser parser = new OptionParser(); 566 OptionSpec<String> cp = parser.accepts("class-path").withRequiredArg(); 567 OptionSpec<String> mpath = parser.accepts("module-path").withRequiredArg(); 568 OptionSpec<String> amods = parser.accepts("add-modules").withRequiredArg(); 569 OptionSpec<String> st = parser.accepts("startup").withRequiredArg(); 570 parser.acceptsAll(asList("n", "no-startup")); 571 OptionSpec<String> fb = parser.accepts("feedback").withRequiredArg(); 572 parser.accepts("q"); 573 parser.accepts("s"); 574 parser.accepts("v"); 575 OptionSpec<String> r = parser.accepts("R").withRequiredArg(); 576 OptionSpec<String> c = parser.accepts("C").withRequiredArg(); 577 parser.acceptsAll(asList("h", "help")); 578 parser.accepts("version"); 579 parser.accepts("full-version"); 580 581 parser.accepts("X"); 582 OptionSpec<String> addExports = parser.accepts("add-exports").withRequiredArg(); 583 584 NonOptionArgumentSpec<String> loadFileSpec = parser.nonOptions(); 585 586 OptionSet options; 587 try { 588 options = parser.parse(args); 589 } catch (OptionException ex) { 590 if (ex.options().isEmpty()) { 591 startmsg("jshell.err.opt.invalid", stream(args).collect(joining(", "))); 592 } else { 593 boolean isKnown = parser.recognizedOptions().containsKey(ex.options().iterator().next()); 594 startmsg(isKnown 595 ? "jshell.err.opt.arg" 596 : "jshell.err.opt.unknown", 597 ex.options() 598 .stream() 599 .collect(joining(", "))); 600 } 601 return null; 602 } 603 604 if (options.has("help")) { 605 printUsage(); 606 return null; 607 } 608 if (options.has("X")) { 609 printUsageX(); 610 return null; 611 } 612 if (options.has("version")) { 613 cmdout.printf("jshell %s\n", version()); 614 return null; 615 } 616 if (options.has("full-version")) { 617 cmdout.printf("jshell %s\n", fullVersion()); 618 return null; 619 } 620 if (options.has(cp)) { 621 List<String> cps = options.valuesOf(cp); 622 if (cps.size() > 1) { 623 startmsg("jshell.err.opt.one", "--class-path"); 624 return null; 625 } 626 cmdlineClasspath = cps.get(0); 627 } 628 if (options.has(st)) { 629 List<String> sts = options.valuesOf(st); 630 if (sts.size() != 1 || options.has("no-startup")) { 631 startmsg("jshell.err.opt.startup.one"); 632 return null; 633 } 634 startup = readFile(sts.get(0), "--startup"); 635 if (startup == null) { 636 return null; 637 } 638 } else if (options.has("no-startup")) { 639 startup = ""; 640 } 641 if ((options.valuesOf(fb).size() + 642 (options.has("q") ? 1 : 0) + 643 (options.has("s") ? 1 : 0) + 644 (options.has("v") ? 1 : 0)) > 1) { 645 startmsg("jshell.err.opt.feedback.one"); 646 return null; 647 } else if (options.has(fb)) { 648 commandLineFeedbackMode = options.valueOf(fb); 649 } else if (options.has("q")) { 650 commandLineFeedbackMode = "concise"; 651 } else if (options.has("s")) { 652 commandLineFeedbackMode = "silent"; 653 } else if (options.has("v")) { 654 commandLineFeedbackMode = "verbose"; 655 } 656 if (options.has(r)) { 657 remoteVMOptions.addAll(options.valuesOf(r)); 658 } 659 if (options.has(c)) { 660 compilerOptions.addAll(options.valuesOf(c)); 661 } 662 if (options.has(mpath)) { 663 compilerOptions.add("--module-path"); 664 compilerOptions.addAll(options.valuesOf(mpath)); 665 remoteVMOptions.add("--module-path"); 666 remoteVMOptions.addAll(options.valuesOf(mpath)); 667 } 668 if (options.has(amods)) { 669 compilerOptions.add("--add-modules"); 670 compilerOptions.addAll(options.valuesOf(amods)); 671 remoteVMOptions.add("--add-modules"); 672 remoteVMOptions.addAll(options.valuesOf(amods)); 673 } 674 675 if (options.has(addExports)) { 676 List<String> exports = options.valuesOf(addExports).stream() 677 .map(mp -> mp + "=ALL-UNNAMED") 678 .flatMap(mp -> Stream.of("--add-exports", mp)) 679 .collect(toList()); 680 remoteVMOptions.addAll(exports); 681 compilerOptions.addAll(exports); 682 } 683 684 return options.valuesOf(loadFileSpec); 685 } 686 687 private void printUsage() { 688 cmdout.print(getResourceString("help.usage")); 689 } 690 691 private void printUsageX() { 692 cmdout.print(getResourceString("help.usage.x")); 693 } 694 695 /** 696 * Message handler to use during initial start-up. 697 */ 698 private class InitMessageHandler implements MessageHandler { 699 700 @Override 701 public void fluff(String format, Object... args) { 702 //ignore 703 } 704 705 @Override 706 public void fluffmsg(String messageKey, Object... args) { 707 //ignore 708 } 709 710 @Override 711 public void hard(String format, Object... args) { 712 //ignore 713 } 714 715 @Override 716 public void hardmsg(String messageKey, Object... args) { 717 //ignore 718 } 719 720 @Override 721 public void errormsg(String messageKey, Object... args) { 722 startmsg(messageKey, args); 723 } 724 725 @Override 726 public boolean showFluff() { 727 return false; 728 } 729 } 730 731 private void resetState() { 732 closeState(); 733 734 // Initialize tool id mapping 735 mainNamespace = new NameSpace("main", ""); 736 startNamespace = new NameSpace("start", "s"); 737 errorNamespace = new NameSpace("error", "e"); 738 mapSnippet = new LinkedHashMap<>(); 739 currentNameSpace = startNamespace; 740 741 // Reset the replayable history, saving the old for restore 742 replayableHistoryPrevious = replayableHistory; 743 replayableHistory = new ArrayList<>(); 744 745 state = JShell.builder() 746 .in(userin) 747 .out(userout) 748 .err(usererr) 749 .tempVariableNameGenerator(()-> "$" + currentNameSpace.tidNext()) 750 .idGenerator((sn, i) -> (currentNameSpace == startNamespace || state.status(sn).isActive()) 751 ? currentNameSpace.tid(sn) 752 : errorNamespace.tid(sn)) 753 .remoteVMOptions(remoteVMOptions.stream().toArray(String[]::new)) 754 .compilerOptions(compilerOptions.stream().toArray(String[]::new)) 755 .build(); 756 shutdownSubscription = state.onShutdown((JShell deadState) -> { 757 if (deadState == state) { 758 hardmsg("jshell.msg.terminated"); 759 live = false; 760 } 761 }); 762 analysis = state.sourceCodeAnalysis(); 763 live = true; 764 if (!feedbackInitialized) { 765 // One time per run feedback initialization 766 feedbackInitialized = true; 767 initFeedback(); 768 } 769 770 if (cmdlineClasspath != null) { 771 state.addToClasspath(cmdlineClasspath); 772 } 773 774 startUpRun(startup); 775 currentNameSpace = mainNamespace; 776 } 777 778 private boolean isRunningInteractive() { 779 return currentNameSpace != null && currentNameSpace == mainNamespace; 780 } 781 782 //where -- one-time per run initialization of feedback modes 783 private void initFeedback() { 784 // No fluff, no prefix, for init failures 785 MessageHandler initmh = new InitMessageHandler(); 786 // Execute the feedback initialization code in the resource file 787 startUpRun(getResourceString("startup.feedback")); 788 // These predefined modes are read-only 789 feedback.markModesReadOnly(); 790 // Restore user defined modes retained on previous run with /set mode -retain 791 String encoded = prefs.get(MODE_KEY, null); 792 if (encoded != null && !encoded.isEmpty()) { 793 if (!feedback.restoreEncodedModes(initmh, encoded)) { 794 // Catastrophic corruption -- remove the retained modes 795 prefs.remove(MODE_KEY); 796 } 797 } 798 if (commandLineFeedbackMode != null) { 799 // The feedback mode to use was specified on the command line, use it 800 if (!setFeedback(initmh, new ArgTokenizer("--feedback", commandLineFeedbackMode))) { 801 regenerateOnDeath = false; 802 } 803 commandLineFeedbackMode = null; 804 } else { 805 String fb = prefs.get(FEEDBACK_KEY, null); 806 if (fb != null) { 807 // Restore the feedback mode to use that was retained 808 // on a previous run with /set feedback -retain 809 setFeedback(initmh, new ArgTokenizer("previous retain feedback", "-retain " + fb)); 810 } 811 } 812 } 813 814 //where 815 private void startUpRun(String start) { 816 try (IOContext suin = new FileScannerIOContext(new StringReader(start))) { 817 run(suin); 818 } catch (Exception ex) { 819 hardmsg("jshell.err.startup.unexpected.exception", ex); 820 ex.printStackTrace(cmdout); 821 } 822 } 823 824 private void closeState() { 825 live = false; 826 JShell oldState = state; 827 if (oldState != null) { 828 oldState.unsubscribe(shutdownSubscription); // No notification 829 oldState.close(); 830 } 831 } 832 833 /** 834 * Main loop 835 * @param in the line input/editing context 836 */ 837 private void run(IOContext in) { 838 IOContext oldInput = input; 839 input = in; 840 try { 841 String incomplete = ""; 842 while (live) { 843 String prompt; 844 if (isRunningInteractive()) { 845 prompt = testPrompt 846 ? incomplete.isEmpty() 847 ? "\u0005" //ENQ 848 : "\u0006" //ACK 849 : incomplete.isEmpty() 850 ? feedback.getPrompt(currentNameSpace.tidNext()) 851 : feedback.getContinuationPrompt(currentNameSpace.tidNext()) 852 ; 853 } else { 854 prompt = ""; 855 } 856 String raw; 857 try { 858 raw = in.readLine(prompt, incomplete); 859 } catch (InputInterruptedException ex) { 860 //input interrupted - clearing current state 861 incomplete = ""; 862 continue; 863 } 864 if (raw == null) { 865 //EOF 866 if (in.interactiveOutput()) { 867 // End after user ctrl-D 868 regenerateOnDeath = false; 869 } 870 break; 871 } 872 String trimmed = trimEnd(raw); 873 if (!trimmed.isEmpty()) { 874 String line = incomplete + trimmed; 875 876 // No commands in the middle of unprocessed source 877 if (incomplete.isEmpty() && line.startsWith("/") && !line.startsWith("//") && !line.startsWith("/*")) { 878 processCommand(line.trim()); 879 } else { 880 incomplete = processSourceCatchingReset(line); 881 } 882 } 883 } 884 } catch (IOException ex) { 885 errormsg("jshell.err.unexpected.exception", ex); 886 } finally { 887 input = oldInput; 888 } 889 } 890 891 private void addToReplayHistory(String s) { 892 if (isRunningInteractive()) { 893 replayableHistory.add(s); 894 } 895 } 896 897 private String processSourceCatchingReset(String src) { 898 try { 899 input.beforeUserCode(); 900 return processSource(src); 901 } catch (IllegalStateException ex) { 902 hard("Resetting..."); 903 live = false; // Make double sure 904 return ""; 905 } finally { 906 input.afterUserCode(); 907 } 908 } 909 910 private void processCommand(String cmd) { 911 if (cmd.startsWith("/-")) { 912 try { 913 //handle "/-[number]" 914 cmdUseHistoryEntry(Integer.parseInt(cmd.substring(1))); 915 return ; 916 } catch (NumberFormatException ex) { 917 //ignore 918 } 919 } 920 String arg = ""; 921 int idx = cmd.indexOf(' '); 922 if (idx > 0) { 923 arg = cmd.substring(idx + 1).trim(); 924 cmd = cmd.substring(0, idx); 925 } 926 Command[] candidates = findCommand(cmd, c -> c.kind.isRealCommand); 927 switch (candidates.length) { 928 case 0: 929 if (!rerunHistoryEntryById(cmd.substring(1))) { 930 errormsg("jshell.err.no.such.command.or.snippet.id", cmd); 931 fluffmsg("jshell.msg.help.for.help"); 932 } break; 933 case 1: 934 Command command = candidates[0]; 935 // If comand was successful and is of a replayable kind, add it the replayable history 936 if (command.run.apply(arg) && command.kind == CommandKind.REPLAY) { 937 addToReplayHistory((command.command + " " + arg).trim()); 938 } break; 939 default: 940 errormsg("jshell.err.command.ambiguous", cmd, 941 Arrays.stream(candidates).map(c -> c.command).collect(Collectors.joining(", "))); 942 fluffmsg("jshell.msg.help.for.help"); 943 break; 944 } 945 } 946 947 private Command[] findCommand(String cmd, Predicate<Command> filter) { 948 Command exact = commands.get(cmd); 949 if (exact != null) 950 return new Command[] {exact}; 951 952 return commands.values() 953 .stream() 954 .filter(filter) 955 .filter(command -> command.command.startsWith(cmd)) 956 .toArray(Command[]::new); 957 } 958 959 private static Path toPathResolvingUserHome(String pathString) { 960 if (pathString.replace(File.separatorChar, '/').startsWith("~/")) 961 return Paths.get(System.getProperty("user.home"), pathString.substring(2)); 962 else 963 return Paths.get(pathString); 964 } 965 966 static final class Command { 967 public final String command; 968 public final String helpKey; 969 public final Function<String,Boolean> run; 970 public final CompletionProvider completions; 971 public final CommandKind kind; 972 973 // NORMAL Commands 974 public Command(String command, Function<String,Boolean> run, CompletionProvider completions) { 975 this(command, run, completions, CommandKind.NORMAL); 976 } 977 978 // Special kinds of Commands 979 public Command(String command, Function<String,Boolean> run, CompletionProvider completions, CommandKind kind) { 980 this(command, "help." + command.substring(1), 981 run, completions, kind); 982 } 983 984 // Documentation pseudo-commands 985 public Command(String command, String helpKey, CommandKind kind) { 986 this(command, helpKey, 987 arg -> { throw new IllegalStateException(); }, 988 EMPTY_COMPLETION_PROVIDER, 989 kind); 990 } 991 992 public Command(String command, String helpKey, Function<String,Boolean> run, CompletionProvider completions, CommandKind kind) { 993 this.command = command; 994 this.helpKey = helpKey; 995 this.run = run; 996 this.completions = completions; 997 this.kind = kind; 998 } 999 1000 } 1001 1002 interface CompletionProvider { 1003 List<Suggestion> completionSuggestions(String input, int cursor, int[] anchor); 1004 1005 } 1006 1007 enum CommandKind { 1008 NORMAL(true, true, true), 1009 REPLAY(true, true, true), 1010 HIDDEN(true, false, false), 1011 HELP_ONLY(false, true, false), 1012 HELP_SUBJECT(false, false, false); 1013 1014 final boolean isRealCommand; 1015 final boolean showInHelp; 1016 final boolean shouldSuggestCompletions; 1017 private CommandKind(boolean isRealCommand, boolean showInHelp, boolean shouldSuggestCompletions) { 1018 this.isRealCommand = isRealCommand; 1019 this.showInHelp = showInHelp; 1020 this.shouldSuggestCompletions = shouldSuggestCompletions; 1021 } 1022 } 1023 1024 static final class FixedCompletionProvider implements CompletionProvider { 1025 1026 private final String[] alternatives; 1027 1028 public FixedCompletionProvider(String... alternatives) { 1029 this.alternatives = alternatives; 1030 } 1031 1032 @Override 1033 public List<Suggestion> completionSuggestions(String input, int cursor, int[] anchor) { 1034 List<Suggestion> result = new ArrayList<>(); 1035 1036 for (String alternative : alternatives) { 1037 if (alternative.startsWith(input)) { 1038 result.add(new ArgSuggestion(alternative)); 1039 } 1040 } 1041 1042 anchor[0] = 0; 1043 1044 return result; 1045 } 1046 1047 } 1048 1049 static final CompletionProvider EMPTY_COMPLETION_PROVIDER = new FixedCompletionProvider(); 1050 private static final CompletionProvider SNIPPET_HISTORY_OPTION_COMPLETION_PROVIDER = new FixedCompletionProvider("-all", "-start ", "-history"); 1051 private static final CompletionProvider SAVE_OPTION_COMPLETION_PROVIDER = new FixedCompletionProvider("-all ", "-start ", "-history "); 1052 private static final CompletionProvider SNIPPET_OPTION_COMPLETION_PROVIDER = new FixedCompletionProvider("-all", "-start " ); 1053 private static final CompletionProvider RELOAD_OPTIONS_COMPLETION_PROVIDER = new FixedCompletionProvider("-restore ", "-quiet "); 1054 private static final CompletionProvider RESTORE_COMPLETION_PROVIDER = new FixedCompletionProvider("-restore"); 1055 private static final CompletionProvider QUIET_COMPLETION_PROVIDER = new FixedCompletionProvider("-quiet"); 1056 private static final CompletionProvider SET_MODE_OPTIONS_COMPLETION_PROVIDER = new FixedCompletionProvider("-command", "-quiet", "-delete"); 1057 private static final CompletionProvider FILE_COMPLETION_PROVIDER = fileCompletions(p -> true); 1058 private final Map<String, Command> commands = new LinkedHashMap<>(); 1059 private void registerCommand(Command cmd) { 1060 commands.put(cmd.command, cmd); 1061 } 1062 1063 private static CompletionProvider skipWordThenCompletion(CompletionProvider completionProvider) { 1064 return (input, cursor, anchor) -> { 1065 List<Suggestion> result = Collections.emptyList(); 1066 1067 int space = input.indexOf(' '); 1068 if (space != -1) { 1069 String rest = input.substring(space + 1); 1070 result = completionProvider.completionSuggestions(rest, cursor - space - 1, anchor); 1071 anchor[0] += space + 1; 1072 } 1073 1074 return result; 1075 }; 1076 } 1077 1078 private static CompletionProvider fileCompletions(Predicate<Path> accept) { 1079 return (code, cursor, anchor) -> { 1080 int lastSlash = code.lastIndexOf('/'); 1081 String path = code.substring(0, lastSlash + 1); 1082 String prefix = lastSlash != (-1) ? code.substring(lastSlash + 1) : code; 1083 Path current = toPathResolvingUserHome(path); 1084 List<Suggestion> result = new ArrayList<>(); 1085 try (Stream<Path> dir = Files.list(current)) { 1086 dir.filter(f -> accept.test(f) && f.getFileName().toString().startsWith(prefix)) 1087 .map(f -> new ArgSuggestion(f.getFileName() + (Files.isDirectory(f) ? "/" : ""))) 1088 .forEach(result::add); 1089 } catch (IOException ex) { 1090 //ignore... 1091 } 1092 if (path.isEmpty()) { 1093 StreamSupport.stream(FileSystems.getDefault().getRootDirectories().spliterator(), false) 1094 .filter(root -> Files.exists(root)) 1095 .filter(root -> accept.test(root) && root.toString().startsWith(prefix)) 1096 .map(root -> new ArgSuggestion(root.toString())) 1097 .forEach(result::add); 1098 } 1099 anchor[0] = path.length(); 1100 return result; 1101 }; 1102 } 1103 1104 private static CompletionProvider classPathCompletion() { 1105 return fileCompletions(p -> Files.isDirectory(p) || 1106 p.getFileName().toString().endsWith(".zip") || 1107 p.getFileName().toString().endsWith(".jar")); 1108 } 1109 1110 // Completion based on snippet supplier 1111 private CompletionProvider snippetCompletion(Supplier<Stream<? extends Snippet>> snippetsSupplier) { 1112 return (prefix, cursor, anchor) -> { 1113 anchor[0] = 0; 1114 int space = prefix.lastIndexOf(' '); 1115 Set<String> prior = new HashSet<>(Arrays.asList(prefix.split(" "))); 1116 if (prior.contains("-all") || prior.contains("-history")) { 1117 return Collections.emptyList(); 1118 } 1119 String argPrefix = prefix.substring(space + 1); 1120 return snippetsSupplier.get() 1121 .filter(k -> !prior.contains(String.valueOf(k.id())) 1122 && (!(k instanceof DeclarationSnippet) 1123 || !prior.contains(((DeclarationSnippet) k).name()))) 1124 .flatMap(k -> (k instanceof DeclarationSnippet) 1125 ? Stream.of(String.valueOf(k.id()) + " ", ((DeclarationSnippet) k).name() + " ") 1126 : Stream.of(String.valueOf(k.id()) + " ")) 1127 .filter(k -> k.startsWith(argPrefix)) 1128 .map(ArgSuggestion::new) 1129 .collect(Collectors.toList()); 1130 }; 1131 } 1132 1133 // Completion based on snippet supplier with -all -start (and sometimes -history) options 1134 private CompletionProvider snippetWithOptionCompletion(CompletionProvider optionProvider, 1135 Supplier<Stream<? extends Snippet>> snippetsSupplier) { 1136 return (code, cursor, anchor) -> { 1137 List<Suggestion> result = new ArrayList<>(); 1138 int pastSpace = code.lastIndexOf(' ') + 1; // zero if no space 1139 if (pastSpace == 0) { 1140 result.addAll(optionProvider.completionSuggestions(code, cursor, anchor)); 1141 } 1142 result.addAll(snippetCompletion(snippetsSupplier).completionSuggestions(code, cursor, anchor)); 1143 anchor[0] += pastSpace; 1144 return result; 1145 }; 1146 } 1147 1148 // Completion of help, commands and subjects 1149 private CompletionProvider helpCompletion() { 1150 return (code, cursor, anchor) -> { 1151 List<Suggestion> result; 1152 int pastSpace = code.indexOf(' ') + 1; // zero if no space 1153 if (pastSpace == 0) { 1154 result = new FixedCompletionProvider(commands.values().stream() 1155 .filter(cmd -> cmd.kind.showInHelp || cmd.kind == CommandKind.HELP_SUBJECT) 1156 .map(c -> c.command + " ") 1157 .toArray(String[]::new)) 1158 .completionSuggestions(code, cursor, anchor); 1159 } else if (code.startsWith("/se")) { 1160 result = new FixedCompletionProvider(SET_SUBCOMMANDS) 1161 .completionSuggestions(code.substring(pastSpace), cursor - pastSpace, anchor); 1162 } else { 1163 result = Collections.emptyList(); 1164 } 1165 anchor[0] += pastSpace; 1166 return result; 1167 }; 1168 } 1169 1170 private static CompletionProvider saveCompletion() { 1171 return (code, cursor, anchor) -> { 1172 List<Suggestion> result = new ArrayList<>(); 1173 int space = code.indexOf(' '); 1174 if (space == (-1)) { 1175 result.addAll(SAVE_OPTION_COMPLETION_PROVIDER.completionSuggestions(code, cursor, anchor)); 1176 } 1177 result.addAll(FILE_COMPLETION_PROVIDER.completionSuggestions(code.substring(space + 1), cursor - space - 1, anchor)); 1178 anchor[0] += space + 1; 1179 return result; 1180 }; 1181 } 1182 1183 private static CompletionProvider reloadCompletion() { 1184 return (code, cursor, anchor) -> { 1185 CompletionProvider provider; 1186 int pastSpace = code.indexOf(' ') + 1; // zero if no space 1187 if (pastSpace == 0) { 1188 provider = RELOAD_OPTIONS_COMPLETION_PROVIDER; 1189 } else { 1190 switch (code.substring(0, pastSpace - 1)) { 1191 case "-quiet": 1192 provider = RESTORE_COMPLETION_PROVIDER; 1193 break; 1194 case "-restore": 1195 provider = QUIET_COMPLETION_PROVIDER; 1196 break; 1197 default: 1198 provider = EMPTY_COMPLETION_PROVIDER; 1199 break; 1200 } 1201 } 1202 List<Suggestion> result = provider.completionSuggestions( 1203 code.substring(pastSpace), cursor - pastSpace, anchor); 1204 anchor[0] += pastSpace; 1205 return result; 1206 }; 1207 } 1208 1209 private static CompletionProvider orMostSpecificCompletion( 1210 CompletionProvider left, CompletionProvider right) { 1211 return (code, cursor, anchor) -> { 1212 int[] leftAnchor = {-1}; 1213 int[] rightAnchor = {-1}; 1214 1215 List<Suggestion> leftSuggestions = left.completionSuggestions(code, cursor, leftAnchor); 1216 List<Suggestion> rightSuggestions = right.completionSuggestions(code, cursor, rightAnchor); 1217 1218 List<Suggestion> suggestions = new ArrayList<>(); 1219 1220 if (leftAnchor[0] >= rightAnchor[0]) { 1221 anchor[0] = leftAnchor[0]; 1222 suggestions.addAll(leftSuggestions); 1223 } 1224 1225 if (leftAnchor[0] <= rightAnchor[0]) { 1226 anchor[0] = rightAnchor[0]; 1227 suggestions.addAll(rightSuggestions); 1228 } 1229 1230 return suggestions; 1231 }; 1232 } 1233 1234 // Snippet lists 1235 1236 Stream<Snippet> allSnippets() { 1237 return state.snippets(); 1238 } 1239 1240 Stream<Snippet> dropableSnippets() { 1241 return state.snippets() 1242 .filter(sn -> state.status(sn).isActive()); 1243 } 1244 1245 Stream<VarSnippet> allVarSnippets() { 1246 return state.snippets() 1247 .filter(sn -> sn.kind() == Snippet.Kind.VAR) 1248 .map(sn -> (VarSnippet) sn); 1249 } 1250 1251 Stream<MethodSnippet> allMethodSnippets() { 1252 return state.snippets() 1253 .filter(sn -> sn.kind() == Snippet.Kind.METHOD) 1254 .map(sn -> (MethodSnippet) sn); 1255 } 1256 1257 Stream<TypeDeclSnippet> allTypeSnippets() { 1258 return state.snippets() 1259 .filter(sn -> sn.kind() == Snippet.Kind.TYPE_DECL) 1260 .map(sn -> (TypeDeclSnippet) sn); 1261 } 1262 1263 // Table of commands -- with command forms, argument kinds, helpKey message, implementation, ... 1264 1265 { 1266 registerCommand(new Command("/list", 1267 this::cmdList, 1268 snippetWithOptionCompletion(SNIPPET_HISTORY_OPTION_COMPLETION_PROVIDER, 1269 this::allSnippets))); 1270 registerCommand(new Command("/edit", 1271 this::cmdEdit, 1272 snippetWithOptionCompletion(SNIPPET_OPTION_COMPLETION_PROVIDER, 1273 this::allSnippets))); 1274 registerCommand(new Command("/drop", 1275 this::cmdDrop, 1276 snippetCompletion(this::dropableSnippets), 1277 CommandKind.REPLAY)); 1278 registerCommand(new Command("/save", 1279 this::cmdSave, 1280 saveCompletion())); 1281 registerCommand(new Command("/open", 1282 this::cmdOpen, 1283 FILE_COMPLETION_PROVIDER)); 1284 registerCommand(new Command("/vars", 1285 this::cmdVars, 1286 snippetWithOptionCompletion(SNIPPET_OPTION_COMPLETION_PROVIDER, 1287 this::allVarSnippets))); 1288 registerCommand(new Command("/methods", 1289 this::cmdMethods, 1290 snippetWithOptionCompletion(SNIPPET_OPTION_COMPLETION_PROVIDER, 1291 this::allMethodSnippets))); 1292 registerCommand(new Command("/types", 1293 this::cmdTypes, 1294 snippetWithOptionCompletion(SNIPPET_OPTION_COMPLETION_PROVIDER, 1295 this::allTypeSnippets))); 1296 registerCommand(new Command("/imports", 1297 arg -> cmdImports(), 1298 EMPTY_COMPLETION_PROVIDER)); 1299 registerCommand(new Command("/exit", 1300 arg -> cmdExit(), 1301 EMPTY_COMPLETION_PROVIDER)); 1302 registerCommand(new Command("/reset", 1303 arg -> cmdReset(), 1304 EMPTY_COMPLETION_PROVIDER)); 1305 registerCommand(new Command("/reload", 1306 this::cmdReload, 1307 reloadCompletion())); 1308 registerCommand(new Command("/classpath", 1309 this::cmdClasspath, 1310 classPathCompletion(), 1311 CommandKind.REPLAY)); 1312 registerCommand(new Command("/history", 1313 arg -> cmdHistory(), 1314 EMPTY_COMPLETION_PROVIDER)); 1315 registerCommand(new Command("/debug", 1316 this::cmdDebug, 1317 EMPTY_COMPLETION_PROVIDER, 1318 CommandKind.HIDDEN)); 1319 registerCommand(new Command("/help", 1320 this::cmdHelp, 1321 helpCompletion())); 1322 registerCommand(new Command("/set", 1323 this::cmdSet, 1324 new ContinuousCompletionProvider(Map.of( 1325 // need more completion for format for usability 1326 "format", feedback.modeCompletions(), 1327 "truncation", feedback.modeCompletions(), 1328 "feedback", feedback.modeCompletions(), 1329 "mode", skipWordThenCompletion(orMostSpecificCompletion( 1330 feedback.modeCompletions(SET_MODE_OPTIONS_COMPLETION_PROVIDER), 1331 SET_MODE_OPTIONS_COMPLETION_PROVIDER)), 1332 "prompt", feedback.modeCompletions(), 1333 "editor", fileCompletions(Files::isExecutable), 1334 "start", FILE_COMPLETION_PROVIDER), 1335 STARTSWITH_MATCHER))); 1336 registerCommand(new Command("/?", 1337 "help.quest", 1338 this::cmdHelp, 1339 helpCompletion(), 1340 CommandKind.NORMAL)); 1341 registerCommand(new Command("/!", 1342 "help.bang", 1343 arg -> cmdUseHistoryEntry(-1), 1344 EMPTY_COMPLETION_PROVIDER, 1345 CommandKind.NORMAL)); 1346 1347 // Documentation pseudo-commands 1348 registerCommand(new Command("/<id>", 1349 "help.id", 1350 CommandKind.HELP_ONLY)); 1351 registerCommand(new Command("/-<n>", 1352 "help.previous", 1353 CommandKind.HELP_ONLY)); 1354 registerCommand(new Command("intro", 1355 "help.intro", 1356 CommandKind.HELP_SUBJECT)); 1357 registerCommand(new Command("shortcuts", 1358 "help.shortcuts", 1359 CommandKind.HELP_SUBJECT)); 1360 1361 commandCompletions = new ContinuousCompletionProvider( 1362 commands.values().stream() 1363 .filter(c -> c.kind.shouldSuggestCompletions) 1364 .collect(toMap(c -> c.command, c -> c.completions)), 1365 STARTSWITH_MATCHER); 1366 } 1367 1368 private ContinuousCompletionProvider commandCompletions; 1369 1370 public List<Suggestion> commandCompletionSuggestions(String code, int cursor, int[] anchor) { 1371 return commandCompletions.completionSuggestions(code, cursor, anchor); 1372 } 1373 1374 public String commandDocumentation(String code, int cursor, boolean shortDescription) { 1375 code = code.substring(0, cursor); 1376 int space = code.indexOf(' '); 1377 1378 if (space != (-1)) { 1379 String cmd = code.substring(0, space); 1380 Command command = commands.get(cmd); 1381 if (command != null) { 1382 return getResourceString(command.helpKey + (shortDescription ? ".summary" : "")); 1383 } 1384 } 1385 1386 return null; 1387 } 1388 1389 // --- Command implementations --- 1390 1391 private static final String[] SET_SUBCOMMANDS = new String[]{ 1392 "format", "truncation", "feedback", "mode", "prompt", "editor", "start"}; 1393 1394 final boolean cmdSet(String arg) { 1395 String cmd = "/set"; 1396 ArgTokenizer at = new ArgTokenizer(cmd, arg.trim()); 1397 String which = subCommand(cmd, at, SET_SUBCOMMANDS); 1398 if (which == null) { 1399 return false; 1400 } 1401 switch (which) { 1402 case "_retain": { 1403 errormsg("jshell.err.setting.to.retain.must.be.specified", at.whole()); 1404 return false; 1405 } 1406 case "_blank": { 1407 // show top-level settings 1408 new SetEditor().set(); 1409 showSetStart(); 1410 setFeedback(this, at); // no args so shows feedback setting 1411 hardmsg("jshell.msg.set.show.mode.settings"); 1412 return true; 1413 } 1414 case "format": 1415 return feedback.setFormat(this, at); 1416 case "truncation": 1417 return feedback.setTruncation(this, at); 1418 case "feedback": 1419 return setFeedback(this, at); 1420 case "mode": 1421 return feedback.setMode(this, at, 1422 retained -> prefs.put(MODE_KEY, retained)); 1423 case "prompt": 1424 return feedback.setPrompt(this, at); 1425 case "editor": 1426 return new SetEditor(at).set(); 1427 case "start": 1428 return setStart(at); 1429 default: 1430 errormsg("jshell.err.arg", cmd, at.val()); 1431 return false; 1432 } 1433 } 1434 1435 boolean setFeedback(MessageHandler messageHandler, ArgTokenizer at) { 1436 return feedback.setFeedback(messageHandler, at, 1437 fb -> prefs.put(FEEDBACK_KEY, fb)); 1438 } 1439 1440 // Find which, if any, sub-command matches. 1441 // Return null on error 1442 String subCommand(String cmd, ArgTokenizer at, String[] subs) { 1443 at.allowedOptions("-retain"); 1444 String sub = at.next(); 1445 if (sub == null) { 1446 // No sub-command was given 1447 return at.hasOption("-retain") 1448 ? "_retain" 1449 : "_blank"; 1450 } 1451 String[] matches = Arrays.stream(subs) 1452 .filter(s -> s.startsWith(sub)) 1453 .toArray(String[]::new); 1454 if (matches.length == 0) { 1455 // There are no matching sub-commands 1456 errormsg("jshell.err.arg", cmd, sub); 1457 fluffmsg("jshell.msg.use.one.of", Arrays.stream(subs) 1458 .collect(Collectors.joining(", ")) 1459 ); 1460 return null; 1461 } 1462 if (matches.length > 1) { 1463 // More than one sub-command matches the initial characters provided 1464 errormsg("jshell.err.sub.ambiguous", cmd, sub); 1465 fluffmsg("jshell.msg.use.one.of", Arrays.stream(matches) 1466 .collect(Collectors.joining(", ")) 1467 ); 1468 return null; 1469 } 1470 return matches[0]; 1471 } 1472 1473 static class EditorSetting { 1474 1475 static String BUILT_IN_REP = "-default"; 1476 static char WAIT_PREFIX = '-'; 1477 static char NORMAL_PREFIX = '*'; 1478 1479 final String[] cmd; 1480 final boolean wait; 1481 1482 EditorSetting(String[] cmd, boolean wait) { 1483 this.wait = wait; 1484 this.cmd = cmd; 1485 } 1486 1487 // returns null if not stored in preferences 1488 static EditorSetting fromPrefs(Preferences prefs) { 1489 // Read retained editor setting (if any) 1490 String editorString = prefs.get(EDITOR_KEY, ""); 1491 if (editorString == null || editorString.isEmpty()) { 1492 return null; 1493 } else if (editorString.equals(BUILT_IN_REP)) { 1494 return BUILT_IN_EDITOR; 1495 } else { 1496 boolean wait = false; 1497 char waitMarker = editorString.charAt(0); 1498 if (waitMarker == WAIT_PREFIX || waitMarker == NORMAL_PREFIX) { 1499 wait = waitMarker == WAIT_PREFIX; 1500 editorString = editorString.substring(1); 1501 } 1502 String[] cmd = editorString.split(RECORD_SEPARATOR); 1503 return new EditorSetting(cmd, wait); 1504 } 1505 } 1506 1507 static void removePrefs(Preferences prefs) { 1508 prefs.remove(EDITOR_KEY); 1509 } 1510 1511 void toPrefs(Preferences prefs) { 1512 prefs.put(EDITOR_KEY, (this == BUILT_IN_EDITOR) 1513 ? BUILT_IN_REP 1514 : (wait ? WAIT_PREFIX : NORMAL_PREFIX) + String.join(RECORD_SEPARATOR, cmd)); 1515 } 1516 1517 @Override 1518 public boolean equals(Object o) { 1519 if (o instanceof EditorSetting) { 1520 EditorSetting ed = (EditorSetting) o; 1521 return Arrays.equals(cmd, ed.cmd) && wait == ed.wait; 1522 } else { 1523 return false; 1524 } 1525 } 1526 1527 @Override 1528 public int hashCode() { 1529 int hash = 7; 1530 hash = 71 * hash + Arrays.deepHashCode(this.cmd); 1531 hash = 71 * hash + (this.wait ? 1 : 0); 1532 return hash; 1533 } 1534 } 1535 1536 class SetEditor { 1537 1538 private final ArgTokenizer at; 1539 private final String[] command; 1540 private final boolean hasCommand; 1541 private final boolean defaultOption; 1542 private final boolean deleteOption; 1543 private final boolean waitOption; 1544 private final boolean retainOption; 1545 private final int primaryOptionCount; 1546 1547 SetEditor(ArgTokenizer at) { 1548 at.allowedOptions("-default", "-wait", "-retain", "-delete"); 1549 String prog = at.next(); 1550 List<String> ed = new ArrayList<>(); 1551 while (at.val() != null) { 1552 ed.add(at.val()); 1553 at.nextToken(); // so that options are not interpreted as jshell options 1554 } 1555 this.at = at; 1556 this.command = ed.toArray(new String[ed.size()]); 1557 this.hasCommand = command.length > 0; 1558 this.defaultOption = at.hasOption("-default"); 1559 this.deleteOption = at.hasOption("-delete"); 1560 this.waitOption = at.hasOption("-wait"); 1561 this.retainOption = at.hasOption("-retain"); 1562 this.primaryOptionCount = (hasCommand? 1 : 0) + (defaultOption? 1 : 0) + (deleteOption? 1 : 0); 1563 } 1564 1565 SetEditor() { 1566 this(new ArgTokenizer("", "")); 1567 } 1568 1569 boolean set() { 1570 if (!check()) { 1571 return false; 1572 } 1573 if (primaryOptionCount == 0 && !retainOption) { 1574 // No settings or -retain, so this is a query 1575 EditorSetting retained = EditorSetting.fromPrefs(prefs); 1576 if (retained != null) { 1577 // retained editor is set 1578 hard("/set editor -retain %s", format(retained)); 1579 } 1580 if (retained == null || !retained.equals(editor)) { 1581 // editor is not retained or retained is different from set 1582 hard("/set editor %s", format(editor)); 1583 } 1584 return true; 1585 } 1586 if (retainOption && deleteOption) { 1587 EditorSetting.removePrefs(prefs); 1588 } 1589 install(); 1590 if (retainOption && !deleteOption) { 1591 editor.toPrefs(prefs); 1592 fluffmsg("jshell.msg.set.editor.retain", format(editor)); 1593 } 1594 return true; 1595 } 1596 1597 private boolean check() { 1598 if (!checkOptionsAndRemainingInput(at)) { 1599 return false; 1600 } 1601 if (primaryOptionCount > 1) { 1602 errormsg("jshell.err.default.option.or.program", at.whole()); 1603 return false; 1604 } 1605 if (waitOption && !hasCommand) { 1606 errormsg("jshell.err.wait.applies.to.external.editor", at.whole()); 1607 return false; 1608 } 1609 return true; 1610 } 1611 1612 private void install() { 1613 if (hasCommand) { 1614 editor = new EditorSetting(command, waitOption); 1615 } else if (defaultOption) { 1616 editor = BUILT_IN_EDITOR; 1617 } else if (deleteOption) { 1618 configEditor(); 1619 } else { 1620 return; 1621 } 1622 fluffmsg("jshell.msg.set.editor.set", format(editor)); 1623 } 1624 1625 private String format(EditorSetting ed) { 1626 if (ed == BUILT_IN_EDITOR) { 1627 return "-default"; 1628 } else { 1629 Stream<String> elems = Arrays.stream(ed.cmd); 1630 if (ed.wait) { 1631 elems = Stream.concat(Stream.of("-wait"), elems); 1632 } 1633 return elems.collect(joining(" ")); 1634 } 1635 } 1636 } 1637 1638 // The sub-command: /set start <start-file> 1639 boolean setStart(ArgTokenizer at) { 1640 at.allowedOptions("-default", "-none", "-retain"); 1641 String fn = at.next(); 1642 if (!checkOptionsAndRemainingInput(at)) { 1643 return false; 1644 } 1645 boolean defaultOption = at.hasOption("-default"); 1646 boolean noneOption = at.hasOption("-none"); 1647 boolean retainOption = at.hasOption("-retain"); 1648 boolean hasFile = fn != null; 1649 1650 int argCount = (defaultOption ? 1 : 0) + (noneOption ? 1 : 0) + (hasFile ? 1 : 0); 1651 if (argCount > 1) { 1652 errormsg("jshell.err.option.or.filename", at.whole()); 1653 return false; 1654 } 1655 if (argCount == 0 && !retainOption) { 1656 // no options or filename, show current setting 1657 showSetStart(); 1658 return true; 1659 } 1660 if (hasFile) { 1661 String init = readFile(fn, "/set start"); 1662 if (init == null) { 1663 return false; 1664 } 1665 startup = init; 1666 } else if (defaultOption) { 1667 startup = DEFAULT_STARTUP; 1668 } else if (noneOption) { 1669 startup = ""; 1670 } 1671 if (retainOption) { 1672 // retain startup setting 1673 prefs.put(STARTUP_KEY, startup); 1674 } 1675 return true; 1676 } 1677 1678 void showSetStart() { 1679 String retained = prefs.get(STARTUP_KEY, null); 1680 if (retained != null) { 1681 showSetStart(true, retained); 1682 } 1683 if (retained == null || !startup.equals(retained)) { 1684 showSetStart(false, startup); 1685 } 1686 } 1687 1688 void showSetStart(boolean isRetained, String start) { 1689 String cmd = "/set start" + (isRetained ? " -retain " : " "); 1690 String stset; 1691 if (start.equals(DEFAULT_STARTUP)) { 1692 stset = cmd + "-default"; 1693 } else if (start.isEmpty()) { 1694 stset = cmd + "-none"; 1695 } else { 1696 stset = "startup.jsh:\n" + start + "\n" + cmd + "startup.jsh"; 1697 } 1698 hard(stset); 1699 } 1700 1701 boolean cmdClasspath(String arg) { 1702 if (arg.isEmpty()) { 1703 errormsg("jshell.err.classpath.arg"); 1704 return false; 1705 } else { 1706 state.addToClasspath(toPathResolvingUserHome(arg).toString()); 1707 fluffmsg("jshell.msg.classpath", arg); 1708 return true; 1709 } 1710 } 1711 1712 boolean cmdDebug(String arg) { 1713 if (arg.isEmpty()) { 1714 debug = !debug; 1715 InternalDebugControl.setDebugFlags(state, debug ? DBG_GEN : 0); 1716 fluff("Debugging %s", debug ? "on" : "off"); 1717 } else { 1718 int flags = 0; 1719 for (char ch : arg.toCharArray()) { 1720 switch (ch) { 1721 case '0': 1722 flags = 0; 1723 debug = false; 1724 fluff("Debugging off"); 1725 break; 1726 case 'r': 1727 debug = true; 1728 fluff("REPL tool debugging on"); 1729 break; 1730 case 'g': 1731 flags |= DBG_GEN; 1732 fluff("General debugging on"); 1733 break; 1734 case 'f': 1735 flags |= DBG_FMGR; 1736 fluff("File manager debugging on"); 1737 break; 1738 case 'c': 1739 flags |= DBG_COMPA; 1740 fluff("Completion analysis debugging on"); 1741 break; 1742 case 'd': 1743 flags |= DBG_DEP; 1744 fluff("Dependency debugging on"); 1745 break; 1746 case 'e': 1747 flags |= DBG_EVNT; 1748 fluff("Event debugging on"); 1749 break; 1750 default: 1751 hard("Unknown debugging option: %c", ch); 1752 fluff("Use: 0 r g f c d"); 1753 return false; 1754 } 1755 } 1756 InternalDebugControl.setDebugFlags(state, flags); 1757 } 1758 return true; 1759 } 1760 1761 private boolean cmdExit() { 1762 regenerateOnDeath = false; 1763 live = false; 1764 if (!replayableHistory.isEmpty()) { 1765 // Prevent history overflow by calculating what will fit, starting 1766 // with most recent 1767 int sepLen = RECORD_SEPARATOR.length(); 1768 int length = 0; 1769 int first = replayableHistory.size(); 1770 while(length < Preferences.MAX_VALUE_LENGTH && --first >= 0) { 1771 length += replayableHistory.get(first).length() + sepLen; 1772 } 1773 String hist = String.join(RECORD_SEPARATOR, 1774 replayableHistory.subList(first + 1, replayableHistory.size())); 1775 prefs.put(REPLAY_RESTORE_KEY, hist); 1776 } 1777 fluffmsg("jshell.msg.goodbye"); 1778 return true; 1779 } 1780 1781 boolean cmdHelp(String arg) { 1782 ArgTokenizer at = new ArgTokenizer("/help", arg); 1783 String subject = at.next(); 1784 if (subject != null) { 1785 Command[] matches = commands.values().stream() 1786 .filter(c -> c.command.startsWith(subject)) 1787 .toArray(Command[]::new); 1788 if (matches.length == 1) { 1789 String cmd = matches[0].command; 1790 if (cmd.equals("/set")) { 1791 // Print the help doc for the specified sub-command 1792 String which = subCommand(cmd, at, SET_SUBCOMMANDS); 1793 if (which == null) { 1794 return false; 1795 } 1796 if (!which.equals("_blank")) { 1797 hardrb("help.set." + which); 1798 return true; 1799 } 1800 } 1801 } 1802 if (matches.length > 0) { 1803 for (Command c : matches) { 1804 hard(""); 1805 hard("%s", c.command); 1806 hard(""); 1807 hardrb(c.helpKey); 1808 } 1809 return true; 1810 } else { 1811 errormsg("jshell.err.help.arg", arg); 1812 } 1813 } 1814 hardmsg("jshell.msg.help.begin"); 1815 hardPairs(commands.values().stream() 1816 .filter(cmd -> cmd.kind.showInHelp), 1817 cmd -> cmd.command + " " + getResourceString(cmd.helpKey + ".args"), 1818 cmd -> getResourceString(cmd.helpKey + ".summary") 1819 ); 1820 hardmsg("jshell.msg.help.subject"); 1821 hardPairs(commands.values().stream() 1822 .filter(cmd -> cmd.kind == CommandKind.HELP_SUBJECT), 1823 cmd -> cmd.command, 1824 cmd -> getResourceString(cmd.helpKey + ".summary") 1825 ); 1826 return true; 1827 } 1828 1829 private boolean cmdHistory() { 1830 cmdout.println(); 1831 for (String s : input.currentSessionHistory()) { 1832 // No number prefix, confusing with snippet ids 1833 cmdout.printf("%s\n", s); 1834 } 1835 return true; 1836 } 1837 1838 /** 1839 * Avoid parameterized varargs possible heap pollution warning. 1840 */ 1841 private interface SnippetPredicate<T extends Snippet> extends Predicate<T> { } 1842 1843 /** 1844 * Apply filters to a stream until one that is non-empty is found. 1845 * Adapted from Stuart Marks 1846 * 1847 * @param supplier Supply the Snippet stream to filter 1848 * @param filters Filters to attempt 1849 * @return The non-empty filtered Stream, or null 1850 */ 1851 @SafeVarargs 1852 private static <T extends Snippet> Stream<T> nonEmptyStream(Supplier<Stream<T>> supplier, 1853 SnippetPredicate<T>... filters) { 1854 for (SnippetPredicate<T> filt : filters) { 1855 Iterator<T> iterator = supplier.get().filter(filt).iterator(); 1856 if (iterator.hasNext()) { 1857 return StreamSupport.stream(Spliterators.spliteratorUnknownSize(iterator, 0), false); 1858 } 1859 } 1860 return null; 1861 } 1862 1863 private boolean inStartUp(Snippet sn) { 1864 return mapSnippet.get(sn).space == startNamespace; 1865 } 1866 1867 private boolean isActive(Snippet sn) { 1868 return state.status(sn).isActive(); 1869 } 1870 1871 private boolean mainActive(Snippet sn) { 1872 return !inStartUp(sn) && isActive(sn); 1873 } 1874 1875 private boolean matchingDeclaration(Snippet sn, String name) { 1876 return sn instanceof DeclarationSnippet 1877 && ((DeclarationSnippet) sn).name().equals(name); 1878 } 1879 1880 /** 1881 * Convert user arguments to a Stream of snippets referenced by those 1882 * arguments (or lack of arguments). 1883 * 1884 * @param snippets the base list of possible snippets 1885 * @param defFilter the filter to apply to the arguments if no argument 1886 * @param rawargs the user's argument to the command, maybe be the empty 1887 * string 1888 * @return a Stream of referenced snippets or null if no matches are found 1889 */ 1890 private <T extends Snippet> Stream<T> argsOptionsToSnippets(Supplier<Stream<T>> snippetSupplier, 1891 Predicate<Snippet> defFilter, String rawargs, String cmd) { 1892 ArgTokenizer at = new ArgTokenizer(cmd, rawargs.trim()); 1893 at.allowedOptions("-all", "-start"); 1894 List<String> args = new ArrayList<>(); 1895 String s; 1896 while ((s = at.next()) != null) { 1897 args.add(s); 1898 } 1899 if (!checkOptionsAndRemainingInput(at)) { 1900 return null; 1901 } 1902 if (at.optionCount() > 0 && args.size() > 0) { 1903 errormsg("jshell.err.may.not.specify.options.and.snippets", at.whole()); 1904 return null; 1905 } 1906 if (at.optionCount() > 1) { 1907 errormsg("jshell.err.conflicting.options", at.whole()); 1908 return null; 1909 } 1910 if (at.hasOption("-all")) { 1911 // all snippets including start-up, failed, and overwritten 1912 return snippetSupplier.get(); 1913 } 1914 if (at.hasOption("-start")) { 1915 // start-up snippets 1916 return snippetSupplier.get() 1917 .filter(this::inStartUp); 1918 } 1919 if (args.isEmpty()) { 1920 // Default is all active user snippets 1921 return snippetSupplier.get() 1922 .filter(defFilter); 1923 } 1924 return argsToSnippets(snippetSupplier, args); 1925 } 1926 1927 /** 1928 * Convert user arguments to a Stream of snippets referenced by those 1929 * arguments. 1930 * 1931 * @param snippetSupplier the base list of possible snippets 1932 * @param args the user's argument to the command, maybe be the empty list 1933 * @return a Stream of referenced snippets or null if no matches to specific 1934 * arg 1935 */ 1936 private <T extends Snippet> Stream<T> argsToSnippets(Supplier<Stream<T>> snippetSupplier, 1937 List<String> args) { 1938 Stream<T> result = null; 1939 for (String arg : args) { 1940 // Find the best match 1941 Stream<T> st = layeredSnippetSearch(snippetSupplier, arg); 1942 if (st == null) { 1943 Stream<Snippet> est = layeredSnippetSearch(state::snippets, arg); 1944 if (est == null) { 1945 errormsg("jshell.err.no.such.snippets", arg); 1946 } else { 1947 errormsg("jshell.err.the.snippet.cannot.be.used.with.this.command", 1948 arg, est.findFirst().get().source()); 1949 } 1950 return null; 1951 } 1952 if (result == null) { 1953 result = st; 1954 } else { 1955 result = Stream.concat(result, st); 1956 } 1957 } 1958 return result; 1959 } 1960 1961 private <T extends Snippet> Stream<T> layeredSnippetSearch(Supplier<Stream<T>> snippetSupplier, String arg) { 1962 return nonEmptyStream( 1963 // the stream supplier 1964 snippetSupplier, 1965 // look for active user declarations matching the name 1966 sn -> isActive(sn) && matchingDeclaration(sn, arg), 1967 // else, look for any declarations matching the name 1968 sn -> matchingDeclaration(sn, arg), 1969 // else, look for an id of this name 1970 sn -> sn.id().equals(arg) 1971 ); 1972 } 1973 1974 private boolean cmdDrop(String rawargs) { 1975 ArgTokenizer at = new ArgTokenizer("/drop", rawargs.trim()); 1976 at.allowedOptions(); 1977 List<String> args = new ArrayList<>(); 1978 String s; 1979 while ((s = at.next()) != null) { 1980 args.add(s); 1981 } 1982 if (!checkOptionsAndRemainingInput(at)) { 1983 return false; 1984 } 1985 if (args.isEmpty()) { 1986 errormsg("jshell.err.drop.arg"); 1987 return false; 1988 } 1989 Stream<Snippet> stream = argsToSnippets(this::dropableSnippets, args); 1990 if (stream == null) { 1991 // Snippet not found. Error already printed 1992 fluffmsg("jshell.msg.see.classes.etc"); 1993 return false; 1994 } 1995 List<Snippet> snippets = stream.collect(toList()); 1996 if (snippets.size() > args.size()) { 1997 // One of the args references more thean one snippet 1998 errormsg("jshell.err.drop.ambiguous"); 1999 fluffmsg("jshell.msg.use.one.of", snippets.stream() 2000 .map(sn -> String.format("\n/drop %-5s : %s", sn.id(), sn.source().replace("\n", "\n "))) 2001 .collect(Collectors.joining(", ")) 2002 ); 2003 return false; 2004 } 2005 snippets.stream() 2006 .forEach(sn -> state.drop(sn).forEach(this::handleEvent)); 2007 return true; 2008 } 2009 2010 private boolean cmdEdit(String arg) { 2011 Stream<Snippet> stream = argsOptionsToSnippets(state::snippets, 2012 this::mainActive, arg, "/edit"); 2013 if (stream == null) { 2014 return false; 2015 } 2016 Set<String> srcSet = new LinkedHashSet<>(); 2017 stream.forEachOrdered(sn -> { 2018 String src = sn.source(); 2019 switch (sn.subKind()) { 2020 case VAR_VALUE_SUBKIND: 2021 break; 2022 case ASSIGNMENT_SUBKIND: 2023 case OTHER_EXPRESSION_SUBKIND: 2024 case TEMP_VAR_EXPRESSION_SUBKIND: 2025 case STATEMENT_SUBKIND: 2026 case UNKNOWN_SUBKIND: 2027 if (!src.endsWith(";")) { 2028 src = src + ";"; 2029 } 2030 srcSet.add(src); 2031 break; 2032 default: 2033 srcSet.add(src); 2034 break; 2035 } 2036 }); 2037 StringBuilder sb = new StringBuilder(); 2038 for (String s : srcSet) { 2039 sb.append(s); 2040 sb.append('\n'); 2041 } 2042 String src = sb.toString(); 2043 Consumer<String> saveHandler = new SaveHandler(src, srcSet); 2044 Consumer<String> errorHandler = s -> hard("Edit Error: %s", s); 2045 if (editor == BUILT_IN_EDITOR) { 2046 return builtInEdit(src, saveHandler, errorHandler); 2047 } else { 2048 // Changes have occurred in temp edit directory, 2049 // transfer the new sources to JShell (unless the editor is 2050 // running directly in JShell's window -- don't make a mess) 2051 String[] buffer = new String[1]; 2052 Consumer<String> extSaveHandler = s -> { 2053 if (input.terminalEditorRunning()) { 2054 buffer[0] = s; 2055 } else { 2056 saveHandler.accept(s); 2057 } 2058 }; 2059 ExternalEditor.edit(editor.cmd, src, 2060 errorHandler, extSaveHandler, 2061 () -> input.suspend(), 2062 () -> input.resume(), 2063 editor.wait, 2064 () -> hardrb("jshell.msg.press.return.to.leave.edit.mode")); 2065 if (buffer[0] != null) { 2066 saveHandler.accept(buffer[0]); 2067 } 2068 } 2069 return true; 2070 } 2071 //where 2072 // start the built-in editor 2073 private boolean builtInEdit(String initialText, 2074 Consumer<String> saveHandler, Consumer<String> errorHandler) { 2075 try { 2076 ServiceLoader<BuildInEditorProvider> sl 2077 = ServiceLoader.load(BuildInEditorProvider.class); 2078 // Find the highest ranking provider 2079 BuildInEditorProvider provider = null; 2080 for (BuildInEditorProvider p : sl) { 2081 if (provider == null || p.rank() > provider.rank()) { 2082 provider = p; 2083 } 2084 } 2085 if (provider != null) { 2086 provider.edit(getResourceString("jshell.label.editpad"), 2087 initialText, saveHandler, errorHandler); 2088 return true; 2089 } else { 2090 errormsg("jshell.err.no.builtin.editor"); 2091 } 2092 } catch (RuntimeException ex) { 2093 errormsg("jshell.err.cant.launch.editor", ex); 2094 } 2095 fluffmsg("jshell.msg.try.set.editor"); 2096 return false; 2097 } 2098 //where 2099 // receives editor requests to save 2100 private class SaveHandler implements Consumer<String> { 2101 2102 String src; 2103 Set<String> currSrcs; 2104 2105 SaveHandler(String src, Set<String> ss) { 2106 this.src = src; 2107 this.currSrcs = ss; 2108 } 2109 2110 @Override 2111 public void accept(String s) { 2112 if (!s.equals(src)) { // quick check first 2113 src = s; 2114 try { 2115 Set<String> nextSrcs = new LinkedHashSet<>(); 2116 boolean failed = false; 2117 while (true) { 2118 CompletionInfo an = analysis.analyzeCompletion(s); 2119 if (!an.completeness().isComplete()) { 2120 break; 2121 } 2122 String tsrc = trimNewlines(an.source()); 2123 if (!failed && !currSrcs.contains(tsrc)) { 2124 failed = processCompleteSource(tsrc); 2125 } 2126 nextSrcs.add(tsrc); 2127 if (an.remaining().isEmpty()) { 2128 break; 2129 } 2130 s = an.remaining(); 2131 } 2132 currSrcs = nextSrcs; 2133 } catch (IllegalStateException ex) { 2134 hardmsg("jshell.msg.resetting"); 2135 resetState(); 2136 currSrcs = new LinkedHashSet<>(); // re-process everything 2137 } 2138 } 2139 } 2140 2141 private String trimNewlines(String s) { 2142 int b = 0; 2143 while (b < s.length() && s.charAt(b) == '\n') { 2144 ++b; 2145 } 2146 int e = s.length() -1; 2147 while (e >= 0 && s.charAt(e) == '\n') { 2148 --e; 2149 } 2150 return s.substring(b, e + 1); 2151 } 2152 } 2153 2154 private boolean cmdList(String arg) { 2155 if (arg.length() >= 2 && "-history".startsWith(arg)) { 2156 return cmdHistory(); 2157 } 2158 Stream<Snippet> stream = argsOptionsToSnippets(state::snippets, 2159 this::mainActive, arg, "/list"); 2160 if (stream == null) { 2161 return false; 2162 } 2163 2164 // prevent double newline on empty list 2165 boolean[] hasOutput = new boolean[1]; 2166 stream.forEachOrdered(sn -> { 2167 if (!hasOutput[0]) { 2168 cmdout.println(); 2169 hasOutput[0] = true; 2170 } 2171 cmdout.printf("%4s : %s\n", sn.id(), sn.source().replace("\n", "\n ")); 2172 }); 2173 return true; 2174 } 2175 2176 private boolean cmdOpen(String filename) { 2177 return runFile(filename, "/open"); 2178 } 2179 2180 private boolean runFile(String filename, String context) { 2181 if (!filename.isEmpty()) { 2182 try { 2183 run(new FileScannerIOContext(toPathResolvingUserHome(filename).toString())); 2184 return true; 2185 } catch (FileNotFoundException e) { 2186 errormsg("jshell.err.file.not.found", context, filename, e.getMessage()); 2187 } catch (Exception e) { 2188 errormsg("jshell.err.file.exception", context, filename, e); 2189 } 2190 } else { 2191 errormsg("jshell.err.file.filename", context); 2192 } 2193 return false; 2194 } 2195 2196 /** 2197 * Read an external file. Error messages accessed via keyPrefix 2198 * 2199 * @param filename file to access or null 2200 * @param context printable non-natural language context for errors 2201 * @return contents of file as string 2202 */ 2203 String readFile(String filename, String context) { 2204 if (filename != null) { 2205 try { 2206 byte[] encoded = Files.readAllBytes(Paths.get(filename)); 2207 return new String(encoded); 2208 } catch (AccessDeniedException e) { 2209 errormsg("jshell.err.file.not.accessible", context, filename, e.getMessage()); 2210 } catch (NoSuchFileException e) { 2211 errormsg("jshell.err.file.not.found", context, filename); 2212 } catch (Exception e) { 2213 errormsg("jshell.err.file.exception", context, filename, e); 2214 } 2215 } else { 2216 errormsg("jshell.err.file.filename", context); 2217 } 2218 return null; 2219 2220 } 2221 2222 private boolean cmdReset() { 2223 live = false; 2224 fluffmsg("jshell.msg.resetting.state"); 2225 return true; 2226 } 2227 2228 private boolean cmdReload(String rawargs) { 2229 ArgTokenizer at = new ArgTokenizer("/reload", rawargs.trim()); 2230 at.allowedOptions("-restore", "-quiet"); 2231 if (!checkOptionsAndRemainingInput(at)) { 2232 return false; 2233 } 2234 Iterable<String> history; 2235 if (at.hasOption("-restore")) { 2236 if (replayableHistoryPrevious == null) { 2237 errormsg("jshell.err.reload.no.previous"); 2238 return false; 2239 } 2240 history = replayableHistoryPrevious; 2241 fluffmsg("jshell.err.reload.restarting.previous.state"); 2242 } else { 2243 history = replayableHistory; 2244 fluffmsg("jshell.err.reload.restarting.state"); 2245 } 2246 boolean echo = !at.hasOption("-quiet"); 2247 resetState(); 2248 run(new ReloadIOContext(history, 2249 echo ? cmdout : null)); 2250 return true; 2251 } 2252 2253 private boolean cmdSave(String rawargs) { 2254 ArgTokenizer at = new ArgTokenizer("/save", rawargs.trim()); 2255 at.allowedOptions("-all", "-start", "-history"); 2256 String filename = at.next(); 2257 if (filename == null) { 2258 errormsg("jshell.err.file.filename", "/save"); 2259 return false; 2260 } 2261 if (!checkOptionsAndRemainingInput(at)) { 2262 return false; 2263 } 2264 if (at.optionCount() > 1) { 2265 errormsg("jshell.err.conflicting.options", at.whole()); 2266 return false; 2267 } 2268 try (BufferedWriter writer = Files.newBufferedWriter(toPathResolvingUserHome(filename), 2269 Charset.defaultCharset(), 2270 CREATE, TRUNCATE_EXISTING, WRITE)) { 2271 if (at.hasOption("-history")) { 2272 for (String s : input.currentSessionHistory()) { 2273 writer.write(s); 2274 writer.write("\n"); 2275 } 2276 } else if (at.hasOption("-start")) { 2277 writer.append(startup); 2278 } else { 2279 String sources = (at.hasOption("-all") 2280 ? state.snippets() 2281 : state.snippets().filter(this::mainActive)) 2282 .map(Snippet::source) 2283 .collect(Collectors.joining("\n")); 2284 writer.write(sources); 2285 } 2286 } catch (FileNotFoundException e) { 2287 errormsg("jshell.err.file.not.found", "/save", filename, e.getMessage()); 2288 return false; 2289 } catch (Exception e) { 2290 errormsg("jshell.err.file.exception", "/save", filename, e); 2291 return false; 2292 } 2293 return true; 2294 } 2295 2296 private boolean cmdVars(String arg) { 2297 Stream<VarSnippet> stream = argsOptionsToSnippets(this::allVarSnippets, 2298 this::isActive, arg, "/vars"); 2299 if (stream == null) { 2300 return false; 2301 } 2302 stream.forEachOrdered(vk -> 2303 { 2304 String val = state.status(vk) == Status.VALID 2305 ? feedback.truncateVarValue(state.varValue(vk)) 2306 : getResourceString("jshell.msg.vars.not.active"); 2307 hard(" %s %s = %s", vk.typeName(), vk.name(), val); 2308 }); 2309 return true; 2310 } 2311 2312 private boolean cmdMethods(String arg) { 2313 Stream<MethodSnippet> stream = argsOptionsToSnippets(this::allMethodSnippets, 2314 this::isActive, arg, "/methods"); 2315 if (stream == null) { 2316 return false; 2317 } 2318 stream.forEachOrdered(mk 2319 -> hard(" %s %s", mk.name(), mk.signature()) 2320 ); 2321 return true; 2322 } 2323 2324 private boolean cmdTypes(String arg) { 2325 Stream<TypeDeclSnippet> stream = argsOptionsToSnippets(this::allTypeSnippets, 2326 this::isActive, arg, "/types"); 2327 if (stream == null) { 2328 return false; 2329 } 2330 stream.forEachOrdered(ck 2331 -> { 2332 String kind; 2333 switch (ck.subKind()) { 2334 case INTERFACE_SUBKIND: 2335 kind = "interface"; 2336 break; 2337 case CLASS_SUBKIND: 2338 kind = "class"; 2339 break; 2340 case ENUM_SUBKIND: 2341 kind = "enum"; 2342 break; 2343 case ANNOTATION_TYPE_SUBKIND: 2344 kind = "@interface"; 2345 break; 2346 default: 2347 assert false : "Wrong kind" + ck.subKind(); 2348 kind = "class"; 2349 break; 2350 } 2351 hard(" %s %s", kind, ck.name()); 2352 }); 2353 return true; 2354 } 2355 2356 private boolean cmdImports() { 2357 state.imports().forEach(ik -> { 2358 hard(" import %s%s", ik.isStatic() ? "static " : "", ik.fullname()); 2359 }); 2360 return true; 2361 } 2362 2363 private boolean cmdUseHistoryEntry(int index) { 2364 List<Snippet> keys = state.snippets().collect(toList()); 2365 if (index < 0) 2366 index += keys.size(); 2367 else 2368 index--; 2369 if (index >= 0 && index < keys.size()) { 2370 rerunSnippet(keys.get(index)); 2371 } else { 2372 errormsg("jshell.err.out.of.range"); 2373 return false; 2374 } 2375 return true; 2376 } 2377 2378 boolean checkOptionsAndRemainingInput(ArgTokenizer at) { 2379 String junk = at.remainder(); 2380 if (!junk.isEmpty()) { 2381 errormsg("jshell.err.unexpected.at.end", junk, at.whole()); 2382 return false; 2383 } else { 2384 String bad = at.badOptions(); 2385 if (!bad.isEmpty()) { 2386 errormsg("jshell.err.unknown.option", bad, at.whole()); 2387 return false; 2388 } 2389 } 2390 return true; 2391 } 2392 2393 private boolean rerunHistoryEntryById(String id) { 2394 Optional<Snippet> snippet = state.snippets() 2395 .filter(s -> s.id().equals(id)) 2396 .findFirst(); 2397 return snippet.map(s -> { 2398 rerunSnippet(s); 2399 return true; 2400 }).orElse(false); 2401 } 2402 2403 private void rerunSnippet(Snippet snippet) { 2404 String source = snippet.source(); 2405 cmdout.printf("%s\n", source); 2406 input.replaceLastHistoryEntry(source); 2407 processSourceCatchingReset(source); 2408 } 2409 2410 /** 2411 * Filter diagnostics for only errors (no warnings, ...) 2412 * @param diagnostics input list 2413 * @return filtered list 2414 */ 2415 List<Diag> errorsOnly(List<Diag> diagnostics) { 2416 return diagnostics.stream() 2417 .filter(Diag::isError) 2418 .collect(toList()); 2419 } 2420 2421 void displayDiagnostics(String source, Diag diag, List<String> toDisplay) { 2422 for (String line : diag.getMessage(null).split("\\r?\\n")) { // TODO: Internationalize 2423 if (!line.trim().startsWith("location:")) { 2424 toDisplay.add(line); 2425 } 2426 } 2427 2428 int pstart = (int) diag.getStartPosition(); 2429 int pend = (int) diag.getEndPosition(); 2430 Matcher m = LINEBREAK.matcher(source); 2431 int pstartl = 0; 2432 int pendl = -2; 2433 while (m.find(pstartl)) { 2434 pendl = m.start(); 2435 if (pendl >= pstart) { 2436 break; 2437 } else { 2438 pstartl = m.end(); 2439 } 2440 } 2441 if (pendl < pstart) { 2442 pendl = source.length(); 2443 } 2444 toDisplay.add(source.substring(pstartl, pendl)); 2445 2446 StringBuilder sb = new StringBuilder(); 2447 int start = pstart - pstartl; 2448 for (int i = 0; i < start; ++i) { 2449 sb.append(' '); 2450 } 2451 sb.append('^'); 2452 boolean multiline = pend > pendl; 2453 int end = (multiline ? pendl : pend) - pstartl - 1; 2454 if (end > start) { 2455 for (int i = start + 1; i < end; ++i) { 2456 sb.append('-'); 2457 } 2458 if (multiline) { 2459 sb.append("-..."); 2460 } else { 2461 sb.append('^'); 2462 } 2463 } 2464 toDisplay.add(sb.toString()); 2465 2466 debug("printDiagnostics start-pos = %d ==> %d -- wrap = %s", diag.getStartPosition(), start, this); 2467 debug("Code: %s", diag.getCode()); 2468 debug("Pos: %d (%d - %d)", diag.getPosition(), 2469 diag.getStartPosition(), diag.getEndPosition()); 2470 } 2471 2472 private String processSource(String srcInput) throws IllegalStateException { 2473 while (true) { 2474 CompletionInfo an = analysis.analyzeCompletion(srcInput); 2475 if (!an.completeness().isComplete()) { 2476 return an.remaining(); 2477 } 2478 boolean failed = processCompleteSource(an.source()); 2479 if (failed || an.remaining().isEmpty()) { 2480 return ""; 2481 } 2482 srcInput = an.remaining(); 2483 } 2484 } 2485 //where 2486 private boolean processCompleteSource(String source) throws IllegalStateException { 2487 debug("Compiling: %s", source); 2488 boolean failed = false; 2489 boolean isActive = false; 2490 List<SnippetEvent> events = state.eval(source); 2491 for (SnippetEvent e : events) { 2492 // Report the event, recording failure 2493 failed |= handleEvent(e); 2494 2495 // If any main snippet is active, this should be replayable 2496 // also ignore var value queries 2497 isActive |= e.causeSnippet() == null && 2498 e.status().isActive() && 2499 e.snippet().subKind() != VAR_VALUE_SUBKIND; 2500 } 2501 // If this is an active snippet and it didn't cause the backend to die, 2502 // add it to the replayable history 2503 if (isActive && live) { 2504 addToReplayHistory(source); 2505 } 2506 2507 return failed; 2508 } 2509 2510 // Handle incoming snippet events -- return true on failure 2511 private boolean handleEvent(SnippetEvent ste) { 2512 Snippet sn = ste.snippet(); 2513 if (sn == null) { 2514 debug("Event with null key: %s", ste); 2515 return false; 2516 } 2517 List<Diag> diagnostics = state.diagnostics(sn).collect(toList()); 2518 String source = sn.source(); 2519 if (ste.causeSnippet() == null) { 2520 // main event 2521 for (Diag d : diagnostics) { 2522 hardmsg(d.isError()? "jshell.msg.error" : "jshell.msg.warning"); 2523 List<String> disp = new ArrayList<>(); 2524 displayDiagnostics(source, d, disp); 2525 disp.stream() 2526 .forEach(l -> hard("%s", l)); 2527 } 2528 2529 if (ste.status() != Status.REJECTED) { 2530 if (ste.exception() != null) { 2531 if (ste.exception() instanceof EvalException) { 2532 printEvalException((EvalException) ste.exception()); 2533 return true; 2534 } else if (ste.exception() instanceof UnresolvedReferenceException) { 2535 printUnresolvedException((UnresolvedReferenceException) ste.exception()); 2536 } else { 2537 hard("Unexpected execution exception: %s", ste.exception()); 2538 return true; 2539 } 2540 } else { 2541 new DisplayEvent(ste, false, ste.value(), diagnostics).displayDeclarationAndValue(); 2542 } 2543 } else { 2544 if (diagnostics.isEmpty()) { 2545 errormsg("jshell.err.failed"); 2546 } 2547 return true; 2548 } 2549 } else { 2550 // Update 2551 if (sn instanceof DeclarationSnippet) { 2552 List<Diag> other = errorsOnly(diagnostics); 2553 2554 // display update information 2555 new DisplayEvent(ste, true, ste.value(), other).displayDeclarationAndValue(); 2556 } 2557 } 2558 return false; 2559 } 2560 //where 2561 void printStackTrace(StackTraceElement[] stes) { 2562 for (StackTraceElement ste : stes) { 2563 StringBuilder sb = new StringBuilder(); 2564 String cn = ste.getClassName(); 2565 if (!cn.isEmpty()) { 2566 int dot = cn.lastIndexOf('.'); 2567 if (dot > 0) { 2568 sb.append(cn.substring(dot + 1)); 2569 } else { 2570 sb.append(cn); 2571 } 2572 sb.append("."); 2573 } 2574 if (!ste.getMethodName().isEmpty()) { 2575 sb.append(ste.getMethodName()); 2576 sb.append(" "); 2577 } 2578 String fileName = ste.getFileName(); 2579 int lineNumber = ste.getLineNumber(); 2580 String loc = ste.isNativeMethod() 2581 ? getResourceString("jshell.msg.native.method") 2582 : fileName == null 2583 ? getResourceString("jshell.msg.unknown.source") 2584 : lineNumber >= 0 2585 ? fileName + ":" + lineNumber 2586 : fileName; 2587 hard(" at %s(%s)", sb, loc); 2588 2589 } 2590 } 2591 //where 2592 void printUnresolvedException(UnresolvedReferenceException ex) { 2593 DeclarationSnippet corralled = ex.getSnippet(); 2594 List<Diag> otherErrors = errorsOnly(state.diagnostics(corralled).collect(toList())); 2595 new DisplayEvent(corralled, state.status(corralled), FormatAction.USED, true, null, otherErrors) 2596 .displayDeclarationAndValue(); 2597 } 2598 //where 2599 void printEvalException(EvalException ex) { 2600 if (ex.getMessage() == null) { 2601 hard("%s thrown", ex.getExceptionClassName()); 2602 } else { 2603 hard("%s thrown: %s", ex.getExceptionClassName(), ex.getMessage()); 2604 } 2605 printStackTrace(ex.getStackTrace()); 2606 } 2607 2608 private FormatAction toAction(Status status, Status previousStatus, boolean isSignatureChange) { 2609 FormatAction act; 2610 switch (status) { 2611 case VALID: 2612 case RECOVERABLE_DEFINED: 2613 case RECOVERABLE_NOT_DEFINED: 2614 if (previousStatus.isActive()) { 2615 act = isSignatureChange 2616 ? FormatAction.REPLACED 2617 : FormatAction.MODIFIED; 2618 } else { 2619 act = FormatAction.ADDED; 2620 } 2621 break; 2622 case OVERWRITTEN: 2623 act = FormatAction.OVERWROTE; 2624 break; 2625 case DROPPED: 2626 act = FormatAction.DROPPED; 2627 break; 2628 case REJECTED: 2629 case NONEXISTENT: 2630 default: 2631 // Should not occur 2632 error("Unexpected status: " + previousStatus.toString() + "=>" + status.toString()); 2633 act = FormatAction.DROPPED; 2634 } 2635 return act; 2636 } 2637 2638 class DisplayEvent { 2639 private final Snippet sn; 2640 private final FormatAction action; 2641 private final boolean update; 2642 private final String value; 2643 private final List<String> errorLines; 2644 private final FormatResolve resolution; 2645 private final String unresolved; 2646 private final FormatUnresolved unrcnt; 2647 private final FormatErrors errcnt; 2648 2649 DisplayEvent(SnippetEvent ste, boolean update, String value, List<Diag> errors) { 2650 this(ste.snippet(), ste.status(), toAction(ste.status(), ste.previousStatus(), ste.isSignatureChange()), update, value, errors); 2651 } 2652 2653 DisplayEvent(Snippet sn, Status status, FormatAction action, boolean update, String value, List<Diag> errors) { 2654 this.sn = sn; 2655 this.action = action; 2656 this.update = update; 2657 this.value = value; 2658 this.errorLines = new ArrayList<>(); 2659 for (Diag d : errors) { 2660 displayDiagnostics(sn.source(), d, errorLines); 2661 } 2662 long unresolvedCount; 2663 if (sn instanceof DeclarationSnippet && (status == Status.RECOVERABLE_DEFINED || status == Status.RECOVERABLE_NOT_DEFINED)) { 2664 resolution = (status == Status.RECOVERABLE_NOT_DEFINED) 2665 ? FormatResolve.NOTDEFINED 2666 : FormatResolve.DEFINED; 2667 unresolved = unresolved((DeclarationSnippet) sn); 2668 unresolvedCount = state.unresolvedDependencies((DeclarationSnippet) sn).count(); 2669 } else { 2670 resolution = FormatResolve.OK; 2671 unresolved = ""; 2672 unresolvedCount = 0; 2673 } 2674 unrcnt = unresolvedCount == 0 2675 ? FormatUnresolved.UNRESOLVED0 2676 : unresolvedCount == 1 2677 ? FormatUnresolved.UNRESOLVED1 2678 : FormatUnresolved.UNRESOLVED2; 2679 errcnt = errors.isEmpty() 2680 ? FormatErrors.ERROR0 2681 : errors.size() == 1 2682 ? FormatErrors.ERROR1 2683 : FormatErrors.ERROR2; 2684 } 2685 2686 private String unresolved(DeclarationSnippet key) { 2687 List<String> unr = state.unresolvedDependencies(key).collect(toList()); 2688 StringBuilder sb = new StringBuilder(); 2689 int fromLast = unr.size(); 2690 if (fromLast > 0) { 2691 sb.append(" "); 2692 } 2693 for (String u : unr) { 2694 --fromLast; 2695 sb.append(u); 2696 switch (fromLast) { 2697 // No suffix 2698 case 0: 2699 break; 2700 case 1: 2701 sb.append(", and "); 2702 break; 2703 default: 2704 sb.append(", "); 2705 break; 2706 } 2707 } 2708 return sb.toString(); 2709 } 2710 2711 private void custom(FormatCase fcase, String name) { 2712 custom(fcase, name, null); 2713 } 2714 2715 private void custom(FormatCase fcase, String name, String type) { 2716 String display = feedback.format(fcase, action, (update ? FormatWhen.UPDATE : FormatWhen.PRIMARY), 2717 resolution, unrcnt, errcnt, 2718 name, type, value, unresolved, errorLines); 2719 if (interactive()) { 2720 cmdout.print(display); 2721 } 2722 } 2723 2724 @SuppressWarnings("fallthrough") 2725 private void displayDeclarationAndValue() { 2726 switch (sn.subKind()) { 2727 case CLASS_SUBKIND: 2728 custom(FormatCase.CLASS, ((TypeDeclSnippet) sn).name()); 2729 break; 2730 case INTERFACE_SUBKIND: 2731 custom(FormatCase.INTERFACE, ((TypeDeclSnippet) sn).name()); 2732 break; 2733 case ENUM_SUBKIND: 2734 custom(FormatCase.ENUM, ((TypeDeclSnippet) sn).name()); 2735 break; 2736 case ANNOTATION_TYPE_SUBKIND: 2737 custom(FormatCase.ANNOTATION, ((TypeDeclSnippet) sn).name()); 2738 break; 2739 case METHOD_SUBKIND: 2740 custom(FormatCase.METHOD, ((MethodSnippet) sn).name(), ((MethodSnippet) sn).parameterTypes()); 2741 break; 2742 case VAR_DECLARATION_SUBKIND: { 2743 VarSnippet vk = (VarSnippet) sn; 2744 custom(FormatCase.VARDECL, vk.name(), vk.typeName()); 2745 break; 2746 } 2747 case VAR_DECLARATION_WITH_INITIALIZER_SUBKIND: { 2748 VarSnippet vk = (VarSnippet) sn; 2749 custom(FormatCase.VARINIT, vk.name(), vk.typeName()); 2750 break; 2751 } 2752 case TEMP_VAR_EXPRESSION_SUBKIND: { 2753 VarSnippet vk = (VarSnippet) sn; 2754 custom(FormatCase.EXPRESSION, vk.name(), vk.typeName()); 2755 break; 2756 } 2757 case OTHER_EXPRESSION_SUBKIND: 2758 error("Unexpected expression form -- value is: %s", (value)); 2759 break; 2760 case VAR_VALUE_SUBKIND: { 2761 ExpressionSnippet ek = (ExpressionSnippet) sn; 2762 custom(FormatCase.VARVALUE, ek.name(), ek.typeName()); 2763 break; 2764 } 2765 case ASSIGNMENT_SUBKIND: { 2766 ExpressionSnippet ek = (ExpressionSnippet) sn; 2767 custom(FormatCase.ASSIGNMENT, ek.name(), ek.typeName()); 2768 break; 2769 } 2770 case SINGLE_TYPE_IMPORT_SUBKIND: 2771 case TYPE_IMPORT_ON_DEMAND_SUBKIND: 2772 case SINGLE_STATIC_IMPORT_SUBKIND: 2773 case STATIC_IMPORT_ON_DEMAND_SUBKIND: 2774 custom(FormatCase.IMPORT, ((ImportSnippet) sn).name()); 2775 break; 2776 case STATEMENT_SUBKIND: 2777 custom(FormatCase.STATEMENT, null); 2778 break; 2779 } 2780 } 2781 } 2782 2783 /** The current version number as a string. 2784 */ 2785 String version() { 2786 return version("release"); // mm.nn.oo[-milestone] 2787 } 2788 2789 /** The current full version number as a string. 2790 */ 2791 String fullVersion() { 2792 return version("full"); // mm.mm.oo[-milestone]-build 2793 } 2794 2795 private String version(String key) { 2796 if (versionRB == null) { 2797 try { 2798 versionRB = ResourceBundle.getBundle(VERSION_RB_NAME, locale); 2799 } catch (MissingResourceException e) { 2800 return "(version info not available)"; 2801 } 2802 } 2803 try { 2804 return versionRB.getString(key); 2805 } 2806 catch (MissingResourceException e) { 2807 return "(version info not available)"; 2808 } 2809 } 2810 2811 class NameSpace { 2812 final String spaceName; 2813 final String prefix; 2814 private int nextNum; 2815 2816 NameSpace(String spaceName, String prefix) { 2817 this.spaceName = spaceName; 2818 this.prefix = prefix; 2819 this.nextNum = 1; 2820 } 2821 2822 String tid(Snippet sn) { 2823 String tid = prefix + nextNum++; 2824 mapSnippet.put(sn, new SnippetInfo(sn, this, tid)); 2825 return tid; 2826 } 2827 2828 String tidNext() { 2829 return prefix + nextNum; 2830 } 2831 } 2832 2833 static class SnippetInfo { 2834 final Snippet snippet; 2835 final NameSpace space; 2836 final String tid; 2837 2838 SnippetInfo(Snippet snippet, NameSpace space, String tid) { 2839 this.snippet = snippet; 2840 this.space = space; 2841 this.tid = tid; 2842 } 2843 } 2844 2845 static class ArgSuggestion implements Suggestion { 2846 2847 private final String continuation; 2848 2849 /** 2850 * Create a {@code Suggestion} instance. 2851 * 2852 * @param continuation a candidate continuation of the user's input 2853 */ 2854 public ArgSuggestion(String continuation) { 2855 this.continuation = continuation; 2856 } 2857 2858 /** 2859 * The candidate continuation of the given user's input. 2860 * 2861 * @return the continuation string 2862 */ 2863 @Override 2864 public String continuation() { 2865 return continuation; 2866 } 2867 2868 /** 2869 * Indicates whether input continuation matches the target type and is thus 2870 * more likely to be the desired continuation. A matching continuation is 2871 * preferred. 2872 * 2873 * @return {@code false}, non-types analysis 2874 */ 2875 @Override 2876 public boolean matchesType() { 2877 return false; 2878 } 2879 } 2880} 2881 2882abstract class NonInteractiveIOContext extends IOContext { 2883 2884 @Override 2885 public boolean interactiveOutput() { 2886 return false; 2887 } 2888 2889 @Override 2890 public Iterable<String> currentSessionHistory() { 2891 return Collections.emptyList(); 2892 } 2893 2894 @Override 2895 public boolean terminalEditorRunning() { 2896 return false; 2897 } 2898 2899 @Override 2900 public void suspend() { 2901 } 2902 2903 @Override 2904 public void resume() { 2905 } 2906 2907 @Override 2908 public void beforeUserCode() { 2909 } 2910 2911 @Override 2912 public void afterUserCode() { 2913 } 2914 2915 @Override 2916 public void replaceLastHistoryEntry(String source) { 2917 } 2918} 2919 2920class ScannerIOContext extends NonInteractiveIOContext { 2921 private final Scanner scannerIn; 2922 2923 ScannerIOContext(Scanner scannerIn) { 2924 this.scannerIn = scannerIn; 2925 } 2926 2927 @Override 2928 public String readLine(String prompt, String prefix) { 2929 if (scannerIn.hasNextLine()) { 2930 return scannerIn.nextLine(); 2931 } else { 2932 return null; 2933 } 2934 } 2935 2936 @Override 2937 public void close() { 2938 scannerIn.close(); 2939 } 2940 2941 @Override 2942 public int readUserInput() { 2943 return -1; 2944 } 2945} 2946 2947class FileScannerIOContext extends ScannerIOContext { 2948 2949 FileScannerIOContext(String fn) throws FileNotFoundException { 2950 this(new FileReader(fn)); 2951 } 2952 2953 FileScannerIOContext(Reader rdr) throws FileNotFoundException { 2954 super(new Scanner(rdr)); 2955 } 2956} 2957 2958class ReloadIOContext extends NonInteractiveIOContext { 2959 private final Iterator<String> it; 2960 private final PrintStream echoStream; 2961 2962 ReloadIOContext(Iterable<String> history, PrintStream echoStream) { 2963 this.it = history.iterator(); 2964 this.echoStream = echoStream; 2965 } 2966 2967 @Override 2968 public String readLine(String prompt, String prefix) { 2969 String s = it.hasNext() 2970 ? it.next() 2971 : null; 2972 if (echoStream != null && s != null) { 2973 String p = "-: "; 2974 String p2 = "\n "; 2975 echoStream.printf("%s%s\n", p, s.replace("\n", p2)); 2976 } 2977 return s; 2978 } 2979 2980 @Override 2981 public void close() { 2982 } 2983 2984 @Override 2985 public int readUserInput() { 2986 return -1; 2987 } 2988} 2989