diff --git a/.github/workflows/lint-soft.yml b/.github/workflows/lint-soft.yml index cd79f53..2eb2526 100644 --- a/.github/workflows/lint-soft.yml +++ b/.github/workflows/lint-soft.yml @@ -20,7 +20,7 @@ jobs: - uses: actions/checkout@v4 - name: golangci-lint - uses: golangci/golangci-lint-action@v5 + uses: golangci/golangci-lint-action@v6 with: # Optional: golangci-lint command line arguments. args: --config .golangci-soft.yml --issues-exit-code=0 diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 7e45846..86612f0 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -20,7 +20,7 @@ jobs: - uses: actions/checkout@v4 - name: golangci-lint - uses: golangci/golangci-lint-action@v5 + uses: golangci/golangci-lint-action@v6 with: # Optional: golangci-lint command line arguments. #args: diff --git a/.golangci.yml b/.golangci.yml index a5a91d0..3affce9 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -16,6 +16,7 @@ linters: enable: - bodyclose - exportloopref + - gofumpt - goimports - gosec - nilerr diff --git a/go.mod b/go.mod index a9f1701..0964f83 100644 --- a/go.mod +++ b/go.mod @@ -4,15 +4,14 @@ go 1.18 require ( github.com/MakeNowJust/heredoc v1.0.0 - github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d github.com/atotto/clipboard v0.1.4 - github.com/charmbracelet/bubbletea v0.26.3-0.20240528172931-7581b6a46c83 + github.com/charmbracelet/bubbletea v0.26.5-0.20240605151746-bca513b1982b github.com/charmbracelet/harmonica v0.2.0 - github.com/charmbracelet/lipgloss v0.10.1-0.20240528172826-972c013da128 + github.com/charmbracelet/lipgloss v0.11.1-0.20240605151253-a724723338e3 + github.com/charmbracelet/x/ansi v0.1.1 github.com/dustin/go-humanize v1.0.1 github.com/lucasb-eyer/go-colorful v1.2.0 github.com/mattn/go-runewidth v0.0.15 - github.com/muesli/reflow v0.3.0 github.com/muesli/termenv v0.15.2 github.com/rivo/uniseg v0.4.7 github.com/sahilm/fuzzy v0.1.1 @@ -20,13 +19,12 @@ require ( require ( github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect - github.com/charmbracelet/x/ansi v0.1.1 // indirect github.com/charmbracelet/x/input v0.1.0 // indirect github.com/charmbracelet/x/term v0.1.1 // indirect github.com/charmbracelet/x/windows v0.1.0 // indirect github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect github.com/kylelemons/godebug v1.1.0 // indirect - github.com/mattn/go-isatty v0.0.18 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect github.com/muesli/cancelreader v0.2.2 // indirect github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect diff --git a/go.sum b/go.sum index f88285e..1046943 100644 --- a/go.sum +++ b/go.sum @@ -1,45 +1,15 @@ github.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ4pzQ= github.com/MakeNowJust/heredoc v1.0.0/go.mod h1:mG5amYoWBHf8vpLOuehzbGGw0EHxpZZ6lCpQ4fNJ8LE= -github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d h1:licZJFw2RwpHMqeKTCYkitsPqHNxTmd4SNR5r94FGM8= -github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d/go.mod h1:asat636LX7Bqt5lYEZ27JNDcqxfjdBQuJ/MM4CN/Lzo= github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4= 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/charmbracelet/bubbletea v0.26.3-0.20240510203954-a434f25ff7d3 h1:Bg0eRBAgsWh84rpAq5X+e52TMAfloxoWeSJkYp9Pyw0= -github.com/charmbracelet/bubbletea v0.26.3-0.20240510203954-a434f25ff7d3/go.mod h1:jxN9TKmttdNsUKWSjjQQonHIY4nCuTBNdkDn3/Bpf7A= -github.com/charmbracelet/bubbletea v0.26.3-0.20240513205551-cf6db9e1506a h1:9p5KZLIfqlGIvR0VvCslSTQHMePwKJ7mRESH2FqvWR4= -github.com/charmbracelet/bubbletea v0.26.3-0.20240513205551-cf6db9e1506a/go.mod h1:K3huPnfaiCxjWojAg1QijEVeqHYY9LO4cAPC12KmpLQ= -github.com/charmbracelet/bubbletea v0.26.3-0.20240514165352-43ffb64832ac h1:oU2Kc74rwT2tmvtVSOK5IQVHkgD7SiIxdICGu3zQ2uI= -github.com/charmbracelet/bubbletea v0.26.3-0.20240514165352-43ffb64832ac/go.mod h1:1dQnHpcm7F+IxzjVZELAgjNMsg8MeTRa2oZefBe/giM= -github.com/charmbracelet/bubbletea v0.26.3-0.20240514171728-e310dbefd4d7 h1:Az6OJpSeWBh/MsWMylnovPv6m1W1bzCYn65vRjXEtuU= -github.com/charmbracelet/bubbletea v0.26.3-0.20240514171728-e310dbefd4d7/go.mod h1:1dQnHpcm7F+IxzjVZELAgjNMsg8MeTRa2oZefBe/giM= -github.com/charmbracelet/bubbletea v0.26.3-0.20240514172205-72ce959b78e3 h1:GugYAtdwJBA1e8+y/diVe41cPS8dgdML7+atTeLS4R8= -github.com/charmbracelet/bubbletea v0.26.3-0.20240514172205-72ce959b78e3/go.mod h1:FixrOn+mReyxbVP0qZcyDsHLKeaZbMHf3wZOxLm8wj4= -github.com/charmbracelet/bubbletea v0.26.3-0.20240514172421-c69b27d7eeb8 h1:uq7c8wSq2/wK/KyvQ3KMQGZp0LFrKhOTAnHK6rNSMG0= -github.com/charmbracelet/bubbletea v0.26.3-0.20240514172421-c69b27d7eeb8/go.mod h1:69vZAJlmlVOxh7Y/Ndvr0csqEP2coOgZolwXx6qvPCk= -github.com/charmbracelet/bubbletea v0.26.3-0.20240515201852-5648cc2d9070 h1:fQQA/hv45Y7AhZH1l0p1WMfmkQ6CwexJEQ0/U+j0TGM= -github.com/charmbracelet/bubbletea v0.26.3-0.20240515201852-5648cc2d9070/go.mod h1:XggZds/FnG8qLKTAc4DRzqooDc72f94hdsj1Qb2cWfM= -github.com/charmbracelet/bubbletea v0.26.3-0.20240528172931-7581b6a46c83 h1:3+xEsSiI+qhBURG8641j/4jz83ECgWhhNtDr8RsEQGQ= -github.com/charmbracelet/bubbletea v0.26.3-0.20240528172931-7581b6a46c83/go.mod h1:g/d1WUOwmXQpbQtzRoTYJ9Dzjait158UnUAOGdcb8R0= +github.com/charmbracelet/bubbletea v0.26.5-0.20240605151746-bca513b1982b h1:IqnzS1grN+x+JrAxhBdZ+9dXffMA5r9E/10OC5sgPxE= +github.com/charmbracelet/bubbletea v0.26.5-0.20240605151746-bca513b1982b/go.mod h1:n5kk+aidUsbwmVVdIlFPfwHB44/aKFGZk4AKVxWWi90= github.com/charmbracelet/harmonica v0.2.0 h1:8NxJWRWg/bzKqqEaaeFNipOu77YR5t8aSwG4pgaUBiQ= github.com/charmbracelet/harmonica v0.2.0/go.mod h1:KSri/1RMQOZLbw7AHqgcBycp8pgJnQMYYT8QZRqZ1Ao= -github.com/charmbracelet/lipgloss v0.10.1-0.20240510203806-a6dc2ab509a8 h1:aOfL0f8fhgqKe+iOIQYAIazSwTO4J0axB5ztPxw5JHY= -github.com/charmbracelet/lipgloss v0.10.1-0.20240510203806-a6dc2ab509a8/go.mod h1:axBjGt1mTZwwTM0kfcq8fPWFzNPj+ZAOjfLCr7JW9fk= -github.com/charmbracelet/lipgloss v0.10.1-0.20240513205513-d059cfa7d6f0 h1:CWvUdfXeiDgBM0PZ9Mix19P7JB1tzaCDUy9F1JiuQ5M= -github.com/charmbracelet/lipgloss v0.10.1-0.20240513205513-d059cfa7d6f0/go.mod h1:axBjGt1mTZwwTM0kfcq8fPWFzNPj+ZAOjfLCr7JW9fk= -github.com/charmbracelet/lipgloss v0.10.1-0.20240514163550-4de43222e210 h1:Q7gfEmAUFJL0n8IgXUs7m/SgjfRASClC6Mu+JpanJu8= -github.com/charmbracelet/lipgloss v0.10.1-0.20240514163550-4de43222e210/go.mod h1:/WJOlugg8bddqSP52FFiGXWVIfXsO+/+KzOGAT7opnw= -github.com/charmbracelet/lipgloss v0.10.1-0.20240514172133-9a1d854f90dd h1:y23y/2Sa7Wa3nseIm0KTP1R0BA44QyFhRB/rvJKOyjI= -github.com/charmbracelet/lipgloss v0.10.1-0.20240514172133-9a1d854f90dd/go.mod h1:/WJOlugg8bddqSP52FFiGXWVIfXsO+/+KzOGAT7opnw= -github.com/charmbracelet/lipgloss v0.10.1-0.20240514172355-88ecbb398449 h1:jy8zvXRALm2v8aK4ivo4m1+/E1/p8nejp5hWRudx2VA= -github.com/charmbracelet/lipgloss v0.10.1-0.20240514172355-88ecbb398449/go.mod h1:/WJOlugg8bddqSP52FFiGXWVIfXsO+/+KzOGAT7opnw= -github.com/charmbracelet/lipgloss v0.10.1-0.20240515201529-1969fb54fefe h1:2SKkDenRBzWlqbqnk7cseOretWPktJNddvQkvORQxWo= -github.com/charmbracelet/lipgloss v0.10.1-0.20240515201529-1969fb54fefe/go.mod h1:/WJOlugg8bddqSP52FFiGXWVIfXsO+/+KzOGAT7opnw= -github.com/charmbracelet/lipgloss v0.10.1-0.20240528172826-972c013da128 h1:/I9AeUxL+sekKPqe9LgQP8WesLt8Zo40WwcZmozjk1g= -github.com/charmbracelet/lipgloss v0.10.1-0.20240528172826-972c013da128/go.mod h1:/WJOlugg8bddqSP52FFiGXWVIfXsO+/+KzOGAT7opnw= -github.com/charmbracelet/x/ansi v0.1.0 h1:o4NbQQCoVgbLpD5RC1cI687baoLwrLZyCOTGlF0gne4= -github.com/charmbracelet/x/ansi v0.1.0/go.mod h1:dk73KoMTT5AX5BsX0KrqhsTqAnhZZoCBjs7dGWp4Ktw= +github.com/charmbracelet/lipgloss v0.11.1-0.20240605151253-a724723338e3 h1:mr/tgxpMK+EUnQguGGByux9bxLQMG3at24Xs+07BBYY= +github.com/charmbracelet/lipgloss v0.11.1-0.20240605151253-a724723338e3/go.mod h1:aA4Y43qJqCRSpm/LoDYWDsLFP9U/Z6h0O99AIjiwThc= github.com/charmbracelet/x/ansi v0.1.1 h1:CGAduulr6egay/YVbGc8Hsu8deMg1xZ/bkaXTPi1JDk= github.com/charmbracelet/x/ansi v0.1.1/go.mod h1:dk73KoMTT5AX5BsX0KrqhsTqAnhZZoCBjs7dGWp4Ktw= github.com/charmbracelet/x/input v0.1.0 h1:TEsGSfZYQyOtp+STIjyBq6tpRaorH0qpwZUj8DavAhQ= @@ -56,20 +26,16 @@ github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0 github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= -github.com/mattn/go-isatty v0.0.18 h1:DOKFKCQ7FNG2L1rbrmstDN4QVRdS89Nkh85u68Uwp98= -github.com/mattn/go-isatty v0.0.18/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= +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-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= 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/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s= -github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8= github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo= github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8= -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= diff --git a/help/help.go b/help/help.go index c2f356e..006956f 100644 --- a/help/help.go +++ b/help/help.go @@ -81,10 +81,10 @@ func New(ctx tea.Context) Model { ShortKey: keyStyle, ShortDesc: descStyle, ShortSeparator: sepStyle, - Ellipsis: sepStyle.Copy(), - FullKey: keyStyle.Copy(), - FullDesc: descStyle.Copy(), - FullSeparator: sepStyle.Copy(), + Ellipsis: sepStyle, + FullKey: keyStyle, + FullDesc: descStyle, + FullSeparator: sepStyle, }, } } @@ -214,9 +214,8 @@ func (m Model) FullHelpView(groups [][]key.Binding) string { if m.Width > 0 && totalWidth > m.Width { break } + out = append(out, sep) } - - out = append(out, sep) } return lipgloss.JoinHorizontal(lipgloss.Top, out...) diff --git a/list/defaultitem.go b/list/defaultitem.go index ed9424f..45836c8 100644 --- a/list/defaultitem.go +++ b/list/defaultitem.go @@ -8,7 +8,7 @@ import ( "github.com/charmbracelet/bubbles/key" tea "github.com/charmbracelet/bubbletea" "github.com/charmbracelet/lipgloss" - "github.com/muesli/reflow/truncate" + "github.com/charmbracelet/x/ansi" ) // DefaultItemStyles defines styling for a default list item. @@ -37,7 +37,7 @@ func NewDefaultItemStyles(ctx tea.Context) (s DefaultItemStyles) { Foreground(lipgloss.AdaptiveColor{Light: "#1a1a1a", Dark: "#dddddd"}). Padding(0, 0, 0, 2) - s.NormalDesc = s.NormalTitle.Copy(). + s.NormalDesc = s.NormalTitle. Foreground(lipgloss.AdaptiveColor{Light: "#A49FA5", Dark: "#777777"}) s.SelectedTitle = ctx.NewStyle(). @@ -46,14 +46,14 @@ func NewDefaultItemStyles(ctx tea.Context) (s DefaultItemStyles) { Foreground(lipgloss.AdaptiveColor{Light: "#EE6FF8", Dark: "#EE6FF8"}). Padding(0, 0, 0, 1) - s.SelectedDesc = s.SelectedTitle.Copy(). + s.SelectedDesc = s.SelectedTitle. Foreground(lipgloss.AdaptiveColor{Light: "#F793FF", Dark: "#AD58B4"}) s.DimmedTitle = ctx.NewStyle(). Foreground(lipgloss.AdaptiveColor{Light: "#A49FA5", Dark: "#777777"}). Padding(0, 0, 0, 2) - s.DimmedDesc = s.DimmedTitle.Copy(). + s.DimmedDesc = s.DimmedTitle. Foreground(lipgloss.AdaptiveColor{Light: "#C2B8C2", Dark: "#4D4D4D"}) s.FilterMatch = ctx.NewStyle().Underline(true) @@ -155,15 +155,15 @@ func (d DefaultDelegate) Render(w io.Writer, m Model, index int, item Item) { } // Prevent text from exceeding list width - textwidth := uint(m.width - s.NormalTitle.GetPaddingLeft() - s.NormalTitle.GetPaddingRight()) - title = truncate.StringWithTail(title, textwidth, ellipsis) + textwidth := m.width - s.NormalTitle.GetPaddingLeft() - s.NormalTitle.GetPaddingRight() + title = ansi.Truncate(title, textwidth, ellipsis) if d.ShowDescription { var lines []string for i, line := range strings.Split(desc, "\n") { if i >= d.height-1 { break } - lines = append(lines, truncate.StringWithTail(line, textwidth, ellipsis)) + lines = append(lines, ansi.Truncate(line, textwidth, ellipsis)) } desc = strings.Join(lines, "\n") } @@ -187,7 +187,7 @@ func (d DefaultDelegate) Render(w io.Writer, m Model, index int, item Item) { if isFiltered { // Highlight matches unmatched := s.SelectedTitle.Inline(true) - matched := unmatched.Copy().Inherit(s.FilterMatch) + matched := unmatched.Inherit(s.FilterMatch) title = lipgloss.StyleRunes(title, matchedRunes, matched, unmatched) } title = s.SelectedTitle.Render(title) @@ -196,7 +196,7 @@ func (d DefaultDelegate) Render(w io.Writer, m Model, index int, item Item) { if isFiltered { // Highlight matches unmatched := s.NormalTitle.Inline(true) - matched := unmatched.Copy().Inherit(s.FilterMatch) + matched := unmatched.Inherit(s.FilterMatch) title = lipgloss.StyleRunes(title, matchedRunes, matched, unmatched) } title = s.NormalTitle.Render(title) @@ -204,10 +204,10 @@ func (d DefaultDelegate) Render(w io.Writer, m Model, index int, item Item) { } if d.ShowDescription { - fmt.Fprintf(w, "%s\n%s", title, desc) + fmt.Fprintf(w, "%s\n%s", title, desc) //nolint: errcheck return } - fmt.Fprintf(w, "%s", title) + fmt.Fprintf(w, "%s", title) //nolint: errcheck } // ShortHelp returns the delegate's short help. diff --git a/list/list.go b/list/list.go index feac1b6..0a5de71 100644 --- a/list/list.go +++ b/list/list.go @@ -17,8 +17,7 @@ import ( "github.com/charmbracelet/bubbles/textinput" tea "github.com/charmbracelet/bubbletea" "github.com/charmbracelet/lipgloss" - "github.com/muesli/reflow/ansi" - "github.com/muesli/reflow/truncate" + "github.com/charmbracelet/x/ansi" "github.com/sahilm/fuzzy" ) @@ -1053,7 +1052,7 @@ func (m Model) View(ctx tea.Context) string { func (m Model) titleView() string { var ( view string - titleBarStyle = m.Styles.TitleBar.Copy() + titleBarStyle = m.Styles.TitleBar // We need to account for the size of the spinner, even if we don't // render it, to reserve some space for it should we turn it on later. @@ -1078,7 +1077,7 @@ func (m Model) titleView() string { // Status message if m.filterState != Filtering { view += " " + m.statusMessage - view = truncate.StringWithTail(view, uint(m.width-spinnerWidth), ellipsis) + view = ansi.Truncate(view, m.width-spinnerWidth, ellipsis) } } @@ -1129,7 +1128,7 @@ func (m Model) statusView() string { if filtered { f := strings.TrimSpace(m.FilterInput.Value()) - f = truncate.StringWithTail(f, 10, "…") + f = ansi.Truncate(f, 10, "…") status += fmt.Sprintf("“%s” ", f) } @@ -1154,14 +1153,14 @@ func (m Model) paginationView() string { // If the dot pagination is wider than the width of the window // use the arabic paginator. - if ansi.PrintableRuneWidth(s) > m.width { + if ansi.StringWidth(s) > m.width { m.Paginator.Type = paginator.Arabic s = m.Styles.ArabicPagination.Render(m.Paginator.View(m.ctx)) } style := m.Styles.PaginationStyle if m.delegate.Spacing() == 0 && style.GetMarginTop() == 0 { - style = style.Copy().MarginTop(1) + style = style.MarginTop(1) } return style.Render(s) diff --git a/progress/progress.go b/progress/progress.go index e1b7c7e..c3e6170 100644 --- a/progress/progress.go +++ b/progress/progress.go @@ -10,8 +10,8 @@ import ( tea "github.com/charmbracelet/bubbletea" "github.com/charmbracelet/harmonica" "github.com/charmbracelet/lipgloss" + "github.com/charmbracelet/x/ansi" "github.com/lucasb-eyer/go-colorful" - "github.com/muesli/reflow/ansi" ) // Internal ID management. Used during animating to assure that frame messages @@ -271,7 +271,7 @@ func (m Model) View(ctx tea.Context) string { func (m Model) ViewAs(ctx tea.Context, percent float64) string { b := strings.Builder{} percentView := m.percentageView(percent) - m.barView(ctx, &b, percent, ansi.PrintableRuneWidth(percentView)) + m.barView(ctx, &b, percent, ansi.StringWidth(percentView)) b.WriteString(percentView) return b.String() } @@ -297,7 +297,7 @@ func (m Model) barView(ctx tea.Context, b *strings.Builder, percent float64, tex if fw == 1 { // this is up for debate: in a gradient of width=1, should the // single character rendered be the first color, the last color - // or exactly 50% inbetween? I opted for 50% + // or exactly 50% in between? I opted for 50% p = 0.5 } else if m.scaleRamp { p = float64(i) / float64(fw-1) diff --git a/textarea/textarea.go b/textarea/textarea.go index 772b326..d1eb6b1 100644 --- a/textarea/textarea.go +++ b/textarea/textarea.go @@ -261,7 +261,7 @@ func New(ctx tea.Context) Model { FocusedStyle: focusedStyle, BlurredStyle: blurredStyle, cache: memoization.NewMemoCache[line, [][]rune](defaultMaxHeight), - EndOfBufferCharacter: '~', + EndOfBufferCharacter: ' ', ShowLineNumbers: true, Cursor: cur, KeyMap: DefaultKeyMap, diff --git a/textarea/textarea_test.go b/textarea/textarea_test.go index 1567a64..89397de 100644 --- a/textarea/textarea_test.go +++ b/textarea/textarea_test.go @@ -6,9 +6,9 @@ import ( "unicode" "github.com/MakeNowJust/heredoc" - "github.com/acarl005/stripansi" tea "github.com/charmbracelet/bubbletea" "github.com/charmbracelet/lipgloss" + "github.com/charmbracelet/x/ansi" ) func TestVerticalScrolling(t *testing.T) { @@ -326,6 +326,8 @@ func TestVerticalNavigationShouldRememberPositionWhileTraversing(t *testing.T) { } func TestView(t *testing.T) { + t.Parallel() + type want struct { view string cursorRow int @@ -342,11 +344,11 @@ func TestView(t *testing.T) { want: want{ view: heredoc.Doc(` > 1 Hello, World! - > ~ - > ~ - > ~ - > ~ - > ~ + > + > + > + > + > `), }, }, @@ -360,11 +362,11 @@ func TestView(t *testing.T) { want: want{ view: heredoc.Doc(` > 1 the first line - > ~ - > ~ - > ~ - > ~ - > ~ + > + > + > + > + > `), cursorRow: 0, cursorCol: 14, @@ -382,9 +384,9 @@ func TestView(t *testing.T) { > 1 the first line > 2 the second line > 3 the third line - > ~ - > ~ - > ~ + > + > + > `), cursorRow: 2, cursorCol: 14, @@ -401,11 +403,11 @@ func TestView(t *testing.T) { want: want{ view: heredoc.Doc(` > the first line - > ~ - > ~ - > ~ - > ~ - > ~ + > + > + > + > + > `), cursorRow: 0, cursorCol: 14, @@ -424,9 +426,9 @@ func TestView(t *testing.T) { > the first line > the second line > the third line - > ~ - > ~ - > ~ + > + > + > `), cursorRow: 2, cursorCol: 14, @@ -529,11 +531,11 @@ func TestView(t *testing.T) { want: want{ view: heredoc.Doc(` * 1 the first line - * ~ - * ~ - * ~ - * ~ - * ~ + * + * + * + * + * `), cursorRow: 0, cursorCol: 14, @@ -552,9 +554,9 @@ func TestView(t *testing.T) { * 1 the first line * 2 the second line * 3 the third line - * ~ - * ~ - * ~ + * + * + * `), cursorRow: 2, cursorCol: 14, @@ -571,11 +573,11 @@ func TestView(t *testing.T) { want: want{ view: heredoc.Doc(` > 1 foo - > ~ - > ~ - > ~ - > ~ - > ~ + > + > + > + > + > `), cursorRow: 0, cursorCol: 3, @@ -594,9 +596,9 @@ func TestView(t *testing.T) { > 1 foo > 2 bar > 3 baz - > ~ - > ~ - > ~ + > + > + > `), cursorRow: 2, cursorCol: 3, @@ -619,9 +621,9 @@ func TestView(t *testing.T) { foo bar baz - ~ - ~ - ~ + + + `), cursorRow: 2, cursorCol: 3, @@ -640,11 +642,11 @@ func TestView(t *testing.T) { want: want{ view: heredoc.Doc(` > 1 foo bar - > ~ - > ~ - > ~ - > ~ - > ~ + > + > + > + > + > `), cursorRow: 0, cursorCol: 7, @@ -664,10 +666,10 @@ func TestView(t *testing.T) { view: heredoc.Doc(` > 1 foo bar baz > 2 foo bar - > ~ - > ~ - > ~ - > ~ + > + > + > + > `), cursorRow: 1, cursorCol: 7, @@ -686,11 +688,11 @@ func TestView(t *testing.T) { want: want{ view: heredoc.Doc(` > 1 12 - > ~ - > ~ - > ~ - > ~ - > ~ + > + > + > + > + > `), cursorRow: 0, cursorCol: 2, @@ -709,11 +711,11 @@ func TestView(t *testing.T) { want: want{ view: heredoc.Doc(` > 1 123 - > ~ - > ~ - > ~ - > ~ - > ~ + > + > + > + > + > `), cursorRow: 0, cursorCol: 3, @@ -733,10 +735,10 @@ func TestView(t *testing.T) { view: heredoc.Doc(` > 1 1234 > - > ~ - > ~ - > ~ - > ~ + > + > + > + > `), cursorRow: 1, cursorCol: 0, @@ -756,10 +758,10 @@ func TestView(t *testing.T) { view: heredoc.Doc(` > 1 1234 > 5 - > ~ - > ~ - > ~ - > ~ + > + > + > + > `), cursorRow: 1, cursorCol: 1, @@ -779,11 +781,11 @@ func TestView(t *testing.T) { want: want{ view: heredoc.Doc(` > 1 123 - > ~ - > ~ - > ~ - > ~ - > ~ + > + > + > + > + > `), cursorRow: 0, cursorCol: 3, @@ -804,10 +806,10 @@ func TestView(t *testing.T) { view: heredoc.Doc(` > 1 1234 > - > ~ - > ~ - > ~ - > ~ + > + > + > + > `), cursorRow: 1, cursorCol: 0, @@ -828,10 +830,10 @@ func TestView(t *testing.T) { view: heredoc.Doc(` > 1 1234 > 5 - > ~ - > ~ - > ~ - > ~ + > + > + > + > `), cursorRow: 1, cursorCol: 1, @@ -853,8 +855,8 @@ func TestView(t *testing.T) { > 2 > 3 > - > ~ - > ~ + > + > `), cursorRow: 3, cursorCol: 0, @@ -876,8 +878,8 @@ func TestView(t *testing.T) { > 2 > 3 > - > ~ - > ~ + > + > `), cursorRow: 3, cursorCol: 0, @@ -900,8 +902,8 @@ func TestView(t *testing.T) { > 2 > 3 > - > ~ - > ~ + > + > `), cursorRow: 3, cursorCol: 0, @@ -925,8 +927,8 @@ func TestView(t *testing.T) { 2 3 - ~ - ~ + + `), cursorRow: 3, cursorCol: 0, @@ -946,10 +948,10 @@ func TestView(t *testing.T) { view: heredoc.Doc(` > 1 12 > 3 - > ~ - > ~ - > ~ - > ~ + > + > + > + > `), cursorRow: 1, cursorCol: 1, @@ -969,11 +971,11 @@ func TestView(t *testing.T) { want: want{ view: heredoc.Doc(` > 123 - > ~ - > ~ - > ~ - > ~ - > ~ + > + > + > + > + > `), cursorRow: 0, cursorCol: 3, @@ -994,10 +996,10 @@ func TestView(t *testing.T) { view: heredoc.Doc(` > 1234 > - > ~ - > ~ - > ~ - > ~ + > + > + > + > `), cursorRow: 1, cursorCol: 0, @@ -1018,10 +1020,10 @@ func TestView(t *testing.T) { view: heredoc.Doc(` > 1234 > 5 - > ~ - > ~ - > ~ - > ~ + > + > + > + > `), cursorRow: 1, cursorCol: 1, @@ -1044,11 +1046,11 @@ func TestView(t *testing.T) { view: heredoc.Doc(` ┌──────────┐ │> 1 1 │ - │> ~ │ - │> ~ │ - │> ~ │ - │> ~ │ - │> ~ │ + │> │ + │> │ + │> │ + │> │ + │> │ └──────────┘ `), cursorRow: 0, @@ -1072,11 +1074,11 @@ func TestView(t *testing.T) { view: heredoc.Doc(` ┌──────────┐ │> 1 123 │ - │> ~ │ - │> ~ │ - │> ~ │ - │> ~ │ - │> ~ │ + │> │ + │> │ + │> │ + │> │ + │> │ └──────────┘ `), cursorRow: 0, @@ -1101,10 +1103,10 @@ func TestView(t *testing.T) { ┌──────────┐ │> 1 1234│ │> │ - │> ~ │ - │> ~ │ - │> ~ │ - │> ~ │ + │> │ + │> │ + │> │ + │> │ └──────────┘ `), cursorRow: 1, @@ -1129,10 +1131,10 @@ func TestView(t *testing.T) { ┌──────────┐ │> 1 1234│ │> 5 │ - │> ~ │ - │> ~ │ - │> ~ │ - │> ~ │ + │> │ + │> │ + │> │ + │> │ └──────────┘ `), cursorRow: 1, @@ -1157,11 +1159,11 @@ func TestView(t *testing.T) { view: heredoc.Doc(` ┌──────────┐ │> 123456 │ - │> ~ │ - │> ~ │ - │> ~ │ - │> ~ │ - │> ~ │ + │> │ + │> │ + │> │ + │> │ + │> │ └──────────┘ `), cursorRow: 0, @@ -1186,11 +1188,11 @@ func TestView(t *testing.T) { view: heredoc.Doc(` ┌──────────┐ │> 1234567 │ - │> ~ │ - │> ~ │ - │> ~ │ - │> ~ │ - │> ~ │ + │> │ + │> │ + │> │ + │> │ + │> │ └──────────┘ `), cursorRow: 0, @@ -1216,10 +1218,10 @@ func TestView(t *testing.T) { ┌──────────┐ │> 12345678│ │> │ - │> ~ │ - │> ~ │ - │> ~ │ - │> ~ │ + │> │ + │> │ + │> │ + │> │ └──────────┘ `), cursorRow: 1, @@ -1245,10 +1247,10 @@ func TestView(t *testing.T) { ┌──────────┐ │> 12345678│ │> 9 │ - │> ~ │ - │> ~ │ - │> ~ │ - │> ~ │ + │> │ + │> │ + │> │ + │> │ └──────────┘ `), cursorRow: 1, @@ -1264,19 +1266,419 @@ func TestView(t *testing.T) { }, want: want{ view: heredoc.Doc(` - > 1 . - > ~ - > ~ - > ~ - > ~ - > ~ + > 1 H + > e + > l + > l + > o + > , + `), + }, + }, + { + name: "placeholder single line", + modelFunc: func(m Model) Model { + m.Placeholder = "placeholder the first line" + m.ShowLineNumbers = false + + return m + }, + want: want{ + view: heredoc.Doc(` + > placeholder the first line + > + > + > + > + > + `), + }, + }, + { + name: "placeholder multiple lines", + modelFunc: func(m Model) Model { + m.Placeholder = "placeholder the first line\nplaceholder the second line\nplaceholder the third line" + m.ShowLineNumbers = false + + return m + }, + want: want{ + view: heredoc.Doc(` + > placeholder the first line + > placeholder the second line + > placeholder the third line + > + > + > + `), + }, + }, + { + name: "placeholder single line with line numbers", + modelFunc: func(m Model) Model { + m.Placeholder = "placeholder the first line" + m.ShowLineNumbers = true + + return m + }, + want: want{ + view: heredoc.Doc(` + > 1 placeholder the first line + > + > + > + > + > + `), + }, + }, + { + name: "placeholder multiple lines with line numbers", + modelFunc: func(m Model) Model { + m.Placeholder = "placeholder the first line\nplaceholder the second line\nplaceholder the third line" + m.ShowLineNumbers = true + + return m + }, + want: want{ + view: heredoc.Doc(` + > 1 placeholder the first line + > placeholder the second line + > placeholder the third line + > + > + > + `), + }, + }, + { + name: "placeholder single line with end of buffer character", + modelFunc: func(m Model) Model { + m.Placeholder = "placeholder the first line" + m.ShowLineNumbers = false + m.EndOfBufferCharacter = '*' + + return m + }, + want: want{ + view: heredoc.Doc(` + > placeholder the first line + > * + > * + > * + > * + > * + `), + }, + }, + { + name: "placeholder multiple lines with with end of buffer character", + modelFunc: func(m Model) Model { + m.Placeholder = "placeholder the first line\nplaceholder the second line\nplaceholder the third line" + m.ShowLineNumbers = false + m.EndOfBufferCharacter = '*' + + return m + }, + want: want{ + view: heredoc.Doc(` + > placeholder the first line + > placeholder the second line + > placeholder the third line + > * + > * + > * + `), + }, + }, + { + name: "placeholder single line with line numbers and end of buffer character", + modelFunc: func(m Model) Model { + m.Placeholder = "placeholder the first line" + m.ShowLineNumbers = true + m.EndOfBufferCharacter = '*' + + return m + }, + want: want{ + view: heredoc.Doc(` + > 1 placeholder the first line + > * + > * + > * + > * + > * + `), + }, + }, + { + name: "placeholder multiple lines with line numbers and end of buffer character", + modelFunc: func(m Model) Model { + m.Placeholder = "placeholder the first line\nplaceholder the second line\nplaceholder the third line" + m.ShowLineNumbers = true + m.EndOfBufferCharacter = '*' + + return m + }, + want: want{ + view: heredoc.Doc(` + > 1 placeholder the first line + > placeholder the second line + > placeholder the third line + > * + > * + > * + `), + }, + }, + { + name: "placeholder single line that is longer than max width", + modelFunc: func(m Model) Model { + m.Placeholder = "placeholder the first line that is longer than the max width" + m.SetWidth(40) + m.ShowLineNumbers = false + + return m + }, + want: want{ + view: heredoc.Doc(` + > placeholder the first line that is + > longer than the max width + > + > + > + > + `), + }, + }, + { + name: "placeholder multiple lines that are longer than max width", + modelFunc: func(m Model) Model { + m.Placeholder = "placeholder the first line that is longer than the max width\nplaceholder the second line that is longer than the max width" + m.ShowLineNumbers = false + m.SetWidth(40) + + return m + }, + want: want{ + view: heredoc.Doc(` + > placeholder the first line that is + > longer than the max width + > placeholder the second line that is + > longer than the max width + > + > + `), + }, + }, + { + name: "placeholder single line that is longer than max width with line numbers", + modelFunc: func(m Model) Model { + m.Placeholder = "placeholder the first line that is longer than the max width" + m.ShowLineNumbers = true + m.SetWidth(40) + + return m + }, + want: want{ + view: heredoc.Doc(` + > 1 placeholder the first line that is + > longer than the max width + > + > + > + > + `), + }, + }, + { + name: "placeholder multiple lines that are longer than max width with line numbers", + modelFunc: func(m Model) Model { + m.Placeholder = "placeholder the first line that is longer than the max width\nplaceholder the second line that is longer than the max width" + m.ShowLineNumbers = true + m.SetWidth(40) + + return m + }, + want: want{ + view: heredoc.Doc(` + > 1 placeholder the first line that is + > longer than the max width + > placeholder the second line that + > is longer than the max width + > + > + `), + }, + }, + { + name: "placeholder single line that is longer than max width at limit", + modelFunc: func(m Model) Model { + m.Placeholder = "123456789012345678" + m.ShowLineNumbers = false + m.SetWidth(20) + + return m + }, + want: want{ + view: heredoc.Doc(` + > 123456789012345678 + > + > + > + > + > + `), + }, + }, + { + name: "placeholder single line that is longer than max width at limit plus one", + modelFunc: func(m Model) Model { + m.Placeholder = "1234567890123456789" + m.ShowLineNumbers = false + m.SetWidth(20) + + return m + }, + want: want{ + view: heredoc.Doc(` + > 123456789012345678 + > 9 + > + > + > + > + `), + }, + }, + { + name: "placeholder single line that is longer than max width with line numbers at limit", + modelFunc: func(m Model) Model { + m.Placeholder = "12345678901234" + m.ShowLineNumbers = true + m.SetWidth(20) + + return m + }, + want: want{ + view: heredoc.Doc(` + > 1 12345678901234 + > + > + > + > + > + `), + }, + }, + { + name: "placeholder single line that is longer than max width with line numbers at limit plus one", + modelFunc: func(m Model) Model { + m.Placeholder = "123456789012345" + m.ShowLineNumbers = true + m.SetWidth(20) + + return m + }, + want: want{ + view: heredoc.Doc(` + > 1 12345678901234 + > 5 + > + > + > + > + `), + }, + }, + { + name: "placeholder multiple lines that are longer than max width at limit", + modelFunc: func(m Model) Model { + m.Placeholder = "123456789012345678\n123456789012345678" + m.ShowLineNumbers = false + m.SetWidth(20) + + return m + }, + want: want{ + view: heredoc.Doc(` + > 123456789012345678 + > 123456789012345678 + > + > + > + > + `), + }, + }, + { + name: "placeholder multiple lines that are longer than max width at limit plus one", + modelFunc: func(m Model) Model { + m.Placeholder = "1234567890123456789\n1234567890123456789" + m.ShowLineNumbers = false + m.SetWidth(20) + + return m + }, + want: want{ + view: heredoc.Doc(` + > 123456789012345678 + > 9 + > 123456789012345678 + > 9 + > + > + `), + }, + }, + { + name: "placeholder multiple lines that are longer than max width with line numbers at limit", + modelFunc: func(m Model) Model { + m.Placeholder = "12345678901234\n12345678901234" + m.ShowLineNumbers = true + m.SetWidth(20) + + return m + }, + want: want{ + view: heredoc.Doc(` + > 1 12345678901234 + > 12345678901234 + > + > + > + > + `), + }, + }, + { + name: "placeholder multiple lines that are longer than max width with line numbers at limit plus one", + modelFunc: func(m Model) Model { + m.Placeholder = "123456789012345\n123456789012345" + m.ShowLineNumbers = true + m.SetWidth(20) + + return m + }, + want: want{ + view: heredoc.Doc(` + > 1 12345678901234 + > 5 + > 12345678901234 + > 5 + > + > `), }, }, } for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + textarea := newTextArea() if tt.modelFunc != nil { @@ -1326,7 +1728,7 @@ func sendString(m Model, str string) Model { } func stripString(str string) string { - s := stripansi.Strip(str) + s := ansi.Strip(str) ss := strings.Split(s, "\n") var lines []string diff --git a/viewport/viewport.go b/viewport/viewport.go index 0053a6a..d3201c5 100644 --- a/viewport/viewport.go +++ b/viewport/viewport.go @@ -378,7 +378,7 @@ func (m Model) View(ctx tea.Context) string { MaxHeight(contentHeight). // truncate height if taller. MaxWidth(contentWidth). // truncate width if wider. Render(strings.Join(m.visibleLines(), "\n")) - return m.Style.Copy(). + return m.Style. UnsetWidth().UnsetHeight(). // Style size already applied in contents. Render(contents) }