langtools/src/jdk.jshell/share/classes/jdk/internal/jshell/tool/JShellTool.java
changeset 35812 6a6ca0bd3c14
parent 35000 952a7b4652f0
child 36160 f42d362d0d17
equal deleted inserted replaced
35811:3779fa4164c2 35812:6a6ca0bd3c14
    33 import java.io.FileReader;
    33 import java.io.FileReader;
    34 import java.io.IOException;
    34 import java.io.IOException;
    35 import java.io.InputStream;
    35 import java.io.InputStream;
    36 import java.io.PrintStream;
    36 import java.io.PrintStream;
    37 import java.io.Reader;
    37 import java.io.Reader;
       
    38 import java.io.StreamTokenizer;
    38 import java.io.StringReader;
    39 import java.io.StringReader;
    39 import java.nio.charset.Charset;
    40 import java.nio.charset.Charset;
    40 import java.nio.file.AccessDeniedException;
    41 import java.nio.file.AccessDeniedException;
    41 import java.nio.file.FileSystems;
    42 import java.nio.file.FileSystems;
    42 import java.nio.file.Files;
    43 import java.nio.file.Files;
   150     private boolean displayPrompt = true;
   151     private boolean displayPrompt = true;
   151     public boolean testPrompt = false;
   152     public boolean testPrompt = false;
   152     private Feedback feedback = Feedback.Default;
   153     private Feedback feedback = Feedback.Default;
   153     private String cmdlineClasspath = null;
   154     private String cmdlineClasspath = null;
   154     private String cmdlineStartup = null;
   155     private String cmdlineStartup = null;
   155     private String editor = null;
   156     private String[] editor = null;
   156 
   157 
   157     // Commands and snippets which should be replayed
   158     // Commands and snippets which should be replayed
   158     private List<String> replayableHistory;
   159     private List<String> replayableHistory;
   159     private List<String> replayableHistoryPrevious;
   160     private List<String> replayableHistoryPrevious;
   160 
   161 
   535         int idx = cmd.indexOf(' ');
   536         int idx = cmd.indexOf(' ');
   536         if (idx > 0) {
   537         if (idx > 0) {
   537             arg = cmd.substring(idx + 1).trim();
   538             arg = cmd.substring(idx + 1).trim();
   538             cmd = cmd.substring(0, idx);
   539             cmd = cmd.substring(0, idx);
   539         }
   540         }
   540         Command[] candidates = findCommand(cmd, c -> c.kind != CommandKind.HELP_ONLY);
   541         Command[] candidates = findCommand(cmd, c -> c.kind.isRealCommand);
   541         if (candidates.length == 0) {
   542         if (candidates.length == 0) {
   542             if (!rerunHistoryEntryById(cmd.substring(1))) {
   543             if (!rerunHistoryEntryById(cmd.substring(1))) {
   543                 hard("No such command or snippet id: %s", cmd);
   544                 hard("No such command or snippet id: %s", cmd);
   544                 fluff("Type /help for help.");
   545                 fluff("Type /help for help.");
   545             }
   546             }
   577 
   578 
   578     static final class Command {
   579     static final class Command {
   579         public final String command;
   580         public final String command;
   580         public final String params;
   581         public final String params;
   581         public final String description;
   582         public final String description;
       
   583         public final String help;
   582         public final Function<String,Boolean> run;
   584         public final Function<String,Boolean> run;
   583         public final CompletionProvider completions;
   585         public final CompletionProvider completions;
   584         public final CommandKind kind;
   586         public final CommandKind kind;
   585 
   587 
   586         public Command(String command, String params, String description, Function<String,Boolean> run, CompletionProvider completions) {
   588         // NORMAL Commands
   587             this(command, params, description, run, completions, CommandKind.NORMAL);
   589         public Command(String command, String params, String description, String help,
   588         }
   590                 Function<String,Boolean> run, CompletionProvider completions) {
   589 
   591             this(command, params, description, help,
   590         public Command(String command, String params, String description, Function<String,Boolean> run, CompletionProvider completions, CommandKind kind) {
   592                     run, completions, CommandKind.NORMAL);
       
   593         }
       
   594 
       
   595         // Documentation pseudo-commands
       
   596         public Command(String command, String description, String help,
       
   597                 CommandKind kind) {
       
   598             this(command, null, description, help,
       
   599                     arg -> { throw new IllegalStateException(); },
       
   600                     EMPTY_COMPLETION_PROVIDER,
       
   601                     kind);
       
   602         }
       
   603 
       
   604         public Command(String command, String params, String description, String help,
       
   605                 Function<String,Boolean> run, CompletionProvider completions, CommandKind kind) {
   591             this.command = command;
   606             this.command = command;
   592             this.params = params;
   607             this.params = params;
   593             this.description = description;
   608             this.description = description;
       
   609             this.help = help;
   594             this.run = run;
   610             this.run = run;
   595             this.completions = completions;
   611             this.completions = completions;
   596             this.kind = kind;
   612             this.kind = kind;
   597         }
   613         }
   598 
   614 
   601     interface CompletionProvider {
   617     interface CompletionProvider {
   602         List<Suggestion> completionSuggestions(String input, int cursor, int[] anchor);
   618         List<Suggestion> completionSuggestions(String input, int cursor, int[] anchor);
   603     }
   619     }
   604 
   620 
   605     enum CommandKind {
   621     enum CommandKind {
   606         NORMAL,
   622         NORMAL(true, true, true),
   607         REPLAY,
   623         REPLAY(true, true, true),
   608         HIDDEN,
   624         HIDDEN(true, false, false),
   609         HELP_ONLY;
   625         HELP_ONLY(false, true, false),
       
   626         HELP_SUBJECT(false, false, false);
       
   627 
       
   628         final boolean isRealCommand;
       
   629         final boolean showInHelp;
       
   630         final boolean shouldSuggestCompletions;
       
   631         private CommandKind(boolean isRealCommand, boolean showInHelp, boolean shouldSuggestCompletions) {
       
   632             this.isRealCommand = isRealCommand;
       
   633             this.showInHelp = showInHelp;
       
   634             this.shouldSuggestCompletions = shouldSuggestCompletions;
       
   635         }
       
   636     }
       
   637 
       
   638     class ArgTokenizer extends StreamTokenizer {
       
   639 
       
   640         ArgTokenizer(String arg) {
       
   641             super(new StringReader(arg));
       
   642             resetSyntax();
       
   643             wordChars(0x00, 0xFF);
       
   644             quoteChar('"');
       
   645             quoteChar('\'');
       
   646 
       
   647             whitespaceChars(0x09, 0x0D);
       
   648             whitespaceChars(0x1C, 0x20);
       
   649             whitespaceChars(0x85, 0x85);
       
   650             whitespaceChars(0xA0, 0xA0);
       
   651             whitespaceChars(0x1680, 0x1680);
       
   652             whitespaceChars(0x180E, 0x180E);
       
   653             whitespaceChars(0x2000, 0x200A);
       
   654             whitespaceChars(0x202F, 0x202F);
       
   655             whitespaceChars(0x205F, 0x205F);
       
   656             whitespaceChars(0x3000, 0x3000);
       
   657         }
       
   658 
       
   659         String next() {
       
   660             try {
       
   661                 nextToken();
       
   662             } catch (Throwable t) {
       
   663                 return null;
       
   664             }
       
   665             return sval;
       
   666         }
       
   667 
       
   668         String val() {
       
   669             return sval;
       
   670         }
       
   671 
       
   672         boolean isQuoted() {
       
   673             return ttype == '\'' || ttype == '"';
       
   674         }
   610     }
   675     }
   611 
   676 
   612     static final class FixedCompletionProvider implements CompletionProvider {
   677     static final class FixedCompletionProvider implements CompletionProvider {
   613 
   678 
   614         private final String[] alternatives;
   679         private final String[] alternatives;
   720     }
   785     }
   721 
   786 
   722     // Table of commands -- with command forms, argument kinds, help message, implementation, ...
   787     // Table of commands -- with command forms, argument kinds, help message, implementation, ...
   723 
   788 
   724     {
   789     {
   725         registerCommand(new Command("/list", "[all|start|history|<name or id>]", "list the source you have typed",
   790         registerCommand(new Command("/list", "[all|start|<name or id>]", "list the source you have typed",
   726                                     arg -> cmdList(arg),
   791                 "Show the source of snippets, prefaced with the snippet id.\n\n" +
   727                                     editKeywordCompletion()));
   792                 "/list\n" +
   728         registerCommand(new Command("/seteditor", "<executable>", "set the external editor command to use",
   793                 "  -- List the currently active snippets of code that you typed or read with /open\n" +
   729                                     arg -> cmdSetEditor(arg),
   794                 "/list start\n" +
   730                                     EMPTY_COMPLETION_PROVIDER));
   795                 "  -- List the automatically evaluated start-up snippets\n" +
       
   796                 "/list all\n" +
       
   797                 "  -- List all snippets including failed, overwritten, dropped, and start-up\n" +
       
   798                 "/list <name>\n" +
       
   799                 "  -- List snippets with the specified name (preference for active snippets)\n" +
       
   800                 "/list <id>\n" +
       
   801                 "  -- List the snippet with the specified snippet id\n",
       
   802                 arg -> cmdList(arg),
       
   803                 editKeywordCompletion()));
       
   804         registerCommand(new Command("/seteditor", "<command>", "set the external editor command to use",
       
   805                 "Specify the command to launch for the /edit command.\n" +
       
   806                 "The command is an operating system dependent string.\n" +
       
   807                 "The command may include space-separated arguments (such as flags).\n" +
       
   808                 "When /edit is used, temporary file to edit will be appended as the last argument.\n",
       
   809                 arg -> cmdSetEditor(arg),
       
   810                 EMPTY_COMPLETION_PROVIDER));
   731         registerCommand(new Command("/edit", "<name or id>", "edit a source entry referenced by name or id",
   811         registerCommand(new Command("/edit", "<name or id>", "edit a source entry referenced by name or id",
   732                                     arg -> cmdEdit(arg),
   812                 "Edit a snippet or snippets of source in an external editor.\n" +
   733                                     editCompletion()));
   813                 "The editor to use is set with /seteditor.\n" +
       
   814                 "If no editor has been set, a simple editor will be launched.\n\n" +
       
   815                 "/edit <name>\n" +
       
   816                 "  -- Edit the snippet or snippets with the specified name (preference for active snippets)\n" +
       
   817                 "/edit <id>\n" +
       
   818                 "  -- Edit the snippet with the specified snippet id\n" +
       
   819                 "/edit\n" +
       
   820                 "  -- Edit the currently active snippets of code that you typed or read with /open\n",
       
   821                 arg -> cmdEdit(arg),
       
   822                 editCompletion()));
   734         registerCommand(new Command("/drop", "<name or id>", "delete a source entry referenced by name or id",
   823         registerCommand(new Command("/drop", "<name or id>", "delete a source entry referenced by name or id",
   735                                     arg -> cmdDrop(arg),
   824                 "Drop a snippet -- making it inactive.\n\n" +
   736                                     editCompletion(),
   825                 "/drop <name>\n" +
   737                                     CommandKind.REPLAY));
   826                 "  -- Drop the snippet with the specified name\n" +
   738         registerCommand(new Command("/save", "[all|history|start] <file>", "save: <none> - current source;\n" +
   827                 "/drop <id>\n" +
   739                                                                            "      all - source including overwritten, failed, and start-up code;\n" +
   828                 "  -- Drop the snippet with the specified snippet id\n",
   740                                                                            "      history - editing history;\n" +
   829                 arg -> cmdDrop(arg),
   741                                                                            "      start - default start-up definitions",
   830                 editCompletion(),
   742                                     arg -> cmdSave(arg),
   831                 CommandKind.REPLAY));
   743                                     saveCompletion()));
   832         registerCommand(new Command("/save", "[all|history|start] <file>", "Save snippet source to a file.",
       
   833                 "Save the specified snippets and/or commands to the specified file.\n\n" +
       
   834                 "/save <file>\n" +
       
   835                 "  -- Save the source of current active snippets to the file\n" +
       
   836                 "/save all <file>\n" +
       
   837                 "  -- Save the source of all snippets to the file\n" +
       
   838                 "     Includes source including overwritten, failed, and start-up code\n" +
       
   839                 "/save history <file>\n" +
       
   840                 "  -- Save the sequential history of all commands and snippets entered since jshell was launched\n" +
       
   841                 "/save start <file>\n" +
       
   842                 "  -- Save the default start-up definitions to the file\n",
       
   843                 arg -> cmdSave(arg),
       
   844                 saveCompletion()));
   744         registerCommand(new Command("/open", "<file>", "open a file as source input",
   845         registerCommand(new Command("/open", "<file>", "open a file as source input",
   745                                     arg -> cmdOpen(arg),
   846                 "Open a file and read its contents as snippets and commands.\n\n" +
   746                                     FILE_COMPLETION_PROVIDER));
   847                 "/open <file>\n" +
       
   848                 "  -- Read the specified file as jshell input.\n",
       
   849                 arg -> cmdOpen(arg),
       
   850                 FILE_COMPLETION_PROVIDER));
   747         registerCommand(new Command("/vars", null, "list the declared variables and their values",
   851         registerCommand(new Command("/vars", null, "list the declared variables and their values",
   748                                     arg -> cmdVars(),
   852                 "List the type, name, and value of the current active jshell variables.\n",
   749                                     EMPTY_COMPLETION_PROVIDER));
   853                 arg -> cmdVars(),
       
   854                 EMPTY_COMPLETION_PROVIDER));
   750         registerCommand(new Command("/methods", null, "list the declared methods and their signatures",
   855         registerCommand(new Command("/methods", null, "list the declared methods and their signatures",
   751                                     arg -> cmdMethods(),
   856                 "List the name, parameter types, and return type of the current active jshell methods.\n",
   752                                     EMPTY_COMPLETION_PROVIDER));
   857                 arg -> cmdMethods(),
       
   858                 EMPTY_COMPLETION_PROVIDER));
   753         registerCommand(new Command("/classes", null, "list the declared classes",
   859         registerCommand(new Command("/classes", null, "list the declared classes",
   754                                     arg -> cmdClasses(),
   860                 "List the current active jshell classes, interfaces, and enums.\n",
   755                                     EMPTY_COMPLETION_PROVIDER));
   861                 arg -> cmdClasses(),
       
   862                 EMPTY_COMPLETION_PROVIDER));
   756         registerCommand(new Command("/imports", null, "list the imported items",
   863         registerCommand(new Command("/imports", null, "list the imported items",
   757                                     arg -> cmdImports(),
   864                 "List the current active jshell imports.\n",
   758                                     EMPTY_COMPLETION_PROVIDER));
   865                 arg -> cmdImports(),
   759         registerCommand(new Command("/exit", null, "exit the REPL",
   866                 EMPTY_COMPLETION_PROVIDER));
   760                                     arg -> cmdExit(),
   867         registerCommand(new Command("/exit", null, "exit jshell",
   761                                     EMPTY_COMPLETION_PROVIDER));
   868                 "Leave the jshell tool.  No work is saved.\n" +
   762         registerCommand(new Command("/reset", null, "reset everything in the REPL",
   869                 "Save any work before using this command\n",
   763                                     arg -> cmdReset(),
   870                 arg -> cmdExit(),
   764                                     EMPTY_COMPLETION_PROVIDER));
   871                 EMPTY_COMPLETION_PROVIDER));
       
   872         registerCommand(new Command("/reset", null, "reset jshell",
       
   873                 "Reset the jshell tool code and execution state:\n" +
       
   874                 "   * All entered code is lost.\n" +
       
   875                 "   * Start-up code is re-executed.\n" +
       
   876                 "   * The execution state is restarted.\n" +
       
   877                 "   * The classpath is cleared.\n" +
       
   878                 "Tool settings are maintained: /feedback, /prompt, and /seteditor\n" +
       
   879                 "Save any work before using this command\n",
       
   880                 arg -> cmdReset(),
       
   881                 EMPTY_COMPLETION_PROVIDER));
   765         registerCommand(new Command("/reload", "[restore] [quiet]", "reset and replay relevant history -- current or previous (restore)",
   882         registerCommand(new Command("/reload", "[restore] [quiet]", "reset and replay relevant history -- current or previous (restore)",
   766                                     arg -> cmdReload(arg),
   883                 "Reset the jshell tool code and execution state then replay each\n" +
   767                                     reloadCompletion()));
   884                 "jshell valid command and valid snippet in the order they were entered.\n\n" +
       
   885                 "/reload\n" +
       
   886                 "  -- Reset and replay the valid history since jshell was entered, or\n" +
       
   887                 "     a /reset, or /reload command was executed -- whichever is most\n" +
       
   888                 "     recent.\n" +
       
   889                 "/reload restore\n" +
       
   890                 "  -- Reset and replay the valid history between the previous and most\n" +
       
   891                 "     recent time that jshell was entered, or a /reset, or /reload\n" +
       
   892                 "     command was executed. This can thus be used to restore a previous\n" +
       
   893                 "     jshell tool sesson.\n" +
       
   894                 "/reload [restore] quiet\n" +
       
   895                 "  -- With the 'quiet' argument the replay is not shown.  Errors will display.\n",
       
   896                 arg -> cmdReload(arg),
       
   897                 reloadCompletion()));
   768         registerCommand(new Command("/feedback", "<level>", "feedback information: off, concise, normal, verbose, default, or ?",
   898         registerCommand(new Command("/feedback", "<level>", "feedback information: off, concise, normal, verbose, default, or ?",
   769                                     arg -> cmdFeedback(arg),
   899                 "Set the level of feedback describing the effect of commands and snippets.\n\n" +
   770                                     new FixedCompletionProvider("off", "concise", "normal", "verbose", "default", "?")));
   900                 "/feedback off\n" +
       
   901                 "  -- Give no feedback\n" +
       
   902                 "/feedback concise\n" +
       
   903                 "  -- Brief and generally symbolic feedback\n" +
       
   904                 "/feedback normal\n" +
       
   905                 "  -- Give a natural language description of the actions\n" +
       
   906                 "/feedback verbose\n" +
       
   907                 "  -- Like normal but with side-effects described\n" +
       
   908                 "/feedback default\n" +
       
   909                 "  -- Same as normal for user input, off for input from a file\n",
       
   910                 arg -> cmdFeedback(arg),
       
   911                 new FixedCompletionProvider("off", "concise", "normal", "verbose", "default", "?")));
   771         registerCommand(new Command("/prompt", null, "toggle display of a prompt",
   912         registerCommand(new Command("/prompt", null, "toggle display of a prompt",
   772                                     arg -> cmdPrompt(),
   913                 "Toggle between displaying an input prompt and not displaying a prompt.\n" +
   773                                     EMPTY_COMPLETION_PROVIDER));
   914                 "Particularly useful when pasting large amounts of text.\n",
       
   915                 arg -> cmdPrompt(),
       
   916                 EMPTY_COMPLETION_PROVIDER));
   774         registerCommand(new Command("/classpath", "<path>", "add a path to the classpath",
   917         registerCommand(new Command("/classpath", "<path>", "add a path to the classpath",
   775                                     arg -> cmdClasspath(arg),
   918                 "Append a additional path to the classpath.\n",
   776                                     classPathCompletion(),
   919                 arg -> cmdClasspath(arg),
   777                                     CommandKind.REPLAY));
   920                 classPathCompletion(),
       
   921                 CommandKind.REPLAY));
   778         registerCommand(new Command("/history", null, "history of what you have typed",
   922         registerCommand(new Command("/history", null, "history of what you have typed",
   779                                     arg -> cmdHistory(),
   923                 "Display the history of snippet and command input since this jshell was launched.\n",
   780                                     EMPTY_COMPLETION_PROVIDER));
   924                 arg -> cmdHistory(),
       
   925                 EMPTY_COMPLETION_PROVIDER));
   781         registerCommand(new Command("/setstart", "<file>", "read file and set as the new start-up definitions",
   926         registerCommand(new Command("/setstart", "<file>", "read file and set as the new start-up definitions",
   782                                     arg -> cmdSetStart(arg),
   927                 "The contents of the specified file become the default start-up snippets and commands.\n",
   783                                     FILE_COMPLETION_PROVIDER));
   928                 arg -> cmdSetStart(arg),
   784         registerCommand(new Command("/debug", "", "toggle debugging of the REPL",
   929                 FILE_COMPLETION_PROVIDER));
   785                                     arg -> cmdDebug(arg),
   930         registerCommand(new Command("/debug", null, "toggle debugging of the jshell",
   786                                     EMPTY_COMPLETION_PROVIDER,
   931                 "Display debugging information for the jshelll implementation.\n" +
   787                                     CommandKind.HIDDEN));
   932                 "0: Debugging off\n" +
   788         registerCommand(new Command("/help", "", "this help message",
   933                 "r: Debugging on\n" +
   789                                     arg -> cmdHelp(),
   934                 "g: General debugging on\n" +
   790                                     EMPTY_COMPLETION_PROVIDER));
   935                 "f: File manager debugging on\n" +
   791         registerCommand(new Command("/?", "", "this help message",
   936                 "c': Completion analysis debugging on\n" +
   792                                     arg -> cmdHelp(),
   937                 "d': Dependency debugging on\n" +
   793                                     EMPTY_COMPLETION_PROVIDER));
   938                 "e': Event debugging on\n",
       
   939                 arg -> cmdDebug(arg),
       
   940                 EMPTY_COMPLETION_PROVIDER,
       
   941                 CommandKind.HIDDEN));
       
   942         registerCommand(new Command("/help", "[<command>|<subject>]", "get information about jshell",
       
   943                 "Display information about jshell.\n" +
       
   944                 "/help\n" +
       
   945                 "  -- List the jshell commands and help subjects.\n" +
       
   946                 "/help <command>\n" +
       
   947                 "  -- Display information about the specified comand. The slash must be included.\n" +
       
   948                 "     Only the first few letters of the command are needed -- if more than one\n" +
       
   949                 "     each will be displayed.  Example:  /help /li\n" +
       
   950                 "/help <subject>\n" +
       
   951                 "  -- Display information about the specified help subject. Example: /help intro\n",
       
   952                 arg -> cmdHelp(arg),
       
   953                 EMPTY_COMPLETION_PROVIDER));
       
   954         registerCommand(new Command("/?", "", "get information about jshell",
       
   955                 "Display information about jshell (abbreviation for /help).\n" +
       
   956                 "/?\n" +
       
   957                 "  -- Display list of commands and help subjects.\n" +
       
   958                 "/? <command>\n" +
       
   959                 "  -- Display information about the specified comand. The slash must be included.\n" +
       
   960                 "     Only the first few letters of the command are needed -- if more than one\n" +
       
   961                 "     match, each will be displayed.  Example:  /? /li\n" +
       
   962                 "/? <subject>\n" +
       
   963                 "  -- Display information about the specified help subject. Example: /? intro\n",
       
   964                 arg -> cmdHelp(arg),
       
   965                 EMPTY_COMPLETION_PROVIDER));
   794         registerCommand(new Command("/!", "", "re-run last snippet",
   966         registerCommand(new Command("/!", "", "re-run last snippet",
   795                                     arg -> cmdUseHistoryEntry(-1),
   967                 "Reevaluate the most recently entered snippet.\n",
   796                                     EMPTY_COMPLETION_PROVIDER));
   968                 arg -> cmdUseHistoryEntry(-1),
   797         registerCommand(new Command("/<id>", "", "re-run snippet by id",
   969                 EMPTY_COMPLETION_PROVIDER));
   798                                     arg -> { throw new IllegalStateException(); },
   970 
   799                                     EMPTY_COMPLETION_PROVIDER,
   971         // Documentation pseudo-commands
   800                                     CommandKind.HELP_ONLY));
   972 
   801         registerCommand(new Command("/-<n>", "", "re-run n-th previous snippet",
   973         registerCommand(new Command("/<id>", "re-run snippet by id",
   802                                     arg -> { throw new IllegalStateException(); },
   974                 "",
   803                                     EMPTY_COMPLETION_PROVIDER,
   975                 CommandKind.HELP_ONLY));
   804                                     CommandKind.HELP_ONLY));
   976         registerCommand(new Command("/-<n>", "re-run n-th previous snippet",
       
   977                 "",
       
   978                 CommandKind.HELP_ONLY));
       
   979         registerCommand(new Command("intro", "An introduction to the jshell tool",
       
   980                 "The jshell tool allows you to execute Java code, getting immediate results.\n" +
       
   981                 "You can enter a Java definition (variable, method, class, etc), like:  int x = 8\n" +
       
   982                 "or a Java expression, like:  x + x\n" +
       
   983                 "or a Java statement or import.\n" +
       
   984                 "These little chunks of Java code are called 'snippets'.\n\n" +
       
   985                 "There are also jshell commands that allow you to understand and\n" +
       
   986                 "control what you are doing, like:  /list\n\n" +
       
   987                 "For a list of commands: /help",
       
   988                 CommandKind.HELP_SUBJECT));
       
   989         registerCommand(new Command("shortcuts", "Describe shortcuts",
       
   990                 "Supported shortcuts include:\n\n" +
       
   991                 "<tab>       -- After entering the first few letters of a Java identifier,\n" +
       
   992                 "               a jshell command, or, in some cases, a jshell command argument,\n" +
       
   993                 "               press the <tab> key to complete the input.\n" +
       
   994                 "               If there is more than one completion, show possible completions.\n" +
       
   995                 "Shift-<tab> -- After the name and open parenthesis of a method or constructor invocation,\n" +
       
   996                 "               hold the <shift> key and press the <tab> to see a synopsis of all\n" +
       
   997                 "               matching methods/constructors.\n",
       
   998                 CommandKind.HELP_SUBJECT));
   805     }
   999     }
   806 
  1000 
   807     public List<Suggestion> commandCompletionSuggestions(String code, int cursor, int[] anchor) {
  1001     public List<Suggestion> commandCompletionSuggestions(String code, int cursor, int[] anchor) {
   808         String prefix = code.substring(0, cursor);
  1002         String prefix = code.substring(0, cursor);
   809         int space = prefix.indexOf(' ');
  1003         int space = prefix.indexOf(' ');
   811 
  1005 
   812         if (space == (-1)) {
  1006         if (space == (-1)) {
   813             result = commands.values()
  1007             result = commands.values()
   814                              .stream()
  1008                              .stream()
   815                              .distinct()
  1009                              .distinct()
   816                              .filter(cmd -> cmd.kind != CommandKind.HIDDEN && cmd.kind != CommandKind.HELP_ONLY)
  1010                              .filter(cmd -> cmd.kind.shouldSuggestCompletions)
   817                              .map(cmd -> cmd.command)
  1011                              .map(cmd -> cmd.command)
   818                              .filter(key -> key.startsWith(prefix))
  1012                              .filter(key -> key.startsWith(prefix))
   819                              .map(key -> new Suggestion(key + " ", false));
  1013                              .map(key -> new Suggestion(key + " ", false));
   820             anchor[0] = 0;
  1014             anchor[0] = 0;
   821         } else {
  1015         } else {
   854     boolean cmdSetEditor(String arg) {
  1048     boolean cmdSetEditor(String arg) {
   855         if (arg.isEmpty()) {
  1049         if (arg.isEmpty()) {
   856             hard("/seteditor requires a path argument");
  1050             hard("/seteditor requires a path argument");
   857             return false;
  1051             return false;
   858         } else {
  1052         } else {
   859             editor = arg;
  1053             List<String> ed = new ArrayList<>();
       
  1054             ArgTokenizer at = new ArgTokenizer(arg);
       
  1055             String n;
       
  1056             while ((n = at.next()) != null) ed.add(n);
       
  1057             editor = ed.toArray(new String[ed.size()]);
   860             fluff("Editor set to: %s", arg);
  1058             fluff("Editor set to: %s", arg);
   861             return true;
  1059             return true;
   862         }
  1060         }
   863     }
  1061     }
   864 
  1062 
   969         }
  1167         }
   970         fluff("Feedback mode: %s", feedback.name().toLowerCase());
  1168         fluff("Feedback mode: %s", feedback.name().toLowerCase());
   971         return true;
  1169         return true;
   972     }
  1170     }
   973 
  1171 
   974     boolean cmdHelp() {
  1172     boolean cmdHelp(String arg) {
       
  1173         if (!arg.isEmpty()) {
       
  1174             StringBuilder sb = new StringBuilder();
       
  1175             commands.values().stream()
       
  1176                     .filter(c -> c.command.startsWith(arg))
       
  1177                     .forEach(c -> {
       
  1178                         sb.append("\n");
       
  1179                         sb.append(c.command);
       
  1180                         sb.append("\n\n");
       
  1181                         sb.append(c.help);
       
  1182                         sb.append("\n");
       
  1183                     });
       
  1184             if (sb.length() > 0) {
       
  1185                 cmdout.print(sb);
       
  1186                 return true;
       
  1187             }
       
  1188             cmdout.printf("No commands or subjects start with the provided argument: %s\n\n", arg);
       
  1189         }
   975         int synopsisLen = 0;
  1190         int synopsisLen = 0;
   976         Map<String, String> synopsis2Description = new LinkedHashMap<>();
  1191         Map<String, String> synopsis2Description = new LinkedHashMap<>();
   977         for (Command cmd : new LinkedHashSet<>(commands.values())) {
  1192         for (Command cmd : new LinkedHashSet<>(commands.values())) {
   978             if (cmd.kind == CommandKind.HIDDEN)
  1193             if (!cmd.kind.showInHelp)
   979                 continue;
  1194                 continue;
   980             StringBuilder synopsis = new StringBuilder();
  1195             StringBuilder synopsis = new StringBuilder();
   981             synopsis.append(cmd.command);
  1196             synopsis.append(cmd.command);
   982             if (cmd.params != null)
  1197             if (cmd.params != null)
   983                 synopsis.append(" ").append(cmd.params);
  1198                 synopsis.append(" ").append(cmd.params);
   992             String indentedNewLine = System.getProperty("line.separator") +
  1207             String indentedNewLine = System.getProperty("line.separator") +
   993                                      String.format("%-" + (synopsisLen + 4) + "s", "");
  1208                                      String.format("%-" + (synopsisLen + 4) + "s", "");
   994             cmdout.println(e.getValue().replace("\n", indentedNewLine));
  1209             cmdout.println(e.getValue().replace("\n", indentedNewLine));
   995         }
  1210         }
   996         cmdout.println();
  1211         cmdout.println();
   997         cmdout.println("Supported shortcuts include:");
  1212         cmdout.println("For more information type '/help' followed by the name of command or a subject.");
   998         cmdout.println("<tab>       -- show possible completions for the current text");
  1213         cmdout.println("For example '/help /list' or '/help intro'.  Subjects:\n");
   999         cmdout.println("Shift-<tab> -- for current method or constructor invocation, show a synopsis of the method/constructor");
  1214         commands.values().stream()
       
  1215                 .filter(c -> c.kind == CommandKind.HELP_SUBJECT)
       
  1216                 .forEach(c -> {
       
  1217             cmdout.printf("%-12s -- %s\n", c.command, c.description);
       
  1218         });
  1000         return true;
  1219         return true;
  1001     }
  1220     }
  1002 
  1221 
  1003     private boolean cmdHistory() {
  1222     private boolean cmdHistory() {
  1004         cmdout.println();
  1223         cmdout.println();
  1031             }
  1250             }
  1032         }
  1251         }
  1033         return null;
  1252         return null;
  1034     }
  1253     }
  1035 
  1254 
       
  1255     private boolean inStartUp(Snippet sn) {
       
  1256         return mapSnippet.get(sn).space == startNamespace;
       
  1257     }
       
  1258 
       
  1259     private boolean isActive(Snippet sn) {
       
  1260         return state.status(sn).isActive;
       
  1261     }
       
  1262 
  1036     private boolean mainActive(Snippet sn) {
  1263     private boolean mainActive(Snippet sn) {
  1037         return notInStartUp(sn) && state.status(sn).isActive;
  1264         return !inStartUp(sn) && isActive(sn);
  1038     }
  1265     }
  1039 
  1266 
  1040     private boolean matchingDeclaration(Snippet sn, String name) {
  1267     private boolean matchingDeclaration(Snippet sn, String name) {
  1041         return sn instanceof DeclarationSnippet
  1268         return sn instanceof DeclarationSnippet
  1042                 && ((DeclarationSnippet) sn).name().equals(name);
  1269                 && ((DeclarationSnippet) sn).name().equals(name);
  1052     private Stream<Snippet> argToSnippets(String arg, boolean allowAll) {
  1279     private Stream<Snippet> argToSnippets(String arg, boolean allowAll) {
  1053         List<Snippet> snippets = state.snippets();
  1280         List<Snippet> snippets = state.snippets();
  1054         if (allowAll && arg.equals("all")) {
  1281         if (allowAll && arg.equals("all")) {
  1055             // all snippets including start-up, failed, and overwritten
  1282             // all snippets including start-up, failed, and overwritten
  1056             return snippets.stream();
  1283             return snippets.stream();
       
  1284         } else if (allowAll && arg.equals("start")) {
       
  1285             // start-up snippets
       
  1286             return snippets.stream()
       
  1287                     .filter(this::inStartUp);
  1057         } else if (arg.isEmpty()) {
  1288         } else if (arg.isEmpty()) {
  1058             // Default is all active user snippets
  1289             // Default is all active user snippets
  1059             return snippets.stream()
  1290             return snippets.stream()
  1060                     .filter(this::mainActive);
  1291                     .filter(this::mainActive);
  1061         } else {
  1292         } else {
  1062             Stream<Snippet> result =
  1293             Stream<Snippet> result =
  1063                     nonEmptyStream(
  1294                     nonEmptyStream(
  1064                             () -> snippets.stream(),
  1295                             () -> snippets.stream(),
  1065                             // look for active user declarations matching the name
  1296                             // look for active user declarations matching the name
  1066                             sn -> mainActive(sn) && matchingDeclaration(sn, arg),
  1297                             sn -> isActive(sn) && matchingDeclaration(sn, arg),
  1067                             // else, look for any declarations matching the name
  1298                             // else, look for any declarations matching the name
  1068                             sn -> matchingDeclaration(sn, arg),
  1299                             sn -> matchingDeclaration(sn, arg),
  1069                             // else, look for an id of this name
  1300                             // else, look for an id of this name
  1070                             sn -> sn.id().equals(arg)
  1301                             sn -> sn.id().equals(arg)
  1071                     );
  1302                     );
  1837             return input == null || input.interactiveOutput() ? Feedback.Normal : Feedback.Off;
  2068             return input == null || input.interactiveOutput() ? Feedback.Normal : Feedback.Off;
  1838         }
  2069         }
  1839         return feedback;
  2070         return feedback;
  1840     }
  2071     }
  1841 
  2072 
  1842     boolean notInStartUp(Snippet sn) {
       
  1843         return mapSnippet.get(sn).space != startNamespace;
       
  1844     }
       
  1845 
       
  1846     /** The current version number as a string.
  2073     /** The current version number as a string.
  1847      */
  2074      */
  1848     static String version() {
  2075     static String version() {
  1849         return version("release");  // mm.nn.oo[-milestone]
  2076         return version("release");  // mm.nn.oo[-milestone]
  1850     }
  2077     }