* Render a slice of rows from the frame's screen. * Each row is rendered followed by a newline. Cursor ends at (0, endY).
( screen: VirtualScreen, frame: Frame, startY: number, endY: number, stylePool: StylePool, )
| 525 | * Each row is rendered followed by a newline. Cursor ends at (0, endY). |
| 526 | */ |
| 527 | function renderFrameSlice( |
| 528 | screen: VirtualScreen, |
| 529 | frame: Frame, |
| 530 | startY: number, |
| 531 | endY: number, |
| 532 | stylePool: StylePool, |
| 533 | ): VirtualScreen { |
| 534 | let currentStyleId = stylePool.none |
| 535 | let currentHyperlink: Hyperlink = undefined |
| 536 | // Track the styleId of the last rendered cell on this line (-1 if none). |
| 537 | // Passed to visibleCellAtIndex to enable fg-only space optimization. |
| 538 | let lastRenderedStyleId = -1 |
| 539 | |
| 540 | const { width: screenWidth, cells, charPool, hyperlinkPool } = frame.screen |
| 541 | |
| 542 | let index = startY * screenWidth |
| 543 | for (let y = startY; y < endY; y += 1) { |
| 544 | // Advance cursor to this row using LF (not CSI CUD / cursor-down). |
| 545 | // CSI CUD stops at the viewport bottom margin and cannot scroll, |
| 546 | // but LF scrolls the viewport to create new lines. Without this, |
| 547 | // when the cursor is at the viewport bottom, moveCursorTo's |
| 548 | // cursor-down silently fails, creating a permanent off-by-one |
| 549 | // between the virtual cursor and the real terminal cursor. |
| 550 | if (screen.cursor.y < y) { |
| 551 | const rowsToAdvance = y - screen.cursor.y |
| 552 | screen.txn(prev => { |
| 553 | const patches: Diff = new Array<Diff[number]>(1 + rowsToAdvance) |
| 554 | patches[0] = CARRIAGE_RETURN |
| 555 | for (let i = 0; i < rowsToAdvance; i++) { |
| 556 | patches[1 + i] = NEWLINE |
| 557 | } |
| 558 | return [patches, { dx: -prev.x, dy: rowsToAdvance }] |
| 559 | }) |
| 560 | } |
| 561 | // Reset at start of each line — no cell rendered yet |
| 562 | lastRenderedStyleId = -1 |
| 563 | |
| 564 | for (let x = 0; x < screenWidth; x += 1, index += 1) { |
| 565 | // Skip spacers, unstyled empty cells, and fg-only styled spaces that |
| 566 | // match the last rendered style (since cursor-forward produces identical |
| 567 | // visual result). visibleCellAtIndex handles the optimization internally |
| 568 | // to avoid allocating Cell objects for skipped cells. |
| 569 | const cell = visibleCellAtIndex( |
| 570 | cells, |
| 571 | charPool, |
| 572 | hyperlinkPool, |
| 573 | index, |
| 574 | lastRenderedStyleId, |
| 575 | ) |
| 576 | if (!cell) { |
| 577 | continue |
| 578 | } |
| 579 | |
| 580 | moveCursorTo(screen, x, y) |
| 581 | |
| 582 | // Handle hyperlink |
| 583 | const targetHyperlink = cell.hyperlink |
| 584 | currentHyperlink = transitionHyperlink( |
no test coverage detected