modal editor is a bit bad
This commit is contained in:
@@ -1,85 +0,0 @@
|
|||||||
/**
|
|
||||||
* Modal Editor - vim-like modal editing example
|
|
||||||
*
|
|
||||||
* Usage: pi --extension ./examples/extensions/modal-editor.ts
|
|
||||||
*
|
|
||||||
* - Escape: insert → normal mode (in normal mode, aborts agent)
|
|
||||||
* - i: normal → insert mode
|
|
||||||
* - hjkl: navigation in normal mode
|
|
||||||
* - ctrl+c, ctrl+d, etc. work in both modes
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { CustomEditor, type ExtensionAPI } from "@mariozechner/pi-coding-agent";
|
|
||||||
import { matchesKey, truncateToWidth, visibleWidth } from "@mariozechner/pi-tui";
|
|
||||||
|
|
||||||
// Normal mode key mappings: key -> escape sequence (or null for mode switch)
|
|
||||||
const NORMAL_KEYS: Record<string, string | null> = {
|
|
||||||
h: "\x1b[D", // left
|
|
||||||
j: "\x1b[B", // down
|
|
||||||
k: "\x1b[A", // up
|
|
||||||
l: "\x1b[C", // right
|
|
||||||
"0": "\x01", // line start
|
|
||||||
$: "\x05", // line end
|
|
||||||
x: "\x1b[3~", // delete char
|
|
||||||
i: null, // insert mode
|
|
||||||
a: null, // append (insert + right)
|
|
||||||
};
|
|
||||||
|
|
||||||
class ModalEditor extends CustomEditor {
|
|
||||||
private mode: "normal" | "insert" = "insert";
|
|
||||||
|
|
||||||
handleInput(data: string): void {
|
|
||||||
// Escape toggles to normal mode, or passes through for app handling
|
|
||||||
if (matchesKey(data, "escape")) {
|
|
||||||
if (this.mode === "insert") {
|
|
||||||
this.mode = "normal";
|
|
||||||
} else {
|
|
||||||
super.handleInput(data); // abort agent, etc.
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Insert mode: pass everything through
|
|
||||||
if (this.mode === "insert") {
|
|
||||||
super.handleInput(data);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Normal mode: check mapped keys
|
|
||||||
if (data in NORMAL_KEYS) {
|
|
||||||
const seq = NORMAL_KEYS[data];
|
|
||||||
if (data === "i") {
|
|
||||||
this.mode = "insert";
|
|
||||||
} else if (data === "a") {
|
|
||||||
this.mode = "insert";
|
|
||||||
super.handleInput("\x1b[C"); // move right first
|
|
||||||
} else if (seq) {
|
|
||||||
super.handleInput(seq);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Pass control sequences (ctrl+c, etc.) to super, ignore printable chars
|
|
||||||
if (data.length === 1 && data.charCodeAt(0) >= 32) return;
|
|
||||||
super.handleInput(data);
|
|
||||||
}
|
|
||||||
|
|
||||||
render(width: number): string[] {
|
|
||||||
const lines = super.render(width);
|
|
||||||
if (lines.length === 0) return lines;
|
|
||||||
|
|
||||||
// Add mode indicator to bottom border
|
|
||||||
const label = this.mode === "normal" ? " NORMAL " : " INSERT ";
|
|
||||||
const last = lines.length - 1;
|
|
||||||
if (visibleWidth(lines[last]!) >= label.length) {
|
|
||||||
lines[last] = truncateToWidth(lines[last]!, width - label.length, "") + label;
|
|
||||||
}
|
|
||||||
return lines;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function (pi: ExtensionAPI) {
|
|
||||||
pi.on("session_start", (_event, ctx) => {
|
|
||||||
ctx.ui.setEditorComponent((tui, theme, kb) => new ModalEditor(tui, theme, kb));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user