Use LayoutStack for huhPrompter MultiSelectWithSearch

Implement a huh-native MultiSelectWithSearch that renders the search
input and multi-select list simultaneously using LayoutStack. The
search input is in Group 0 and the multi-select in Group 1, with
OptionsFunc bound to the search query so results update when the
user presses Enter to advance focus. Users can Shift+Tab back to
refine their search, and selections persist across queries.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
Kynan Ware 2026-03-06 19:50:14 -07:00 committed by William Martin
parent 87426ee236
commit 726714d1a7

View file

@ -83,7 +83,94 @@ func (p *huhPrompter) MultiSelect(prompt string, defaults []string, options []st
}
func (p *huhPrompter) MultiSelectWithSearch(prompt, searchPrompt string, defaultValues, persistentValues []string, searchFunc func(string) MultiSelectSearchResult) ([]string, error) {
return multiSelectWithSearch(p, prompt, searchPrompt, defaultValues, persistentValues, searchFunc)
selectedValues := make([]string, len(defaultValues))
copy(selectedValues, defaultValues)
optionKeyLabels := make(map[string]string)
for _, k := range selectedValues {
optionKeyLabels[k] = k
}
buildOptions := func(query string) []huh.Option[string] {
result := searchFunc(query)
if result.Err != nil {
return nil
}
for i, k := range result.Keys {
optionKeyLabels[k] = result.Labels[i]
}
var formOptions []huh.Option[string]
seen := make(map[string]bool)
// 1. Currently selected values (persisted across searches).
for _, k := range selectedValues {
if seen[k] {
continue
}
seen[k] = true
l := optionKeyLabels[k]
if l == "" {
l = k
}
formOptions = append(formOptions, huh.NewOption(l, k))
}
// 2. Search results.
for i, k := range result.Keys {
if seen[k] {
continue
}
seen[k] = true
l := result.Labels[i]
if l == "" {
l = k
}
formOptions = append(formOptions, huh.NewOption(l, k))
}
// 3. Persistent options.
for _, k := range persistentValues {
if seen[k] {
continue
}
seen[k] = true
l := optionKeyLabels[k]
if l == "" {
l = k
}
formOptions = append(formOptions, huh.NewOption(l, k))
}
if len(formOptions) == 0 {
formOptions = append(formOptions, huh.NewOption("No results", ""))
}
return formOptions
}
var searchQuery string
err := p.newForm(
huh.NewGroup(
huh.NewInput().
Title(searchPrompt).
Value(&searchQuery),
huh.NewMultiSelect[string]().
Title(prompt).
Options(buildOptions("")...).
OptionsFunc(func() []huh.Option[string] {
return buildOptions(searchQuery)
}, &searchQuery).
Value(&selectedValues).
Limit(0),
),
).Run()
if err != nil {
return nil, err
}
return selectedValues, nil
}
func (p *huhPrompter) Input(prompt, defaultValue string) (string, error) {