From e5296a2b0fd6b4b7db8cb54cc15b99b681b7d949 Mon Sep 17 00:00:00 2001 From: Carlos Alexandro Becker Date: Fri, 15 Nov 2024 19:00:41 -0300 Subject: [PATCH 1/7] ci: fix goreleaser config (#668) Signed-off-by: Carlos Alexandro Becker --- .gitignore | 1 + .goreleaser.yml | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index e43b0f9..1a07445 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ .DS_Store +dist/ diff --git a/.goreleaser.yml b/.goreleaser.yml index c61970e..3353d02 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -1,5 +1,5 @@ +# yaml-language-server: $schema=https://goreleaser.com/static/schema-pro.json +version: 2 includes: - from_url: url: charmbracelet/meta/main/goreleaser-lib.yaml -# yaml-language-server: $schema=https://goreleaser.com/static/schema-pro.json - From 8624776d4572078ae6ff098d454c719047f9eb83 Mon Sep 17 00:00:00 2001 From: Mikael Fangel <34864484+MikaelFangel@users.noreply.github.com> Date: Thu, 5 Dec 2024 22:40:33 +0100 Subject: [PATCH 2/7] fix(textinput): slicing outside cap (#532) * textinput: fix slicing outside cap * textinput: add test that makes slicing outside cap occur * chore: tidy with gofmt --------- Co-authored-by: bashbunni --- textinput/textinput.go | 4 +++- textinput/textinput_test.go | 7 +++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/textinput/textinput.go b/textinput/textinput.go index d1abf12..66e4518 100644 --- a/textinput/textinput.go +++ b/textinput/textinput.go @@ -700,10 +700,12 @@ func (m Model) View() string { func (m Model) placeholderView() string { var ( v string - p = []rune(m.Placeholder) style = m.PlaceholderStyle.Inline(true).Render ) + p := make([]rune, m.Width+1) + copy(p, []rune(m.Placeholder)) + m.Cursor.TextStyle = m.PlaceholderStyle m.Cursor.SetChar(string(p[:1])) v += m.Cursor.View() diff --git a/textinput/textinput_test.go b/textinput/textinput_test.go index 27a7640..95ef0c6 100644 --- a/textinput/textinput_test.go +++ b/textinput/textinput_test.go @@ -30,3 +30,10 @@ func Test_CurrentSuggestion(t *testing.T) { t.Fatalf("Error: expected first suggestion but was %s", suggestion) } } + +func Test_SlicingOutsideCap(t *testing.T) { + textinput := New() + textinput.Placeholder = "作業ディレクトリを指定してください" + textinput.Width = 32 + textinput.View() +} From 2e3a42396e9952e9b3de4ac9eda4d81134ef9af3 Mon Sep 17 00:00:00 2001 From: Ayman Bagabas Date: Thu, 12 Dec 2024 10:51:23 -0500 Subject: [PATCH 3/7] chore(progress)!: migrate progress to lipgloss (#676) * chore(progress)!: migrate progress to lipgloss This removes the dependency on termenv and replaces it with lipgloss. This change also removes the WithColorProfile option, as it is no longer needed. The new API uses `color.Color` types for colors, which are more flexible and allow for more advanced color manipulation. * Update progress/progress_test.go Co-authored-by: Christian Rocha * Update progress/progress_test.go Co-authored-by: Christian Rocha * chore: go mod tidy --------- Co-authored-by: Christian Rocha --- go.mod | 5 +---- go.sum | 11 ++-------- progress/progress.go | 45 ++++++++++++--------------------------- progress/progress_test.go | 12 +++++------ 4 files changed, 22 insertions(+), 51 deletions(-) diff --git a/go.mod b/go.mod index 6c74148..15668ce 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.18 require ( github.com/MakeNowJust/heredoc v1.0.0 github.com/atotto/clipboard v0.1.4 - github.com/charmbracelet/bubbletea/v2 v2.0.0-alpha.2.0.20241121171714-fbd5423ea935 + github.com/charmbracelet/bubbletea/v2 v2.0.0-alpha.2.0.20241126192050-a8ed96118b08 github.com/charmbracelet/harmonica v0.2.0 github.com/charmbracelet/lipgloss/v2 v2.0.0-alpha.2.0.20241121164047-8448a9be4804 github.com/charmbracelet/x/ansi v0.5.1 @@ -13,13 +13,11 @@ require ( github.com/dustin/go-humanize v1.0.1 github.com/lucasb-eyer/go-colorful v1.2.0 github.com/mattn/go-runewidth v0.0.16 - github.com/muesli/termenv v0.15.2 github.com/rivo/uniseg v0.4.7 github.com/sahilm/fuzzy v0.1.1 ) require ( - github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect github.com/aymanbagabas/go-udiff v0.2.0 // indirect github.com/charmbracelet/colorprofile v0.1.8 // indirect github.com/charmbracelet/x/cellbuf v0.0.6 // indirect @@ -28,7 +26,6 @@ require ( github.com/charmbracelet/x/wcwidth v0.0.0-20241113152101-0af7d04e9f32 // indirect github.com/charmbracelet/x/windows v0.2.0 // indirect github.com/kylelemons/godebug v1.1.0 // indirect - github.com/mattn/go-isatty v0.0.20 // indirect github.com/muesli/cancelreader v0.2.2 // indirect github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect golang.org/x/sync v0.9.0 // indirect diff --git a/go.sum b/go.sum index dfc4079..2c3c84b 100644 --- a/go.sum +++ b/go.sum @@ -2,12 +2,10 @@ github.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ github.com/MakeNowJust/heredoc v1.0.0/go.mod h1:mG5amYoWBHf8vpLOuehzbGGw0EHxpZZ6lCpQ4fNJ8LE= 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/aymanbagabas/go-udiff v0.2.0 h1:TK0fH4MteXUDspT88n8CKzvK0X9O2xu9yQjWpi6yML8= github.com/aymanbagabas/go-udiff v0.2.0/go.mod h1:RE4Ex0qsGkTAJoQdQQCA0uG+nAzJO/pI/QwceO5fgrA= -github.com/charmbracelet/bubbletea/v2 v2.0.0-alpha.2.0.20241121171714-fbd5423ea935 h1:S+hhEwWnJxDeZMtHqIHgGVilNWsez3xmFOpwSc9GbcE= -github.com/charmbracelet/bubbletea/v2 v2.0.0-alpha.2.0.20241121171714-fbd5423ea935/go.mod h1:BbC4R+6e9TLjbskxrjISt/DDCn4OiB6v+ArqfYiPyyg= +github.com/charmbracelet/bubbletea/v2 v2.0.0-alpha.2.0.20241126192050-a8ed96118b08 h1:8hwULvCHjF6JjaeosebMGbB06oCv46d4s+Lbs5ytAT4= +github.com/charmbracelet/bubbletea/v2 v2.0.0-alpha.2.0.20241126192050-a8ed96118b08/go.mod h1:BbC4R+6e9TLjbskxrjISt/DDCn4OiB6v+ArqfYiPyyg= github.com/charmbracelet/colorprofile v0.1.8 h1:PywDeXsiAzlPtkiiKgMEVLvb6nlEuKrMj9+FJBtj4jU= github.com/charmbracelet/colorprofile v0.1.8/go.mod h1:+jpmObxZl1Dab3H3IMVIPSZTsKcFpjJUv97G0dLqM60= github.com/charmbracelet/harmonica v0.2.0 h1:8NxJWRWg/bzKqqEaaeFNipOu77YR5t8aSwG4pgaUBiQ= @@ -34,14 +32,10 @@ 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.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.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= 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/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo= -github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8= 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= @@ -52,7 +46,6 @@ github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJu golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI= golang.org/x/sync v0.9.0 h1:fEo0HyrW1GIgZdpbhCRO0PkJajUS5H9IFUztCgEo2jQ= golang.org/x/sync v0.9.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s= golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/text v0.20.0 h1:gK/Kv2otX8gz+wn7Rmb3vT96ZwuoxnQlY+HlJVj7Qug= diff --git a/progress/progress.go b/progress/progress.go index b4c9129..b596c30 100644 --- a/progress/progress.go +++ b/progress/progress.go @@ -2,6 +2,7 @@ package progress import ( "fmt" + "image/color" "math" "strings" "sync/atomic" @@ -12,7 +13,6 @@ import ( "github.com/charmbracelet/lipgloss/v2" "github.com/charmbracelet/x/ansi" "github.com/lucasb-eyer/go-colorful" - "github.com/muesli/termenv" ) // Internal ID management. Used during animating to assure that frame messages @@ -65,7 +65,7 @@ func WithScaledGradient(colorA, colorB string) Option { } // WithSolidFill sets the progress to use a solid fill with the given color. -func WithSolidFill(color string) Option { +func WithSolidFill(color color.Color) Option { return func(m *Model) { m.FullColor = color m.useRamp = false @@ -108,13 +108,6 @@ func WithSpringOptions(frequency, damping float64) Option { } } -// WithColorProfile sets the color profile to use for the progress bar. -func WithColorProfile(p termenv.Profile) Option { - return func(m *Model) { - m.colorProfile = p - } -} - // FrameMsg indicates that an animation step should occur. type FrameMsg struct { id int @@ -135,11 +128,11 @@ type Model struct { // "Filled" sections of the progress bar. Full rune - FullColor string + FullColor color.Color // "Empty" sections of the progress bar. Empty rune - EmptyColor string + EmptyColor color.Color // Settings for rendering the numeric percentage. ShowPercentage bool @@ -162,9 +155,6 @@ type Model struct { // of the progress bar. When false, the width of the gradient will be set // to the full width of the progress bar. scaleRamp bool - - // Color profile for the progress bar. - colorProfile termenv.Profile } // New returns a model with default values. @@ -173,12 +163,11 @@ func New(opts ...Option) Model { id: nextID(), width: defaultWidth, Full: '█', - FullColor: "#7571F9", + FullColor: lipgloss.Color("#7571F9"), Empty: '░', - EmptyColor: "#606060", + EmptyColor: lipgloss.Color("#606060"), ShowPercentage: true, PercentFormat: " %3.0f%%", - colorProfile: termenv.ColorProfile(), } for _, opt := range opts { @@ -316,23 +305,21 @@ func (m Model) barView(b *strings.Builder, percent float64, textWidth int) { } else { p = float64(i) / float64(tw-1) } - c := m.rampColorA.BlendLuv(m.rampColorB, p).Hex() - b.WriteString(termenv. - String(string(m.Full)). - Foreground(m.color(c)). - String(), - ) + c := m.rampColorA.BlendLuv(m.rampColorB, p) + b.WriteString(lipgloss.NewStyle().Foreground(c).Render(string(m.Full))) } } else { // Solid fill - s := termenv.String(string(m.Full)).Foreground(m.color(m.FullColor)).String() - b.WriteString(strings.Repeat(s, fw)) + b.WriteString(lipgloss.NewStyle(). + Foreground(m.FullColor). + Render(strings.Repeat(string(m.Full), fw))) } // Empty fill - e := termenv.String(string(m.Empty)).Foreground(m.color(m.EmptyColor)).String() n := max(0, tw-fw) - b.WriteString(strings.Repeat(e, n)) + b.WriteString(lipgloss.NewStyle(). + Foreground(m.EmptyColor). + Render(strings.Repeat(string(m.Empty), n))) } func (m Model) percentageView(percent float64) string { @@ -358,10 +345,6 @@ func (m *Model) setRamp(colorA, colorB string, scaled bool) { m.rampColorB = b } -func (m Model) color(c string) termenv.Color { - return m.colorProfile.Color(c) -} - func max(a, b int) int { if a > b { return a diff --git a/progress/progress_test.go b/progress/progress_test.go index a4ba720..08bd52b 100644 --- a/progress/progress_test.go +++ b/progress/progress_test.go @@ -4,15 +4,14 @@ import ( "strings" "testing" - "github.com/muesli/termenv" + "github.com/charmbracelet/lipgloss/v2" ) const ( - AnsiReset = "\x1b[0m" + AnsiReset = "\x1b[m" ) func TestGradient(t *testing.T) { - colA := "#FF0000" colB := "#00FF00" @@ -21,7 +20,7 @@ func TestGradient(t *testing.T) { for _, scale := range []bool{false, true} { opts := []Option{ - WithColorProfile(termenv.TrueColor), WithoutPercentage(), + WithoutPercentage(), } if scale { descr = "progress bar with scaled gradient" @@ -36,10 +35,10 @@ func TestGradient(t *testing.T) { // build the expected colors by colorizing an empty string and then cutting off the following reset sequence sb := strings.Builder{} - sb.WriteString(termenv.String("").Foreground(p.color(colA)).String()) + sb.WriteString(lipgloss.NewStyle().Foreground(lipgloss.Color(colA)).String()) expFirst := strings.Split(sb.String(), AnsiReset)[0] sb.Reset() - sb.WriteString(termenv.String("").Foreground(p.color(colB)).String()) + sb.WriteString(lipgloss.NewStyle().Foreground(lipgloss.Color(colB)).String()) expLast := strings.Split(sb.String(), AnsiReset)[0] for _, width := range []int{3, 5, 50} { @@ -62,5 +61,4 @@ func TestGradient(t *testing.T) { } }) } - } From 2d53a618a93c9cf2d3dd066d1b913fc6f46084cc Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 10 Jan 2025 12:38:48 +0100 Subject: [PATCH 4/7] feat(viewport): horizontal scroll (#240) * horizontal scroll * rebase branch * add tests * add tests with 2 cells symbols * trimLeft, move to charmbracelete/x/ansi lib * up ansi package * Update viewport/viewport.go Co-authored-by: Carlos Alexandro Becker * fix: do not navigate out to the right * fix: cache line width on setcontent * fix tests * fix viewport tests * add test for preventing right overscroll * chore(viewport): increase horizontal step to 6 * chore(viewport): make horizontal scroll API better match vertical scroll API * fix: nolint * fix: use ansi.Cut * perf: do not cut anything if not needed * feat: expose HorizontalScrollPercent * fix: do not scroll if width is 0 Signed-off-by: Carlos Alexandro Becker * fix: visible lines take frame into account --------- Signed-off-by: Carlos Alexandro Becker Co-authored-by: Carlos Alexandro Becker Co-authored-by: Christian Rocha --- go.mod | 2 +- go.sum | 4 +- viewport/keymap.go | 10 + viewport/viewport.go | 101 +++++++++- viewport/viewport_test.go | 383 ++++++++++++++++++++++++++++++++++++++ 5 files changed, 491 insertions(+), 9 deletions(-) create mode 100644 viewport/viewport_test.go diff --git a/go.mod b/go.mod index 575d4f5..c35957b 100644 --- a/go.mod +++ b/go.mod @@ -8,7 +8,7 @@ require ( github.com/charmbracelet/bubbletea v1.1.2 github.com/charmbracelet/harmonica v0.2.0 github.com/charmbracelet/lipgloss v1.0.0 - github.com/charmbracelet/x/ansi v0.4.2 + github.com/charmbracelet/x/ansi v0.6.1-0.20250107110353-48b574af22a5 github.com/charmbracelet/x/exp/golden v0.0.0-20241011142426-46044092ad91 github.com/dustin/go-humanize v1.0.1 github.com/lucasb-eyer/go-colorful v1.2.0 diff --git a/go.sum b/go.sum index bd41a6f..7542d37 100644 --- a/go.sum +++ b/go.sum @@ -12,8 +12,8 @@ github.com/charmbracelet/harmonica v0.2.0 h1:8NxJWRWg/bzKqqEaaeFNipOu77YR5t8aSwG github.com/charmbracelet/harmonica v0.2.0/go.mod h1:KSri/1RMQOZLbw7AHqgcBycp8pgJnQMYYT8QZRqZ1Ao= github.com/charmbracelet/lipgloss v1.0.0 h1:O7VkGDvqEdGi93X+DeqsQ7PKHDgtQfF8j8/O2qFMQNg= github.com/charmbracelet/lipgloss v1.0.0/go.mod h1:U5fy9Z+C38obMs+T+tJqst9VGzlOYGj4ri9reL3qUlo= -github.com/charmbracelet/x/ansi v0.4.2 h1:0JM6Aj/g/KC154/gOP4vfxun0ff6itogDYk41kof+qk= -github.com/charmbracelet/x/ansi v0.4.2/go.mod h1:dk73KoMTT5AX5BsX0KrqhsTqAnhZZoCBjs7dGWp4Ktw= +github.com/charmbracelet/x/ansi v0.6.1-0.20250107110353-48b574af22a5 h1:TSjbA80sXnABV/Vxhnb67Ho7p8bEYqz6NIdhLAx+1yg= +github.com/charmbracelet/x/ansi v0.6.1-0.20250107110353-48b574af22a5/go.mod h1:KBUFw1la39nl0dLl10l5ORDAqGXaeurTQmwyyVKse/Q= github.com/charmbracelet/x/exp/golden v0.0.0-20241011142426-46044092ad91 h1:payRxjMjKgx2PaCWLZ4p3ro9y97+TVLZNaRZgJwSVDQ= github.com/charmbracelet/x/exp/golden v0.0.0-20241011142426-46044092ad91/go.mod h1:wDlXFlCrmJ8J+swcL/MnGUuYnqgQdW9rhSD61oNMb6U= github.com/charmbracelet/x/term v0.2.0 h1:cNB9Ot9q8I711MyZ7myUR5HFWL/lc3OpU8jZ4hwm0x0= diff --git a/viewport/keymap.go b/viewport/keymap.go index 9289706..060bb87 100644 --- a/viewport/keymap.go +++ b/viewport/keymap.go @@ -15,6 +15,8 @@ type KeyMap struct { HalfPageDown key.Binding Down key.Binding Up key.Binding + Left key.Binding + Right key.Binding } // DefaultKeyMap returns a set of pager-like default keybindings. @@ -44,5 +46,13 @@ func DefaultKeyMap() KeyMap { key.WithKeys("down", "j"), key.WithHelp("↓/j", "down"), ), + Left: key.NewBinding( + key.WithKeys("left", "h"), + key.WithHelp("←/h", "move left"), + ), + Right: key.NewBinding( + key.WithKeys("right", "l"), + key.WithHelp("→/l", "move right"), + ), } } diff --git a/viewport/viewport.go b/viewport/viewport.go index f220d1e..1835b8d 100644 --- a/viewport/viewport.go +++ b/viewport/viewport.go @@ -7,6 +7,11 @@ import ( "github.com/charmbracelet/bubbles/key" tea "github.com/charmbracelet/bubbletea" "github.com/charmbracelet/lipgloss" + "github.com/charmbracelet/x/ansi" +) + +const ( + defaultHorizontalStep = 6 ) // New returns a new model with the given width and height as well as default @@ -34,6 +39,13 @@ type Model struct { // YOffset is the vertical scroll position. YOffset int + // xOffset is the horizontal scroll position. + xOffset int + + // horizontalStep is the number of columns we move left or right during a + // default horizontal scroll. + horizontalStep int + // YPosition is the position of the viewport in relation to the terminal // window. It's used in high performance rendering only. YPosition int @@ -54,8 +66,9 @@ type Model struct { // Deprecated: high performance rendering is now deprecated in Bubble Tea. HighPerformanceRendering bool - initialized bool - lines []string + initialized bool + lines []string + longestLineWidth int } func (m *Model) setInitialValues() { @@ -63,6 +76,7 @@ func (m *Model) setInitialValues() { m.MouseWheelEnabled = true m.MouseWheelDelta = 3 m.initialized = true + m.horizontalStep = defaultHorizontalStep } // Init exists to satisfy the tea.Model interface for composability purposes. @@ -99,10 +113,24 @@ func (m Model) ScrollPercent() float64 { return math.Max(0.0, math.Min(1.0, v)) } +// HorizontalScrollPercent returns the amount horizontally scrolled as a float +// between 0 and 1. +func (m Model) HorizontalScrollPercent() float64 { + if m.xOffset >= m.longestLineWidth-m.Width { + return 1.0 + } + y := float64(m.xOffset) + h := float64(m.Width) + t := float64(m.longestLineWidth) + v := y / (t - h) + return math.Max(0.0, math.Min(1.0, v)) +} + // SetContent set the pager's text content. func (m *Model) SetContent(s string) { s = strings.ReplaceAll(s, "\r\n", "\n") // normalize line endings m.lines = strings.Split(s, "\n") + m.longestLineWidth = findLongestLineWidth(m.lines) if m.YOffset > len(m.lines)-1 { m.GotoBottom() @@ -118,12 +146,24 @@ func (m Model) maxYOffset() int { // visibleLines returns the lines that should currently be visible in the // viewport. func (m Model) visibleLines() (lines []string) { + h := m.Height - m.Style.GetVerticalFrameSize() + w := m.Width - m.Style.GetHorizontalFrameSize() + if len(m.lines) > 0 { top := max(0, m.YOffset) - bottom := clamp(m.YOffset+m.Height, top, len(m.lines)) + bottom := clamp(m.YOffset+h, top, len(m.lines)) lines = m.lines[top:bottom] } - return lines + + if (m.xOffset == 0 && m.longestLineWidth <= w) || w == 0 { + return lines + } + + cutLines := make([]string, len(lines)) + for i := range lines { + cutLines[i] = ansi.Cut(lines[i], m.xOffset, m.xOffset+w) + } + return cutLines } // scrollArea returns the scrollable boundaries for high performance rendering. @@ -273,7 +313,7 @@ func ViewDown(m Model, lines []string) tea.Cmd { // XXX: high performance rendering is deprecated in Bubble Tea. In a v2 we // won't need to return a command here. - return tea.ScrollDown(lines, top, bottom) + return tea.ScrollDown(lines, top, bottom) //nolint:staticcheck } // ViewUp is a high performance command the moves the viewport down by a given @@ -287,7 +327,40 @@ func ViewUp(m Model, lines []string) tea.Cmd { // XXX: high performance rendering is deprecated in Bubble Tea. In a v2 we // won't need to return a command here. - return tea.ScrollUp(lines, top, bottom) + return tea.ScrollUp(lines, top, bottom) //nolint:staticcheck +} + +// SetHorizontalStep sets the amount of cells that the viewport moves in the +// default viewport keymapping. If set to 0 or less, horizontal scrolling is +// disabled. +func (m *Model) SetHorizontalStep(n int) { + if n < 0 { + n = 0 + } + + m.horizontalStep = n +} + +// MoveLeft moves the viewport to the left by the given number of columns. +func (m *Model) MoveLeft(cols int) { + m.xOffset -= cols + if m.xOffset < 0 { + m.xOffset = 0 + } +} + +// MoveRight moves viewport to the right by the given number of columns. +func (m *Model) MoveRight(cols int) { + // prevents over scrolling to the right + if m.xOffset >= m.longestLineWidth-m.Width { + return + } + m.xOffset += cols +} + +// Resets lines indent to zero. +func (m *Model) ResetIndent() { + m.xOffset = 0 } // Update handles standard message-based viewport updates. @@ -344,6 +417,12 @@ func (m Model) updateAsModel(msg tea.Msg) (Model, tea.Cmd) { if m.HighPerformanceRendering { cmd = ViewUp(m, lines) } + + case key.Matches(msg, m.KeyMap.Left): + m.MoveLeft(m.horizontalStep) + + case key.Matches(msg, m.KeyMap.Right): + m.MoveRight(m.horizontalStep) } case tea.MouseMsg: @@ -418,3 +497,13 @@ func max(a, b int) int { } return b } + +func findLongestLineWidth(lines []string) int { + w := 0 + for _, l := range lines { + if ww := ansi.StringWidth(l); ww > w { + w = ww + } + } + return w +} diff --git a/viewport/viewport_test.go b/viewport/viewport_test.go new file mode 100644 index 0000000..ef70a54 --- /dev/null +++ b/viewport/viewport_test.go @@ -0,0 +1,383 @@ +package viewport + +import ( + "strings" + "testing" +) + +func TestNew(t *testing.T) { + t.Parallel() + + t.Run("default values on create by New", func(t *testing.T) { + t.Parallel() + + m := New(10, 10) + + if !m.initialized { + t.Errorf("on create by New, Model should be initialized") + } + + if m.horizontalStep != defaultHorizontalStep { + t.Errorf("default horizontalStep should be %d, got %d", defaultHorizontalStep, m.horizontalStep) + } + + if m.MouseWheelDelta != 3 { + t.Errorf("default MouseWheelDelta should be 3, got %d", m.MouseWheelDelta) + } + + if !m.MouseWheelEnabled { + t.Error("mouse wheel should be enabled by default") + } + }) +} + +func TestSetInitialValues(t *testing.T) { + t.Parallel() + + t.Run("default horizontalStep", func(t *testing.T) { + t.Parallel() + + m := Model{} + m.setInitialValues() + + if m.horizontalStep != defaultHorizontalStep { + t.Errorf("default horizontalStep should be %d, got %d", defaultHorizontalStep, m.horizontalStep) + } + }) +} + +func TestSetHorizontalStep(t *testing.T) { + t.Parallel() + + t.Run("change default", func(t *testing.T) { + t.Parallel() + + m := New(10, 10) + + if m.horizontalStep != defaultHorizontalStep { + t.Errorf("default horizontalStep should be %d, got %d", defaultHorizontalStep, m.horizontalStep) + } + + newStep := 8 + m.SetHorizontalStep(newStep) + if m.horizontalStep != newStep { + t.Errorf("horizontalStep should be %d, got %d", newStep, m.horizontalStep) + } + }) + + t.Run("no negative", func(t *testing.T) { + t.Parallel() + + m := New(10, 10) + + if m.horizontalStep != defaultHorizontalStep { + t.Errorf("default horizontalStep should be %d, got %d", defaultHorizontalStep, m.horizontalStep) + } + + zero := 0 + m.SetHorizontalStep(-1) + if m.horizontalStep != zero { + t.Errorf("horizontalStep should be %d, got %d", zero, m.horizontalStep) + } + }) +} + +func TestMoveLeft(t *testing.T) { + t.Parallel() + + zeroPosition := 0 + + t.Run("zero position", func(t *testing.T) { + t.Parallel() + + m := New(10, 10) + if m.xOffset != zeroPosition { + t.Errorf("default indent should be %d, got %d", zeroPosition, m.xOffset) + } + + m.MoveLeft(m.horizontalStep) + if m.xOffset != zeroPosition { + t.Errorf("indent should be %d, got %d", zeroPosition, m.xOffset) + } + }) + + t.Run("move", func(t *testing.T) { + t.Parallel() + m := New(10, 10) + if m.xOffset != zeroPosition { + t.Errorf("default indent should be %d, got %d", zeroPosition, m.xOffset) + } + + m.xOffset = defaultHorizontalStep * 2 + m.MoveLeft(m.horizontalStep) + newIndent := defaultHorizontalStep + if m.xOffset != newIndent { + t.Errorf("indent should be %d, got %d", newIndent, m.xOffset) + } + }) +} + +func TestMoveRight(t *testing.T) { + t.Parallel() + + t.Run("move", func(t *testing.T) { + t.Parallel() + + zeroPosition := 0 + + m := New(10, 10) + m.SetContent("Some line that is longer than width") + if m.xOffset != zeroPosition { + t.Errorf("default indent should be %d, got %d", zeroPosition, m.xOffset) + } + + m.MoveRight(m.horizontalStep) + newIndent := defaultHorizontalStep + if m.xOffset != newIndent { + t.Errorf("indent should be %d, got %d", newIndent, m.xOffset) + } + }) +} + +func TestResetIndent(t *testing.T) { + t.Parallel() + + t.Run("reset", func(t *testing.T) { + t.Parallel() + + zeroPosition := 0 + + m := New(10, 10) + m.xOffset = 500 + + m.ResetIndent() + if m.xOffset != zeroPosition { + t.Errorf("indent should be %d, got %d", zeroPosition, m.xOffset) + } + }) +} + +func TestVisibleLines(t *testing.T) { + t.Parallel() + + defaultList := []string{ + `57 Precepts of narcissistic comedy character Zote from an awesome "Hollow knight" game (https://store.steampowered.com/app/367520/Hollow_Knight/).`, + `Precept One: 'Always Win Your Battles'. Losing a battle earns you nothing and teaches you nothing. Win your battles, or don't engage in them at all!`, + `Precept Two: 'Never Let Them Laugh at You'. Fools laugh at everything, even at their superiors. But beware, laughter isn't harmless! Laughter spreads like a disease, and soon everyone is laughing at you. You need to strike at the source of this perverse merriment quickly to stop it from spreading.`, + `Precept Three: 'Always Be Rested'. Fighting and adventuring take their toll on your body. When you rest, your body strengthens and repairs itself. The longer you rest, the stronger you become.`, + `Precept Four: 'Forget Your Past'. The past is painful, and thinking about your past can only bring you misery. Think about something else instead, such as the future, or some food.`, + `Precept Five: 'Strength Beats Strength'. Is your opponent strong? No matter! Simply overcome their strength with even more strength, and they'll soon be defeated.`, + `Precept Six: 'Choose Your Own Fate'. Our elders teach that our fate is chosen for us before we are even born. I disagree.`, + `Precept Seven: 'Mourn Not the Dead'. When we die, do things get better for us or worse? There's no way to tell, so we shouldn't bother mourning. Or celebrating for that matter.`, + `Precept Eight: 'Travel Alone'. You can rely on nobody, and nobody will always be loyal. Therefore, nobody should be your constant companion.`, + `Precept Nine: 'Keep Your Home Tidy'. Your home is where you keep your most prized possession - yourself. Therefore, you should make an effort to keep it nice and clean.`, + `Precept Ten: 'Keep Your Weapon Sharp'. I make sure that my weapon, 'Life Ender', is kept well-sharpened at all times. This makes it much easier to cut things.`, + `Precept Eleven: 'Mothers Will Always Betray You'. This Precept explains itself.`, + `Precept Twelve: 'Keep Your Cloak Dry'. If your cloak gets wet, dry it as soon as you can. Wearing wet cloaks is unpleasant, and can lead to illness.`, + `Precept Thirteen: 'Never Be Afraid'. Fear can only hold you back. Facing your fears can be a tremendous effort. Therefore, you should just not be afraid in the first place.`, + `Precept Fourteen: 'Respect Your Superiors'. If someone is your superior in strength or intellect or both, you need to show them your respect. Don't ignore them or laugh at them.`, + `Precept Fifteen: 'One Foe, One Blow'. You should only use a single blow to defeat an enemy. Any more is a waste. Also, by counting your blows as you fight, you'll know how many foes you've defeated.`, + `...`, + } + + t.Run("empty list", func(t *testing.T) { + t.Parallel() + + m := New(10, 10) + list := m.visibleLines() + + if len(list) != 0 { + t.Errorf("list should be empty, got %d", len(list)) + } + }) + + t.Run("empty list: with indent", func(t *testing.T) { + t.Parallel() + + m := New(10, 10) + list := m.visibleLines() + m.xOffset = 5 + + if len(list) != 0 { + t.Errorf("list should be empty, got %d", len(list)) + } + }) + + t.Run("list", func(t *testing.T) { + t.Parallel() + numberOfLines := 10 + + m := New(10, numberOfLines) + m.SetContent(strings.Join(defaultList, "\n")) + + list := m.visibleLines() + if len(list) != numberOfLines { + t.Errorf("list should have %d lines, got %d", numberOfLines, len(list)) + } + + lastItemIdx := numberOfLines - 1 + // we trim line if it doesn't fit to width of the viewport + shouldGet := defaultList[lastItemIdx][:m.Width] + if list[lastItemIdx] != shouldGet { + t.Errorf(`%dth list item should be '%s', got '%s'`, lastItemIdx, shouldGet, list[lastItemIdx]) + } + }) + + t.Run("list: with y offset", func(t *testing.T) { + t.Parallel() + numberOfLines := 10 + + m := New(10, numberOfLines) + m.SetContent(strings.Join(defaultList, "\n")) + m.YOffset = 5 + + list := m.visibleLines() + if len(list) != numberOfLines { + t.Errorf("list should have %d lines, got %d", numberOfLines, len(list)) + } + + if list[0] == defaultList[0] { + t.Error("first item of list should not be the first item of initial list because of Y offset") + } + + lastItemIdx := numberOfLines - 1 + // we trim line if it doesn't fit to width of the viewport + shouldGet := defaultList[m.YOffset+lastItemIdx][:m.Width] + if list[lastItemIdx] != shouldGet { + t.Errorf(`%dth list item should be '%s', got '%s'`, lastItemIdx, shouldGet, list[lastItemIdx]) + } + }) + + t.Run("list: with y offset: horizontal scroll", func(t *testing.T) { + t.Parallel() + numberOfLines := 10 + + m := New(10, numberOfLines) + m.lines = defaultList + m.YOffset = 7 + + // default list + list := m.visibleLines() + if len(list) != numberOfLines { + t.Errorf("list should have %d lines, got %d", numberOfLines, len(list)) + } + + lastItem := numberOfLines - 1 + defaultLastItem := len(defaultList) - 1 + if list[lastItem] != defaultList[defaultLastItem] { + t.Errorf("%dth list item should the the same as %dth default list item", lastItem, defaultLastItem) + } + + perceptPrefix := "Precept" + if !strings.HasPrefix(list[0], perceptPrefix) { + t.Errorf("first list item has to have prefix %s", perceptPrefix) + } + + // move right + m.MoveRight(m.horizontalStep) + list = m.visibleLines() + + newPrefix := perceptPrefix[m.xOffset:] + if !strings.HasPrefix(list[0], newPrefix) { + t.Errorf("first list item has to have prefix %s, get %s", newPrefix, list[0]) + } + + if list[lastItem] != "..." { + t.Errorf("last item should be empty, got %s", list[lastItem]) + } + + // move left + m.MoveLeft(m.horizontalStep) + list = m.visibleLines() + if !strings.HasPrefix(list[0], perceptPrefix) { + t.Errorf("first list item has to have prefix %s", perceptPrefix) + } + + if list[lastItem] != defaultList[defaultLastItem] { + t.Errorf("%dth list item should the the same as %dth default list item", lastItem, defaultLastItem) + } + }) + + t.Run("list: with 2 cells symbols: horizontal scroll", func(t *testing.T) { + t.Parallel() + + const horizontalStep = 5 + + initList := []string{ + "あいうえお", + "Aあいうえお", + "あいうえお", + "Aあいうえお", + } + numberOfLines := len(initList) + + m := New(20, numberOfLines) + m.lines = initList + m.longestLineWidth = 30 // dirty hack: not checking right overscroll for this test case + + // default list + list := m.visibleLines() + if len(list) != numberOfLines { + t.Errorf("list should have %d lines, got %d", numberOfLines, len(list)) + } + + lastItemIdx := numberOfLines - 1 + initLastItem := len(initList) - 1 + shouldGet := initList[initLastItem] + if list[lastItemIdx] != shouldGet { + t.Errorf("%dth list item should the the same as %dth default list item", lastItemIdx, initLastItem) + } + + // move right + m.MoveRight(horizontalStep) + list = m.visibleLines() + + for i := range list { + cutLine := "うえお" + if list[i] != cutLine { + t.Errorf("line must be `%s`, get `%s`", cutLine, list[i]) + } + } + + // move left + m.MoveLeft(horizontalStep) + list = m.visibleLines() + for i := range list { + if list[i] != initList[i] { + t.Errorf("line must be `%s`, get `%s`", list[i], initList[i]) + } + } + + // move left second times do not change lites if indent == 0 + m.xOffset = 0 + m.MoveLeft(horizontalStep) + list = m.visibleLines() + for i := range list { + if list[i] != initList[i] { + t.Errorf("line must be `%s`, get `%s`", list[i], initList[i]) + } + } + }) +} + +func TestRightOverscroll(t *testing.T) { + t.Parallel() + + t.Run("prevent right overscroll", func(t *testing.T) { + t.Parallel() + content := "Content is short" + m := New(len(content)+1, 5) + m.SetContent(content) + + for i := 0; i < 10; i++ { + m.MoveRight(m.horizontalStep) + } + + visibleLines := m.visibleLines() + visibleLine := visibleLines[0] + + if visibleLine != content { + t.Error("visible line should stay the same as content") + } + }) +} From 3487634e4dc12b23d03640f1ec6036c166afbff1 Mon Sep 17 00:00:00 2001 From: Carlos Alexandro Becker Date: Tue, 14 Jan 2025 14:26:31 -0300 Subject: [PATCH 5/7] chore(deps): update ansi Signed-off-by: Carlos Alexandro Becker --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 981a844..b7e6924 100644 --- a/go.mod +++ b/go.mod @@ -8,7 +8,7 @@ require ( github.com/charmbracelet/bubbletea/v2 v2.0.0-alpha.2.0.20241126192050-a8ed96118b08 github.com/charmbracelet/harmonica v0.2.0 github.com/charmbracelet/lipgloss/v2 v2.0.0-alpha.2.0.20241121164047-8448a9be4804 - github.com/charmbracelet/x/ansi v0.6.1-0.20250107110353-48b574af22a5 + github.com/charmbracelet/x/ansi v0.7.0 github.com/charmbracelet/x/exp/golden v0.0.0-20241011142426-46044092ad91 github.com/dustin/go-humanize v1.0.1 github.com/lucasb-eyer/go-colorful v1.2.0 diff --git a/go.sum b/go.sum index f9a188e..b247fb4 100644 --- a/go.sum +++ b/go.sum @@ -12,8 +12,8 @@ github.com/charmbracelet/harmonica v0.2.0 h1:8NxJWRWg/bzKqqEaaeFNipOu77YR5t8aSwG github.com/charmbracelet/harmonica v0.2.0/go.mod h1:KSri/1RMQOZLbw7AHqgcBycp8pgJnQMYYT8QZRqZ1Ao= github.com/charmbracelet/lipgloss/v2 v2.0.0-alpha.2.0.20241121164047-8448a9be4804 h1:7CYjb9YMZA4kMhLgGdtlXvq+nu1oyENpMyMQlTvqSFw= github.com/charmbracelet/lipgloss/v2 v2.0.0-alpha.2.0.20241121164047-8448a9be4804/go.mod h1:F/6E/LGdH3eHCJf2rG8/O3CjlW8cZFL5YJCknJs1GkI= -github.com/charmbracelet/x/ansi v0.6.1-0.20250107110353-48b574af22a5 h1:TSjbA80sXnABV/Vxhnb67Ho7p8bEYqz6NIdhLAx+1yg= -github.com/charmbracelet/x/ansi v0.6.1-0.20250107110353-48b574af22a5/go.mod h1:KBUFw1la39nl0dLl10l5ORDAqGXaeurTQmwyyVKse/Q= +github.com/charmbracelet/x/ansi v0.7.0 h1:/QfFmiXOGGwN6fRbzvQaYp7fu1pkxpZ3qFBZWBsP404= +github.com/charmbracelet/x/ansi v0.7.0/go.mod h1:KBUFw1la39nl0dLl10l5ORDAqGXaeurTQmwyyVKse/Q= github.com/charmbracelet/x/cellbuf v0.0.6 h1:pJUWN/G1jbt1Nj/+ILfC2/ABQoZzWu1vG73yHQEYELI= github.com/charmbracelet/x/cellbuf v0.0.6/go.mod h1:d72o71glp8flkCz54PHLe3+nuw5u2v3UxmKqruUERWQ= github.com/charmbracelet/x/exp/golden v0.0.0-20241011142426-46044092ad91 h1:payRxjMjKgx2PaCWLZ4p3ro9y97+TVLZNaRZgJwSVDQ= From fbe642df174c024b1ce775bd0780657a21e00437 Mon Sep 17 00:00:00 2001 From: Carlos Alexandro Becker Date: Tue, 14 Jan 2025 15:34:37 -0300 Subject: [PATCH 6/7] chore(deps): update --- go.mod | 9 +++++---- go.sum | 18 ++++++++++-------- textinput/textinput.go | 2 +- textinput/textinput_test.go | 2 +- viewport/viewport_test.go | 32 ++++++++++++++++---------------- 5 files changed, 33 insertions(+), 30 deletions(-) diff --git a/go.mod b/go.mod index b7e6924..f1f1431 100644 --- a/go.mod +++ b/go.mod @@ -5,9 +5,9 @@ go 1.18 require ( github.com/MakeNowJust/heredoc v1.0.0 github.com/atotto/clipboard v0.1.4 - github.com/charmbracelet/bubbletea/v2 v2.0.0-alpha.2.0.20241126192050-a8ed96118b08 + github.com/charmbracelet/bubbletea/v2 v2.0.0-alpha.2.0.20250114183054-9f703251e0d7 github.com/charmbracelet/harmonica v0.2.0 - github.com/charmbracelet/lipgloss/v2 v2.0.0-alpha.2.0.20241121164047-8448a9be4804 + github.com/charmbracelet/lipgloss/v2 v2.0.0-alpha.2.0.20250114171829-b67eb015d607 github.com/charmbracelet/x/ansi v0.7.0 github.com/charmbracelet/x/exp/golden v0.0.0-20241011142426-46044092ad91 github.com/dustin/go-humanize v1.0.1 @@ -21,6 +21,7 @@ require ( github.com/aymanbagabas/go-udiff v0.2.0 // indirect github.com/charmbracelet/colorprofile v0.1.8 // indirect github.com/charmbracelet/x/cellbuf v0.0.6 // indirect + github.com/charmbracelet/x/input v0.3.0 // indirect github.com/charmbracelet/x/term v0.2.1 // indirect github.com/charmbracelet/x/vt v0.0.0-20241121165045-a3720547cbb4 // indirect github.com/charmbracelet/x/wcwidth v0.0.0-20241113152101-0af7d04e9f32 // indirect @@ -28,7 +29,7 @@ require ( github.com/kylelemons/godebug v1.1.0 // indirect github.com/muesli/cancelreader v0.2.2 // indirect github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect - golang.org/x/sync v0.9.0 // indirect - golang.org/x/sys v0.27.0 // indirect + golang.org/x/sync v0.10.0 // indirect + golang.org/x/sys v0.28.0 // indirect golang.org/x/text v0.20.0 // indirect ) diff --git a/go.sum b/go.sum index b247fb4..9b0b881 100644 --- a/go.sum +++ b/go.sum @@ -4,20 +4,22 @@ github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI= github.com/aymanbagabas/go-udiff v0.2.0 h1:TK0fH4MteXUDspT88n8CKzvK0X9O2xu9yQjWpi6yML8= github.com/aymanbagabas/go-udiff v0.2.0/go.mod h1:RE4Ex0qsGkTAJoQdQQCA0uG+nAzJO/pI/QwceO5fgrA= -github.com/charmbracelet/bubbletea/v2 v2.0.0-alpha.2.0.20241126192050-a8ed96118b08 h1:8hwULvCHjF6JjaeosebMGbB06oCv46d4s+Lbs5ytAT4= -github.com/charmbracelet/bubbletea/v2 v2.0.0-alpha.2.0.20241126192050-a8ed96118b08/go.mod h1:BbC4R+6e9TLjbskxrjISt/DDCn4OiB6v+ArqfYiPyyg= +github.com/charmbracelet/bubbletea/v2 v2.0.0-alpha.2.0.20250114183054-9f703251e0d7 h1:Gn2noktdut/Qz95+4viQE8wZobkScUD64Fo4VdUFDPU= +github.com/charmbracelet/bubbletea/v2 v2.0.0-alpha.2.0.20250114183054-9f703251e0d7/go.mod h1:Ynvl3LVehMYuuEs2B0QKNETMOBPsq/Z05pNBrKnpK1k= github.com/charmbracelet/colorprofile v0.1.8 h1:PywDeXsiAzlPtkiiKgMEVLvb6nlEuKrMj9+FJBtj4jU= github.com/charmbracelet/colorprofile v0.1.8/go.mod h1:+jpmObxZl1Dab3H3IMVIPSZTsKcFpjJUv97G0dLqM60= 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/v2 v2.0.0-alpha.2.0.20241121164047-8448a9be4804 h1:7CYjb9YMZA4kMhLgGdtlXvq+nu1oyENpMyMQlTvqSFw= -github.com/charmbracelet/lipgloss/v2 v2.0.0-alpha.2.0.20241121164047-8448a9be4804/go.mod h1:F/6E/LGdH3eHCJf2rG8/O3CjlW8cZFL5YJCknJs1GkI= +github.com/charmbracelet/lipgloss/v2 v2.0.0-alpha.2.0.20250114171829-b67eb015d607 h1:lERE4ow371r5WMqQAt7Eqlg1A4tBNA8T4RLwdXnKyBo= +github.com/charmbracelet/lipgloss/v2 v2.0.0-alpha.2.0.20250114171829-b67eb015d607/go.mod h1:MD7Vb+O1zFRgBo+F94JHHuME7df8XBByNKuX5k/L/qs= github.com/charmbracelet/x/ansi v0.7.0 h1:/QfFmiXOGGwN6fRbzvQaYp7fu1pkxpZ3qFBZWBsP404= github.com/charmbracelet/x/ansi v0.7.0/go.mod h1:KBUFw1la39nl0dLl10l5ORDAqGXaeurTQmwyyVKse/Q= github.com/charmbracelet/x/cellbuf v0.0.6 h1:pJUWN/G1jbt1Nj/+ILfC2/ABQoZzWu1vG73yHQEYELI= github.com/charmbracelet/x/cellbuf v0.0.6/go.mod h1:d72o71glp8flkCz54PHLe3+nuw5u2v3UxmKqruUERWQ= github.com/charmbracelet/x/exp/golden v0.0.0-20241011142426-46044092ad91 h1:payRxjMjKgx2PaCWLZ4p3ro9y97+TVLZNaRZgJwSVDQ= github.com/charmbracelet/x/exp/golden v0.0.0-20241011142426-46044092ad91/go.mod h1:wDlXFlCrmJ8J+swcL/MnGUuYnqgQdW9rhSD61oNMb6U= +github.com/charmbracelet/x/input v0.3.0 h1:lVzEz92E2u9jCU0mUwcyKeSOxkoeat+1eUkjzL0WCYI= +github.com/charmbracelet/x/input v0.3.0/go.mod h1:M8CHPIYnmmiNHA17hqXmvSfeZLO2lj9pzJFX3aWvzgw= 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/vt v0.0.0-20241121165045-a3720547cbb4 h1:EacjHxcQEEgOZ7TbkAU3b84hd1Bn5NwA8YV5uyJ9EI4= @@ -44,9 +46,9 @@ github.com/sahilm/fuzzy v0.1.1/go.mod h1:VFvziUEIMCrT6A6tw2RFIXPXXmzXbOsSHF0DOI8 github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no= github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI= -golang.org/x/sync v0.9.0 h1:fEo0HyrW1GIgZdpbhCRO0PkJajUS5H9IFUztCgEo2jQ= -golang.org/x/sync v0.9.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s= -golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= +golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= +golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/text v0.20.0 h1:gK/Kv2otX8gz+wn7Rmb3vT96ZwuoxnQlY+HlJVj7Qug= golang.org/x/text v0.20.0/go.mod h1:D4IsuqiFMhST5bX19pQ9ikHC2GsaKyk/oF+pn3ducp4= diff --git a/textinput/textinput.go b/textinput/textinput.go index feb4a1c..92c468a 100644 --- a/textinput/textinput.go +++ b/textinput/textinput.go @@ -706,7 +706,7 @@ func (m Model) placeholderView() string { style = m.PlaceholderStyle.Inline(true).Render ) - p := make([]rune, m.Width+1) + p := make([]rune, m.Width()+1) copy(p, []rune(m.Placeholder)) m.Cursor.TextStyle = m.PlaceholderStyle diff --git a/textinput/textinput_test.go b/textinput/textinput_test.go index 95ef0c6..b13f60d 100644 --- a/textinput/textinput_test.go +++ b/textinput/textinput_test.go @@ -34,6 +34,6 @@ func Test_CurrentSuggestion(t *testing.T) { func Test_SlicingOutsideCap(t *testing.T) { textinput := New() textinput.Placeholder = "作業ディレクトリを指定してください" - textinput.Width = 32 + textinput.SetWidth(32) textinput.View() } diff --git a/viewport/viewport_test.go b/viewport/viewport_test.go index ef70a54..180cddb 100644 --- a/viewport/viewport_test.go +++ b/viewport/viewport_test.go @@ -11,7 +11,7 @@ func TestNew(t *testing.T) { t.Run("default values on create by New", func(t *testing.T) { t.Parallel() - m := New(10, 10) + m := New(WithHeight(10), WithWidth(10)) if !m.initialized { t.Errorf("on create by New, Model should be initialized") @@ -52,7 +52,7 @@ func TestSetHorizontalStep(t *testing.T) { t.Run("change default", func(t *testing.T) { t.Parallel() - m := New(10, 10) + m := New(WithHeight(10), WithWidth(10)) if m.horizontalStep != defaultHorizontalStep { t.Errorf("default horizontalStep should be %d, got %d", defaultHorizontalStep, m.horizontalStep) @@ -68,7 +68,7 @@ func TestSetHorizontalStep(t *testing.T) { t.Run("no negative", func(t *testing.T) { t.Parallel() - m := New(10, 10) + m := New(WithHeight(10), WithWidth(10)) if m.horizontalStep != defaultHorizontalStep { t.Errorf("default horizontalStep should be %d, got %d", defaultHorizontalStep, m.horizontalStep) @@ -90,7 +90,7 @@ func TestMoveLeft(t *testing.T) { t.Run("zero position", func(t *testing.T) { t.Parallel() - m := New(10, 10) + m := New(WithHeight(10), WithWidth(10)) if m.xOffset != zeroPosition { t.Errorf("default indent should be %d, got %d", zeroPosition, m.xOffset) } @@ -103,7 +103,7 @@ func TestMoveLeft(t *testing.T) { t.Run("move", func(t *testing.T) { t.Parallel() - m := New(10, 10) + m := New(WithHeight(10), WithWidth(10)) if m.xOffset != zeroPosition { t.Errorf("default indent should be %d, got %d", zeroPosition, m.xOffset) } @@ -125,7 +125,7 @@ func TestMoveRight(t *testing.T) { zeroPosition := 0 - m := New(10, 10) + m := New(WithHeight(10), WithWidth(10)) m.SetContent("Some line that is longer than width") if m.xOffset != zeroPosition { t.Errorf("default indent should be %d, got %d", zeroPosition, m.xOffset) @@ -147,7 +147,7 @@ func TestResetIndent(t *testing.T) { zeroPosition := 0 - m := New(10, 10) + m := New(WithHeight(10), WithWidth(10)) m.xOffset = 500 m.ResetIndent() @@ -183,7 +183,7 @@ func TestVisibleLines(t *testing.T) { t.Run("empty list", func(t *testing.T) { t.Parallel() - m := New(10, 10) + m := New(WithHeight(10), WithWidth(10)) list := m.visibleLines() if len(list) != 0 { @@ -194,7 +194,7 @@ func TestVisibleLines(t *testing.T) { t.Run("empty list: with indent", func(t *testing.T) { t.Parallel() - m := New(10, 10) + m := New(WithHeight(10), WithWidth(10)) list := m.visibleLines() m.xOffset = 5 @@ -207,7 +207,7 @@ func TestVisibleLines(t *testing.T) { t.Parallel() numberOfLines := 10 - m := New(10, numberOfLines) + m := New(WithHeight(numberOfLines), WithWidth(10)) m.SetContent(strings.Join(defaultList, "\n")) list := m.visibleLines() @@ -217,7 +217,7 @@ func TestVisibleLines(t *testing.T) { lastItemIdx := numberOfLines - 1 // we trim line if it doesn't fit to width of the viewport - shouldGet := defaultList[lastItemIdx][:m.Width] + shouldGet := defaultList[lastItemIdx][:m.Width()] if list[lastItemIdx] != shouldGet { t.Errorf(`%dth list item should be '%s', got '%s'`, lastItemIdx, shouldGet, list[lastItemIdx]) } @@ -227,7 +227,7 @@ func TestVisibleLines(t *testing.T) { t.Parallel() numberOfLines := 10 - m := New(10, numberOfLines) + m := New(WithHeight(numberOfLines), WithWidth(10)) m.SetContent(strings.Join(defaultList, "\n")) m.YOffset = 5 @@ -242,7 +242,7 @@ func TestVisibleLines(t *testing.T) { lastItemIdx := numberOfLines - 1 // we trim line if it doesn't fit to width of the viewport - shouldGet := defaultList[m.YOffset+lastItemIdx][:m.Width] + shouldGet := defaultList[m.YOffset+lastItemIdx][:m.Width()] if list[lastItemIdx] != shouldGet { t.Errorf(`%dth list item should be '%s', got '%s'`, lastItemIdx, shouldGet, list[lastItemIdx]) } @@ -252,7 +252,7 @@ func TestVisibleLines(t *testing.T) { t.Parallel() numberOfLines := 10 - m := New(10, numberOfLines) + m := New(WithHeight(numberOfLines), WithWidth(10)) m.lines = defaultList m.YOffset = 7 @@ -311,7 +311,7 @@ func TestVisibleLines(t *testing.T) { } numberOfLines := len(initList) - m := New(20, numberOfLines) + m := New(WithHeight(numberOfLines), WithWidth(20)) m.lines = initList m.longestLineWidth = 30 // dirty hack: not checking right overscroll for this test case @@ -366,7 +366,7 @@ func TestRightOverscroll(t *testing.T) { t.Run("prevent right overscroll", func(t *testing.T) { t.Parallel() content := "Content is short" - m := New(len(content)+1, 5) + m := New(WithHeight(5), WithWidth(len(content)+1)) m.SetContent(content) for i := 0; i < 10; i++ { From 0c83e6f4c8d3df04d4d58c63d9974171fc837247 Mon Sep 17 00:00:00 2001 From: Carlos Alexandro Becker Date: Wed, 15 Jan 2025 12:25:09 -0300 Subject: [PATCH 7/7] chore(deps): update Signed-off-by: Carlos Alexandro Becker --- go.mod | 11 +++++------ go.sum | 22 ++++++++++------------ 2 files changed, 15 insertions(+), 18 deletions(-) diff --git a/go.mod b/go.mod index f1f1431..4c08477 100644 --- a/go.mod +++ b/go.mod @@ -5,11 +5,11 @@ go 1.18 require ( github.com/MakeNowJust/heredoc v1.0.0 github.com/atotto/clipboard v0.1.4 - github.com/charmbracelet/bubbletea/v2 v2.0.0-alpha.2.0.20250114183054-9f703251e0d7 + github.com/charmbracelet/bubbletea/v2 v2.0.0-alpha.2.0.20250114201644-43a5b4dd0af0 github.com/charmbracelet/harmonica v0.2.0 github.com/charmbracelet/lipgloss/v2 v2.0.0-alpha.2.0.20250114171829-b67eb015d607 github.com/charmbracelet/x/ansi v0.7.0 - github.com/charmbracelet/x/exp/golden v0.0.0-20241011142426-46044092ad91 + github.com/charmbracelet/x/exp/golden v0.0.0-20241212170349-ad4b7ae0f25f github.com/dustin/go-humanize v1.0.1 github.com/lucasb-eyer/go-colorful v1.2.0 github.com/mattn/go-runewidth v0.0.16 @@ -19,17 +19,16 @@ require ( require ( github.com/aymanbagabas/go-udiff v0.2.0 // indirect - github.com/charmbracelet/colorprofile v0.1.8 // indirect - github.com/charmbracelet/x/cellbuf v0.0.6 // indirect + github.com/charmbracelet/colorprofile v0.1.9 // indirect + github.com/charmbracelet/x/cellbuf v0.0.7-0.20250113065325-800d48271e72 // indirect github.com/charmbracelet/x/input v0.3.0 // indirect github.com/charmbracelet/x/term v0.2.1 // indirect - github.com/charmbracelet/x/vt v0.0.0-20241121165045-a3720547cbb4 // indirect github.com/charmbracelet/x/wcwidth v0.0.0-20241113152101-0af7d04e9f32 // indirect github.com/charmbracelet/x/windows v0.2.0 // indirect github.com/kylelemons/godebug v1.1.0 // indirect github.com/muesli/cancelreader v0.2.2 // indirect github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect golang.org/x/sync v0.10.0 // indirect - golang.org/x/sys v0.28.0 // indirect + golang.org/x/sys v0.29.0 // indirect golang.org/x/text v0.20.0 // indirect ) diff --git a/go.sum b/go.sum index 9b0b881..2346187 100644 --- a/go.sum +++ b/go.sum @@ -4,26 +4,24 @@ github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI= github.com/aymanbagabas/go-udiff v0.2.0 h1:TK0fH4MteXUDspT88n8CKzvK0X9O2xu9yQjWpi6yML8= github.com/aymanbagabas/go-udiff v0.2.0/go.mod h1:RE4Ex0qsGkTAJoQdQQCA0uG+nAzJO/pI/QwceO5fgrA= -github.com/charmbracelet/bubbletea/v2 v2.0.0-alpha.2.0.20250114183054-9f703251e0d7 h1:Gn2noktdut/Qz95+4viQE8wZobkScUD64Fo4VdUFDPU= -github.com/charmbracelet/bubbletea/v2 v2.0.0-alpha.2.0.20250114183054-9f703251e0d7/go.mod h1:Ynvl3LVehMYuuEs2B0QKNETMOBPsq/Z05pNBrKnpK1k= -github.com/charmbracelet/colorprofile v0.1.8 h1:PywDeXsiAzlPtkiiKgMEVLvb6nlEuKrMj9+FJBtj4jU= -github.com/charmbracelet/colorprofile v0.1.8/go.mod h1:+jpmObxZl1Dab3H3IMVIPSZTsKcFpjJUv97G0dLqM60= +github.com/charmbracelet/bubbletea/v2 v2.0.0-alpha.2.0.20250114201644-43a5b4dd0af0 h1:BWjXQRSwBjoCpLeNu8zT93n+NHhZZhkQQLveXMmnkYc= +github.com/charmbracelet/bubbletea/v2 v2.0.0-alpha.2.0.20250114201644-43a5b4dd0af0/go.mod h1:hT2875Ank3ylgW13kqu6cjDc9XIk9sE5JsOFYdl09b8= +github.com/charmbracelet/colorprofile v0.1.9 h1:5JnfvX+I9D6rRNu8xK3pgIqknaBVTXHU9pGu1jkZxLw= +github.com/charmbracelet/colorprofile v0.1.9/go.mod h1:+jpmObxZl1Dab3H3IMVIPSZTsKcFpjJUv97G0dLqM60= 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/v2 v2.0.0-alpha.2.0.20250114171829-b67eb015d607 h1:lERE4ow371r5WMqQAt7Eqlg1A4tBNA8T4RLwdXnKyBo= github.com/charmbracelet/lipgloss/v2 v2.0.0-alpha.2.0.20250114171829-b67eb015d607/go.mod h1:MD7Vb+O1zFRgBo+F94JHHuME7df8XBByNKuX5k/L/qs= github.com/charmbracelet/x/ansi v0.7.0 h1:/QfFmiXOGGwN6fRbzvQaYp7fu1pkxpZ3qFBZWBsP404= github.com/charmbracelet/x/ansi v0.7.0/go.mod h1:KBUFw1la39nl0dLl10l5ORDAqGXaeurTQmwyyVKse/Q= -github.com/charmbracelet/x/cellbuf v0.0.6 h1:pJUWN/G1jbt1Nj/+ILfC2/ABQoZzWu1vG73yHQEYELI= -github.com/charmbracelet/x/cellbuf v0.0.6/go.mod h1:d72o71glp8flkCz54PHLe3+nuw5u2v3UxmKqruUERWQ= -github.com/charmbracelet/x/exp/golden v0.0.0-20241011142426-46044092ad91 h1:payRxjMjKgx2PaCWLZ4p3ro9y97+TVLZNaRZgJwSVDQ= -github.com/charmbracelet/x/exp/golden v0.0.0-20241011142426-46044092ad91/go.mod h1:wDlXFlCrmJ8J+swcL/MnGUuYnqgQdW9rhSD61oNMb6U= +github.com/charmbracelet/x/cellbuf v0.0.7-0.20250113065325-800d48271e72 h1:P90NI2rZuBISjB1HIHdkBDE+riKtVzIOi6Xun3qjUn8= +github.com/charmbracelet/x/cellbuf v0.0.7-0.20250113065325-800d48271e72/go.mod h1:VXZSjC/QYH0t+9CG1qtcEx3XZubTDJb5ilWS6qJg4/0= +github.com/charmbracelet/x/exp/golden v0.0.0-20241212170349-ad4b7ae0f25f h1:UytXHv0UxnsDFmL/7Z9Q5SBYPwSuRLXHbwx+6LycZ2w= +github.com/charmbracelet/x/exp/golden v0.0.0-20241212170349-ad4b7ae0f25f/go.mod h1:wDlXFlCrmJ8J+swcL/MnGUuYnqgQdW9rhSD61oNMb6U= github.com/charmbracelet/x/input v0.3.0 h1:lVzEz92E2u9jCU0mUwcyKeSOxkoeat+1eUkjzL0WCYI= github.com/charmbracelet/x/input v0.3.0/go.mod h1:M8CHPIYnmmiNHA17hqXmvSfeZLO2lj9pzJFX3aWvzgw= 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/vt v0.0.0-20241121165045-a3720547cbb4 h1:EacjHxcQEEgOZ7TbkAU3b84hd1Bn5NwA8YV5uyJ9EI4= -github.com/charmbracelet/x/vt v0.0.0-20241121165045-a3720547cbb4/go.mod h1:1/jFoHl7/I4br0StC9OXXEondkK9qi3nUtKoqI35HcI= github.com/charmbracelet/x/wcwidth v0.0.0-20241113152101-0af7d04e9f32 h1:14czE6R5CgOlvONsJYa2B1uTyLvXzGXpBqw2AyZeTh4= github.com/charmbracelet/x/wcwidth v0.0.0-20241113152101-0af7d04e9f32/go.mod h1:hyua5CY63kyl7IfyIxv1SjVEqoKze/XmDkEglItuVjA= github.com/charmbracelet/x/windows v0.2.0 h1:ilXA1GJjTNkgOm94CLPeSz7rar54jtFatdmoiONPuEw= @@ -48,7 +46,7 @@ github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJu golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI= golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= -golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= +golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/text v0.20.0 h1:gK/Kv2otX8gz+wn7Rmb3vT96ZwuoxnQlY+HlJVj7Qug= golang.org/x/text v0.20.0/go.mod h1:D4IsuqiFMhST5bX19pQ9ikHC2GsaKyk/oF+pn3ducp4=