diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index f8da420..5a74cbb 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -14,7 +14,7 @@ jobs: - name: Install Go uses: actions/setup-go@v5 with: - go-version: ~1.19 + go-version: ~1.21 - name: Checkout code uses: actions/checkout@v4 diff --git a/choose/choose.go b/choose/choose.go deleted file mode 100644 index cc72ec8..0000000 --- a/choose/choose.go +++ /dev/null @@ -1,220 +0,0 @@ -// Package choose provides an interface to choose one option from a given list -// of options. The options can be provided as (new-line separated) stdin or a -// list of arguments. -// -// It is different from the filter command as it does not provide a fuzzy -// finding input, so it is best used for smaller lists of options. -// -// Let's pick from a list of gum flavors: -// -// $ gum choose "Strawberry" "Banana" "Cherry" -package choose - -import ( - "strings" - "time" - - "github.com/charmbracelet/gum/timeout" - - "github.com/charmbracelet/bubbles/paginator" - tea "github.com/charmbracelet/bubbletea" - "github.com/charmbracelet/lipgloss" -) - -type model struct { - height int - cursor string - selectedPrefix string - unselectedPrefix string - cursorPrefix string - header string - items []item - quitting bool - index int - limit int - numSelected int - currentOrder int - paginator paginator.Model - aborted bool - - // styles - cursorStyle lipgloss.Style - headerStyle lipgloss.Style - itemStyle lipgloss.Style - selectedItemStyle lipgloss.Style - hasTimeout bool - timeout time.Duration -} - -type item struct { - text string - selected bool - order int -} - -func (m model) Init() tea.Cmd { - return timeout.Init(m.timeout, nil) -} - -func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { - switch msg := msg.(type) { - case tea.WindowSizeMsg: - return m, nil - case timeout.TickTimeoutMsg: - if msg.TimeoutValue <= 0 { - m.quitting = true - // If the user hasn't selected any items in a multi-select. - // Then we select the item that they have pressed enter on. If they - // have selected items, then we simply return them. - if m.numSelected < 1 { - m.items[m.index].selected = true - } - return m, tea.Quit - } - m.timeout = msg.TimeoutValue - return m, timeout.Tick(msg.TimeoutValue, msg.Data) - case tea.KeyMsg: - start, end := m.paginator.GetSliceBounds(len(m.items)) - switch keypress := msg.String(); keypress { - case "down", "j", "ctrl+j", "ctrl+n": - m.index++ - if m.index >= len(m.items) { - m.index = 0 - m.paginator.Page = 0 - } - if m.index >= end { - m.paginator.NextPage() - } - case "up", "k", "ctrl+k", "ctrl+p": - m.index-- - if m.index < 0 { - m.index = len(m.items) - 1 - m.paginator.Page = m.paginator.TotalPages - 1 - } - if m.index < start { - m.paginator.PrevPage() - } - case "right", "l", "ctrl+f": - m.index = clamp(m.index+m.height, 0, len(m.items)-1) - m.paginator.NextPage() - case "left", "h", "ctrl+b": - m.index = clamp(m.index-m.height, 0, len(m.items)-1) - m.paginator.PrevPage() - case "G", "end": - m.index = len(m.items) - 1 - m.paginator.Page = m.paginator.TotalPages - 1 - case "g", "home": - m.index = 0 - m.paginator.Page = 0 - case "a": - if m.limit <= 1 { - break - } - for i := range m.items { - if m.numSelected >= m.limit { - break // do not exceed given limit - } - if m.items[i].selected { - continue - } - m.items[i].selected = true - m.items[i].order = m.currentOrder - m.numSelected++ - m.currentOrder++ - } - case "A": - if m.limit <= 1 { - break - } - for i := range m.items { - m.items[i].selected = false - m.items[i].order = 0 - } - m.numSelected = 0 - m.currentOrder = 0 - case "ctrl+c", "esc": - m.aborted = true - m.quitting = true - return m, tea.Quit - case " ", "tab", "x", "ctrl+@": - if m.limit == 1 { - break // no op - } - - if m.items[m.index].selected { - m.items[m.index].selected = false - m.numSelected-- - } else if m.numSelected < m.limit { - m.items[m.index].selected = true - m.items[m.index].order = m.currentOrder - m.numSelected++ - m.currentOrder++ - } - case "enter": - m.quitting = true - if m.limit <= 1 && m.numSelected < 1 { - m.items[m.index].selected = true - } - return m, tea.Quit - } - } - - var cmd tea.Cmd - m.paginator, cmd = m.paginator.Update(msg) - return m, cmd -} - -func (m model) View() string { - if m.quitting { - return "" - } - - var s strings.Builder - var timeoutStr string - - start, end := m.paginator.GetSliceBounds(len(m.items)) - for i, item := range m.items[start:end] { - if i == m.index%m.height { - s.WriteString(m.cursorStyle.Render(m.cursor)) - } else { - s.WriteString(strings.Repeat(" ", lipgloss.Width(m.cursor))) - } - - if item.selected { - if m.hasTimeout { - timeoutStr = timeout.Str(m.timeout) - } - s.WriteString(m.selectedItemStyle.Render(m.selectedPrefix + item.text + timeoutStr)) - } else if i == m.index%m.height { - s.WriteString(m.cursorStyle.Render(m.cursorPrefix + item.text)) - } else { - s.WriteString(m.itemStyle.Render(m.unselectedPrefix + item.text)) - } - if i != m.height { - s.WriteRune('\n') - } - } - - if m.paginator.TotalPages > 1 { - s.WriteString(strings.Repeat("\n", m.height-m.paginator.ItemsOnPage(len(m.items))+1)) - s.WriteString(" " + m.paginator.View()) - } - - if m.header != "" { - header := m.headerStyle.Render(m.header) - return lipgloss.JoinVertical(lipgloss.Left, header, s.String()) - } - - return s.String() -} - -//nolint:unparam -func clamp(x, min, max int) int { - if x < min { - return min - } - if x > max { - return max - } - return x -} diff --git a/choose/command.go b/choose/command.go index 66691d8..6851cd4 100644 --- a/choose/command.go +++ b/choose/command.go @@ -4,28 +4,20 @@ import ( "errors" "fmt" "os" - "sort" "strings" - "github.com/charmbracelet/bubbles/paginator" - tea "github.com/charmbracelet/bubbletea" + "github.com/charmbracelet/huh" "github.com/charmbracelet/lipgloss" "github.com/mattn/go-isatty" "github.com/charmbracelet/gum/ansi" - "github.com/charmbracelet/gum/internal/exit" "github.com/charmbracelet/gum/internal/stdin" ) -var ( - subduedStyle = lipgloss.NewStyle().Foreground(lipgloss.AdaptiveColor{Light: "#847A85", Dark: "#979797"}) - verySubduedStyle = lipgloss.NewStyle().Foreground(lipgloss.AdaptiveColor{Light: "#DDDADA", Dark: "#3C3C3C"}) -) - // Run provides a shell script interface for choosing between different through // options. func (o Options) Run() error { - if len(o.Options) == 0 { + if len(o.Options) <= 0 { input, _ := stdin.Read() if input == "" { return errors.New("no options provided, see `gum choose --help`") @@ -38,127 +30,101 @@ func (o Options) Run() error { return nil } - // We don't need to display prefixes if we are only picking one option. - // Simply displaying the cursor is enough. - if o.Limit == 1 && !o.NoLimit { - o.SelectedPrefix = "" - o.UnselectedPrefix = "" - o.CursorPrefix = "" - } + theme := huh.ThemeCharm() + options := huh.NewOptions(o.Options...) - // If we've set no limit then we can simply select as many options as there - // are so let's set the limit to the number of options. - if o.NoLimit { - o.Limit = len(o.Options) + 1 - } + theme.Focused.Base.Border(lipgloss.Border{}) + theme.Focused.Title = o.HeaderStyle.ToLipgloss() + theme.Focused.SelectSelector = o.CursorStyle.ToLipgloss().SetString(o.Cursor) + theme.Focused.MultiSelectSelector = o.CursorStyle.ToLipgloss().SetString(o.Cursor) + theme.Focused.SelectedOption = o.SelectedItemStyle.ToLipgloss() + theme.Focused.UnselectedOption = o.ItemStyle.ToLipgloss() + theme.Focused.SelectedPrefix = o.SelectedItemStyle.ToLipgloss().SetString(o.SelectedPrefix) + theme.Focused.UnselectedPrefix = o.ItemStyle.ToLipgloss().SetString(o.UnselectedPrefix) - if len(o.Selected) > o.Limit { - return errors.New("number of selected options cannot be greater than the limit") - } - - // Keep track of the selected items. - currentSelected := 0 - // Check if selected items should be used. - hasSelectedItems := len(o.Selected) > 0 - - startingIndex := 0 - currentOrder := 0 - - items := make([]item, len(o.Options)) - - for i, option := range o.Options { - var order int - // Check if the option should be selected. - isSelected := hasSelectedItems && currentSelected < o.Limit && arrayContains(o.Selected, option) - // If the option is selected then increment the current selected count. - if isSelected { - if o.Limit == 1 { - // When the user can choose only one option don't select the option but - // start with the cursor hovering over it. - startingIndex = i - isSelected = false - } else { - currentSelected++ - order = currentOrder - currentOrder++ + for _, s := range o.Selected { + for i, opt := range options { + if s == opt.Key || s == opt.Value { + options[i] = opt.Selected(true) } } - - items[i] = item{text: option, selected: isSelected, order: order} } - // Use the pagination model to display the current and total number of - // pages. - pager := paginator.New() - pager.SetTotalPages((len(items) + o.Height - 1) / o.Height) - pager.PerPage = o.Height - pager.Type = paginator.Dots - pager.ActiveDot = subduedStyle.Render("•") - pager.InactiveDot = verySubduedStyle.Render("•") - pager.KeyMap = paginator.KeyMap{} - pager.Page = startingIndex / o.Height + if o.NoLimit { + o.Limit = len(o.Options) + } - // Disable Keybindings since we will control it ourselves. - tm, err := tea.NewProgram(model{ - index: startingIndex, - currentOrder: currentOrder, - height: o.Height, - cursor: o.Cursor, - header: o.Header, - selectedPrefix: o.SelectedPrefix, - unselectedPrefix: o.UnselectedPrefix, - cursorPrefix: o.CursorPrefix, - items: items, - limit: o.Limit, - paginator: pager, - cursorStyle: o.CursorStyle.ToLipgloss(), - headerStyle: o.HeaderStyle.ToLipgloss(), - itemStyle: o.ItemStyle.ToLipgloss(), - selectedItemStyle: o.SelectedItemStyle.ToLipgloss(), - numSelected: currentSelected, - hasTimeout: o.Timeout > 0, - timeout: o.Timeout, - }, tea.WithOutput(os.Stderr)).Run() + width := max(widest(o.Options)+ + max(lipgloss.Width(o.SelectedPrefix), lipgloss.Width(o.UnselectedPrefix))+ + lipgloss.Width(o.Cursor)+1, lipgloss.Width(o.Header)+1) + + if o.Limit > 1 { + var choices []string + err := huh.NewForm( + huh.NewGroup( + huh.NewMultiSelect[string](). + Options(options...). + Title(o.Header). + Filterable(false). + Height(o.Height). + Limit(o.Limit). + Value(&choices), + ), + ). + WithWidth(width). + WithShowHelp(false). + WithTheme(theme). + Run() + if err != nil { + return err + } + if len(choices) > 0 { + s := strings.Join(choices, "\n") + if isatty.IsTerminal(os.Stdout.Fd()) { + fmt.Println(s) + } else { + fmt.Print(ansi.Strip(s)) + } + } + return nil + } + + var choice string + + err := huh.NewForm( + huh.NewGroup( + huh.NewSelect[string](). + Options(options...). + Title(o.Header). + Height(o.Height). + Value(&choice), + ), + ). + WithWidth(width). + WithTheme(theme). + WithShowHelp(false). + Run() if err != nil { - return fmt.Errorf("failed to start tea program: %w", err) - } - - m := tm.(model) - if m.aborted { - return exit.ErrAborted - } - - if o.Ordered && o.Limit > 1 { - sort.Slice(m.items, func(i, j int) bool { - return m.items[i].order < m.items[j].order - }) - } - - var s strings.Builder - - for _, item := range m.items { - if item.selected { - s.WriteString(item.text) - s.WriteRune('\n') - } + return err } if isatty.IsTerminal(os.Stdout.Fd()) { - fmt.Print(s.String()) + fmt.Println(choice) } else { - fmt.Print(ansi.Strip(s.String())) + fmt.Print(ansi.Strip(choice)) } return nil } -// Check if an array contains a value. -func arrayContains(strArray []string, value string) bool { - for _, str := range strArray { - if str == value { - return true +func widest(options []string) int { + var max int + for _, o := range options { + w := lipgloss.Width(o) + if w > max { + max = w } } - return false + return max } diff --git a/choose/options.go b/choose/options.go index cab9e80..552fed6 100644 --- a/choose/options.go +++ b/choose/options.go @@ -15,13 +15,13 @@ type Options struct { Height int `help:"Height of the list" default:"10" env:"GUM_CHOOSE_HEIGHT"` Cursor string `help:"Prefix to show on item that corresponds to the cursor position" default:"> " env:"GUM_CHOOSE_CURSOR"` Header string `help:"Header value" default:"" env:"GUM_CHOOSE_HEADER"` - CursorPrefix string `help:"Prefix to show on the cursor item (hidden if limit is 1)" default:"○ " env:"GUM_CHOOSE_CURSOR_PREFIX"` - SelectedPrefix string `help:"Prefix to show on selected items (hidden if limit is 1)" default:"◉ " env:"GUM_CHOOSE_SELECTED_PREFIX"` - UnselectedPrefix string `help:"Prefix to show on unselected items (hidden if limit is 1)" default:"○ " env:"GUM_CHOOSE_UNSELECTED_PREFIX"` + CursorPrefix string `help:"Prefix to show on the cursor item (hidden if limit is 1)" default:"• " env:"GUM_CHOOSE_CURSOR_PREFIX"` + SelectedPrefix string `help:"Prefix to show on selected items (hidden if limit is 1)" default:"✓ " env:"GUM_CHOOSE_SELECTED_PREFIX"` + UnselectedPrefix string `help:"Prefix to show on unselected items (hidden if limit is 1)" default:"• " env:"GUM_CHOOSE_UNSELECTED_PREFIX"` Selected []string `help:"Options that should start as selected" default:"" env:"GUM_CHOOSE_SELECTED"` SelectIfOne bool `help:"Select the given option if there is only one" group:"Selection"` CursorStyle style.Styles `embed:"" prefix:"cursor." set:"defaultForeground=212" envprefix:"GUM_CHOOSE_CURSOR_"` - HeaderStyle style.Styles `embed:"" prefix:"header." set:"defaultForeground=240" envprefix:"GUM_CHOOSE_HEADER_"` + HeaderStyle style.Styles `embed:"" prefix:"header." set:"defaultForeground=99" envprefix:"GUM_CHOOSE_HEADER_"` ItemStyle style.Styles `embed:"" prefix:"item." hidden:"" envprefix:"GUM_CHOOSE_ITEM_"` SelectedItemStyle style.Styles `embed:"" prefix:"selected." set:"defaultForeground=212" envprefix:"GUM_CHOOSE_SELECTED_"` Timeout time.Duration `help:"Timeout until choose returns selected element" default:"0" env:"GUM_CCHOOSE_TIMEOUT"` // including timeout command options [Timeout,...] diff --git a/go.mod b/go.mod index 8ee5837..5d70db2 100644 --- a/go.mod +++ b/go.mod @@ -1,15 +1,16 @@ module github.com/charmbracelet/gum -go 1.19 +go 1.21 require ( - github.com/alecthomas/kong v0.8.1 + github.com/alecthomas/kong v0.9.0 github.com/alecthomas/mango-kong v0.1.0 github.com/charmbracelet/bubbles v0.18.0 github.com/charmbracelet/bubbletea v0.25.0 github.com/charmbracelet/glamour v0.7.0 + github.com/charmbracelet/huh v0.3.1-0.20240327025511-ec643317aa10 github.com/charmbracelet/lipgloss v0.10.0 - github.com/charmbracelet/log v0.3.1 + github.com/charmbracelet/log v0.4.0 github.com/mattn/go-isatty v0.0.20 github.com/muesli/reflow v0.3.0 github.com/muesli/roff v0.1.0 @@ -18,30 +19,32 @@ require ( ) require ( - github.com/alecthomas/chroma/v2 v2.8.0 // indirect + github.com/alecthomas/chroma/v2 v2.13.0 // indirect github.com/atotto/clipboard v0.1.4 // indirect github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect github.com/aymerick/douceur v0.2.0 // indirect - github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 // indirect - github.com/dlclark/regexp2 v1.8.1 // indirect + github.com/catppuccin/go v0.2.0 // indirect + github.com/charmbracelet/x/exp/strings v0.0.0-20240328150354-ab9afc214dfd // indirect + github.com/containerd/console v1.0.4 // indirect + github.com/dlclark/regexp2 v1.11.0 // indirect github.com/dustin/go-humanize v1.0.1 // indirect github.com/go-logfmt/logfmt v0.6.0 // indirect - github.com/gorilla/css v1.0.0 // indirect + github.com/gorilla/css v1.0.1 // indirect github.com/lucasb-eyer/go-colorful v1.2.0 // indirect github.com/mattn/go-localereader v0.0.1 // indirect github.com/mattn/go-runewidth v0.0.15 // indirect - github.com/microcosm-cc/bluemonday v1.0.25 // indirect + github.com/microcosm-cc/bluemonday v1.0.26 // indirect github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect github.com/muesli/cancelreader v0.2.2 // indirect github.com/muesli/mango v0.2.0 // indirect github.com/olekukonko/tablewriter v0.0.5 // indirect github.com/rivo/uniseg v0.4.7 // indirect - github.com/yuin/goldmark v1.5.4 // indirect + github.com/yuin/goldmark v1.7.0 // indirect github.com/yuin/goldmark-emoji v1.0.2 // indirect - golang.org/x/exp v0.0.0-20231006140011-7918f672742d // indirect - golang.org/x/net v0.17.0 // indirect - golang.org/x/sync v0.1.0 // indirect - golang.org/x/sys v0.13.0 // indirect - golang.org/x/term v0.13.0 // indirect - golang.org/x/text v0.13.0 // indirect + golang.org/x/exp v0.0.0-20240325151524-a685a6edb6d8 // indirect + golang.org/x/net v0.22.0 // indirect + golang.org/x/sync v0.6.0 // indirect + golang.org/x/sys v0.18.0 // indirect + golang.org/x/term v0.18.0 // indirect + golang.org/x/text v0.14.0 // indirect ) diff --git a/go.sum b/go.sum index b1f710f..1d6cb9d 100644 --- a/go.sum +++ b/go.sum @@ -2,8 +2,12 @@ github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d h1:licZJFw2RwpH github.com/alecthomas/assert/v2 v2.2.1 h1:XivOgYcduV98QCahG8T5XTezV5bylXe+lBxLG2K2ink= github.com/alecthomas/chroma/v2 v2.8.0 h1:w9WJUjFFmHHB2e8mRpL9jjy3alYDlU0QLDezj1xE264= github.com/alecthomas/chroma/v2 v2.8.0/go.mod h1:yrkMI9807G1ROx13fhe1v6PN2DDeaR73L3d+1nmYQtw= +github.com/alecthomas/chroma/v2 v2.13.0 h1:VP72+99Fb2zEcYM0MeaWJmV+xQvz5v5cxRHd+ooU1lI= +github.com/alecthomas/chroma/v2 v2.13.0/go.mod h1:BUGjjsD+ndS6eX37YgTchSEG+Jg9Jv1GiZs9sqPqztk= github.com/alecthomas/kong v0.8.1 h1:acZdn3m4lLRobeh3Zi2S2EpnXTd1mOL6U7xVml+vfkY= github.com/alecthomas/kong v0.8.1/go.mod h1:n1iCIO2xS46oE8ZfYCNDqdR0b0wZNrXAIAqro/2132U= +github.com/alecthomas/kong v0.9.0 h1:G5diXxc85KvoV2f0ZRVuMsi45IrBgx9zDNGNj165aPA= +github.com/alecthomas/kong v0.9.0/go.mod h1:Y47y5gKfHp1hDc7CH7OeXgLIpp+Q2m1Ni0L5s3bI8Os= github.com/alecthomas/mango-kong v0.1.0 h1:iFVfP1k1K4qpml3JUQmD5I8MCQYfIvsD9mRdrw7jJC4= github.com/alecthomas/mango-kong v0.1.0/go.mod h1:t+TYVdsONUolf/BwVcm+15eqcdAj15h4Qe9MMFAwwT4= github.com/alecthomas/repr v0.2.0 h1:HAzS41CIzNW5syS8Mf9UwXhNH1J9aix/BvDRf1Ml2Yk= @@ -13,27 +17,43 @@ github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiE github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk= github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4= +github.com/catppuccin/go v0.2.0 h1:ktBeIrIP42b/8FGiScP9sgrWOss3lw0Z5SktRoithGA= +github.com/catppuccin/go v0.2.0/go.mod h1:8IHJuMGaUUjQM82qBrGNBv7LFq6JI3NnQCF6MOlZjpc= github.com/charmbracelet/bubbles v0.18.0 h1:PYv1A036luoBGroX6VWjQIE9Syf2Wby2oOl/39KLfy0= github.com/charmbracelet/bubbles v0.18.0/go.mod h1:08qhZhtIwzgrtBjAcJnij1t1H0ZRjwHyGsy6AL11PSw= github.com/charmbracelet/bubbletea v0.25.0 h1:bAfwk7jRz7FKFl9RzlIULPkStffg5k6pNt5dywy4TcM= github.com/charmbracelet/bubbletea v0.25.0/go.mod h1:EN3QDR1T5ZdWmdfDzYcqOCAps45+QIJbLOBxmVNWNNg= github.com/charmbracelet/glamour v0.7.0 h1:2BtKGZ4iVJCDfMF229EzbeR1QRKLWztO9dMtjmqZSng= github.com/charmbracelet/glamour v0.7.0/go.mod h1:jUMh5MeihljJPQbJ/wf4ldw2+yBP59+ctV36jASy7ps= +github.com/charmbracelet/huh v0.3.0 h1:CxPplWkgW2yUTDDG0Z4S5HH8SJOosWHd4LxCvi0XsKE= +github.com/charmbracelet/huh v0.3.0/go.mod h1:fujUdKX8tC45CCSaRQdw789O6uaCRwx8l2NDyKfC4jA= +github.com/charmbracelet/huh v0.3.1-0.20240327025511-ec643317aa10 h1:779PmXc9Zt/Hxa0yg4I8sQk/1Nfa5TQisXaHZEW60Yk= +github.com/charmbracelet/huh v0.3.1-0.20240327025511-ec643317aa10/go.mod h1:x0rYoA1kpsaefXhRJZuxLM+qP4CYyEFE67T3ZGl7zPU= github.com/charmbracelet/lipgloss v0.10.0 h1:KWeXFSexGcfahHX+54URiZGkBFazf70JNMtwg/AFW3s= github.com/charmbracelet/lipgloss v0.10.0/go.mod h1:Wig9DSfvANsxqkRsqj6x87irdy123SR4dOXlKa91ciE= github.com/charmbracelet/log v0.3.1 h1:TjuY4OBNbxmHWSwO3tosgqs5I3biyY8sQPny/eCMTYw= github.com/charmbracelet/log v0.3.1/go.mod h1:OR4E1hutLsax3ZKpXbgUqPtTjQfrh1pG3zwHGWuuq8g= +github.com/charmbracelet/log v0.4.0 h1:G9bQAcx8rWA2T3pWvx7YtPTPwgqpk7D68BX21IRW8ZM= +github.com/charmbracelet/log v0.4.0/go.mod h1:63bXt/djrizTec0l11H20t8FDSvA4CRZJ1KH22MdptM= +github.com/charmbracelet/x/exp/strings v0.0.0-20240328150354-ab9afc214dfd h1:yTFoT3v/wDWzeoRXt9mIKlslAKfVNr0XdVCOVwRK8ck= +github.com/charmbracelet/x/exp/strings v0.0.0-20240328150354-ab9afc214dfd/go.mod h1:pBhA0ybfXv6hDjQUZ7hk1lVxBiUbupdw5R31yPUViVQ= github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 h1:q2hJAaP1k2wIvVRd/hEHD7lacgqrCPS+k8g1MndzfWY= github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81/go.mod h1:YynlIjWYF8myEu6sdkwKIvGQq+cOckRm6So2avqoYAk= +github.com/containerd/console v1.0.4 h1:F2g4+oChYvBTsASRTz8NP6iIAi97J3TtSAsLbIFn4ro= +github.com/containerd/console v1.0.4/go.mod h1:YynlIjWYF8myEu6sdkwKIvGQq+cOckRm6So2avqoYAk= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/dlclark/regexp2 v1.8.1 h1:6Lcdwya6GjPUNsBct8Lg/yRPwMhABj269AAzdGSiR+0= github.com/dlclark/regexp2 v1.8.1/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= +github.com/dlclark/regexp2 v1.11.0 h1:G/nrcoOa7ZXlpoa/91N3X7mM3r8eIlMBBJZvsz/mxKI= +github.com/dlclark/regexp2 v1.11.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4= github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= github.com/gorilla/css v1.0.0 h1:BQqNyPTi50JCFMTw/b67hByjMVXZRwGha6wxVGkeihY= github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c= +github.com/gorilla/css v1.0.1 h1:ntNaBIghp6JmvWnxbZKANoLyuXTPZ4cAMlo6RyhlbO8= +github.com/gorilla/css v1.0.1/go.mod h1:BvnYkspnSzMmwRK+b8/xgNPLiIuNZr6vbZBTPQ2A3b0= github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= @@ -48,6 +68,8 @@ github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZ github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/microcosm-cc/bluemonday v1.0.25 h1:4NEwSfiJ+Wva0VxN5B8OwMicaJvD8r9tlJWm9rtloEg= github.com/microcosm-cc/bluemonday v1.0.25/go.mod h1:ZIOjCQp1OrzBBPIJmfX4qDYFuhU02nx4bn030ixfHLE= +github.com/microcosm-cc/bluemonday v1.0.26 h1:xbqSvqzQMeEHCqMi64VAs4d8uy6Mequs3rQ0k/Khz58= +github.com/microcosm-cc/bluemonday v1.0.26/go.mod h1:JyzOCs9gkyQyjs+6h10UEVSe02CGwkhd72Xdqh78TWs= github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI= github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo= github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA= @@ -73,20 +95,34 @@ github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcU github.com/yuin/goldmark v1.3.7/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.5.4 h1:2uY/xC0roWy8IBEGLgB1ywIoEJFGmRrX21YQcvGZzjU= github.com/yuin/goldmark v1.5.4/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +github.com/yuin/goldmark v1.7.0 h1:EfOIvIMZIzHdB/R/zVrikYLPPwJlfMcNczJFMs1m6sA= +github.com/yuin/goldmark v1.7.0/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E= github.com/yuin/goldmark-emoji v1.0.2 h1:c/RgTShNgHTtc6xdz2KKI74jJr6rWi7FPgnP9GAsO5s= github.com/yuin/goldmark-emoji v1.0.2/go.mod h1:RhP/RWpexdp+KHs7ghKnifRoIs/Bq4nDS7tRbCkOwKY= golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI= golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo= +golang.org/x/exp v0.0.0-20240325151524-a685a6edb6d8 h1:aAcj0Da7eBAtrTp03QXWvm88pSyOt+UgdZw2BFZ+lEw= +golang.org/x/exp v0.0.0-20240325151524-a685a6edb6d8/go.mod h1:CQ1k9gNrJ50XIzaKCRR2hssIjF07kZFEiieALBM/ARQ= golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= -golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= -golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc= +golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= +golang.org/x/sync v0.4.0 h1:zxkM55ReGkDlKSM+Fu41A+zmbZuaPVbGMzvvdUPznYQ= +golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= +golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= +golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= +golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.13.0 h1:bb+I9cTfFazGW51MZqBVmZy7+JEJMouUHTUSKVQLBek= golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= +golang.org/x/term v0.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8= +golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=