Fix input field horizontal scroll offset not clamping after text deletion

When text in an input field overflows the visible width, the viewport
scrolls right to follow the cursor. After deleting characters with
backspace, the scroll origin was not clamped back, leaving empty space
on the right while earlier text remained hidden.

Add GetContentWidth() to TextArea and clamp the horizontal scroll
origin in RenderTextArea and the view resize path so the viewport
never scrolls past the actual content width.

Fixes #5633

Signed-off-by: Gautam Kumar <gautamkumarofficial@users.noreply.github.com>
This commit is contained in:
Gautam Kumar 2026-06-17 08:31:51 +05:30
parent 69004ce208
commit d17828c2bb
4 changed files with 64 additions and 0 deletions

View file

@ -327,6 +327,17 @@ func (g *Gui) SetView(name string, x0, y0, x1, y1 int, overlaps byte) (*View, er
newViewCursorX, newOriginX := updatedCursorAndOrigin(0, v.InnerWidth(), cursorX)
newViewCursorY, newOriginY := updatedCursorAndOrigin(0, v.InnerHeight(), cursorY)
contentWidth := v.TextArea.GetContentWidth()
usableWidth := v.InnerWidth() - 1
maxOriginX := contentWidth - usableWidth
if maxOriginX < 0 {
maxOriginX = 0
}
if newOriginX > maxOriginX {
newOriginX = maxOriginX
newViewCursorX = cursorX - newOriginX
}
v.SetCursor(newViewCursorX, newViewCursorY)
v.SetOrigin(newOriginX, newOriginY)
}

View file

@ -392,6 +392,14 @@ func (self *TextArea) GetUnwrappedContent() string {
return self.content
}
func (self *TextArea) GetContentWidth() int {
if len(self.cells) == 0 {
return 0
}
last := self.cells[len(self.cells)-1]
return last.x + last.width
}
func (self *TextArea) ToggleOverwrite() {
self.overwrite = !self.overwrite
}

View file

@ -1722,6 +1722,17 @@ func (v *View) RenderTextArea() {
newViewCursorX, newOriginX := updatedCursorAndOrigin(prevOriginX, width, cursorX)
newViewCursorY, newOriginY := updatedCursorAndOrigin(prevOriginY, height, cursorY)
contentWidth := v.TextArea.GetContentWidth()
usableWidth := width - 1
maxOriginX := contentWidth - usableWidth
if maxOriginX < 0 {
maxOriginX = 0
}
if newOriginX > maxOriginX {
newOriginX = maxOriginX
newViewCursorX = cursorX - newOriginX
}
v.SetCursor(newViewCursorX, newViewCursorY)
v.SetOrigin(newOriginX, newOriginY)
}

View file

@ -120,6 +120,40 @@ func TestUpdatedCursorAndOrigin(t *testing.T) {
}
}
func TestRenderTextAreaClampsScrollOffset(t *testing.T) {
// View with inner width 10: x0=0, x1=11 → Width=12, InnerWidth=10
v := NewView("name", 0, 0, 11, 0, OutputNormal)
v.Editable = true
// Type 15 characters to overflow the viewport
for i := 0; i < 15; i++ {
v.TextArea.TypeCharacter("a")
}
// Cursor is at position 15, content width is 15
cursorX, _ := v.TextArea.GetCursorXY()
assert.Equal(t, 15, cursorX)
// Simulate scrolling: set origin so viewport shows positions 6-15
v.SetOrigin(6, 0)
v.RenderTextArea()
originX, _ := v.Origin()
assert.Equal(t, 6, originX)
// Now delete 11 characters so content is only 4 chars wide
for i := 0; i < 11; i++ {
v.TextArea.BackSpaceChar()
}
// Cursor is at position 4, content width is 4
cursorX, _ = v.TextArea.GetCursorXY()
assert.Equal(t, 4, cursorX)
assert.Equal(t, 4, v.TextArea.GetContentWidth())
// After rendering, origin should be clamped to 0 since content (4) < viewport (10)
v.RenderTextArea()
originX, _ = v.Origin()
assert.Equal(t, 0, originX, "origin should be clamped to 0 when content is narrower than viewport")
}
func TestAutoRenderingHyperlinks(t *testing.T) {
v := NewView("name", 0, 0, 10, 10, OutputNormal)
v.AutoRenderHyperLinks = true