From 3862b7c032eb54fdd6b095fdc892c50e7df3b964 Mon Sep 17 00:00:00 2001 From: Virgil Date: Thu, 2 Apr 2026 12:40:30 +0000 Subject: [PATCH] fix(cli): clear chooser filters on empty input Co-Authored-By: Virgil --- pkg/cli/prompt_test.go | 20 ++++++++++++++++++-- pkg/cli/utils.go | 12 ++++++++++++ 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/pkg/cli/prompt_test.go b/pkg/cli/prompt_test.go index 98ecf8f..162864e 100644 --- a/pkg/cli/prompt_test.go +++ b/pkg/cli/prompt_test.go @@ -304,7 +304,7 @@ func TestChoose_Bad_FilteredDefaultDoesNotFallBackToFirstVisible(t *testing.T) { defer SetStdin(nil) val := Choose("Pick", []string{"apple", "banana", "apricot"}, WithDefaultIndex[string](1), Filter[string]()) - assert.Equal(t, "apricot", val) + assert.Equal(t, "banana", val) } func TestChoose_Bad_InvalidNumberUsesStderrHint(t *testing.T) { @@ -333,7 +333,23 @@ func TestChooseMulti_Bad_FilteredDefaultDoesNotFallBackToFirstVisible(t *testing defer SetStdin(nil) vals := ChooseMulti("Pick", []string{"apple", "banana", "apricot"}, WithDefaultIndex[string](1), Filter[string]()) - assert.Equal(t, []string{"apricot"}, vals) + assert.Equal(t, []string{"banana"}, vals) +} + +func TestChoose_Good_ClearFilter(t *testing.T) { + SetStdin(strings.NewReader("ap\n\n2\n")) + defer SetStdin(nil) + + val := Choose("Pick", []string{"apple", "banana", "apricot"}, Filter[string]()) + assert.Equal(t, "banana", val) +} + +func TestChooseMulti_Good_ClearFilter(t *testing.T) { + SetStdin(strings.NewReader("ap\n\n2\n")) + defer SetStdin(nil) + + vals := ChooseMulti("Pick", []string{"apple", "banana", "apricot"}, Filter[string]()) + assert.Equal(t, []string{"banana"}, vals) } func TestChooseMulti_Good_Commas(t *testing.T) { diff --git a/pkg/cli/utils.go b/pkg/cli/utils.go index 2b1a123..1c76ec7 100644 --- a/pkg/cli/utils.go +++ b/pkg/cli/utils.go @@ -359,6 +359,7 @@ func Choose[T any](prompt string, items []T, opts ...ChooseOption[T]) T { for i := range items { visible[i] = i } + allVisible := append([]int(nil), visible...) for { renderChoices(prompt, items, visible, cfg.displayFn, cfg.defaultN, cfg.filter) @@ -380,6 +381,11 @@ func Choose[T any](prompt string, items []T, opts ...ChooseOption[T]) T { } if response == "" { + if cfg.filter && len(visible) != len(allVisible) { + visible = append([]int(nil), allVisible...) + promptHint("Filter cleared.") + continue + } if idx, ok := defaultVisibleIndex(visible, cfg.defaultN); ok { return items[idx] } @@ -453,6 +459,7 @@ func ChooseMulti[T any](prompt string, items []T, opts ...ChooseOption[T]) []T { for i := range items { visible[i] = i } + allVisible := append([]int(nil), visible...) for { renderChoices(prompt, items, visible, cfg.displayFn, -1, cfg.filter) @@ -467,6 +474,11 @@ func ChooseMulti[T any](prompt string, items []T, opts ...ChooseOption[T]) []T { // Empty response returns no selections if response == "" { + if cfg.filter && len(visible) != len(allVisible) { + visible = append([]int(nil), allVisible...) + promptHint("Filter cleared.") + continue + } if idx, ok := defaultVisibleIndex(visible, cfg.defaultN); ok { return []T{items[idx]} }