Skip to content

Commit f98f3ea

Browse files
t3dotggclaude
andcommitted
fix(web): terminal drag selection scrolls faster the farther past the edge
Co-Authored-By: Claude Opus 5.5 <[email protected]>
1 parent 066c8fc commit f98f3ea

2 files changed

Lines changed: 32 additions & 1 deletion

File tree

‎apps/web/src/terminal/ghostty/surface.test.ts‎

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
resolveTerminalMouseData,
1212
terminalLinkAtPositionWithRange,
1313
terminalContentOriginY,
14+
terminalSelectionAutoscrollRows,
1415
terminalWheelDeltaRows,
1516
GhosttyTerminalSurface,
1617
type GhosttyTerminalSurfaceOptions,
@@ -748,6 +749,21 @@ describe("terminalWheelDeltaRows", () => {
748749
});
749750
});
750751

752+
// regression: #19492 drag selection scrolled one row per tick however far past the edge
753+
describe("terminalSelectionAutoscrollRows", () => {
754+
it("scrolls more rows per tick the farther the pointer is past an edge", () => {
755+
const top = 100;
756+
const bottom = 500;
757+
expect(terminalSelectionAutoscrollRows(300, top, bottom)).toBe(0);
758+
expect(terminalSelectionAutoscrollRows(99, top, bottom)).toBe(-1);
759+
expect(terminalSelectionAutoscrollRows(75, top, bottom)).toBe(-8);
760+
expect(terminalSelectionAutoscrollRows(501, top, bottom)).toBe(1);
761+
expect(terminalSelectionAutoscrollRows(525, top, bottom)).toBe(8);
762+
expect(terminalSelectionAutoscrollRows(550, top, bottom)).toBe(15);
763+
expect(terminalSelectionAutoscrollRows(2_000, top, bottom)).toBe(15);
764+
});
765+
});
766+
751767
describe("advanceTerminalSelectionClickSequence", () => {
752768
it("recognizes stationary double and triple pointer presses without PointerEvent.detail", () => {
753769
const first = advanceTerminalSelectionClickSequence(null, {

‎apps/web/src/terminal/ghostty/surface.ts‎

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -433,6 +433,21 @@ export function terminalWheelDeltaRows(
433433
return { rows, remainder: total - rows };
434434
}
435435

436+
// Ramp from 1 to 15 rows per autoscroll tick over the first 50px past the edge, as xterm.js does.
437+
const SELECTION_AUTOSCROLL_RAMP_PX = 50;
438+
const SELECTION_AUTOSCROLL_MAX_ROWS = 15;
439+
440+
export function terminalSelectionAutoscrollRows(
441+
clientY: number,
442+
top: number,
443+
bottom: number,
444+
): number {
445+
const distance = clientY < top ? clientY - top : clientY > bottom ? clientY - bottom : 0;
446+
if (distance === 0) return 0;
447+
const ramp = Math.max(-1, Math.min(1, distance / SELECTION_AUTOSCROLL_RAMP_PX));
448+
return Math.sign(ramp) + Math.round(ramp * (SELECTION_AUTOSCROLL_MAX_ROWS - 1));
449+
}
450+
436451
function terminalWheelArrowData(rows: number, applicationCursorKeys: boolean): string {
437452
if (rows === 0) return "";
438453
const sequence =
@@ -1488,7 +1503,7 @@ export class GhosttyTerminalSurface {
14881503
const bounds = this.canvas.getBoundingClientRect();
14891504
const top = Math.max(bounds.top, 0) + CONTENT_PADDING;
14901505
const bottom = Math.min(bounds.bottom, window.innerHeight) - CONTENT_PADDING;
1491-
this.setSelectionAutoscroll(event.clientY < top ? -1 : event.clientY > bottom ? 1 : 0);
1506+
this.setSelectionAutoscroll(terminalSelectionAutoscrollRows(event.clientY, top, bottom));
14921507
const cell = this.cellAt(event.clientX, event.clientY);
14931508
if (cell.x === this.selectionEnd?.x && cell.y === this.selectionEnd.y) return;
14941509
this.extendSelectionTo(event.clientX, event.clientY);

0 commit comments

Comments
 (0)