diff --git a/choose/choose.go b/choose/choose.go index 48f885709..115464749 100644 --- a/choose/choose.go +++ b/choose/choose.go @@ -13,11 +13,11 @@ package choose import ( "strings" - "github.com/charmbracelet/bubbles/help" - "github.com/charmbracelet/bubbles/key" - "github.com/charmbracelet/bubbles/paginator" - tea "github.com/charmbracelet/bubbletea" - "github.com/charmbracelet/lipgloss" + "github.com/charmbracelet/bubbles/v2/help" + "github.com/charmbracelet/bubbles/v2/key" + "github.com/charmbracelet/bubbles/v2/paginator" + tea "github.com/charmbracelet/bubbletea/v2" + "github.com/charmbracelet/lipgloss/v2" ) func defaultKeymap() keymap { diff --git a/choose/command.go b/choose/command.go index 63194f04e..195707782 100644 --- a/choose/command.go +++ b/choose/command.go @@ -8,21 +8,28 @@ import ( "sort" "strings" - "github.com/charmbracelet/bubbles/help" - "github.com/charmbracelet/bubbles/paginator" - tea "github.com/charmbracelet/bubbletea" + "github.com/charmbracelet/bubbles/v2/help" + "github.com/charmbracelet/bubbles/v2/paginator" + tea "github.com/charmbracelet/bubbletea/v2" "github.com/charmbracelet/gum/internal/stdin" "github.com/charmbracelet/gum/internal/timeout" "github.com/charmbracelet/gum/internal/tty" - "github.com/charmbracelet/lipgloss" + "github.com/charmbracelet/lipgloss/v2" + "github.com/charmbracelet/lipgloss/v2/compat" ) // Run provides a shell script interface for choosing between different through // options. func (o Options) Run() error { var ( - subduedStyle = lipgloss.NewStyle().Foreground(lipgloss.AdaptiveColor{Light: "#847A85", Dark: "#979797"}) - verySubduedStyle = lipgloss.NewStyle().Foreground(lipgloss.AdaptiveColor{Light: "#DDDADA", Dark: "#3C3C3C"}) + subduedStyle = lipgloss.NewStyle().Foreground(compat.AdaptiveColor{ + Light: lipgloss.Color("#847A85"), + Dark: lipgloss.Color("#979797"), + }) + verySubduedStyle = lipgloss.NewStyle().Foreground(compat.AdaptiveColor{ + Light: lipgloss.Color("#DDDADA"), + Dark: lipgloss.Color("#3C3C3C"), + }) ) input, _ := stdin.Read(stdin.StripANSI(o.StripANSI)) diff --git a/confirm/command.go b/confirm/command.go index abbfad932..0c6f55a95 100644 --- a/confirm/command.go +++ b/confirm/command.go @@ -4,8 +4,8 @@ import ( "fmt" "os" - "github.com/charmbracelet/bubbles/help" - tea "github.com/charmbracelet/bubbletea" + "github.com/charmbracelet/bubbles/v2/help" + tea "github.com/charmbracelet/bubbletea/v2" "github.com/charmbracelet/gum/internal/exit" "github.com/charmbracelet/gum/internal/stdin" "github.com/charmbracelet/gum/internal/timeout" diff --git a/confirm/confirm.go b/confirm/confirm.go index c32ade985..4dd0f6c19 100644 --- a/confirm/confirm.go +++ b/confirm/confirm.go @@ -11,11 +11,10 @@ package confirm import ( - "github.com/charmbracelet/bubbles/help" - "github.com/charmbracelet/bubbles/key" - - tea "github.com/charmbracelet/bubbletea" - "github.com/charmbracelet/lipgloss" + "github.com/charmbracelet/bubbles/v2/help" + "github.com/charmbracelet/bubbles/v2/key" + tea "github.com/charmbracelet/bubbletea/v2" + "github.com/charmbracelet/lipgloss/v2" ) func defaultKeymap(affirmative, negative string) keymap { diff --git a/cursor/cursor.go b/cursor/cursor.go index aa49c0505..7db2bb991 100644 --- a/cursor/cursor.go +++ b/cursor/cursor.go @@ -1,8 +1,6 @@ package cursor -import ( - "github.com/charmbracelet/bubbles/cursor" -) +import "github.com/charmbracelet/bubbles/v2/cursor" // Modes maps strings to cursor modes. var Modes = map[string]cursor.Mode{ diff --git a/file/command.go b/file/command.go index 89472dd9c..8e7571f32 100644 --- a/file/command.go +++ b/file/command.go @@ -6,9 +6,9 @@ import ( "os" "path/filepath" - "github.com/charmbracelet/bubbles/filepicker" - "github.com/charmbracelet/bubbles/help" - tea "github.com/charmbracelet/bubbletea" + "github.com/charmbracelet/bubbles/v2/filepicker" + "github.com/charmbracelet/bubbles/v2/help" + tea "github.com/charmbracelet/bubbletea/v2" "github.com/charmbracelet/gum/internal/timeout" ) @@ -30,7 +30,7 @@ func (o Options) Run() error { fp := filepicker.New() fp.CurrentDirectory = path fp.Path = path - fp.Height = o.Height + fp.SetHeight(o.Height) fp.AutoHeight = o.Height == 0 fp.Cursor = o.Cursor fp.DirAllowed = o.Directory diff --git a/file/file.go b/file/file.go index a9878af71..07b2e887a 100644 --- a/file/file.go +++ b/file/file.go @@ -13,11 +13,11 @@ package file import ( - "github.com/charmbracelet/bubbles/filepicker" - "github.com/charmbracelet/bubbles/help" - "github.com/charmbracelet/bubbles/key" - tea "github.com/charmbracelet/bubbletea" - "github.com/charmbracelet/lipgloss" + "github.com/charmbracelet/bubbles/v2/filepicker" + "github.com/charmbracelet/bubbles/v2/help" + "github.com/charmbracelet/bubbles/v2/key" + tea "github.com/charmbracelet/bubbletea/v2" + "github.com/charmbracelet/lipgloss/v2" ) type keymap filepicker.KeyMap @@ -69,7 +69,7 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { switch msg := msg.(type) { case tea.WindowSizeMsg: if m.showHelp { - m.filepicker.Height -= lipgloss.Height(m.helpView()) + m.filepicker.SetHeight(m.filepicker.Height() - lipgloss.Height(m.helpView())) } case tea.KeyMsg: switch { diff --git a/filter/command.go b/filter/command.go index 41851006d..092a346ba 100644 --- a/filter/command.go +++ b/filter/command.go @@ -7,10 +7,10 @@ import ( "slices" "strings" - "github.com/charmbracelet/bubbles/help" - "github.com/charmbracelet/bubbles/textinput" - "github.com/charmbracelet/bubbles/viewport" - tea "github.com/charmbracelet/bubbletea" + "github.com/charmbracelet/bubbles/v2/help" + "github.com/charmbracelet/bubbles/v2/textinput" + "github.com/charmbracelet/bubbles/v2/viewport" + tea "github.com/charmbracelet/bubbletea/v2" "github.com/charmbracelet/gum/internal/files" "github.com/charmbracelet/gum/internal/stdin" "github.com/charmbracelet/gum/internal/timeout" @@ -26,12 +26,17 @@ func (o Options) Run() error { i.Focus() i.Prompt = o.Prompt - i.PromptStyle = o.PromptStyle.ToLipgloss() - i.PlaceholderStyle = o.PlaceholderStyle.ToLipgloss() + i.Styles.Focused.Prompt = o.PromptStyle.ToLipgloss() + i.Styles.Blurred.Prompt = i.Styles.Focused.Prompt + i.Styles.Focused.Placeholder = o.PlaceholderStyle.ToLipgloss() + i.Styles.Blurred.Placeholder = i.Styles.Focused.Placeholder i.Placeholder = o.Placeholder - i.Width = o.Width + i.SetWidth(o.Width) - v := viewport.New(o.Width, o.Height) + v := viewport.New( + viewport.WithWidth(o.Width), + viewport.WithHeight(o.Height), + ) if len(o.Options) == 0 { if input, _ := stdin.Read(stdin.StripANSI(o.StripANSI)); input != "" { diff --git a/filter/filter.go b/filter/filter.go index d321bcb68..fec985f7f 100644 --- a/filter/filter.go +++ b/filter/filter.go @@ -13,12 +13,12 @@ package filter import ( "strings" - "github.com/charmbracelet/bubbles/help" - "github.com/charmbracelet/bubbles/key" - "github.com/charmbracelet/bubbles/textinput" - "github.com/charmbracelet/bubbles/viewport" - tea "github.com/charmbracelet/bubbletea" - "github.com/charmbracelet/lipgloss" + "github.com/charmbracelet/bubbles/v2/help" + "github.com/charmbracelet/bubbles/v2/key" + "github.com/charmbracelet/bubbles/v2/textinput" + "github.com/charmbracelet/bubbles/v2/viewport" + tea "github.com/charmbracelet/bubbletea/v2" + "github.com/charmbracelet/lipgloss/v2" "github.com/rivo/uniseg" "github.com/sahilm/fuzzy" ) @@ -31,6 +31,18 @@ func defaultKeymap() keymap { Up: key.NewBinding( key.WithKeys("up", "ctrl+k", "ctrl+p"), ), + Left: key.NewBinding( + key.WithKeys("left"), + ), + Right: key.NewBinding( + key.WithKeys("right"), + ), + NLeft: key.NewBinding( + key.WithKeys("h"), + ), + NRight: key.NewBinding( + key.WithKeys("l"), + ), NDown: key.NewBinding( key.WithKeys("j"), ), @@ -93,6 +105,10 @@ type keymap struct { Up, NDown, NUp, + Right, + Left, + NRight, + NLeft, Home, End, ToggleAndNext, @@ -111,8 +127,8 @@ func (k keymap) FullHelp() [][]key.Binding { return nil } func (k keymap) ShortHelp() []key.Binding { return []key.Binding{ key.NewBinding( - key.WithKeys("up", "down"), - key.WithHelp("↓↑", "navigate"), + key.WithKeys("left", "down", "up", "right"), + key.WithHelp("←↓↑→", "navigate"), ), k.FocusInSearch, k.FocusOutSearch, @@ -168,8 +184,8 @@ func (m model) View() string { // For reverse layout, if the number of matches is less than the viewport // height, we need to offset the matches so that the first match is at the // bottom edge of the viewport instead of in the middle. - if m.reverse && len(m.matches) < m.viewport.Height { - s.WriteString(strings.Repeat("\n", m.viewport.Height-len(m.matches))) + if m.reverse && len(m.matches) < m.viewport.Height() { + s.WriteString(strings.Repeat("\n", m.viewport.Height()-len(m.matches))) } // Since there are matches, display them so that the user can see, in real @@ -187,22 +203,11 @@ func (m model) View() string { // The line's text style is set depending on whether or not the cursor // points to this line. if i == m.cursor { - s.WriteString(m.indicatorStyle.Render(m.indicator)) lineTextStyle = m.cursorTextStyle } else { - s.WriteString(strings.Repeat(" ", lipgloss.Width(m.indicator))) lineTextStyle = m.textStyle } - // If there are multiple selections mark them, otherwise leave an empty space - if _, ok := m.selected[match.Str]; ok { - s.WriteString(m.selectedPrefixStyle.Render(m.selectedPrefix)) - } else if m.limit > 1 { - s.WriteString(m.unselectedPrefixStyle.Render(m.unselectedPrefix)) - } else { - s.WriteString(" ") - } - styledOption := m.choices[match.Str] if len(match.MatchedIndexes) == 0 { // No matches, just render the text. @@ -263,25 +268,48 @@ func (m model) helpView() string { return "\n\n" + m.help.View(m.keymap) } +func (m model) gutter(gc viewport.GutterContext) string { + selected := m.selectedPrefixStyle.Render(m.selectedPrefix) + unselected := m.unselectedPrefixStyle.Render(m.unselectedPrefix) + indicator := m.indicatorStyle.Render(m.indicator) + empty := strings.Repeat(" ", lipgloss.Width(indicator)) + + selectGutter := "" + if m.limit > 1 { + selectGutter = unselected + } + if gc.Index < len(m.matches) { + if _, ok := m.selected[m.matches[gc.Index].Str]; ok { + selectGutter = selected + } + } + if gc.Index == m.cursor { + return indicator + selectGutter + } + return empty + selectGutter +} + func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { - var cmd, icmd tea.Cmd + var cmd, icmd, vcmd tea.Cmd m.textinput, icmd = m.textinput.Update(msg) + *m.viewport, vcmd = m.viewport.Update(msg) switch msg := msg.(type) { case tea.WindowSizeMsg: + m.textinput.SetWidth(msg.Width) if m.height == 0 || m.height > msg.Height { - m.viewport.Height = msg.Height - lipgloss.Height(m.textinput.View()) + m.viewport.SetHeight(msg.Height - lipgloss.Height(m.textinput.View())) } // Include the header in the height calculation. if m.header != "" { - m.viewport.Height = m.viewport.Height - lipgloss.Height(m.headerStyle.Render(m.header)) + m.viewport.SetHeight(m.viewport.Height() - lipgloss.Height(m.headerStyle.Render(m.header))) } // Include the help in the total height calculation. if m.showHelp { - m.viewport.Height = m.viewport.Height - lipgloss.Height(m.helpView()) + m.viewport.SetHeight(m.viewport.Height() - lipgloss.Height(m.helpView())) } - m.viewport.Width = msg.Width + m.viewport.SetWidth(msg.Width) if m.reverse { - m.viewport.YOffset = clamp(0, len(m.matches), len(m.matches)-m.viewport.Height) + m.viewport.SetYOffset(clamp(0, len(m.matches), len(m.matches)-m.viewport.Height())) } case tea.KeyMsg: km := m.keymap @@ -343,7 +371,7 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { // in the reverse layout. var yOffsetFromBottom int if m.reverse { - yOffsetFromBottom = max(0, len(m.matches)-m.viewport.YOffset) + yOffsetFromBottom = max(0, len(m.matches)-m.viewport.YOffset()) } // A character was entered, this likely means that the text input has @@ -372,14 +400,20 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { // For reverse layout, we need to offset the viewport so that the // it remains at a constant position relative to the cursor. if m.reverse { - maxYOffset := max(0, len(m.matches)-m.viewport.Height) - m.viewport.YOffset = clamp(0, maxYOffset, len(m.matches)-yOffsetFromBottom) + maxYOffset := max(0, len(m.matches)-m.viewport.Height()) + m.viewport.SetYOffset(clamp(0, maxYOffset, len(m.matches)-yOffsetFromBottom)) } } } m.keymap.FocusInSearch.SetEnabled(!m.textinput.Focused()) m.keymap.FocusOutSearch.SetEnabled(m.textinput.Focused()) + m.viewport.KeyMap.Left.SetEnabled(!m.textinput.Focused()) + m.viewport.KeyMap.Right.SetEnabled(!m.textinput.Focused()) + m.keymap.Left.SetEnabled(!m.textinput.Focused()) + m.keymap.Right.SetEnabled(!m.textinput.Focused()) + m.keymap.NLeft.SetEnabled(!m.textinput.Focused()) + m.keymap.NRight.SetEnabled(!m.textinput.Focused()) m.keymap.NUp.SetEnabled(!m.textinput.Focused()) m.keymap.NDown.SetEnabled(!m.textinput.Focused()) m.keymap.Home.SetEnabled(!m.textinput.Focused()) @@ -388,7 +422,8 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { // It's possible that filtering items have caused fewer matches. So, ensure // that the selected index is within the bounds of the number of matches. m.cursor = clamp(0, len(m.matches)-1, m.cursor) - return m, tea.Batch(cmd, icmd) + m.viewport.LeftGutterFunc = m.gutter + return m, tea.Batch(cmd, icmd, vcmd) } func (m *model) CursorUp() { @@ -397,19 +432,19 @@ func (m *model) CursorUp() { } if m.reverse { //nolint:nestif m.cursor = (m.cursor + 1) % len(m.matches) - if len(m.matches)-m.cursor <= m.viewport.YOffset { - m.viewport.LineUp(1) + if len(m.matches)-m.cursor <= m.viewport.YOffset() { + m.viewport.ScrollUp(1) } - if len(m.matches)-m.cursor > m.viewport.Height+m.viewport.YOffset { - m.viewport.SetYOffset(len(m.matches) - m.viewport.Height) + if len(m.matches)-m.cursor > m.viewport.Height()+m.viewport.YOffset() { + m.viewport.SetYOffset(len(m.matches) - m.viewport.Height()) } } else { m.cursor = (m.cursor - 1 + len(m.matches)) % len(m.matches) - if m.cursor < m.viewport.YOffset { - m.viewport.LineUp(1) + if m.cursor < m.viewport.YOffset() { + m.viewport.ScrollUp(1) } - if m.cursor >= m.viewport.YOffset+m.viewport.Height { - m.viewport.SetYOffset(len(m.matches) - m.viewport.Height) + if m.cursor >= m.viewport.YOffset()+m.viewport.Height() { + m.viewport.SetYOffset(len(m.matches) - m.viewport.Height()) } } } @@ -420,18 +455,18 @@ func (m *model) CursorDown() { } if m.reverse { //nolint:nestif m.cursor = (m.cursor - 1 + len(m.matches)) % len(m.matches) - if len(m.matches)-m.cursor > m.viewport.Height+m.viewport.YOffset { - m.viewport.LineDown(1) + if len(m.matches)-m.cursor > m.viewport.Height()+m.viewport.YOffset() { + m.viewport.ScrollDown(1) } - if len(m.matches)-m.cursor <= m.viewport.YOffset { + if len(m.matches)-m.cursor <= m.viewport.YOffset() { m.viewport.GotoTop() } } else { m.cursor = (m.cursor + 1) % len(m.matches) - if m.cursor >= m.viewport.YOffset+m.viewport.Height { - m.viewport.LineDown(1) + if m.cursor >= m.viewport.YOffset()+m.viewport.Height() { + m.viewport.ScrollDown(1) } - if m.cursor < m.viewport.YOffset { + if m.cursor < m.viewport.YOffset() { m.viewport.GotoTop() } } diff --git a/filter/options.go b/filter/options.go index e63644b95..1790fdf39 100644 --- a/filter/options.go +++ b/filter/options.go @@ -11,17 +11,17 @@ type Options struct { Options []string `arg:"" optional:"" help:"Options to filter."` Indicator string `help:"Character for selection" default:"•" env:"GUM_FILTER_INDICATOR"` - IndicatorStyle style.Styles `embed:"" prefix:"indicator." set:"defaultForeground=212" envprefix:"GUM_FILTER_INDICATOR_"` + IndicatorStyle style.Styles `embed:"" prefix:"indicator." set:"defaultForeground=212" set:"defaultPadding=0 1 0 0" envprefix:"GUM_FILTER_INDICATOR_"` Limit int `help:"Maximum number of options to pick" default:"1" group:"Selection"` NoLimit bool `help:"Pick unlimited number of options (ignores limit)" group:"Selection"` SelectIfOne bool `help:"Select the given option if there is only one" group:"Selection"` Selected []string `help:"Options that should start as selected (selects all if given *)" default:"" env:"GUM_FILTER_SELECTED"` ShowHelp bool `help:"Show help keybinds" default:"true" negatable:"" env:"GUM_FILTER_SHOW_HELP"` Strict bool `help:"Only returns if anything matched. Otherwise return Filter" negatable:"" default:"true" group:"Selection"` - SelectedPrefix string `help:"Character to indicate selected items (hidden if limit is 1)" default:" ◉ " env:"GUM_FILTER_SELECTED_PREFIX"` - SelectedPrefixStyle style.Styles `embed:"" prefix:"selected-indicator." set:"defaultForeground=212" envprefix:"GUM_FILTER_SELECTED_PREFIX_"` - UnselectedPrefix string `help:"Character to indicate unselected items (hidden if limit is 1)" default:" ○ " env:"GUM_FILTER_UNSELECTED_PREFIX"` - UnselectedPrefixStyle style.Styles `embed:"" prefix:"unselected-prefix." set:"defaultForeground=240" envprefix:"GUM_FILTER_UNSELECTED_PREFIX_"` + SelectedPrefix string `help:"Character to indicate selected items (hidden if limit is 1)" default:" ◉" env:"GUM_FILTER_SELECTED_PREFIX"` + SelectedPrefixStyle style.Styles `embed:"" prefix:"selected-indicator." set:"defaultForeground=212" set:"defaultPadding=0 1 0 0" envprefix:"GUM_FILTER_SELECTED_PREFIX_"` + UnselectedPrefix string `help:"Character to indicate unselected items (hidden if limit is 1)" default:" ○" env:"GUM_FILTER_UNSELECTED_PREFIX"` + UnselectedPrefixStyle style.Styles `embed:"" prefix:"unselected-prefix." set:"defaultForeground=240" set:"defaultPadding=0 1 0 0" envprefix:"GUM_FILTER_UNSELECTED_PREFIX_"` HeaderStyle style.Styles `embed:"" prefix:"header." set:"defaultForeground=99" envprefix:"GUM_FILTER_HEADER_"` Header string `help:"Header value" default:"" env:"GUM_FILTER_HEADER"` TextStyle style.Styles `embed:"" prefix:"text." envprefix:"GUM_FILTER_TEXT_"` diff --git a/format/formats.go b/format/formats.go index 9e63e6628..beceef0b2 100644 --- a/format/formats.go +++ b/format/formats.go @@ -5,7 +5,7 @@ import ( "fmt" tpl "text/template" - "github.com/charmbracelet/glamour" + "github.com/charmbracelet/glamour/v2" "github.com/muesli/termenv" ) diff --git a/go.mod b/go.mod index 8e8a1f530..0e8a6d6e0 100644 --- a/go.mod +++ b/go.mod @@ -8,11 +8,11 @@ require ( github.com/Masterminds/semver/v3 v3.3.1 github.com/alecthomas/kong v1.9.0 github.com/alecthomas/mango-kong v0.1.0 - github.com/charmbracelet/bubbles v0.20.0 - github.com/charmbracelet/bubbletea v1.3.4 - github.com/charmbracelet/glamour v0.9.1 - github.com/charmbracelet/lipgloss v1.1.0 - github.com/charmbracelet/log v0.4.1 + github.com/charmbracelet/bubbles/v2 v2.0.0-beta.1 + github.com/charmbracelet/bubbletea/v2 v2.0.0-beta.1.0.20250328000255-bf331300c52e + github.com/charmbracelet/glamour/v2 v2.0.0-20250327175542-c0a9b8dab672 + github.com/charmbracelet/lipgloss/v2 v2.0.0-beta1 + github.com/charmbracelet/log/v2 v2.0.0-20250226163916-c379e29ff706 github.com/charmbracelet/x/ansi v0.8.0 github.com/charmbracelet/x/editor v0.1.0 github.com/charmbracelet/x/term v0.2.1 @@ -29,26 +29,24 @@ require ( 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/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc // indirect - github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd // indirect + github.com/charmbracelet/colorprofile v0.3.0 // indirect + github.com/charmbracelet/x/cellbuf v0.0.14-0.20250326144200-0875329e71da // indirect github.com/charmbracelet/x/conpty v0.1.0 // indirect github.com/charmbracelet/x/errors v0.0.0-20240508181413-e8d8b6e2de86 // indirect + github.com/charmbracelet/x/input v0.3.4 // indirect github.com/charmbracelet/x/termios v0.1.1 // indirect + github.com/charmbracelet/x/windows v0.2.0 // indirect github.com/creack/pty v1.1.24 // indirect github.com/dlclark/regexp2 v1.11.0 // indirect github.com/dustin/go-humanize v1.0.1 // indirect - github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect github.com/go-logfmt/logfmt v0.6.0 // indirect github.com/gorilla/css v1.0.1 // indirect github.com/lucasb-eyer/go-colorful v1.2.0 // indirect github.com/mattn/go-isatty v0.0.20 // indirect - github.com/mattn/go-localereader v0.0.1 // indirect github.com/mattn/go-runewidth v0.0.16 // indirect github.com/microcosm-cc/bluemonday v1.0.27 // 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/muesli/reflow v0.3.0 // indirect github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect github.com/yuin/goldmark v1.7.8 // indirect github.com/yuin/goldmark-emoji v1.0.5 // indirect @@ -56,5 +54,4 @@ require ( golang.org/x/net v0.33.0 // indirect golang.org/x/sync v0.12.0 // indirect golang.org/x/sys v0.31.0 // indirect - golang.org/x/term v0.30.0 // indirect ) diff --git a/go.sum b/go.sum index 84eb0f4d3..dfb70ee65 100644 --- a/go.sum +++ b/go.sum @@ -20,34 +20,38 @@ github.com/aymanbagabas/go-udiff v0.2.0 h1:TK0fH4MteXUDspT88n8CKzvK0X9O2xu9yQjWp github.com/aymanbagabas/go-udiff v0.2.0/go.mod h1:RE4Ex0qsGkTAJoQdQQCA0uG+nAzJO/pI/QwceO5fgrA= 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/charmbracelet/bubbles v0.20.0 h1:jSZu6qD8cRQ6k9OMfR1WlM+ruM8fkPWkHvQWD9LIutE= -github.com/charmbracelet/bubbles v0.20.0/go.mod h1:39slydyswPy+uVOHZ5x/GjwVAFkCsV8IIVy+4MhzwwU= -github.com/charmbracelet/bubbletea v1.3.4 h1:kCg7B+jSCFPLYRA52SDZjr51kG/fMUEoPoZrkaDHyoI= -github.com/charmbracelet/bubbletea v1.3.4/go.mod h1:dtcUCyCGEX3g9tosuYiut3MXgY/Jsv9nKVdibKKRRXo= -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/glamour v0.9.1 h1:11dEfiGP8q1BEqvGoIjivuc2rBk+5qEXdPtaQ2WoiCM= -github.com/charmbracelet/glamour v0.9.1/go.mod h1:+SHvIS8qnwhgTpVMiXwn7OfGomSqff1cHBCI8jLOetk= -github.com/charmbracelet/lipgloss v1.1.0 h1:vYXsiLHVkK7fp74RkV7b2kq9+zDLoEU4MZoFqR/noCY= -github.com/charmbracelet/lipgloss v1.1.0/go.mod h1:/6Q8FR2o+kj8rz4Dq0zQc3vYf7X+B0binUUBwA0aL30= -github.com/charmbracelet/log v0.4.1 h1:6AYnoHKADkghm/vt4neaNEXkxcXLSV2g1rdyFDOpTyk= -github.com/charmbracelet/log v0.4.1/go.mod h1:pXgyTsqsVu4N9hGdHmQ0xEA4RsXof402LX9ZgiITn2I= +github.com/charmbracelet/bubbles/v2 v2.0.0-beta.1 h1:swACzss0FjnyPz1enfX56GKkLiuKg5FlyVmOLIlU2kE= +github.com/charmbracelet/bubbles/v2 v2.0.0-beta.1/go.mod h1:6HamsBKWqEC/FVHuQMHgQL+knPyvHH55HwJDHl/adMw= +github.com/charmbracelet/bubbletea/v2 v2.0.0-beta.1.0.20250328000255-bf331300c52e h1:0kRhGpsFiFVPOx75o8pYUBaCjDrrcGrvtLYh6kKaeRs= +github.com/charmbracelet/bubbletea/v2 v2.0.0-beta.1.0.20250328000255-bf331300c52e/go.mod h1:hYsjyXwOT3RiI2CI4WQYPUg4vg2O1TNTpk8TQTavCPI= +github.com/charmbracelet/colorprofile v0.3.0 h1:KtLh9uuu1RCt+Hml4s6Hz+kB1PfV3wi++1h5ia65yKQ= +github.com/charmbracelet/colorprofile v0.3.0/go.mod h1:oHJ340RS2nmG1zRGPmhJKJ/jf4FPNNk0P39/wBPA1G0= +github.com/charmbracelet/glamour/v2 v2.0.0-20250327175542-c0a9b8dab672 h1:DF8za137pUh5LAXAdXISh0bhS6zU0mHl+cLxyivZjuI= +github.com/charmbracelet/glamour/v2 v2.0.0-20250327175542-c0a9b8dab672/go.mod h1:4iwRPRwCj2jWI3odZnAPqc1nyCXDuBlnfkGavmjl9NI= +github.com/charmbracelet/lipgloss/v2 v2.0.0-beta1 h1:SOylT6+BQzPHEjn15TIzawBPVD0QmhKXbcb3jY0ZIKU= +github.com/charmbracelet/lipgloss/v2 v2.0.0-beta1/go.mod h1:tRlx/Hu0lo/j9viunCN2H+Ze6JrmdjQlXUQvvArgaOc= +github.com/charmbracelet/log/v2 v2.0.0-20250226163916-c379e29ff706 h1:WkwO6Ks3mSIGnGuSdKl9qDSyfbYK50z2wc2gGMggegE= +github.com/charmbracelet/log/v2 v2.0.0-20250226163916-c379e29ff706/go.mod h1:mjJGp00cxcfvD5xdCa+bso251Jt4owrQvuimJtVmEmM= github.com/charmbracelet/x/ansi v0.8.0 h1:9GTq3xq9caJW8ZrBTe0LIe2fvfLR/bYXKTx2llXn7xE= github.com/charmbracelet/x/ansi v0.8.0/go.mod h1:wdYl/ONOLHLIVmQaxbIYEC/cRKOQyjTkowiI4blgS9Q= -github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd h1:vy0GVL4jeHEwG5YOXDmi86oYw2yuYUGqz6a8sLwg0X8= -github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd/go.mod h1:xe0nKWGd3eJgtqZRaN9RjMtK7xUYchjzPr7q6kcvCCs= +github.com/charmbracelet/x/cellbuf v0.0.14-0.20250326144200-0875329e71da h1:8MGKD5WBtuzfXglq0CnyzVSwGojv57X+H46OL9OUyRA= +github.com/charmbracelet/x/cellbuf v0.0.14-0.20250326144200-0875329e71da/go.mod h1:xe0nKWGd3eJgtqZRaN9RjMtK7xUYchjzPr7q6kcvCCs= github.com/charmbracelet/x/conpty v0.1.0 h1:4zc8KaIcbiL4mghEON8D72agYtSeIgq8FSThSPQIb+U= github.com/charmbracelet/x/conpty v0.1.0/go.mod h1:rMFsDJoDwVmiYM10aD4bH2XiRgwI7NYJtQgl5yskjEQ= github.com/charmbracelet/x/editor v0.1.0 h1:p69/dpvlwRTs9uYiPeAWruwsHqTFzHhTvQOd/WVSX98= github.com/charmbracelet/x/editor v0.1.0/go.mod h1:oivrEbcP/AYt/Hpvk5pwDXXrQ933gQS6UzL6fxqAGSA= github.com/charmbracelet/x/errors v0.0.0-20240508181413-e8d8b6e2de86 h1:JSt3B+U9iqk37QUU2Rvb6DSBYRLtWqFqfxf8l5hOZUA= github.com/charmbracelet/x/errors v0.0.0-20240508181413-e8d8b6e2de86/go.mod h1:2P0UgXMEa6TsToMSuFqKFQR+fZTO9CNGUNokkPatT/0= -github.com/charmbracelet/x/exp/golden v0.0.0-20240815200342-61de596daa2b h1:MnAMdlwSltxJyULnrYbkZpp4k58Co7Tah3ciKhSNo0Q= -github.com/charmbracelet/x/exp/golden v0.0.0-20240815200342-61de596daa2b/go.mod h1:wDlXFlCrmJ8J+swcL/MnGUuYnqgQdW9rhSD61oNMb6U= +github.com/charmbracelet/x/exp/golden v0.0.0-20250207160936-21c02780d27a h1:FsHEJ52OC4VuTzU8t+n5frMjLvpYWEznSr/u8tnkCYw= +github.com/charmbracelet/x/exp/golden v0.0.0-20250207160936-21c02780d27a/go.mod h1:wDlXFlCrmJ8J+swcL/MnGUuYnqgQdW9rhSD61oNMb6U= +github.com/charmbracelet/x/input v0.3.4 h1:Mujmnv/4DaitU0p+kIsrlfZl/UlmeLKw1wAP3e1fMN0= +github.com/charmbracelet/x/input v0.3.4/go.mod h1:JI8RcvdZWQIhn09VzeK3hdp4lTz7+yhiEdpEQtZN+2c= github.com/charmbracelet/x/term v0.2.1 h1:AQeHeLZ1OqSXhrAWpYUtZyX1T3zVxfpZuEQMIQaGIAQ= github.com/charmbracelet/x/term v0.2.1/go.mod h1:oQ4enTYFV7QN4m0i9mzHrViD7TQKvNEEkHUMCmsxdUg= github.com/charmbracelet/x/termios v0.1.1 h1:o3Q2bT8eqzGnGPOYheoYS8eEleT5ZVNYNy8JawjaNZY= github.com/charmbracelet/x/termios v0.1.1/go.mod h1:rB7fnv1TgOPOyyKRJ9o+AsTU/vK5WHJ2ivHeut/Pcwo= +github.com/charmbracelet/x/windows v0.2.0 h1:ilXA1GJjTNkgOm94CLPeSz7rar54jtFatdmoiONPuEw= +github.com/charmbracelet/x/windows v0.2.0/go.mod h1:ZibNFR49ZFqCXgP76sYanisxRyC+EYrBE7TTknD8s1s= github.com/charmbracelet/x/xpty v0.1.2 h1:Pqmu4TEJ8KeA9uSkISKMU3f+C1F6OGBn8ABuGlqCbtI= github.com/charmbracelet/x/xpty v0.1.2/go.mod h1:XK2Z0id5rtLWcpeNiMYBccNNBrP2IJnzHI0Lq13Xzq4= github.com/creack/pty v1.1.24 h1:bJrF4RRfyJnbTJqzRLHzcGaZK1NeM5kTC9jGgovnR1s= @@ -58,8 +62,6 @@ github.com/dlclark/regexp2 v1.11.0 h1:G/nrcoOa7ZXlpoa/91N3X7mM3r8eIlMBBJZvsz/mxK 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/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4= -github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM= 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.1 h1:ntNaBIghp6JmvWnxbZKANoLyuXTPZ4cAMlo6RyhlbO8= @@ -72,28 +74,20 @@ github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69 github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4= -github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88= -github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/microcosm-cc/bluemonday v1.0.27 h1:MpEUotklkwCSLeH+Qdx1VJgNqLlpY2KXwXFM08ygZfk= github.com/microcosm-cc/bluemonday v1.0.27/go.mod h1:jFi9vgW+H7c3V0lb6nR74Ib/DIB5OBs92Dimizgw2cA= -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= github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo= github.com/muesli/mango v0.2.0 h1:iNNc0c5VLQ6fsMgAqGQofByNUBH2Q2nEbD6TaI+5yyQ= github.com/muesli/mango v0.2.0/go.mod h1:5XFpbC8jY5UUv89YQciiXNlbi+iJgt29VDC5xbzrLL4= -github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s= -github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8= github.com/muesli/roff v0.1.0 h1:YD0lalCotmYuF5HhZliKWlIx7IEhiXeSfq7hNjFqGF8= github.com/muesli/roff v0.1.0/go.mod h1:pjAHQM9hdUUwm/krAfrLGgJkXJ+YuhtsfZ42kieB2Ig= github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc= github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= @@ -114,12 +108,9 @@ golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw= golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= -golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= -golang.org/x/term v0.30.0 h1:PQ39fJZ+mfadBm0y5WlL4vlM7Sx1Hgf13sMIY2+QS9Y= -golang.org/x/term v0.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g= golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/input/command.go b/input/command.go index 7d96e72b4..271d9efbc 100644 --- a/input/command.go +++ b/input/command.go @@ -5,10 +5,9 @@ import ( "fmt" "os" - "github.com/charmbracelet/bubbles/help" - "github.com/charmbracelet/bubbles/textinput" - tea "github.com/charmbracelet/bubbletea" - "github.com/charmbracelet/gum/cursor" + "github.com/charmbracelet/bubbles/v2/help" + "github.com/charmbracelet/bubbles/v2/textinput" + tea "github.com/charmbracelet/bubbletea/v2" "github.com/charmbracelet/gum/internal/stdin" "github.com/charmbracelet/gum/internal/timeout" ) @@ -31,11 +30,15 @@ func (o Options) Run() error { i.Focus() i.Prompt = o.Prompt i.Placeholder = o.Placeholder - i.Width = o.Width - i.PromptStyle = o.PromptStyle.ToLipgloss() - i.PlaceholderStyle = o.PlaceholderStyle.ToLipgloss() - i.Cursor.Style = o.CursorStyle.ToLipgloss() - i.Cursor.SetMode(cursor.Modes[o.CursorMode]) + i.SetWidth(o.Width) + i.Styles.Focused.Prompt = o.PromptStyle.ToLipgloss() + i.Styles.Blurred.Prompt = i.Styles.Focused.Prompt + i.Styles.Focused.Placeholder = o.PlaceholderStyle.ToLipgloss() + i.Styles.Blurred.Placeholder = i.Styles.Focused.Placeholder + i.Styles.Cursor.Color = o.CursorStyle.ToLipgloss().GetForeground() + i.Styles.Cursor.Blink = o.CursorMode == "blink" + // XXX: set hidden + // i.Cursor.SetMode(cursor.Modes[o.CursorMode]) i.CharLimit = o.CharLimit if o.Password { diff --git a/input/input.go b/input/input.go index 505b05205..f3c18caa4 100644 --- a/input/input.go +++ b/input/input.go @@ -8,17 +8,17 @@ package input import ( - "github.com/charmbracelet/bubbles/help" - "github.com/charmbracelet/bubbles/key" - "github.com/charmbracelet/bubbles/textinput" - tea "github.com/charmbracelet/bubbletea" - "github.com/charmbracelet/lipgloss" + "github.com/charmbracelet/bubbles/v2/help" + "github.com/charmbracelet/bubbles/v2/key" + "github.com/charmbracelet/bubbles/v2/textinput" + tea "github.com/charmbracelet/bubbletea/v2" + "github.com/charmbracelet/lipgloss/v2" ) type keymap textinput.KeyMap func defaultKeymap() keymap { - k := textinput.DefaultKeyMap + k := textinput.DefaultKeyMap() return keymap(k) } @@ -73,7 +73,7 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { switch msg := msg.(type) { case tea.WindowSizeMsg: if m.autoWidth { - m.textinput.Width = msg.Width - lipgloss.Width(m.textinput.Prompt) - 1 + m.textinput.SetWidth(msg.Width - lipgloss.Width(m.textinput.Prompt) - 1) } case tea.KeyMsg: switch msg.String() { diff --git a/internal/decode/align.go b/internal/decode/align.go index 813bcdd46..f3b1d6c2d 100644 --- a/internal/decode/align.go +++ b/internal/decode/align.go @@ -1,6 +1,6 @@ package decode -import "github.com/charmbracelet/lipgloss" +import "github.com/charmbracelet/lipgloss/v2" // Align maps strings to `lipgloss.Position`s. var Align = map[string]lipgloss.Position{ diff --git a/internal/utils/utils.go b/internal/utils/utils.go index 0e385986c..385e2dd7a 100644 --- a/internal/utils/utils.go +++ b/internal/utils/utils.go @@ -3,7 +3,7 @@ package utils import ( "strings" - "github.com/charmbracelet/lipgloss" + "github.com/charmbracelet/lipgloss/v2" ) // LipglossPadding calculates how much padding a string is given by a style. diff --git a/join/command.go b/join/command.go index 9563ec795..8b39ded97 100644 --- a/join/command.go +++ b/join/command.go @@ -18,7 +18,7 @@ package join import ( "fmt" - "github.com/charmbracelet/lipgloss" + "github.com/charmbracelet/lipgloss/v2" "github.com/charmbracelet/gum/internal/decode" ) diff --git a/log/command.go b/log/command.go index aff18cfbe..ac9f5caa0 100644 --- a/log/command.go +++ b/log/command.go @@ -7,8 +7,8 @@ import ( "strings" "time" - "github.com/charmbracelet/lipgloss" - "github.com/charmbracelet/log" + "github.com/charmbracelet/lipgloss/v2" + "github.com/charmbracelet/log/v2" ) // Run is the command-line interface for logging text. diff --git a/main.go b/main.go index 18fa9f087..af33045ee 100644 --- a/main.go +++ b/main.go @@ -7,10 +7,10 @@ import ( "runtime/debug" "github.com/alecthomas/kong" - tea "github.com/charmbracelet/bubbletea" + tea "github.com/charmbracelet/bubbletea/v2" "github.com/charmbracelet/gum/internal/exit" - "github.com/charmbracelet/lipgloss" - "github.com/muesli/termenv" + "github.com/charmbracelet/lipgloss/v2" + "github.com/charmbracelet/lipgloss/v2/compat" ) const shaLen = 7 @@ -28,7 +28,7 @@ var ( var bubbleGumPink = lipgloss.NewStyle().Foreground(lipgloss.Color("212")) func main() { - lipgloss.SetColorProfile(termenv.NewOutput(os.Stderr).Profile) + compat.HasDarkBackground = lipgloss.HasDarkBackground(os.Stdin, os.Stderr) if Version == "" { if info, ok := debug.ReadBuildInfo(); ok && info.Main.Sum != "" { diff --git a/pager/command.go b/pager/command.go index 414bee74d..9a451a86a 100644 --- a/pager/command.go +++ b/pager/command.go @@ -2,11 +2,10 @@ package pager import ( "fmt" - "regexp" - "github.com/charmbracelet/bubbles/help" - "github.com/charmbracelet/bubbles/viewport" - tea "github.com/charmbracelet/bubbletea" + "github.com/charmbracelet/bubbles/v2/help" + "github.com/charmbracelet/bubbles/v2/viewport" + tea "github.com/charmbracelet/bubbletea/v2" "github.com/charmbracelet/gum/internal/stdin" "github.com/charmbracelet/gum/internal/timeout" ) @@ -14,34 +13,50 @@ import ( // Run provides a shell script interface for the viewport bubble. // https://github.com/charmbracelet/bubbles/viewport func (o Options) Run() error { - vp := viewport.New(o.Style.Width, o.Style.Height) + vp := viewport.New( + viewport.WithWidth(o.Style.Width), + viewport.WithHeight(o.Style.Height), + ) vp.Style = o.Style.ToLipgloss() if o.Content == "" { - stdin, err := stdin.Read() + stdin, err := stdin.Read(stdin.StripANSI(true)) if err != nil { return fmt.Errorf("unable to read stdin") } if stdin != "" { - // Sanitize the input from stdin by removing backspace sequences. - backspace := regexp.MustCompile(".\x08") - o.Content = backspace.ReplaceAllString(stdin, "") + o.Content = stdin } else { return fmt.Errorf("provide some content to display") } } + if o.ShowLineNumbers { + vp.LeftGutterFunc = func(info viewport.GutterContext) string { + style := o.LineNumberStyle.ToLipgloss() + if info.Soft { + return style.Render(" │ ") + } + if info.Index >= info.TotalLines { + return style.Render(" ~ │ ") + } + // TODO: handle more lines + return style.Render(fmt.Sprintf("%4d │ ", info.Index+1)) + } + } + + vp.SoftWrap = o.SoftWrap + vp.FillHeight = o.ShowLineNumbers + vp.SetContent(o.Content) + vp.HighlightStyle = o.MatchStyle.ToLipgloss() + vp.SelectedHighlightStyle = o.MatchHighlightStyle.ToLipgloss() + m := model{ - viewport: vp, - help: help.New(), - content: o.Content, - origContent: o.Content, - showLineNumbers: o.ShowLineNumbers, - lineNumberStyle: o.LineNumberStyle.ToLipgloss(), - softWrap: o.SoftWrap, - matchStyle: o.MatchStyle.ToLipgloss(), - matchHighlightStyle: o.MatchHighlightStyle.ToLipgloss(), - keymap: defaultKeymap(), + viewport: vp, + help: help.New(), + showLineNumbers: o.ShowLineNumbers, + lineNumberStyle: o.LineNumberStyle.ToLipgloss(), + keymap: defaultKeymap(), } ctx, cancel := timeout.Context(o.Timeout) @@ -51,6 +66,7 @@ func (o Options) Run() error { m, tea.WithAltScreen(), tea.WithReportFocus(), + tea.WithMouseAllMotion(), tea.WithContext(ctx), ).Run() if err != nil { diff --git a/pager/options.go b/pager/options.go index a44fca78f..ab9422c1e 100644 --- a/pager/options.go +++ b/pager/options.go @@ -11,7 +11,7 @@ type Options struct { //nolint:staticcheck Style style.Styles `embed:"" help:"Style the pager" set:"defaultBorder=rounded" set:"defaultPadding=0 1" set:"defaultBorderForeground=212" envprefix:"GUM_PAGER_"` Content string `arg:"" optional:"" help:"Display content to scroll"` - ShowLineNumbers bool `help:"Show line numbers" default:"true"` + ShowLineNumbers bool `help:"Show line numbers" default:"true" negatable:""` LineNumberStyle style.Styles `embed:"" prefix:"line-number." help:"Style the line numbers" set:"defaultForeground=237" envprefix:"GUM_PAGER_LINE_NUMBER_"` SoftWrap bool `help:"Soft wrap lines" default:"true" negatable:""` MatchStyle style.Styles `embed:"" prefix:"match." help:"Style the matched text" set:"defaultForeground=212" set:"defaultBold=true" envprefix:"GUM_PAGER_MATCH_"` //nolint:staticcheck diff --git a/pager/pager.go b/pager/pager.go index 6b384035c..3c448a057 100644 --- a/pager/pager.go +++ b/pager/pager.go @@ -4,16 +4,12 @@ package pager import ( - "fmt" - "strings" - - "github.com/charmbracelet/bubbles/help" - "github.com/charmbracelet/bubbles/key" - "github.com/charmbracelet/bubbles/textinput" - "github.com/charmbracelet/bubbles/viewport" - tea "github.com/charmbracelet/bubbletea" - "github.com/charmbracelet/lipgloss" - "github.com/charmbracelet/x/ansi" + "github.com/charmbracelet/bubbles/v2/help" + "github.com/charmbracelet/bubbles/v2/key" + "github.com/charmbracelet/bubbles/v2/textinput" + "github.com/charmbracelet/bubbles/v2/viewport" + tea "github.com/charmbracelet/bubbletea/v2" + "github.com/charmbracelet/lipgloss/v2" ) type keymap struct { @@ -37,8 +33,8 @@ func (k keymap) FullHelp() [][]key.Binding { func (k keymap) ShortHelp() []key.Binding { return []key.Binding{ key.NewBinding( - key.WithKeys("up", "down"), - key.WithHelp("↓↑", "navigate"), + key.WithKeys("left", "down", "up", "right"), + key.WithHelp("←↓↑→", "navigate"), ), k.Quit, k.Search, @@ -89,18 +85,13 @@ func defaultKeymap() keymap { } type model struct { - content string - origContent string - viewport viewport.Model - help help.Model - showLineNumbers bool - lineNumberStyle lipgloss.Style - softWrap bool - search search - matchStyle lipgloss.Style - matchHighlightStyle lipgloss.Style - maxWidth int - keymap keymap + viewport viewport.Model + help help.Model + showLineNumbers bool + lineNumberStyle lipgloss.Style + search search + maxWidth int + keymap keymap } func (m model) Init() tea.Cmd { return nil } @@ -109,16 +100,21 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { switch msg := msg.(type) { case tea.WindowSizeMsg: m.processText(msg) + m.search.input.SetWidth(msg.Width) case tea.KeyMsg: return m.keyHandler(msg) } - m.keymap.PrevMatch.SetEnabled(m.search.query != nil) - m.keymap.NextMatch.SetEnabled(m.search.query != nil) + m.keymap.PrevMatch.SetEnabled(m.search.navigating) + m.keymap.NextMatch.SetEnabled(m.search.navigating) var cmd tea.Cmd + var cmds []tea.Cmd + m.viewport, cmd = m.viewport.Update(msg) + cmds = append(cmds, cmd) m.search.input, cmd = m.search.input.Update(msg) - return m, cmd + cmds = append(cmds, cmd) + return m, tea.Batch(cmds...) } func (m *model) helpView() string { @@ -126,71 +122,28 @@ func (m *model) helpView() string { } func (m *model) processText(msg tea.WindowSizeMsg) { - m.viewport.Height = msg.Height - lipgloss.Height(m.helpView()) - m.viewport.Width = msg.Width - textStyle := lipgloss.NewStyle().Width(m.viewport.Width) - var text strings.Builder + m.viewport.SetHeight(msg.Height - lipgloss.Height(m.helpView())) + m.viewport.SetWidth(msg.Width) // Determine max width of a line. - m.maxWidth = m.viewport.Width - if m.softWrap { - vpStyle := m.viewport.Style - m.maxWidth -= vpStyle.GetHorizontalBorderSize() + vpStyle.GetHorizontalMargins() + vpStyle.GetHorizontalPadding() - if m.showLineNumbers { - m.maxWidth -= lipgloss.Width(" │ ") - } - } - - for i, line := range strings.Split(m.content, "\n") { - line = strings.ReplaceAll(line, "\t", " ") - if m.showLineNumbers { - text.WriteString(m.lineNumberStyle.Render(fmt.Sprintf("%4d │ ", i+1))) - } - idx := 0 - if w := ansi.StringWidth(line); m.softWrap && w > m.maxWidth { - for w > idx { - if m.showLineNumbers && idx != 0 { - text.WriteString(m.lineNumberStyle.Render(" │ ")) - } - truncatedLine := ansi.Cut(line, idx, m.maxWidth+idx) - idx += m.maxWidth - text.WriteString(textStyle.Render(truncatedLine)) - text.WriteString("\n") - } - } else { - text.WriteString(textStyle.Render(line)) //nolint: gosec - text.WriteString("\n") - } - } - - diffHeight := m.viewport.Height - lipgloss.Height(text.String()) - if diffHeight > 0 && m.showLineNumbers { - remainingLines := " ~ │ " + strings.Repeat("\n ~ │ ", diffHeight-1) - text.WriteString(m.lineNumberStyle.Render(remainingLines)) - } - m.viewport.SetContent(text.String()) + m.maxWidth = m.viewport.Width() } -const heightOffset = 2 - func (m model) keyHandler(msg tea.KeyMsg) (model, tea.Cmd) { km := m.keymap var cmd tea.Cmd - if m.search.active { + if m.search.visible { switch { case key.Matches(msg, km.ConfirmSearch): if m.search.input.Value() != "" { - m.content = m.origContent - m.search.Execute(&m) - - // Trigger a view update to highlight the found matches. - m.search.NextMatch(&m) - m.processText(tea.WindowSizeMsg{Height: m.viewport.Height + heightOffset, Width: m.viewport.Width}) + m.viewport.SetHighlights(m.search.Execute(m.viewport.GetContent())) } else { m.search.Done() + m.viewport.ClearHighlights() } case key.Matches(msg, km.CancelSearch): m.search.Done() + m.viewport.ClearHighlights() default: m.search.input, cmd = m.search.input.Update(msg) } @@ -201,14 +154,12 @@ func (m model) keyHandler(msg tea.KeyMsg) (model, tea.Cmd) { case key.Matches(msg, km.End): m.viewport.GotoBottom() case key.Matches(msg, km.Search): - m.search.Begin() + m.search.Show(m.viewport.Width()) return m, textinput.Blink case key.Matches(msg, km.PrevMatch): - m.search.PrevMatch(&m) - m.processText(tea.WindowSizeMsg{Height: m.viewport.Height + heightOffset, Width: m.viewport.Width}) + m.viewport.HighlightPrevious() case key.Matches(msg, km.NextMatch): - m.search.NextMatch(&m) - m.processText(tea.WindowSizeMsg{Height: m.viewport.Height + heightOffset, Width: m.viewport.Width}) + m.viewport.HighlightNext() case key.Matches(msg, km.Quit): return m, tea.Quit case key.Matches(msg, km.Abort): @@ -221,7 +172,7 @@ func (m model) keyHandler(msg tea.KeyMsg) (model, tea.Cmd) { } func (m model) View() string { - if m.search.active { + if m.search.visible { return m.viewport.View() + "\n " + m.search.input.View() } diff --git a/pager/search.go b/pager/search.go index be59a534e..df18b501d 100644 --- a/pager/search.go +++ b/pager/search.go @@ -1,168 +1,52 @@ package pager import ( - "fmt" "regexp" - "strings" - "github.com/charmbracelet/bubbles/textinput" - "github.com/charmbracelet/gum/internal/utils" - "github.com/charmbracelet/lipgloss" - "github.com/charmbracelet/x/ansi" + "github.com/charmbracelet/bubbles/v2/textinput" + "github.com/charmbracelet/lipgloss/v2" ) type search struct { - active bool - input textinput.Model - query *regexp.Regexp - matchIndex int - matchLipglossStr string - matchString string + visible bool + navigating bool + input textinput.Model } func (s *search) new() { input := textinput.New() input.Placeholder = "search" input.Prompt = "/" - input.PromptStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("240")) + input.Styles.Focused.Prompt = lipgloss.NewStyle().Foreground(lipgloss.Color("240")) + input.Styles.Blurred.Prompt = input.Styles.Focused.Prompt s.input = input } -func (s *search) Begin() { +func (s *search) Show(w int) { s.new() - s.active = true + s.visible = true + s.input.SetWidth(w) s.input.Focus() } // Execute find all lines in the model with a match. -func (s *search) Execute(m *model) { - defer s.Done() +func (s *search) Execute(content string) [][]int { if s.input.Value() == "" { - s.query = nil - return + s.navigating = false + s.visible = false + return nil } - var err error - s.query, err = regexp.Compile(s.input.Value()) + s.navigating = true + s.visible = false + query, err := regexp.Compile(s.input.Value()) if err != nil { - s.query = nil - return - } - query := regexp.MustCompile(fmt.Sprintf("(%s)", s.query.String())) - m.content = query.ReplaceAllString(m.content, m.matchStyle.Render("$1")) - - // Recompile the regex to match the an replace the highlights. - leftPad, _ := utils.LipglossPadding(m.matchStyle) - matchingString := regexp.QuoteMeta(m.matchStyle.Render()[:leftPad]) + s.query.String() + regexp.QuoteMeta(m.matchStyle.Render()[leftPad:]) - s.query, err = regexp.Compile(matchingString) - if err != nil { - s.query = nil + return nil } + return query.FindAllStringIndex(content, -1) } func (s *search) Done() { - s.active = false - - // To account for the first match is always executed. - s.matchIndex = -1 -} - -func (s *search) NextMatch(m *model) { - // Check that we are within bounds. - if s.query == nil { - return - } - - // Remove previous highlight. - m.content = strings.Replace(m.content, s.matchLipglossStr, s.matchString, 1) - - // Highlight the next match. - allMatches := s.query.FindAllStringIndex(m.content, -1) - if len(allMatches) == 0 { - return - } - - leftPad, rightPad := utils.LipglossPadding(m.matchStyle) - s.matchIndex = (s.matchIndex + 1) % len(allMatches) - match := allMatches[s.matchIndex] - lhs := m.content[:match[0]] - rhs := m.content[match[0]:] - s.matchString = m.content[match[0]:match[1]] - s.matchLipglossStr = m.matchHighlightStyle.Render(s.matchString[leftPad : len(s.matchString)-rightPad]) - m.content = lhs + strings.Replace(rhs, m.content[match[0]:match[1]], s.matchLipglossStr, 1) - - // Update the viewport position. - var line int - formatStr := softWrapEm(m.content, m.maxWidth, m.softWrap) - index := strings.Index(formatStr, s.matchLipglossStr) - if index != -1 { - line = strings.Count(formatStr[:index], "\n") - } - - // Only update if the match is not within the viewport. - if index != -1 && (line > m.viewport.YOffset-1+m.viewport.VisibleLineCount()-1 || line < m.viewport.YOffset) { - m.viewport.SetYOffset(line) - } -} - -func (s *search) PrevMatch(m *model) { - // Check that we are within bounds. - if s.query == nil { - return - } - - // Remove previous highlight. - m.content = strings.Replace(m.content, s.matchLipglossStr, s.matchString, 1) - - // Highlight the previous match. - allMatches := s.query.FindAllStringIndex(m.content, -1) - if len(allMatches) == 0 { - return - } - - s.matchIndex = (s.matchIndex - 1) % len(allMatches) - if s.matchIndex < 0 { - s.matchIndex = len(allMatches) - 1 - } - - leftPad, rightPad := utils.LipglossPadding(m.matchStyle) - match := allMatches[s.matchIndex] - lhs := m.content[:match[0]] - rhs := m.content[match[0]:] - s.matchString = m.content[match[0]:match[1]] - s.matchLipglossStr = m.matchHighlightStyle.Render(s.matchString[leftPad : len(s.matchString)-rightPad]) - m.content = lhs + strings.Replace(rhs, m.content[match[0]:match[1]], s.matchLipglossStr, 1) - - // Update the viewport position. - var line int - formatStr := softWrapEm(m.content, m.maxWidth, m.softWrap) - index := strings.Index(formatStr, s.matchLipglossStr) - if index != -1 { - line = strings.Count(formatStr[:index], "\n") - } - - // Only update if the match is not within the viewport. - if index != -1 && (line > m.viewport.YOffset-1+m.viewport.VisibleLineCount()-1 || line < m.viewport.YOffset) { - m.viewport.SetYOffset(line) - } -} - -func softWrapEm(str string, maxWidth int, softWrap bool) string { - var text strings.Builder - for _, line := range strings.Split(str, "\n") { - idx := 0 - if w := ansi.StringWidth(line); softWrap && w > maxWidth { - for w > idx { - truncatedLine := ansi.Cut(line, idx, maxWidth+idx) - idx += maxWidth - text.WriteString(truncatedLine) - text.WriteString("\n") - } - } else { - text.WriteString(line) //nolint: gosec - text.WriteString("\n") - } - } - - return text.String() + s.visible = false + s.navigating = false } diff --git a/spin/command.go b/spin/command.go index 08cb3bda8..ba33c229d 100644 --- a/spin/command.go +++ b/spin/command.go @@ -4,8 +4,8 @@ import ( "fmt" "os" - "github.com/charmbracelet/bubbles/spinner" - tea "github.com/charmbracelet/bubbletea" + "github.com/charmbracelet/bubbles/v2/spinner" + tea "github.com/charmbracelet/bubbletea/v2" "github.com/charmbracelet/gum/internal/exit" "github.com/charmbracelet/gum/internal/timeout" "github.com/charmbracelet/x/term" diff --git a/spin/spin.go b/spin/spin.go index ee7ea7d32..4f7356091 100644 --- a/spin/spin.go +++ b/spin/spin.go @@ -23,8 +23,8 @@ import ( "runtime" "syscall" - "github.com/charmbracelet/bubbles/spinner" - tea "github.com/charmbracelet/bubbletea" + "github.com/charmbracelet/bubbles/v2/spinner" + tea "github.com/charmbracelet/bubbletea/v2" "github.com/charmbracelet/x/term" "github.com/charmbracelet/x/xpty" ) diff --git a/spin/spinners.go b/spin/spinners.go index a8e235e74..6ce101b63 100644 --- a/spin/spinners.go +++ b/spin/spinners.go @@ -1,6 +1,6 @@ package spin -import "github.com/charmbracelet/bubbles/spinner" +import "github.com/charmbracelet/bubbles/v2/spinner" var spinnerMap = map[string]spinner.Spinner{ "line": spinner.Line, diff --git a/style/borders.go b/style/borders.go index b55c5cade..9b2f7968b 100644 --- a/style/borders.go +++ b/style/borders.go @@ -1,6 +1,6 @@ package style -import "github.com/charmbracelet/lipgloss" +import "github.com/charmbracelet/lipgloss/v2" // Border maps strings to `lipgloss.Border`s. var Border map[string]lipgloss.Border = map[string]lipgloss.Border{ diff --git a/style/lipgloss.go b/style/lipgloss.go index 9ba52a2f6..1666bbe75 100644 --- a/style/lipgloss.go +++ b/style/lipgloss.go @@ -1,7 +1,7 @@ package style import ( - "github.com/charmbracelet/lipgloss" + "github.com/charmbracelet/lipgloss/v2" "github.com/charmbracelet/gum/internal/decode" ) diff --git a/table/command.go b/table/command.go index 4ee0fb99d..f4490b955 100644 --- a/table/command.go +++ b/table/command.go @@ -5,14 +5,14 @@ import ( "fmt" "os" - "github.com/charmbracelet/bubbles/help" - "github.com/charmbracelet/bubbles/table" - tea "github.com/charmbracelet/bubbletea" + "github.com/charmbracelet/bubbles/v2/help" + "github.com/charmbracelet/bubbles/v2/table" + tea "github.com/charmbracelet/bubbletea/v2" "github.com/charmbracelet/gum/internal/stdin" "github.com/charmbracelet/gum/internal/timeout" "github.com/charmbracelet/gum/style" - "github.com/charmbracelet/lipgloss" - ltable "github.com/charmbracelet/lipgloss/table" + "github.com/charmbracelet/lipgloss/v2" + ltable "github.com/charmbracelet/lipgloss/v2/table" "golang.org/x/text/encoding" "golang.org/x/text/encoding/unicode" "golang.org/x/text/transform" diff --git a/table/table.go b/table/table.go index 9c6ed72b2..c50c7bfc9 100644 --- a/table/table.go +++ b/table/table.go @@ -18,10 +18,10 @@ import ( "fmt" "strconv" - "github.com/charmbracelet/bubbles/help" - "github.com/charmbracelet/bubbles/key" - "github.com/charmbracelet/bubbles/table" - tea "github.com/charmbracelet/bubbletea" + "github.com/charmbracelet/bubbles/v2/help" + "github.com/charmbracelet/bubbles/v2/key" + "github.com/charmbracelet/bubbles/v2/table" + tea "github.com/charmbracelet/bubbletea/v2" ) type keymap struct { diff --git a/write/command.go b/write/command.go index adc3c69db..3cb7b77e9 100644 --- a/write/command.go +++ b/write/command.go @@ -6,10 +6,9 @@ import ( "os" "strings" - "github.com/charmbracelet/bubbles/help" - "github.com/charmbracelet/bubbles/textarea" - tea "github.com/charmbracelet/bubbletea" - "github.com/charmbracelet/gum/cursor" + "github.com/charmbracelet/bubbles/v2/help" + "github.com/charmbracelet/bubbles/v2/textarea" + tea "github.com/charmbracelet/bubbletea/v2" "github.com/charmbracelet/gum/internal/stdin" "github.com/charmbracelet/gum/internal/timeout" ) @@ -31,7 +30,7 @@ func (o Options) Run() error { a.CharLimit = o.CharLimit a.MaxHeight = o.MaxLines - style := textarea.Style{ + style := textarea.StyleState{ Base: o.BaseStyle.ToLipgloss(), Placeholder: o.PlaceholderStyle.ToLipgloss(), CursorLine: o.CursorLineStyle.ToLipgloss(), @@ -41,10 +40,12 @@ func (o Options) Run() error { Prompt: o.PromptStyle.ToLipgloss(), } - a.BlurredStyle = style - a.FocusedStyle = style - a.Cursor.Style = o.CursorStyle.ToLipgloss() - a.Cursor.SetMode(cursor.Modes[o.CursorMode]) + a.Styles.Focused = style + a.Styles.Blurred = style + a.Styles.Cursor.Color = o.CursorStyle.ToLipgloss().GetForeground() + a.Styles.Cursor.Blink = o.CursorMode == "blink" + // TODO: handle cursor hidden + // a.Cursor.SetMode(cursor.Modes[o.CursorMode]) a.SetWidth(o.Width) a.SetHeight(o.Height) diff --git a/write/write.go b/write/write.go index 323dc912d..4b88996f2 100644 --- a/write/write.go +++ b/write/write.go @@ -12,11 +12,11 @@ import ( "io" "os" - "github.com/charmbracelet/bubbles/help" - "github.com/charmbracelet/bubbles/key" - "github.com/charmbracelet/bubbles/textarea" - tea "github.com/charmbracelet/bubbletea" - "github.com/charmbracelet/lipgloss" + "github.com/charmbracelet/bubbles/v2/help" + "github.com/charmbracelet/bubbles/v2/key" + "github.com/charmbracelet/bubbles/v2/textarea" + tea "github.com/charmbracelet/bubbletea/v2" + "github.com/charmbracelet/lipgloss/v2" "github.com/charmbracelet/x/editor" ) @@ -41,7 +41,7 @@ func (k keymap) ShortHelp() []key.Binding { } func defaultKeymap() keymap { - km := textarea.DefaultKeyMap + km := textarea.DefaultKeyMap() km.InsertNewline = key.NewBinding( key.WithKeys("ctrl+j"), key.WithHelp("ctrl+j", "insert newline"),