Skip to content

fix(zsh): remove trailing space from completions and add directory slash#556

Merged
jdx merged 4 commits intomainfrom
fix/zsh-completion-trailing-space
Mar 22, 2026
Merged

fix(zsh): remove trailing space from completions and add directory slash#556
jdx merged 4 commits intomainfrom
fix/zsh-completion-trailing-space

Conversation

@jdx
Copy link
Owner

@jdx jdx commented Mar 22, 2026

Summary

  • Use _describe with -S '' instead of _arguments in zsh completion template to prevent unwanted trailing spaces after completions (e.g., node@ no longer gets a space appended)
  • Append trailing / to directory entries in complete_path so directory completions show /opt/homebrew/ instead of /opt/homebrew
  • Change zsh complete-word output format from 'val'\:'desc' to val:desc to match _describe expectations

Closes #67

Test plan

  • Added test_zsh_complete_word_output_format — verifies new name:description output format
  • Added test_complete_path_adds_trailing_slash_for_directories — verifies dirs get / suffix, files don't
  • Updated zsh completion snapshots
  • All existing tests pass

🤖 Generated with Claude Code


Note

Medium Risk
Changes zsh completion generation and complete-word output formatting, which can break shell completion behavior if the new format is not accepted across zsh setups. Also tweaks path completion output (adds / for directories), affecting user-visible CLI suggestions.

Overview
Fixes zsh completions to avoid unwanted trailing spaces by switching the generated zsh completion scripts from _arguments command substitution to collecting candidates and feeding them into _describe with -S ''.

Updates usage complete-word --shell zsh to emit value:description (with : escaped) to match _describe expectations, and enhances path completion to append / to directory candidates. Adds integration tests covering the new zsh output format and directory-slash behavior, and updates zsh completion snapshots accordingly.

Written by Cursor Bugbot for commit fc5ec7a. This will update automatically on new commits. Configure here.

Use _describe with -S '' instead of _arguments to prevent zsh from
adding unwanted trailing spaces after completions. This fixes partial
completions like `node@` and directory paths like `/opt/homebrew/`.
Also append trailing `/` to directory entries in complete_path.

Closes #67

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@gemini-code-assist
Copy link

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request refines the Zsh shell completion experience by addressing two key areas: the removal of extraneous trailing spaces after completions and the consistent addition of trailing slashes for directory suggestions. These changes enhance the accuracy and user-friendliness of Zsh completions, particularly for path and command arguments, and are supported by updated internal logic and new integration tests.

Highlights

  • Zsh Completion Spacing: Eliminated unwanted trailing spaces after Zsh completions by switching from _arguments to _describe with the -S '' option in the completion template.
  • Directory Trailing Slashes: Ensured that directory completions in Zsh now correctly include a trailing slash, improving usability and visual consistency for path suggestions.
  • Zsh Output Format: Modified the complete-word output format for Zsh from 'val'\:'desc' to val:desc to align with _describe expectations.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@codecov
Copy link

codecov bot commented Mar 22, 2026

Codecov Report

❌ Patch coverage is 16.66667% with 15 lines in your changes missing coverage. Please review.
✅ Project coverage is 72.74%. Comparing base (00210b2) to head (a5097c9).
⚠️ Report is 3 commits behind head on main.

Files with missing lines Patch % Lines
cli/src/cli/complete_word.rs 16.66% 1 Missing and 14 partials ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #556      +/-   ##
==========================================
+ Coverage   72.40%   72.74%   +0.34%     
==========================================
  Files          48       48              
  Lines        6830     6983     +153     
  Branches     6830     6983     +153     
==========================================
+ Hits         4945     5080     +135     
+ Misses       1242     1233       -9     
- Partials      643      670      +27     

☔ 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

@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 correctly addresses issues with zsh completions by switching to _describe to remove trailing spaces and adds logic to append a trailing slash to directory completions. The changes are accompanied by new tests that verify the fixes. I have one suggestion to improve the performance of path completion.

Comment on lines 338 to 350
.map(|de| de.path())
.filter(|p| filter(p))
.map(|p| {
p.strip_prefix(base)
let mut s = p
.strip_prefix(base)
.unwrap_or(&p)
.to_string_lossy()
.to_string()
.to_string();
if p.is_dir() {
s.push('/');
}
s
})

Choose a reason for hiding this comment

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

medium

For better performance, we can avoid extra stat syscalls by using DirEntry::file_type(). This information is often available without a separate syscall after read_dir, which can make path completions noticeably faster, especially in large directories. The current implementation converts DirEntry to Path and then calls is_dir(), which issues a syscall for every entry. By preserving the DirEntry, we can optimize this.

Suggested change
.map(|de| de.path())
.filter(|p| filter(p))
.map(|p| {
p.strip_prefix(base)
let mut s = p
.strip_prefix(base)
.unwrap_or(&p)
.to_string_lossy()
.to_string()
.to_string();
if p.is_dir() {
s.push('/');
}
s
})
.filter(|de| filter(&de.path()))
.map(|de| {
let p = de.path();
let mut s = p
.strip_prefix(base)
.unwrap_or(&p)
.to_string_lossy()
.to_string();
if de.file_type().map(|ft| ft.is_dir()).unwrap_or_else(|_| p.is_dir()) {
s.push('/');
}
s
})

@greptile-apps
Copy link

greptile-apps bot commented Mar 22, 2026

Greptile Summary

This PR fixes two related zsh completion UX issues: trailing spaces appended after completions (e.g., node@<space>) and missing / on directory completions. It does so by switching the generated zsh completion script from the _arguments "$(...)" command-substitution pattern to populating a local array with while IFS= read and calling _describe 'completions' completions -S '', updating the complete-word --shell zsh output format from the old 'val'\:'desc' quoting to the val:desc colon-separated format that _describe expects, and appending / to directory entries in complete_path using DirEntry::file_type() (with a fallback to Path::is_dir() on error).

  • cli/assets/completions/_usage / lib/src/complete/zsh.rs: Old _arguments "*(( $(...) ))" pattern replaced by while IFS= read + _describe; the -S '' suffix-suppressor is passed without the -- separator (addressing the prior review concern).
  • cli/src/cli/complete_word.rs: Zsh output format simplified from shell-escaped single-quotes to plain val:desc, with : in values and descriptions escaped as \: via .replace(':', "\\:"). The complete_path refactor calls de.path() in both the new .filter() closure and inside the subsequent .map() closure, allocating a PathBuf twice per entry — consolidating into filter_map would halve that overhead (P2 comment left).
  • Snapshots: All three zsh snapshots updated consistently with the new template.
  • Tests: New integration tests verify the val:desc format and the trailing-slash-for-directories behaviour; both are Rust-level tests against complete-word output rather than live zsh tests, which is a reasonable trade-off for CI.

Confidence Score: 4/5

  • PR is on a clear path to merge; prior review concerns are addressed and the implementation is clean.
  • The three main changes (array-based _describe, val:desc format, trailing slash for dirs) are logically consistent, tests are added for the new behaviours, and snapshots are updated. The one remaining P2 is a minor efficiency note (double PathBuf allocation) rather than a correctness issue. The only non-trivial uncertainty is whether _describe correctly threads -S '' through to compadd in all zsh versions, but that was already surfaced in a prior review thread and the developer has addressed it by removing the erroneous --.
  • cli/assets/completions/_usage and lib/src/complete/zsh.rs — worth manually validating the _describe -S '' suffix suppression against a live zsh session if not already done.

Important Files Changed

Filename Overview
cli/assets/completions/_usage Switches zsh completion from _arguments with $() substitution to populating a local array line-by-line and calling _describe with -S '' to suppress trailing spaces.
cli/src/cli/complete_word.rs Updates zsh output format from 'val':'desc' to val:desc (colon-separated, colons in value/description escaped as :) to match _describe expectations; adds trailing / to directory entries in complete_path using DirEntry::file_type().
lib/src/complete/zsh.rs Template updated to match the new _describe pattern; correctly uses local -a and reads each completion line into the array before calling _describe.
cli/tests/shell_completions_integration.rs Adds two new integration tests: one verifying the zsh val:desc output format, one verifying directory entries get a trailing slash while files do not.

Sequence Diagram

sequenceDiagram
    participant ZSH as Zsh Completion
    participant Script as _usage function
    participant UsageBin as usage binary
    participant Describe as _describe

    ZSH->>Script: Tab pressed in shell
    Script->>UsageBin: usage --usage-spec > spec_file
    Script->>UsageBin: complete-word --shell zsh -f spec_file -- words[@]
    Note over UsageBin: Formats output as val:desc
    Note over UsageBin: Appends / to directory paths
    UsageBin-->>Script: "sub:A subcommand"
    UsageBin-->>Script: "dir/: "
    UsageBin-->>Script: "file.txt"
    Note over Script: Reads lines into completions array
    Script->>Describe: _describe completions completions -S ""
    Note over Describe: -S suppresses trailing space
    Describe-->>ZSH: Rendered completions
Loading

Fix All in Claude Code

Reviews (2): Last reviewed commit: "[autofix.ci] apply automated fixes" | Re-trigger Greptile

jdx and others added 2 commits March 22, 2026 16:28
- Remove `--` before `-S ''` in _describe call so the option is
  correctly forwarded to compadd
- Use DirEntry::file_type() instead of Path::is_dir() to avoid
  extra stat syscalls in complete_path
- Move colon escaping inside the descriptions branch only

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Comment on lines +338 to 356
.filter(|de| filter(&de.path()))
.map(|de| {
let p = de.path();
let is_dir = de
.file_type()
.map(|ft| ft.is_dir())
.unwrap_or_else(|_| p.is_dir());
let mut s = p
.strip_prefix(base)
.unwrap_or(&p)
.to_string_lossy()
.to_string()
.to_string();
if is_dir {
s.push('/');
}
s
})
.sorted()
.collect()
Copy link

Choose a reason for hiding this comment

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

P2 de.path() called twice per entry

de.path() allocates a new PathBuf on each call. In the current chain it is called once in .filter(|de| filter(&de.path())) and again inside .map() via let p = de.path(). For large directories this doubles the allocation count unnecessarily.

Consider folding the filter into the map to call de.path() only once:

Suggested change
.filter(|de| filter(&de.path()))
.map(|de| {
let p = de.path();
let is_dir = de
.file_type()
.map(|ft| ft.is_dir())
.unwrap_or_else(|_| p.is_dir());
let mut s = p
.strip_prefix(base)
.unwrap_or(&p)
.to_string_lossy()
.to_string()
.to_string();
if is_dir {
s.push('/');
}
s
})
.sorted()
.collect()
.filter_map(|de| {
let p = de.path();
if !filter(&p) {
return None;
}
let is_dir = de
.file_type()
.map(|ft| ft.is_dir())
.unwrap_or_else(|_| p.is_dir());
let mut s = p
.strip_prefix(base)
.unwrap_or(&p)
.to_string_lossy()
.to_string();
if is_dir {
s.push('/');
}
Some(s)
})

Fix in Claude Code

@jdx jdx merged commit ae67b38 into main Mar 22, 2026
8 checks passed
@jdx jdx deleted the fix/zsh-completion-trailing-space branch March 22, 2026 16:40
jdx pushed a commit that referenced this pull request Mar 22, 2026
### 🚀 Features

- **(cli)** render all doc-related fields in --help output by
[@jdx](https://github.com/jdx) in
[#554](#554)
- **(cli)** support reading spec from stdin via --file - by
[@jdx](https://github.com/jdx) in
[#555](#555)

### 🐛 Bug Fixes

- **(zsh)** remove trailing space from completions and add directory
slash by [@jdx](https://github.com/jdx) in
[#556](#556)
- use field assignment for non-exhaustive Spec in benchmarks by
[@jdx](https://github.com/jdx) in
[#552](#552)

### 📦️ Dependency Updates

- update apple-actions/import-codesign-certs digest to fe74d46 by
[@renovate[bot]](https://github.com/renovate[bot]) in
[#550](#550)
- update codecov/codecov-action digest to 1af5884 by
[@renovate[bot]](https://github.com/renovate[bot]) in
[#551](#551)
- lock file maintenance by
[@renovate[bot]](https://github.com/renovate[bot]) in
[#547](#547)
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.

zsh completion adds incorrect spaces

1 participant