Files
task/internal/complete/complete.go
Valentin Maerten 57bc6dd8cb fix(completion): harden the __complete engine and shell wrappers
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.
2026-07-03 16:03:33 +02:00

90 lines
3.2 KiB
Go

// Package complete implements the `task __complete` protocol consumed by the
// shell completion wrappers. The protocol mirrors cobra v2 so a future
// migration stays cheap.
package complete
import "os"
// CommandName is the hidden subcommand the shell wrappers invoke to drive
// completion: `task __complete <words...>`.
const CommandName = "__complete"
// IsActive reports whether the process was invoked in completion mode, i.e.
// the first argument is the __complete subcommand.
func IsActive() bool {
return len(os.Args) >= 2 && os.Args[1] == CommandName
}
// Directive mirrors cobra's ShellCompDirective bitfield. It is emitted on the
// final output line as `:<directive>` and tells the shell wrapper how to treat
// the suggestions (file fallback, trailing space, ordering, …).
type Directive int
const (
// DirectiveDefault leaves the shell to perform its default file completion.
DirectiveDefault Directive = 0
// DirectiveError signals an error; the shell should not offer completion.
DirectiveError Directive = 1 << 0
// DirectiveNoSpace prevents the shell from appending a space after the
// suggestion (e.g. so `VAR=` can be followed by a value).
DirectiveNoSpace Directive = 1 << 1
// DirectiveNoFileComp disables the shell's fallback file completion.
DirectiveNoFileComp Directive = 1 << 2
// DirectiveFilterFileExt restricts file completion to the emitted extensions.
DirectiveFilterFileExt Directive = 1 << 3
// DirectiveFilterDirs restricts completion to directories.
DirectiveFilterDirs Directive = 1 << 4
// DirectiveKeepOrder tells the shell to preserve the emitted order instead
// of sorting alphabetically.
DirectiveKeepOrder Directive = 1 << 5
)
// Suggestion is a single completion candidate: the Value inserted on the
// command line and an optional human-readable Description.
type Suggestion struct {
Value string
Description string
}
// Options tunes what the engine emits. The zero value shows everything; use
// DefaultOptions for the default and flip fields off from the __complete flags.
type Options struct {
ShowAliases bool
ShowDescriptions bool
}
// DefaultOptions returns the options used when no completion-control flag is
// passed: aliases and descriptions are both shown.
func DefaultOptions() Options {
return Options{ShowAliases: true, ShowDescriptions: true}
}
// Completion-control flags. Shell wrappers prepend these to the __complete
// invocation to tune the output (e.g. zsh maps its show-aliases / verbose
// zstyles to them). They are consumed by ParseOptions before the remaining
// args are treated as the user's command line.
const (
FlagNoAliases = "--no-aliases"
FlagNoDescriptions = "--no-descriptions"
)
// ParseOptions peels the leading completion-control flags off args and returns
// the resulting Options together with the remaining args (the user's command
// line to complete). Only leading flags are consumed, so a `--no-aliases` typed
// by the user further down the line is left untouched.
func ParseOptions(args []string) (Options, []string) {
opts := DefaultOptions()
for len(args) > 0 {
switch args[0] {
case FlagNoAliases:
opts.ShowAliases = false
case FlagNoDescriptions:
opts.ShowDescriptions = false
default:
return opts, args
}
args = args[1:]
}
return opts, args
}