Skip to content

fix(zsh): consistently single-quote choice values containing spaces#635

Merged
jdx merged 4 commits into
mainfrom
claude/silly-blackwell-e61338
May 13, 2026
Merged

fix(zsh): consistently single-quote choice values containing spaces#635
jdx merged 4 commits into
mainfrom
claude/silly-blackwell-e61338

Conversation

@jdx
Copy link
Copy Markdown
Owner

@jdx jdx commented May 13, 2026

Summary

  • Choices containing spaces (e.g. Alice Alice) are now inserted by zsh completion as 'Alice Alice', matching the existing behavior for values with shell metacharacters like 'A B & C'. Previously zsh's auto-quoting heuristic chose backslash-style for one and single-quote style for the other.
  • Strips user-supplied quoting from words via ${(Q)...} before invoking usage complete-word, so completion of subsequent args works even when earlier args were already entered quoted.
  • Forces compstate[insert]=menu when any value is pre-quoted, so the longest-common-prefix heuristic doesn't insert just a stray opening quote.

Fixes #634

Test plan

  • cargo test --all --all-features — all tests pass except a pre-existing test_bash_completion_init_integration failure unrelated to this change (system bash lacks complete -D).
  • New regression test test_zsh_completion_quotes_choices_with_spaces validates the generated script's parsing/quoting logic.
  • Manually reproduced the exact mise scenario from choices sometimes wraps in quotes, not always #634 (regenerating mise's _mise completion against this branch) — mise run test <TAB> produces mise run test 'A B & C', and mise run test 'A B & C' <TAB> produces mise run test 'A B & C' 'Alice Alice' with a clean menu showing Alice Alice Bob Bob Carol Carol.
  • Verified simple values (no spaces/specials) still get normal LCP completion — e.g. simple d<TAB> still expands to simple dev.

Note

Medium Risk
Changes the zsh completion output format and generated zsh completion scripts, which could break existing consumers or edge-case completion behaviors if any scripts assumed the previous single-column output.

Overview
Fixes zsh completions to consistently insert values with spaces/metacharacters using single-quote quoting by switching usage complete-word --shell zsh output to two tab-separated columns: a _describe-escaped display string and a shell-quoted insert string.

Updates generated zsh completion scripts to parse the new <display>\t<insert> format, pass unquoted user input via ${(Q)words[@]}, use _describe ... -U -Q to insert the pre-quoted value verbatim, and force compstate[insert]=menu when any insert is pre-quoted; snapshots and integration/unit tests are updated and expanded to cover these behaviors.

Reviewed by Cursor Bugbot for commit e0fb91a. Bugbot is set up for automated code reviews on this repo. Configure here.

Previously, zsh's auto-quoting heuristic produced inconsistent quoting
styles: a choice value containing shell metacharacters like `A B & C`
was inserted as `'A B & C'`, but a value containing only spaces like
`Alice Alice` was inserted as `Alice\ Alice`. Both are valid, but the
inconsistency is jarring.

Now the generated zsh completion script:
- Strips user-supplied quoting from `words` via `${(Q)...}` before
  invoking `usage complete-word`, so previously-typed args like
  `'A B & C'` match their choice definitions.
- Builds a parallel `inserts` array where each value is pre-quoted with
  `${(q-)val}` (single quotes for values needing quoting, raw
  otherwise), then passes it to `_describe` with `-Q` to bypass zsh's
  built-in auto-quoting.
- Forces `compstate[insert]=menu` when any value is pre-quoted, so the
  longest-common-prefix heuristic doesn't insert just a stray opening
  quote.

Fixes #634
Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request enhances Zsh completion by improving the handling of choices containing spaces and ensuring proper quoting/unquoting of arguments. It introduces a regression test and updates the Zsh completion templates to use the (q-) flag for quoting choices and the (Q) flag for unquoting input words. Review feedback suggests making shell variables local to prevent environment pollution, replacing fragile manual character unescaping with Zsh's robust ${(Q)...} parameter expansion, and refactoring duplicated logic within the completion templates.

Comment thread lib/src/complete/zsh.rs Outdated
Comment thread lib/src/complete/zsh.rs Outdated
Comment thread cli/tests/shell_completions_integration.rs Outdated
@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented May 13, 2026

Greptile Summary

This PR fixes inconsistent zsh completion quoting for choice values containing spaces or shell metacharacters by switching usage complete-word --shell zsh to emit two tab-separated columns (<display>\t<insert>), where the insert column is pre-shell-quoted via a new zsh_shell_quote helper. Generated completion scripts are updated to parse the new format, pass user input through ${(Q)words[@]} to strip shell quoting before invoking complete-word, and use _describe ... -U -Q -S '' to insert the pre-quoted value verbatim.

  • New output format: complete-word --shell zsh now emits display\tinsert per line, where display is the _describe-escaped string for menu rendering and insert is the shell-quoted form ready for verbatim insertion via compadd -Q.
  • Unquoting user input: ${(Q)words[@]} is applied before calling complete-word so that already-quoted earlier args (e.g. 'A B & C') are unquoted to their raw form, allowing the parser to correctly match them against defined choices and continue completing subsequent arguments.
  • needs_menu guard: When any insert starts with ', compstate[insert]=menu is set to prevent zsh's longest-common-prefix heuristic from inserting just a stray opening quote.

Confidence Score: 5/5

Safe to merge — the change is well-scoped to zsh completion output, manually verified end-to-end, and covered by both unit and integration tests.

The core logic in zsh_shell_quote is correct (apostrophe close-open dance handles embedded ' properly, and the safe-char set correctly excludes space and shell metacharacters). The ${(Q)words[@]} unquoting is the right zsh idiom for stripping user-supplied quoting before passing to an external command. The needs_menu guard correctly prevents a lone ' from being inserted via longest-common-prefix. Snapshot and integration tests have been updated and a targeted regression test was added. No data paths outside zsh completion are affected.

No files require special attention.

Important Files Changed

Filename Overview
cli/src/cli/complete_word.rs Adds zsh_shell_quote for single-quote wrapping with apostrophe close-open dance, and switches zsh output from a single column to display\tinsert format. Logic is correct including edge cases (apostrophes in values, empty strings, safe character set).
lib/src/complete/zsh.rs Extracts a render_completion_loop helper to DRY up the identical loop between complete_zsh and complete_zsh_init; minor 2-space vs 4-space indentation inconsistency in the init script output, but functionally correct.
cli/assets/completions/_usage Updated bundled completion for usage itself to use the new two-column loop; consistent with all other generated scripts.
cli/tests/shell_completions_integration.rs Adds test_zsh_completion_quotes_choices_with_spaces regression test covering the raw output format, simple unquoted case, and generated script structure; existing format assertions updated to the new tab-separated form.
cli/tests/complete_word.rs Unit tests updated to expect the new display\tinsert format; assertions are now exact string matches rather than substring checks, which is stricter and better.

Reviews (3): Last reviewed commit: "chore: regenerate cli/assets/completions..." | Re-trigger Greptile

Comment thread cli/tests/shell_completions_integration.rs Outdated
@codecov
Copy link
Copy Markdown

codecov Bot commented May 13, 2026

Codecov Report

❌ Patch coverage is 34.48276% with 38 lines in your changes missing coverage. Please review.
✅ Project coverage is 75.34%. Comparing base (697c25c) to head (d1f53e2).

Files with missing lines Patch % Lines
lib/src/complete/zsh.rs 32.35% 11 Missing and 12 partials ⚠️
cli/src/cli/complete_word.rs 37.50% 8 Missing and 7 partials ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #635      +/-   ##
==========================================
- Coverage   78.94%   75.34%   -3.60%     
==========================================
  Files          49       49              
  Lines        7284     7389     +105     
  Branches     7284     7389     +105     
==========================================
- Hits         5750     5567     -183     
- Misses       1147     1217      +70     
- Partials      387      605     +218     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link
Copy Markdown

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit b07823f. Configure here.

Comment thread lib/src/complete/zsh.rs Outdated
jdx added 3 commits May 13, 2026 10:05
…late

Replaces the inline shell parser that hand-rolled escape-aware splitting
of `value:description` strings with a tab-separated `<display>\t<insert>`
format emitted directly by `usage complete-word --shell zsh`.

Now the Rust side decides how to shell-quote each value (single quotes
when needed, raw otherwise) and the shell side is reduced to a four-line
loop that pushes the two columns into parallel arrays. `-U` is added to
`_describe` so it doesn't re-filter the already-prefix-filtered matches
returned by `complete-word`, which would otherwise discard them because
their literal text starts with `'`.

Behavior is unchanged from the previous commit's fix for #634; this is
purely a structural cleanup.
The per-bin completion script (`complete_zsh`) and the shebang-fallback
handler (`complete_zsh_init`) both emit the same loop that reads
`<display>\t<insert>` columns and hands them to `_describe`. Factor
that into `render_completion_loop` so a future change to the matching
or quoting logic only has to be made in one place.

Behavior unchanged.
@jdx jdx merged commit cd0fd34 into main May 13, 2026
6 checks passed
@jdx jdx deleted the claude/silly-blackwell-e61338 branch May 13, 2026 15:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

choices sometimes wraps in quotes, not always

1 participant