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
6 changes: 6 additions & 0 deletions cursed_renderer.go
Original file line number Diff line number Diff line change
Expand Up @@ -675,6 +675,12 @@ func (s *cursedRenderer) resize(w, h int) {
s.scr.Erase()
s.width, s.height = w, h
s.scr.Resize(s.width, s.height)
// Restore tab stops across the new width. The terminal keeps the stops
// from the previous size unless we send DECST8C again.
if s.hardTabs {
s.scr.SetTabStops(s.width)
_, _ = s.scr.WriteString(ansi.SetTabEvery8Columns)
}
s.pendingErase = true
s.mu.Unlock()
}
Expand Down
55 changes: 55 additions & 0 deletions cursed_renderer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -201,3 +201,58 @@ func TestCursedRenderer_updatesKittyKeyboardFlagsInPlace(t *testing.T) {
t.Fatalf("expected kitty keyboard protocol to be pushed once, got %d pushes in %q", n, got)
}
}

// Fixes: https://github.com/charmbracelet/bubbletea/issues/1838
func TestCursedRenderer_restoresTabStopsOnResize(t *testing.T) {
t.Parallel()

render := func(t *testing.T, r *cursedRenderer, v View) {
t.Helper()
r.render(v)
if err := r.flush(false); err != nil {
t.Fatal(err)
}
}

t.Run("hard tabs", func(t *testing.T) {
t.Parallel()

var out bytes.Buffer
r := newCursedRenderer(&out, []string{"TERM=xterm-256color"}, 80, 24)
r.setOptimizations(true, false, false)
r.start()
render(t, r, NewView("hello"))

got := out.String()
if n := strings.Count(got, ansi.SetTabEvery8Columns); n != 1 {
t.Fatalf("expected initial %q once, got %d in %q", ansi.SetTabEvery8Columns, n, got)
}

// Widen, then flush. resize queues DECST8C (and refreshes tab stops);
// the following flush is what writes it to the terminal.
r.resize(160, 24)
render(t, r, NewView("hello"))

got = out.String()
if n := strings.Count(got, ansi.SetTabEvery8Columns); n != 2 {
t.Fatalf("expected %q again after widen, got %d in %q", ansi.SetTabEvery8Columns, n, got)
}
assertInOrder(t, got, ansi.SetTabEvery8Columns, ansi.SetTabEvery8Columns)
})

t.Run("no hard tabs", func(t *testing.T) {
t.Parallel()

var out bytes.Buffer
r := newCursedRenderer(&out, []string{"TERM=xterm-256color"}, 80, 24)
r.setOptimizations(false, false, false)
r.start()
render(t, r, NewView("hello"))
r.resize(160, 24)
render(t, r, NewView("hello"))

if strings.Contains(out.String(), ansi.SetTabEvery8Columns) {
t.Fatalf("expected no %q without hard tabs, got %q", ansi.SetTabEvery8Columns, out.String())
}
})
}