feat: add shell completion built-in (generate + install)#30
Merged
Conversation
Add a framework-level `completion` built-in so every consumer CLI gets shell completion for free, alongside the existing help/tree/guide built-ins. - `<bin> completion [shell]` prints a raw completion script to stdout (bypasses the JSON envelope via CliRunOutput.rendered). - `<bin> completion --install [shell]` installs the script to the shell's auto-load location and idempotently edits the rc file via a managed marker block. - Supports bash, zsh, fish, powershell, and elvish; auto-detects the shell from $SHELL when omitted. - Drives generation from the engine's own clap command tree via clap_complete. - All writes go through fs::write_string_atomic inside spawn_blocking; adds fs::home_dir() for bare-$HOME resolution. The completion module is pub(crate); the built-in is always-on with no opt-in toggle. Install is install-only (no uninstall) and idempotent.
There was a problem hiding this comment.
Pull request overview
Adds a framework-level completion built-in to cli-engine, enabling consumer CLIs to generate and install shell completion scripts driven from the engine’s clap command tree.
Changes:
- Adds a new
completionbuilt-in command with script generation and per-shell install behavior. - Introduces
fs::home_dir()for consistent$HOME/%USERPROFILE%resolution and updates tests accordingly. - Adds integration + consumer tests, documentation, and a new
clap_completedependency.
Reviewed changes
Copilot reviewed 8 out of 9 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/consumer_cli.rs | Adds consumer-CLI coverage for completion output and unknown-shell errors. |
| tests/completion.rs | Adds hermetic integration tests for printing, autodetect, install, and idempotency. |
| src/fs.rs | Adds home_dir() helper and unit tests for env + absolute-path behavior. |
| src/cli/completion.rs | Implements completion shell parsing/detection, script generation, rc managed-block logic, and install paths. |
| src/cli/builtins.rs | Registers clap args for the new completion built-in and tests parsing. |
| src/cli.rs | Wires the completion built-in into the root command and dispatch path. |
| docs/completion.md | Documents usage, install behavior, and shell-specific locations. |
| Cargo.toml | Adds clap_complete dependency. |
| Cargo.lock | Locks clap_complete and updates dependency graph. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Resolve five Copilot review findings on the completion built-in: - parse_shell: accept the `pwsh` alias for PowerShell Core. - detect_shell: split $SHELL basename on both '/' and '\' and strip a trailing `.exe` so Windows-style paths (e.g. `...\pwsh.exe`) resolve. - apply_managed_block: locate the end marker only after the begin marker so a stray earlier end marker cannot delete unrelated rc content. - cli dispatch: remove a stale "stub until T6 lands" TODO comment. - docs: sync the zsh note to the emitted `autoload -Uz compinit && compinit` snippet. Add tests for the pwsh alias, Windows/.exe basename handling, and the stray-end-marker safety case.
- completion install (powershell): normalize spliced rc content to LF before converting to CRLF, so an existing CRLF profile is not corrupted into `\r\r\n`. - docs: correct the bash note to describe the managed `source` line that sources the generated script directly (not `bash-completion`). - tests: replace manual env-var save/restore with a Drop-based EnvVarGuard so a panicking assertion cannot leak HOME/XDG_* into other tests.
- render_completion_print: route errors through render_cli_error so
--output json is respected and exit codes follow the engine policy
(was returning bare format!("error: {e}") with hard-coded exit 1)
- generate_script: return Result<String> instead of using
from_utf8_lossy, which silently corrupted scripts on non-UTF-8 output
- apply_managed_block: remove orphaned begin marker before appending
when end marker is absent, preventing duplicate markers on re-runs
- shell_basename: strip version suffixes like "-5.9" so paths such as
/usr/bin/zsh-5.9 are recognized during shell auto-detection
- zsh install: check $ZDOTDIR before falling back to $HOME/.zshrc so
the managed block lands in the file zsh actually sources
- BUILTIN_COMMAND_NAMES: hoist to module-level const; use it in both
refresh_root_long and collect_command_search_documents instead of a
second hardcoded "completion" string literal
- with_module / with_modules: document the four reserved group names
so consumers know what top-level names to avoid
- Shell enum: restore #[allow(clippy::enum_variant_names)] with a
comment explaining it is needed because PowerShell ends with Shell
- tests: add shell_basename_strips_version_suffix,
managed_block_replaces_orphaned_begin_marker,
install_zsh_respects_zdotdir; clarify integration-test duplication comment
- read_rc: return Result instead of silently treating permission errors
as empty content; prevents write_string_atomic from overwriting an
unreadable rc file with only the managed block
- install: restructure to compute all paths (env var reads) on the
async thread, then run all file I/O in a single spawn_blocking closure
so the executor thread is never blocked
- install: introduce RcSpec struct to carry path/body/crlf cleanly
across the async/blocking boundary without double-calling completion_args
- write_string_atomic: only apply 0700 chmod to newly-created directories;
pre-existing directories (e.g. $HOME) are left unchanged
- add_module_group: reject reserved built-in names ("completion", "help",
"guide", "tree") with a tracing::warn so consumer modules cannot shadow
engine built-ins in the clap command tree
- shell_basename: use rfind so "fish-shell-3.7" yields "fish-shell"
rather than "fish"
- apply_managed_block: consume \r\n (2 bytes) when removing an orphaned
begin marker on CRLF content, not just \n (1 byte)
- fix double completion_args() call in the completion dispatch block
- add install tests for Elvish and PowerShell (CRLF, idempotency, no
double-CR on repeated install)
jgowdy-godaddy
approved these changes
Jun 25, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds a framework-level
completionbuilt-in to cli-engine so every consumer CLI gets shell completion for free, alongside the existinghelp/tree/guidebuilt-ins. This moves the tab-completion capability (previously prototyped in a consumer CLI) into the engine itself.What it does
<bin> completion [shell]— prints a raw completion script to stdout. Output bypasses the JSON envelope viaCliRunOutput.rendered, so the script is emitted verbatim.<bin> completion --install [shell]— installs the script to the shell's auto-load location and idempotently edits the rc file via a managed marker block (# >>> <bin> completion (managed) >>>…# <<< <bin> completion (managed) <<<).$SHELLwhen the argument is omitted (Windows → PowerShell).clapcommand tree viaclap_complete.Per-shell install layout
$XDG_DATA_HOME/bash-completion/completions/<bin>~/.bashrc(source)~/.zfunc/_<bin>~/.zshrc(fpath + compinit)$XDG_CONFIG_HOME/fish/completions/<bin>.fish$XDG_CONFIG_HOME/elvish/lib/<bin>-completion.elv$XDG_CONFIG_HOME/elvish/rc.elv(use)~/Documents/PowerShell/<bin>-completion.ps1$PROFILE(dot-source, CRLF)Design notes
pub(crate)— no new public Rust API surface beyondfs::home_dir().completionis therefore a reserved subcommand name.fs::write_string_atomicinsidespawn_blocking(atomic rename,0600/0700perms on Unix). Addsfs::home_dir()for bare-$HOMEresolution.Testing
tests/completion.rs(all 5 shells print raw scripts,$SHELLauto-detect,--installwrites + idempotency, unknown-shell errors cleanly without panic). Tests pinHOME/XDG_*to tempdirs.fs::home_dir, the clap arg definitions, and the managed-block logic.cargo fmt --all --check,cargo clippy --all-targets -- -D warnings,RUSTDOCFLAGS='-D warnings' cargo doc --no-deps,cargo test --all-targets(all pass),cargo rustdoc --lib -- -W missing-docs(0).Docs
Adds
docs/completion.mddocumenting usage, supported shells, per-shell install locations, idempotency, and the reserved built-in name.