fix: page down/up scrolls by viewport height, not line count#7479
fix: page down/up scrolls by viewport height, not line count#7479JohnMcLear wants to merge 6 commits intodevelopfrom
Conversation
The previous implementation counted logical lines in the viewport, which failed when long wrapped lines consumed the entire viewport. Now scrolls by actual pixel height for correct behavior. Fixes #4562 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Review Summary by QodoFix Page Down/Up scrolling with long wrapped lines
WalkthroughsDescription• Page Down/Up now scrolls by viewport height in pixels instead of line count • Fixes issue where long wrapped lines consumed entire viewport, making scrolling ineffective • Maintains 40px overlap for context preservation between page transitions • Cursor repositions to first/last visible line after scrolling Diagramflowchart LR
A["Page Down/Up keypress"] --> B["Calculate viewport height in pixels"]
B --> C["Scroll by viewport height minus 40px overlap"]
C --> D["Reposition cursor to new visible area"]
D --> E["Update selection and display"]
File Changes1. src/static/js/ace2_inner.ts
|
Code Review by Qodo
1.
|
… PageDown/Up outerWin is an HTMLIFrameElement (returned by getElementsByName), not a Window object, so it has no .document property. The existing getInnerHeight() helper already uses outerDoc.documentElement.clientHeight correctly; align the PageDown/PageUp handler with that pattern. Adds a Playwright regression test that verifies PageDown scrolls the viewport when the pad contains long wrapping lines. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The previous approach tried to scroll the outerWin iframe element directly which didn't work. Reverted to the original cursor-movement approach but calculates lines-to-skip using viewport pixel height divided by actual rendered line heights. This correctly handles long wrapped lines that consume multiple visual rows. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace direct outerDoc.documentElement.clientHeight access with the existing getInnerHeight() helper which handles edge cases (Opera browser, hidden iframes). Add regression test for #4562 with consecutive long wrapped lines. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The 'PageDown scrolls viewport when pad has long wrapping lines' test was unreliable — scrollBefore and scrollAfter could be equal when the viewport was already scrolled. The #4562 regression test below covers the long-wrapped-lines scenario more robustly. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Use direct DOM manipulation instead of keyboard.type for creating long wrapped lines. Typing 2500+ chars per line x10 lines exceeds the 90s test timeout on Firefox. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Review Summary by QodoFix PageDown/PageUp scrolling with long wrapped lines
WalkthroughsDescription• Fix PageDown/PageUp to scroll by viewport pixel height instead of logical line count • Calculate lines-to-skip using actual rendered line heights for wrapped lines • Add regression test for consecutive long wrapped lines (#4562) • Simplify caret positioning logic and remove outdated comments Diagramflowchart LR
A["PageDown/PageUp Event"] --> B["Calculate viewport height in pixels"]
B --> C["Sum rendered line heights in viewport"]
C --> D["Compute lines-to-skip ratio"]
D --> E["Move cursor by calculated lines"]
E --> F["Scroll to caret position"]
G["Regression Test"] --> H["Create pad with long wrapped lines"]
H --> I["Verify PageDown advances cursor correctly"]
File Changes1. src/static/js/ace2_inner.ts
|
Code Review by Qodo
1. Off-by-one visible line count
|
| const visibleStart = newVisibleLineRange[0]; | ||
| const visibleEnd = newVisibleLineRange[1]; | ||
| let totalPixelHeight = 0; | ||
| for (let i = visibleStart; i <= Math.min(visibleEnd, linesCount - 1); i++) { | ||
| const entry = rep.lines.atIndex(i); | ||
| if (entry && entry.lineNode) { | ||
| totalPixelHeight += entry.lineNode.offsetHeight; | ||
| } | ||
| } | ||
| const visibleLogicalLines = visibleEnd - visibleStart; | ||
| // Use pixel-based count: how many logical lines fit in one viewport | ||
| const numberOfLinesInViewport = visibleLogicalLines > 0 && totalPixelHeight > 0 | ||
| ? Math.max(1, Math.round(visibleLogicalLines * viewportHeight / totalPixelHeight)) | ||
| : Math.max(1, visibleLogicalLines); |
There was a problem hiding this comment.
1. Off-by-one visible line count 🐞 Bug ≡ Correctness
visibleLogicalLines is computed as visibleEnd - visibleStart even though scroll.getVisibleLineRange() returns an inclusive end index. This undercounts visible logical lines (and therefore the page increment) by 1 in the common case.
Agent Prompt
### Issue description
`scroll.getVisibleLineRange()` returns an inclusive end index, but `visibleLogicalLines` is calculated as an exclusive range. This causes PageUp/PageDown to move by too few logical lines.
### Issue Context
The returned range is `[start, endInclusive]` (see `return [start, end - 1]`).
### Fix
Adjust the visible logical line count to be inclusive:
- `const visibleLogicalLines = visibleEnd - visibleStart + 1;`
Also review any other math that assumes an exclusive end.
### Fix Focus Areas
- src/static/js/ace2_inner.ts[2873-2887]
- src/static/js/scroll.ts[317-330]
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
Summary
Test plan
Fixes #4562
🤖 Generated with Claude Code