Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions deno.jsonc
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
1 change: 1 addition & 0 deletions deno.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

17 changes: 14 additions & 3 deletions src/run.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,22 @@ async function loadYaml<T>(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 <KEYS:string>', '(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<PartialContext>(joinPath(XDG_CONFIG_HOME, 'wk', 'config.yaml')).catch(() =>
undefined
Expand All @@ -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')
Expand All @@ -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) {
Expand Down
33 changes: 20 additions & 13 deletions src/tui.ts
Original file line number Diff line number Diff line change
@@ -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 }.
Expand All @@ -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')
Expand All @@ -49,22 +50,22 @@ 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')
.cursorLeft.text(promptLine)
.bytes(),
)

this.#writer.writeSync(
this.#tty.writeSync(
ansi
.text('\n')
.text('\x1b[0J')
Expand All @@ -77,6 +78,12 @@ export class TUI {
}

async *keypress(): AsyncIterable<KeyPressEvent> {
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
Expand Down
4 changes: 2 additions & 2 deletions src/widget.eta
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down