From 17ca9dac9784898d3ec754c00682f7f4fca87699 Mon Sep 17 00:00:00 2001 From: 844196 <844196@users.noreply.github.com> Date: Mon, 9 Sep 2024 02:23:22 +0900 Subject: [PATCH] :sparkles: Implement `wk widget` features --- .gitignore | 2 + README.md | 184 ++++++++++++++++++++++++++++-------------- Taskfile.yaml | 9 +++ aqua.yaml | 1 + deno.jsonc | 5 +- deno.lock | 5 ++ schemas/bindings.json | 6 ++ src/cli.ts | 29 +++++-- src/widget.eta | 50 ++++++++++++ 9 files changed, 221 insertions(+), 70 deletions(-) create mode 100644 src/widget.eta diff --git a/.gitignore b/.gitignore index 2f6844b..2d02cd3 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,4 @@ +.task/ dist/ src/version.generated.json +src/widget.generated.json diff --git a/README.md b/README.md index 9956e59..aae11f0 100644 --- a/README.md +++ b/README.md @@ -7,66 +7,126 @@ ## :package: Installation - - -## :gear: Configuration example (zsh) - -```shell -# $ZDOTDIR/.zshrc - -space-wk() { - case $BUFFER in - '') - # Avoid cursor flickering - echo -n $'\x1b[?25l\x1b[0`' >$TTY - - local res='' - res=$(wk run --no-validation 2>&1) - local wk_exit=$? - - zle redisplay - - case $wk_exit in - 0) - # NOOP - ;; - 1|2) - # Abort, Timeout - return - ;; - *) - zle -M "wk: $res" - return $wk_exit - ;; - esac - - res=("${(@ps:\t:)res}") - - if [[ "${res[(rb:2:)eval:*]}" == 'eval:true' ]]; then - BUFFER=${(e)res[1]} - else - BUFFER=${res[1]} - fi - CURSOR=${#BUFFER} - - case "${res[(rb:2:)trigger:*]}" in - trigger:ACCEPT) - zle accept-line - ;; - trigger:COMPLETE) - zle expand-or-complete - ;; - esac - - zle redisplay - ;; - - *) - zle self-insert - ;; - esac -} - -zle -N space-wk -bindkey ' ' space-wk +1. Download the latest release and put into your `$PATH`: + + + +2. Activate in `$ZDOTDIR/.zshrc`: + + ```shell + # Register a widget with the name "_wk_widget", and bind it to the ^G. + eval "$(wk widget zsh)" + ``` + +3. Restart zsh. + +> [!TIP] +> If you want to change the trigger key, change it as follows: +> +> ```shell +> eval "$(wk widget zsh --bindkey '^T')" +> ``` +> +> If you want to register only the widget, change it as follows: +> +> ```shell +> eval "$(wk widget zsh --no-bindkey)" +> ``` + +## :gear: Configuration + +### Config + +`${XDG_CONFIG_HOME:-$HOME/.config}/wk/config.yaml` + +```yaml +--- +timeout: 60000 +symbols: + prompt: "❯ " +colors: + prompt: 8 + inputKeys: + color: 8 + attrs: [dim] + breadcrumb: + color: 8 + attrs: [dim] + lastInputKey: + color: 6 + attrs: [underline] + bindingKey: 5 + separator: + color: 5 + attrs: [dim] + group: 8 + bindingIcon: 8 + bindingDescription: 8 +``` + +See [schemas/config.json](./schemas/config.json) for more details. + +### Global bindings + +`${XDG_CONFIG_HOME:-$HOME/.config}/wk/bindings.yaml` + +```yaml +--- +- key: g + desc: Git + type: bindings + bindings: + - key: p + desc: Push/Pull + type: bindings + bindings: + - key: return + desc: git push + type: command + buffer: 'git push origin $(git symbolic-ref --short HEAD)' + eval: true + - key: f + desc: git push -f + type: command + buffer: 'git push --force-with-lease --force-if-includes origin $(git symbolic-ref --short HEAD)' + eval: true + - key: l + type: command + buffer: 'git pull' + accept: true +- key: y + desc: Yank + type: bindings + bindings: + - key: . + desc: Copy $PWD + type: command + buffer: ' echo -ne "\e]52;c;$(base64 <(pwd | tee >$TTY | sed -z ''$s/\n$//''))\a"' + accept: true +``` + +See [schemas/bindings.json](./schemas/bindings.json) for more details. + +### Local bindings + +`$PWD/wk.bindings.yaml` + +```yaml +--- +- key: m + desc: Major + type: bindings + bindings: + - key: b + type: command + buffer: 'npm run build' + accept: true + - key: l + type: command + buffer: 'npm run lint' + accept: true + - key: t + type: command + buffer: 'npm run test' + accept: true ``` diff --git a/Taskfile.yaml b/Taskfile.yaml index e207ab4..2b3554d 100644 --- a/Taskfile.yaml +++ b/Taskfile.yaml @@ -9,6 +9,7 @@ tasks: check: deps: - version_file + - widget_file cmds: - deno fmt --check - deno lint @@ -17,6 +18,7 @@ tasks: compile: deps: - version_file + - widget_file vars: TARGETS: - x86_64-unknown-linux-gnu @@ -35,3 +37,10 @@ tasks: vars: VERSION: '{{.VERSION | default "development"}}' cmd: echo '"{{.VERSION}}"' > src/version.generated.json + + widget_file: + sources: + - src/widget.eta + generates: + - src/widget.generated.json + cmd: jq --raw-input --slurp < src/widget.eta > src/widget.generated.json diff --git a/aqua.yaml b/aqua.yaml index 2b9fdae..8cd9f22 100644 --- a/aqua.yaml +++ b/aqua.yaml @@ -3,3 +3,4 @@ registries: ref: v4.212.0 packages: - name: go-task/task@v3.38.0 + - name: jqlang/jq@jq-1.7.1 diff --git a/deno.jsonc b/deno.jsonc index 4dacf81..8700c68 100644 --- a/deno.jsonc +++ b/deno.jsonc @@ -15,12 +15,13 @@ "@cliffy/command": "jsr:@cliffy/command@1.0.0-rc.5", "@cliffy/keycode": "jsr:@cliffy/keycode@1.0.0-rc.5", "@cliffy/table": "jsr:@cliffy/table@1.0.0-rc.5", + "@dunosaurs/color": "https://deno.land/x/color@v0.3.0/mod.ts", + "@eta-dev/eta": "jsr:@eta-dev/eta@^3.5.0", "@lambdalisue/github-emoji": "jsr:@lambdalisue/github-emoji@1.0.0", "@std/collections": "jsr:@std/collections@1.0.5", "@std/path": "jsr:@std/path@1.0.2", "rc-config-loader": "npm:rc-config-loader@4.1.3", "xdg-basedir": "npm:xdg-basedir@5.1.0", - "zod": "npm:zod@3.23.8", - "@dunosaurs/color": "https://deno.land/x/color@v0.3.0/mod.ts" + "zod": "npm:zod@3.23.8" } } diff --git a/deno.lock b/deno.lock index 89d2ed2..b2c28c7 100644 --- a/deno.lock +++ b/deno.lock @@ -8,6 +8,7 @@ "jsr:@cliffy/internal@1.0.0-rc.5": "jsr:@cliffy/internal@1.0.0-rc.5", "jsr:@cliffy/keycode@1.0.0-rc.5": "jsr:@cliffy/keycode@1.0.0-rc.5", "jsr:@cliffy/table@1.0.0-rc.5": "jsr:@cliffy/table@1.0.0-rc.5", + "jsr:@eta-dev/eta@^3.5.0": "jsr:@eta-dev/eta@3.5.0", "jsr:@lambdalisue/github-emoji@1.0.0": "jsr:@lambdalisue/github-emoji@1.0.0", "jsr:@std/assert@^0.221.0": "jsr:@std/assert@0.221.0", "jsr:@std/cli@1.0.0-rc.2": "jsr:@std/cli@1.0.0-rc.2", @@ -62,6 +63,9 @@ "jsr:@std/fmt@~0.225.4" ] }, + "@eta-dev/eta@3.5.0": { + "integrity": "6b70827efc14c7cbf08498ac7a922ecab003641caf3852a6cb5b1b12ee58fb37" + }, "@lambdalisue/github-emoji@1.0.0": { "integrity": "cc6b1ae72cde7a61397c886caf4013d0cc8d1108c70459192b2a7b0cccbfef2c", "dependencies": [ @@ -201,6 +205,7 @@ "jsr:@cliffy/command@1.0.0-rc.5", "jsr:@cliffy/keycode@1.0.0-rc.5", "jsr:@cliffy/table@1.0.0-rc.5", + "jsr:@eta-dev/eta@^3.5.0", "jsr:@lambdalisue/github-emoji@1.0.0", "jsr:@std/collections@1.0.5", "jsr:@std/path@1.0.2", diff --git a/schemas/bindings.json b/schemas/bindings.json index e4be2ef..7b0c3c8 100644 --- a/schemas/bindings.json +++ b/schemas/bindings.json @@ -35,6 +35,12 @@ }, "delimiter": { "type": "string" + }, + "eval": { + "type": "boolean" + }, + "accept": { + "type": "boolean" } }, "additionalProperties": { diff --git a/src/cli.ts b/src/cli.ts index 091dba0..4e88479 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -13,12 +13,30 @@ import { getKeySymbol } from './ui.ts' import version from './version.generated.json' with { type: 'json' } import { renderPrompt } from './ui.ts' import { renderTable } from './ui.ts' +import { Eta } from '@eta-dev/eta' +import widgetTemplate from './widget.generated.json' with { type: 'json' } const cli = new Command() .name('wk') .version(version) .versionOption('-v, --version', 'Show the version number for this program.', { global: true }) +const widget = new Command() + .description('Outputs shell widget source code.') + .arguments('') + .option('--bindkey ', 'Bind the widget to the key.', { default: '^G' }) + .option('--no-bindkey', 'Do not bind the widget to the key.') + .action(({ bindkey }) => { + const eta = new Eta() + + const rendered = eta.renderString(widgetTemplate, { + wk_path: Deno.execPath(), + bindkey, + }) + + console.log(rendered) + }) + const run = new Command() .description('Run the workflow.') .option('--no-validation', 'Skip validation of the configuration files.') @@ -76,8 +94,7 @@ const run = new Command() let timeoutTimerId: number | undefined const handleTimeout = () => { tui.close() - console.error('Timeout') - Deno.exit(2) + Deno.exit(4) } const deps: Dependencies = { @@ -128,16 +145,15 @@ const run = new Command() } catch (e: unknown) { if (e instanceof AbortError) { tui.close() - console.error('Abort') - Deno.exit(1) + Deno.exit(3) } else if (e instanceof UndefinedKeyError) { tui.close() console.error(`"${e.getInputKeys().map((k) => getKeySymbol(ctx, k)).join(' ')}" is undefined`) - Deno.exit(3) + Deno.exit(5) } else if (e instanceof KeyParseError) { tui.close() console.error('Failed to parse key', e.getKey()) - Deno.exit(4) + Deno.exit(6) } else { throw e } @@ -147,5 +163,6 @@ const run = new Command() }) await cli + .command('widget', widget) .command('run', run) .parse(Deno.args) diff --git a/src/widget.eta b/src/widget.eta new file mode 100644 index 0000000..fc12b49 --- /dev/null +++ b/src/widget.eta @@ -0,0 +1,50 @@ +_wk_widget() { + local opts + zstyle -a ':wk:*' options opts || opts=() + + local res='' + res=$(<%= it.wk_path %> run ${=opts} 2>&1) + local wk_exit=$? + + zle redisplay + echo -n $'\x1b[?25h' >$TTY # Ensure cursor is visible + + case $wk_exit in + 0) + # NOOP + ;; + 3|4) + # Abort, Timeout + return + ;; + *) + # 1: General errors + # 2: Invalid arguments or options error + # 5: No match error + # 6: Key parse error + # *: Unknown errors + zle -M "wk: $res" + return $wk_exit + ;; + esac + + reply=("${(@ps:\t:)res}") + + if [[ "${reply[(rb:2:)eval:*]}" == 'eval:true' ]]; then + BUFFER=${(e)reply[1]} + else + BUFFER=${reply[1]} + fi + CURSOR=${#BUFFER} + + if [[ "${reply[(rb:2:)accept:*]}" == 'accept:true' ]]; then + local accept_widget + zstyle -s ':wk:*' accept-widget accept_widget || accept_widget='accept-line' + zle $accept_widget + fi +} + +zle -N _wk_widget +<% if (typeof it.bindkey === 'string') { %> +bindkey '<%= it.bindkey %>' _wk_widget +<% } %>