summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2026-03-27 06:23:24 +0200
committerPaul Buetow <paul@buetow.org>2026-03-27 06:23:24 +0200
commit2edeaa610553b667776010bf2e9f5596aaa8ddbd (patch)
treeb32b5c3946061f3e06ae274b5c4260358985f04f
parent6b964400deb653d2c47aa8932ab5444346833b0d (diff)
task 73fabcf6-d4a1-4bf3-aae1-a390a734e517: extend ask selector completion
-rw-r--r--docs/fish-completion.md3
-rw-r--r--internal/askcli/command_complete_uuids.go19
-rw-r--r--internal/askcli/command_complete_uuids_test.go24
-rw-r--r--internal/askcli/completion.go24
-rw-r--r--internal/askcli/completion_test.go10
-rw-r--r--internal/askcli/dispatch_test.go4
6 files changed, 58 insertions, 26 deletions
diff --git a/docs/fish-completion.md b/docs/fish-completion.md
index 195d29a..d7e7289 100644
--- a/docs/fish-completion.md
+++ b/docs/fish-completion.md
@@ -3,7 +3,8 @@
The `ask` task-management CLI embeds its Fish completion script in the binary and prints it with `ask fish`.
It completes the top-level `ask` subcommands and the nested `ask dep` operations.
-It also completes task UUIDs for UUID-taking commands by reading the current project from `ask all --json` and filtering out completed and deleted tasks.
+It also completes task selectors for UUID-taking commands by reading pending tasks through `ask complete-uuids`, which uses the local alias cache for stable short IDs.
+Fish suggests each task's alias ID first and also keeps the raw UUID available as a fallback selector.
The script preserves the global `--json` flag.
Load it into the current Fish session:
diff --git a/internal/askcli/command_complete_uuids.go b/internal/askcli/command_complete_uuids.go
index 755c3bb..8d37a80 100644
--- a/internal/askcli/command_complete_uuids.go
+++ b/internal/askcli/command_complete_uuids.go
@@ -18,14 +18,27 @@ func (d Dispatcher) handleCompleteUUIDs(ctx context.Context, stdout, stderr io.W
fmt.Fprintf(stderr, "error: failed to parse task data: %v\n", err)
return 1, nil
}
- if _, err := ensureTaskAliases(tasks); err != nil {
+ aliases, err := ensureTaskAliases(tasks)
+ if err != nil {
fmt.Fprintf(stderr, "warning: failed to update task alias cache: %v\n", err)
+ aliases = nil
+ }
+ for _, selector := range taskCompletionSelectors(tasks, aliases) {
+ _, _ = io.WriteString(stdout, selector+"\n")
}
+ return 0, nil
+}
+
+func taskCompletionSelectors(tasks []TaskExport, aliases map[string]string) []string {
+ selectors := make([]string, 0, len(tasks)*2)
for _, task := range tasks {
if task.UUID == "" {
continue
}
- _, _ = io.WriteString(stdout, task.UUID+"\n")
+ if alias := displayTaskAlias(task.UUID, aliases); alias != "" && alias != task.UUID {
+ selectors = append(selectors, alias)
+ }
+ selectors = append(selectors, task.UUID)
}
- return 0, nil
+ return selectors
}
diff --git a/internal/askcli/command_complete_uuids_test.go b/internal/askcli/command_complete_uuids_test.go
index 2c8fc34..4a68e51 100644
--- a/internal/askcli/command_complete_uuids_test.go
+++ b/internal/askcli/command_complete_uuids_test.go
@@ -39,8 +39,8 @@ func TestHandleCompleteUUIDs_PrintsPendingUUIDs(t *testing.T) {
if code != 0 {
t.Fatalf("handleCompleteUUIDs code = %d, want 0", code)
}
- if got := stdout.String(); got != "uuid-1\nuuid-2\n" {
- t.Fatalf("stdout = %q, want UUID list", got)
+ if got := stdout.String(); got != "0\nuuid-1\n1\nuuid-2\n" {
+ t.Fatalf("stdout = %q, want alias-first selector list", got)
}
if stderr.Len() != 0 {
t.Fatalf("stderr = %q, want empty", stderr.String())
@@ -109,9 +109,27 @@ func TestHandleCompleteUUIDs_WarnsOnInvalidAliasCache(t *testing.T) {
t.Fatalf("handleCompleteUUIDs code = %d, want 0", code)
}
if got := stdout.String(); got != "uuid-1\n" {
- t.Fatalf("stdout = %q, want UUID list", got)
+ t.Fatalf("stdout = %q, want UUID-only fallback list", got)
}
if !strings.Contains(stderr.String(), "failed to update task alias cache") {
t.Fatalf("stderr = %q, want cache warning", stderr.String())
}
}
+
+func TestTaskCompletionSelectors_SkipsMissingAndDuplicateAliases(t *testing.T) {
+ tasks := []TaskExport{
+ {UUID: "uuid-1"},
+ {UUID: ""},
+ {UUID: "uuid-2"},
+ }
+ aliases := map[string]string{
+ "uuid-1": "0",
+ "uuid-2": "uuid-2",
+ }
+
+ got := taskCompletionSelectors(tasks, aliases)
+ want := []string{"0", "uuid-1", "uuid-2"}
+ if strings.Join(got, "\n") != strings.Join(want, "\n") {
+ t.Fatalf("taskCompletionSelectors = %v, want %v", got, want)
+ }
+}
diff --git a/internal/askcli/completion.go b/internal/askcli/completion.go
index 5684e33..ec7631e 100644
--- a/internal/askcli/completion.go
+++ b/internal/askcli/completion.go
@@ -57,7 +57,7 @@ func FishCompletionFor(binaryPath string) string {
var b strings.Builder
writeFishPreamble(&b)
writeFishContextFunctions(&b)
- writeFishTaskUUIDFunction(&b, binaryPath)
+ writeFishTaskSelectorFunction(&b, binaryPath)
b.WriteString("complete -c ask -f\n")
b.WriteString("complete -c ask -s j -l json -d 'Emit JSON output'\n")
for _, item := range askRootCompletionItems {
@@ -66,8 +66,8 @@ func FishCompletionFor(binaryPath string) string {
for _, item := range askDepCompletionItems {
writeFishCompletionLine(&b, "__ask_in_dep_context", item)
}
- writeFishUUIDCompletionLine(&b, "__ask_in_uuid_context", "Task UUID")
- writeFishUUIDCompletionLine(&b, "__ask_in_dep_uuid_context", "Task UUID")
+ writeFishUUIDCompletionLine(&b, "__ask_in_uuid_context", "Task selector")
+ writeFishUUIDCompletionLine(&b, "__ask_in_dep_uuid_context", "Task selector")
return b.String()
}
@@ -181,23 +181,23 @@ func writeFishDepUUIDContextFunction(b *strings.Builder) {
b.WriteString("end\n\n")
}
-func writeFishTaskUUIDFunction(b *strings.Builder, binaryPath string) {
- b.WriteString("function __ask_task_uuids\n")
+func writeFishTaskSelectorFunction(b *strings.Builder, binaryPath string) {
+ b.WriteString("function __ask_task_selectors\n")
b.WriteString(" set -l ask_bin ")
b.WriteString(quoteFishString(binaryPath))
b.WriteString("\n")
b.WriteString(" set -l now (date +%s)\n")
- b.WriteString(" if set -q __ask_task_uuid_cache_until; and test $__ask_task_uuid_cache_until -ge $now\n")
- b.WriteString(" printf '%s\\n' $__ask_task_uuid_cache\n")
+ b.WriteString(" if set -q __ask_task_selector_cache_until; and test $__ask_task_selector_cache_until -ge $now\n")
+ b.WriteString(" printf '%s\\n' $__ask_task_selector_cache\n")
b.WriteString(" return 0\n")
b.WriteString(" end\n")
- b.WriteString(" set -l uuids (command $ask_bin complete-uuids 2>/dev/null)\n")
+ b.WriteString(" set -l selectors (command $ask_bin complete-uuids 2>/dev/null)\n")
b.WriteString(" if test $status -ne 0\n")
b.WriteString(" return 1\n")
b.WriteString(" end\n")
- b.WriteString(" set -g __ask_task_uuid_cache $uuids\n")
- b.WriteString(" set -g __ask_task_uuid_cache_until (math $now + 2)\n")
- b.WriteString(" printf '%s\\n' $uuids\n")
+ b.WriteString(" set -g __ask_task_selector_cache $selectors\n")
+ b.WriteString(" set -g __ask_task_selector_cache_until (math $now + 2)\n")
+ b.WriteString(" printf '%s\\n' $selectors\n")
b.WriteString("end\n\n")
}
@@ -214,7 +214,7 @@ func writeFishCompletionLine(b *strings.Builder, condition string, item fishComp
func writeFishUUIDCompletionLine(b *strings.Builder, condition, description string) {
b.WriteString("complete -c ask -n '")
b.WriteString(condition)
- b.WriteString("' -a '(__ask_task_uuids)' -d '")
+ b.WriteString("' -a '(__ask_task_selectors)' -d '")
b.WriteString(strings.ReplaceAll(description, "'", "\\'"))
b.WriteString("'\n")
}
diff --git a/internal/askcli/completion_test.go b/internal/askcli/completion_test.go
index ad25cbd..87a3ca9 100644
--- a/internal/askcli/completion_test.go
+++ b/internal/askcli/completion_test.go
@@ -17,11 +17,11 @@ func TestFishCompletion_IncludesCommandsAndExcludesExport(t *testing.T) {
"complete -c ask -n '__ask_in_dep_context' -a 'add' -d 'Add a dependency'",
"complete -c ask -n '__ask_in_dep_context' -a 'rm' -d 'Remove a dependency'",
"complete -c ask -n '__ask_in_dep_context' -a 'list' -d 'List dependencies'",
- "function __ask_task_uuids",
+ "function __ask_task_selectors",
`set -l ask_bin "ask"`,
- "set -l uuids (command $ask_bin complete-uuids 2>/dev/null)",
- "complete -c ask -n '__ask_in_uuid_context' -a '(__ask_task_uuids)' -d 'Task UUID'",
- "complete -c ask -n '__ask_in_dep_uuid_context' -a '(__ask_task_uuids)' -d 'Task UUID'",
+ "set -l selectors (command $ask_bin complete-uuids 2>/dev/null)",
+ "complete -c ask -n '__ask_in_uuid_context' -a '(__ask_task_selectors)' -d 'Task selector'",
+ "complete -c ask -n '__ask_in_dep_uuid_context' -a '(__ask_task_selectors)' -d 'Task selector'",
} {
if !strings.Contains(script, line) {
t.Fatalf("script missing dep completion line %q", line)
@@ -39,7 +39,7 @@ func TestFishCompletionFor_EmbedsBinaryPath(t *testing.T) {
script := FishCompletionFor(`/tmp/ask "$HOME"`)
for _, line := range []string{
`set -l ask_bin "/tmp/ask \"\$HOME\""`,
- "set -l uuids (command $ask_bin complete-uuids 2>/dev/null)",
+ "set -l selectors (command $ask_bin complete-uuids 2>/dev/null)",
} {
if !strings.Contains(script, line) {
t.Fatalf("script missing %q", line)
diff --git a/internal/askcli/dispatch_test.go b/internal/askcli/dispatch_test.go
index 040f305..cd83e48 100644
--- a/internal/askcli/dispatch_test.go
+++ b/internal/askcli/dispatch_test.go
@@ -66,8 +66,8 @@ func TestDispatcher_CompleteUUIDsSubcommand(t *testing.T) {
if code != 0 {
t.Fatalf("complete-uuids code = %d, want 0", code)
}
- if got := stdout.String(); got != "uuid-1\n" {
- t.Fatalf("stdout = %q, want UUID list", got)
+ if got := stdout.String(); got != "0\nuuid-1\n" {
+ t.Fatalf("stdout = %q, want selector list", got)
}
}