diff options
| author | Paul Buetow <paul@buetow.org> | 2026-03-26 22:28:52 +0200 |
|---|---|---|
| committer | Paul Buetow <paul@buetow.org> | 2026-03-26 22:28:52 +0200 |
| commit | c514262eb8bfd4042719efd28335f0e1ad9a4f48 (patch) | |
| tree | f6cea304240a88e41a99ab33280594858b28af8d | |
| parent | 5603667541240cc7eaf3b8cd2352bf12c1bdd8f2 (diff) | |
ask: speed up fish UUID completion
| -rw-r--r-- | assets/ask.fish | 28 | ||||
| -rw-r--r-- | internal/askcli/command_complete_uuids.go | 28 | ||||
| -rw-r--r-- | internal/askcli/command_complete_uuids_test.go | 54 | ||||
| -rw-r--r-- | internal/askcli/completion.go | 27 | ||||
| -rw-r--r-- | internal/askcli/completion_test.go | 8 | ||||
| -rw-r--r-- | internal/askcli/dispatch.go | 2 | ||||
| -rw-r--r-- | internal/askcli/dispatch_test.go | 21 |
7 files changed, 136 insertions, 32 deletions
diff --git a/assets/ask.fish b/assets/ask.fish index 68fcf9f..46cb87d 100644 --- a/assets/ask.fish +++ b/assets/ask.fish @@ -92,7 +92,18 @@ function __ask_in_dep_uuid_context end function __ask_task_uuids - command ask all --json 2>/dev/null | jq -r '.[] | select(.status != "completed" and .status != "deleted") | .uuid' 2>/dev/null + set -l now (date +%s) + if set -q __ask_task_uuid_cache_until; and test $__ask_task_uuid_cache_until -ge $now + printf '%s\n' $__ask_task_uuid_cache + return 0 + end + set -l uuids (command ask complete-uuids 2>/dev/null) + if test $status -ne 0 + return 1 + end + set -g __ask_task_uuid_cache $uuids + set -g __ask_task_uuid_cache_until (math $now + 2) + printf '%s\n' $uuids end complete -c ask -f @@ -117,16 +128,5 @@ complete -c ask -n '__ask_needs_root_completion' -a 'help' -d 'Show help' 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' -complete -c ask -n '__ask_in_uuid_context' -a '(__ask_task_uuids)' -d 'Show task details' -complete -c ask -n '__ask_in_uuid_context' -a '(__ask_task_uuids)' -d 'Add an annotation' -complete -c ask -n '__ask_in_uuid_context' -a '(__ask_task_uuids)' -d 'Start a task' -complete -c ask -n '__ask_in_uuid_context' -a '(__ask_task_uuids)' -d 'Stop a task' -complete -c ask -n '__ask_in_uuid_context' -a '(__ask_task_uuids)' -d 'Mark a task complete' -complete -c ask -n '__ask_in_uuid_context' -a '(__ask_task_uuids)' -d 'Set priority' -complete -c ask -n '__ask_in_uuid_context' -a '(__ask_task_uuids)' -d 'Add or remove a tag' -complete -c ask -n '__ask_in_uuid_context' -a '(__ask_task_uuids)' -d 'Modify task fields' -complete -c ask -n '__ask_in_uuid_context' -a '(__ask_task_uuids)' -d 'Remove an annotation' -complete -c ask -n '__ask_in_uuid_context' -a '(__ask_task_uuids)' -d 'Delete a task' -complete -c ask -n '__ask_in_dep_uuid_context' -a '(__ask_task_uuids)' -d 'Add a dependency' -complete -c ask -n '__ask_in_dep_uuid_context' -a '(__ask_task_uuids)' -d 'Remove a dependency' -complete -c ask -n '__ask_in_dep_uuid_context' -a '(__ask_task_uuids)' -d 'List dependencies' +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' diff --git a/internal/askcli/command_complete_uuids.go b/internal/askcli/command_complete_uuids.go new file mode 100644 index 0000000..99f1e0e --- /dev/null +++ b/internal/askcli/command_complete_uuids.go @@ -0,0 +1,28 @@ +package askcli + +import ( + "bytes" + "context" + "fmt" + "io" +) + +func (d Dispatcher) handleCompleteUUIDs(ctx context.Context, stdout, stderr io.Writer) (int, error) { + var outBuf bytes.Buffer + code, err := d.runner.Run(ctx, []string{"status:pending", "export"}, nil, &outBuf, stderr) + if code != 0 { + return code, err + } + tasks, err := ParseTaskExport(&outBuf) + if err != nil { + fmt.Fprintf(stderr, "error: failed to parse task data: %v\n", err) + return 1, nil + } + for _, task := range tasks { + if task.UUID == "" { + continue + } + _, _ = io.WriteString(stdout, task.UUID+"\n") + } + return 0, nil +} diff --git a/internal/askcli/command_complete_uuids_test.go b/internal/askcli/command_complete_uuids_test.go new file mode 100644 index 0000000..ff9d142 --- /dev/null +++ b/internal/askcli/command_complete_uuids_test.go @@ -0,0 +1,54 @@ +package askcli + +import ( + "bytes" + "context" + "io" + "strings" + "testing" +) + +func TestHandleCompleteUUIDs_PrintsPendingUUIDs(t *testing.T) { + d := NewDispatcher(&spyRunner{runFn: func(ctx context.Context, args []string, stdin io.Reader, stdout, stderr io.Writer) (int, error) { + want := []string{"status:pending", "export"} + if strings.Join(args, " ") != strings.Join(want, " ") { + t.Fatalf("args = %v, want %v", args, want) + } + _, _ = io.WriteString(stdout, `[{"uuid":"uuid-1"},{"uuid":"uuid-2"},{"uuid":""}]`) + return 0, nil + }}) + + var stdout, stderr bytes.Buffer + code, err := d.handleCompleteUUIDs(context.Background(), &stdout, &stderr) + if err != nil { + t.Fatalf("handleCompleteUUIDs returned error: %v", err) + } + 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 stderr.Len() != 0 { + t.Fatalf("stderr = %q, want empty", stderr.String()) + } +} + +func TestHandleCompleteUUIDs_ParseError(t *testing.T) { + d := NewDispatcher(&spyRunner{runFn: func(ctx context.Context, args []string, stdin io.Reader, stdout, stderr io.Writer) (int, error) { + _, _ = io.WriteString(stdout, `not-json`) + return 0, nil + }}) + + var stdout, stderr bytes.Buffer + code, err := d.handleCompleteUUIDs(context.Background(), &stdout, &stderr) + if err != nil { + t.Fatalf("handleCompleteUUIDs returned error: %v", err) + } + if code != 1 { + t.Fatalf("handleCompleteUUIDs code = %d, want 1", code) + } + if !strings.Contains(stderr.String(), "failed to parse task data") { + t.Fatalf("stderr = %q, want parse error", stderr.String()) + } +} diff --git a/internal/askcli/completion.go b/internal/askcli/completion.go index e947495..6033415 100644 --- a/internal/askcli/completion.go +++ b/internal/askcli/completion.go @@ -48,12 +48,6 @@ var askUUIDCompletionItems = []fishCompletionItem{ {name: "delete", description: "Delete a task"}, } -var askDepUUIDCompletionItems = []fishCompletionItem{ - {name: "add", description: "Add a dependency"}, - {name: "rm", description: "Remove a dependency"}, - {name: "list", description: "List dependencies"}, -} - func FishCompletion() string { var b strings.Builder writeFishPreamble(&b) @@ -67,12 +61,8 @@ func FishCompletion() string { for _, item := range askDepCompletionItems { writeFishCompletionLine(&b, "__ask_in_dep_context", item) } - for _, item := range askUUIDCompletionItems { - writeFishUUIDCompletionLine(&b, "__ask_in_uuid_context", item.description) - } - for _, item := range askDepUUIDCompletionItems { - writeFishUUIDCompletionLine(&b, "__ask_in_dep_uuid_context", item.description) - } + writeFishUUIDCompletionLine(&b, "__ask_in_uuid_context", "Task UUID") + writeFishUUIDCompletionLine(&b, "__ask_in_dep_uuid_context", "Task UUID") return b.String() } @@ -189,7 +179,18 @@ func writeFishDepUUIDContextFunction(b *strings.Builder) { func writeFishTaskUUIDFunction(b *strings.Builder) { b.WriteString("function __ask_task_uuids\n") - b.WriteString(" command ask all --json 2>/dev/null | jq -r '.[] | select(.status != \"completed\" and .status != \"deleted\") | .uuid' 2>/dev/null\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(" return 0\n") + b.WriteString(" end\n") + b.WriteString(" set -l uuids (command ask 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("end\n\n") } diff --git a/internal/askcli/completion_test.go b/internal/askcli/completion_test.go index 52cdce8..5cab89b 100644 --- a/internal/askcli/completion_test.go +++ b/internal/askcli/completion_test.go @@ -36,11 +36,9 @@ func TestFishCompletion_IncludesCommandsAndExcludesExport(t *testing.T) { "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", - "command ask all --json 2>/dev/null | jq -r '.[] | select(.status != \"completed\" and .status != \"deleted\") | .uuid' 2>/dev/null", - "complete -c ask -n '__ask_in_uuid_context' -a '(__ask_task_uuids)' -d 'Show task details'", - "complete -c ask -n '__ask_in_dep_uuid_context' -a '(__ask_task_uuids)' -d 'Add a dependency'", - "complete -c ask -n '__ask_in_dep_uuid_context' -a '(__ask_task_uuids)' -d 'Remove a dependency'", - "complete -c ask -n '__ask_in_dep_uuid_context' -a '(__ask_task_uuids)' -d 'List dependencies'", + "set -l uuids (command ask 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'", } { if !strings.Contains(script, line) { t.Fatalf("script missing dep completion line %q", line) diff --git a/internal/askcli/dispatch.go b/internal/askcli/dispatch.go index 19e9b58..bc4715c 100644 --- a/internal/askcli/dispatch.go +++ b/internal/askcli/dispatch.go @@ -76,6 +76,8 @@ func (d Dispatcher) Dispatch(ctx context.Context, args []string, stdin io.Reader return d.handleDelete(ctx, args, stdin, stdout, stderr) case "help": return d.help(stdout) + case "complete-uuids": + return d.handleCompleteUUIDs(ctx, stdout, stderr) default: return d.unknownCommand(stderr, subcommand) } diff --git a/internal/askcli/dispatch_test.go b/internal/askcli/dispatch_test.go index 1586447..9a1bf43 100644 --- a/internal/askcli/dispatch_test.go +++ b/internal/askcli/dispatch_test.go @@ -46,6 +46,27 @@ func TestDispatcher_UnknownSubcommand(t *testing.T) { } } +func TestDispatcher_CompleteUUIDsSubcommand(t *testing.T) { + d := NewDispatcher(&spyRunner{runFn: func(ctx context.Context, args []string, stdin io.Reader, stdout, stderr io.Writer) (int, error) { + if strings.Join(args, " ") != "status:pending export" { + t.Fatalf("args = %v, want pending export", args) + } + _, _ = io.WriteString(stdout, `[{"uuid":"uuid-1"}]`) + return 0, nil + }}) + var stdout, stderr bytes.Buffer + code, err := d.Dispatch(context.Background(), []string{"complete-uuids"}, nil, &stdout, &stderr) + if err != nil { + t.Fatalf("complete-uuids returned error: %v", err) + } + 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) + } +} + func TestDispatcher_LongHelp(t *testing.T) { d := NewDispatcher(nil) var stdout bytes.Buffer |
