diff --git a/src/main/java/de/prob2/jupyter/Command.java b/src/main/java/de/prob2/jupyter/Command.java
index 01fccee5bdd5b9fc6a801eb77e5298d014016141..bd7b6279ab402407b6b0399fb137aa153abf1284 100644
--- a/src/main/java/de/prob2/jupyter/Command.java
+++ b/src/main/java/de/prob2/jupyter/Command.java
@@ -1,6 +1,5 @@
 package de.prob2.jupyter;
 
-import io.github.spencerpark.jupyter.kernel.ReplacementOptions;
 import io.github.spencerpark.jupyter.kernel.display.DisplayData;
 
 import org.jetbrains.annotations.NotNull;
@@ -45,5 +44,5 @@ public interface Command {
 	
 	public abstract @NotNull ParameterInspectors getParameterInspectors();
 	
-	public abstract @Nullable ReplacementOptions complete(final @NotNull String argString, final int at);
+	public abstract @NotNull ParameterCompleters getParameterCompleters();
 }
diff --git a/src/main/java/de/prob2/jupyter/CommandUtils.java b/src/main/java/de/prob2/jupyter/CommandUtils.java
index c36d84ecd2e2798039646a2f31de4cfa1d27ceff..cd77b5107727d973b893a9d00c84abebb9e127cf 100644
--- a/src/main/java/de/prob2/jupyter/CommandUtils.java
+++ b/src/main/java/de/prob2/jupyter/CommandUtils.java
@@ -300,34 +300,6 @@ public final class CommandUtils {
 		}
 	}
 	
-	public static @NotNull ReplacementOptions offsetReplacementOptions(final @NotNull ReplacementOptions replacements, final int offset) {
-		return new ReplacementOptions(
-			replacements.getReplacements(),
-			replacements.getSourceStart() + offset,
-			replacements.getSourceEnd() + offset
-		);
-	}
-	
-	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()) {
-			if (argSplitMatcher.end() > at) {
-				argEnd = argSplitMatcher.start();
-				break;
-			}
-			argStart = argSplitMatcher.end();
-			if (i >= completers.length-1) {
-				break;
-			}
-			i++;
-		}
-		final ReplacementOptions replacements = completers[i].complete(argString.substring(argStart, argEnd), at - argStart);
-		return replacements == null ? null : offsetReplacementOptions(replacements, argStart);
-	}
-	
 	public static @Nullable Matcher matchBIdentifierAt(final @NotNull String code, final int at) {
 		final Matcher identifierMatcher = B_IDENTIFIER_PATTERN.matcher(code);
 		while (identifierMatcher.find() && identifierMatcher.start() < at) {
diff --git a/src/main/java/de/prob2/jupyter/ParameterCompleters.java b/src/main/java/de/prob2/jupyter/ParameterCompleters.java
new file mode 100644
index 0000000000000000000000000000000000000000..98e98bd3bfe9bdd5cc5cf24a1f583e20baa12e7d
--- /dev/null
+++ b/src/main/java/de/prob2/jupyter/ParameterCompleters.java
@@ -0,0 +1,41 @@
+package de.prob2.jupyter;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Optional;
+
+import com.google.common.base.MoreObjects;
+
+import org.jetbrains.annotations.NotNull;
+
+public final class ParameterCompleters {
+	public static final @NotNull ParameterCompleters NONE = new ParameterCompleters(Collections.emptyMap());
+	
+	private final @NotNull Map<@NotNull Parameter<?>, CommandUtils.@NotNull Completer> completers;
+	
+	public ParameterCompleters(final @NotNull Map<@NotNull Parameter<?>, CommandUtils.@NotNull Completer> completers) {
+		super();
+		
+		this.completers = Collections.unmodifiableMap(new HashMap<>(completers));
+	}
+	
+	public @NotNull Map<@NotNull Parameter<?>, CommandUtils.@NotNull Completer> getCompleters() {
+		return this.completers;
+	}
+	
+	@Override
+	public String toString() {
+		return MoreObjects.toStringHelper(this)
+			.add("completers", this.getCompleters())
+			.toString();
+	}
+	
+	public @NotNull Optional<CommandUtils.Completer> getCompleterForParameter(final @NotNull Parameter<?> parameter) {
+		if (this.getCompleters().containsKey(parameter)) {
+			return Optional.of(this.getCompleters().get(parameter));
+		} else {
+			return Optional.empty();
+		}
+	}
+}
diff --git a/src/main/java/de/prob2/jupyter/ProBKernel.java b/src/main/java/de/prob2/jupyter/ProBKernel.java
index b2c986437ef99fde95b885ba0041580da3c563f4..7e65456748838b82c0fabeeac7be04baa8f59c1a 100644
--- a/src/main/java/de/prob2/jupyter/ProBKernel.java
+++ b/src/main/java/de/prob2/jupyter/ProBKernel.java
@@ -403,6 +403,32 @@ public final class ProBKernel extends BaseKernel {
 		}
 	}
 	
+	private static @NotNull ReplacementOptions offsetReplacementOptions(final @NotNull ReplacementOptions replacements, final int offset) {
+		return new ReplacementOptions(
+			replacements.getReplacements(),
+			replacements.getSourceStart() + offset,
+			replacements.getSourceEnd() + offset
+		);
+	}
+	
+	private static @Nullable ReplacementOptions completeCommandArguments(final @NotNull Command command, final @NotNull String argString, final int at) {
+		final SplitResult split = CommandUtils.splitArgs(command.getParameters(), argString, at);
+		if (split.getParameterAtPosition().isPresent()) {
+			final Optional<CommandUtils.Completer> completer = command.getParameterCompleters().getCompleterForParameter(split.getParameterAtPosition().get());
+			if (completer.isPresent()) {
+				final List<PositionedString> argsAtPosition = split.getArguments().get(split.getParameterAtPosition().get());
+				assert !argsAtPosition.isEmpty();
+				final PositionedString lastArgument = argsAtPosition.get(argsAtPosition.size() - 1);
+				final ReplacementOptions replacements = completer.get().complete(lastArgument.getValue(), at - lastArgument.getStartPosition());
+				return replacements == null ? null : offsetReplacementOptions(replacements, lastArgument.getStartPosition());
+			} else {
+				return null;
+			}
+		} else {
+			return null;
+		}
+	}
+	
 	@Override
 	public @Nullable ReplacementOptions complete(final @NotNull String code, final int at) {
 		final Matcher commandMatcher = COMMAND_PATTERN.matcher(code);
@@ -426,8 +452,8 @@ public final class ProBKernel extends BaseKernel {
 				assert name != null;
 				final String argString = commandMatcher.group(2) == null ? "" : commandMatcher.group(2);
 				if (this.getCommands().containsKey(name)) {
-					final ReplacementOptions replacements = this.getCommands().get(name).complete(argString, at - argOffset);
-					return replacements == null ? null : CommandUtils.offsetReplacementOptions(replacements, argOffset);
+					final ReplacementOptions replacements = completeCommandArguments(this.getCommands().get(name), argString, at - argOffset);
+					return replacements == null ? null : offsetReplacementOptions(replacements, argOffset);
 				} else {
 					// Invalid command, can't provide any completions.
 					return null;
@@ -436,7 +462,7 @@ public final class ProBKernel extends BaseKernel {
 		} else if (SPACE_PATTERN.matcher(code).matches()) {
 			// The code contains only whitespace, provide completions from :eval and for command names.
 			final List<String> replacementStrings = new ArrayList<>();
-			final ReplacementOptions evalReplacements = this.getCommands().get(":eval").complete(code, at);
+			final ReplacementOptions evalReplacements = completeCommandArguments(this.getCommands().get(":eval"), code, at);
 			if (evalReplacements != null) {
 				replacementStrings.addAll(evalReplacements.getReplacements());
 			}
@@ -444,7 +470,7 @@ public final class ProBKernel extends BaseKernel {
 			return new ReplacementOptions(replacementStrings, at, at);
 		} else {
 			// The code is not a valid command, ask :eval for completions.
-			return this.getCommands().get(":eval").complete(code, at);
+			return completeCommandArguments(this.getCommands().get(":eval"), code, at);
 		}
 	}
 	
diff --git a/src/main/java/de/prob2/jupyter/commands/AssertCommand.java b/src/main/java/de/prob2/jupyter/commands/AssertCommand.java
index cfe9e22d5f6de80e6a48d5db7d37569abfbc68ee..9b8a8be7362f7eda50e9fae2e7979fb99370a65d 100644
--- a/src/main/java/de/prob2/jupyter/commands/AssertCommand.java
+++ b/src/main/java/de/prob2/jupyter/commands/AssertCommand.java
@@ -12,13 +12,13 @@ import de.prob.statespace.AnimationSelector;
 import de.prob2.jupyter.Command;
 import de.prob2.jupyter.CommandUtils;
 import de.prob2.jupyter.Parameter;
+import de.prob2.jupyter.ParameterCompleters;
 import de.prob2.jupyter.ParameterInspectors;
 import de.prob2.jupyter.Parameters;
 import de.prob2.jupyter.ParsedArguments;
 import de.prob2.jupyter.ProBKernel;
 import de.prob2.jupyter.UserErrorException;
 
-import io.github.spencerpark.jupyter.kernel.ReplacementOptions;
 import io.github.spencerpark.jupyter.kernel.display.DisplayData;
 import io.github.spencerpark.jupyter.kernel.display.mime.MIMEType;
 
@@ -91,7 +91,9 @@ public final class AssertCommand implements Command {
 	}
 	
 	@Override
-	public @NotNull ReplacementOptions complete(final @NotNull String argString, final int at) {
-		return CommandUtils.completeInBExpression(this.animationSelector.getCurrentTrace(), argString, at);
+	public @NotNull ParameterCompleters getParameterCompleters() {
+		return new ParameterCompleters(Collections.singletonMap(
+			FORMULA_PARAM, CommandUtils.bExpressionCompleter(this.animationSelector.getCurrentTrace())
+		));
 	}
 }
diff --git a/src/main/java/de/prob2/jupyter/commands/BrowseCommand.java b/src/main/java/de/prob2/jupyter/commands/BrowseCommand.java
index a42e3c72f4d7cf5bd061d6dae768916cbfe081e1..36d89b6cbdaf296baecf6e4258711cbbd4ca9bae 100644
--- a/src/main/java/de/prob2/jupyter/commands/BrowseCommand.java
+++ b/src/main/java/de/prob2/jupyter/commands/BrowseCommand.java
@@ -12,15 +12,14 @@ import de.prob.statespace.LoadedMachine;
 import de.prob.statespace.Trace;
 import de.prob.statespace.Transition;
 import de.prob2.jupyter.Command;
+import de.prob2.jupyter.ParameterCompleters;
 import de.prob2.jupyter.ParameterInspectors;
 import de.prob2.jupyter.Parameters;
 import de.prob2.jupyter.ParsedArguments;
 
-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;
 
 import se.sawano.java.text.AlphanumericComparator;
 
@@ -96,7 +95,7 @@ public final class BrowseCommand implements Command {
 	}
 	
 	@Override
-	public @Nullable ReplacementOptions complete(final @NotNull String argString, final int at) {
-		return null;
+	public @NotNull ParameterCompleters getParameterCompleters() {
+		return ParameterCompleters.NONE;
 	}
 }
diff --git a/src/main/java/de/prob2/jupyter/commands/BsymbCommand.java b/src/main/java/de/prob2/jupyter/commands/BsymbCommand.java
index fd6b68e7ff26f932cc263c92460413f9d61f7185..d725b476871747d89918498359e20baa72db3561 100644
--- a/src/main/java/de/prob2/jupyter/commands/BsymbCommand.java
+++ b/src/main/java/de/prob2/jupyter/commands/BsymbCommand.java
@@ -3,15 +3,14 @@ package de.prob2.jupyter.commands;
 import com.google.inject.Inject;
 
 import de.prob2.jupyter.Command;
+import de.prob2.jupyter.ParameterCompleters;
 import de.prob2.jupyter.ParameterInspectors;
 import de.prob2.jupyter.Parameters;
 import de.prob2.jupyter.ParsedArguments;
 
-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 BsymbCommand implements Command {
 	@Inject
@@ -59,7 +58,7 @@ public final class BsymbCommand implements Command {
 	}
 	
 	@Override
-	public @Nullable ReplacementOptions complete(final @NotNull String argString, final int at) {
-		return null;
+	public @NotNull ParameterCompleters getParameterCompleters() {
+		return ParameterCompleters.NONE;
 	}
 }
diff --git a/src/main/java/de/prob2/jupyter/commands/CheckCommand.java b/src/main/java/de/prob2/jupyter/commands/CheckCommand.java
index 6f8be9fe49bcce00c80220e98cec86def31e25ba..5dfeac2600f276aff4f85ad0e51e1cf562d23f26 100644
--- a/src/main/java/de/prob2/jupyter/commands/CheckCommand.java
+++ b/src/main/java/de/prob2/jupyter/commands/CheckCommand.java
@@ -23,6 +23,7 @@ import de.prob.unicode.UnicodeTranslator;
 import de.prob2.jupyter.Command;
 import de.prob2.jupyter.CommandUtils;
 import de.prob2.jupyter.Parameter;
+import de.prob2.jupyter.ParameterCompleters;
 import de.prob2.jupyter.ParameterInspectors;
 import de.prob2.jupyter.Parameters;
 import de.prob2.jupyter.ParsedArguments;
@@ -118,11 +119,15 @@ public final class CheckCommand implements Command {
 	}
 	
 	@Override
-	public @NotNull ReplacementOptions complete(final @NotNull String argString, final int at) {
-		final String prefix = argString.substring(0, at);
-		return new ReplacementOptions(CHILDREN_BASE_CLASS_MAP.keySet()
-			.stream()
-			.filter(s -> s.startsWith(prefix))
-			.collect(Collectors.toList()), 0, argString.length());
+	public @NotNull ParameterCompleters getParameterCompleters() {
+		return new ParameterCompleters(Collections.singletonMap(
+			WHAT_PARAM, (argString, at) -> {
+				final String prefix = argString.substring(0, at);
+				return new ReplacementOptions(CHILDREN_BASE_CLASS_MAP.keySet()
+					.stream()
+					.filter(s -> s.startsWith(prefix))
+					.collect(Collectors.toList()), 0, argString.length());
+			}
+		));
 	}
 }
diff --git a/src/main/java/de/prob2/jupyter/commands/ConstantsCommand.java b/src/main/java/de/prob2/jupyter/commands/ConstantsCommand.java
index d7d2a14e1c6e48232c1953167e2b18e67be91c8d..5bb95a3152d71f319753efa565c6e8b76da352d3 100644
--- a/src/main/java/de/prob2/jupyter/commands/ConstantsCommand.java
+++ b/src/main/java/de/prob2/jupyter/commands/ConstantsCommand.java
@@ -13,12 +13,12 @@ import de.prob.statespace.Transition;
 import de.prob2.jupyter.Command;
 import de.prob2.jupyter.CommandUtils;
 import de.prob2.jupyter.Parameter;
+import de.prob2.jupyter.ParameterCompleters;
 import de.prob2.jupyter.ParameterInspectors;
 import de.prob2.jupyter.Parameters;
 import de.prob2.jupyter.ParsedArguments;
 import de.prob2.jupyter.ProBKernel;
 
-import io.github.spencerpark.jupyter.kernel.ReplacementOptions;
 import io.github.spencerpark.jupyter.kernel.display.DisplayData;
 
 import org.jetbrains.annotations.NotNull;
@@ -87,7 +87,9 @@ public final class ConstantsCommand implements Command {
 	}
 	
 	@Override
-	public @NotNull ReplacementOptions complete(final @NotNull String argString, final int at) {
-		return CommandUtils.completeInBExpression(this.animationSelector.getCurrentTrace(), argString, at);
+	public @NotNull ParameterCompleters getParameterCompleters() {
+		return new ParameterCompleters(Collections.singletonMap(
+			PREDICATE_PARAM, CommandUtils.bExpressionCompleter(this.animationSelector.getCurrentTrace())
+		));
 	}
 }
diff --git a/src/main/java/de/prob2/jupyter/commands/DotCommand.java b/src/main/java/de/prob2/jupyter/commands/DotCommand.java
index f1d39882f54db31ee4c0e41b08f9ac42ab1e94f4..321fa101a2356baedc2157f830a15611be60928b 100644
--- a/src/main/java/de/prob2/jupyter/commands/DotCommand.java
+++ b/src/main/java/de/prob2/jupyter/commands/DotCommand.java
@@ -24,6 +24,7 @@ import de.prob.statespace.Trace;
 import de.prob2.jupyter.Command;
 import de.prob2.jupyter.CommandUtils;
 import de.prob2.jupyter.Parameter;
+import de.prob2.jupyter.ParameterCompleters;
 import de.prob2.jupyter.ParameterInspectors;
 import de.prob2.jupyter.Parameters;
 import de.prob2.jupyter.ParsedArguments;
@@ -34,7 +35,6 @@ 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 static final @NotNull Parameter.RequiredSingle COMMAND_PARAM = Parameter.required("command");
@@ -145,14 +145,13 @@ public final class DotCommand implements Command {
 	}
 	
 	@Override
-	public @Nullable ReplacementOptions complete(final @NotNull String argString, final int at) {
-		return CommandUtils.completeArgs(
-			argString, at,
-			(commandName, at0) -> {
+	public @NotNull ParameterCompleters getParameterCompleters() {
+		return new ParameterCompleters(ImmutableMap.of(
+			COMMAND_PARAM, (commandName, at) -> {
 				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 String prefix = commandName.substring(0, at);
 				final List<String> commands = cmd.getCommands().stream()
 					.filter(DynamicCommandItem::isAvailable)
 					.map(DynamicCommandItem::getCommand)
@@ -161,7 +160,7 @@ public final class DotCommand implements Command {
 					.collect(Collectors.toList());
 				return new ReplacementOptions(commands, 0, commandName.length());
 			},
-			CommandUtils.bExpressionCompleter(this.animationSelector.getCurrentTrace())
-		);
+			FORMULA_PARAM, CommandUtils.bExpressionCompleter(this.animationSelector.getCurrentTrace())
+		));
 	}
 }
diff --git a/src/main/java/de/prob2/jupyter/commands/EvalCommand.java b/src/main/java/de/prob2/jupyter/commands/EvalCommand.java
index 972926f604c67bea93cdf560bc89218c58c89596..ee8e47021bbc49aa89c0438cb3096b1492725e98 100644
--- a/src/main/java/de/prob2/jupyter/commands/EvalCommand.java
+++ b/src/main/java/de/prob2/jupyter/commands/EvalCommand.java
@@ -10,12 +10,12 @@ import de.prob.statespace.AnimationSelector;
 import de.prob2.jupyter.Command;
 import de.prob2.jupyter.CommandUtils;
 import de.prob2.jupyter.Parameter;
+import de.prob2.jupyter.ParameterCompleters;
 import de.prob2.jupyter.ParameterInspectors;
 import de.prob2.jupyter.Parameters;
 import de.prob2.jupyter.ParsedArguments;
 import de.prob2.jupyter.ProBKernel;
 
-import io.github.spencerpark.jupyter.kernel.ReplacementOptions;
 import io.github.spencerpark.jupyter.kernel.display.DisplayData;
 
 import org.jetbrains.annotations.NotNull;
@@ -74,7 +74,9 @@ public final class EvalCommand implements Command {
 	}
 	
 	@Override
-	public @NotNull ReplacementOptions complete(final @NotNull String argString, final int at) {
-		return CommandUtils.completeInBExpression(this.animationSelector.getCurrentTrace(), argString, at);
+	public @NotNull ParameterCompleters getParameterCompleters() {
+		return new ParameterCompleters(Collections.singletonMap(
+			FORMULA_PARAM, 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 3b123e295e0de3d8b970ada4fae7bb576da3253b..f3914382c10328431ba68bbe429ba45877caca7a 100644
--- a/src/main/java/de/prob2/jupyter/commands/ExecCommand.java
+++ b/src/main/java/de/prob2/jupyter/commands/ExecCommand.java
@@ -16,6 +16,7 @@ import de.prob.statespace.Transition;
 import de.prob2.jupyter.Command;
 import de.prob2.jupyter.CommandUtils;
 import de.prob2.jupyter.Parameter;
+import de.prob2.jupyter.ParameterCompleters;
 import de.prob2.jupyter.ParameterInspectors;
 import de.prob2.jupyter.Parameters;
 import de.prob2.jupyter.ParsedArguments;
@@ -25,7 +26,6 @@ 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 static final @NotNull Parameter.RequiredSingle OPERATION_PARAM = Parameter.required("operation");
@@ -96,11 +96,10 @@ public final class ExecCommand implements Command {
 	}
 	
 	@Override
-	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);
+	public @NotNull ParameterCompleters getParameterCompleters() {
+		return new ParameterCompleters(ImmutableMap.of(
+			OPERATION_PARAM, (operation, at) -> {
+				final String prefix = operation.substring(0, at);
 				final List<String> opNames = this.animationSelector.getCurrentTrace()
 					.getNextTransitions()
 					.stream()
@@ -112,7 +111,7 @@ public final class ExecCommand implements Command {
 					.collect(Collectors.toList());
 				return new ReplacementOptions(opNames, 0, operation.length());
 			},
-			CommandUtils.bExpressionCompleter(this.animationSelector.getCurrentTrace())
-		);
+			PREDICATE_PARAM, CommandUtils.bExpressionCompleter(this.animationSelector.getCurrentTrace())
+		));
 	}
 }
diff --git a/src/main/java/de/prob2/jupyter/commands/FindCommand.java b/src/main/java/de/prob2/jupyter/commands/FindCommand.java
index 055d4695a806fedb70446ddf77cc794c27a96008..8e2beb04845c11112f15781139cb494dc8c2dd8b 100644
--- a/src/main/java/de/prob2/jupyter/commands/FindCommand.java
+++ b/src/main/java/de/prob2/jupyter/commands/FindCommand.java
@@ -12,12 +12,12 @@ import de.prob.statespace.Trace;
 import de.prob2.jupyter.Command;
 import de.prob2.jupyter.CommandUtils;
 import de.prob2.jupyter.Parameter;
+import de.prob2.jupyter.ParameterCompleters;
 import de.prob2.jupyter.ParameterInspectors;
 import de.prob2.jupyter.Parameters;
 import de.prob2.jupyter.ParsedArguments;
 import de.prob2.jupyter.ProBKernel;
 
-import io.github.spencerpark.jupyter.kernel.ReplacementOptions;
 import io.github.spencerpark.jupyter.kernel.display.DisplayData;
 
 import org.jetbrains.annotations.NotNull;
@@ -82,7 +82,9 @@ public final class FindCommand implements Command {
 	}
 	
 	@Override
-	public @NotNull ReplacementOptions complete(final @NotNull String argString, final int at) {
-		return CommandUtils.completeInBExpression(this.animationSelector.getCurrentTrace(), argString, at);
+	public @NotNull ParameterCompleters getParameterCompleters() {
+		return new ParameterCompleters(Collections.singletonMap(
+			PREDICATE_PARAM, CommandUtils.bExpressionCompleter(this.animationSelector.getCurrentTrace())
+		));
 	}
 }
diff --git a/src/main/java/de/prob2/jupyter/commands/GotoCommand.java b/src/main/java/de/prob2/jupyter/commands/GotoCommand.java
index 557b7511a66bbe9f0b85119937568c23378da642..292cc4044fc2a029cf4e112577f4dc081813e352 100644
--- a/src/main/java/de/prob2/jupyter/commands/GotoCommand.java
+++ b/src/main/java/de/prob2/jupyter/commands/GotoCommand.java
@@ -8,16 +8,15 @@ import de.prob.statespace.AnimationSelector;
 import de.prob.statespace.Trace;
 import de.prob2.jupyter.Command;
 import de.prob2.jupyter.Parameter;
+import de.prob2.jupyter.ParameterCompleters;
 import de.prob2.jupyter.ParameterInspectors;
 import de.prob2.jupyter.Parameters;
 import de.prob2.jupyter.ParsedArguments;
 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 static final @NotNull Parameter.RequiredSingle INDEX_PARAM = Parameter.required("index");
@@ -74,7 +73,7 @@ public final class GotoCommand implements Command {
 	}
 	
 	@Override
-	public @Nullable ReplacementOptions complete(final @NotNull String argString, final int at) {
-		return null;
+	public @NotNull ParameterCompleters getParameterCompleters() {
+		return ParameterCompleters.NONE;
 	}
 }
diff --git a/src/main/java/de/prob2/jupyter/commands/GroovyCommand.java b/src/main/java/de/prob2/jupyter/commands/GroovyCommand.java
index 2f085e925d93a914af2721f9f8d9597415a50da2..45056ed9cf121acdcceae68396f6f5381b78ef87 100644
--- a/src/main/java/de/prob2/jupyter/commands/GroovyCommand.java
+++ b/src/main/java/de/prob2/jupyter/commands/GroovyCommand.java
@@ -12,16 +12,15 @@ import com.google.inject.Injector;
 import de.prob.scripting.ScriptEngineProvider;
 import de.prob2.jupyter.Command;
 import de.prob2.jupyter.Parameter;
+import de.prob2.jupyter.ParameterCompleters;
 import de.prob2.jupyter.ParameterInspectors;
 import de.prob2.jupyter.Parameters;
 import de.prob2.jupyter.ParsedArguments;
 import de.prob2.jupyter.ProBKernel;
 
-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 GroovyCommand implements Command {
 	private static final @NotNull Parameter.RequiredSingle EXPRESSION_PARAM = Parameter.requiredRemainder("expression");
@@ -82,7 +81,7 @@ public final class GroovyCommand implements Command {
 	}
 	
 	@Override
-	public @Nullable ReplacementOptions complete(final @NotNull String argString, final int at) {
-		return null;
+	public @NotNull ParameterCompleters getParameterCompleters() {
+		return ParameterCompleters.NONE;
 	}
 }
diff --git a/src/main/java/de/prob2/jupyter/commands/HelpCommand.java b/src/main/java/de/prob2/jupyter/commands/HelpCommand.java
index 9b9ca2f2b26e56868b67d549ada4a7018ab25611..7b78a10859ef38441064cb5ccf710b6254cd0d42 100644
--- a/src/main/java/de/prob2/jupyter/commands/HelpCommand.java
+++ b/src/main/java/de/prob2/jupyter/commands/HelpCommand.java
@@ -14,6 +14,7 @@ import com.google.inject.Injector;
 
 import de.prob2.jupyter.Command;
 import de.prob2.jupyter.Parameter;
+import de.prob2.jupyter.ParameterCompleters;
 import de.prob2.jupyter.ParameterInspectors;
 import de.prob2.jupyter.Parameters;
 import de.prob2.jupyter.ParsedArguments;
@@ -175,18 +176,22 @@ public final class HelpCommand implements Command {
 	}
 	
 	@Override
-	public @NotNull ReplacementOptions complete(final @NotNull String argString, final int at) {
-		final String prefix = argString.substring(0, at);
-		return new ReplacementOptions(
-			this.injector.getInstance(ProBKernel.class)
-				.getCommands()
-				.keySet()
-				.stream()
-				.filter(s -> s.startsWith(prefix))
-				.sorted()
-				.collect(Collectors.toList()),
-			0,
-			argString.length()
-		);
+	public @NotNull ParameterCompleters getParameterCompleters() {
+		return new ParameterCompleters(Collections.singletonMap(
+			COMMAND_NAME_PARAM, (commandName, at) -> {
+				final String prefix = commandName.substring(0, at);
+				return new ReplacementOptions(
+					this.injector.getInstance(ProBKernel.class)
+						.getCommands()
+						.keySet()
+						.stream()
+						.filter(s -> s.startsWith(prefix))
+						.sorted()
+						.collect(Collectors.toList()),
+					0,
+					commandName.length()
+				);
+			}
+		));
 	}
 }
diff --git a/src/main/java/de/prob2/jupyter/commands/InitialiseCommand.java b/src/main/java/de/prob2/jupyter/commands/InitialiseCommand.java
index 278de524c558353836cbf1f76b618fe15d8f0164..27d64266e6c1de1cad5af3f5f021d16f0f637d1e 100644
--- a/src/main/java/de/prob2/jupyter/commands/InitialiseCommand.java
+++ b/src/main/java/de/prob2/jupyter/commands/InitialiseCommand.java
@@ -13,12 +13,12 @@ import de.prob.statespace.Transition;
 import de.prob2.jupyter.Command;
 import de.prob2.jupyter.CommandUtils;
 import de.prob2.jupyter.Parameter;
+import de.prob2.jupyter.ParameterCompleters;
 import de.prob2.jupyter.ParameterInspectors;
 import de.prob2.jupyter.Parameters;
 import de.prob2.jupyter.ParsedArguments;
 import de.prob2.jupyter.ProBKernel;
 
-import io.github.spencerpark.jupyter.kernel.ReplacementOptions;
 import io.github.spencerpark.jupyter.kernel.display.DisplayData;
 
 import org.jetbrains.annotations.NotNull;
@@ -87,7 +87,9 @@ public final class InitialiseCommand implements Command {
 	}
 	
 	@Override
-	public @NotNull ReplacementOptions complete(final @NotNull String argString, final int at) {
-		return CommandUtils.completeInBExpression(this.animationSelector.getCurrentTrace(), argString, at);
+	public @NotNull ParameterCompleters getParameterCompleters() {
+		return new ParameterCompleters(Collections.singletonMap(
+			PREDICATE_PARAM, CommandUtils.bExpressionCompleter(this.animationSelector.getCurrentTrace())
+		));
 	}
 }
diff --git a/src/main/java/de/prob2/jupyter/commands/LetCommand.java b/src/main/java/de/prob2/jupyter/commands/LetCommand.java
index d77fa45c58f0bada4e3aaf994f0ed407c3128d81..dbe689bb3bd2c0e74e588f4692c53ff0197a6d9a 100644
--- a/src/main/java/de/prob2/jupyter/commands/LetCommand.java
+++ b/src/main/java/de/prob2/jupyter/commands/LetCommand.java
@@ -12,16 +12,15 @@ import de.prob.statespace.AnimationSelector;
 import de.prob2.jupyter.Command;
 import de.prob2.jupyter.CommandUtils;
 import de.prob2.jupyter.Parameter;
+import de.prob2.jupyter.ParameterCompleters;
 import de.prob2.jupyter.ParameterInspectors;
 import de.prob2.jupyter.Parameters;
 import de.prob2.jupyter.ParsedArguments;
 import de.prob2.jupyter.ProBKernel;
 
-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 LetCommand implements Command {
 	private static final @NotNull Parameter.RequiredSingle NAME_PARAM = Parameter.required("name");
@@ -83,8 +82,8 @@ public final class LetCommand implements Command {
 	}
 	
 	@Override
-	public @Nullable ReplacementOptions complete(final @NotNull String argString, final int at) {
+	public @NotNull ParameterCompleters getParameterCompleters() {
 		// TODO
-		return null;
+		return ParameterCompleters.NONE;
 	}
 }
diff --git a/src/main/java/de/prob2/jupyter/commands/LoadCellCommand.java b/src/main/java/de/prob2/jupyter/commands/LoadCellCommand.java
index 92a416b8e7553e94d1ebfffafafc66ff246c4275..e9dbafd0d01858a6b633b6097745f5793b9ef679 100644
--- a/src/main/java/de/prob2/jupyter/commands/LoadCellCommand.java
+++ b/src/main/java/de/prob2/jupyter/commands/LoadCellCommand.java
@@ -14,16 +14,15 @@ import de.prob.statespace.Trace;
 import de.prob2.jupyter.Command;
 import de.prob2.jupyter.CommandUtils;
 import de.prob2.jupyter.Parameter;
+import de.prob2.jupyter.ParameterCompleters;
 import de.prob2.jupyter.ParameterInspectors;
 import de.prob2.jupyter.Parameters;
 import de.prob2.jupyter.ParsedArguments;
 import de.prob2.jupyter.ProBKernel;
 
-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 LoadCellCommand implements Command {
 	private static final @NotNull Parameter.Multiple PREFS_PARAM = Parameter.optionalMultiple("prefs");
@@ -94,14 +93,10 @@ public final class LoadCellCommand implements Command {
 	}
 	
 	@Override
-	public @Nullable ReplacementOptions complete(final @NotNull String argString, final int at) {
-		final int newlinePos = argString.indexOf('\n');
-		if (newlinePos == -1 || at < newlinePos) {
-			// Cursor is on the first line, provide preference name completions.
-			return CommandUtils.completeInPreferences(this.animationSelector.getCurrentTrace(), argString, at);
-		} else {
-			// Cursor is in the body, provide B completions.
-			return CommandUtils.completeInBExpression(this.animationSelector.getCurrentTrace(), argString, at);
-		}
+	public @NotNull ParameterCompleters getParameterCompleters() {
+		return new ParameterCompleters(ImmutableMap.of(
+			PREFS_PARAM, CommandUtils.preferencesCompleter(this.animationSelector.getCurrentTrace()),
+			CODE_PARAM, 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 123d0778629b4b23e6c63d1905c71c9c46e1ff46..48f5e62ba9aa93db736ef3bed0af916490786564 100644
--- a/src/main/java/de/prob2/jupyter/commands/LoadFileCommand.java
+++ b/src/main/java/de/prob2/jupyter/commands/LoadFileCommand.java
@@ -23,6 +23,7 @@ import de.prob.statespace.Trace;
 import de.prob2.jupyter.Command;
 import de.prob2.jupyter.CommandUtils;
 import de.prob2.jupyter.Parameter;
+import de.prob2.jupyter.ParameterCompleters;
 import de.prob2.jupyter.ParameterInspectors;
 import de.prob2.jupyter.Parameters;
 import de.prob2.jupyter.ParsedArguments;
@@ -33,7 +34,6 @@ 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;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -120,11 +120,10 @@ public final class LoadFileCommand implements Command {
 	}
 	
 	@Override
-	public @Nullable ReplacementOptions complete(final @NotNull String argString, final int at) {
-		return CommandUtils.completeArgs(
-			argString, at,
-			(filename, at0) -> {
-				final String prefix = filename.substring(0, at0);
+	public @NotNull ParameterCompleters getParameterCompleters() {
+		return new ParameterCompleters(ImmutableMap.of(
+			FILE_NAME_PARAM, (filename, at) -> {
+				final String prefix = filename.substring(0, at);
 				final List<String> fileNames;
 				try (final Stream<Path> list = Files.list(Paths.get(""))) {
 					fileNames = list
@@ -146,7 +145,7 @@ public final class LoadFileCommand implements Command {
 				}
 				return new ReplacementOptions(fileNames, 0, filename.length());
 			},
-			CommandUtils.preferencesCompleter(this.animationSelector.getCurrentTrace())
-		);
+			PREFS_PARAM, CommandUtils.preferencesCompleter(this.animationSelector.getCurrentTrace())
+		));
 	}
 }
diff --git a/src/main/java/de/prob2/jupyter/commands/ModelCheckCommand.java b/src/main/java/de/prob2/jupyter/commands/ModelCheckCommand.java
index 65e1d91d0f2d5a029a977cdefcd8c15f7bad8ef8..9e2280dd1bc22732f1ec81481eef2d6cc1663a25 100644
--- a/src/main/java/de/prob2/jupyter/commands/ModelCheckCommand.java
+++ b/src/main/java/de/prob2/jupyter/commands/ModelCheckCommand.java
@@ -16,6 +16,7 @@ import de.prob.check.StateSpaceStats;
 import de.prob.statespace.AnimationSelector;
 import de.prob.statespace.StateSpace;
 import de.prob2.jupyter.Command;
+import de.prob2.jupyter.ParameterCompleters;
 import de.prob2.jupyter.ParameterInspectors;
 import de.prob2.jupyter.Parameters;
 import de.prob2.jupyter.ParsedArguments;
@@ -23,7 +24,6 @@ import de.prob2.jupyter.ProBKernel;
 import de.prob2.jupyter.UserErrorException;
 
 import io.github.spencerpark.jupyter.kernel.JupyterIO;
-import io.github.spencerpark.jupyter.kernel.ReplacementOptions;
 import io.github.spencerpark.jupyter.kernel.display.DisplayData;
 
 import org.jetbrains.annotations.NotNull;
@@ -134,7 +134,7 @@ public final class ModelCheckCommand implements Command {
 	}
 	
 	@Override
-	public @Nullable ReplacementOptions complete(final @NotNull String argString, final int at) {
-		return null;
+	public @NotNull ParameterCompleters getParameterCompleters() {
+		return ParameterCompleters.NONE;
 	}
 }
diff --git a/src/main/java/de/prob2/jupyter/commands/PrefCommand.java b/src/main/java/de/prob2/jupyter/commands/PrefCommand.java
index da0fe8a0c3722d919377cbbf8eec1299f73c0a36..7132c9323035bfccd853ba3e9c0649e522c6ccb3 100644
--- a/src/main/java/de/prob2/jupyter/commands/PrefCommand.java
+++ b/src/main/java/de/prob2/jupyter/commands/PrefCommand.java
@@ -15,16 +15,15 @@ import de.prob.statespace.AnimationSelector;
 import de.prob2.jupyter.Command;
 import de.prob2.jupyter.CommandUtils;
 import de.prob2.jupyter.Parameter;
+import de.prob2.jupyter.ParameterCompleters;
 import de.prob2.jupyter.ParameterInspectors;
 import de.prob2.jupyter.Parameters;
 import de.prob2.jupyter.ParsedArguments;
 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 PrefCommand implements Command {
 	private static final @NotNull Parameter.Multiple PREFS_PARAM = Parameter.optionalMultiple("prefs");
@@ -118,7 +117,9 @@ public final class PrefCommand implements Command {
 	}
 	
 	@Override
-	public @Nullable ReplacementOptions complete(final @NotNull String argString, final int at) {
-		return CommandUtils.completeInPreferences(this.animationSelector.getCurrentTrace(), argString, at);
+	public @NotNull ParameterCompleters getParameterCompleters() {
+		return new ParameterCompleters(Collections.singletonMap(
+			PREFS_PARAM, CommandUtils.preferencesCompleter(this.animationSelector.getCurrentTrace())
+		));
 	}
 }
diff --git a/src/main/java/de/prob2/jupyter/commands/PrettyPrintCommand.java b/src/main/java/de/prob2/jupyter/commands/PrettyPrintCommand.java
index 0cf7bbb3668c50c0095cf87d25d664bcdf24a607..45b6493e59e97b4dfd12f89efb46730ea7f033af 100644
--- a/src/main/java/de/prob2/jupyter/commands/PrettyPrintCommand.java
+++ b/src/main/java/de/prob2/jupyter/commands/PrettyPrintCommand.java
@@ -11,11 +11,11 @@ import de.prob.statespace.AnimationSelector;
 import de.prob2.jupyter.Command;
 import de.prob2.jupyter.CommandUtils;
 import de.prob2.jupyter.Parameter;
+import de.prob2.jupyter.ParameterCompleters;
 import de.prob2.jupyter.ParameterInspectors;
 import de.prob2.jupyter.Parameters;
 import de.prob2.jupyter.ParsedArguments;
 
-import io.github.spencerpark.jupyter.kernel.ReplacementOptions;
 import io.github.spencerpark.jupyter.kernel.display.DisplayData;
 
 import org.jetbrains.annotations.NotNull;
@@ -82,7 +82,9 @@ public final class PrettyPrintCommand implements Command {
 	}
 	
 	@Override
-	public @NotNull ReplacementOptions complete(final @NotNull String argString, final int at) {
-		return CommandUtils.completeInBExpression(this.animationSelector.getCurrentTrace(), argString, at);
+	public @NotNull ParameterCompleters getParameterCompleters() {
+		return new ParameterCompleters(Collections.singletonMap(
+			PREDICATE_PARAM, CommandUtils.bExpressionCompleter(this.animationSelector.getCurrentTrace())
+		));
 	}
 }
diff --git a/src/main/java/de/prob2/jupyter/commands/RenderCommand.java b/src/main/java/de/prob2/jupyter/commands/RenderCommand.java
index 7e1d4484399ec1b60aad4f07bc79b5d2db2e3759..f52c2190ce0c367ad6261176cc6d0d614eb99436 100644
--- a/src/main/java/de/prob2/jupyter/commands/RenderCommand.java
+++ b/src/main/java/de/prob2/jupyter/commands/RenderCommand.java
@@ -6,15 +6,14 @@ import com.google.inject.Inject;
 
 import de.prob2.jupyter.Command;
 import de.prob2.jupyter.Parameter;
+import de.prob2.jupyter.ParameterCompleters;
 import de.prob2.jupyter.ParameterInspectors;
 import de.prob2.jupyter.Parameters;
 import de.prob2.jupyter.ParsedArguments;
 
-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 RenderCommand implements Command {
 	private static final @NotNull Parameter.RequiredSingle MIME_TYPE_PARAM = Parameter.required("mimeType");
@@ -66,7 +65,7 @@ public final class RenderCommand implements Command {
 	}
 	
 	@Override
-	public @Nullable ReplacementOptions complete(final @NotNull String argString, final int at) {
-		return null;
+	public @NotNull ParameterCompleters getParameterCompleters() {
+		return ParameterCompleters.NONE;
 	}
 }
diff --git a/src/main/java/de/prob2/jupyter/commands/ShowCommand.java b/src/main/java/de/prob2/jupyter/commands/ShowCommand.java
index 9621989cb875b74105bfe0b529f7b91f3c17f83a..b1ef1842ea10bf826d5f56654b153b70da6daf33 100644
--- a/src/main/java/de/prob2/jupyter/commands/ShowCommand.java
+++ b/src/main/java/de/prob2/jupyter/commands/ShowCommand.java
@@ -16,17 +16,16 @@ import de.prob.animator.domainobjects.AnimationMatrixEntry;
 import de.prob.statespace.AnimationSelector;
 import de.prob.statespace.Trace;
 import de.prob2.jupyter.Command;
+import de.prob2.jupyter.ParameterCompleters;
 import de.prob2.jupyter.ParameterInspectors;
 import de.prob2.jupyter.Parameters;
 import de.prob2.jupyter.ParsedArguments;
 import de.prob2.jupyter.ProBKernel;
 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 ShowCommand implements Command {
 	private final @NotNull AnimationSelector animationSelector;
@@ -158,7 +157,7 @@ public final class ShowCommand implements Command {
 	}
 	
 	@Override
-	public @Nullable ReplacementOptions complete(final @NotNull String argString, final int at) {
-		return null;
+	public @NotNull ParameterCompleters getParameterCompleters() {
+		return ParameterCompleters.NONE;
 	}
 }
diff --git a/src/main/java/de/prob2/jupyter/commands/SolveCommand.java b/src/main/java/de/prob2/jupyter/commands/SolveCommand.java
index 3b29bc922580bd8cff545f8bf0039b68a555d4ae..8d01043072bc16780849a0f9d587cae5408c2ce7 100644
--- a/src/main/java/de/prob2/jupyter/commands/SolveCommand.java
+++ b/src/main/java/de/prob2/jupyter/commands/SolveCommand.java
@@ -17,6 +17,7 @@ import de.prob.statespace.Trace;
 import de.prob2.jupyter.Command;
 import de.prob2.jupyter.CommandUtils;
 import de.prob2.jupyter.Parameter;
+import de.prob2.jupyter.ParameterCompleters;
 import de.prob2.jupyter.ParameterInspectors;
 import de.prob2.jupyter.Parameters;
 import de.prob2.jupyter.ParsedArguments;
@@ -27,7 +28,6 @@ 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 Parameter.RequiredSingle SOLVER_PARAM = Parameter.required("solver");
@@ -102,18 +102,17 @@ public final class SolveCommand implements Command {
 	}
 	
 	@Override
-	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);
+	public @NotNull ParameterCompleters getParameterCompleters() {
+		return new ParameterCompleters(ImmutableMap.of(
+			SOLVER_PARAM, (solverName, at) -> {
+				final String prefix = solverName.substring(0, at);
 				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())
-		);
+			PREDICATE_PARAM, CommandUtils.bExpressionCompleter(this.animationSelector.getCurrentTrace())
+		));
 	}
 }
diff --git a/src/main/java/de/prob2/jupyter/commands/StatsCommand.java b/src/main/java/de/prob2/jupyter/commands/StatsCommand.java
index abe5917f948884b64b20bec2d3655fc7d70c2709..54ffdd4ac1b6cc9dd6f9ed68f08e17a6868ecaf6 100644
--- a/src/main/java/de/prob2/jupyter/commands/StatsCommand.java
+++ b/src/main/java/de/prob2/jupyter/commands/StatsCommand.java
@@ -6,15 +6,14 @@ import de.prob.animator.command.ComputeStateSpaceStatsCommand;
 import de.prob.check.StateSpaceStats;
 import de.prob.statespace.AnimationSelector;
 import de.prob2.jupyter.Command;
+import de.prob2.jupyter.ParameterCompleters;
 import de.prob2.jupyter.ParameterInspectors;
 import de.prob2.jupyter.Parameters;
 import de.prob2.jupyter.ParsedArguments;
 
-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 StatsCommand implements Command {
 	private final @NotNull AnimationSelector animationSelector;
@@ -87,7 +86,7 @@ public final class StatsCommand implements Command {
 	}
 	
 	@Override
-	public @Nullable ReplacementOptions complete(final @NotNull String argString, final int at) {
-		return null;
+	public @NotNull ParameterCompleters getParameterCompleters() {
+		return ParameterCompleters.NONE;
 	}
 }
diff --git a/src/main/java/de/prob2/jupyter/commands/TableCommand.java b/src/main/java/de/prob2/jupyter/commands/TableCommand.java
index 0e09bfa11fb7ffbb22b60b0d1309658819058ebe..ea1049fffb22216216172a07ed937d11e7f8b19e 100644
--- a/src/main/java/de/prob2/jupyter/commands/TableCommand.java
+++ b/src/main/java/de/prob2/jupyter/commands/TableCommand.java
@@ -19,12 +19,12 @@ import de.prob.unicode.UnicodeTranslator;
 import de.prob2.jupyter.Command;
 import de.prob2.jupyter.CommandUtils;
 import de.prob2.jupyter.Parameter;
+import de.prob2.jupyter.ParameterCompleters;
 import de.prob2.jupyter.ParameterInspectors;
 import de.prob2.jupyter.Parameters;
 import de.prob2.jupyter.ParsedArguments;
 import de.prob2.jupyter.ProBKernel;
 
-import io.github.spencerpark.jupyter.kernel.ReplacementOptions;
 import io.github.spencerpark.jupyter.kernel.display.DisplayData;
 
 import org.jetbrains.annotations.NotNull;
@@ -118,7 +118,9 @@ public final class TableCommand implements Command {
 	}
 	
 	@Override
-	public @NotNull ReplacementOptions complete(final @NotNull String argString, final int at) {
-		return CommandUtils.completeInBExpression(this.animationSelector.getCurrentTrace(), argString, at);
+	public @NotNull ParameterCompleters getParameterCompleters() {
+		return new ParameterCompleters(Collections.singletonMap(
+			EXPRESSION_PARAM, CommandUtils.bExpressionCompleter(this.animationSelector.getCurrentTrace())
+		));
 	}
 }
diff --git a/src/main/java/de/prob2/jupyter/commands/TimeCommand.java b/src/main/java/de/prob2/jupyter/commands/TimeCommand.java
index 0f653b2d769fa665c4727bae5c7d566a1c6d005c..a7cf5c95c3b774178eedcf6f2e16411f2254d69b 100644
--- a/src/main/java/de/prob2/jupyter/commands/TimeCommand.java
+++ b/src/main/java/de/prob2/jupyter/commands/TimeCommand.java
@@ -10,12 +10,12 @@ import com.google.inject.Injector;
 
 import de.prob2.jupyter.Command;
 import de.prob2.jupyter.Parameter;
+import de.prob2.jupyter.ParameterCompleters;
 import de.prob2.jupyter.ParameterInspectors;
 import de.prob2.jupyter.Parameters;
 import de.prob2.jupyter.ParsedArguments;
 import de.prob2.jupyter.ProBKernel;
 
-import io.github.spencerpark.jupyter.kernel.ReplacementOptions;
 import io.github.spencerpark.jupyter.kernel.display.DisplayData;
 
 import org.jetbrains.annotations.NotNull;
@@ -81,7 +81,9 @@ public final class TimeCommand implements Command {
 	}
 	
 	@Override
-	public @Nullable ReplacementOptions complete(final @NotNull String argString, final int at) {
-		return this.injector.getInstance(ProBKernel.class).complete(argString, at);
+	public @NotNull ParameterCompleters getParameterCompleters() {
+		return new ParameterCompleters(Collections.singletonMap(
+			COMMAND_AND_ARGS_PARAM, (argString, at) -> this.injector.getInstance(ProBKernel.class).complete(argString, at)
+		));
 	}
 }
diff --git a/src/main/java/de/prob2/jupyter/commands/TraceCommand.java b/src/main/java/de/prob2/jupyter/commands/TraceCommand.java
index 6073818ea2d62d945a7ecde450a2943b5fd3141b..82db34f6f0725188020ad9711fcd4b3829b8935a 100644
--- a/src/main/java/de/prob2/jupyter/commands/TraceCommand.java
+++ b/src/main/java/de/prob2/jupyter/commands/TraceCommand.java
@@ -8,15 +8,14 @@ import de.prob.statespace.AnimationSelector;
 import de.prob.statespace.Trace;
 import de.prob.statespace.Transition;
 import de.prob2.jupyter.Command;
+import de.prob2.jupyter.ParameterCompleters;
 import de.prob2.jupyter.ParameterInspectors;
 import de.prob2.jupyter.Parameters;
 import de.prob2.jupyter.ParsedArguments;
 
-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;
@@ -95,7 +94,7 @@ public final class TraceCommand implements Command {
 	}
 	
 	@Override
-	public @Nullable ReplacementOptions complete(final @NotNull String argString, final int at) {
-		return null;
+	public @NotNull ParameterCompleters getParameterCompleters() {
+		return ParameterCompleters.NONE;
 	}
 }
diff --git a/src/main/java/de/prob2/jupyter/commands/TypeCommand.java b/src/main/java/de/prob2/jupyter/commands/TypeCommand.java
index 61c18438baf79c5c8f8fe4453eec70d7c527348f..09e466fb940bc9121cfa13e698634b83ad842678 100644
--- a/src/main/java/de/prob2/jupyter/commands/TypeCommand.java
+++ b/src/main/java/de/prob2/jupyter/commands/TypeCommand.java
@@ -14,12 +14,12 @@ import de.prob.statespace.Trace;
 import de.prob2.jupyter.Command;
 import de.prob2.jupyter.CommandUtils;
 import de.prob2.jupyter.Parameter;
+import de.prob2.jupyter.ParameterCompleters;
 import de.prob2.jupyter.ParameterInspectors;
 import de.prob2.jupyter.Parameters;
 import de.prob2.jupyter.ParsedArguments;
 import de.prob2.jupyter.ProBKernel;
 
-import io.github.spencerpark.jupyter.kernel.ReplacementOptions;
 import io.github.spencerpark.jupyter.kernel.display.DisplayData;
 
 import org.jetbrains.annotations.NotNull;
@@ -84,7 +84,9 @@ public final class TypeCommand implements Command {
 	}
 	
 	@Override
-	public @NotNull ReplacementOptions complete(final @NotNull String argString, final int at) {
-		return CommandUtils.completeInBExpression(this.animationSelector.getCurrentTrace(), argString, at);
+	public @NotNull ParameterCompleters getParameterCompleters() {
+		return new ParameterCompleters(Collections.singletonMap(
+			FORMULA_PARAM, CommandUtils.bExpressionCompleter(this.animationSelector.getCurrentTrace())
+		));
 	}
 }
diff --git a/src/main/java/de/prob2/jupyter/commands/UnletCommand.java b/src/main/java/de/prob2/jupyter/commands/UnletCommand.java
index 2d6960dba561fdfb97ebae9c99d6f6f7c9fdb741..67f66445a7358a37f72b79466530cdb9169fe4f4 100644
--- a/src/main/java/de/prob2/jupyter/commands/UnletCommand.java
+++ b/src/main/java/de/prob2/jupyter/commands/UnletCommand.java
@@ -8,13 +8,13 @@ import com.google.inject.Injector;
 
 import de.prob2.jupyter.Command;
 import de.prob2.jupyter.Parameter;
+import de.prob2.jupyter.ParameterCompleters;
 import de.prob2.jupyter.ParameterInspectors;
 import de.prob2.jupyter.Parameters;
 import de.prob2.jupyter.ParsedArguments;
 import de.prob2.jupyter.ProBKernel;
 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;
@@ -75,8 +75,8 @@ public final class UnletCommand implements Command {
 	}
 	
 	@Override
-	public @Nullable ReplacementOptions complete(final @NotNull String argString, final int at) {
+	public @NotNull ParameterCompleters getParameterCompleters() {
 		// TODO
-		return null;
+		return ParameterCompleters.NONE;
 	}
 }
diff --git a/src/main/java/de/prob2/jupyter/commands/VersionCommand.java b/src/main/java/de/prob2/jupyter/commands/VersionCommand.java
index 147296df9ec5aa87039bed947d9a51bbfeec4343..48bf67aee7cd2aedd74a49a16e68633fa5a69f78 100644
--- a/src/main/java/de/prob2/jupyter/commands/VersionCommand.java
+++ b/src/main/java/de/prob2/jupyter/commands/VersionCommand.java
@@ -7,16 +7,15 @@ import de.prob.Main;
 import de.prob.animator.command.GetVersionCommand;
 import de.prob.statespace.AnimationSelector;
 import de.prob2.jupyter.Command;
+import de.prob2.jupyter.ParameterCompleters;
 import de.prob2.jupyter.ParameterInspectors;
 import de.prob2.jupyter.Parameters;
 import de.prob2.jupyter.ParsedArguments;
 import de.prob2.jupyter.ProBKernel;
 
-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 VersionCommand implements Command {
 	private final @NotNull AnimationSelector animationSelector;
@@ -83,7 +82,7 @@ public final class VersionCommand implements Command {
 	}
 	
 	@Override
-	public @Nullable ReplacementOptions complete(final @NotNull String argString, final int at) {
-		return null;
+	public @NotNull ParameterCompleters getParameterCompleters() {
+		return ParameterCompleters.NONE;
 	}
 }