Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions textarea/textarea.go
Original file line number Diff line number Diff line change
Expand Up @@ -921,7 +921,14 @@ func (m *Model) characterLeft(insideLine bool) {
// cursor blink should be reset. If input is masked, move input to the start
// so as not to reveal word breaks in the masked input.
func (m *Model) wordLeft() {
// Skip spaces backward. characterLeft is a no-op at the very beginning
// of the buffer (row=0, col=0), so without an explicit check the loop
// spins forever on an empty textarea or one whose content is all
// trailing whitespace. See #1652 (filed against bubbletea, lives here).
for {
if m.row == 0 && m.col == 0 {
return
}
m.characterLeft(true /* insideLine */)
if m.col < len(m.value[m.row]) && !unicode.IsSpace(m.value[m.row][m.col]) {
break
Expand Down
23 changes: 23 additions & 0 deletions textarea/textarea_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"fmt"
"strings"
"testing"
"time"
"unicode"

tea "charm.land/bubbletea/v2"
Expand Down Expand Up @@ -1974,6 +1975,28 @@ func TestWord(t *testing.T) {
})
}

func TestWordLeftOnEmptyDoesNotHang(t *testing.T) {
// Regression test for charmbracelet/bubbletea#1652. Pressing alt+left
// (the WordBackward binding) on an empty textarea used to spin
// wordLeft's "skip spaces backward" loop forever because characterLeft
// is a no-op at (0,0) and the break condition can never become true.
textarea := newTextArea()
textarea.SetHeight(3)
textarea.SetWidth(20)

done := make(chan struct{})
go func() {
_, _ = textarea.Update(tea.KeyPressMsg{Code: tea.KeyLeft, Mod: tea.ModAlt, Text: "alt+left"})
close(done)
}()

select {
case <-done:
case <-time.After(2 * time.Second):
t.Fatal("wordLeft never returned on an empty textarea (would have hung the event loop)")
}
}

func newTextArea() Model {
textarea := New()

Expand Down