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: |
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."); |