diff --git a/go.mod b/go.mod
index 2e7516f..9c75e54 100644
--- a/go.mod
+++ b/go.mod
@@ -9,9 +9,9 @@ require (
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armsubscriptions v1.3.0
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azsecrets v1.4.0
github.com/BurntSushi/toml v1.5.0
- github.com/charmbracelet/bubbles v0.21.0
+ github.com/charmbracelet/bubbles v0.21.1-0.20250623103423-23b8fd6302d7
github.com/charmbracelet/bubbletea v1.3.9
- github.com/charmbracelet/huh v0.7.0
+ github.com/charmbracelet/huh v0.8.0
github.com/charmbracelet/huh/spinner v0.0.0-20250714122654-40d2b68703eb
github.com/charmbracelet/lipgloss v1.1.0
github.com/charmbracelet/log v0.4.2
diff --git a/go.sum b/go.sum
index 0b4b02a..2509c8f 100644
--- a/go.sum
+++ b/go.sum
@@ -32,18 +32,18 @@ github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z
github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
-github.com/aymanbagabas/go-udiff v0.2.0 h1:TK0fH4MteXUDspT88n8CKzvK0X9O2xu9yQjWpi6yML8=
-github.com/aymanbagabas/go-udiff v0.2.0/go.mod h1:RE4Ex0qsGkTAJoQdQQCA0uG+nAzJO/pI/QwceO5fgrA=
+github.com/aymanbagabas/go-udiff v0.3.1 h1:LV+qyBQ2pqe0u42ZsUEtPiCaUoqgA9gYRDs3vj1nolY=
+github.com/aymanbagabas/go-udiff v0.3.1/go.mod h1:G0fsKmG+P6ylD0r6N/KgQD/nWzgfnl8ZBcNLgcbrw8E=
github.com/catppuccin/go v0.3.0 h1:d+0/YicIq+hSTo5oPuRi5kOpqkVA5tAsU6dNhvRu+aY=
github.com/catppuccin/go v0.3.0/go.mod h1:8IHJuMGaUUjQM82qBrGNBv7LFq6JI3NnQCF6MOlZjpc=
-github.com/charmbracelet/bubbles v0.21.0 h1:9TdC97SdRVg/1aaXNVWfFH3nnLAwOXr8Fn6u6mfQdFs=
-github.com/charmbracelet/bubbles v0.21.0/go.mod h1:HF+v6QUR4HkEpz62dx7ym2xc71/KBHg+zKwJtMw+qtg=
+github.com/charmbracelet/bubbles v0.21.1-0.20250623103423-23b8fd6302d7 h1:JFgG/xnwFfbezlUnFMJy0nusZvytYysV4SCS2cYbvws=
+github.com/charmbracelet/bubbles v0.21.1-0.20250623103423-23b8fd6302d7/go.mod h1:ISC1gtLcVilLOf23wvTfoQuYbW2q0JevFxPfUzZ9Ybw=
github.com/charmbracelet/bubbletea v1.3.9 h1:OBYdfRo6QnlIcXNmcoI2n1NNS65Nk6kI2L2FO1puS/4=
github.com/charmbracelet/bubbletea v1.3.9/go.mod h1:ORQfo0fk8U+po9VaNvnV95UPWA1BitP1E0N6xJPlHr4=
github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc h1:4pZI35227imm7yK2bGPcfpFEmuY1gc2YSTShr4iJBfs=
github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc/go.mod h1:X4/0JoqgTIPSFcRA/P6INZzIuyqdFY5rm8tb41s9okk=
-github.com/charmbracelet/huh v0.7.0 h1:W8S1uyGETgj9Tuda3/JdVkc3x7DBLZYPZc4c+/rnRdc=
-github.com/charmbracelet/huh v0.7.0/go.mod h1:UGC3DZHlgOKHvHC07a5vHag41zzhpPFj34U92sOmyuk=
+github.com/charmbracelet/huh v0.8.0 h1:Xz/Pm2h64cXQZn/Jvele4J3r7DDiqFCNIVteYukxDvY=
+github.com/charmbracelet/huh v0.8.0/go.mod h1:5YVc+SlZ1IhQALxRPpkGwwEKftN/+OlJlnJYlDRFqN4=
github.com/charmbracelet/huh/spinner v0.0.0-20250714122654-40d2b68703eb h1:foK5EYUrChM3+7lK6qCEH43p/3oljGMtWtRq+tv3As4=
github.com/charmbracelet/huh/spinner v0.0.0-20250714122654-40d2b68703eb/go.mod h1:imftm8y+Db+rZ4Jcb6A7qJ0eOX78s9m84n8cdipC+R0=
github.com/charmbracelet/lipgloss v1.1.0 h1:vYXsiLHVkK7fp74RkV7b2kq9+zDLoEU4MZoFqR/noCY=
diff --git a/vendor/github.com/charmbracelet/bubbles/cursor/cursor.go b/vendor/github.com/charmbracelet/bubbles/cursor/cursor.go
index d101332..155d56c 100644
--- a/vendor/github.com/charmbracelet/bubbles/cursor/cursor.go
+++ b/vendor/github.com/charmbracelet/bubbles/cursor/cursor.go
@@ -173,11 +173,13 @@ func (m *Model) BlinkCmd() tea.Cmd {
m.blinkTag++
+ blinkMsg := BlinkMsg{id: m.id, tag: m.blinkTag}
+
return func() tea.Msg {
defer cancel()
<-ctx.Done()
if ctx.Err() == context.DeadlineExceeded {
- return BlinkMsg{id: m.id, tag: m.blinkTag}
+ return blinkMsg
}
return blinkCanceled{}
}
diff --git a/vendor/github.com/charmbracelet/bubbles/table/table.go b/vendor/github.com/charmbracelet/bubbles/table/table.go
index ed29d8c..4dd1203 100644
--- a/vendor/github.com/charmbracelet/bubbles/table/table.go
+++ b/vendor/github.com/charmbracelet/bubbles/table/table.go
@@ -4,12 +4,13 @@ package table
import (
"strings"
- "github.com/charmbracelet/bubbles/help"
- "github.com/charmbracelet/bubbles/key"
- "github.com/charmbracelet/bubbles/viewport"
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss"
"github.com/mattn/go-runewidth"
+
+ "github.com/charmbracelet/bubbles/help"
+ "github.com/charmbracelet/bubbles/key"
+ "github.com/charmbracelet/bubbles/viewport"
)
// Model defines a state for the table widget.
@@ -306,6 +307,11 @@ func (m Model) Columns() []Column {
// SetRows sets a new rows state.
func (m *Model) SetRows(r []Row) {
m.rows = r
+
+ if m.cursor > len(m.rows)-1 {
+ m.cursor = len(m.rows) - 1
+ }
+
m.UpdateViewport()
}
diff --git a/vendor/github.com/charmbracelet/bubbles/textarea/textarea.go b/vendor/github.com/charmbracelet/bubbles/textarea/textarea.go
index d82ae97..61c5537 100644
--- a/vendor/github.com/charmbracelet/bubbles/textarea/textarea.go
+++ b/vendor/github.com/charmbracelet/bubbles/textarea/textarea.go
@@ -653,7 +653,7 @@ func (m *Model) deleteWordLeft() {
// Linter note: it's critical that we acquire the initial cursor position
// here prior to altering it via SetCursor() below. As such, moving this
// call into the corresponding if clause does not apply here.
- oldCol := m.col //nolint:ifshort
+ oldCol := m.col
m.SetCursor(m.col - 1)
for unicode.IsSpace(m.value[m.row][m.col]) {
diff --git a/vendor/github.com/charmbracelet/bubbles/textinput/textinput.go b/vendor/github.com/charmbracelet/bubbles/textinput/textinput.go
index 84cbbc9..9c6666e 100644
--- a/vendor/github.com/charmbracelet/bubbles/textinput/textinput.go
+++ b/vendor/github.com/charmbracelet/bubbles/textinput/textinput.go
@@ -400,7 +400,7 @@ func (m *Model) deleteWordBackward() {
// Linter note: it's critical that we acquire the initial cursor position
// here prior to altering it via SetCursor() below. As such, moving this
// call into the corresponding if clause does not apply here.
- oldPos := m.pos //nolint:ifshort
+ oldPos := m.pos
m.SetCursor(m.pos - 1)
for unicode.IsSpace(m.value[m.pos]) {
diff --git a/vendor/github.com/charmbracelet/huh/.golangci.yml b/vendor/github.com/charmbracelet/huh/.golangci.yml
index 7c0a115..929cb0a 100644
--- a/vendor/github.com/charmbracelet/huh/.golangci.yml
+++ b/vendor/github.com/charmbracelet/huh/.golangci.yml
@@ -7,7 +7,6 @@ linters:
- exhaustive
- goconst
- godot
- - godox
- gomoddirectives
- goprintffuncname
- gosec
@@ -27,13 +26,13 @@ linters:
- whitespace
- wrapcheck
exclusions:
+ rules:
+ - text: '(slog|log)\.\w+'
+ linters:
+ - noctx
generated: lax
presets:
- common-false-positives
- paths:
- - third_party$
- - builtin$
- - examples$
issues:
max-issues-per-linter: 0
max-same-issues: 0
@@ -43,7 +42,3 @@ formatters:
- goimports
exclusions:
generated: lax
- paths:
- - third_party$
- - builtin$
- - examples$
diff --git a/vendor/github.com/charmbracelet/huh/README.md b/vendor/github.com/charmbracelet/huh/README.md
index 5694c79..03484db 100644
--- a/vendor/github.com/charmbracelet/huh/README.md
+++ b/vendor/github.com/charmbracelet/huh/README.md
@@ -403,14 +403,15 @@ For more on Spinners see the [spinner examples](./spinner/examples) and
-In addition to its standalone mode, `huh?` has first-class support for
-[Bubble Tea][tea] and can be easily integrated into Bubble Tea applications.
-It’s incredibly useful in portions of your Bubble Tea application that need
-form-like input.
+Huh is built on [Bubble Tea][tea] and, in addition to its standalone mode,
+`huh?` has first-class support and can be easily integrated into
+Bubble Tea applications. It’s very useful in portions of your Bubble Tea
+application that need form-like input, and for times when you need more
+flexibility than `huh?` alone can offer.
-A `huh.Form` is merely a `tea.Model`, so you can use it just as
+A `huh.Form` is just a `tea.Model`, so you can use it just as
you would any other [Bubble](https://github.com/charmbracelet/bubbles).
```go
@@ -476,7 +477,6 @@ For some `Huh?` programs in production, see:
* [glyphs](https://github.com/maaslalani/glyphs): a unicode symbol picker
* [meteor](https://github.com/stefanlogue/meteor): a highly customisable conventional commit message tool
* [freeze](https://github.com/charmbracelet/freeze): a tool for generating images of code and terminal output
-* [gum](https://github.com/charmbracelet/gum): a tool for glamorous shell scripts
* [savvy](https://github.com/getsavvyinc/savvy-cli): the easiest way to create, share, and run runbooks in the terminal
## Contributing
diff --git a/vendor/github.com/charmbracelet/huh/field_confirm.go b/vendor/github.com/charmbracelet/huh/field_confirm.go
index 1c4b572..d788f89 100644
--- a/vendor/github.com/charmbracelet/huh/field_confirm.go
+++ b/vendor/github.com/charmbracelet/huh/field_confirm.go
@@ -35,7 +35,7 @@ type Confirm struct {
width int
height int
inline bool
- accessible bool
+ accessible bool // Deprecated: use RunAccessible instead.
theme *Theme
keymap ConfirmKeyMap
buttonAlignment lipgloss.Position
@@ -285,10 +285,7 @@ func (c *Confirm) View() string {
promptWidth := lipgloss.Width(sb.String())
buttonsWidth := lipgloss.Width(buttonsRow)
- renderWidth := promptWidth
- if buttonsWidth > renderWidth {
- renderWidth = buttonsWidth
- }
+ renderWidth := max(buttonsWidth, promptWidth)
style := lipgloss.NewStyle().Width(renderWidth).Align(c.buttonAlignment)
@@ -299,14 +296,14 @@ func (c *Confirm) View() string {
// Run runs the confirm field in accessible mode.
func (c *Confirm) Run() error {
- if c.accessible {
- return c.runAccessible(os.Stdout, os.Stdin)
+ if c.accessible { // TODO: remove in a future release.
+ return c.RunAccessible(os.Stdout, os.Stdin)
}
return Run(c)
}
-// runAccessible runs the confirm field in accessible mode.
-func (c *Confirm) runAccessible(w io.Writer, r io.Reader) error {
+// RunAccessible runs the confirm field in accessible mode.
+func (c *Confirm) RunAccessible(w io.Writer, r io.Reader) error {
styles := c.activeStyles()
defaultValue := c.GetValue().(bool)
opts := "[y/N]"
@@ -343,6 +340,9 @@ func (c *Confirm) WithKeyMap(k *KeyMap) Field {
}
// WithAccessible sets the accessible mode of the confirm field.
+//
+// Deprecated: you may now call [Confirm.RunAccessible] directly to run the
+// field in accessible mode.
func (c *Confirm) WithAccessible(accessible bool) Field {
c.accessible = accessible
return c
diff --git a/vendor/github.com/charmbracelet/huh/field_filepicker.go b/vendor/github.com/charmbracelet/huh/field_filepicker.go
index 06ab056..760c4ab 100644
--- a/vendor/github.com/charmbracelet/huh/field_filepicker.go
+++ b/vendor/github.com/charmbracelet/huh/field_filepicker.go
@@ -36,7 +36,7 @@ type FilePicker struct {
// options
width int
height int
- accessible bool
+ accessible bool // Deprecated: use RunAccessible instead.
theme *Theme
keymap FilePickerKeyMap
}
@@ -102,7 +102,7 @@ func (f *FilePicker) FileAllowed(v bool) *FilePicker {
return f
}
-// DirAllowed sets whether to allow files to be selected.
+// DirAllowed sets whether to allow directories to be selected.
func (f *FilePicker) DirAllowed(v bool) *FilePicker {
f.picker.DirAllowed = v
return f
@@ -274,7 +274,7 @@ func (f *FilePicker) View() string {
}
parts = append(parts, f.pickerView())
return styles.Base.Width(f.width).Height(f.height).
- Render(lipgloss.JoinVertical(lipgloss.Top, parts...))
+ Render(strings.Join(parts, "\n"))
}
func (f *FilePicker) pickerView() string {
@@ -308,14 +308,14 @@ func (f *FilePicker) setPicking(v bool) {
// Run runs the file field.
func (f *FilePicker) Run() error {
- if f.accessible {
- return f.runAccessible(os.Stdout, os.Stdin)
+ if f.accessible { // TODO: remove in a future release.
+ return f.RunAccessible(os.Stdout, os.Stdin)
}
return Run(f)
}
-// runAccessible runs an accessible file field.
-func (f *FilePicker) runAccessible(w io.Writer, r io.Reader) error {
+// RunAccessible runs an accessible file field.
+func (f *FilePicker) RunAccessible(w io.Writer, r io.Reader) error {
styles := f.activeStyles()
prompt := styles.Title.
PaddingRight(1).
@@ -403,6 +403,9 @@ func (f *FilePicker) WithKeyMap(k *KeyMap) Field {
}
// WithAccessible sets the accessible mode of the file field.
+//
+// Deprecated: you may now call [FilePicker.RunAccessible] directly to run the
+// field in accessible mode.
func (f *FilePicker) WithAccessible(accessible bool) Field {
f.accessible = accessible
return f
diff --git a/vendor/github.com/charmbracelet/huh/field_input.go b/vendor/github.com/charmbracelet/huh/field_input.go
index 7a66dcd..78a5bd7 100644
--- a/vendor/github.com/charmbracelet/huh/field_input.go
+++ b/vendor/github.com/charmbracelet/huh/field_input.go
@@ -38,9 +38,9 @@ type Input struct {
err error
focused bool
- accessible bool
+ accessible bool // Deprecated: use RunAccessible instead.
width int
- height int // not really used anywhere
+ height int
theme *Theme
keymap InputKeyMap
@@ -417,8 +417,8 @@ func (i *Input) View() string {
// Run runs the input field in accessible mode.
func (i *Input) Run() error {
- if i.accessible {
- return i.runAccessible(os.Stdout, os.Stdin)
+ if i.accessible { // TODO: remove in a future release.
+ return i.RunAccessible(os.Stdout, os.Stdin)
}
return i.run()
}
@@ -428,8 +428,8 @@ func (i *Input) run() error {
return Run(i)
}
-// runAccessible runs the input field in accessible mode.
-func (i *Input) runAccessible(w io.Writer, r io.Reader) error {
+// RunAccessible runs the input field in accessible mode.
+func (i *Input) RunAccessible(w io.Writer, r io.Reader) error {
styles := i.activeStyles()
validator := func(input string) error {
if i.textinput.CharLimit > 0 && len(input) > i.textinput.CharLimit {
@@ -438,8 +438,7 @@ func (i *Input) runAccessible(w io.Writer, r io.Reader) error {
return i.validate(input)
}
- //nolint:exhaustive
- switch i.textinput.EchoMode {
+ switch i.textinput.EchoMode { //nolint:exhaustive
case textinput.EchoNormal:
prompt := styles.Title.
PaddingRight(1).
@@ -471,6 +470,9 @@ func (i *Input) WithKeyMap(k *KeyMap) Field {
}
// WithAccessible sets the accessible mode of the input field.
+//
+// Deprecated: you may now call [Input.RunAccessible] directly to run the
+// field in accessible mode.
func (i *Input) WithAccessible(accessible bool) Field {
i.accessible = accessible
return i
diff --git a/vendor/github.com/charmbracelet/huh/field_multiselect.go b/vendor/github.com/charmbracelet/huh/field_multiselect.go
index f3328a5..4b66156 100644
--- a/vendor/github.com/charmbracelet/huh/field_multiselect.go
+++ b/vendor/github.com/charmbracelet/huh/field_multiselect.go
@@ -47,7 +47,7 @@ type MultiSelect[T comparable] struct {
// options
width int
- accessible bool
+ accessible bool // Deprecated: use RunAccessible instead.
theme *Theme
keymap MultiSelectKeyMap
}
@@ -700,14 +700,14 @@ func (m *MultiSelect[T]) setSelectAllHelp() {
// Run runs the multi-select field.
func (m *MultiSelect[T]) Run() error {
- if m.accessible {
- return m.runAccessible(os.Stdout, os.Stdin)
+ if m.accessible { // TODO: remove in a future release.
+ return m.RunAccessible(os.Stdout, os.Stdin)
}
return Run(m)
}
-// runAccessible() runs the multi-select field in accessible mode.
-func (m *MultiSelect[T]) runAccessible(w io.Writer, r io.Reader) error {
+// RunAccessible runs the multi-select field in accessible mode.
+func (m *MultiSelect[T]) RunAccessible(w io.Writer, r io.Reader) error {
styles := m.activeStyles()
title := styles.Title.
PaddingRight(1).
@@ -725,7 +725,7 @@ func (m *MultiSelect[T]) runAccessible(w io.Writer, r io.Reader) error {
prompt := fmt.Sprintf("Input a number between %d and %d: ", 0, len(m.options.val))
choice = accessibility.PromptInt(w, r, prompt, 0, len(m.options.val), nil)
- if choice == 0 {
+ if choice <= 0 {
m.updateValue()
err := m.validate(m.accessor.Get())
if err != nil {
@@ -774,6 +774,9 @@ func (m *MultiSelect[T]) WithKeyMap(k *KeyMap) Field {
}
// WithAccessible sets the accessible mode of the multi-select field.
+//
+// Deprecated: you may now call [MultiSelect.RunAccessible] directly to run the
+// field in accessible mode.
func (m *MultiSelect[T]) WithAccessible(accessible bool) Field {
m.accessible = accessible
return m
diff --git a/vendor/github.com/charmbracelet/huh/field_note.go b/vendor/github.com/charmbracelet/huh/field_note.go
index cb4285c..0b4bdc4 100644
--- a/vendor/github.com/charmbracelet/huh/field_note.go
+++ b/vendor/github.com/charmbracelet/huh/field_note.go
@@ -26,7 +26,7 @@ type Note struct {
showNextButton bool
skip bool
- accessible bool
+ accessible bool // Deprecated: use RunAccessible instead.
height int
width int
@@ -242,14 +242,14 @@ func (n *Note) View() string {
// Run runs the note field.
func (n *Note) Run() error {
- if n.accessible {
- return n.runAccessible(os.Stdout, os.Stdin)
+ if n.accessible { // TODO: remove in a future release.
+ return n.RunAccessible(os.Stdout, os.Stdin)
}
return Run(n)
}
-// runAccessible runs an accessible note field.
-func (n *Note) runAccessible(w io.Writer, _ io.Reader) error {
+// RunAccessible runs an accessible note field.
+func (n *Note) RunAccessible(w io.Writer, _ io.Reader) error {
styles := n.activeStyles()
if n.title.val != "" {
_, _ = fmt.Fprintln(w, styles.Title.Render(n.title.val))
@@ -276,6 +276,9 @@ func (n *Note) WithKeyMap(k *KeyMap) Field {
}
// WithAccessible sets the accessible mode of the note field.
+//
+// Deprecated: you may now call [Note.RunAccessible] directly to run the
+// field in accessible mode.
func (n *Note) WithAccessible(accessible bool) Field {
n.accessible = accessible
return n
diff --git a/vendor/github.com/charmbracelet/huh/field_select.go b/vendor/github.com/charmbracelet/huh/field_select.go
index 5fe66fe..115c542 100644
--- a/vendor/github.com/charmbracelet/huh/field_select.go
+++ b/vendor/github.com/charmbracelet/huh/field_select.go
@@ -52,7 +52,7 @@ type Select[T comparable] struct {
inline bool
width int
height int
- accessible bool
+ accessible bool // Deprecated: use RunAccessible instead.
theme *Theme
keymap SelectKeyMap
}
@@ -74,6 +74,7 @@ func NewSelect[T comparable]() *Select[T] {
validate: func(T) error { return nil },
filtering: false,
filter: filter,
+ id: nextID(),
options: Eval[[]Option[T]]{cache: make(map[uint64][]Option[T])},
title: Eval[string]{cache: make(map[uint64]string)},
description: Eval[string]{cache: make(map[uint64]string)},
@@ -539,6 +540,7 @@ func (s *Select[T]) updateViewportHeight() {
}
s.viewport.Height = max(minHeight, s.height-offset)
+ s.viewport.YOffset = s.selected
}
func (s *Select[T]) activeStyles() *FieldStyles {
@@ -561,7 +563,6 @@ func (s *Select[T]) titleView() string {
if s.filtering {
sb.WriteString(s.filter.View())
} else if s.filter.Value() != "" && !s.inline {
- sb.WriteString(styles.Title.Render(wrap(s.title.val, maxWidth)))
sb.WriteString(styles.Description.Render("/" + s.filter.Value()))
} else {
sb.WriteString(styles.Title.Render(wrap(s.title.val, maxWidth)))
@@ -672,7 +673,7 @@ func (s *Select[T]) View() string {
}
parts = append(parts, s.viewport.View())
return styles.Base.Width(s.width).Height(s.height).
- Render(lipgloss.JoinVertical(lipgloss.Top, parts...))
+ Render(strings.Join(parts, "\n"))
}
// clearFilter clears the value of the filter.
@@ -701,14 +702,14 @@ func (s *Select[T]) filterFunc(option string) bool {
// Run runs the select field.
func (s *Select[T]) Run() error {
- if s.accessible {
- return s.runAccessible(os.Stdout, os.Stdin)
+ if s.accessible { // TODO: remove in a future release.
+ return s.RunAccessible(os.Stdout, os.Stdin)
}
return Run(s)
}
-// runAccessible runs an accessible select field.
-func (s *Select[T]) runAccessible(w io.Writer, r io.Reader) error {
+// RunAccessible runs an accessible select field.
+func (s *Select[T]) RunAccessible(w io.Writer, r io.Reader) error {
styles := s.activeStyles()
_, _ = fmt.Fprintln(w, styles.Title.
PaddingRight(1).
@@ -718,10 +719,19 @@ func (s *Select[T]) runAccessible(w io.Writer, r io.Reader) error {
_, _ = fmt.Fprintf(w, "%d. %s\n", i+1, option.Key)
}
- prompt := fmt.Sprintf("Input a number between %d and %d: ", 1, len(s.options.val))
+ var defaultValue *int
+ switch s.accessor.(type) {
+ case *PointerAccessor[T]: // if its of this type, it means it has a default value
+ s.selectOption() // make sure s.selected is set
+ idx := s.selected + 1
+ defaultValue = &idx
+ }
+ prompt := fmt.Sprintf("Enter a number between %d and %d: ", 1, len(s.options.val))
+ if len(s.options.val) == 1 {
+ prompt = "There is only one option available; enter the number 1:"
+ }
for {
- selected := s.selected + 1
- choice := accessibility.PromptInt(w, r, prompt, 1, len(s.options.val), &selected)
+ choice := accessibility.PromptInt(w, r, prompt, 1, len(s.options.val), defaultValue)
option := s.options.val[choice-1]
if err := s.validate(option.Value); err != nil {
_, _ = fmt.Fprintln(w, err.Error())
@@ -759,6 +769,9 @@ func (s *Select[T]) WithKeyMap(k *KeyMap) Field {
}
// WithAccessible sets the accessible mode of the select field.
+//
+// Deprecated: you may now call [Select.RunAccessible] directly to run the
+// field in accessible mode.
func (s *Select[T]) WithAccessible(accessible bool) Field {
s.accessible = accessible
return s
diff --git a/vendor/github.com/charmbracelet/huh/field_text.go b/vendor/github.com/charmbracelet/huh/field_text.go
index 827ad65..8f21627 100644
--- a/vendor/github.com/charmbracelet/huh/field_text.go
+++ b/vendor/github.com/charmbracelet/huh/field_text.go
@@ -40,7 +40,7 @@ type Text struct {
validate func(string) error
err error
- accessible bool
+ accessible bool // Deprecated: use RunAccessible instead.
width int
theme *Theme
@@ -402,19 +402,20 @@ func (t *Text) View() string {
}
parts = append(parts, t.textarea.View())
- return styles.Base.Render(lipgloss.JoinVertical(lipgloss.Top, parts...))
+ return styles.Base.
+ Render(strings.Join(parts, "\n"))
}
// Run runs the text field.
func (t *Text) Run() error {
- if t.accessible {
- return t.runAccessible(os.Stdout, os.Stdin)
+ if t.accessible { // TODO: remove in a future release.
+ return t.RunAccessible(os.Stdout, os.Stdin)
}
return Run(t)
}
-// runAccessible runs an accessible text field.
-func (t *Text) runAccessible(w io.Writer, r io.Reader) error {
+// RunAccessible runs an accessible text field.
+func (t *Text) RunAccessible(w io.Writer, r io.Reader) error {
styles := t.activeStyles()
prompt := styles.Title.
PaddingRight(1).
@@ -456,6 +457,9 @@ func (t *Text) WithKeyMap(k *KeyMap) Field {
}
// WithAccessible sets the accessible mode of the text field.
+//
+// Deprecated: you may now call [Text.RunAccessible] directly to run the
+// field in accessible mode.
func (t *Text) WithAccessible(accessible bool) Field {
t.accessible = accessible
return t
diff --git a/vendor/github.com/charmbracelet/huh/form.go b/vendor/github.com/charmbracelet/huh/form.go
index f04bd72..978bc89 100644
--- a/vendor/github.com/charmbracelet/huh/form.go
+++ b/vendor/github.com/charmbracelet/huh/form.go
@@ -151,7 +151,8 @@ type Field interface {
// Run runs the field individually.
Run() error
- runAccessible(w io.Writer, r io.Reader) error
+ // RunAccessible runs the field in accessible mode with the given IO.
+ RunAccessible(w io.Writer, r io.Reader) error
// Skip returns whether this input should be skipped or not.
Skip() bool
@@ -167,6 +168,8 @@ type Field interface {
WithTheme(*Theme) Field
// WithAccessible sets whether the field should run in accessible mode.
+ //
+ // Deprecated: you may now call [Field.RunAccessible] directly to run the field in accessible mode.
WithAccessible(bool) Field
// WithKeyMap sets the keymap on a field.
@@ -540,9 +543,7 @@ func (f *Form) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
// heightest group, accounting for the width, wraps, etc.
neededHeight := 0
f.selector.Range(func(_ int, group *Group) bool {
- if h := group.rawHeight(); h > neededHeight {
- neededHeight = h
- }
+ neededHeight = max(neededHeight, group.rawHeight())
return true
})
@@ -709,7 +710,7 @@ func (f *Form) runAccessible(w io.Writer, r io.Reader) error {
group.selector.Range(func(_ int, field Field) bool {
field.Init()
field.Focus()
- _ = field.WithAccessible(true).runAccessible(w, r)
+ _ = field.WithAccessible(true).RunAccessible(w, r)
_, _ = fmt.Fprintln(w)
return true
})
diff --git a/vendor/github.com/charmbracelet/huh/group.go b/vendor/github.com/charmbracelet/huh/group.go
index 4f9bdb3..e8f9af7 100644
--- a/vendor/github.com/charmbracelet/huh/group.go
+++ b/vendor/github.com/charmbracelet/huh/group.go
@@ -126,7 +126,7 @@ func (g *Group) WithWidth(width int) *Group {
// WithHeight sets the height on a group.
func (g *Group) WithHeight(height int) *Group {
g.height = height
- h := height - g.nonContentHeight()
+ h := height - g.titleFooterHeight()
g.viewport.Height = h
g.selector.Range(func(_ int, field Field) bool {
// A field height must not exceed the form height.
@@ -322,7 +322,6 @@ func (g *Group) getContent() (int, string) {
func (g *Group) buildView() {
offset, content := g.getContent()
-
g.viewport.SetContent(content)
g.viewport.SetYOffset(offset)
}
@@ -337,24 +336,24 @@ func (g *Group) Header() string {
if g.description != "" {
parts = append(parts, styles.Description.Render(wrap(g.description, g.width)))
}
- return lipgloss.JoinVertical(lipgloss.Top, parts...)
+ return strings.Join(parts, "\n")
}
-// nonContentHeight returns the height of the footer + header.
-func (g *Group) nonContentHeight() int {
- var parts []string
+// titleFooterHeight returns the height of the footer + header.
+func (g *Group) titleFooterHeight() int {
+ h := 0
if s := g.Header(); s != "" {
- parts = append(parts, s)
+ h += lipgloss.Height(s)
}
if s := g.Footer(); s != "" {
- parts = append(parts, s)
+ h += lipgloss.Height(s)
}
- return lipgloss.Height(lipgloss.JoinVertical(lipgloss.Top, parts...))
+ return h
}
// rawHeight returns the full height of the group, without using a viewport.
func (g *Group) rawHeight() int {
- return lipgloss.Height(g.Content()) + g.nonContentHeight()
+ return lipgloss.Height(g.Content()) + g.titleFooterHeight()
}
// View renders the group.
@@ -368,7 +367,14 @@ func (g *Group) View() string {
// append an empty line, and the footer (usually the help).
parts = append(parts, "", s)
}
- return lipgloss.JoinVertical(lipgloss.Top, parts...)
+ if len(parts) > 0 {
+ // Trim suffix spaces from the last part as it can accidentally
+ // scroll the view up on some terminals (like Apple's Terminal.app)
+ // when we right to the bottom rightmost corner cell.
+ lastIdx := len(parts) - 1
+ parts[lastIdx] = strings.TrimSuffix(parts[lastIdx], " ")
+ }
+ return strings.Join(parts, "\n")
}
// Content renders the group's content only (no footer).
@@ -392,8 +398,6 @@ func (g *Group) Footer() string {
))
}
}
- return g.styles().Base.Render(lipgloss.JoinVertical(
- lipgloss.Top,
- parts...,
- ))
+ return g.styles().Base.
+ Render(strings.Join(parts, "\n"))
}
diff --git a/vendor/github.com/charmbracelet/huh/internal/accessibility/accessibility.go b/vendor/github.com/charmbracelet/huh/internal/accessibility/accessibility.go
index 9e139f4..f25f7f1 100644
--- a/vendor/github.com/charmbracelet/huh/internal/accessibility/accessibility.go
+++ b/vendor/github.com/charmbracelet/huh/internal/accessibility/accessibility.go
@@ -14,6 +14,13 @@ import (
"github.com/charmbracelet/x/term"
)
+func atoi(s string) (int, error) {
+ if strings.TrimSpace(s) == "" {
+ return -1, nil
+ }
+ return strconv.Atoi(s) //nolint:wrapcheck
+}
+
// PromptInt prompts a user for an integer between a certain range.
//
// Given invalid input (non-integers, integers outside of the range), the user
@@ -32,9 +39,12 @@ func PromptInt(
if strings.TrimSpace(s) == "" && defaultValue != nil {
return nil
}
- i, err := strconv.Atoi(s)
+ i, err := atoi(s)
if err != nil || i < low || i > high {
- return fmt.Errorf("Invalid: must be between %d and %d", low, high) //nolint:staticcheck
+ if low == high {
+ return fmt.Errorf("Invalid: must be %d", low) //nolint:staticcheck
+ }
+ return fmt.Errorf("Invalid: must be a number between %d and %d", low, high) //nolint:staticcheck
}
return nil
}
diff --git a/vendor/github.com/charmbracelet/huh/layout.go b/vendor/github.com/charmbracelet/huh/layout.go
index 4bb7f46..8b7965f 100644
--- a/vendor/github.com/charmbracelet/huh/layout.go
+++ b/vendor/github.com/charmbracelet/huh/layout.go
@@ -1,6 +1,8 @@
package huh
import (
+ "strings"
+
"github.com/charmbracelet/lipgloss"
)
@@ -76,11 +78,11 @@ func (l *layoutColumns) View(f *Form) string {
header := f.selector.Selected().Header()
footer := f.selector.Selected().Footer()
- return lipgloss.JoinVertical(lipgloss.Left,
+ return strings.Join([]string{
header,
lipgloss.JoinHorizontal(lipgloss.Left, columns...),
footer,
- )
+ }, "\n")
}
func (l *layoutColumns) GroupWidth(_ *Form, _ *Group, w int) int {
@@ -99,7 +101,7 @@ func (l *layoutStack) View(f *Form) string {
if footer := f.selector.Selected().Footer(); footer != "" {
columns = append(columns, footer)
}
- return lipgloss.JoinVertical(lipgloss.Top, columns...)
+ return strings.Join(columns, "\n")
}
func (l *layoutStack) GroupWidth(_ *Form, _ *Group, w int) int {
@@ -159,10 +161,7 @@ func (l *layoutGrid) View(f *Form) string {
}
footer := f.selector.Selected().Footer()
- return lipgloss.JoinVertical(
- lipgloss.Top,
- append(rows, footer)...,
- )
+ return strings.Join(append(rows, footer), "\n")
}
func (l *layoutGrid) GroupWidth(_ *Form, _ *Group, w int) int {
diff --git a/vendor/modules.txt b/vendor/modules.txt
index 236a23e..28f663f 100644
--- a/vendor/modules.txt
+++ b/vendor/modules.txt
@@ -86,7 +86,7 @@ github.com/aymanbagabas/go-osc52/v2
# github.com/catppuccin/go v0.3.0
## explicit; go 1.19
github.com/catppuccin/go
-# github.com/charmbracelet/bubbles v0.21.0
+# github.com/charmbracelet/bubbles v0.21.1-0.20250623103423-23b8fd6302d7
## explicit; go 1.23.0
github.com/charmbracelet/bubbles/cursor
github.com/charmbracelet/bubbles/filepicker
@@ -105,7 +105,7 @@ github.com/charmbracelet/bubbletea
# github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc
## explicit; go 1.18
github.com/charmbracelet/colorprofile
-# github.com/charmbracelet/huh v0.7.0
+# github.com/charmbracelet/huh v0.8.0
## explicit; go 1.23.0
github.com/charmbracelet/huh
github.com/charmbracelet/huh/internal/accessibility