aboutsummaryrefslogtreecommitdiff
path: root/core/src/main/java/coffee/liz/abstractionengine/app/ui
diff options
context:
space:
mode:
authorElizabeth Hunt <me@liz.coffee>2026-02-21 12:29:54 -0800
committerElizabeth Hunt <me@liz.coffee>2026-02-21 12:29:54 -0800
commit170f5c496054c9c5c2df9b8a1c5c8df7643ea004 (patch)
tree1ed217130939a04234761d54ba2ec607866f51ad /core/src/main/java/coffee/liz/abstractionengine/app/ui
parentd6a294e14604aecd9046cbc19dc1837ca694fbb0 (diff)
downloadthe-abstraction-engine-java-main.tar.gz
the-abstraction-engine-java-main.zip
Diffstat (limited to 'core/src/main/java/coffee/liz/abstractionengine/app/ui')
-rw-r--r--core/src/main/java/coffee/liz/abstractionengine/app/ui/editor/LambdaEditor.java192
-rw-r--r--core/src/main/java/coffee/liz/abstractionengine/app/ui/editor/vim/VimEngine.java31
-rw-r--r--core/src/main/java/coffee/liz/abstractionengine/app/ui/editor/vim/context/KeyContext.java10
-rw-r--r--core/src/main/java/coffee/liz/abstractionengine/app/ui/editor/vim/handler/CommandModeHandler.java38
-rw-r--r--core/src/main/java/coffee/liz/abstractionengine/app/ui/editor/vim/handler/NormalModeHandler.java2
-rw-r--r--core/src/main/java/coffee/liz/abstractionengine/app/ui/editor/vim/state/CommandState.java7
-rw-r--r--core/src/main/java/coffee/liz/abstractionengine/app/ui/editor/vim/state/ModeState.java2
-rw-r--r--core/src/main/java/coffee/liz/abstractionengine/app/ui/editor/vim/status/StatusFormatter.java2
8 files changed, 249 insertions, 35 deletions
diff --git a/core/src/main/java/coffee/liz/abstractionengine/app/ui/editor/LambdaEditor.java b/core/src/main/java/coffee/liz/abstractionengine/app/ui/editor/LambdaEditor.java
index 11a4b0e..1affd43 100644
--- a/core/src/main/java/coffee/liz/abstractionengine/app/ui/editor/LambdaEditor.java
+++ b/core/src/main/java/coffee/liz/abstractionengine/app/ui/editor/LambdaEditor.java
@@ -3,78 +3,214 @@ package coffee.liz.abstractionengine.app.ui.editor;
import coffee.liz.abstractionengine.app.Theme;
import coffee.liz.abstractionengine.app.ui.editor.vim.VimEngine;
import coffee.liz.abstractionengine.app.utils.FontHelper;
+import coffee.liz.lambda.LambdaDriver;
+import coffee.liz.lambda.ast.LambdaProgram;
+import coffee.liz.lambda.ast.SourceCode;
+import coffee.liz.lambda.format.Formatter;
import com.badlogic.gdx.graphics.g2d.BitmapFont;
import com.badlogic.gdx.graphics.g2d.GlyphLayout;
import com.badlogic.gdx.scenes.scene2d.ui.ScrollPane;
+import com.badlogic.gdx.scenes.scene2d.utils.ChangeListener;
import com.badlogic.gdx.utils.Align;
import com.kotcrab.vis.ui.VisUI;
import com.kotcrab.vis.ui.util.TableUtils;
import com.kotcrab.vis.ui.util.highlight.Highlighter;
import com.kotcrab.vis.ui.widget.VisLabel;
+import com.kotcrab.vis.ui.widget.VisSelectBox;
+import com.kotcrab.vis.ui.widget.VisTable;
+import com.kotcrab.vis.ui.widget.VisTextButton;
import com.kotcrab.vis.ui.widget.VisTextField.VisTextFieldStyle;
import com.kotcrab.vis.ui.widget.VisWindow;
+import lombok.Getter;
+
+import java.util.function.Consumer;
public class LambdaEditor extends VisWindow {
private static final int FONT_SIZE = 15;
private static final float WINDOW_ALPHA = 0.92f;
- private static final String DEFAULT_TEXT = "-- Church numerals\n" + "let zero = λf.λx.x;\n"
- + "let one = λf.λx.f x;\n" + "let succ = λn.λf.λx.f (n f x);\n" + "let add = λm.λn.λf.λx.m f (n f x);\n"
- + "\n" + "succ (add one zero)\n";
-
private final BitmapFont font;
+ private final Consumer<SourceCode> onSave;
+ private final boolean vimMode;
+ private final VisLabel statusLabel;
+ private final VisSelectBox<SourceCode.Syntax> syntaxSwitch;
+ @Getter
+ private final EditorTextArea editorTextArea;
+ private SourceCode.Syntax syntax;
+ private boolean updatingSyntaxSwitch = false;
- public LambdaEditor(final boolean vimMode) {
+ public LambdaEditor(final boolean vimMode, final SourceCode initialSource, final Consumer<SourceCode> onSave) {
super("λ-editor");
+ this.vimMode = vimMode;
+ this.onSave = onSave != null ? onSave : _ -> { };
+ this.syntax = syntaxOf(initialSource);
+ this.font = FontHelper.initFont(FONT_SIZE, FontHelper.MONO, 1f);
+ this.statusLabel = new VisLabel(vimMode ? "-- NORMAL --" : "-- INSERT --");
+ this.syntaxSwitch = new VisSelectBox<>();
TableUtils.setSpacingDefaults(this);
columnDefaults(0).left();
- this.font = FontHelper.initFont(FONT_SIZE, FontHelper.MONO, 1f);
-
setResizable(true);
addCloseButton();
- addEditor(vimMode);
- setSize(400, 300);
- setColor(1, 1, 1, WINDOW_ALPHA);
- centerWindow();
- }
-
- private void addEditor(final boolean vimMode) {
final VisTextFieldStyle style = new VisTextFieldStyle(VisUI.getSkin().get("textArea", VisTextFieldStyle.class));
style.font = font;
final GlyphLayout layout = new GlyphLayout(font, "000 ");
final float gutterWidth = layout.width;
- final EditorTextArea textArea = new EditorTextArea(DEFAULT_TEXT, style, gutterWidth);
- textArea.setHighlighter(createHighlighter());
+ this.editorTextArea = new EditorTextArea(sourceOf(initialSource), style, gutterWidth);
+ editorTextArea.setHighlighter(createHighlighter(syntax));
- final ScrollPane scrollPane = textArea.createCompatibleScrollPane();
- scrollPane.setFadeScrollBars(false);
+ bindInput(vimMode);
+ buildLayout();
+ bindSyntaxSwitch();
+ syntaxSwitch.setSelected(syntax);
- final VisLabel statusLabel = new VisLabel(vimMode ? "-- NORMAL --" : "-- INSERT --");
- statusLabel.setAlignment(Align.left);
+ setSize(560, 380);
+ setColor(1, 1, 1, WINDOW_ALPHA);
+ }
- if (vimMode) {
- final VimEngine controller = new VimEngine(textArea, statusLabel::setText);
- textArea.setInputStrategy(controller);
- statusLabel.setText(controller.getStatusText());
+ private void bindInput(final boolean vimEnabled) {
+ statusLabel.setAlignment(Align.left);
+ if (!vimEnabled) {
+ return;
}
+ final VimEngine controller = new VimEngine(editorTextArea, statusLabel::setText, this::executeExCommand);
+ editorTextArea.setInputStrategy(controller);
+ statusLabel.setText(controller.getStatusText());
+ }
+
+ private void buildLayout() {
+ final ScrollPane scrollPane = editorTextArea.createCompatibleScrollPane();
+ scrollPane.setFadeScrollBars(false);
+
+ final VisTextButton saveButton = new VisTextButton(vimMode ? "Save (:w)" : "Save");
+ saveButton.addListener(new ChangeListener() {
+ @Override
+ public void changed(final ChangeEvent event, final com.badlogic.gdx.scenes.scene2d.Actor actor) {
+ saveCurrentTerm();
+ }
+ });
+
+ final VisTable controls = new VisTable();
+ controls.add(statusLabel).growX().left();
+ controls.add(syntaxSwitch).padLeft(8f);
+ controls.add(saveButton).padLeft(8f);
add(scrollPane).grow().row();
- add(statusLabel).growX().padTop(6f);
+ add(controls).growX().padTop(6f);
+ }
+
+ private void bindSyntaxSwitch() {
+ syntaxSwitch.setItems(SourceCode.Syntax.ARROW, SourceCode.Syntax.LAMBDA);
+ syntaxSwitch.addListener(new ChangeListener() {
+ @Override
+ public void changed(final ChangeEvent event, final com.badlogic.gdx.scenes.scene2d.Actor actor) {
+ if (updatingSyntaxSwitch) {
+ return;
+ }
+ applySyntaxSwitch(syntaxSwitch.getSelected());
+ }
+ });
+ }
+
+ private void applySyntaxSwitch(final SourceCode.Syntax nextSyntax) {
+ if (nextSyntax == syntax) {
+ return;
+ }
+ try {
+ final LambdaProgram program = LambdaDriver.parse(sourceFromEditor());
+ final String formatted = Formatter.emit(program, nextSyntax);
+ syntax = nextSyntax;
+ editorTextArea.setText(formatted);
+ editorTextArea.syncCursorState();
+ editorTextArea.setHighlighter(createHighlighter(nextSyntax));
+ setStatus("Converted to " + nextSyntax.name().toLowerCase() + " syntax");
+ } catch (final RuntimeException parseException) {
+ setStatus("Cannot switch syntax: parse error");
+ updatingSyntaxSwitch = true;
+ syntaxSwitch.setSelected(syntax);
+ updatingSyntaxSwitch = false;
+ }
+ }
+
+ private void executeExCommand(final String command) {
+ final String normalized = command == null ? "" : command.trim();
+ switch (normalized) {
+ case "w" -> saveCurrentTerm();
+ case "wq" -> {
+ if (saveCurrentTerm()) {
+ remove();
+ }
+ }
+ case "q" -> remove();
+ default -> setStatus("Unknown command :" + normalized);
+ }
+ }
+
+ private boolean saveCurrentTerm() {
+ try {
+ final LambdaProgram program = LambdaDriver.parse(sourceFromEditor());
+ final String formatted = Formatter.emit(program, syntax);
+ final SourceCode updatedTerm = sourceCodeForSyntax(formatted, syntax);
+ editorTextArea.setText(formatted);
+ editorTextArea.syncCursorState();
+ onSave.accept(updatedTerm);
+ setStatus("Saved");
+ return true;
+ } catch (final RuntimeException parseException) {
+ setStatus("Save failed: parse error");
+ return false;
+ }
+ }
+
+ private SourceCode sourceFromEditor() {
+ return sourceCodeForSyntax(editorTextArea.getText(), syntax);
}
- private static Highlighter createHighlighter() {
+ private static SourceCode sourceCodeForSyntax(final String source, final SourceCode.Syntax syntax) {
+ return switch (syntax) {
+ case ARROW -> SourceCode.ofArrow(source);
+ case LAMBDA -> SourceCode.ofLambda(source);
+ };
+ }
+
+ private static SourceCode.Syntax syntaxOf(final SourceCode sourceCode) {
+ return switch (sourceCode) {
+ case SourceCode.Arrow ignored -> SourceCode.Syntax.ARROW;
+ case SourceCode.Lambda ignored -> SourceCode.Syntax.LAMBDA;
+ };
+ }
+
+ private static String sourceOf(final SourceCode sourceCode) {
+ return switch (sourceCode) {
+ case SourceCode.Arrow(String source) -> source;
+ case SourceCode.Lambda(String source) -> source;
+ };
+ }
+
+ private void setStatus(final String text) {
+ if (text == null || text.isBlank()) {
+ return;
+ }
+ statusLabel.setText(text);
+ }
+
+ private static Highlighter createHighlighter(final SourceCode.Syntax syntax) {
final Highlighter highlighter = new Highlighter();
highlighter.regex(Theme.MUTED, "--[^\\n]*");
highlighter.regex(Theme.PRIMARY, "\\blet\\b");
- highlighter.regex(Theme.SECONDARY, "[λ\\\\]");
- highlighter.regex(Theme.SECONDARY, "\\.");
highlighter.regex(Theme.BORDER_LIGHT, "[=;]");
highlighter.regex(Theme.BORDER_LIGHT, "[()]");
+ switch (syntax) {
+ case ARROW -> highlighter.regex(Theme.SECONDARY, "->");
+ case LAMBDA -> {
+ highlighter.regex(Theme.SECONDARY, "[λ\\\\]");
+ highlighter.regex(Theme.SECONDARY, "\\.");
+ }
+ }
return highlighter;
}
}
diff --git a/core/src/main/java/coffee/liz/abstractionengine/app/ui/editor/vim/VimEngine.java b/core/src/main/java/coffee/liz/abstractionengine/app/ui/editor/vim/VimEngine.java
index ab5f203..d2fc843 100644
--- a/core/src/main/java/coffee/liz/abstractionengine/app/ui/editor/vim/VimEngine.java
+++ b/core/src/main/java/coffee/liz/abstractionengine/app/ui/editor/vim/VimEngine.java
@@ -9,6 +9,7 @@ import coffee.liz.abstractionengine.app.ui.editor.RenderState;
import coffee.liz.abstractionengine.app.ui.editor.EditorTextArea;
import coffee.liz.abstractionengine.app.ui.editor.vim.buffer.TextAreaBuffer;
import coffee.liz.abstractionengine.app.ui.editor.vim.context.KeyContext;
+import coffee.liz.abstractionengine.app.ui.editor.vim.handler.CommandModeHandler;
import coffee.liz.abstractionengine.app.ui.editor.vim.handler.HandleResult;
import coffee.liz.abstractionengine.app.ui.editor.vim.handler.InsertModeHandler;
import coffee.liz.abstractionengine.app.ui.editor.vim.handler.ModeHandler;
@@ -21,6 +22,7 @@ import coffee.liz.abstractionengine.app.ui.editor.vim.model.UndoManager;
import coffee.liz.abstractionengine.app.ui.editor.vim.navigation.TextNavigation;
import coffee.liz.abstractionengine.app.ui.editor.vim.state.InsertState;
import coffee.liz.abstractionengine.app.ui.editor.vim.state.ModeState;
+import coffee.liz.abstractionengine.app.ui.editor.vim.state.CommandState;
import coffee.liz.abstractionengine.app.ui.editor.vim.state.NormalState;
import coffee.liz.abstractionengine.app.ui.editor.vim.state.SearchState;
import coffee.liz.abstractionengine.app.ui.editor.vim.state.VisualLineState;
@@ -39,38 +41,55 @@ public class VimEngine implements InputStrategy {
private final ModeHandler<VisualState> visualModeHandler;
private final ModeHandler<VisualLineState> visualLineModeHandler;
private final ModeHandler<SearchState> searchModeHandler;
+ private final ModeHandler<CommandState> commandModeHandler;
private ModeState state;
public VimEngine(final EditorTextArea textArea, final Consumer<String> statusSink, final VimConfig config) {
- this(new TextAreaBuffer(textArea), statusSink, config);
+ this(new TextAreaBuffer(textArea), statusSink, null, config);
+ }
+
+ public VimEngine(final EditorTextArea textArea, final Consumer<String> statusSink,
+ final Consumer<String> commandSink, final VimConfig config) {
+ this(new TextAreaBuffer(textArea), statusSink, commandSink, config);
+ }
+
+ public VimEngine(final EditorTextArea textArea, final Consumer<String> statusSink,
+ final Consumer<String> commandSink) {
+ this(new TextAreaBuffer(textArea), statusSink, commandSink, VimConfig.defaults());
}
public VimEngine(final EditorTextArea textArea, final Consumer<String> statusSink) {
- this(textArea, statusSink, VimConfig.defaults());
+ this(textArea, statusSink, null, VimConfig.defaults());
}
public VimEngine(final EditorTextArea textArea) {
- this(textArea, null, VimConfig.defaults());
+ this(textArea, null, null, VimConfig.defaults());
}
public VimEngine(final EditorBuffer buffer, final Consumer<String> statusSink, final VimConfig config) {
+ this(buffer, statusSink, null, config);
+ }
+
+ public VimEngine(final EditorBuffer buffer, final Consumer<String> statusSink, final Consumer<String> commandSink,
+ final VimConfig config) {
this.config = config != null ? config : VimConfig.defaults();
this.context = new KeyContext(buffer, statusSink, new TextNavigation(),
- new UndoManager(this.config.maxUndoStates()), new Register(), this.config);
+ new UndoManager(this.config.maxUndoStates()), new Register(), this.config, commandSink);
this.statusFormatter = new StatusFormatter();
this.normalModeHandler = new NormalModeHandler();
this.insertModeHandler = new InsertModeHandler();
this.visualModeHandler = new VisualModeHandler();
this.visualLineModeHandler = new VisualLineModeHandler();
this.searchModeHandler = new SearchModeHandler();
+ this.commandModeHandler = new CommandModeHandler();
this.state = NormalState.DEFAULT;
context.recordUndoState();
publishStatus();
}
public VimEngine(final EditorBuffer buffer, final Consumer<String> statusSink) {
- this(buffer, statusSink, VimConfig.defaults());
+ this(buffer, statusSink, null, VimConfig.defaults());
}
@Override
@@ -160,6 +179,7 @@ public class VimEngine implements InputStrategy {
case VisualState visualState -> visualModeHandler.onKeyDown(visualState, context);
case VisualLineState visualLineState -> visualLineModeHandler.onKeyDown(visualLineState, context);
case SearchState searchState -> searchModeHandler.onKeyDown(searchState, context);
+ case CommandState commandState -> commandModeHandler.onKeyDown(commandState, context);
};
}
@@ -171,6 +191,7 @@ public class VimEngine implements InputStrategy {
case VisualLineState visualLineState ->
visualLineModeHandler.onKeyTyped(visualLineState, character, context);
case SearchState searchState -> searchModeHandler.onKeyTyped(searchState, character, context);
+ case CommandState commandState -> commandModeHandler.onKeyTyped(commandState, character, context);
};
}
diff --git a/core/src/main/java/coffee/liz/abstractionengine/app/ui/editor/vim/context/KeyContext.java b/core/src/main/java/coffee/liz/abstractionengine/app/ui/editor/vim/context/KeyContext.java
index 285bbde..e269b58 100644
--- a/core/src/main/java/coffee/liz/abstractionengine/app/ui/editor/vim/context/KeyContext.java
+++ b/core/src/main/java/coffee/liz/abstractionengine/app/ui/editor/vim/context/KeyContext.java
@@ -33,6 +33,7 @@ public class KeyContext {
private final UndoManager undoManager;
private final Register register;
private final VimConfig config;
+ private final Consumer<String> commandExecutor;
@Getter
private String statusText;
@@ -48,13 +49,14 @@ public class KeyContext {
public KeyContext(final EditorBuffer editorBuffer, final Consumer<String> statusUpdater,
final TextNavigation navigation, final UndoManager undoManager, final Register register,
- final VimConfig config) {
+ final VimConfig config, final Consumer<String> commandExecutor) {
this.editorBuffer = editorBuffer;
this.statusUpdater = statusUpdater;
this.navigation = navigation;
this.undoManager = undoManager;
this.register = register;
this.config = config;
+ this.commandExecutor = commandExecutor;
this.statusText = "";
this.lastSearchQuery = "";
this.lastSearchDirection = SearchDirection.FORWARD;
@@ -66,6 +68,12 @@ public class KeyContext {
this.currentKeyEvent = new KeyEvent(Input.Keys.UNKNOWN, Set.of());
}
+ public void executeCommand(final String command) {
+ if (commandExecutor != null) {
+ commandExecutor.accept(command == null ? "" : command);
+ }
+ }
+
public EditorBuffer editorBuffer() {
return editorBuffer;
}
diff --git a/core/src/main/java/coffee/liz/abstractionengine/app/ui/editor/vim/handler/CommandModeHandler.java b/core/src/main/java/coffee/liz/abstractionengine/app/ui/editor/vim/handler/CommandModeHandler.java
new file mode 100644
index 0000000..fea2f8a
--- /dev/null
+++ b/core/src/main/java/coffee/liz/abstractionengine/app/ui/editor/vim/handler/CommandModeHandler.java
@@ -0,0 +1,38 @@
+package coffee.liz.abstractionengine.app.ui.editor.vim.handler;
+
+import coffee.liz.abstractionengine.app.ui.editor.vim.context.KeyContext;
+import coffee.liz.abstractionengine.app.ui.editor.vim.state.CommandState;
+import coffee.liz.abstractionengine.app.ui.editor.vim.state.NormalState;
+import com.badlogic.gdx.Input;
+
+public class CommandModeHandler implements ModeHandler<CommandState> {
+ @Override
+ public HandleResult onKeyDown(final CommandState state, final KeyContext context) {
+ if (context.currentKeyEvent().keycode() == Input.Keys.ESCAPE) {
+ return HandleResult.handled(NormalState.DEFAULT);
+ }
+ return HandleResult.handled();
+ }
+
+ @Override
+ public HandleResult onKeyTyped(final CommandState state, final char character, final KeyContext context) {
+ if (character == '\r' || character == '\n') {
+ context.executeCommand(state.command().toString());
+ return HandleResult.handled(NormalState.DEFAULT);
+ }
+
+ if (character == '\b') {
+ if (state.command().length() > 0) {
+ state.command().setLength(state.command().length() - 1);
+ }
+ return HandleResult.handled(state);
+ }
+
+ if (character < 32 || character == 127) {
+ return HandleResult.handled(state);
+ }
+
+ state.command().append(character);
+ return HandleResult.handled(state);
+ }
+}
diff --git a/core/src/main/java/coffee/liz/abstractionengine/app/ui/editor/vim/handler/NormalModeHandler.java b/core/src/main/java/coffee/liz/abstractionengine/app/ui/editor/vim/handler/NormalModeHandler.java
index 95dc2bc..3fc24d3 100644
--- a/core/src/main/java/coffee/liz/abstractionengine/app/ui/editor/vim/handler/NormalModeHandler.java
+++ b/core/src/main/java/coffee/liz/abstractionengine/app/ui/editor/vim/handler/NormalModeHandler.java
@@ -6,6 +6,7 @@ import coffee.liz.abstractionengine.app.ui.editor.vim.search.SearchDirection;
import coffee.liz.abstractionengine.app.ui.editor.vim.state.InsertState;
import coffee.liz.abstractionengine.app.ui.editor.vim.state.NormalState;
import coffee.liz.abstractionengine.app.ui.editor.vim.state.PendingOperator;
+import coffee.liz.abstractionengine.app.ui.editor.vim.state.CommandState;
import coffee.liz.abstractionengine.app.ui.editor.vim.state.SearchState;
import coffee.liz.abstractionengine.app.ui.editor.vim.state.VisualLineState;
import coffee.liz.abstractionengine.app.ui.editor.vim.state.VisualState;
@@ -296,6 +297,7 @@ public class NormalModeHandler implements ModeHandler<NormalState> {
return HandleResult.handled(new NormalState(0, PendingOperator.NONE, state.preferredColumn()));
});
map.put('u', (state, context) -> HandleResult.handled(context.undoToNormalState()));
+ map.put(':', (state, context) -> HandleResult.handled(new CommandState(new StringBuilder())));
return map;
}
diff --git a/core/src/main/java/coffee/liz/abstractionengine/app/ui/editor/vim/state/CommandState.java b/core/src/main/java/coffee/liz/abstractionengine/app/ui/editor/vim/state/CommandState.java
new file mode 100644
index 0000000..e0112e0
--- /dev/null
+++ b/core/src/main/java/coffee/liz/abstractionengine/app/ui/editor/vim/state/CommandState.java
@@ -0,0 +1,7 @@
+package coffee.liz.abstractionengine.app.ui.editor.vim.state;
+
+public record CommandState(StringBuilder command) implements ModeState {
+ public CommandState {
+ command = command == null ? new StringBuilder() : command;
+ }
+}
diff --git a/core/src/main/java/coffee/liz/abstractionengine/app/ui/editor/vim/state/ModeState.java b/core/src/main/java/coffee/liz/abstractionengine/app/ui/editor/vim/state/ModeState.java
index e878dd7..20ce01e 100644
--- a/core/src/main/java/coffee/liz/abstractionengine/app/ui/editor/vim/state/ModeState.java
+++ b/core/src/main/java/coffee/liz/abstractionengine/app/ui/editor/vim/state/ModeState.java
@@ -1,6 +1,6 @@
package coffee.liz.abstractionengine.app.ui.editor.vim.state;
-public sealed interface ModeState permits InsertState, NormalState, SearchState, VisualLineState, VisualState {
+public sealed interface ModeState permits CommandState, InsertState, NormalState, SearchState, VisualLineState, VisualState {
default PendingOperator pendingOperator() {
return PendingOperator.NONE;
}
diff --git a/core/src/main/java/coffee/liz/abstractionengine/app/ui/editor/vim/status/StatusFormatter.java b/core/src/main/java/coffee/liz/abstractionengine/app/ui/editor/vim/status/StatusFormatter.java
index 5ff2654..d38f9d6 100644
--- a/core/src/main/java/coffee/liz/abstractionengine/app/ui/editor/vim/status/StatusFormatter.java
+++ b/core/src/main/java/coffee/liz/abstractionengine/app/ui/editor/vim/status/StatusFormatter.java
@@ -3,6 +3,7 @@ package coffee.liz.abstractionengine.app.ui.editor.vim.status;
import coffee.liz.abstractionengine.app.ui.editor.vim.VimConfig;
import coffee.liz.abstractionengine.app.ui.editor.vim.state.InsertState;
import coffee.liz.abstractionengine.app.ui.editor.vim.state.ModeState;
+import coffee.liz.abstractionengine.app.ui.editor.vim.state.CommandState;
import coffee.liz.abstractionengine.app.ui.editor.vim.state.NormalState;
import coffee.liz.abstractionengine.app.ui.editor.vim.state.PendingOperator;
import coffee.liz.abstractionengine.app.ui.editor.vim.state.SearchState;
@@ -17,6 +18,7 @@ public class StatusFormatter {
case VisualState ignored -> config.visualStatusLabel();
case VisualLineState ignored -> config.visualLineStatusLabel();
case SearchState searchState -> '/' + searchState.query().toString();
+ case CommandState commandState -> ':' + commandState.command().toString();
};
}