feat(completion): add Nushell completions (#2966)

This commit is contained in:
Valentin Maerten
2026-08-11 20:12:17 +02:00
committed by GitHub
parent e8404ea6e0
commit 993508c782
7 changed files with 233 additions and 2 deletions

View File

@@ -79,6 +79,8 @@ nfpms:
dst: /usr/share/fish/vendor_completions.d/task.fish
- src: completion/zsh/_task
dst: /usr/local/share/zsh/site-functions/_task
- src: completion/nu/task-completions.nu
dst: /usr/share/nushell/vendor/autoload/task-completions.nu
brews:
- name: go-task
@@ -96,6 +98,7 @@ brews:
bash_completion.install "completion/bash/task.bash" => "task"
zsh_completion.install "completion/zsh/_task" => "_task"
fish_completion.install "completion/fish/task.fish"
(share/"nushell/vendor/autoload").install "completion/nu/task-completions.nu"
commit_author:
name: task-bot
email: 106601941+task-bot@users.noreply.github.com

View File

@@ -41,6 +41,9 @@
stopping without a trace (#2240 by @Drino).
- Fixed pressing `Esc` at an interactive variable prompt not cancelling the run
(#2942 by @anilnatha).
- Added Nushell completions, available via `task --completion nu`. They complete
task names and aliases, every flag with its description, and the values of
`--completion`, `--output` and `--sort` (#2966 by @vmaerten).
## v3.52.0 - 2026-07-02

View File

@@ -11,6 +11,9 @@ var completionBash string
//go:embed completion/fish/task.fish
var completionFish string
//go:embed completion/nu/task-completions.nu
var completionNu string
//go:embed completion/ps/task.ps1
var completionPowershell string
@@ -24,6 +27,8 @@ func Completion(completion string) (string, error) {
return completionBash, nil
case "fish":
return completionFish, nil
case "nu", "nushell":
return completionNu, nil
case "powershell":
return completionPowershell, nil
case "zsh":

View File

@@ -69,7 +69,7 @@ complete -c $GO_TASK_PROGNAME \
complete -c $GO_TASK_PROGNAME -s a -l list-all -d 'list all tasks'
complete -c $GO_TASK_PROGNAME -s c -l color -d 'colored output (default true)'
complete -c $GO_TASK_PROGNAME -s C -l concurrency -d 'limit number of concurrent tasks'
complete -c $GO_TASK_PROGNAME -l completion -d 'generate shell completion script' -xa "bash zsh fish powershell"
complete -c $GO_TASK_PROGNAME -l completion -d 'generate shell completion script' -xa "bash zsh fish powershell nu"
complete -c $GO_TASK_PROGNAME -s d -l dir -d 'set directory of execution'
complete -c $GO_TASK_PROGNAME -l disable-fuzzy -d 'disable fuzzy matching for task names'
complete -c $GO_TASK_PROGNAME -s n -l dry -d 'compile and print tasks without executing'

View File

@@ -0,0 +1,180 @@
# Nushell completions for Task (https://taskfile.dev).
#
# Nushell cannot source a script from stdin, so save this file where Nushell
# picks it up automatically:
# mkdir ($nu.data-dir | path join "vendor/autoload")
# task --completion nu | save --force ($nu.data-dir | path join "vendor/autoload/task-completions.nu")
#
# The file must not be named task.nu: Nushell refuses to export a known external
# named like its module, which would break `use task-completions.nu *`.
# Name or path of the Task executable, like the other completion scripts.
# The *completed* command is always `task`: an `extern` declaration requires a
# literal name. For a renamed executable, alias it instead: `alias go-task = task`.
def "nu-complete task-exe" [] {
$env.TASK_EXE? | default "task"
}
def "nu-complete task-words" [context: string] {
$context | split row --regex '\s+' | where {|word| $word != "" }
}
# Strips the quotes the user may have typed around a value and expands `~`,
# which Nushell does not do for a value coming from a variable.
def "nu-complete task-value" [value: string] {
let unquoted = ($value | str trim --char '"' | str trim --char "'")
if ($unquoted | str starts-with "~") {
$unquoted | path expand --no-symlink
} else {
$unquoted
}
}
# Rebuilds the flags deciding *which* Taskfile is read, so the task list follows
# the `-t/--taskfile`, `-d/--dir` and `-g/--global` already on the command line.
def "nu-complete task-scope" [words: list<string>] {
mut scope: list<string> = []
mut pending = ""
for word in ($words | skip 1) {
if $pending != "" {
$scope = ($scope | append [$pending, (nu-complete task-value $word)])
$pending = ""
continue
}
let parts = ($word | split row "=")
let name = ($parts | first)
let inline = if ($parts | length) > 1 { $parts | skip 1 | str join "=" } else { null }
if $name in ["-g", "--global"] {
$scope = ($scope | append "--global")
} else if $name in ["-t", "--taskfile", "-d", "--dir"] {
let long = if $name in ["-t", "--taskfile"] { "--taskfile" } else { "--dir" }
if $inline != null {
$scope = ($scope | append [$long, (nu-complete task-value $inline)])
} else {
$pending = $long
}
}
}
$scope
}
# Lists the tasks of the targeted Taskfile. `--no-status` keeps completion fast:
# without it Task fingerprints every task's sources on each keystroke. Returns an
# empty list when Task exits non-zero (no Taskfile, invalid Taskfile).
def "nu-complete task-list" [words: list<string>] {
let exe = (nu-complete task-exe)
let args = [...(nu-complete task-scope $words) "--list-all" "--json" "--no-status"]
let result = (try { do { ^$exe ...$args } | complete } catch { null })
if ($result | is-empty) or $result.exit_code != 0 {
return []
}
try { $result.stdout | from json | get tasks } catch { [] }
}
def "nu-complete task" [context: string] {
let words = (nu-complete task-words $context)
# Words after `--` are forwarded to the task as CLI_ARGS: stop offering task
# names and let Nushell fall back to its own file completion.
if "--" in $words {
return null
}
let completions = (
nu-complete task-list $words
| each {|item|
# `task` is the invocable name; `name` may be a display-only label.
let name = ($item.task | str trim --right --char ':')
let desc = ($item.desc? | default "")
let aliases = (
$item.aliases?
| default []
| each {|alias| {
value: ($alias | str trim --right --char ':')
description: (if ($desc | is-empty) { $"alias of ($name)" } else { $"($desc) \(alias of ($name)\)" })
} }
)
[{ value: $name, description: $desc }] | append $aliases
}
| flatten
)
# `sort: false` keeps the order Task chose, which honours --sort and .taskrc.
{ options: { sort: false }, completions: $completions }
}
def "nu-complete task-shells" [] {
["bash", "zsh", "fish", "powershell", "nu"]
}
def "nu-complete task-output" [] {
["interleaved", "group", "prefixed"]
}
def "nu-complete task-sort" [] {
["default", "alphanumeric", "none"]
}
# Runs the specified task(s). Falls back to the "default" task if no task name
# was specified, or lists all tasks if an unknown task name was specified.
#
# An `extern` signature is static, so the experimental flags at the bottom are
# always offered; Task rejects them when the experiment is off. Run
# `task --experiments` to see which ones are enabled.
export extern "task" [
...tasks: string@"nu-complete task" # task(s) to run
--list(-l) # list tasks with a description
--list-all(-a) # list all tasks, with or without a description
--json(-j) # format the task list as JSON
--no-status # ignore status when listing tasks as JSON
--nested # nest namespaces when listing tasks as JSON
--sort: string@"nu-complete task-sort" # change the order of the tasks when listed
--init(-i) # create a new Taskfile.yml in the current folder
--completion: string@"nu-complete task-shells" # generate a shell completion script
--taskfile(-t): glob # choose which Taskfile to run
--dir(-d): directory # set the directory in which Task will execute
--global(-g) # run the global Taskfile from $HOME
--temp-dir: directory # directory used to store Task temporary files
--force(-f) # force execution even when the task is up-to-date
--status # exit with a non-zero code if tasks are not up-to-date
--dry(-n) # compile and print the tasks without executing them
--summary # show the summary of a task instead of running it
--watch(-w) # watch the given tasks and re-run them on changes
--interval(-I): string # interval to watch for changes, e.g. 500ms
--parallel(-p) # run the tasks given on the command line in parallel
--concurrency(-C): int # limit the number of tasks run concurrently
--failfast(-F) # when running in parallel, stop everything if one task fails
--exit-code(-x) # pass through the exit code of the task command
--interactive # prompt for missing required variables
--yes(-y) # assume "yes" as the answer to all prompts
--output(-o): string@"nu-complete task-output" # set the output style
--output-group-begin: string # message template printed before a task's grouped output
--output-group-end: string # message template printed after a task's grouped output
--output-group-error-only # swallow the output of successful tasks
--color(-c) # colored output, enabled by default
--silent(-s) # disable echoing
--verbose(-v) # enable verbose mode
--disable-fuzzy # disable fuzzy matching for task names
--insecure # allow Taskfiles to be downloaded over insecure connections
--experiments # list the available experiments and whether they are enabled
--version # show the Task version
--help(-h) # show Task usage
--force-all # [GENTLE_FORCE] force the called task and all its dependencies
--download # [REMOTE_TASKFILES] download a cached version of a remote Taskfile
--offline # [REMOTE_TASKFILES] only use local or cached Taskfiles
--clear-cache # [REMOTE_TASKFILES] clear the remote Taskfile cache
--trusted-hosts: string # [REMOTE_TASKFILES] trusted hosts for remote Taskfiles (comma-separated)
--timeout: string # [REMOTE_TASKFILES] timeout for downloading remote Taskfiles
--expiry: string # [REMOTE_TASKFILES] expiry duration for cached remote Taskfiles
--remote-cache-dir: directory # [REMOTE_TASKFILES] directory used to cache remote Taskfiles
--cacert: path # [REMOTE_TASKFILES] custom CA certificate for HTTPS connections
--cert: path # [REMOTE_TASKFILES] client certificate for HTTPS connections
--cert-key: path # [REMOTE_TASKFILES] client certificate key for HTTPS connections
]

View File

@@ -94,7 +94,7 @@ _task() {
'(-F --failfast)'{-F,--failfast}'[when running tasks in parallel, stop all tasks if one fails]'
'(-f --force)'{-f,--force}'[run even if task is up-to-date]'
'(-c --color)'{-c,--color}'[colored output]'
'(--completion)--completion[generate shell completion script]:shell:(bash zsh fish powershell)'
'(--completion)--completion[generate shell completion script]:shell:(bash zsh fish powershell nu)'
'(-d --dir)'{-d,--dir}'[dir to run in]:execution dir:_dirs'
'(--disable-fuzzy)--disable-fuzzy[disable fuzzy matching for task names]'
'(-n --dry)'{-n,--dry}'[compiles and prints tasks without executing]'

View File

@@ -407,6 +407,16 @@ task --completion fish | source
Invoke-Expression (&task --completion powershell | Out-String)
```
```nu [nushell]
# ~/.config/nushell/config.nu
#
# Nushell cannot source a script from stdin, so the script is saved where
# Nushell auto-loads it at startup. Autoload directories are read after
# config.nu, so the completions become available in the next shell.
mkdir ($nu.data-dir | path join "vendor/autoload")
task --completion nu | save --force ($nu.data-dir | path join "vendor/autoload/task-completions.nu")
```
:::
### Option 2. Copy the script to your shell's completions directory
@@ -428,6 +438,10 @@ task --completion zsh > /usr/local/share/zsh/site-functions/_task
task --completion fish > ~/.config/fish/completions/task.fish
```
```nu [nushell]
task --completion nu | save --force ($nu.data-dir | path join "vendor/autoload/task-completions.nu")
```
:::
### Zsh customization
@@ -446,3 +460,29 @@ canonical task names, add the `show-aliases` zstyle:
```shell
zstyle ':completion:*:*:task:*' show-aliases false
```
### Nushell caveats
Nushell cannot source a script from stdin, so both options above write the script
to an autoload directory. Option 1 rewrites it at every startup, which keeps it
in sync with the installed version of Task — the refreshed completions are picked
up by the next shell. With option 2, re-run the command after upgrading Task.
The completions are attached to an `extern "task"` declaration, which Nushell
requires to be static. Three consequences are worth knowing:
- The experimental flags (`--force-all`, `--download`, `--offline`, …) are always
offered, even when the corresponding experiment is disabled. Their description
is prefixed with the experiment name, and `task --experiments` lists the ones
that are enabled.
- Passing a value to a boolean flag with `=` does not work: Nushell forwards
`--color=false` as two arguments, so Task reads `false` as a task name. Use
`NO_COLOR=1`, or bypass the declaration with `^task --color=false`.
- `TASK_EXE` selects the executable that is run, but not the command name the
completions are attached to, which is always `task`. For a renamed executable,
alias it instead:
```nu
use ($nu.data-dir | path join "vendor/autoload/task-completions.nu") *
alias go-task = task
```