feat(completion): unify shell completions behind an opt-in `task __complete` engine
Bash, Fish, Zsh, Nushell and PowerShell now share a single backend: `task __complete` returns the suggestions plus a directive, and every wrapper is a thin shim around it. All five shells offer the same suggestions — task names, aliases, flags, flag values and per-task CLI variables. The Zsh `show-aliases` and `verbose` zstyles keep working, now backed by the `--no-aliases` and `--no-descriptions` completion flags.
The engine is opt-in via `task --new-completion <shell>`, leaving `--completion` and the legacy scripts untouched; it will become the default in a future release. The new wrappers live under `completion/next/`.
Completing a keystroke never reaches the network, never blocks on a stdin entrypoint, and honors every flag that decides how the Taskfile is loaded. Ref resolution shared by `requires` and enum completion moved to `internal/refs`.
A cross-shell test suite exercises the protocol in Go with thin shell smoke tests, and runs in CI.
2026-08-20 13:56:01 +02:00
|
|
|
# vim: set tabstop=2 shiftwidth=2 expandtab:
|
|
|
|
|
#
|
|
|
|
|
# Thin wrapper around `task __complete`: all suggestion logic lives in the Go engine.
|
|
|
|
|
|
|
|
|
|
TASK_CMD="${TASK_EXE:-task}"
|
|
|
|
|
|
|
|
|
|
# `=` stays inside the current word (see `_init_completion -n =:`), so an inline
|
|
|
|
|
# `--flag=` prefix must be stripped before _filedir and re-applied after.
|
|
|
|
|
_task_filedir() {
|
|
|
|
|
local fpfx="" savecur="$cur"
|
|
|
|
|
if [[ "$cur" == -*=* ]]; then
|
|
|
|
|
fpfx="${cur%%=*}="
|
|
|
|
|
cur="${cur#*=}"
|
|
|
|
|
fi
|
|
|
|
|
_filedir ${1:+"$1"}
|
|
|
|
|
cur="$savecur"
|
|
|
|
|
if [[ -n "$fpfx" ]]; then
|
|
|
|
|
COMPREPLY=( ${COMPREPLY[@]+"${COMPREPLY[@]/#/$fpfx}"} )
|
|
|
|
|
fi
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_task() {
|
|
|
|
|
local cur prev words cword
|
|
|
|
|
|
|
|
|
|
# Completion directives, mirroring internal/complete/complete.go.
|
fix(completion): complete task names and required vars together
CLI variables are global to the invocation, not scoped to a task: `args.Parse` turns every word holding `=` into a global and every other word into a call, so `task build ENV=dev deploy` and `task build deploy ENV=dev` are the same command. The engine assumed the opposite and, as soon as a word matched a task, served only that task's variables — nothing at all when it had none. `task build <TAB>` and even `task build de<TAB>` went silent, where all five legacy wrappers offered task names at every position.
The engine now unions the still-unset requirements of every task named on the line, and falls through to task names once they are all set. The line resolves itself: fill in what blocks execution, then add another task. Keeping the two families exclusive means each keeps a coherent directive, so nothing loses its trailing space.
Task words are matched with FindMatchingTasks instead of a hand-built list of names truncated at their first `*`, which is why `task wildcard-foo <TAB>` used to offer task names rather than the variables of `wildcard-*`. Completion also disables fuzzy matching: a suggestion list has no "did you mean".
Three fixes ride along. `--sort default` left the sorter nil and cleared the one NewExecutor had set, so completion listed tasks in Taskfile order while `--list` sorted them — and a single templated description silently restored the sort through GetTaskList. The bash wrapper never defined KeepOrder, losing the declaration order of `requires`; it now passes `compopt -o nosort`, which bash 3.2 ignores as it already ignores nospace. And the shell suite unsets TASK_EXE and GO_TASK_PROGNAME: fish, Nushell and PowerShell resolve the binary through them, so an ambient value silently tested something other than the binary just built.
2026-08-20 16:11:49 +02:00
|
|
|
local -ri NO_SPACE=2 NO_FILE_COMP=4 FILTER_FILE_EXT=8 FILTER_DIRS=16 KEEP_ORDER=32
|
feat(completion): unify shell completions behind an opt-in `task __complete` engine
Bash, Fish, Zsh, Nushell and PowerShell now share a single backend: `task __complete` returns the suggestions plus a directive, and every wrapper is a thin shim around it. All five shells offer the same suggestions — task names, aliases, flags, flag values and per-task CLI variables. The Zsh `show-aliases` and `verbose` zstyles keep working, now backed by the `--no-aliases` and `--no-descriptions` completion flags.
The engine is opt-in via `task --new-completion <shell>`, leaving `--completion` and the legacy scripts untouched; it will become the default in a future release. The new wrappers live under `completion/next/`.
Completing a keystroke never reaches the network, never blocks on a stdin entrypoint, and honors every flag that decides how the Taskfile is loaded. Ref resolution shared by `requires` and enum completion moved to `internal/refs`.
A cross-shell test suite exercises the protocol in Go with thin shell smoke tests, and runs in CI.
2026-08-20 13:56:01 +02:00
|
|
|
|
|
|
|
|
# `=` and `:` out of the word breaks: `--output=`, `docs:serve` stay one token.
|
|
|
|
|
_init_completion -n =: || return
|
|
|
|
|
|
|
|
|
|
local -a args=( "${words[@]:1:cword}" )
|
|
|
|
|
if (( ${#args[@]} == 0 )); then
|
|
|
|
|
args=( "" )
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
local output
|
|
|
|
|
output=$("$TASK_CMD" __complete "${args[@]}" 2>/dev/null)
|
|
|
|
|
if [[ -z "$output" ]]; then
|
|
|
|
|
_task_filedir
|
|
|
|
|
return
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
local -a lines=()
|
|
|
|
|
local line
|
|
|
|
|
while IFS= read -r line; do
|
|
|
|
|
lines+=( "$line" )
|
|
|
|
|
done <<< "$output"
|
|
|
|
|
|
|
|
|
|
local last_idx=$(( ${#lines[@]} - 1 ))
|
|
|
|
|
local directive="${lines[$last_idx]#:}"
|
|
|
|
|
unset 'lines[$last_idx]'
|
|
|
|
|
|
|
|
|
|
if (( directive & FILTER_FILE_EXT )); then
|
|
|
|
|
local exts=""
|
|
|
|
|
# ${arr[@]+…} guards an empty array under `set -u` in bash 3.2 (macOS).
|
|
|
|
|
for line in ${lines[@]+"${lines[@]}"}; do
|
|
|
|
|
exts+="${exts:+|}$line"
|
|
|
|
|
done
|
|
|
|
|
_task_filedir "@($exts)"
|
|
|
|
|
return
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
if (( directive & FILTER_DIRS )); then
|
|
|
|
|
_task_filedir -d
|
|
|
|
|
return
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
# Not `compgen -W`: it splits the word list on IFS, mangling values with spaces.
|
|
|
|
|
local value
|
|
|
|
|
COMPREPLY=()
|
|
|
|
|
for line in ${lines[@]+"${lines[@]}"}; do
|
|
|
|
|
value="${line%%$'\t'*}"
|
|
|
|
|
if [[ -z "$cur" || "$value" == "$cur"* ]]; then
|
|
|
|
|
COMPREPLY+=( "$value" )
|
|
|
|
|
fi
|
|
|
|
|
done
|
|
|
|
|
|
|
|
|
|
if (( directive & NO_SPACE )); then
|
|
|
|
|
compopt -o nospace 2>/dev/null
|
|
|
|
|
fi
|
|
|
|
|
|
fix(completion): complete task names and required vars together
CLI variables are global to the invocation, not scoped to a task: `args.Parse` turns every word holding `=` into a global and every other word into a call, so `task build ENV=dev deploy` and `task build deploy ENV=dev` are the same command. The engine assumed the opposite and, as soon as a word matched a task, served only that task's variables — nothing at all when it had none. `task build <TAB>` and even `task build de<TAB>` went silent, where all five legacy wrappers offered task names at every position.
The engine now unions the still-unset requirements of every task named on the line, and falls through to task names once they are all set. The line resolves itself: fill in what blocks execution, then add another task. Keeping the two families exclusive means each keeps a coherent directive, so nothing loses its trailing space.
Task words are matched with FindMatchingTasks instead of a hand-built list of names truncated at their first `*`, which is why `task wildcard-foo <TAB>` used to offer task names rather than the variables of `wildcard-*`. Completion also disables fuzzy matching: a suggestion list has no "did you mean".
Three fixes ride along. `--sort default` left the sorter nil and cleared the one NewExecutor had set, so completion listed tasks in Taskfile order while `--list` sorted them — and a single templated description silently restored the sort through GetTaskList. The bash wrapper never defined KeepOrder, losing the declaration order of `requires`; it now passes `compopt -o nosort`, which bash 3.2 ignores as it already ignores nospace. And the shell suite unsets TASK_EXE and GO_TASK_PROGNAME: fish, Nushell and PowerShell resolve the binary through them, so an ambient value silently tested something other than the binary just built.
2026-08-20 16:11:49 +02:00
|
|
|
# nosort needs bash 4.4; the 3.2 shipped by macOS ignores it and stays sorted.
|
|
|
|
|
if (( directive & KEEP_ORDER )); then
|
|
|
|
|
compopt -o nosort 2>/dev/null
|
|
|
|
|
fi
|
|
|
|
|
|
feat(completion): unify shell completions behind an opt-in `task __complete` engine
Bash, Fish, Zsh, Nushell and PowerShell now share a single backend: `task __complete` returns the suggestions plus a directive, and every wrapper is a thin shim around it. All five shells offer the same suggestions — task names, aliases, flags, flag values and per-task CLI variables. The Zsh `show-aliases` and `verbose` zstyles keep working, now backed by the `--no-aliases` and `--no-descriptions` completion flags.
The engine is opt-in via `task --new-completion <shell>`, leaving `--completion` and the legacy scripts untouched; it will become the default in a future release. The new wrappers live under `completion/next/`.
Completing a keystroke never reaches the network, never blocks on a stdin entrypoint, and honors every flag that decides how the Taskfile is loaded. Ref resolution shared by `requires` and enum completion moved to `internal/refs`.
A cross-shell test suite exercises the protocol in Go with thin shell smoke tests, and runs in CI.
2026-08-20 13:56:01 +02:00
|
|
|
__ltrim_colon_completions "$cur"
|
|
|
|
|
|
|
|
|
|
if (( ${#COMPREPLY[@]} == 0 )) && ! (( directive & NO_FILE_COMP )); then
|
|
|
|
|
_task_filedir
|
|
|
|
|
fi
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
complete -F _task "$TASK_CMD"
|