1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
|
package terminal
import "github.com/gdamore/tcell/v2"
// Resize changes the screen's size, keeping as much of what is on it as fits.
//
// Lines that no longer fit at the bottom go into the scrollback rather than
// being dropped, which is what makes shrinking a window and growing it again
// leave the session's history intact. Columns beyond the new width are lost;
// no terminal reflows them, and pretending otherwise would move text the
// program believes it has already placed.
func (s *Screen) Resize(width, height int) {
width, height = max(width, 1), max(height, 1)
if width == s.width && height == s.height {
return
}
previous := s.lines
s.width, s.height = width, height
dropped := rowsToDrop(len(previous), height, s.cursor.Row)
for i := range dropped {
s.remember(fitLine(previous[i], width, s.style))
}
s.lines = make([]Line, height)
for row := range height {
if source := row + dropped; source < len(previous) {
s.lines[row] = fitLine(previous[source], width, s.style)
} else {
s.lines[row] = s.blankLine()
}
}
s.cursor.Row -= dropped
s.resetScrollRegion()
s.fitCursor()
s.resizePrimary(width, height)
}
// resizePrimary keeps the grid put aside for the alternate screen the same
// size as the visible one, so that leaving the alternate screen does not
// restore a grid of the wrong shape.
func (s *Screen) resizePrimary(width, height int) {
if !s.alternate {
return
}
lines := make([]Line, height)
for row := range height {
if row < len(s.primaryLines) {
lines[row] = fitLine(s.primaryLines[row], width, s.primaryStyle)
} else {
lines[row] = blankLineOf(width, s.primaryStyle)
}
}
s.primaryLines = lines
s.primaryCursor.Row = min(s.primaryCursor.Row, height-1)
s.primaryCursor.Col = min(s.primaryCursor.Col, width-1)
}
// fitLine returns a copy of a line at a new width, padded or cut.
// rowsToDrop returns how many rows must come off the top when a screen shrinks.
//
// Only as many as it takes to keep the cursor on screen — never more.
//
// Dropping every row that no longer fits is the obvious rule and is wrong for
// the common case: a command that prints one line and exits leaves the cursor
// near the top and the rest of the screen blank, and dropping from the top then
// throws the output away while keeping the blank rows below it. That is how
// `echo TADA` in a tool window came out empty, the window having been created
// at 80×24 and resized to fit its frame a moment later.
//
// A screen that really is full behaves as before, because a cursor at the
// bottom needs exactly the rows that no longer fit to be dropped.
func rowsToDrop(previous, height, cursorRow int) int {
needed := cursorRow - height + 1
return min(max(needed, 0), max(previous-height, 0))
}
func fitLine(line Line, width int, style tcell.Style) Line {
out := blankLineOf(width, style)
copy(out, line[:min(len(line), width)])
return out
}
// blankLineOf returns an empty line of a given width.
func blankLineOf(width int, style tcell.Style) Line {
line := make(Line, width)
for i := range line {
line[i] = blank(style)
}
return line
}
|