feat(completion): unify shell completions behind task __complete#2897
Open
vmaerten wants to merge 18 commits into
Open
feat(completion): unify shell completions behind task __complete#2897vmaerten wants to merge 18 commits into
task __complete#2897vmaerten wants to merge 18 commits into
Conversation
vmaerten
force-pushed
the
feat/completion-engine
branch
3 times, most recently
from
June 29, 2026 15:42
b13cc89 to
f9f2ecb
Compare
Member
Author
|
@arturict, please don't comment on draft PRs. Also, your comments appear to be AI-generated. We're still open to meaningful, human-written comments. Finally, please avoid giving instructions to maintainers as if you were one of them. |
|
Understood, and sorry about that. I should not have commented on a draft or phrased the comment as an instruction. I'll step back. |
Engine: - Emit DirectiveKeepOrder for task variables so the `requires` declaration order is preserved instead of being sorted by the shell. - Return full `--flag=value` candidates for the inline `--output=` form so all shells match against the whole current token. - Add `--no-aliases` / `--no-descriptions` completion flags (via complete.Options) parsed from the __complete invocation; the zsh wrapper maps its show-aliases and verbose zstyles onto them. - Skip Taskfile setup when completing flags (NeedsTaskfile) and load the task list lazily; drop unused parameters; document exported identifiers. Shell wrappers: - zsh: reindent to tabs (.editorconfig), bridge zstyles to engine flags. - fish: reindent to 2 spaces, handle every file-completion directive in the wrapper (--no-files disables the native fallback), drop the duplicated yml/yaml extension list now that it lives only in the engine. - bash: prefix-filter by hand to preserve values containing spaces, exclude `=` from word breaks so `--output=` reaches the engine as one token. - powershell: filter candidates by the current word, fall back to file completion for DirectiveDefault. NoSpace is not representable in fish/PowerShell completion APIs; documented in the wrappers.
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.
Follow cobra's testing philosophy: the completion protocol (candidates + directive) is business logic and belongs in Go, while the shell wrappers only need to be checked for how they interpret each directive. - Add completion/protocol_test.go: a table-driven black-box test that runs the real `task __complete` binary and asserts the offered values and the emitted directive. Unlike the in-process engine tests, it exercises the actual entrypoint dispatch, runComplete wiring and — crucially — the real flag set, so it catches drift between the completion enum/directive maps and the flag definitions. Runs on every OS, including Windows where the shell smokes don't. - Delete completion/tests/engine.sh (the protocol is now covered in Go) and drop it from run.sh. - Reduce the bash/zsh/fish/powershell smokes to directive routing only (files vs dirs vs no-files vs no-space), removing the task/alias/var assertions that the Go tests already own.
Move the thin __complete wrappers under completion/next/ and restore the stable, hand-written scripts at their canonical paths so the default output of `task --completion <shell>` (and the goreleaser/brew packaged scripts) is unchanged. Repoint the cross-shell test harness at completion/next/. This is the groundwork for offering the new engine as opt-in before it becomes the default.
Embed the completion/next/ wrappers and expose them through a new `task --new-completion <shell>` flag, alongside the unchanged `task --completion <shell>`. This lets users try the unified __complete engine (same suggestions across bash/zsh/fish/powershell: task names, aliases, flags, flag values and requires vars) while it is opt-in; it will become the default of --completion in a future release.
Add a subsection to the installation guide explaining how to try the new completion engine by swapping --completion for --new-completion, and noting it will become the default in a future release.
vmaerten
force-pushed
the
feat/completion-engine
branch
from
July 19, 2026 15:47
1521c9e to
32cfaec
Compare
PowerShell replaces the whole token being completed with the CompletionResult text, so returning `$_.Name` (the basename) for file/dir suggestions dropped any directory the user had already typed (e.g. `task --dir sub/<TAB>` turned `sub/` into `foo`). Prepend the typed path prefix to each candidate and make the default file fallback honor the current word instead of always listing the working directory. Covered by a nested-path smoke assertion.
…st word The task-var detection evaluated taskNames(e) — a full walk of every task and alias — unconditionally, even though detectTaskName bails out when there is no prior word. Guarding on len(args) > 1 avoids that walk (and a second one via GetTaskList) for the common `task <TAB>` case, which matters on large monorepo Taskfiles where completion latency is user-visible.
The --new-completion flag takes a shell name (bash/zsh/fish/powershell) just like --completion, but it was missing from flagEnums, so `task --new-completion <TAB>` offered nothing. Add the matching entry.
sanitize built a new strings.Replacer on every call (twice per suggestion). Hoist it to a package var.
Inline path flags (`--dir=foo`, `--taskfile=foo`, `--cacert=foo`) were broken: the shells ran file/dir completion against the whole `--flag=value` token, so nothing matched. Each wrapper now strips the `--flag=` prefix before completing and re-applies it to the results (zsh via `compset -P '*='`, the others by hand). Also fixes two smaller wrapper issues in the process: PowerShell prefix matching is now case-insensitive, and the fish wrapper erases inherited completion rules before registering (fish accumulates them, unlike bash/zsh/PowerShell). Adds inline smoke assertions for bash, fish and PowerShell.
The requires field added to the --list --json output is unused by the new completion engine (which reads requires via FastCompiledTask), untested, and did not handle enum.ref. Revert it to keep the JSON API change out of this branch.
…text) Use slices.Contains for the after-dash scan (modernize), and exec.CommandContext in the protocol test (noctx), with a targeted gosec exception for launching the test-built binary with test-controlled args.
…nt label The Options doc claimed the zero value shows everything, but it shows neither aliases nor descriptions (DefaultOptions enables both). Also remove the redundant `# FilterDirs` label in the PowerShell wrapper.
vmaerten
marked this pull request as ready for review
July 19, 2026 18:20
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds a unified shell completion engine: a single
task __completecommand, backed by a shared Go package (internal/complete), that drives Bash, Fish, Zsh and PowerShell. The shell scripts become thin wrappers that forward the current words totask __completeand render the suggestions, so every shell gets the same, richer behaviour and there is no more per-shell drift.The wire protocol mirrors cobra v2 (
value\tdescriptionlines followed by a:<directive>bitfield) behavior.Why
It's painful to maintain 4 (or more) different completion scripts, and it's also difficult to test them. With this approach, all new flags will be included in every shell by default.
Opt-in rollout
Rather than switching every user at once (millions of users, 4 shells, multiple OSes), the new engine ships opt-in:
task --completion <shell>is unchanged and keeps returning the current, stable scripts (still what goreleaser/Homebrew package).task --new-completion <shell>returns the new__complete-powered wrappers.completion/next/; the stable ones stay at their canonicalcompletion/<shell>/paths.--new-completionwill become the default of--completionin a future release, at which point thenext/wrappers move to the canonical paths and the flag is removed.What the engine completes
--xxx) and their shorthands--taskfile, directories for--dir, enums for--output/--sort/--completion/--new-completionenumvalues (task build VAR=)--taskfile foo) and inline (--taskfile=foo) flag forms, including nested paths (--dir=sub/…)The engine skips loading the Taskfile entirely when only flags/flag-values are being completed (
NeedsTaskfile), and remote-Taskfile completion never blocks: the approval prompt is a no-op without a TTY, so completion degrades to flag-only suggestions until the remote is approved by runningtasknormally.Testing
Following the cobra approach, the protocol is tested in Go and the generated scripts are only smoke-tested for how they route each directive:
internal/complete/complete_test.go: white-box tests of the engine.completion/protocol_test.go: black-box test of the real binary's__completeoutput (candidates + directive).completion/tests/: thin per-shell smokes (bash/zsh/fish/powershell) that assert directive routing (no-files, dir-only, extension filter, inline--flag=, …), runnable viatask test:completion.completionCI job runs the smokes on Ubuntu and macOS (with fish and PowerShell installed, strict mode).