langtools/src/jdk.jshell/share/classes/jdk/internal/jshell/tool/JShellTool.java
changeset 34570 8a8f52a733dd
parent 34477 64001b0533a2
child 34999 aacf94dab449
equal deleted inserted replaced
34569:8b372e28f106 34570:8a8f52a733dd
    84 import jdk.jshell.JShell.Subscription;
    84 import jdk.jshell.JShell.Subscription;
    85 
    85 
    86 import static java.nio.file.StandardOpenOption.CREATE;
    86 import static java.nio.file.StandardOpenOption.CREATE;
    87 import static java.nio.file.StandardOpenOption.TRUNCATE_EXISTING;
    87 import static java.nio.file.StandardOpenOption.TRUNCATE_EXISTING;
    88 import static java.nio.file.StandardOpenOption.WRITE;
    88 import static java.nio.file.StandardOpenOption.WRITE;
    89 import java.util.Locale;
       
    90 import java.util.MissingResourceException;
    89 import java.util.MissingResourceException;
    91 import java.util.Optional;
    90 import java.util.Optional;
    92 import java.util.ResourceBundle;
    91 import java.util.ResourceBundle;
       
    92 import java.util.Spliterators;
       
    93 import java.util.function.Supplier;
    93 import static java.util.stream.Collectors.toList;
    94 import static java.util.stream.Collectors.toList;
    94 
    95 
    95 /**
    96 /**
    96  * Command line REPL tool for Java using the JShell API.
    97  * Command line REPL tool for Java using the JShell API.
    97  * @author Robert Field
    98  * @author Robert Field
   598         }
   599         }
   599 
   600 
   600     }
   601     }
   601 
   602 
   602     private static final CompletionProvider EMPTY_COMPLETION_PROVIDER = new FixedCompletionProvider();
   603     private static final CompletionProvider EMPTY_COMPLETION_PROVIDER = new FixedCompletionProvider();
       
   604     private static final CompletionProvider KEYWORD_COMPLETION_PROVIDER = new FixedCompletionProvider("all ", "start ", "history ");
   603     private static final CompletionProvider FILE_COMPLETION_PROVIDER = fileCompletions(p -> true);
   605     private static final CompletionProvider FILE_COMPLETION_PROVIDER = fileCompletions(p -> true);
   604     private final Map<String, Command> commands = new LinkedHashMap<>();
   606     private final Map<String, Command> commands = new LinkedHashMap<>();
   605     private void registerCommand(Command cmd) {
   607     private void registerCommand(Command cmd) {
   606         commands.put(cmd.command, cmd);
   608         commands.put(cmd.command, cmd);
   607     }
   609     }
   648                         .map(k -> new Suggestion(k, false))
   650                         .map(k -> new Suggestion(k, false))
   649                         .collect(Collectors.toList());
   651                         .collect(Collectors.toList());
   650         };
   652         };
   651     }
   653     }
   652 
   654 
       
   655     private CompletionProvider editKeywordCompletion() {
       
   656         return (code, cursor, anchor) -> {
       
   657             List<Suggestion> result = new ArrayList<>();
       
   658             result.addAll(KEYWORD_COMPLETION_PROVIDER.completionSuggestions(code, cursor, anchor));
       
   659             result.addAll(editCompletion().completionSuggestions(code, cursor, anchor));
       
   660             return result;
       
   661         };
       
   662     }
       
   663 
   653     private static CompletionProvider saveCompletion() {
   664     private static CompletionProvider saveCompletion() {
   654         CompletionProvider keyCompletion = new FixedCompletionProvider("all ", "history ", "start ");
       
   655         return (code, cursor, anchor) -> {
   665         return (code, cursor, anchor) -> {
   656             List<Suggestion> result = new ArrayList<>();
   666             List<Suggestion> result = new ArrayList<>();
   657             int space = code.indexOf(' ');
   667             int space = code.indexOf(' ');
   658             if (space == (-1)) {
   668             if (space == (-1)) {
   659                 result.addAll(keyCompletion.completionSuggestions(code, cursor, anchor));
   669                 result.addAll(KEYWORD_COMPLETION_PROVIDER.completionSuggestions(code, cursor, anchor));
   660             }
   670             }
   661             result.addAll(FILE_COMPLETION_PROVIDER.completionSuggestions(code.substring(space + 1), cursor - space - 1, anchor));
   671             result.addAll(FILE_COMPLETION_PROVIDER.completionSuggestions(code.substring(space + 1), cursor - space - 1, anchor));
   662             anchor[0] += space + 1;
   672             anchor[0] += space + 1;
   663             return result;
   673             return result;
   664         };
   674         };
   665     }
   675     }
   666 
   676 
   667     // Table of commands -- with command forms, argument kinds, help message, implementation, ...
   677     // Table of commands -- with command forms, argument kinds, help message, implementation, ...
   668 
   678 
   669     {
   679     {
   670         registerCommand(new Command("/list", "[all]", "list the source you have typed",
   680         registerCommand(new Command("/list", "[all|start|history|<name or id>]", "list the source you have typed",
   671                                     arg -> cmdList(arg),
   681                                     arg -> cmdList(arg),
   672                                     new FixedCompletionProvider("all")));
   682                                     editKeywordCompletion()));
   673         registerCommand(new Command("/seteditor", "<executable>", "set the external editor command to use",
   683         registerCommand(new Command("/seteditor", "<executable>", "set the external editor command to use",
   674                                     arg -> cmdSetEditor(arg),
   684                                     arg -> cmdSetEditor(arg),
   675                                     EMPTY_COMPLETION_PROVIDER));
   685                                     EMPTY_COMPLETION_PROVIDER));
   676         registerCommand(new Command("/edit", "<name or id>", "edit a source entry referenced by name or id",
   686         registerCommand(new Command("/edit", "<name or id>", "edit a source entry referenced by name or id",
   677                                     arg -> cmdEdit(arg),
   687                                     arg -> cmdEdit(arg),
   935             cmdout.printf("%s\n", s);
   945             cmdout.printf("%s\n", s);
   936         }
   946         }
   937     }
   947     }
   938 
   948 
   939     /**
   949     /**
   940      * Convert a user argument to a list of snippets referenced by that
   950      * Avoid parameterized varargs possible heap pollution warning.
   941      * argument (or lack of argument).
       
   942      * @param arg The user's argument to the command
       
   943      * @return a list of referenced snippets
       
   944      */
   951      */
   945     private List<Snippet> argToSnippets(String arg) {
   952     private interface SnippetPredicate extends Predicate<Snippet> { }
   946         List<Snippet> snippets = new ArrayList<>();
   953 
   947         if (arg.isEmpty()) {
   954     /**
   948             // Default is all user snippets
   955      * Apply filters to a stream until one that is non-empty is found.
   949             for (Snippet sn : state.snippets()) {
   956      * Adapted from Stuart Marks
   950                 if (notInStartUp(sn)) {
   957      *
   951                     snippets.add(sn);
   958      * @param supplier Supply the Snippet stream to filter
   952                 }
   959      * @param filters Filters to attempt
   953             }
   960      * @return The non-empty filtered Stream, or null
       
   961      */
       
   962     private static Stream<Snippet> nonEmptyStream(Supplier<Stream<Snippet>> supplier,
       
   963             SnippetPredicate... filters) {
       
   964         for (SnippetPredicate filt : filters) {
       
   965             Iterator<Snippet> iterator = supplier.get().filter(filt).iterator();
       
   966             if (iterator.hasNext()) {
       
   967                 return StreamSupport.stream(Spliterators.spliteratorUnknownSize(iterator, 0), false);
       
   968             }
       
   969         }
       
   970         return null;
       
   971     }
       
   972 
       
   973     private boolean mainActive(Snippet sn) {
       
   974         return notInStartUp(sn) && state.status(sn).isActive;
       
   975     }
       
   976 
       
   977     private boolean matchingDeclaration(Snippet sn, String name) {
       
   978         return sn instanceof DeclarationSnippet
       
   979                 && ((DeclarationSnippet) sn).name().equals(name);
       
   980     }
       
   981 
       
   982     /**
       
   983      * Convert a user argument to a Stream of snippets referenced by that argument
       
   984      * (or lack of argument).
       
   985      *
       
   986      * @param arg The user's argument to the command, maybe be the empty string
       
   987      * @return a Stream of referenced snippets or null if no matches to specific arg
       
   988      */
       
   989     private Stream<Snippet> argToSnippets(String arg, boolean allowAll) {
       
   990         List<Snippet> snippets = state.snippets();
       
   991         if (allowAll && arg.equals("all")) {
       
   992             // all snippets including start-up, failed, and overwritten
       
   993             return snippets.stream();
       
   994         } else if (arg.isEmpty()) {
       
   995             // Default is all active user snippets
       
   996             return snippets.stream()
       
   997                     .filter(this::mainActive);
   954         } else {
   998         } else {
   955             // Look for all declarations with matching names
   999             Stream<Snippet> result =
   956             for (Snippet key : state.snippets()) {
  1000                     nonEmptyStream(
   957                 switch (key.kind()) {
  1001                             () -> snippets.stream(),
   958                     case METHOD:
  1002                             // look for active user declarations matching the name
   959                     case VAR:
  1003                             sn -> mainActive(sn) && matchingDeclaration(sn, arg),
   960                     case TYPE_DECL:
  1004                             // else, look for any declarations matching the name
   961                         if (((DeclarationSnippet) key).name().equals(arg)) {
  1005                             sn -> matchingDeclaration(sn, arg),
   962                             snippets.add(key);
  1006                             // else, look for an id of this name
   963                         }
  1007                             sn -> sn.id().equals(arg)
   964                         break;
  1008                     );
   965                 }
  1009             return result;
   966             }
  1010         }
   967             // If no declarations found, look for an id of this name
       
   968             if (snippets.isEmpty()) {
       
   969                 for (Snippet sn : state.snippets()) {
       
   970                     if (sn.id().equals(arg)) {
       
   971                         snippets.add(sn);
       
   972                         break;
       
   973                     }
       
   974                 }
       
   975             }
       
   976             // If still no matches found, give an error
       
   977             if (snippets.isEmpty()) {
       
   978                 hard("No definition or id named %s found.  See /classes /methods /vars or /list", arg);
       
   979                 return null;
       
   980             }
       
   981         }
       
   982         return snippets;
       
   983     }
  1011     }
   984 
  1012 
   985     private void cmdDrop(String arg) {
  1013     private void cmdDrop(String arg) {
   986         if (arg.isEmpty()) {
  1014         if (arg.isEmpty()) {
   987             hard("In the /drop argument, please specify an import, variable, method, or class to drop.");
  1015             hard("In the /drop argument, please specify an import, variable, method, or class to drop.");
   988             hard("Specify by id or name. Use /list to see ids. Use /reset to reset all state.");
  1016             hard("Specify by id or name. Use /list to see ids. Use /reset to reset all state.");
   989             return;
  1017             return;
   990         }
  1018         }
   991         List<Snippet> snippetSet = argToSnippets(arg);
  1019         Stream<Snippet> stream = argToSnippets(arg, false);
   992         if (snippetSet == null) {
  1020         if (stream == null) {
       
  1021             hard("No definition or id named %s found.  See /classes, /methods, /vars, or /list", arg);
   993             return;
  1022             return;
   994         }
  1023         }
   995         snippetSet = snippetSet.stream()
  1024         List<Snippet> snippets = stream
   996                 .filter(sn -> state.status(sn).isActive)
  1025                 .filter(sn -> state.status(sn).isActive && sn instanceof PersistentSnippet)
   997                 .collect(toList());
  1026                 .collect(toList());
   998         snippetSet.removeIf(sn -> !(sn instanceof PersistentSnippet));
  1027         if (snippets.isEmpty()) {
   999         if (snippetSet.isEmpty()) {
  1028             hard("The argument did not specify an active import, variable, method, or class to drop.");
  1000             hard("The argument did not specify an import, variable, method, or class to drop.");
       
  1001             return;
  1029             return;
  1002         }
  1030         }
  1003         if (snippetSet.size() > 1) {
  1031         if (snippets.size() > 1) {
  1004             hard("The argument references more than one import, variable, method, or class.");
  1032             hard("The argument references more than one import, variable, method, or class.");
  1005             hard("Try again with one of the ids below:");
  1033             hard("Try again with one of the ids below:");
  1006             for (Snippet sn : snippetSet) {
  1034             for (Snippet sn : snippets) {
  1007                 cmdout.printf("%4s : %s\n", sn.id(), sn.source().replace("\n", "\n       "));
  1035                 cmdout.printf("%4s : %s\n", sn.id(), sn.source().replace("\n", "\n       "));
  1008             }
  1036             }
  1009             return;
  1037             return;
  1010         }
  1038         }
  1011         PersistentSnippet psn = (PersistentSnippet) snippetSet.iterator().next();
  1039         PersistentSnippet psn = (PersistentSnippet) snippets.get(0);
  1012         state.drop(psn).forEach(this::handleEvent);
  1040         state.drop(psn).forEach(this::handleEvent);
  1013     }
  1041     }
  1014 
  1042 
  1015     private void cmdEdit(String arg) {
  1043     private void cmdEdit(String arg) {
  1016         List<Snippet> snippetSet = argToSnippets(arg);
  1044         Stream<Snippet> stream = argToSnippets(arg, true);
  1017         if (snippetSet == null) {
  1045         if (stream == null) {
       
  1046             hard("No definition or id named %s found.  See /classes, /methods, /vars, or /list", arg);
  1018             return;
  1047             return;
  1019         }
  1048         }
  1020         Set<String> srcSet = new LinkedHashSet<>();
  1049         Set<String> srcSet = new LinkedHashSet<>();
  1021         for (Snippet key : snippetSet) {
  1050         stream.forEachOrdered(sn -> {
  1022             String src = key.source();
  1051             String src = sn.source();
  1023             switch (key.subKind()) {
  1052             switch (sn.subKind()) {
  1024                 case VAR_VALUE_SUBKIND:
  1053                 case VAR_VALUE_SUBKIND:
  1025                     break;
  1054                     break;
  1026                 case ASSIGNMENT_SUBKIND:
  1055                 case ASSIGNMENT_SUBKIND:
  1027                 case OTHER_EXPRESSION_SUBKIND:
  1056                 case OTHER_EXPRESSION_SUBKIND:
  1028                 case TEMP_VAR_EXPRESSION_SUBKIND:
  1057                 case TEMP_VAR_EXPRESSION_SUBKIND:
  1033                     break;
  1062                     break;
  1034                 default:
  1063                 default:
  1035                     srcSet.add(src);
  1064                     srcSet.add(src);
  1036                     break;
  1065                     break;
  1037             }
  1066             }
  1038         }
  1067         });
  1039         StringBuilder sb = new StringBuilder();
  1068         StringBuilder sb = new StringBuilder();
  1040         for (String s : srcSet) {
  1069         for (String s : srcSet) {
  1041             sb.append(s);
  1070             sb.append(s);
  1042             sb.append('\n');
  1071             sb.append('\n');
  1043         }
  1072         }
  1105             return s.substring(b, e + 1);
  1134             return s.substring(b, e + 1);
  1106         }
  1135         }
  1107     }
  1136     }
  1108 
  1137 
  1109     private void cmdList(String arg) {
  1138     private void cmdList(String arg) {
  1110         boolean all = false;
  1139         if (arg.equals("history")) {
  1111         switch (arg) {
  1140             cmdHistory();
  1112             case "all":
  1141             return;
  1113                 all = true;
  1142         }
  1114                 break;
  1143         Stream<Snippet> stream = argToSnippets(arg, true);
  1115             case "history":
  1144         if (stream == null) {
  1116                 cmdHistory();
  1145             // Check if there are any definitions at all
  1117                 return;
  1146             if (argToSnippets("", false).iterator().hasNext()) {
  1118             case "":
  1147                 hard("No definition or id named %s found.  Try /list without arguments.", arg);
  1119                 break;
  1148             } else {
  1120             default:
  1149                 hard("No definition or id named %s found.  There are no active definitions.", arg);
  1121                 hard("Invalid /list argument: %s", arg);
  1150             }
  1122                 return;
  1151             return;
  1123         }
  1152         }
  1124         boolean hasOutput = false;
  1153 
  1125         for (Snippet sn : state.snippets()) {
  1154         // prevent double newline on empty list
  1126             if (all || (notInStartUp(sn) && state.status(sn).isActive)) {
  1155         boolean[] hasOutput = new boolean[1];
  1127                 if (!hasOutput) {
  1156         stream.forEachOrdered(sn -> {
  1128                     cmdout.println();
  1157             if (!hasOutput[0]) {
  1129                     hasOutput = true;
  1158                 cmdout.println();
  1130                 }
  1159                 hasOutput[0] = true;
  1131                 cmdout.printf("%4s : %s\n", sn.id(), sn.source().replace("\n", "\n       "));
  1160             }
  1132 
  1161             cmdout.printf("%4s : %s\n", sn.id(), sn.source().replace("\n", "\n       "));
  1133             }
  1162         });
  1134         }
       
  1135     }
  1163     }
  1136 
  1164 
  1137     private void cmdOpen(String filename) {
  1165     private void cmdOpen(String filename) {
  1138         if (filename.isEmpty()) {
  1166         if (filename.isEmpty()) {
  1139             hard("The /open command requires a filename argument.");
  1167             hard("The /open command requires a filename argument.");
  1164         if (!mat.find()) {
  1192         if (!mat.find()) {
  1165             hard("Malformed argument to the /save command: %s", arg_filename);
  1193             hard("Malformed argument to the /save command: %s", arg_filename);
  1166             return;
  1194             return;
  1167         }
  1195         }
  1168         boolean useHistory = false;
  1196         boolean useHistory = false;
  1169         boolean saveAll = false;
  1197         String saveAll = "";
  1170         boolean saveStart = false;
  1198         boolean saveStart = false;
  1171         String cmd = mat.group("cmd");
  1199         String cmd = mat.group("cmd");
  1172         if (cmd != null) switch (cmd) {
  1200         if (cmd != null) switch (cmd) {
  1173             case "all":
  1201             case "all":
  1174                 saveAll = true;
  1202                 saveAll = "all";
  1175                 break;
  1203                 break;
  1176             case "history":
  1204             case "history":
  1177                 useHistory = true;
  1205                 useHistory = true;
  1178                 break;
  1206                 break;
  1179             case "start":
  1207             case "start":
  1194                     writer.write("\n");
  1222                     writer.write("\n");
  1195                 }
  1223                 }
  1196             } else if (saveStart) {
  1224             } else if (saveStart) {
  1197                 writer.append(DEFAULT_STARTUP);
  1225                 writer.append(DEFAULT_STARTUP);
  1198             } else {
  1226             } else {
  1199                 for (Snippet sn : state.snippets()) {
  1227                 Stream<Snippet> stream = argToSnippets(saveAll, true);
  1200                     if (saveAll || notInStartUp(sn)) {
  1228                 if (stream != null) {
       
  1229                     for (Snippet sn : stream.collect(toList())) {
  1201                         writer.write(sn.source());
  1230                         writer.write(sn.source());
  1202                         writer.write("\n");
  1231                         writer.write("\n");
  1203                     }
  1232                     }
  1204                 }
  1233                 }
  1205             }
  1234             }