diff --git a/src/main/java/de/prob2/jupyter/commands/CommandUtils.java b/src/main/java/de/prob2/jupyter/commands/CommandUtils.java index 8d1517e2241058b1fcd9248e9e9287070a03355b..aca5a81ffaf383fc4025233aead53ebf13ebd41b 100644 --- a/src/main/java/de/prob2/jupyter/commands/CommandUtils.java +++ b/src/main/java/de/prob2/jupyter/commands/CommandUtils.java @@ -38,6 +38,10 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; public final class CommandUtils { + public interface Completer { + public abstract @Nullable ReplacementOptions complete(final @NotNull String argString, final int offset); + } + private static final @NotNull Logger LOGGER = LoggerFactory.getLogger(CommandUtils.class); public static final @NotNull Pattern ARG_SPLIT_PATTERN = Pattern.compile("\\h+"); @@ -220,6 +224,23 @@ public final class CommandUtils { ); } + public static @Nullable ReplacementOptions completeArgs(final @NotNull String argString, final int at, final @NotNull Completer @NotNull... completers) { + final Matcher argSplitMatcher = ARG_SPLIT_PATTERN.matcher(argString); + int argStart = 0; + int argEnd = argString.length(); + int i = 0; + while (argSplitMatcher.find() && i < completers.length) { + if (argSplitMatcher.end() > at) { + argEnd = argSplitMatcher.start(); + break; + } + argStart = argSplitMatcher.end(); + i++; + } + final ReplacementOptions replacements = completers[i].complete(argString.substring(argStart, argEnd), at - argStart); + return replacements == null ? null : offsetReplacementOptions(replacements, argStart); + } + public static @NotNull ReplacementOptions completeInBExpression(final @NotNull Trace trace, final @NotNull String code, final int at) { final Matcher identifierMatcher = B_IDENTIFIER_PATTERN.matcher(code); String identifier = ""; @@ -251,6 +272,10 @@ public final class CommandUtils { return new ReplacementOptions(new ArrayList<>(completions), start, end); } + public static @NotNull Completer bExpressionCompleter(final @NotNull Trace trace) { + return (code, at) -> completeInBExpression(trace, code, at); + } + public static @Nullable ReplacementOptions completeInPreferences(final @NotNull Trace trace, final @NotNull String code, final int at) { final Matcher argSplitMatcher = ARG_SPLIT_PATTERN.matcher(code); int prefNameStart = 0; @@ -269,4 +294,8 @@ public final class CommandUtils { return null; } } + + public static @NotNull Completer preferencesCompleter(final @NotNull Trace trace) { + return (code, at) -> completeInPreferences(trace, code, at); + } } diff --git a/src/main/java/de/prob2/jupyter/commands/DotCommand.java b/src/main/java/de/prob2/jupyter/commands/DotCommand.java index 53ee6926423c26dbe6d0f420b157e8edf52a93d5..6b2aecafcc9125c520d0e20ec806c8a4e57f1aa3 100644 --- a/src/main/java/de/prob2/jupyter/commands/DotCommand.java +++ b/src/main/java/de/prob2/jupyter/commands/DotCommand.java @@ -6,7 +6,6 @@ import java.nio.file.Files; import java.nio.file.Path; import java.util.Collections; import java.util.List; -import java.util.regex.Matcher; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -26,6 +25,7 @@ import io.github.spencerpark.jupyter.kernel.ReplacementOptions; import io.github.spencerpark.jupyter.kernel.display.DisplayData; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; public final class DotCommand implements Command { private final @NotNull AnimationSelector animationSelector; @@ -118,32 +118,23 @@ public final class DotCommand implements Command { } @Override - public @NotNull ReplacementOptions complete(final @NotNull String argString, final int at) { - final int cmdNameEnd; - final Matcher argSplitMatcher = CommandUtils.ARG_SPLIT_PATTERN.matcher(argString); - if (argSplitMatcher.find()) { - cmdNameEnd = argSplitMatcher.start(); - } else { - cmdNameEnd = argString.length(); - } - - if (cmdNameEnd < at) { - // Cursor is in the formula part of the arguments, provide B completions. - final ReplacementOptions replacements = CommandUtils.completeInBExpression(this.animationSelector.getCurrentTrace(), argString.substring(cmdNameEnd), at - cmdNameEnd); - return CommandUtils.offsetReplacementOptions(replacements, cmdNameEnd); - } else { - // Cursor is in the first part of the arguments, provide possible command names. - final Trace trace = this.animationSelector.getCurrentTrace(); - final GetAllDotCommands cmd = new GetAllDotCommands(trace.getCurrentState()); - trace.getStateSpace().execute(cmd); - final String prefix = argString.substring(0, at); - final List<String> commands = cmd.getCommands().stream() - .filter(DynamicCommandItem::isAvailable) - .map(DynamicCommandItem::getCommand) - .filter(s -> s.startsWith(prefix)) - .sorted() - .collect(Collectors.toList()); - return new ReplacementOptions(commands, 0, argString.length()); - } + public @Nullable ReplacementOptions complete(final @NotNull String argString, final int at) { + return CommandUtils.completeArgs( + argString, at, + (commandName, at0) -> { + final Trace trace = this.animationSelector.getCurrentTrace(); + final GetAllDotCommands cmd = new GetAllDotCommands(trace.getCurrentState()); + trace.getStateSpace().execute(cmd); + final String prefix = commandName.substring(0, at0); + final List<String> commands = cmd.getCommands().stream() + .filter(DynamicCommandItem::isAvailable) + .map(DynamicCommandItem::getCommand) + .filter(s -> s.startsWith(prefix)) + .sorted() + .collect(Collectors.toList()); + return new ReplacementOptions(commands, 0, commandName.length()); + }, + CommandUtils.bExpressionCompleter(this.animationSelector.getCurrentTrace()) + ); } } diff --git a/src/main/java/de/prob2/jupyter/commands/ExecCommand.java b/src/main/java/de/prob2/jupyter/commands/ExecCommand.java index 8d679fc0386dbb89e838ef3597e2b561409bbf39..ed7f52fe5bc96e23b4459a9e224f174f90ac195f 100644 --- a/src/main/java/de/prob2/jupyter/commands/ExecCommand.java +++ b/src/main/java/de/prob2/jupyter/commands/ExecCommand.java @@ -2,7 +2,6 @@ package de.prob2.jupyter.commands; import java.util.Collections; import java.util.List; -import java.util.regex.Matcher; import java.util.stream.Collectors; import com.google.inject.Inject; @@ -16,6 +15,7 @@ import io.github.spencerpark.jupyter.kernel.ReplacementOptions; import io.github.spencerpark.jupyter.kernel.display.DisplayData; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; public final class ExecCommand implements Command { private final @NotNull AnimationSelector animationSelector; @@ -60,32 +60,23 @@ public final class ExecCommand implements Command { } @Override - public @NotNull ReplacementOptions complete(final @NotNull String argString, final int at) { - final int opNameEnd; - final Matcher argSplitMatcher = CommandUtils.ARG_SPLIT_PATTERN.matcher(argString); - if (argSplitMatcher.find()) { - opNameEnd = argSplitMatcher.start(); - } else { - opNameEnd = argString.length(); - } - - if (opNameEnd < at) { - // Cursor is in the predicate part of the arguments, provide B completions. - final ReplacementOptions replacements = CommandUtils.completeInBExpression(this.animationSelector.getCurrentTrace(), argString.substring(opNameEnd), at - opNameEnd); - return CommandUtils.offsetReplacementOptions(replacements, opNameEnd); - } else { - // Cursor is in the first part of the arguments, provide possible operation names. - final String prefix = argString.substring(0, at); - final List<String> opNames = this.animationSelector.getCurrentTrace() - .getNextTransitions() - .stream() - .map(Transition::getName) - .map(CommandUtils::prettyOperationName) - .distinct() - .filter(s -> s.startsWith(prefix)) - .sorted() - .collect(Collectors.toList()); - return new ReplacementOptions(opNames, 0, opNameEnd); - } + public @Nullable ReplacementOptions complete(final @NotNull String argString, final int at) { + return CommandUtils.completeArgs( + argString, at, + (operation, at0) -> { + final String prefix = operation.substring(0, at0); + final List<String> opNames = this.animationSelector.getCurrentTrace() + .getNextTransitions() + .stream() + .map(Transition::getName) + .map(CommandUtils::prettyOperationName) + .distinct() + .filter(s -> s.startsWith(prefix)) + .sorted() + .collect(Collectors.toList()); + return new ReplacementOptions(opNames, 0, operation.length()); + }, + CommandUtils.bExpressionCompleter(this.animationSelector.getCurrentTrace()) + ); } } diff --git a/src/main/java/de/prob2/jupyter/commands/LoadFileCommand.java b/src/main/java/de/prob2/jupyter/commands/LoadFileCommand.java index d0f90818902dc8abfbf5bde23f4473b4bcdf564f..56143ae56ef98d3fe26c06db68f5a36b102fb9e8 100644 --- a/src/main/java/de/prob2/jupyter/commands/LoadFileCommand.java +++ b/src/main/java/de/prob2/jupyter/commands/LoadFileCommand.java @@ -8,7 +8,6 @@ import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.regex.Matcher; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -111,41 +110,32 @@ public final class LoadFileCommand implements Command { @Override public @Nullable ReplacementOptions complete(final @NotNull String argString, final int at) { - final int fileNameEnd; - final Matcher argSplitMatcher = CommandUtils.ARG_SPLIT_PATTERN.matcher(argString); - if (argSplitMatcher.find()) { - fileNameEnd = argSplitMatcher.start(); - } else { - fileNameEnd = argString.length(); - } - - if (fileNameEnd < at) { - // Cursor is in the preferences, provide preference name completions. - final ReplacementOptions replacements = CommandUtils.completeInPreferences(this.animationSelector.getCurrentTrace(), argString.substring(fileNameEnd), at - fileNameEnd); - return replacements == null ? null : CommandUtils.offsetReplacementOptions(replacements, fileNameEnd); - } else { - // Cursor is in the file name, provide machine files from the current directory as completions. - final String prefix = argString.substring(0, at); - final List<String> fileNames; - try (final Stream<Path> list = Files.list(Paths.get(""))) { - fileNames = list - .map(Path::getFileName) - .map(Object::toString) - .filter(s -> s.startsWith(prefix)) - .filter(s -> { - final int dotIndex = s.lastIndexOf('.'); - if (dotIndex == -1) { - return false; - } - final String extension = s.substring(dotIndex+1); - return EXTENSION_TO_FACTORY_MAP.containsKey(extension); - }) - .collect(Collectors.toList()); - } catch (final IOException e) { - LOGGER.warn("Could not list contents of the current directory, cannot provide file name completions for :load", e); - return null; - } - return new ReplacementOptions(fileNames, 0, fileNameEnd); - } + return CommandUtils.completeArgs( + argString, at, + (filename, at0) -> { + final String prefix = filename.substring(0, at0); + final List<String> fileNames; + try (final Stream<Path> list = Files.list(Paths.get(""))) { + fileNames = list + .map(Path::getFileName) + .map(Object::toString) + .filter(s -> s.startsWith(prefix)) + .filter(s -> { + final int dotIndex = s.lastIndexOf('.'); + if (dotIndex == -1) { + return false; + } + final String extension = s.substring(dotIndex+1); + return EXTENSION_TO_FACTORY_MAP.containsKey(extension); + }) + .collect(Collectors.toList()); + } catch (final IOException e) { + LOGGER.warn("Could not list contents of the current directory, cannot provide file name completions for :load", e); + return null; + } + return new ReplacementOptions(fileNames, 0, filename.length()); + }, + CommandUtils.preferencesCompleter(this.animationSelector.getCurrentTrace()) + ); } } diff --git a/src/main/java/de/prob2/jupyter/commands/SolveCommand.java b/src/main/java/de/prob2/jupyter/commands/SolveCommand.java index 6b451e540c6469080874e73b365751af93be4167..da3e5852044cb9e488933197d1a68aad548f5e68 100644 --- a/src/main/java/de/prob2/jupyter/commands/SolveCommand.java +++ b/src/main/java/de/prob2/jupyter/commands/SolveCommand.java @@ -3,7 +3,6 @@ package de.prob2.jupyter.commands; import java.util.Arrays; import java.util.List; import java.util.Map; -import java.util.regex.Matcher; import java.util.stream.Collectors; import com.google.inject.Inject; @@ -20,6 +19,7 @@ import io.github.spencerpark.jupyter.kernel.ReplacementOptions; import io.github.spencerpark.jupyter.kernel.display.DisplayData; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; public final class SolveCommand implements Command { private static final @NotNull Map<@NotNull String, CbcSolveCommand.@NotNull Solvers> SOLVERS = Arrays.stream(CbcSolveCommand.Solvers.values()) @@ -76,27 +76,18 @@ public final class SolveCommand implements Command { } @Override - public @NotNull ReplacementOptions complete(final @NotNull String argString, final int at) { - final int solverNameEnd; - final Matcher argSplitMatcher = CommandUtils.ARG_SPLIT_PATTERN.matcher(argString); - if (argSplitMatcher.find()) { - solverNameEnd = argSplitMatcher.start(); - } else { - solverNameEnd = argString.length(); - } - - if (solverNameEnd < at) { - // Cursor is in the predicate part of the arguments, provide B completions. - final ReplacementOptions replacements = CommandUtils.completeInBExpression(this.animationSelector.getCurrentTrace(), argString.substring(solverNameEnd), at - solverNameEnd); - return CommandUtils.offsetReplacementOptions(replacements, solverNameEnd); - } else { - // Cursor is in the solver name. - final String prefix = argString.substring(0, at); - final List<String> solverNames = SOLVERS.keySet().stream() - .filter(s -> s.startsWith(prefix)) - .sorted() - .collect(Collectors.toList()); - return new ReplacementOptions(solverNames, 0, solverNameEnd); - } + public @Nullable ReplacementOptions complete(final @NotNull String argString, final int at) { + return CommandUtils.completeArgs( + argString, at, + (solverName, at0) -> { + final String prefix = solverName.substring(0, at0); + final List<String> solverNames = SOLVERS.keySet().stream() + .filter(s -> s.startsWith(prefix)) + .sorted() + .collect(Collectors.toList()); + return new ReplacementOptions(solverNames, 0, solverName.length()); + }, + CommandUtils.bExpressionCompleter(this.animationSelector.getCurrentTrace()) + ); } }