Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 12 additions & 1 deletion cli/src/commands/base.rs
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,18 @@ pub async fn run_cli() -> std::io::Result<()> {
.unwrap_or_else(|e| {
out.err(ExitKind::Api, format!("failed to generate completions: {e}"))
});
io::stdout().write_all(&output.stdout).ok();
let mut stdout = io::stdout();
stdout.write_all(&output.stdout).ok();
// clap's dynamic zsh script registers the completer only when sourced; installed
// as an fpath autoload file it yields nothing on the first TAB. Bridge the autoload
// case so the function completes on its first invocation too.
if shell == Shell::Zsh {
stdout
.write_all(
b"\n[[ ${funcstack[1]} = _gradient ]] && _clap_dynamic_completer_gradient \"$@\"\n",
)
.ok();
}
}
MainCommands::Config { key, value } => {
set_get_value_from_string(key, value, false)
Expand Down
19 changes: 19 additions & 0 deletions cli/tests/completion.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,3 +45,22 @@ fn completion_zsh_registers_lowercase_binary() {
assert!(!script.contains("Gradient"), "zsh script: {script}");
assert!(script.contains("gradient"), "zsh script: {script}");
}

// clap's dynamic zsh script is built to be sourced; the Nix package installs it as an
// fpath autoload `_gradient` file, where the completer is otherwise registered only on
// the first TAB (producing nothing). The appended bridge must run it on first invocation.
#[test]
fn completion_zsh_bridges_autoload_first_tab() {
let output = Command::cargo_bin("gradient")
.unwrap()
.args(["completion", "zsh"])
.output()
.unwrap();

assert!(output.status.success());
let script = String::from_utf8(output.stdout).unwrap();
assert!(
script.contains(r#"[[ ${funcstack[1]} = _gradient ]] && _clap_dynamic_completer_gradient "$@""#),
"zsh script must bridge the autoload first-TAB case:\n{script}"
);
}
2 changes: 1 addition & 1 deletion docs/src/tests.md
Original file line number Diff line number Diff line change
Expand Up @@ -2516,7 +2516,7 @@ loading degrades silently when `/etc/ssl/certs` is missing.
CLI integration tests in `cli/tests/`:

- `download_attr.rs` - `gradient download '#attr' --json` writes the right files; `--json` without args returns a structured missing-argument envelope and exits 2.
- `completion.rs` - regression for the broken completion bin name: `gradient completion {bash,zsh}` must emit a script that registers against the real `gradient` binary (`-F _clap_complete_gradient gradient`) and never the capitalised `Gradient` app name, which silently disabled `gradient <TAB>`.
- `completion.rs` - regression for the broken completion bin name: `gradient completion {bash,zsh}` must emit a script that registers against the real `gradient` binary (`-F _clap_complete_gradient gradient`) and never the capitalised `Gradient` app name, which silently disabled `gradient <TAB>`. Also asserts the zsh script appends the autoload bridge (`[[ ${funcstack[1]} = _gradient ]] && _clap_dynamic_completer_gradient "$@"`) so the fpath autoload file the Nix package installs completes on the first TAB instead of only after a second.

Dynamic completer unit tests live in `cli/src/commands/completion.rs` (`#[cfg(test)]`,
wiremock-backed). They drive each completer core against a mock server and assert it
Expand Down
Loading