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
27 changes: 25 additions & 2 deletions e2e/tasks/test_task_completion_global_cd
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
#!/usr/bin/env bash
# Regression test for https://github.com/jdx/mise/discussions/10069
# Completion must work when the global -C/--cd flag precedes `run` and the
# task has a positional arg with choices.
# Completion must work when a root global flag (e.g. -C/--cd) precedes `run`, or
# a `run` flag precedes the task, when the task has a positional arg with
# choices. -C/--cd, -j/--jobs and -q/--quiet are handled by the upstream usage
# parser fix (jdx/usage#649). -r/--raw and -S/--silent still need mise's
# completion-spec promotion: their root globals are long-only, so the parser
# would drop the -r/-S shorts that only exist on the non-global `run`
# redeclarations.

cat <<'EOF' >mise.toml
[tools]
Expand Down Expand Up @@ -29,3 +34,21 @@ gamma gamma"
assert "mise exec -- usage complete-word --shell zsh -f ./mise.usage.kdl -- mise -C . tasks run sample:run -- ''" "alpha alpha
beta beta
gamma gamma"

# orphan-short flags: -r/--raw and -S/--silent have no short on the root global,
# so usage#649 alone would drop the short. The completion-spec promotion keeps
# them recognized before the mounted task (previously errored: unexpected word).
assert "mise exec -- usage complete-word --shell zsh -f ./mise.usage.kdl -- mise run -r sample:run -- ''" "alpha alpha
beta beta
gamma gamma"
assert "mise exec -- usage complete-word --shell zsh -f ./mise.usage.kdl -- mise run -S sample:run -- ''" "alpha alpha
beta beta
gamma gamma"

# and the same two over the `tasks run` path
assert "mise exec -- usage complete-word --shell zsh -f ./mise.usage.kdl -- mise tasks run -r sample:run -- ''" "alpha alpha
beta beta
gamma gamma"
assert "mise exec -- usage complete-word --shell zsh -f ./mise.usage.kdl -- mise tasks run -S sample:run -- ''" "alpha alpha
beta beta
gamma gamma"
14 changes: 7 additions & 7 deletions mise.usage.kdl
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
min_usage_version "2.11"
min_usage_version "3.4"
name mise
bin mise
about "Dev tools, env vars, and tasks in one CLI"
Expand Down Expand Up @@ -2279,15 +2279,15 @@ Examples:

"""#
flag "-c --continue-on-error" help="Continue running tasks even if one fails"
flag "-C --cd" help="Change to this directory before executing the command" global=#true {
flag "-C --cd" help="Change to this directory before executing the command" {
arg <CD>
}
flag "-f --force" help="Force the tasks to run even if outputs are up to date"
flag "-j --jobs" help=#"""
Number of tasks to run in parallel
[default: 4]
Configure with `jobs` config or `MISE_JOBS` env var
"""# global=#true {
"""# {
arg <JOBS>
}
flag "-n --dry-run" help="Don't actually run the task(s), just print them in order of execution"
Expand All @@ -2305,7 +2305,7 @@ Change how tasks information is output when running tasks
"""#
arg <OUTPUT>
}
flag "-q --quiet" help="Don't show extra output" global=#true
flag "-q --quiet" help="Don't show extra output"
flag "-r --raw" help=#"""
Read/write directly to stdin/stdout/stderr instead of by line
Redactions are not applied with this option
Expand Down Expand Up @@ -3074,15 +3074,15 @@ Examples:

"""#
flag "-c --continue-on-error" help="Continue running tasks even if one fails"
flag "-C --cd" help="Change to this directory before executing the command" global=#true {
flag "-C --cd" help="Change to this directory before executing the command" {
arg <CD>
}
flag "-f --force" help="Force the tasks to run even if outputs are up to date"
flag "-j --jobs" help=#"""
Number of tasks to run in parallel
[default: 4]
Configure with `jobs` config or `MISE_JOBS` env var
"""# global=#true {
"""# {
arg <JOBS>
}
flag "-n --dry-run" help="Don't actually run the task(s), just print them in order of execution"
Expand All @@ -3100,7 +3100,7 @@ Change how tasks information is output when running tasks
"""#
arg <OUTPUT>
}
flag "-q --quiet" help="Don't show extra output" global=#true
flag "-q --quiet" help="Don't show extra output"
flag "-r --raw" help=#"""
Read/write directly to stdin/stdout/stderr instead of by line
Redactions are not applied with this option
Expand Down
47 changes: 28 additions & 19 deletions src/cli/usage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,20 +19,24 @@ impl Usage {
// Enable "naked" task completions: `mise foo` completes like `mise run foo`
spec.default_subcommand = Some("run".to_string());

// Promote completion-spec flags that collide with a root-level global flag
// (e.g. `-C`/`--cd`) to global on the mounted `run`/`tasks run` subcommands.
// The `run`/`tasks run` subcommands redeclare some root globals as their own
// non-global flags (see `cli::run::Run`). When the usage parser descends into
// a mounted task subcommand it keeps only inherited global flags, so a leading
// `mise -C <dir> run <task>` (or `mise run <flag> <task>`) used to mis-parse
// the redeclared flag during completion. See mise#10069.
//
// The `run` subcommand redeclares some root globals as its own non-global
// flags (notably `-C`/`--cd`, see `cli::run::Run::cd`). When the usage parser
// descends into a mounted task subcommand it keeps only `global` flags
// (`available_flags.retain(|_, f| f.global)`), so the non-global redeclaration
// causes the inherited global to be dropped. A leading `mise -C <dir> run
// <task> ...` then mis-parses `-C` as the task's positional arg during
// completion. Marking the colliding flags global here (completion-spec only,
// no effect on clap runtime parsing) keeps them recognized. See mise#10069.
// jdx/usage#649 fixes the common case in the parser: when a subcommand
// redeclares an inherited global as non-global, the inherited global (with all
// its aliases) is now preserved. That covers `-C`/`--cd`, `-j`/`--jobs`, and
// `-q`/`--quiet`, whose short *is* a root global short, so no spec workaround
// is needed for them anymore.
//
// Collect the root global flag identifiers up front so the immutable borrow
// of `spec.cmd.flags` is released before the subcommands are borrowed mutably.
// It cannot cover `-r`/`--raw` and `-S`/`--silent`: the root globals are
// long-only (`--raw`/`--silent` have no short, see `cli::Cli`), and the `-r`/
// `-S` shorts live only on the non-global `run` redeclarations. The parser
// preserves the short-less root global and drops the redeclaration, losing the
// short. So we still promote just those "orphan short" flags to global in the
// completion spec (spec-only; clap runtime parsing is unchanged).
let global_shorts: HashSet<char> = spec
.cmd
.flags
Expand All @@ -47,11 +51,13 @@ impl Usage {
.filter(|f| f.global)
.flat_map(|f| f.long.iter().cloned())
.collect();
let promote = |cmd: &mut usage::SpecCommand| {
// Promote a redeclared flag only when it carries a short alias that the
// matching root global lacks (so jdx/usage#649 would otherwise drop it).
let promote_orphan_shorts = |cmd: &mut usage::SpecCommand| {
for f in cmd.flags.iter_mut() {
if f.short.iter().any(|c| global_shorts.contains(c))
|| f.long.iter().any(|l| global_longs.contains(l))
{
let long_is_root_global = f.long.iter().any(|l| global_longs.contains(l));
let has_orphan_short = f.short.iter().any(|c| !global_shorts.contains(c));
if long_is_root_global && has_orphan_short {
f.global = true;
}
}
Expand All @@ -64,7 +70,7 @@ impl Usage {
});
// Enable completions after ::: separator for multi-task invocations
run.restart_token = Some(":::".to_string());
promote(run);
promote_orphan_shorts(run);
}

if let Some(tasks_run) = spec
Expand All @@ -77,10 +83,13 @@ impl Usage {
run: "mise tasks --usage".to_string(),
});
tasks_run.restart_token = Some(":::".to_string());
promote(tasks_run);
promote_orphan_shorts(tasks_run);
}

let min_version = r#"min_usage_version "2.11""#;
// Require usage >= 3.4, the release that ships the jdx/usage#649 parser fix
// (see jdx/usage#652). This guards old `usage` CLIs from silently
// re-triggering the mise#10069 mis-parse without the fix.
let min_version = r#"min_usage_version "3.4""#;
let extra = include_str!("../assets/mise-extra.usage.kdl").trim();
println!("{min_version}\n{}\n{extra}", spec.to_string().trim());
Ok(())
Expand Down
Loading