(text: string, rows: number, cols: number)
| 20 | * wrapped height (ceil(len / cols)) until the row budget is spent. |
| 21 | */ |
| 22 | export function tailForViewport(text: string, rows: number, cols: number): string { |
| 23 | const budget = Math.max(6, (rows || 24) - 10); // room for header, input box, status, margin |
| 24 | const width = Math.max(20, cols || 80); |
| 25 | const logical = text.split('\n'); |
| 26 | let used = 0; |
| 27 | const kept: string[] = []; |
| 28 | for (let i = logical.length - 1; i >= 0; i--) { |
| 29 | const line = logical[i]; |
| 30 | const vis = Math.max(1, Math.ceil(line.length / width)); // wrapped rows for this line |
| 31 | if (used + vis > budget) { |
| 32 | // This line doesn't fully fit. A SINGLE line can wrap to more rows than the |
| 33 | // whole budget (a long unbroken line, or wrapped RTL/Persian paragraph). If we |
| 34 | // kept it whole it would push the live region past the terminal height, scroll |
| 35 | // into scrollback, and — repainted each token — oscillate. So keep only the |
| 36 | // TRAILING rows of this top line that still fit; its scrolled-off head is |
| 37 | // exactly what the terminal would have hidden anyway. (Also covers the case |
| 38 | // where nothing has been kept yet — the bottom line alone is taller than budget.) |
| 39 | const rowsLeft = budget - used; |
| 40 | if (rowsLeft > 0) kept.unshift(line.slice(line.length - rowsLeft * width)); |
| 41 | break; |
| 42 | } |
| 43 | used += vis; |
| 44 | kept.unshift(line); |
| 45 | if (used >= budget) break; |
| 46 | } |
| 47 | return kept.join('\n'); |
| 48 | } |
| 49 | |
| 50 | /** |
| 51 | * Did the terminal get SMALLER in either dimension? |
no outgoing calls
no test coverage detected