Files
goclaw/cmd/prompt.go
T
2026-03-07 00:46:29 +07:00

148 lines
3.5 KiB
Go

package cmd
import (
"github.com/charmbracelet/huh"
)
// runWithHelp wraps a huh field in a Form with help hints visible at the bottom.
func runWithHelp(fields ...huh.Field) error {
return huh.NewForm(huh.NewGroup(fields...)).WithShowHelp(true).Run()
}
// promptString prompts for a text input using huh TUI.
// If defaultVal is non-empty it is shown as placeholder; pressing Enter returns it.
func promptString(title, description, defaultVal string) (string, error) {
var value string
inp := huh.NewInput().
Title(title).
Value(&value)
if description != "" {
inp = inp.Description(description)
}
if defaultVal != "" {
inp = inp.Placeholder(defaultVal)
}
if err := runWithHelp(inp); err != nil {
return "", err
}
if value == "" {
return defaultVal, nil
}
return value, nil
}
// promptPassword prompts for a password input (hidden characters) using huh TUI.
func promptPassword(title, description string) (string, error) {
var value string
inp := huh.NewInput().
Title(title).
EchoMode(huh.EchoModePassword).
Value(&value)
if description != "" {
inp = inp.Description(description)
}
if err := runWithHelp(inp); err != nil {
return "", err
}
return value, nil
}
// filterThreshold: enable type-to-filter only when there are more than this many options.
const filterThreshold = 5
const scrollableThreshold = 15 // enable scrollbars when there are more than this many options
// promptSelect shows a single-select list using huh TUI.
// Returns the value of the selected option.
func promptSelect[T comparable](title string, options []SelectOption[T], defaultIdx int) (T, error) {
var value T
huhOpts := make([]huh.Option[T], len(options))
for i, opt := range options {
huhOpts[i] = huh.NewOption(opt.Label, opt.Value)
}
if defaultIdx >= 0 && defaultIdx < len(options) {
huhOpts[defaultIdx] = huhOpts[defaultIdx].Selected(true)
}
sel := huh.NewSelect[T]().
Options(huhOpts...).
Value(&value)
if len(options) > scrollableThreshold {
sel.Height(scrollableThreshold) // show scrollbar if too many options
title += " (scroll for more)"
}
sel.Title(title)
if err := runWithHelp(sel); err != nil {
var zero T
return zero, err
}
return value, nil
}
// promptMultiSelect shows a multi-select list using huh TUI.
// Returns the values of all selected options.
func promptMultiSelect[T comparable](title, description string, options []SelectOption[T], preselected []T) ([]T, error) {
var values []T
// Build pre-selected set for fast lookup
preSet := make(map[T]bool, len(preselected))
for _, v := range preselected {
preSet[v] = true
}
huhOpts := make([]huh.Option[T], len(options))
for i, opt := range options {
o := huh.NewOption(opt.Label, opt.Value)
if preSet[opt.Value] {
o = o.Selected(true)
}
huhOpts[i] = o
}
ms := huh.NewMultiSelect[T]().
Title(title).
Options(huhOpts...).
Value(&values)
if description != "" {
ms = ms.Description(description)
}
if len(options) > filterThreshold {
ms = ms.Filtering(true)
}
if err := runWithHelp(ms); err != nil {
return nil, err
}
return values, nil
}
// promptConfirm asks a yes/no question using huh TUI. Returns true for yes.
func promptConfirm(title string, defaultYes bool) (bool, error) {
value := defaultYes
c := huh.NewConfirm().
Title(title).
Affirmative("Yes").
Negative("No").
Value(&value)
if err := runWithHelp(c); err != nil {
return false, err
}
return value, nil
}
// SelectOption represents a single option in a select prompt.
type SelectOption[T any] struct {
Label string
Value T
}