diff --git a/src/main/java/de/prob2/jupyter/ProBKernel.java b/src/main/java/de/prob2/jupyter/ProBKernel.java index 920481204c5facf14a68cd264a4073a626d4c385..04add0dafb7ffb1ee755d4f93174a1cd0e18b293 100644 --- a/src/main/java/de/prob2/jupyter/ProBKernel.java +++ b/src/main/java/de/prob2/jupyter/ProBKernel.java @@ -29,6 +29,7 @@ import de.prob2.jupyter.commands.ConstantsCommand; import de.prob2.jupyter.commands.DotCommand; import de.prob2.jupyter.commands.EvalCommand; import de.prob2.jupyter.commands.ExecCommand; +import de.prob2.jupyter.commands.GotoCommand; import de.prob2.jupyter.commands.GroovyCommand; import de.prob2.jupyter.commands.HelpCommand; import de.prob2.jupyter.commands.InitialiseCommand; @@ -42,6 +43,7 @@ import de.prob2.jupyter.commands.ShowCommand; import de.prob2.jupyter.commands.SolveCommand; import de.prob2.jupyter.commands.TableCommand; import de.prob2.jupyter.commands.TimeCommand; +import de.prob2.jupyter.commands.TraceCommand; import de.prob2.jupyter.commands.TypeCommand; import de.prob2.jupyter.commands.VersionCommand; @@ -150,9 +152,11 @@ public final class ProBKernel extends BaseKernel { this.commands.put("::load", injector.getInstance(LoadCellCommand.class)); this.commands.put(":pref", injector.getInstance(PrefCommand.class)); this.commands.put(":browse", injector.getInstance(BrowseCommand.class)); + this.commands.put(":trace", injector.getInstance(TraceCommand.class)); this.commands.put(":exec", injector.getInstance(ExecCommand.class)); this.commands.put(":constants", injector.getInstance(ConstantsCommand.class)); this.commands.put(":init", injector.getInstance(InitialiseCommand.class)); + this.commands.put(":goto", injector.getInstance(GotoCommand.class)); this.commands.put(":show", injector.getInstance(ShowCommand.class)); this.commands.put(":dot", injector.getInstance(DotCommand.class)); this.commands.put(":assert", injector.getInstance(AssertCommand.class)); diff --git a/src/main/java/de/prob2/jupyter/commands/GotoCommand.java b/src/main/java/de/prob2/jupyter/commands/GotoCommand.java new file mode 100644 index 0000000000000000000000000000000000000000..37f8d862a730677c216ed24d4aea9b0d098f09cc --- /dev/null +++ b/src/main/java/de/prob2/jupyter/commands/GotoCommand.java @@ -0,0 +1,56 @@ +package de.prob2.jupyter.commands; + +import com.google.inject.Inject; + +import de.prob.statespace.AnimationSelector; +import de.prob.statespace.Trace; + +import de.prob2.jupyter.UserErrorException; + +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 GotoCommand implements Command { + private final @NotNull AnimationSelector animationSelector; + + @Inject + private GotoCommand(final @NotNull AnimationSelector animationSelector) { + super(); + + this.animationSelector = animationSelector; + } + + @Override + public @NotNull String getSyntax() { + return ":goto INDEX"; + } + + @Override + public @NotNull String getShortHelp() { + return "Go to the state with the specified index in the current trace."; + } + + @Override + public @NotNull String getHelpBody() { + return "Use the `:trace` command to view the current trace and the indices of its states. Index -1 refers to the root state and is always available.\n\nGoing backwards in the current trace does *not* discard any parts of the trace, so it is possible to go forward again afterwards. However, executing an operation in a state *will* discard any parts of the trace after that state (and replace them with the destination state of the executed transition)."; + } + + @Override + public @NotNull DisplayData run(final @NotNull String argString) { + final int index = Integer.parseInt(argString); + final Trace trace = this.animationSelector.getCurrentTrace(); + if (index < -1 || index >= trace.size()) { + throw new UserErrorException(String.format("Invalid trace index %d, must be in -1..%d", index, trace.size()-1)); + } + this.animationSelector.changeCurrentAnimation(trace.gotoPosition(index)); + return new DisplayData("Changed to state with index " + index); + } + + @Override + public @Nullable ReplacementOptions complete(final @NotNull String argString, final int at) { + return null; + } +} diff --git a/src/main/java/de/prob2/jupyter/commands/TraceCommand.java b/src/main/java/de/prob2/jupyter/commands/TraceCommand.java new file mode 100644 index 0000000000000000000000000000000000000000..72ff13fe1dfd3e5a9487df6402b2813d0390b039 --- /dev/null +++ b/src/main/java/de/prob2/jupyter/commands/TraceCommand.java @@ -0,0 +1,86 @@ +package de.prob2.jupyter.commands; + +import java.util.List; + +import com.google.inject.Inject; + +import de.prob.statespace.AnimationSelector; +import de.prob.statespace.Trace; +import de.prob.statespace.Transition; + +import de.prob2.jupyter.UserErrorException; + +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 TraceCommand implements Command { + private final @NotNull AnimationSelector animationSelector; + + @Inject + private TraceCommand(final @NotNull AnimationSelector animationSelector) { + super(); + + this.animationSelector = animationSelector; + } + + @Override + public @NotNull String getSyntax() { + return ":trace"; + } + + @Override + public @NotNull String getShortHelp() { + return "Display all states and transitions in the current trace."; + } + + @Override + public @NotNull String getHelpBody() { + return "Each state has an index, which can be passed to the `:goto` command to go to that state.\n\nThe first state (index -1) is always the root state. All other states are reached from the root state by following (previously executed) transitions."; + } + + @Override + public @NotNull DisplayData run(final @NotNull String argString) { + if (!argString.isEmpty()) { + throw new UserErrorException("Expected no arguments"); + } + + final Trace trace = this.animationSelector.getCurrentTrace(); + final StringBuilder sbPlain = new StringBuilder("-1: Root state"); + final StringBuilder sbMarkdown = new StringBuilder("* -1: Root state"); + + if (trace.getCurrent().getIndex() == -1) { + sbPlain.append(" (current)"); + sbMarkdown.append(" **(current)**"); + } + + final List<Transition> transitionList = trace.getTransitionList(); + for (int i = 0; i < transitionList.size(); i++) { + final Transition transition = transitionList.get(i); + sbPlain.append('\n'); + sbPlain.append(i); + sbPlain.append(": "); + sbPlain.append(transition); + sbMarkdown.append("\n* "); + sbMarkdown.append(i); + sbMarkdown.append(": "); + sbMarkdown.append(transition); + + if (trace.getCurrent().getIndex() == i) { + sbPlain.append(" (current)"); + sbMarkdown.append(" **(current)**"); + } + } + + final DisplayData result = new DisplayData(sbPlain.toString()); + result.putMarkdown(sbMarkdown.toString()); + return result; + } + + @Override + public @Nullable ReplacementOptions complete(final @NotNull String argString, final int at) { + return null; + } +}