Fix mobile terminal selection autoscroll (#3343)

This commit is contained in:
Neil 2026-05-29 18:53:17 -07:00 committed by GitHub
parent 4c69fa605f
commit 8ace23b6c8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 45 additions and 6 deletions

View File

@ -882,6 +882,8 @@ const XTERM_HTML = `<!DOCTYPE html>
var longPressOrigin = null; // {x,y, identifier}
var edgeScrollTimer = null;
var edgeScrollDir = 0;
var edgeScrollClientX = 0;
var edgeScrollClientY = 0;
// Eviction watchdog: linesEverWritten counts onLineFeed since last init.
// Once buffer is full, every onLineFeed evicts the top row in xterm and
@ -1370,10 +1372,30 @@ const XTERM_HTML = `<!DOCTYPE html>
selMenu.style.left = clampedLeft + 'px';
}
function syncSelectionHandleToViewportPoint(handle, clientX, clientY) {
var c = viewportToCell(clientX, clientY);
if (!c || !sel) return false;
if (handle === 'start') sel.anchor = c;
else sel.focus = c;
applyXtermSelection();
return true;
}
function syncEdgeScrollSelectionEndpoint() {
if (!sel || !sel.activeHandle) return false;
// Why: WebView may not emit new touchmove events while a handle is held
// at the edge; resample the stored finger point after each viewport scroll.
return syncSelectionHandleToViewportPoint(
sel.activeHandle,
edgeScrollClientX,
edgeScrollClientY
);
}
function startEdgeScroll(dir) {
if (edgeScrollDir === dir) return;
edgeScrollDir = dir;
stopEdgeScroll();
edgeScrollDir = dir;
edgeScrollTimer = setInterval(function() {
if (!term || edgeScrollDir === 0) return;
var beforeY = term.buffer.active.viewportY;
@ -1384,6 +1406,7 @@ const XTERM_HTML = `<!DOCTYPE html>
stopEdgeScroll();
return;
}
syncEdgeScrollSelectionEndpoint();
repositionOverlay();
}, EDGE_SCROLL_INTERVAL);
}
@ -1397,11 +1420,9 @@ const XTERM_HTML = `<!DOCTYPE html>
}
function handleDragMove(handle, clientX, clientY) {
var c = viewportToCell(clientX, clientY);
if (!c || !sel) return;
if (handle === 'start') sel.anchor = c;
else sel.focus = c;
applyXtermSelection();
edgeScrollClientX = clientX;
edgeScrollClientY = clientY;
if (!syncSelectionHandleToViewportPoint(handle, clientX, clientY)) return;
repositionOverlay();
if (clientY < EDGE_SCROLL_PX) startEdgeScroll(-1);
else if (clientY > window.innerHeight - EDGE_SCROLL_PX) startEdgeScroll(1);

View File

@ -107,4 +107,22 @@ describe('TerminalWebView scroll routing', () => {
expect(source).toContain('var FRICTION = 0.972;')
expect(source).toContain('var MIN_VEL = 0.012;')
})
it('keeps selection edge autoscroll active and extends the dragged endpoint', () => {
const startBlock = sliceBetween('function startEdgeScroll(dir)', 'function stopEdgeScroll()')
expect(startBlock.indexOf('stopEdgeScroll();')).toBeLessThan(
startBlock.indexOf('edgeScrollDir = dir;')
)
expect(startBlock.indexOf('term.scrollLines(edgeScrollDir);')).toBeLessThan(
startBlock.indexOf('syncEdgeScrollSelectionEndpoint();')
)
const dragMoveBlock = sliceBetween(
'function handleDragMove(handle, clientX, clientY)',
' // ============================================================\n // LATCHING TOUCH DISPATCHER'
)
expect(dragMoveBlock).toContain('edgeScrollClientX = clientX;')
expect(dragMoveBlock).toContain('edgeScrollClientY = clientY;')
expect(dragMoveBlock).toContain('syncSelectionHandleToViewportPoint(handle, clientX, clientY)')
})
})