Skip to content

Commit 39528e6

Browse files
authored
Make checkout picker height dynamic (#469)
1 parent 5fe0a50 commit 39528e6

2 files changed

Lines changed: 47 additions & 20 deletions

File tree

‎internal/tui/checkoutview/model.go‎

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -253,23 +253,22 @@ func (m *Model) moveCursor(delta int) {
253253
m.ensureVisible()
254254
}
255255

256-
// maxVisibleRows caps how many stack rows the inline picker shows at once. The
257-
// rest are reached by scrolling, so the picker never takes over the screen.
258-
const maxVisibleRows = 10
256+
// initialVisibleRows caps the picker before the terminal size is known, so the
257+
// first frame cannot overflow a large stack.
258+
const initialVisibleRows = 10
259259

260260
// bodyHeight returns the number of table rows shown at once: the row count
261-
// capped at maxVisibleRows, and further shrunk to fit a short terminal. It never
262-
// returns less than 1 (a line is reserved for the empty-state message).
261+
// capped by the available terminal height. Before the terminal size is known,
262+
// it falls back to initialVisibleRows. It never returns less than 1 (a line is
263+
// reserved for the empty-state message).
263264
func (m Model) bodyHeight() int {
264265
rows := len(m.filtered)
265266
if rows < 1 {
266267
rows = 1
267268
}
268-
limit := maxVisibleRows
269+
limit := initialVisibleRows
269270
if m.height > 0 {
270-
if avail := m.height - m.chromeHeight(); avail < limit {
271-
limit = avail
272-
}
271+
limit = m.height - m.chromeHeight()
273272
}
274273
if limit < 1 {
275274
limit = 1
@@ -281,9 +280,9 @@ func (m Model) bodyHeight() int {
281280
}
282281

283282
// chromeHeight is the number of lines reserved around the table body when
284-
// fitting the picker to a short terminal: title, tabs, blank, header, footer
285-
// (5), plus one line of breathing room so the inline picker never exactly fills
286-
// the terminal (which would make it scroll). The search line adds one more.
283+
// fitting the picker to the terminal: title, tabs, blank, header, footer (5),
284+
// plus one line of breathing room so the inline picker never exactly fills the
285+
// terminal (which would make it scroll). The search line adds one more.
287286
func (m Model) chromeHeight() int {
288287
chrome := 6
289288
if m.searching {

‎internal/tui/checkoutview/model_test.go‎

Lines changed: 36 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -317,21 +317,49 @@ func manyRows(n int) []StackRow {
317317
return rows
318318
}
319319

320-
func TestView_InlineHeightIsBounded(t *testing.T) {
321-
// A long list on a tall terminal must not take over the screen: it shows at
322-
// most maxVisibleRows rows plus a little chrome, and offers a scroll hint.
323-
m := drive(New(manyRows(30)), tea.WindowSizeMsg{Width: 90, Height: 50})
320+
func TestBodyHeight_UsesAvailableTerminalSpace(t *testing.T) {
321+
tests := []struct {
322+
name string
323+
rows int
324+
height int
325+
searching bool
326+
want int
327+
}{
328+
{name: "unknown terminal size uses fallback", rows: 30, want: initialVisibleRows},
329+
{name: "small list stays compact", rows: 3, height: 50, want: 3},
330+
{name: "tall terminal expands past fallback", rows: 100, height: 50, want: 44},
331+
{name: "short terminal shrinks", rows: 30, height: 12, want: 6},
332+
{name: "search reserves another line", rows: 100, height: 50, searching: true, want: 43},
333+
{name: "empty list reserves one row", height: 50, want: 1},
334+
{name: "tiny terminal reserves one row", rows: 30, height: 3, want: 1},
335+
}
336+
337+
for _, tt := range tests {
338+
t.Run(tt.name, func(t *testing.T) {
339+
m := New(manyRows(tt.rows))
340+
m.height = tt.height
341+
m.searching = tt.searching
342+
assert.Equal(t, tt.want, m.bodyHeight())
343+
})
344+
}
345+
}
346+
347+
func TestView_ExpandsToAvailableTerminalHeight(t *testing.T) {
348+
const terminalHeight = 50
349+
m := drive(New(manyRows(100)), tea.WindowSizeMsg{Width: 90, Height: terminalHeight})
350+
351+
assert.Greater(t, m.bodyHeight(), initialVisibleRows)
352+
assert.Equal(t, terminalHeight-m.chromeHeight(), m.bodyHeight())
324353
lines := len(strings.Split(m.View(), "\n"))
325-
assert.LessOrEqual(t, lines, maxVisibleRows+6, "picker must not fill a tall terminal")
326-
assert.GreaterOrEqual(t, lines, maxVisibleRows, "shows up to maxVisibleRows rows")
327-
assert.Contains(t, stripANSI(m.View()), "of 30", "shows a scroll position indicator")
354+
assert.Equal(t, terminalHeight-1, lines, "picker keeps one safety row free")
355+
assert.Contains(t, stripANSI(m.View()), "of 100", "shows a scroll position indicator")
328356
}
329357

330358
func TestView_ShrinksToShortTerminal(t *testing.T) {
331359
m := drive(New(manyRows(30)), tea.WindowSizeMsg{Width: 90, Height: 12})
332360
lines := len(strings.Split(m.View(), "\n"))
361+
assert.Equal(t, 12-m.chromeHeight(), m.bodyHeight())
333362
assert.LessOrEqual(t, lines, 12, "must fit within a short terminal")
334-
assert.Less(t, lines, maxVisibleRows+6)
335363
}
336364

337365
func TestView_ClearsOnExit(t *testing.T) {

0 commit comments

Comments
 (0)