langtools/src/jdk.jshell/share/classes/jdk/internal/jshell/tool/JShellTool.java
changeset 34999 aacf94dab449
parent 34570 8a8f52a733dd
child 35000 952a7b4652f0
equal deleted inserted replaced
34998:416dfba03d33 34999:aacf94dab449
       
     1 
     1 /*
     2 /*
     2  * Copyright (c) 2014, 2015, Oracle and/or its affiliates. All rights reserved.
     3  * Copyright (c) 2014, 2015, Oracle and/or its affiliates. All rights reserved.
     3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
     4  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
     4  *
     5  *
     5  * This code is free software; you can redistribute it and/or modify it
     6  * This code is free software; you can redistribute it and/or modify it
    88 import static java.nio.file.StandardOpenOption.WRITE;
    89 import static java.nio.file.StandardOpenOption.WRITE;
    89 import java.util.MissingResourceException;
    90 import java.util.MissingResourceException;
    90 import java.util.Optional;
    91 import java.util.Optional;
    91 import java.util.ResourceBundle;
    92 import java.util.ResourceBundle;
    92 import java.util.Spliterators;
    93 import java.util.Spliterators;
       
    94 import java.util.function.Function;
    93 import java.util.function.Supplier;
    95 import java.util.function.Supplier;
    94 import static java.util.stream.Collectors.toList;
    96 import static java.util.stream.Collectors.toList;
       
    97 import static jdk.jshell.Snippet.SubKind.VAR_VALUE_SUBKIND;
    95 
    98 
    96 /**
    99 /**
    97  * Command line REPL tool for Java using the JShell API.
   100  * Command line REPL tool for Java using the JShell API.
    98  * @author Robert Field
   101  * @author Robert Field
    99  */
   102  */
   100 public class JShellTool {
   103 public class JShellTool {
   101 
   104 
   102     private static final Pattern LINEBREAK = Pattern.compile("\\R");
   105     private static final Pattern LINEBREAK = Pattern.compile("\\R");
   103     private static final Pattern HISTORY_ALL_START_FILENAME = Pattern.compile(
   106     private static final Pattern HISTORY_ALL_START_FILENAME = Pattern.compile(
   104             "((?<cmd>(all|history|start))(\\z|\\p{javaWhitespace}+))?(?<filename>.*)");
   107             "((?<cmd>(all|history|start))(\\z|\\p{javaWhitespace}+))?(?<filename>.*)");
       
   108     private static final String RECORD_SEPARATOR = "\u241E";
   105 
   109 
   106     final InputStream cmdin;
   110     final InputStream cmdin;
   107     final PrintStream cmdout;
   111     final PrintStream cmdout;
   108     final PrintStream cmderr;
   112     final PrintStream cmderr;
   109     final PrintStream console;
   113     final PrintStream console;
   148     private Feedback feedback = Feedback.Default;
   152     private Feedback feedback = Feedback.Default;
   149     private String cmdlineClasspath = null;
   153     private String cmdlineClasspath = null;
   150     private String cmdlineStartup = null;
   154     private String cmdlineStartup = null;
   151     private String editor = null;
   155     private String editor = null;
   152 
   156 
   153     static final Preferences PREFS = Preferences.userRoot().node("tool/REPL");
   157     // Commands and snippets which should be replayed
       
   158     private List<String> replayableHistory;
       
   159     private List<String> replayableHistoryPrevious;
       
   160 
       
   161     static final Preferences PREFS = Preferences.userRoot().node("tool/JShell");
   154 
   162 
   155     static final String STARTUP_KEY = "STARTUP";
   163     static final String STARTUP_KEY = "STARTUP";
       
   164     static final String REPLAY_RESTORE_KEY = "REPLAY_RESTORE";
   156 
   165 
   157     static final String DEFAULT_STARTUP =
   166     static final String DEFAULT_STARTUP =
   158             "\n" +
   167             "\n" +
   159             "import java.util.*;\n" +
   168             "import java.util.*;\n" +
   160             "import java.io.*;\n" +
   169             "import java.io.*;\n" +
   163             "import java.util.concurrent.*;\n" +
   172             "import java.util.concurrent.*;\n" +
   164             "import java.util.prefs.*;\n" +
   173             "import java.util.prefs.*;\n" +
   165             "import java.util.regex.*;\n" +
   174             "import java.util.regex.*;\n" +
   166             "void printf(String format, Object... args) { System.out.printf(format, args); }\n";
   175             "void printf(String format, Object... args) { System.out.printf(format, args); }\n";
   167 
   176 
   168     // Tool id (tid) mapping
   177     // Tool id (tid) mapping: the three name spaces
   169     NameSpace mainNamespace;
   178     NameSpace mainNamespace;
   170     NameSpace startNamespace;
   179     NameSpace startNamespace;
   171     NameSpace errorNamespace;
   180     NameSpace errorNamespace;
       
   181 
       
   182     // Tool id (tid) mapping: the current name spaces
   172     NameSpace currentNameSpace;
   183     NameSpace currentNameSpace;
       
   184 
   173     Map<Snippet,SnippetInfo> mapSnippet;
   185     Map<Snippet,SnippetInfo> mapSnippet;
   174 
   186 
   175     void debug(String format, Object... args) {
   187     void debug(String format, Object... args) {
   176         if (debug) {
   188         if (debug) {
   177             cmderr.printf(format + "\n", args);
   189             cmderr.printf(format + "\n", args);
   249         }
   261         }
   250     }
   262     }
   251 
   263 
   252     private void start(IOContext in, List<String> loadList) {
   264     private void start(IOContext in, List<String> loadList) {
   253         resetState(); // Initialize
   265         resetState(); // Initialize
       
   266 
       
   267         // Read replay history from last jshell session into previous history
       
   268         String prevReplay = PREFS.get(REPLAY_RESTORE_KEY, null);
       
   269         if (prevReplay != null) {
       
   270             replayableHistoryPrevious = Arrays.asList(prevReplay.split(RECORD_SEPARATOR));
       
   271         }
   254 
   272 
   255         for (String loadFile : loadList) {
   273         for (String loadFile : loadList) {
   256             cmdOpen(loadFile);
   274             cmdOpen(loadFile);
   257         }
   275         }
   258 
   276 
   368         startNamespace = new NameSpace("start", "s");
   386         startNamespace = new NameSpace("start", "s");
   369         errorNamespace = new NameSpace("error", "e");
   387         errorNamespace = new NameSpace("error", "e");
   370         mapSnippet = new LinkedHashMap<>();
   388         mapSnippet = new LinkedHashMap<>();
   371         currentNameSpace = startNamespace;
   389         currentNameSpace = startNamespace;
   372 
   390 
       
   391         // Reset the replayable history, saving the old for restore
       
   392         replayableHistoryPrevious = replayableHistory;
       
   393         replayableHistory = new ArrayList<>();
       
   394 
   373         state = JShell.builder()
   395         state = JShell.builder()
   374                 .in(userin)
   396                 .in(userin)
   375                 .out(userout)
   397                 .out(userout)
   376                 .err(usererr)
   398                 .err(usererr)
   377                 .tempVariableNameGenerator(()-> "$" + currentNameSpace.tidNext())
   399                 .tempVariableNameGenerator(()-> "$" + currentNameSpace.tidNext())
   380                         : errorNamespace.tid(sn))
   402                         : errorNamespace.tid(sn))
   381                 .build();
   403                 .build();
   382         analysis = state.sourceCodeAnalysis();
   404         analysis = state.sourceCodeAnalysis();
   383         shutdownSubscription = state.onShutdown((JShell deadState) -> {
   405         shutdownSubscription = state.onShutdown((JShell deadState) -> {
   384             if (deadState == state) {
   406             if (deadState == state) {
   385                 hard("State engine terminated.  See /history");
   407                 hard("State engine terminated.");
       
   408                 hard("Restore definitions with: /reload restore");
   386                 live = false;
   409                 live = false;
   387             }
   410             }
   388         });
   411         });
   389         live = true;
   412         live = true;
   390 
   413 
   391         if (cmdlineClasspath != null) {
   414         if (cmdlineClasspath != null) {
   392             state.addToClasspath(cmdlineClasspath);
   415             state.addToClasspath(cmdlineClasspath);
   393         }
   416         }
   394 
       
   395 
   417 
   396         String start;
   418         String start;
   397         if (cmdlineStartup == null) {
   419         if (cmdlineStartup == null) {
   398             start = PREFS.get(STARTUP_KEY, "<nada>");
   420             start = PREFS.get(STARTUP_KEY, "<nada>");
   399             if (start.equals("<nada>")) {
   421             if (start.equals("<nada>")) {
   429         input = in;
   451         input = in;
   430         try {
   452         try {
   431             String incomplete = "";
   453             String incomplete = "";
   432             while (live) {
   454             while (live) {
   433                 String prompt;
   455                 String prompt;
   434                 if (in.interactiveOutput() && displayPrompt) {
   456                 if (displayPrompt) {
   435                     prompt = testPrompt
   457                     prompt = testPrompt
   436                                     ? incomplete.isEmpty()
   458                                     ? incomplete.isEmpty()
   437                                             ? "\u0005" //ENQ
   459                                             ? "\u0005" //ENQ
   438                                             : "\u0006" //ACK
   460                                             : "\u0006" //ACK
   439                                     : incomplete.isEmpty()
   461                                     : incomplete.isEmpty()
   478         } finally {
   500         } finally {
   479             input = oldInput;
   501             input = oldInput;
   480         }
   502         }
   481     }
   503     }
   482 
   504 
       
   505     private void addToReplayHistory(String s) {
       
   506         if (currentNameSpace == mainNamespace) {
       
   507             replayableHistory.add(s);
       
   508         }
       
   509     }
       
   510 
   483     private String processSourceCatchingReset(String src) {
   511     private String processSourceCatchingReset(String src) {
   484         try {
   512         try {
   485             input.beforeUserCode();
   513             input.beforeUserCode();
   486             return processSource(src);
   514             return processSource(src);
   487         } catch (IllegalStateException ex) {
   515         } catch (IllegalStateException ex) {
   514             if (!rerunHistoryEntryById(cmd.substring(1))) {
   542             if (!rerunHistoryEntryById(cmd.substring(1))) {
   515                 hard("No such command or snippet id: %s", cmd);
   543                 hard("No such command or snippet id: %s", cmd);
   516                 fluff("Type /help for help.");
   544                 fluff("Type /help for help.");
   517             }
   545             }
   518         } else if (candidates.length == 1) {
   546         } else if (candidates.length == 1) {
   519             candidates[0].run.accept(arg);
   547             Command command = candidates[0];
       
   548 
       
   549             // If comand was successful and is of a replayable kind, add it the replayable history
       
   550             if (command.run.apply(arg) && command.kind == CommandKind.REPLAY) {
       
   551                 addToReplayHistory((command.command + " " + arg).trim());
       
   552             }
   520         } else {
   553         } else {
   521             hard("Command: %s is ambiguous: %s", cmd, Arrays.stream(candidates).map(c -> c.command).collect(Collectors.joining(", ")));
   554             hard("Command: %s is ambiguous: %s", cmd, Arrays.stream(candidates).map(c -> c.command).collect(Collectors.joining(", ")));
   522             fluff("Type /help for help.");
   555             fluff("Type /help for help.");
   523         }
   556         }
   524     }
   557     }
   544 
   577 
   545     static final class Command {
   578     static final class Command {
   546         public final String command;
   579         public final String command;
   547         public final String params;
   580         public final String params;
   548         public final String description;
   581         public final String description;
   549         public final Consumer<String> run;
   582         public final Function<String,Boolean> run;
   550         public final CompletionProvider completions;
   583         public final CompletionProvider completions;
   551         public final CommandKind kind;
   584         public final CommandKind kind;
   552 
   585 
   553         public Command(String command, String params, String description, Consumer<String> run, CompletionProvider completions) {
   586         public Command(String command, String params, String description, Function<String,Boolean> run, CompletionProvider completions) {
   554             this(command, params, description, run, completions, CommandKind.NORMAL);
   587             this(command, params, description, run, completions, CommandKind.NORMAL);
   555         }
   588         }
   556 
   589 
   557         public Command(String command, String params, String description, Consumer<String> run, CompletionProvider completions, CommandKind kind) {
   590         public Command(String command, String params, String description, Function<String,Boolean> run, CompletionProvider completions, CommandKind kind) {
   558             this.command = command;
   591             this.command = command;
   559             this.params = params;
   592             this.params = params;
   560             this.description = description;
   593             this.description = description;
   561             this.run = run;
   594             this.run = run;
   562             this.completions = completions;
   595             this.completions = completions;
   569         List<Suggestion> completionSuggestions(String input, int cursor, int[] anchor);
   602         List<Suggestion> completionSuggestions(String input, int cursor, int[] anchor);
   570     }
   603     }
   571 
   604 
   572     enum CommandKind {
   605     enum CommandKind {
   573         NORMAL,
   606         NORMAL,
       
   607         REPLAY,
   574         HIDDEN,
   608         HIDDEN,
   575         HELP_ONLY;
   609         HELP_ONLY;
   576     }
   610     }
   577 
   611 
   578     static final class FixedCompletionProvider implements CompletionProvider {
   612     static final class FixedCompletionProvider implements CompletionProvider {
   600 
   634 
   601     }
   635     }
   602 
   636 
   603     private static final CompletionProvider EMPTY_COMPLETION_PROVIDER = new FixedCompletionProvider();
   637     private static final CompletionProvider EMPTY_COMPLETION_PROVIDER = new FixedCompletionProvider();
   604     private static final CompletionProvider KEYWORD_COMPLETION_PROVIDER = new FixedCompletionProvider("all ", "start ", "history ");
   638     private static final CompletionProvider KEYWORD_COMPLETION_PROVIDER = new FixedCompletionProvider("all ", "start ", "history ");
       
   639     private static final CompletionProvider RELOAD_OPTIONS_COMPLETION_PROVIDER = new FixedCompletionProvider("restore", "quiet");
   605     private static final CompletionProvider FILE_COMPLETION_PROVIDER = fileCompletions(p -> true);
   640     private static final CompletionProvider FILE_COMPLETION_PROVIDER = fileCompletions(p -> true);
   606     private final Map<String, Command> commands = new LinkedHashMap<>();
   641     private final Map<String, Command> commands = new LinkedHashMap<>();
   607     private void registerCommand(Command cmd) {
   642     private void registerCommand(Command cmd) {
   608         commands.put(cmd.command, cmd);
   643         commands.put(cmd.command, cmd);
   609     }
   644     }
   672             anchor[0] += space + 1;
   707             anchor[0] += space + 1;
   673             return result;
   708             return result;
   674         };
   709         };
   675     }
   710     }
   676 
   711 
       
   712     private static CompletionProvider reloadCompletion() {
       
   713         return (code, cursor, anchor) -> {
       
   714             List<Suggestion> result = new ArrayList<>();
       
   715             int pastSpace = code.indexOf(' ') + 1; // zero if no space
       
   716             result.addAll(RELOAD_OPTIONS_COMPLETION_PROVIDER.completionSuggestions(code.substring(pastSpace), cursor - pastSpace, anchor));
       
   717             anchor[0] += pastSpace;
       
   718             return result;
       
   719         };
       
   720     }
       
   721 
   677     // Table of commands -- with command forms, argument kinds, help message, implementation, ...
   722     // Table of commands -- with command forms, argument kinds, help message, implementation, ...
   678 
   723 
   679     {
   724     {
   680         registerCommand(new Command("/list", "[all|start|history|<name or id>]", "list the source you have typed",
   725         registerCommand(new Command("/list", "[all|start|history|<name or id>]", "list the source you have typed",
   681                                     arg -> cmdList(arg),
   726                                     arg -> cmdList(arg),
   686         registerCommand(new Command("/edit", "<name or id>", "edit a source entry referenced by name or id",
   731         registerCommand(new Command("/edit", "<name or id>", "edit a source entry referenced by name or id",
   687                                     arg -> cmdEdit(arg),
   732                                     arg -> cmdEdit(arg),
   688                                     editCompletion()));
   733                                     editCompletion()));
   689         registerCommand(new Command("/drop", "<name or id>", "delete a source entry referenced by name or id",
   734         registerCommand(new Command("/drop", "<name or id>", "delete a source entry referenced by name or id",
   690                                     arg -> cmdDrop(arg),
   735                                     arg -> cmdDrop(arg),
   691                                     editCompletion()));
   736                                     editCompletion(),
       
   737                                     CommandKind.REPLAY));
   692         registerCommand(new Command("/save", "[all|history|start] <file>", "save: <none> - current source;\n" +
   738         registerCommand(new Command("/save", "[all|history|start] <file>", "save: <none> - current source;\n" +
   693                                                                            "      all - source including overwritten, failed, and start-up code;\n" +
   739                                                                            "      all - source including overwritten, failed, and start-up code;\n" +
   694                                                                            "      history - editing history;\n" +
   740                                                                            "      history - editing history;\n" +
   695                                                                            "      start - default start-up definitions",
   741                                                                            "      start - default start-up definitions",
   696                                     arg -> cmdSave(arg),
   742                                     arg -> cmdSave(arg),
   714                                     arg -> cmdExit(),
   760                                     arg -> cmdExit(),
   715                                     EMPTY_COMPLETION_PROVIDER));
   761                                     EMPTY_COMPLETION_PROVIDER));
   716         registerCommand(new Command("/reset", null, "reset everything in the REPL",
   762         registerCommand(new Command("/reset", null, "reset everything in the REPL",
   717                                     arg -> cmdReset(),
   763                                     arg -> cmdReset(),
   718                                     EMPTY_COMPLETION_PROVIDER));
   764                                     EMPTY_COMPLETION_PROVIDER));
       
   765         registerCommand(new Command("/reload", "[restore] [quiet]", "reset and replay relevant history -- current or previous (restore)",
       
   766                                     arg -> cmdReload(arg),
       
   767                                     reloadCompletion()));
   719         registerCommand(new Command("/feedback", "<level>", "feedback information: off, concise, normal, verbose, default, or ?",
   768         registerCommand(new Command("/feedback", "<level>", "feedback information: off, concise, normal, verbose, default, or ?",
   720                                     arg -> cmdFeedback(arg),
   769                                     arg -> cmdFeedback(arg),
   721                                     new FixedCompletionProvider("off", "concise", "normal", "verbose", "default", "?")));
   770                                     new FixedCompletionProvider("off", "concise", "normal", "verbose", "default", "?")));
   722         registerCommand(new Command("/prompt", null, "toggle display of a prompt",
   771         registerCommand(new Command("/prompt", null, "toggle display of a prompt",
   723                                     arg -> cmdPrompt(),
   772                                     arg -> cmdPrompt(),
   724                                     EMPTY_COMPLETION_PROVIDER));
   773                                     EMPTY_COMPLETION_PROVIDER));
   725         registerCommand(new Command("/classpath", "<path>", "add a path to the classpath",
   774         registerCommand(new Command("/classpath", "<path>", "add a path to the classpath",
   726                                     arg -> cmdClasspath(arg),
   775                                     arg -> cmdClasspath(arg),
   727                                     classPathCompletion()));
   776                                     classPathCompletion(),
       
   777                                     CommandKind.REPLAY));
   728         registerCommand(new Command("/history", null, "history of what you have typed",
   778         registerCommand(new Command("/history", null, "history of what you have typed",
   729                                     arg -> cmdHistory(),
   779                                     arg -> cmdHistory(),
   730                                     EMPTY_COMPLETION_PROVIDER));
   780                                     EMPTY_COMPLETION_PROVIDER));
   731         registerCommand(new Command("/setstart", "<file>", "read file and set as the new start-up definitions",
   781         registerCommand(new Command("/setstart", "<file>", "read file and set as the new start-up definitions",
   732                                     arg -> cmdSetStart(arg),
   782                                     arg -> cmdSetStart(arg),
   799         return null;
   849         return null;
   800     }
   850     }
   801 
   851 
   802     // --- Command implementations ---
   852     // --- Command implementations ---
   803 
   853 
   804     void cmdSetEditor(String arg) {
   854     boolean cmdSetEditor(String arg) {
   805         if (arg.isEmpty()) {
   855         if (arg.isEmpty()) {
   806             hard("/seteditor requires a path argument");
   856             hard("/seteditor requires a path argument");
       
   857             return false;
   807         } else {
   858         } else {
   808             editor = arg;
   859             editor = arg;
   809             fluff("Editor set to: %s", arg);
   860             fluff("Editor set to: %s", arg);
   810         }
   861             return true;
   811     }
   862         }
   812 
   863     }
   813     void cmdClasspath(String arg) {
   864 
       
   865     boolean cmdClasspath(String arg) {
   814         if (arg.isEmpty()) {
   866         if (arg.isEmpty()) {
   815             hard("/classpath requires a path argument");
   867             hard("/classpath requires a path argument");
       
   868             return false;
   816         } else {
   869         } else {
   817             state.addToClasspath(toPathResolvingUserHome(arg).toString());
   870             state.addToClasspath(toPathResolvingUserHome(arg).toString());
   818             fluff("Path %s added to classpath", arg);
   871             fluff("Path %s added to classpath", arg);
   819         }
   872             return true;
   820     }
   873         }
   821 
   874     }
   822     void cmdDebug(String arg) {
   875 
       
   876     boolean cmdDebug(String arg) {
   823         if (arg.isEmpty()) {
   877         if (arg.isEmpty()) {
   824             debug = !debug;
   878             debug = !debug;
   825             InternalDebugControl.setDebugFlags(state, debug ? InternalDebugControl.DBG_GEN : 0);
   879             InternalDebugControl.setDebugFlags(state, debug ? InternalDebugControl.DBG_GEN : 0);
   826             fluff("Debugging %s", debug ? "on" : "off");
   880             fluff("Debugging %s", debug ? "on" : "off");
   827         } else {
   881         } else {
   858                         fluff("Event debugging on");
   912                         fluff("Event debugging on");
   859                         break;
   913                         break;
   860                     default:
   914                     default:
   861                         hard("Unknown debugging option: %c", ch);
   915                         hard("Unknown debugging option: %c", ch);
   862                         fluff("Use: 0 r g f c d");
   916                         fluff("Use: 0 r g f c d");
   863                         break;
   917                         return false;
   864                 }
   918                 }
   865             }
   919             }
   866             InternalDebugControl.setDebugFlags(state, flags);
   920             InternalDebugControl.setDebugFlags(state, flags);
   867         }
   921         }
   868     }
   922         return true;
   869 
   923     }
   870     private void cmdExit() {
   924 
       
   925     private boolean cmdExit() {
   871         regenerateOnDeath = false;
   926         regenerateOnDeath = false;
   872         live = false;
   927         live = false;
       
   928         if (!replayableHistory.isEmpty()) {
       
   929             PREFS.put(REPLAY_RESTORE_KEY, replayableHistory.stream().reduce(
       
   930                     (a, b) -> a + RECORD_SEPARATOR + b).get());
       
   931         }
   873         fluff("Goodbye\n");
   932         fluff("Goodbye\n");
   874     }
   933         return true;
   875 
   934     }
   876     private void cmdFeedback(String arg) {
   935 
       
   936     private boolean cmdFeedback(String arg) {
   877         switch (arg) {
   937         switch (arg) {
   878             case "":
   938             case "":
   879             case "d":
   939             case "d":
   880             case "default":
   940             case "default":
   881                 feedback = Feedback.Default;
   941                 feedback = Feedback.Default;
   903                 hard("  normal");
   963                 hard("  normal");
   904                 hard("  verbose");
   964                 hard("  verbose");
   905                 hard("  default");
   965                 hard("  default");
   906                 hard("You may also use just the first letter, for example: /f c");
   966                 hard("You may also use just the first letter, for example: /f c");
   907                 hard("In interactive mode 'default' is the same as 'normal', from a file it is the same as 'off'");
   967                 hard("In interactive mode 'default' is the same as 'normal', from a file it is the same as 'off'");
   908                 return;
   968                 return false;
   909         }
   969         }
   910         fluff("Feedback mode: %s", feedback.name().toLowerCase());
   970         fluff("Feedback mode: %s", feedback.name().toLowerCase());
   911     }
   971         return true;
   912 
   972     }
   913     void cmdHelp() {
   973 
       
   974     boolean cmdHelp() {
   914         int synopsisLen = 0;
   975         int synopsisLen = 0;
   915         Map<String, String> synopsis2Description = new LinkedHashMap<>();
   976         Map<String, String> synopsis2Description = new LinkedHashMap<>();
   916         for (Command cmd : new LinkedHashSet<>(commands.values())) {
   977         for (Command cmd : new LinkedHashSet<>(commands.values())) {
   917             if (cmd.kind == CommandKind.HIDDEN)
   978             if (cmd.kind == CommandKind.HIDDEN)
   918                 continue;
   979                 continue;
   934         }
   995         }
   935         cmdout.println();
   996         cmdout.println();
   936         cmdout.println("Supported shortcuts include:");
   997         cmdout.println("Supported shortcuts include:");
   937         cmdout.println("<tab>       -- show possible completions for the current text");
   998         cmdout.println("<tab>       -- show possible completions for the current text");
   938         cmdout.println("Shift-<tab> -- for current method or constructor invocation, show a synopsis of the method/constructor");
   999         cmdout.println("Shift-<tab> -- for current method or constructor invocation, show a synopsis of the method/constructor");
   939     }
  1000         return true;
   940 
  1001     }
   941     private void cmdHistory() {
  1002 
       
  1003     private boolean cmdHistory() {
   942         cmdout.println();
  1004         cmdout.println();
   943         for (String s : input.currentSessionHistory()) {
  1005         for (String s : input.currentSessionHistory()) {
   944             // No number prefix, confusing with snippet ids
  1006             // No number prefix, confusing with snippet ids
   945             cmdout.printf("%s\n", s);
  1007             cmdout.printf("%s\n", s);
   946         }
  1008         }
       
  1009         return true;
   947     }
  1010     }
   948 
  1011 
   949     /**
  1012     /**
   950      * Avoid parameterized varargs possible heap pollution warning.
  1013      * Avoid parameterized varargs possible heap pollution warning.
   951      */
  1014      */
  1008                     );
  1071                     );
  1009             return result;
  1072             return result;
  1010         }
  1073         }
  1011     }
  1074     }
  1012 
  1075 
  1013     private void cmdDrop(String arg) {
  1076     private boolean cmdDrop(String arg) {
  1014         if (arg.isEmpty()) {
  1077         if (arg.isEmpty()) {
  1015             hard("In the /drop argument, please specify an import, variable, method, or class to drop.");
  1078             hard("In the /drop argument, please specify an import, variable, method, or class to drop.");
  1016             hard("Specify by id or name. Use /list to see ids. Use /reset to reset all state.");
  1079             hard("Specify by id or name. Use /list to see ids. Use /reset to reset all state.");
  1017             return;
  1080             return false;
  1018         }
  1081         }
  1019         Stream<Snippet> stream = argToSnippets(arg, false);
  1082         Stream<Snippet> stream = argToSnippets(arg, false);
  1020         if (stream == null) {
  1083         if (stream == null) {
  1021             hard("No definition or id named %s found.  See /classes, /methods, /vars, or /list", arg);
  1084             hard("No definition or id named %s found.  See /classes, /methods, /vars, or /list", arg);
  1022             return;
  1085             return false;
  1023         }
  1086         }
  1024         List<Snippet> snippets = stream
  1087         List<Snippet> snippets = stream
  1025                 .filter(sn -> state.status(sn).isActive && sn instanceof PersistentSnippet)
  1088                 .filter(sn -> state.status(sn).isActive && sn instanceof PersistentSnippet)
  1026                 .collect(toList());
  1089                 .collect(toList());
  1027         if (snippets.isEmpty()) {
  1090         if (snippets.isEmpty()) {
  1028             hard("The argument did not specify an active import, variable, method, or class to drop.");
  1091             hard("The argument did not specify an active import, variable, method, or class to drop.");
  1029             return;
  1092             return false;
  1030         }
  1093         }
  1031         if (snippets.size() > 1) {
  1094         if (snippets.size() > 1) {
  1032             hard("The argument references more than one import, variable, method, or class.");
  1095             hard("The argument references more than one import, variable, method, or class.");
  1033             hard("Try again with one of the ids below:");
  1096             hard("Try again with one of the ids below:");
  1034             for (Snippet sn : snippets) {
  1097             for (Snippet sn : snippets) {
  1035                 cmdout.printf("%4s : %s\n", sn.id(), sn.source().replace("\n", "\n       "));
  1098                 cmdout.printf("%4s : %s\n", sn.id(), sn.source().replace("\n", "\n       "));
  1036             }
  1099             }
  1037             return;
  1100             return false;
  1038         }
  1101         }
  1039         PersistentSnippet psn = (PersistentSnippet) snippets.get(0);
  1102         PersistentSnippet psn = (PersistentSnippet) snippets.get(0);
  1040         state.drop(psn).forEach(this::handleEvent);
  1103         state.drop(psn).forEach(this::handleEvent);
  1041     }
  1104         return true;
  1042 
  1105     }
  1043     private void cmdEdit(String arg) {
  1106 
       
  1107     private boolean cmdEdit(String arg) {
  1044         Stream<Snippet> stream = argToSnippets(arg, true);
  1108         Stream<Snippet> stream = argToSnippets(arg, true);
  1045         if (stream == null) {
  1109         if (stream == null) {
  1046             hard("No definition or id named %s found.  See /classes, /methods, /vars, or /list", arg);
  1110             hard("No definition or id named %s found.  See /classes, /methods, /vars, or /list", arg);
  1047             return;
  1111             return false;
  1048         }
  1112         }
  1049         Set<String> srcSet = new LinkedHashSet<>();
  1113         Set<String> srcSet = new LinkedHashSet<>();
  1050         stream.forEachOrdered(sn -> {
  1114         stream.forEachOrdered(sn -> {
  1051             String src = sn.source();
  1115             String src = sn.source();
  1052             switch (sn.subKind()) {
  1116             switch (sn.subKind()) {
  1076         if (editor == null) {
  1140         if (editor == null) {
  1077             EditPad.edit(errorHandler, src, saveHandler);
  1141             EditPad.edit(errorHandler, src, saveHandler);
  1078         } else {
  1142         } else {
  1079             ExternalEditor.edit(editor, errorHandler, src, saveHandler, input);
  1143             ExternalEditor.edit(editor, errorHandler, src, saveHandler, input);
  1080         }
  1144         }
       
  1145         return true;
  1081     }
  1146     }
  1082     //where
  1147     //where
  1083     // receives editor requests to save
  1148     // receives editor requests to save
  1084     private class SaveHandler implements Consumer<String> {
  1149     private class SaveHandler implements Consumer<String> {
  1085 
  1150 
  1133             }
  1198             }
  1134             return s.substring(b, e + 1);
  1199             return s.substring(b, e + 1);
  1135         }
  1200         }
  1136     }
  1201     }
  1137 
  1202 
  1138     private void cmdList(String arg) {
  1203     private boolean cmdList(String arg) {
  1139         if (arg.equals("history")) {
  1204         if (arg.equals("history")) {
  1140             cmdHistory();
  1205             return cmdHistory();
  1141             return;
       
  1142         }
  1206         }
  1143         Stream<Snippet> stream = argToSnippets(arg, true);
  1207         Stream<Snippet> stream = argToSnippets(arg, true);
  1144         if (stream == null) {
  1208         if (stream == null) {
  1145             // Check if there are any definitions at all
  1209             // Check if there are any definitions at all
  1146             if (argToSnippets("", false).iterator().hasNext()) {
  1210             if (argToSnippets("", false).iterator().hasNext()) {
  1147                 hard("No definition or id named %s found.  Try /list without arguments.", arg);
  1211                 hard("No definition or id named %s found.  Try /list without arguments.", arg);
  1148             } else {
  1212             } else {
  1149                 hard("No definition or id named %s found.  There are no active definitions.", arg);
  1213                 hard("No definition or id named %s found.  There are no active definitions.", arg);
  1150             }
  1214             }
  1151             return;
  1215             return false;
  1152         }
  1216         }
  1153 
  1217 
  1154         // prevent double newline on empty list
  1218         // prevent double newline on empty list
  1155         boolean[] hasOutput = new boolean[1];
  1219         boolean[] hasOutput = new boolean[1];
  1156         stream.forEachOrdered(sn -> {
  1220         stream.forEachOrdered(sn -> {
  1158                 cmdout.println();
  1222                 cmdout.println();
  1159                 hasOutput[0] = true;
  1223                 hasOutput[0] = true;
  1160             }
  1224             }
  1161             cmdout.printf("%4s : %s\n", sn.id(), sn.source().replace("\n", "\n       "));
  1225             cmdout.printf("%4s : %s\n", sn.id(), sn.source().replace("\n", "\n       "));
  1162         });
  1226         });
  1163     }
  1227         return true;
  1164 
  1228     }
  1165     private void cmdOpen(String filename) {
  1229 
       
  1230     private boolean cmdOpen(String filename) {
  1166         if (filename.isEmpty()) {
  1231         if (filename.isEmpty()) {
  1167             hard("The /open command requires a filename argument.");
  1232             hard("The /open command requires a filename argument.");
       
  1233             return false;
  1168         } else {
  1234         } else {
  1169             try {
  1235             try {
  1170                 run(new FileScannerIOContext(toPathResolvingUserHome(filename).toString()));
  1236                 run(new FileScannerIOContext(toPathResolvingUserHome(filename).toString()));
  1171             } catch (FileNotFoundException e) {
  1237             } catch (FileNotFoundException e) {
  1172                 hard("File '%s' is not found: %s", filename, e.getMessage());
  1238                 hard("File '%s' is not found: %s", filename, e.getMessage());
       
  1239                 return false;
  1173             } catch (Exception e) {
  1240             } catch (Exception e) {
  1174                 hard("Exception while reading file: %s", e);
  1241                 hard("Exception while reading file: %s", e);
  1175             }
  1242                 return false;
  1176         }
  1243             }
  1177     }
  1244         }
  1178 
  1245         return true;
  1179     private void cmdPrompt() {
  1246     }
       
  1247 
       
  1248     private boolean cmdPrompt() {
  1180         displayPrompt = !displayPrompt;
  1249         displayPrompt = !displayPrompt;
  1181         fluff("Prompt will %sdisplay. Use /prompt to toggle.", displayPrompt ? "" : "NOT ");
  1250         fluff("Prompt will %sdisplay. Use /prompt to toggle.", displayPrompt ? "" : "NOT ");
  1182         concise("Prompt: %s", displayPrompt ? "on" : "off");
  1251         concise("Prompt: %s", displayPrompt ? "on" : "off");
  1183     }
  1252         return true;
  1184 
  1253     }
  1185     private void cmdReset() {
  1254 
       
  1255     private boolean cmdReset() {
  1186         live = false;
  1256         live = false;
  1187         fluff("Resetting state.");
  1257         fluff("Resetting state.");
  1188     }
  1258         return true;
  1189 
  1259     }
  1190     private void cmdSave(String arg_filename) {
  1260 
       
  1261     private boolean cmdReload(String arg) {
       
  1262         Iterable<String> history = replayableHistory;
       
  1263         boolean echo = true;
       
  1264         if (arg.length() > 0) {
       
  1265             if ("restore".startsWith(arg)) {
       
  1266                 if (replayableHistoryPrevious == null) {
       
  1267                     hard("No previous history to restore\n", arg);
       
  1268                     return false;
       
  1269                 }
       
  1270                 history = replayableHistoryPrevious;
       
  1271             } else if ("quiet".startsWith(arg)) {
       
  1272                 echo = false;
       
  1273             } else {
       
  1274                 hard("Invalid argument to reload command: %s\nUse 'restore', 'quiet', or no argument\n", arg);
       
  1275                 return false;
       
  1276             }
       
  1277         }
       
  1278         fluff("Restarting and restoring %s.",
       
  1279                 history == replayableHistoryPrevious
       
  1280                         ? "from previous state"
       
  1281                         : "state");
       
  1282         resetState();
       
  1283         run(new ReloadIOContext(history,
       
  1284                 echo? cmdout : null));
       
  1285         return true;
       
  1286     }
       
  1287 
       
  1288     private boolean cmdSave(String arg_filename) {
  1191         Matcher mat = HISTORY_ALL_START_FILENAME.matcher(arg_filename);
  1289         Matcher mat = HISTORY_ALL_START_FILENAME.matcher(arg_filename);
  1192         if (!mat.find()) {
  1290         if (!mat.find()) {
  1193             hard("Malformed argument to the /save command: %s", arg_filename);
  1291             hard("Malformed argument to the /save command: %s", arg_filename);
  1194             return;
  1292             return false;
  1195         }
  1293         }
  1196         boolean useHistory = false;
  1294         boolean useHistory = false;
  1197         String saveAll = "";
  1295         String saveAll = "";
  1198         boolean saveStart = false;
  1296         boolean saveStart = false;
  1199         String cmd = mat.group("cmd");
  1297         String cmd = mat.group("cmd");
  1209                 break;
  1307                 break;
  1210         }
  1308         }
  1211         String filename = mat.group("filename");
  1309         String filename = mat.group("filename");
  1212         if (filename == null ||filename.isEmpty()) {
  1310         if (filename == null ||filename.isEmpty()) {
  1213             hard("The /save command requires a filename argument.");
  1311             hard("The /save command requires a filename argument.");
  1214             return;
  1312             return false;
  1215         }
  1313         }
  1216         try (BufferedWriter writer = Files.newBufferedWriter(toPathResolvingUserHome(filename),
  1314         try (BufferedWriter writer = Files.newBufferedWriter(toPathResolvingUserHome(filename),
  1217                 Charset.defaultCharset(),
  1315                 Charset.defaultCharset(),
  1218                 CREATE, TRUNCATE_EXISTING, WRITE)) {
  1316                 CREATE, TRUNCATE_EXISTING, WRITE)) {
  1219             if (useHistory) {
  1317             if (useHistory) {
  1232                     }
  1330                     }
  1233                 }
  1331                 }
  1234             }
  1332             }
  1235         } catch (FileNotFoundException e) {
  1333         } catch (FileNotFoundException e) {
  1236             hard("File '%s' for save is not accessible: %s", filename, e.getMessage());
  1334             hard("File '%s' for save is not accessible: %s", filename, e.getMessage());
       
  1335             return false;
  1237         } catch (Exception e) {
  1336         } catch (Exception e) {
  1238             hard("Exception while saving: %s", e);
  1337             hard("Exception while saving: %s", e);
  1239         }
  1338             return false;
  1240     }
  1339         }
  1241 
  1340         return true;
  1242     private void cmdSetStart(String filename) {
  1341     }
       
  1342 
       
  1343     private boolean cmdSetStart(String filename) {
  1243         if (filename.isEmpty()) {
  1344         if (filename.isEmpty()) {
  1244             hard("The /setstart command requires a filename argument.");
  1345             hard("The /setstart command requires a filename argument.");
  1245         } else {
  1346         } else {
  1246             try {
  1347             try {
  1247                 byte[] encoded = Files.readAllBytes(toPathResolvingUserHome(filename));
  1348                 byte[] encoded = Files.readAllBytes(toPathResolvingUserHome(filename));
  1248                 String init = new String(encoded);
  1349                 String init = new String(encoded);
  1249                 PREFS.put(STARTUP_KEY, init);
  1350                 PREFS.put(STARTUP_KEY, init);
  1250             } catch (AccessDeniedException e) {
  1351             } catch (AccessDeniedException e) {
  1251                 hard("File '%s' for /setstart is not accessible.", filename);
  1352                 hard("File '%s' for /setstart is not accessible.", filename);
       
  1353                 return false;
  1252             } catch (NoSuchFileException e) {
  1354             } catch (NoSuchFileException e) {
  1253                 hard("File '%s' for /setstart is not found.", filename);
  1355                 hard("File '%s' for /setstart is not found.", filename);
       
  1356                 return false;
  1254             } catch (Exception e) {
  1357             } catch (Exception e) {
  1255                 hard("Exception while reading start set file: %s", e);
  1358                 hard("Exception while reading start set file: %s", e);
  1256             }
  1359                 return false;
  1257         }
  1360             }
  1258     }
  1361         }
  1259 
  1362         return true;
  1260     private void cmdVars() {
  1363     }
       
  1364 
       
  1365     private boolean cmdVars() {
  1261         for (VarSnippet vk : state.variables()) {
  1366         for (VarSnippet vk : state.variables()) {
  1262             String val = state.status(vk) == Status.VALID
  1367             String val = state.status(vk) == Status.VALID
  1263                     ? state.varValue(vk)
  1368                     ? state.varValue(vk)
  1264                     : "(not-active)";
  1369                     : "(not-active)";
  1265             hard("  %s %s = %s", vk.typeName(), vk.name(), val);
  1370             hard("  %s %s = %s", vk.typeName(), vk.name(), val);
  1266         }
  1371         }
  1267     }
  1372         return true;
  1268 
  1373     }
  1269     private void cmdMethods() {
  1374 
       
  1375     private boolean cmdMethods() {
  1270         for (MethodSnippet mk : state.methods()) {
  1376         for (MethodSnippet mk : state.methods()) {
  1271             hard("  %s %s", mk.name(), mk.signature());
  1377             hard("  %s %s", mk.name(), mk.signature());
  1272         }
  1378         }
  1273     }
  1379         return true;
  1274 
  1380     }
  1275     private void cmdClasses() {
  1381 
       
  1382     private boolean cmdClasses() {
  1276         for (TypeDeclSnippet ck : state.types()) {
  1383         for (TypeDeclSnippet ck : state.types()) {
  1277             String kind;
  1384             String kind;
  1278             switch (ck.subKind()) {
  1385             switch (ck.subKind()) {
  1279                 case INTERFACE_SUBKIND:
  1386                 case INTERFACE_SUBKIND:
  1280                     kind = "interface";
  1387                     kind = "interface";
  1293                     kind = "class";
  1400                     kind = "class";
  1294                     break;
  1401                     break;
  1295             }
  1402             }
  1296             hard("  %s %s", kind, ck.name());
  1403             hard("  %s %s", kind, ck.name());
  1297         }
  1404         }
  1298     }
  1405         return true;
  1299 
  1406     }
  1300     private void cmdImports() {
  1407 
       
  1408     private boolean cmdImports() {
  1301         state.imports().forEach(ik -> {
  1409         state.imports().forEach(ik -> {
  1302             hard("  import %s%s", ik.isStatic() ? "static " : "", ik.fullname());
  1410             hard("  import %s%s", ik.isStatic() ? "static " : "", ik.fullname());
  1303         });
  1411         });
  1304     }
  1412         return true;
  1305 
  1413     }
  1306     private void cmdUseHistoryEntry(int index) {
  1414 
       
  1415     private boolean cmdUseHistoryEntry(int index) {
  1307         List<Snippet> keys = state.snippets();
  1416         List<Snippet> keys = state.snippets();
  1308         if (index < 0)
  1417         if (index < 0)
  1309             index += keys.size();
  1418             index += keys.size();
  1310         else
  1419         else
  1311             index--;
  1420             index--;
  1312         if (index >= 0 && index < keys.size()) {
  1421         if (index >= 0 && index < keys.size()) {
  1313             rerunSnippet(keys.get(index));
  1422             rerunSnippet(keys.get(index));
  1314         } else {
  1423         } else {
  1315             hard("Cannot find snippet %d", index + 1);
  1424             hard("Cannot find snippet %d", index + 1);
  1316         }
  1425             return false;
       
  1426         }
       
  1427         return true;
  1317     }
  1428     }
  1318 
  1429 
  1319     private boolean rerunHistoryEntryById(String id) {
  1430     private boolean rerunHistoryEntryById(String id) {
  1320         Optional<Snippet> snippet = state.snippets().stream()
  1431         Optional<Snippet> snippet = state.snippets().stream()
  1321             .filter(s -> s.id().equals(id))
  1432             .filter(s -> s.id().equals(id))
  1423     }
  1534     }
  1424     //where
  1535     //where
  1425     private boolean processCompleteSource(String source) throws IllegalStateException {
  1536     private boolean processCompleteSource(String source) throws IllegalStateException {
  1426         debug("Compiling: %s", source);
  1537         debug("Compiling: %s", source);
  1427         boolean failed = false;
  1538         boolean failed = false;
       
  1539         boolean isActive = false;
  1428         List<SnippetEvent> events = state.eval(source);
  1540         List<SnippetEvent> events = state.eval(source);
  1429         for (SnippetEvent e : events) {
  1541         for (SnippetEvent e : events) {
       
  1542             // Report the event, recording failure
  1430             failed |= handleEvent(e);
  1543             failed |= handleEvent(e);
  1431         }
  1544 
       
  1545             // If any main snippet is active, this should be replayable
       
  1546             // also ignore var value queries
       
  1547             isActive |= e.causeSnippet() == null &&
       
  1548                     e.status().isActive &&
       
  1549                     e.snippet().subKind() != VAR_VALUE_SUBKIND;
       
  1550         }
       
  1551         // If this is an active snippet and it didn't cause the backend to die,
       
  1552         // add it to the replayable history
       
  1553         if (isActive && live) {
       
  1554             addToReplayHistory(source);
       
  1555         }
       
  1556 
  1432         return failed;
  1557         return failed;
  1433     }
  1558     }
  1434 
  1559 
  1435     private boolean handleEvent(SnippetEvent ste) {
  1560     private boolean handleEvent(SnippetEvent ste) {
  1436         Snippet sn = ste.snippet();
  1561         Snippet sn = ste.snippet();
  1782             this.tid = tid;
  1907             this.tid = tid;
  1783         }
  1908         }
  1784     }
  1909     }
  1785 }
  1910 }
  1786 
  1911 
  1787 class ScannerIOContext extends IOContext {
  1912 abstract class NonInteractiveIOContext extends IOContext {
  1788 
  1913 
       
  1914     @Override
       
  1915     public boolean interactiveOutput() {
       
  1916         return false;
       
  1917     }
       
  1918 
       
  1919     @Override
       
  1920     public Iterable<String> currentSessionHistory() {
       
  1921         return Collections.emptyList();
       
  1922     }
       
  1923 
       
  1924     @Override
       
  1925     public boolean terminalEditorRunning() {
       
  1926         return false;
       
  1927     }
       
  1928 
       
  1929     @Override
       
  1930     public void suspend() {
       
  1931     }
       
  1932 
       
  1933     @Override
       
  1934     public void resume() {
       
  1935     }
       
  1936 
       
  1937     @Override
       
  1938     public void beforeUserCode() {
       
  1939     }
       
  1940 
       
  1941     @Override
       
  1942     public void afterUserCode() {
       
  1943     }
       
  1944 
       
  1945     @Override
       
  1946     public void replaceLastHistoryEntry(String source) {
       
  1947     }
       
  1948 }
       
  1949 
       
  1950 class ScannerIOContext extends NonInteractiveIOContext {
  1789     private final Scanner scannerIn;
  1951     private final Scanner scannerIn;
  1790     private final PrintStream pStream;
  1952 
  1791 
  1953     ScannerIOContext(Scanner scannerIn) {
  1792     public ScannerIOContext(Scanner scannerIn, PrintStream pStream) {
       
  1793         this.scannerIn = scannerIn;
  1954         this.scannerIn = scannerIn;
  1794         this.pStream = pStream;
       
  1795     }
  1955     }
  1796 
  1956 
  1797     @Override
  1957     @Override
  1798     public String readLine(String prompt, String prefix) {
  1958     public String readLine(String prompt, String prefix) {
  1799         if (pStream != null && prompt != null) {
       
  1800             pStream.print(prompt);
       
  1801         }
       
  1802         if (scannerIn.hasNextLine()) {
  1959         if (scannerIn.hasNextLine()) {
  1803             return scannerIn.nextLine();
  1960             return scannerIn.nextLine();
  1804         } else {
  1961         } else {
  1805             return null;
  1962             return null;
  1806         }
  1963         }
  1807     }
  1964     }
  1808 
  1965 
  1809     @Override
  1966     @Override
  1810     public boolean interactiveOutput() {
  1967     public void close() {
  1811         return true;
  1968         scannerIn.close();
       
  1969     }
       
  1970 }
       
  1971 
       
  1972 class FileScannerIOContext extends ScannerIOContext {
       
  1973 
       
  1974     FileScannerIOContext(String fn) throws FileNotFoundException {
       
  1975         this(new FileReader(fn));
       
  1976     }
       
  1977 
       
  1978     FileScannerIOContext(Reader rdr) throws FileNotFoundException {
       
  1979         super(new Scanner(rdr));
       
  1980     }
       
  1981 }
       
  1982 
       
  1983 class ReloadIOContext extends NonInteractiveIOContext {
       
  1984     private final Iterator<String> it;
       
  1985     private final PrintStream echoStream;
       
  1986 
       
  1987     ReloadIOContext(Iterable<String> history, PrintStream echoStream) {
       
  1988         this.it = history.iterator();
       
  1989         this.echoStream = echoStream;
  1812     }
  1990     }
  1813 
  1991 
  1814     @Override
  1992     @Override
  1815     public Iterable<String> currentSessionHistory() {
  1993     public String readLine(String prompt, String prefix) {
  1816         return Collections.emptyList();
  1994         String s = it.hasNext()
       
  1995                 ? it.next()
       
  1996                 : null;
       
  1997         if (echoStream != null && s != null) {
       
  1998             String p = "-: ";
       
  1999             String p2 = "\n   ";
       
  2000             echoStream.printf("%s%s\n", p, s.replace("\n", p2));
       
  2001         }
       
  2002         return s;
  1817     }
  2003     }
  1818 
  2004 
  1819     @Override
  2005     @Override
  1820     public void close() {
  2006     public void close() {
  1821         scannerIn.close();
       
  1822     }
       
  1823 
       
  1824     @Override
       
  1825     public boolean terminalEditorRunning() {
       
  1826         return false;
       
  1827     }
       
  1828 
       
  1829     @Override
       
  1830     public void suspend() {
       
  1831     }
       
  1832 
       
  1833     @Override
       
  1834     public void resume() {
       
  1835     }
       
  1836 
       
  1837     @Override
       
  1838     public void beforeUserCode() {
       
  1839     }
       
  1840 
       
  1841     @Override
       
  1842     public void afterUserCode() {
       
  1843     }
       
  1844 
       
  1845     @Override
       
  1846     public void replaceLastHistoryEntry(String source) {
       
  1847     }
  2007     }
  1848 }
  2008 }
  1849 
       
  1850 class FileScannerIOContext extends ScannerIOContext {
       
  1851 
       
  1852     public FileScannerIOContext(String fn) throws FileNotFoundException {
       
  1853         this(new FileReader(fn));
       
  1854     }
       
  1855 
       
  1856     public FileScannerIOContext(Reader rdr) throws FileNotFoundException {
       
  1857         super(new Scanner(rdr), null);
       
  1858     }
       
  1859 
       
  1860     @Override
       
  1861     public boolean interactiveOutput() {
       
  1862         return false;
       
  1863     }
       
  1864 }
       
  1865