diff --git a/deno.jsonc b/deno.jsonc index c7b08c8..b556cae 100644 --- a/deno.jsonc +++ b/deno.jsonc @@ -16,6 +16,7 @@ "imports": { "@cliffy/ansi": "jsr:@cliffy/ansi@1.0.0-rc.7", "@cliffy/command": "jsr:@cliffy/command@1.0.0-rc.7", + "@cliffy/keycode": "jsr:@cliffy/keycode@1.0.0-rc.7", "@cliffy/keypress": "jsr:@cliffy/keypress@1.0.0-rc.7", "@cliffy/table": "jsr:@cliffy/table@1.0.0-rc.7", "@eta-dev/eta": "jsr:@eta-dev/eta@3.5.0", diff --git a/deno.lock b/deno.lock index c65d102..2573d23 100644 --- a/deno.lock +++ b/deno.lock @@ -104,6 +104,7 @@ "dependencies": [ "jsr:@cliffy/ansi@1.0.0-rc.7", "jsr:@cliffy/command@1.0.0-rc.7", + "jsr:@cliffy/keycode@1.0.0-rc.7", "jsr:@cliffy/keypress@1.0.0-rc.7", "jsr:@cliffy/table@1.0.0-rc.7", "jsr:@eta-dev/eta@3.5.0", diff --git a/src/run.ts b/src/run.ts index 410e59a..a2c0505 100644 --- a/src/run.ts +++ b/src/run.ts @@ -14,11 +14,22 @@ async function loadYaml(path: string) { return parseYaml(text) as T } +function unescapeAnsi(given: string): string { + return given.replace(/\\x([0-9A-Fa-f]{2})/g, (_, hex) => String.fromCharCode(parseInt(hex, 16))) +} + export const runCommand = new Command() .description('Run.') .type('boolOrAuto', new EnumType(['true', 'false', 'auto'])) .option('--up-one-line [VALUE:boolOrAuto]', 'Whether to move the input up one line.', { default: 'auto' }) - .action(async ({ upOneLine }) => { + .option('--inputs ', '(Experimental) Simulate input keys.') + .example('wk run', 'Run.') + .example( + "wk run --inputs 'g p f'", + `Run with simulated input keys. (Space separated) +For example, this simulates pressing "g", "p", and "f".`, + ) + .action(async ({ upOneLine, inputs }) => { const fetchContextWaiting = (async () => { const found = await loadYaml(joinPath(XDG_CONFIG_HOME, 'wk', 'config.yaml')).catch(() => undefined @@ -35,7 +46,7 @@ export const runCommand = new Command() ]).then(([globalBindings, localBindings]) => [...globalBindings, ...localBindings]) const tty = await Deno.open('/dev/tty', { read: true, write: true }) - const tui = new TUI(tty, tty) + const tui = new TUI(tty, inputs === undefined ? [] : inputs.split(' ').map(unescapeAnsi)) try { tui.init(upOneLine === true ? true : upOneLine === 'true' ? true : upOneLine === 'false' ? false : 'auto') @@ -49,7 +60,7 @@ export const runCommand = new Command() } const deps: Dependencies = { - keypress: tui.keypress, + keypress: tui.keypress.bind(tui), draw: (inputKeys, bindings) => tui.draw(renderPrompt(ctx, inputKeys), renderTable(ctx, bindings).toString()), setTimeoutTimer: () => { if (ctx.timeout > 0) { diff --git a/src/tui.ts b/src/tui.ts index 9547c8c..5662543 100644 --- a/src/tui.ts +++ b/src/tui.ts @@ -1,21 +1,22 @@ import { ansi } from '@cliffy/ansi' -import { keypress, type KeyPressEvent } from '@cliffy/keypress' +import { keypress, KeyPressEvent } from '@cliffy/keypress' import { stripAnsiCode } from '@std/fmt/colors' import { getCursorPosition } from '@cliffy/ansi/cursor-position' +import { parse as parseKeycode } from '@cliffy/keycode' export class TUI { - #reader: Deno.FsFile - #writer: Deno.FsFile + #tty: Deno.FsFile + #inputs: string[] #upOneLine: boolean = false - constructor(reader: Deno.FsFile, writer: Deno.FsFile) { - this.#reader = reader - this.#writer = writer + constructor(tty: Deno.FsFile, inputs: string[]) { + this.#tty = tty + this.#inputs = inputs } init(upOneLine: boolean | 'auto'): void { if (upOneLine === 'auto') { - const pos = getCursorPosition({ reader: this.#reader, writer: this.#writer }) + const pos = getCursorPosition({ reader: this.#tty, writer: this.#tty }) // getCursorPosition() ordinary returns 1-based position. // However, if the process fails, it returns { x: 0, y: 0 }. @@ -27,16 +28,16 @@ export class TUI { } if (this.#upOneLine) { - this.#writer.writeSync(ansi.text('\n').bytes()) + this.#tty.writeSync(ansi.text('\n').bytes()) } } showCursor(): void { - this.#writer.writeSync(ansi.cursorShow.bytes()) + this.#tty.writeSync(ansi.cursorShow.bytes()) } clear(): void { - this.#writer.writeSync( + this.#tty.writeSync( ansi .cursorHide .cursorLeft.eraseLine.text('\x1b[0J') @@ -49,14 +50,14 @@ export class TUI { this.clear() if (this.#upOneLine) { - this.#writer.writeSync(ansi.cursorUp.bytes()) + this.#tty.writeSync(ansi.cursorUp.bytes()) } this.showCursor() } draw(promptLine: string, tableLines: string): void { - this.#writer.writeSync( + this.#tty.writeSync( ansi .cursorHide .eraseLine.text('\x1b[0J') @@ -64,7 +65,7 @@ export class TUI { .bytes(), ) - this.#writer.writeSync( + this.#tty.writeSync( ansi .text('\n') .text('\x1b[0J') @@ -77,6 +78,12 @@ export class TUI { } async *keypress(): AsyncIterable { + for (const input of this.#inputs) { + for (const keycode of parseKeycode(input)) { + yield new KeyPressEvent('keydown', keycode) + } + } + for await (const key of keypress()) { if (key.sequence?.match(/\[\d+;\d+R/)) { // CSI 6 n response continue diff --git a/src/widget.eta b/src/widget.eta index 3ad2575..d4cf40c 100644 --- a/src/widget.eta +++ b/src/widget.eta @@ -3,7 +3,7 @@ _wk_widget() { zstyle -a ':wk:*' options opts || opts=() local res='' - res=$(<%= it.wk_path %> run ${=opts} < $TTY 2>&1) + res=$(<%= it.wk_path %> run ${=opts} "$@" < $TTY 2>&1) local wk_exit=$? zle redisplay @@ -48,7 +48,7 @@ zle -N _wk_widget <% if (!it.bindkeyGlobal) { %> _wk_or_self_insert() { if [[ -z "$BUFFER" ]]; then - _wk_widget + zle _wk_widget else zle self-insert fi