Skip to content

feat: add shell completion built-in (generate + install)#30

Merged
jgowdy-godaddy merged 6 commits into
mainfrom
feat/completion-builtin
Jun 25, 2026
Merged

feat: add shell completion built-in (generate + install)#30
jgowdy-godaddy merged 6 commits into
mainfrom
feat/completion-builtin

Conversation

@rmarkins-godaddy

Copy link
Copy Markdown
Contributor

Summary

Adds a framework-level completion built-in to cli-engine so every consumer CLI gets shell completion for free, alongside the existing help / tree / guide built-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 via CliRunOutput.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) <<<).
  • Supports bash, zsh, fish, powershell, elvish; auto-detects the shell from $SHELL when the argument is omitted (Windows → PowerShell).
  • Generation is driven from the engine's own clap command tree via clap_complete.

Per-shell install layout

Shell Script location rc edit
bash $XDG_DATA_HOME/bash-completion/completions/<bin> ~/.bashrc (source)
zsh ~/.zfunc/_<bin> ~/.zshrc (fpath + compinit)
fish $XDG_CONFIG_HOME/fish/completions/<bin>.fish none
elvish $XDG_CONFIG_HOME/elvish/lib/<bin>-completion.elv $XDG_CONFIG_HOME/elvish/rc.elv (use)
powershell ~/Documents/PowerShell/<bin>-completion.ps1 $PROFILE (dot-source, CRLF)

Design notes

  • The completion module is pub(crate) — no new public Rust API surface beyond fs::home_dir().
  • The built-in is always-on, no opt-in toggle (consistent with help/tree/guide). completion is therefore a reserved subcommand name.
  • Install is install-only (no uninstall) and idempotent — re-running replaces the managed block in place rather than appending.
  • All file writes go through fs::write_string_atomic inside spawn_blocking (atomic rename, 0600/0700 perms on Unix). Adds fs::home_dir() for bare-$HOME resolution.
  • No envelope changes; no direct stdout/stderr in production paths.

Testing

  • 11 hermetic integration tests in tests/completion.rs (all 5 shells print raw scripts, $SHELL auto-detect, --install writes + idempotency, unknown-shell errors cleanly without panic). Tests pin HOME/XDG_* to tempdirs.
  • 2 additional consumer-CLI tests; unit tests for fs::home_dir, the clap arg definitions, and the managed-block logic.
  • Verification: 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.md documenting usage, supported shells, per-shell install locations, idempotency, and the reserved built-in name.

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.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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 completion built-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_complete dependency.

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.

Comment thread src/cli/completion.rs
Comment thread src/cli/completion.rs
Comment thread src/cli/completion.rs Outdated
Comment thread src/cli.rs Outdated
Comment thread docs/completion.md Outdated
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.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 8 out of 9 changed files in this pull request and generated 3 comments.

Comment thread src/cli/completion.rs Outdated
Comment thread docs/completion.md Outdated
Comment thread tests/completion.rs Outdated
rmarkins-godaddy and others added 3 commits June 23, 2026 14:43
- 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 jgowdy-godaddy merged commit 021a45e into main Jun 25, 2026
2 checks passed
@jgowdy-godaddy jgowdy-godaddy deleted the feat/completion-builtin branch June 25, 2026 07:39
jgowdy-godaddy pushed a commit that referenced this pull request Jun 26, 2026
🤖 I have created a release *beep* *boop*
---


##
[0.3.3](cli-engine-v0.3.2...cli-engine-v0.3.3)
(2026-06-25)


### Features

* add shell completion built-in (generate + install)
([#30](#30))
([021a45e](021a45e))

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
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.

3 participants