From 9da6d4f19f3927b0177faabd86eb2b894775d6d9 Mon Sep 17 00:00:00 2001 From: 844196 <844196@users.noreply.github.com> Date: Mon, 21 Jul 2025 00:13:41 +0900 Subject: [PATCH 1/3] :recycle: (tui): Remove unnecessary stream properties --- src/run.ts | 2 +- src/tui.ts | 22 ++++++++++------------ 2 files changed, 11 insertions(+), 13 deletions(-) diff --git a/src/run.ts b/src/run.ts index 410e59a..b5a8f2e 100644 --- a/src/run.ts +++ b/src/run.ts @@ -35,7 +35,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) try { tui.init(upOneLine === true ? true : upOneLine === 'true' ? true : upOneLine === 'false' ? false : 'auto') diff --git a/src/tui.ts b/src/tui.ts index 9547c8c..c4fd1d6 100644 --- a/src/tui.ts +++ b/src/tui.ts @@ -4,18 +4,16 @@ import { stripAnsiCode } from '@std/fmt/colors' import { getCursorPosition } from '@cliffy/ansi/cursor-position' export class TUI { - #reader: Deno.FsFile - #writer: Deno.FsFile + #tty: Deno.FsFile #upOneLine: boolean = false - constructor(reader: Deno.FsFile, writer: Deno.FsFile) { - this.#reader = reader - this.#writer = writer + constructor(tty: Deno.FsFile) { + this.#tty = tty } 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 +25,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 +47,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 +62,7 @@ export class TUI { .bytes(), ) - this.#writer.writeSync( + this.#tty.writeSync( ansi .text('\n') .text('\x1b[0J') From 283b4784b7c2760fc72fed7f8b3e94b9a1cbd8c6 Mon Sep 17 00:00:00 2001 From: 844196 <844196@users.noreply.github.com> Date: Mon, 21 Jul 2025 03:29:10 +0900 Subject: [PATCH 2/3] :adhesive_bandage: (widget): Prepend `zle` --- src/widget.eta | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/widget.eta b/src/widget.eta index 3ad2575..ef7004e 100644 --- a/src/widget.eta +++ b/src/widget.eta @@ -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 From e27542d5346919597b116279b3c11d56271c9f61 Mon Sep 17 00:00:00 2001 From: 844196 <844196@users.noreply.github.com> Date: Mon, 21 Jul 2025 03:37:06 +0900 Subject: [PATCH 3/3] :sparkles: (run): Make it possible to start from a specific binding --- deno.jsonc | 1 + deno.lock | 1 + src/run.ts | 17 ++++++++++++++--- src/tui.ts | 13 +++++++++++-- src/widget.eta | 2 +- 5 files changed, 28 insertions(+), 6 deletions(-) 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 b5a8f2e..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) + 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 c4fd1d6..5662543 100644 --- a/src/tui.ts +++ b/src/tui.ts @@ -1,14 +1,17 @@ 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 { #tty: Deno.FsFile + #inputs: string[] #upOneLine: boolean = false - constructor(tty: Deno.FsFile) { + constructor(tty: Deno.FsFile, inputs: string[]) { this.#tty = tty + this.#inputs = inputs } init(upOneLine: boolean | 'auto'): void { @@ -75,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 ef7004e..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