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