Skip to content

Commit 9e5d53c

Browse files
committed
fix(textinput): account for horizontal scroll offset in Cursor()
1 parent 492ed7d commit 9e5d53c

2 files changed

Lines changed: 39 additions & 2 deletions

File tree

textinput/textinput.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -921,8 +921,9 @@ func (m Model) Cursor() *tea.Cursor {
921921
w := lipgloss.Width
922922

923923
promptWidth := w(m.promptView())
924-
xOffset := m.Position() +
925-
promptWidth
924+
// Like View, use the scroll-adjusted column (pos-offset) so the cursor
925+
// tracks the visible position when the value overflows the width.
926+
xOffset := max(0, m.pos-m.offset) + promptWidth
926927
if m.width > 0 {
927928
xOffset = min(xOffset, m.width+promptWidth)
928929
}

textinput/textinput_test.go

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"testing"
88

99
tea "charm.land/bubbletea/v2"
10+
"charm.land/lipgloss/v2"
1011
)
1112

1213
func Test_CurrentSuggestion(t *testing.T) {
@@ -118,3 +119,38 @@ func sendString(m Model, str string) Model {
118119

119120
return m
120121
}
122+
123+
// Cursor() must report the scroll-adjusted column (pos-offset) like View() when
124+
// the value overflows the width.
125+
func TestCursorXAccountsForScrollOffset(t *testing.T) {
126+
m := New()
127+
m.Focus()
128+
m.SetVirtualCursor(false)
129+
m.CharLimit = 200
130+
m.SetWidth(20)
131+
m.SetValue(strings.Repeat("a", 30))
132+
133+
// Scroll to the end, then move into the middle of the viewport, where the
134+
// width clamp doesn't mask the wrong column.
135+
m.CursorEnd()
136+
m.SetCursor(25)
137+
138+
if m.offset == 0 {
139+
t.Fatalf("test setup: expected a non-zero scroll offset for an overflowing input, got 0")
140+
}
141+
142+
cur := m.Cursor()
143+
if cur == nil {
144+
t.Fatal("Cursor() returned nil")
145+
}
146+
147+
// Oracle: the cursor must land on the column where View renders the glyph
148+
// for value[pos] — the prompt width plus the rendered width of the visible
149+
// text before the cursor (value[offset:pos]). Measured from the rendered
150+
// text, so it's independent of Cursor()'s own arithmetic.
151+
want := lipgloss.Width(m.promptView()) + lipgloss.Width(string(m.value[m.offset:m.pos]))
152+
if cur.X != want {
153+
t.Errorf("Cursor().X = %d, want %d (column where View renders the cursor glyph; pos=%d, offset=%d)",
154+
cur.X, want, m.pos, m.offset)
155+
}
156+
}

0 commit comments

Comments
 (0)