From 014143e9d5f22fda4c7d513c1b4c78100f5d8098 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Fri, 26 Jun 2026 11:04:03 +0000 Subject: [PATCH] fix(textinput): account for scroll offset in hardware Cursor() X position Cursor() used m.Position() (absolute index) instead of the scroll-adjusted visible column (m.pos - m.offset), causing the hardware cursor to be placed at the wrong column when input text overflows the configured width. Fixes charmbracelet/bubbles#1001 --- textinput/textinput.go | 2 +- textinput/textinput_test.go | 24 ++++++++++++++++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/textinput/textinput.go b/textinput/textinput.go index 363089b2d..dab4598f6 100644 --- a/textinput/textinput.go +++ b/textinput/textinput.go @@ -921,7 +921,7 @@ func (m Model) Cursor() *tea.Cursor { w := lipgloss.Width promptWidth := w(m.promptView()) - xOffset := m.Position() + + xOffset := max(0, m.pos-m.offset) + promptWidth if m.width > 0 { xOffset = min(xOffset, m.width+promptWidth) diff --git a/textinput/textinput_test.go b/textinput/textinput_test.go index b5e344b99..839eec299 100644 --- a/textinput/textinput_test.go +++ b/textinput/textinput_test.go @@ -118,3 +118,27 @@ func sendString(m Model, str string) Model { return m } + +func TestCursorXAccountsForScrollOffset(t *testing.T) { + m := New() + m.Focus() + m.SetVirtualCursor(false) + m.CharLimit = 200 + m.SetWidth(20) + + text := strings.Repeat("a", 30) + m.SetValue(text) + m.SetCursor(15) + + cur := m.Cursor() + if cur == nil { + t.Fatal("Cursor() returned nil") + } + + // With 30 chars in a width-20 viewport, offset=10 and visible column=5. + // Hardware cursor X must include the prompt ("> ", width 2). + const wantX = 7 + if cur.X != wantX { + t.Errorf("Cursor().X = %d, want %d (scroll-adjusted visible column + prompt width)", cur.X, wantX) + } +}