Files
task/completion/bash/task.bash
Valentin Maerten 7c90ee35f8 test(completion): add cross-shell completion test suite and CI job
Add completion/tests/ harnesses that exercise the engine protocol and every
shell wrapper without a TTY: the engine via `task __complete`, bash/zsh by
stubbing the completion-system helpers, fish via `complete -C`, and PowerShell
via the completion API. A `test:completion` task and a matching CI job (with a
strict mode that fails when an expected shell is missing) run them all.

Writing the suite surfaced and fixed real wrapper bugs:
- fish: `math` has no bitwise `&`, so every directive check errored; test bits
  with integer division + modulo instead. Also filter FilterFileExt results
  ourselves, as __fish_complete_suffix only prioritizes the extension.
- powershell: `Get-ChildItem -Include` is ignored without -Recurse, so file
  extension filtering returned nothing; filter with Where-Object instead.
- bash: guard empty-array expansion so completion with zero candidates does not
  trip `set -u` on bash 3.2.

Also name the completion directive bits (NO_SPACE, FILTER_FILE_EXT, …) in every
wrapper instead of using raw numbers.
2026-07-03 22:29:42 +02:00

82 lines
2.0 KiB
Bash

# vim: set tabstop=2 shiftwidth=2 expandtab:
#
# Thin wrapper around `task __complete`. All suggestion logic lives in the
# Go engine — do not add completion logic here.
TASK_CMD="${TASK_EXE:-task}"
_task() {
local cur prev words cword
# Completion directives, mirroring internal/complete/complete.go.
local -ri NO_SPACE=2 NO_FILE_COMP=4 FILTER_FILE_EXT=8 FILTER_DIRS=16
# Exclude both `=` and `:` from the word breaks so `--output=` and
# `docs:serve` reach the engine as single tokens.
_init_completion -n =: || return
local -a args=()
if (( cword > 0 )); then
args=( "${words[@]:1:cword}" )
fi
if (( ${#args[@]} == 0 )); then
args=( "" )
fi
local output
output=$("$TASK_CMD" __complete "${args[@]}" 2>/dev/null)
if [[ -z "$output" ]]; then
_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 against "unbound variable" on an empty array under
# `set -u` in bash 3.2 (macOS).
for line in ${lines[@]+"${lines[@]}"}; do
exts+="${exts:+|}$line"
done
_filedir "@($exts)"
return
fi
if (( directive & FILTER_DIRS )); then
_filedir -d
return
fi
# Prefix-filter by hand instead of `compgen -W`: the latter joins/splits the
# word list on IFS, which mangles any suggestion value containing a space.
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
__ltrim_colon_completions "$cur"
if (( ${#COMPREPLY[@]} == 0 )) && ! (( directive & NO_FILE_COMP )); then
_filedir
fi
}
complete -F _task "$TASK_CMD"