lotabout.skim/shell/completion.nu
LoricAndre 776d708ede
feat: allow binding actions & more events (#1125)
* Add `start` and `load` events alongside `change`

Introduce `start` and `load` bindable events, mirroring the existing
`change` event, so `--bind start:<action>` and `--bind load:<action>`
work.

To avoid scattering magic high-F-key literals (`F(255)` for `change`),
add a `SkimEvent` enum in `binds.rs` with `Start`, `Load` and `Change`
variants that transparently convert to the reserved `KeyEvent`s used to
route them through the keymap. `parse_key` now accepts the friendly
names `start`, `load` and `change` via `SkimEvent::from_name`. The keymap
key type stays `KeyEvent`, so the public API is unchanged.

Firing:
- `change` is emitted by `on_query_changed` (now via the named variant).
- `start` fires exactly once when skim enters its event loop
  (`Skim::fire_start_event`).
- `load` fires once the reader has finished AND the freshly-read items
  have been rendered into the list, so a `load` binding acts on a
  fully-populated, stable list. It is re-armed on `reload`.

Add unit coverage for the event-name round-trip and integration tests
for `start` and `load` bindings, and document the events in
ARCHITECTURE.md.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019QKxusGbxLxqWdYyeqrb68

* Add result/focus/zero/one events and action follow-up bindings

Extend the bindable-event set and generalise binding so that actions can
themselves be bound.

New finder events (fired from the post-draw `Event::Render` path, so a
binding sees a stable, up-to-date list):
- `result` — filtering for the current query completed
- `focus`  — the focused item changed (cursor move or result update)
- `zero`   — a completed search has no matches
- `one`    — a completed search has exactly one match

`zero`/`one` read `MatcherControl::get_num_matched()` rather than the
rendered list count, which can briefly lag the matcher.

Actions as events: any action can be bound as if it were an event, so a
follow-up chain runs after it (e.g. `reload:first`, `first:last`). This is
parsed by `parse_action_binds` into `SkimOptions::action_binds` (keyed by
`Action::name`) and applied in `handle_action`, which now wraps the
per-variant `dispatch_action`.

- Keys win: a name shared by a key and an action binds the key; use an
  `act-` prefix to target the action (`act-up:down`).
- New `skip` action suppresses the triggering action's own behaviour, so
  `act-up:skip+down` remaps the up action to down and `up:skip` disables
  the up key.

Also fixes `parse_key` so a non-numeric `f…` name (e.g. `focus`, `first`)
falls through to name/event matching instead of erroring on the function-
key branch.

Adds unit and snapshot tests and documents everything in ARCHITECTURE.md.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019QKxusGbxLxqWdYyeqrb68

* Rename skip action to suppress and document it in the manpage

Rename the `skip` action to `suppress`, which more clearly conveys that
it cancels the triggering action's default behaviour. Add it to the
manpage actions list, noting that when bound to an action it suppresses
that action's default (so the rest of the chain runs in its place), and
when bound to a key it is equivalent to `ignore`.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019QKxusGbxLxqWdYyeqrb68

* Fire finder events from callbacks instead of the render path

Move the synthetic finder events off the per-render check:

- `focus` now rides along with `App::on_selection_changed` (via a small
  `take_focus_event` helper), firing only when the focused item actually
  changes on cursor movement.
- `load`/`result`/`zero`/`one` track async reader/matcher completion, which
  has no synchronous callback, so `App::poll_completion_events` edge-triggers
  them from the `Heartbeat` handler rather than the render path. A `Render`
  is queued just before them so a list-inspecting binding (e.g. `load:first`)
  still sees the finished results.

This removes the branching that previously ran on every render tick and
keeps the event logic out of the unrelated `dispatch_action` arms.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019QKxusGbxLxqWdYyeqrb68

* misc tweaks incl. noremap

* fixes

* docs: document new binds

* coderabbit review

* fix: address Copilot review comments on action binds

- Split `--bind` specs with top-level comma splitting in `SkimOptions::build`
  so commas inside parenthesized action arguments (e.g.
  `act-up:execute(echo a,b)`) no longer garble follow-up bindings. Reuses the
  existing `split_top_level` helper (now `pub(crate)`), matching
  `KeyMap::add_keymaps_str`.
- Correct the misleading `load` event comment in `check_reader`: the event is
  fired from `App::poll_completion_events` (the heartbeat handler), not the
  render path.
- Add a unit test covering commas inside action arguments.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pxems1gNKKezFcxtZfURJp

* fix: harden action-trigger binds (logging, runtime bind/unbind, API surface)

Address three review findings on the action-trigger feature:

- Log dropped `--bind` specs: an unknown trigger name or an invalid
  follow-up chain in parse_action_binds is now reported via debug!
  instead of vanishing silently, matching the keymap path's behavior.
- Make the runtime `bind`/`unbind` actions manage action triggers as
  well as keys: `bind(act-up:last)` merges into action_binds and
  `unbind(act-up)` removes the trigger, with the same keys-win
  precedence as `--bind`. Trigger-name resolution is shared through a
  new binds::action_trigger_name helper.
- Narrow the new App fields (reader_done, load_event_fired,
  result_pending) to pub(crate): they are a Skim<->App coordination
  protocol, not public API.

Update the manpage (regenerated sk.1) and ARCHITECTURE.md accordingly,
and cover the new behavior with unit tests.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TgSqqsXn1GfQRZhQWAWi7t

* fix: don't abort the event loop on an invalid if-* branch chain

`if-*` branch chains are stored unparsed by `parse_action`, so an invalid
action name only surfaces when the binding fires. `dispatch_conditional`
propagated that parse error out of `App::handle_event`, killing the whole
finder mid-session on a bind typo. Log and skip the chain instead,
matching the invalid-chain handling of `parse_action_binds`.

Also refresh the stale line numbers in the ARCHITECTURE.md
cross-reference table.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TgSqqsXn1GfQRZhQWAWi7t

* fix: make sure to render before start in sync mode

* chore: push snaps

* fix: misc

* fix: address review comments on start event, if-* logging and docs

- skim.rs: retry the one-shot `start` event from `tick()` so a momentarily
  full bounded `event_tx` at the `start()`/`enter()` call sites can no longer
  drop it permanently. Idempotent via the `start_fired` guard.
- app.rs: log an invalid `if-*` conditional action chain at `warn!` instead of
  `debug!` so a misconfigured binding is discoverable by default.
- ARCHITECTURE.md: clarify that `Skim::check_reader` only records `reader_done`;
  `App::poll_completion_events` owns and emits `load`/`result`/`zero`/`one`.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019QKxusGbxLxqWdYyeqrb68

* chore: minor formatting

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-07-21 11:42:47 +00:00

192 lines
9.4 KiB
Plaintext

module completions {
def "nu-complete sk tiebreak" [] {
[ "score" "-score" "begin" "-begin" "end" "-end" "length" "-length" "index" "-index" "pathname" "-pathname" ]
}
def "nu-complete sk algorithm" [] {
[ "arinae" "clangd" "fzy" "frizbee" "skim_v2" ]
}
def "nu-complete sk case" [] {
[ "respect" "ignore" "smart" ]
}
def "nu-complete sk scheme" [] {
[ "default" "path" "history" ]
}
def "nu-complete sk layout" [] {
[ "default" "reverse" "reverse-list" ]
}
def "nu-complete sk border" [] {
[ "force-off" "none" "plain" "rounded" "double" "thick" "light-double-dashed" "heavy-double-dashed" "light-triple-dashed" "heavy-triple-dashed" "light-quadruple-dashed" "heavy-quadruple-dashed" "quadrant-inside" "quadrant-outside" ]
}
def "nu-complete sk image" [] {
[ "detect" "halfblocks" ]
}
def "nu-complete sk shell" [] {
[ "bash" "elvish" "fish" "nushell" "power-shell" "zsh" ]
}
def "nu-complete sk flags" [] {
[ "no-preview-pty" "show-score" "show-index" "single-reader" "single-matcher" ]
}
# Fuzzy Finder in rust!
export extern sk [
--tac # Show results in reverse order
--min-query-length: string # Minimum query length to start showing results
--no-sort # Do not sort the results
--tiebreak(-t): string@"nu-complete sk tiebreak" # Comma-separated list of sort criteria to apply when the scores are tied.
--nth(-n): string # Fields to be matched
--with-nth: string # Fields to be transformed
--hide-nth: string # Fields to hide from display while keeping them searchable
--delimiter(-d): string # Delimiter between fields
--exact(-e) # Run in exact mode
--regex # Start in regex mode instead of fuzzy-match
--algo: string@"nu-complete sk algorithm" # Fuzzy matching algorithm
--case: string@"nu-complete sk case" # Case sensitivity
--typos: string # Enable typo-tolerant matching
--no-typos # Disable typo-tolerant matching
--normalize # Normalize unicode characters
--split-match: string # Enable split matching and set delimiter
--last-match # Highlight the last match found, not the first one This makes tiebreak more pertinent on path items where we want to prioritize a match on the last parts
--scheme: string@"nu-complete sk scheme"
--bind(-b): string # Comma-separated key, event, and action bindings
--multi(-m) # Enable multiple selection
--no-multi # Disable multiple selection
--no-mouse # Disable mouse
--cmd(-c): string # Command to invoke dynamically in interactive mode
--interactive(-i) # Start skim in interactive mode
-I: string # Replace replstr with the selected item in commands
--color: string # Set color theme
--highlight-line # Highlight the entire current line, not just the text
--no-hscroll # Disable horizontal scroll
--keep-right # Keep the right end of the line visible on overflow
--skip-to-pattern: string # Show the matched pattern at the line start
--no-clear-if-empty # Do not clear previous line if the command returns an empty result
--no-clear-start # Do not clear items on start
--no-clear # Do not clear screen on exit
--show-cmd-error # Show error message if command fails
--cycle # Cycle the results by wrapping around when scrolling
--disabled # Disable matching entirely
--disable-pattern: string # Disable items based on this regex pattern
--layout: string@"nu-complete sk layout" # Set layout
--reverse # Shorthand for reverse layout
--height: string # Height of skim's window
--no-height # Disable height (force full screen)
--min-height: string # Minimum height of skim's window
--margin: string # Screen margin
--prompt(-p): string # Set prompt
--cmd-prompt: string # Set prompt in command mode
--selector: string # Set selected item icon
--multi-selector: string # Set multi-selected item icon
--ansi # Parse ANSI color codes in input strings
--tabstop: string # Number of spaces that make up a tab
--ellipsis: string # The characters used to display truncated lines
--info: string # Set matching result count display position
--no-info # Alias for --info=hidden
--inline-info # Alias for --info=inline
--header: string # Set header, displayed next to the info
--header-lines: string # Number of lines of the input treated as header
--border: string@"nu-complete sk border" # Draw borders around the UI components
--border-no-collapse # Do not collapse adjacent borders into a shared row or column
--no-border # Disables all borders, including in tmux/zellij popups
--wrap # Wrap items in the item list
--multiline: string # Split item text into multiple display lines at the given separator character defaults to \n if read0 is set, and \\n if not (matching literal \n in text)
--scrollbar: string # Set scrollbar style for the item list
--no-scrollbar # Disable the scrollbar in the item list
--history: string # History file
--history-size: string # Maximum number of query history entries to keep
--cmd-history: string # Command history file
--cmd-history-size: string # Maximum number of query history entries to keep
--preview: string # Preview command
--preview-window: string # Preview window layout
--image: string@"nu-complete sk image" # Enable image preview
--query(-q): string # Initial query
--cmd-query: string # Initial query in interactive mode
--read0 # Read input delimited by ASCII NUL(\0) characters
--print0 # Print output delimited by ASCII NUL(\0) characters
--print-query # Print the query as the first line
--print-cmd # Print the command as the first line (after print-query)
--print-score # Print the score after each item
--print-header # Print the header as the first line (after print-score)
--print-current # Print the current (highlighted) item as the first line (after print-header)
--output-format: string # Set the output format If set, overrides all print_ options Will be expanded the same way as preview or commands
--no-strip-ansi # Print the ANSI codes, making the output exactly match the input even when --ansi is on
--select-1(-1) # Do not enter the TUI if the query passed in -q matches only one item and return it
--exit-0(-0) # Do not enter the TUI if the query passed in -q does not match any item
--sync # Synchronous search for multi-staged filtering
--pre-select-n: string # Pre-select the first n items in multi-selection mode
--pre-select-pat: string # Pre-select the matched items in multi-selection mode
--pre-select-items: string # Pre-select the items separated by newline character
--pre-select-file: string # Pre-select the items read from this file
--filter(-f): string # Query for filter mode
--shell: string@"nu-complete sk shell" # Generate shell completion script
--shell-bindings # Generate shell key bindings - only for bash, zsh and fish
--man # Generate man page and output it to stdout
--listen: string # Run an IPC socket with optional name (defaults to sk)
--remote: string # Send commands to an IPC socket with optional name (defaults to sk)
--popup: string # Run in a tmux or zellij popup
--log-level: string # Set the log level
--log-file: string # Pipe log output to a file
--flags: string@"nu-complete sk flags" # Feature flags
--extended(-x)
--literal
--hscroll-off: string
--filepath-word
--jump-labels: string
--no-bold
--phony
--tail: string
--style: string
--no-color
--padding: string
--border-label: string
--border-label-pos: string
--wrap-sign: string
--no-multi-line
--raw
--track
--gap: string
--gap-line: string
--freeze-left: string
--freeze-right: string
--scroll-off: string
--gutter: string
--gutter-raw: string
--marker-multi-line: string
--list-border: string
--list-label: string
--list-label-pos: string
--no-input
--info-command: string
--separator: string
--no-separator
--ghost: string
--input-border: string
--input-label: string
--input-label-pos: string
--preview-label: string
--preview-label-pos: string
--header-first
--header-border: string
--header-lines-border: string
--footer: string
--footer-border: string
--footer-label: string
--footer-label-pos: string
--with-shell: string
--expect: string # Deprecated, kept for compatibility purposes. See accept() bind instead
--help(-h) # Print help (see more with '--help')
--version(-V) # Print version
]
}
export use completions *