From de8aaac34404d7de75b4f84270ffd711e14510b2 Mon Sep 17 00:00:00 2001 From: Neil <4138956+nwparker@users.noreply.github.com> Date: Tue, 1 Sep 2026 03:30:51 -0700 Subject: [PATCH] refactor(mobile): name terminal WebView modules for their contents fragment-01..10 were arbitrary line-count slices of one template literal. Two seams fell mid-expression -- inside buildMouseClickInput and inside the touchmove listener -- so those pieces had no identity to name. Re-splits at real statement boundaries and names each for what it holds. The composed output is byte-identical: sha256 42cc000f..., 729776 bytes, verified before, after the regroup, and after formatting. Also fixes two ratchet tests that read fragment paths directly, one of which duplicated the composer's file list. --- ...rminal-webview-html-source.test-support.ts | 16 +- mobile/src/terminal/terminal-webview-html.ts | 48 ++--- .../{fragment-01.ts => document-shell.ts} | 2 +- ...ent-03.ts => fit-scale-and-write-queue.ts} | 3 +- .../terminal-webview-html/fragment-10.ts | 132 ------------- ...{fragment-05.ts => host-message-router.ts} | 43 +---- .../message-bridge-and-document-close.ts | 43 +++++ ....ts => mouse-report-and-scroll-routing.ts} | 174 ++++++++---------- ...2.ts => runtime-state-and-text-scaling.ts} | 3 +- .../{fragment-08.ts => selection-overlay.ts} | 3 +- .../selection-state-and-eviction.ts | 71 +++++++ ....ts => smooth-scroll-and-cell-geometry.ts} | 80 +------- ...agment-09.ts => surface-touch-gestures.ts} | 92 ++++++++- .../term-observers-and-mode-mirroring.ts | 67 +++++++ ...gment-04.ts => terminal-init-and-write.ts} | 2 +- .../terminal-webview-scroll-routing.test.ts | 12 +- .../terminal-webview-text-zoom.test.ts | 6 +- 17 files changed, 405 insertions(+), 392 deletions(-) rename mobile/src/terminal/terminal-webview-html/{fragment-01.ts => document-shell.ts} (98%) rename mobile/src/terminal/terminal-webview-html/{fragment-03.ts => fit-scale-and-write-queue.ts} (98%) delete mode 100644 mobile/src/terminal/terminal-webview-html/fragment-10.ts rename mobile/src/terminal/terminal-webview-html/{fragment-05.ts => host-message-router.ts} (77%) create mode 100644 mobile/src/terminal/terminal-webview-html/message-bridge-and-document-close.ts rename mobile/src/terminal/terminal-webview-html/{fragment-06.ts => mouse-report-and-scroll-routing.ts} (56%) rename mobile/src/terminal/terminal-webview-html/{fragment-02.ts => runtime-state-and-text-scaling.ts} (97%) rename mobile/src/terminal/terminal-webview-html/{fragment-08.ts => selection-overlay.ts} (97%) create mode 100644 mobile/src/terminal/terminal-webview-html/selection-state-and-eviction.ts rename mobile/src/terminal/terminal-webview-html/{fragment-07.ts => smooth-scroll-and-cell-geometry.ts} (54%) rename mobile/src/terminal/terminal-webview-html/{fragment-09.ts => surface-touch-gestures.ts} (62%) create mode 100644 mobile/src/terminal/terminal-webview-html/term-observers-and-mode-mirroring.ts rename mobile/src/terminal/terminal-webview-html/{fragment-04.ts => terminal-init-and-write.ts} (98%) diff --git a/mobile/src/terminal/terminal-webview-html-source.test-support.ts b/mobile/src/terminal/terminal-webview-html-source.test-support.ts index 2491591572f..25902305f41 100644 --- a/mobile/src/terminal/terminal-webview-html-source.test-support.ts +++ b/mobile/src/terminal/terminal-webview-html-source.test-support.ts @@ -2,10 +2,18 @@ import { readFileSync } from 'node:fs' const SOURCE_FILES = [ './terminal-webview-html.ts', - ...Array.from( - { length: 10 }, - (_, index) => `./terminal-webview-html/fragment-${String(index + 1).padStart(2, '0')}.ts` - ) + './terminal-webview-html/document-shell.ts', + './terminal-webview-html/runtime-state-and-text-scaling.ts', + './terminal-webview-html/fit-scale-and-write-queue.ts', + './terminal-webview-html/terminal-init-and-write.ts', + './terminal-webview-html/host-message-router.ts', + './terminal-webview-html/selection-state-and-eviction.ts', + './terminal-webview-html/term-observers-and-mode-mirroring.ts', + './terminal-webview-html/mouse-report-and-scroll-routing.ts', + './terminal-webview-html/smooth-scroll-and-cell-geometry.ts', + './terminal-webview-html/selection-overlay.ts', + './terminal-webview-html/surface-touch-gestures.ts', + './terminal-webview-html/message-bridge-and-document-close.ts' ] as const /** Reads the TypeScript source that assembles the in-WebView document. */ diff --git a/mobile/src/terminal/terminal-webview-html.ts b/mobile/src/terminal/terminal-webview-html.ts index b55421bbe4d..40b7a2db22c 100644 --- a/mobile/src/terminal/terminal-webview-html.ts +++ b/mobile/src/terminal/terminal-webview-html.ts @@ -1,28 +1,34 @@ -import { TERMINAL_HTML_FRAGMENT_01 } from './terminal-webview-html/fragment-01' -import { TERMINAL_HTML_FRAGMENT_02 } from './terminal-webview-html/fragment-02' -import { TERMINAL_HTML_FRAGMENT_03 } from './terminal-webview-html/fragment-03' -import { TERMINAL_HTML_FRAGMENT_04 } from './terminal-webview-html/fragment-04' -import { TERMINAL_HTML_FRAGMENT_05 } from './terminal-webview-html/fragment-05' -import { TERMINAL_HTML_FRAGMENT_06 } from './terminal-webview-html/fragment-06' -import { TERMINAL_HTML_FRAGMENT_07 } from './terminal-webview-html/fragment-07' -import { TERMINAL_HTML_FRAGMENT_08 } from './terminal-webview-html/fragment-08' -import { TERMINAL_HTML_FRAGMENT_09 } from './terminal-webview-html/fragment-09' -import { TERMINAL_HTML_FRAGMENT_10 } from './terminal-webview-html/fragment-10' +import { TERMINAL_HTML_DOCUMENT_SHELL } from './terminal-webview-html/document-shell' +import { TERMINAL_HTML_RUNTIME_STATE_AND_TEXT_SCALING } from './terminal-webview-html/runtime-state-and-text-scaling' +import { TERMINAL_HTML_FIT_SCALE_AND_WRITE_QUEUE } from './terminal-webview-html/fit-scale-and-write-queue' +import { TERMINAL_HTML_INIT_AND_WRITE } from './terminal-webview-html/terminal-init-and-write' +import { TERMINAL_HTML_HOST_MESSAGE_ROUTER } from './terminal-webview-html/host-message-router' +import { TERMINAL_HTML_SELECTION_STATE_AND_EVICTION } from './terminal-webview-html/selection-state-and-eviction' +import { TERMINAL_HTML_OBSERVERS_AND_MODE_MIRRORING } from './terminal-webview-html/term-observers-and-mode-mirroring' +import { TERMINAL_HTML_MOUSE_REPORT_AND_SCROLL_ROUTING } from './terminal-webview-html/mouse-report-and-scroll-routing' +import { TERMINAL_HTML_SMOOTH_SCROLL_AND_CELL_GEOMETRY } from './terminal-webview-html/smooth-scroll-and-cell-geometry' +import { TERMINAL_HTML_SELECTION_OVERLAY } from './terminal-webview-html/selection-overlay' +import { TERMINAL_HTML_SURFACE_TOUCH_GESTURES } from './terminal-webview-html/surface-touch-gestures' +import { TERMINAL_HTML_MESSAGE_BRIDGE_AND_DOCUMENT_CLOSE } from './terminal-webview-html/message-bridge-and-document-close' export { MOBILE_TERMINAL_CARET_OPTIONS } from './terminal-webview-html/theme' -// Why: keep the document source stable while each script/style concern remains independently reviewable. +// Why: keep the document source stable while each script/style concern remains independently +// reviewable. Boundaries can only fall where the emitted document allows, so a few modules +// carry a second concern noted at the top of the file. export const XTERM_HTML = [ - TERMINAL_HTML_FRAGMENT_01, - TERMINAL_HTML_FRAGMENT_02, - TERMINAL_HTML_FRAGMENT_03, - TERMINAL_HTML_FRAGMENT_04, - TERMINAL_HTML_FRAGMENT_05, - TERMINAL_HTML_FRAGMENT_06, - TERMINAL_HTML_FRAGMENT_07, - TERMINAL_HTML_FRAGMENT_08, - TERMINAL_HTML_FRAGMENT_09, - TERMINAL_HTML_FRAGMENT_10 + TERMINAL_HTML_DOCUMENT_SHELL, + TERMINAL_HTML_RUNTIME_STATE_AND_TEXT_SCALING, + TERMINAL_HTML_FIT_SCALE_AND_WRITE_QUEUE, + TERMINAL_HTML_INIT_AND_WRITE, + TERMINAL_HTML_HOST_MESSAGE_ROUTER, + TERMINAL_HTML_SELECTION_STATE_AND_EVICTION, + TERMINAL_HTML_OBSERVERS_AND_MODE_MIRRORING, + TERMINAL_HTML_MOUSE_REPORT_AND_SCROLL_ROUTING, + TERMINAL_HTML_SMOOTH_SCROLL_AND_CELL_GEOMETRY, + TERMINAL_HTML_SELECTION_OVERLAY, + TERMINAL_HTML_SURFACE_TOUCH_GESTURES, + TERMINAL_HTML_MESSAGE_BRIDGE_AND_DOCUMENT_CLOSE ].join('') export const XTERM_WEBVIEW_SOURCE = { html: XTERM_HTML } diff --git a/mobile/src/terminal/terminal-webview-html/fragment-01.ts b/mobile/src/terminal/terminal-webview-html/document-shell.ts similarity index 98% rename from mobile/src/terminal/terminal-webview-html/fragment-01.ts rename to mobile/src/terminal/terminal-webview-html/document-shell.ts index 77dba06232a..d6e733cdd77 100644 --- a/mobile/src/terminal/terminal-webview-html/fragment-01.ts +++ b/mobile/src/terminal/terminal-webview-html/document-shell.ts @@ -1,7 +1,7 @@ import { colors } from '../../theme/mobile-theme' import { XTERM_ENGINE_CSS, XTERM_ENGINE_JS } from '../terminal-webview-engine.generated' -export const TERMINAL_HTML_FRAGMENT_01 = ` +export const TERMINAL_HTML_DOCUMENT_SHELL = ` diff --git a/mobile/src/terminal/terminal-webview-html/fragment-03.ts b/mobile/src/terminal/terminal-webview-html/fit-scale-and-write-queue.ts similarity index 98% rename from mobile/src/terminal/terminal-webview-html/fragment-03.ts rename to mobile/src/terminal/terminal-webview-html/fit-scale-and-write-queue.ts index 075ee819ec6..074185d43a5 100644 --- a/mobile/src/terminal/terminal-webview-html/fragment-03.ts +++ b/mobile/src/terminal/terminal-webview-html/fit-scale-and-write-queue.ts @@ -1,6 +1,7 @@ import { TERMINAL_WEBVIEW_THEME_JS } from '../terminal-webview-theme-injected' -export const TERMINAL_HTML_FRAGMENT_03 = `${TERMINAL_WEBVIEW_THEME_JS} +// Also carries the DECSET mouse-mode scanner: emitted-document order pins it between these two concerns. +export const TERMINAL_HTML_FIT_SCALE_AND_WRITE_QUEUE = `${TERMINAL_WEBVIEW_THEME_JS} function getCellHeight() { if (!term || !term._core) return 15; diff --git a/mobile/src/terminal/terminal-webview-html/fragment-10.ts b/mobile/src/terminal/terminal-webview-html/fragment-10.ts deleted file mode 100644 index d461f4a2039..00000000000 --- a/mobile/src/terminal/terminal-webview-html/fragment-10.ts +++ /dev/null @@ -1,132 +0,0 @@ -export const TERMINAL_HTML_FRAGMENT_10 = ` updateTransform(); - } - - var deltaY = ts.lastY - y; - ts.lastTime = now; - if (shouldRouteScrollToTerminalInput()) { - updateTouchVelocity(deltaY, dt); - resetSmoothScrollOffset(); - var effectiveCellH = getCellHeight() * getTotalScale(); - ts.accumDelta += deltaY; - var lines = Math.trunc(ts.accumDelta / effectiveCellH); - if (lines !== 0) { - ts.accumDelta -= lines * effectiveCellH; - routeScrollLines(lines, x, y); - } - } else { - if (enqueueNormalBufferScrollDelta(deltaY)) { - updateTouchVelocity(deltaY, dt); - } else { - ts.velY = 0; - } - } - ts.lastX = x; - ts.lastY = y; - } - }, { capture: true, passive: false }); - - targetSurface.addEventListener('touchend', function(e) { - if (dispatcherShouldBlockSurface()) return; - if (!term) return; - - if (ts.isPinching && e.touches.length < 2) { - ts.isPinching = false; - // Why: a finished pinch snaps to the nearest preset and becomes the new - // font size (reflowing the grid), so pinch-to-zoom IS the in-terminal way - // to set the text size. The CSS pinch zoom (userScale) is reset; the real - // size change reflows columns and RN persists + resizes the PTY to match. - var target = snapToTextScalePreset(currentTextScale * userScale); - var changed = target !== currentTextScale; - userScale = 1; - panX = 0; panY = 0; - applyTextScale(target); - updateTransform(); - notify({ type: 'font-scale-changed', fontScale: target }); - if (changed) notify({ type: 'haptic', kind: 'selection' }); - if (e.touches.length === 1) { - ts.lastX = e.touches[0].clientX; - ts.lastY = e.touches[0].clientY; - ts.lastTime = Date.now(); - ts.velY = 0; - ts.accumDelta = 0; - } - return; - } - - if (e.touches.length === 0) { - var vel = ts.velY; - var FRICTION = 0.972; - var MIN_VEL = 0.012; - function momentumStep() { - vel *= FRICTION; - if (Math.abs(vel) < MIN_VEL) { ts.momentumId = null; return; } - var delta = vel * 16; - if (shouldRouteScrollToTerminalInput()) { - resetSmoothScrollOffset(); - var effectiveCellH = getCellHeight() * getTotalScale(); - ts.accumDelta += delta; - var lines = Math.trunc(ts.accumDelta / effectiveCellH); - if (lines !== 0) { - ts.accumDelta -= lines * effectiveCellH; - routeScrollLines(lines, ts.lastX, ts.lastY); - } - } else { - if (!applyNormalBufferScrollDelta(delta)) { - ts.momentumId = null; - return; - } - } - ts.momentumId = requestAnimationFrame(momentumStep); - } - if (Math.abs(vel) > MIN_VEL) { - ts.momentumId = requestAnimationFrame(momentumStep); - } - } - }, { capture: true, passive: true }); - } - - attachSurfaceEventHandlers(surface); - - function handleIncomingMessage(e) { - var msg; - try { - msg = typeof e.data === 'string' ? JSON.parse(e.data) : e.data; - } catch (ex) { - return; - } - try { - handleMsg(msg); - } catch(ex) { - reportEngineError( - msg && msg.type === 'init' ? 'terminal init failed' : 'terminal message failed', - ex, - msg && msg.type === 'init' && !everReady - ); - } - } - - window.addEventListener('message', handleIncomingMessage); - - document.addEventListener('message', handleIncomingMessage); - - window.addEventListener('resize', function() { - // Why: viewport changed (keyboard open/close, orientation, RN container - // size update). Re-fit so the scale matches the new vpWidth — without - // this, opening the keyboard leaves the terminal at the old scale even - // though there's now less vertical room and the fit ratio may differ. - applyFitScale('window-resize'); - adjustRowsForViewport(); - repositionOverlay(); - clampPan(); - updateTransform(); - }); - - if (window.Terminal) { - notify({ type: 'web-ready' }); - } else { - reportEngineError('terminal engine missing', 'xterm failed to load', true); - } -})(); - - -` diff --git a/mobile/src/terminal/terminal-webview-html/fragment-05.ts b/mobile/src/terminal/terminal-webview-html/host-message-router.ts similarity index 77% rename from mobile/src/terminal/terminal-webview-html/fragment-05.ts rename to mobile/src/terminal/terminal-webview-html/host-message-router.ts index c8852c50d78..29cba25df36 100644 --- a/mobile/src/terminal/terminal-webview-html/fragment-05.ts +++ b/mobile/src/terminal/terminal-webview-html/host-message-router.ts @@ -1,6 +1,6 @@ import { TERMINAL_REFLOW_JS } from '../terminal-webview-reflow-injected' -export const TERMINAL_HTML_FRAGMENT_05 = ` ${TERMINAL_REFLOW_JS} +export const TERMINAL_HTML_HOST_MESSAGE_ROUTER = ` ${TERMINAL_REFLOW_JS} function notify(msg) { if (window.ReactNativeWebView) { @@ -186,45 +186,4 @@ export const TERMINAL_HTML_FRAGMENT_05 = ` ${TERMINAL_REFLOW_JS} } } - // ============================================================ - // SELECTION MODE (long-press → handles → Copy) - // ============================================================ - var WORD_RE = /[\\p{L}\\p{N}_./:@~+=?&#%-]/u; - var LONG_PRESS_MS = 500; - var LONG_PRESS_SLOP = 10; - // Why: a tap that opens a link/path must survive small finger jitter. The - // long-press slop (10px) only cancels the press-to-select timer; reusing it - // to gate the tap dropped any URL/file tap that wandered >10px — at fit scale - // a few screen px of jitter is a normal tap. Use a wider, time-bounded tap - // window so deliberate scrolls/pans still don't fire a tap. - var TAP_SLOP = 24; - var TAP_MAX_MS = 700; - var EDGE_SCROLL_PX = 40; - var EDGE_SCROLL_INTERVAL = 60; - - var selectionOverlay = document.getElementById('selection-overlay'); - var handleStart = document.getElementById('sel-handle-start'); - var handleEnd = document.getElementById('sel-handle-end'); - var selMenu = document.getElementById('sel-menu'); - var btnCopy = document.getElementById('sel-menu-copy'); - var btnSelAll = document.getElementById('sel-menu-all'); - - // mode: 'navigate' | 'select' - var selMode = 'navigate'; - var sel = null; // { anchor:{col,row}, focus:{col,row}, activeHandle:null|'start'|'end' } - var longPressTimer = null; - var longPressOrigin = null; // {x,y, identifier} - // Why: tap detection is tracked separately from the long-press timer so a - // small jitter that cancels the press-to-select timer does not also cancel - // the tap (which opens links/paths). {x,y,t,identifier} or null once the - // gesture is disqualified as a tap (moved too far or held too long). - var tapCandidate = null; - 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 - // we mirror that by decrementing stored absolute rows. ` diff --git a/mobile/src/terminal/terminal-webview-html/message-bridge-and-document-close.ts b/mobile/src/terminal/terminal-webview-html/message-bridge-and-document-close.ts new file mode 100644 index 00000000000..49cd3b4d84f --- /dev/null +++ b/mobile/src/terminal/terminal-webview-html/message-bridge-and-document-close.ts @@ -0,0 +1,43 @@ +export const TERMINAL_HTML_MESSAGE_BRIDGE_AND_DOCUMENT_CLOSE = ` function handleIncomingMessage(e) { + var msg; + try { + msg = typeof e.data === 'string' ? JSON.parse(e.data) : e.data; + } catch (ex) { + return; + } + try { + handleMsg(msg); + } catch(ex) { + reportEngineError( + msg && msg.type === 'init' ? 'terminal init failed' : 'terminal message failed', + ex, + msg && msg.type === 'init' && !everReady + ); + } + } + + window.addEventListener('message', handleIncomingMessage); + + document.addEventListener('message', handleIncomingMessage); + + window.addEventListener('resize', function() { + // Why: viewport changed (keyboard open/close, orientation, RN container + // size update). Re-fit so the scale matches the new vpWidth — without + // this, opening the keyboard leaves the terminal at the old scale even + // though there's now less vertical room and the fit ratio may differ. + applyFitScale('window-resize'); + adjustRowsForViewport(); + repositionOverlay(); + clampPan(); + updateTransform(); + }); + + if (window.Terminal) { + notify({ type: 'web-ready' }); + } else { + reportEngineError('terminal engine missing', 'xterm failed to load', true); + } +})(); + + +` diff --git a/mobile/src/terminal/terminal-webview-html/fragment-06.ts b/mobile/src/terminal/terminal-webview-html/mouse-report-and-scroll-routing.ts similarity index 56% rename from mobile/src/terminal/terminal-webview-html/fragment-06.ts rename to mobile/src/terminal/terminal-webview-html/mouse-report-and-scroll-routing.ts index 1a03331df34..3b7e7f24cf3 100644 --- a/mobile/src/terminal/terminal-webview-html/fragment-06.ts +++ b/mobile/src/terminal/terminal-webview-html/mouse-report-and-scroll-routing.ts @@ -1,100 +1,6 @@ -import { TERMINAL_KEYBOARD_AVOIDANCE_METRICS_JS } from '../terminal-keyboard-avoidance-metrics-injected' import { TERMINAL_MOUSE_REPORT_CELL_JS } from '../terminal-webview-mouse-report-cell-injected' -export const TERMINAL_HTML_FRAGMENT_06 = ` var linesEverWritten = 0; - - function resetEvictionCounter() { linesEverWritten = 0; } - - function isBufferFull() { - if (!term) return false; - return linesEverWritten >= 5000 + (term.rows || 0); - } - - function checkEviction() { - if (selMode !== 'select' || !sel) return; - var oldest = Math.min(sel.anchor.row, sel.focus.row); - if (oldest < 0) { - notify({ type: 'selection-evicted' }); - cancelSelect(); - } - } - - function logFeedAndEvict() { - linesEverWritten++; - if (initialOscLinkEvictionReady && isBufferFull()) initialOscLinkRowOffset += 1; - if (selMode === 'select' && sel && isBufferFull()) { - sel.anchor.row -= 1; - sel.focus.row -= 1; - checkEviction(); - repositionOverlay(); - } - } - - function emitModesIfChanged() { - if (!term) return; - var bp = !!(term.modes && term.modes.bracketedPasteMode); - var alt = false; - var mouseTrackingMode = getMouseTrackingMode(); - try { alt = term.buffer && term.buffer.active && term.buffer.active.type === 'alternate'; } catch (e) {} - if ( - bp !== lastEmittedModes.bracketedPasteMode || - alt !== lastEmittedModes.altScreen || - mouseTrackingMode !== lastEmittedModes.mouseTrackingMode || - sgrMouseMode !== lastEmittedModes.sgrMouseMode || - sgrMousePixelsMode !== lastEmittedModes.sgrMousePixelsMode - ) { - lastEmittedModes = { - bracketedPasteMode: bp, - altScreen: alt, - mouseTrackingMode: mouseTrackingMode, - sgrMouseMode: sgrMouseMode, - sgrMousePixelsMode: sgrMousePixelsMode - }; - notify({ - type: 'modes', - bracketedPasteMode: bp, - altScreen: alt, - mouseTrackingMode: mouseTrackingMode, - sgrMouseMode: sgrMouseMode, - sgrMousePixelsMode: sgrMousePixelsMode - }); - } - } - var lastEmittedModes = { - bracketedPasteMode: false, - altScreen: false, - mouseTrackingMode: 'none', - sgrMouseMode: false, - sgrMousePixelsMode: false - }; - - ${TERMINAL_KEYBOARD_AVOIDANCE_METRICS_JS} - - function attachTermObservers() { - if (!term) return; - disposeTermObservers(); - try { termObserverDisposables.push(term.onLineFeed(logFeedAndEvict)); } catch (e) {} - try { - termObserverDisposables.push(term.onScroll(function() { updateScrollIndicator(false); })); - } catch (e) {} - // Why: emit modes on every parsed write so RN's mirror stays current - // without round-trip; covers \\x1b[?2004h/l and alt-screen toggles. - try { - if (term.onWriteParsed) { - termObserverDisposables.push(term.onWriteParsed(function() { - emitModesIfChanged(); - emitKeyboardAvoidanceMetrics(); - })); - } - } catch (e) {} - // Initial emit once buffer settles. - afterWritesDrained(function() { - emitModesIfChanged(); - emitKeyboardAvoidanceMetrics(); - }); - } - - function viewportToCell(clientX, clientY) { +export const TERMINAL_HTML_MOUSE_REPORT_AND_SCROLL_ROUTING = ` function viewportToCell(clientX, clientY) { if (!term) return null; var cellW = getCellWidth(); var cellH = getCellHeight(); @@ -201,4 +107,82 @@ export const TERMINAL_HTML_FRAGMENT_06 = ` var linesEverWritten = 0; } if (sgrMouseMode) { // Why: xterm increments zero-based mouse cells before encoding reports. + var sgrCol = cell.col + 1; + var sgrRow = cell.row + 1; + if (!isSafeSgrMouseCoordinate(sgrCol) || !isSafeSgrMouseCoordinate(sgrRow)) return ''; + var sgrPress = ESC + '[<0;' + sgrCol + ';' + sgrRow + 'M'; + if (mouseTrackingMode === 'x10') return sgrPress; + return sgrPress + ESC + '[<0;' + sgrCol + ';' + sgrRow + 'm'; + } + // Why: non-SGR click coordinates use printable ASCII bytes on the mobile + // bridge; unsafe wide-terminal cells must not turn into corrupted input. + var col = cell.col + 1 + 32; + var row = cell.row + 1 + 32; + if (col > 126 || row > 126) return ''; + var press = ESC + '[M' + String.fromCharCode(32) + String.fromCharCode(col) + String.fromCharCode(row); + if (mouseTrackingMode === 'x10') return press; + return press + ESC + '[M' + String.fromCharCode(35) + String.fromCharCode(col) + String.fromCharCode(row); + } + + function isClickMouseTrackingMode(mode) { + return mode !== 'none'; + } + + function isWheelMouseTrackingMode(mode) { + return mode !== 'none' && mode !== 'x10'; + } + + function shouldRouteScrollToTerminalInput() { + return isWheelMouseTrackingMode(getMouseTrackingMode()) || isAlternateBufferActive(); + } + + function buildMouseWheelScrollInput(lines, clientX, clientY) { + var count = Math.min(Math.abs(lines), 32); + if (count === 0) return ''; + var sequence = buildMouseWheelSequence(lines, clientX, clientY); + if (!sequence) return ''; + return repeatSequence(sequence, count); + } + + function buildTuiScrollInput(lines, clientX, clientY) { + var count = Math.min(Math.abs(lines), 32); + if (count === 0) return ''; + var mouseTrackingMode = getMouseTrackingMode(); + var sequence = ''; + if (isWheelMouseTrackingMode(mouseTrackingMode)) { + sequence = buildMouseWheelSequence(lines, clientX, clientY); + } + if (!sequence) sequence = buildArrowScrollSequence(lines); + return repeatSequence(sequence, count); + } + + function routeScrollLines(lines, clientX, clientY) { + if (!term || lines === 0) return; + var mouseTrackingMode = getMouseTrackingMode(); + var alternateBufferActive = isAlternateBufferActive(); + if (isWheelMouseTrackingMode(mouseTrackingMode)) { + // Why: xterm sends wheel events to mouse-aware TUIs before considering + // scrollback, even if the app stays on the normal buffer. + var mouseInput = buildMouseWheelScrollInput(lines, clientX, clientY); + if (mouseInput) { + notify({ type: 'terminal-input', bytes: mouseInput }); + return; + } + // Why: default mouse encoding can be unrepresentable in our ASCII-safe + // RPC path on wide terminals. Send bounded arrows instead of local + // scrollback/no-op while a mouse-aware app owns scroll gestures. + var fallbackInput = buildTuiScrollInput(lines, clientX, clientY); + if (fallbackInput) notify({ type: 'terminal-input', bytes: fallbackInput }); + return; + } + if (alternateBufferActive) { + // Why: alternate-screen TUIs own their scroll state and xterm has no + // scrollback there, so mobile scroll gestures must become terminal input. + var input = buildTuiScrollInput(lines, clientX, clientY); + if (input) notify({ type: 'terminal-input', bytes: input }); + return; + } + term.scrollLines(lines); + } + ` diff --git a/mobile/src/terminal/terminal-webview-html/fragment-02.ts b/mobile/src/terminal/terminal-webview-html/runtime-state-and-text-scaling.ts similarity index 97% rename from mobile/src/terminal/terminal-webview-html/fragment-02.ts rename to mobile/src/terminal/terminal-webview-html/runtime-state-and-text-scaling.ts index 7d25b0fb6b2..44ce1d39042 100644 --- a/mobile/src/terminal/terminal-webview-html/fragment-02.ts +++ b/mobile/src/terminal/terminal-webview-html/runtime-state-and-text-scaling.ts @@ -3,7 +3,8 @@ import { TERMINAL_SURFACE_SWAP_JS } from '../terminal-webview-surface-swap-injec import { TERMINAL_TEXT_SCALES } from '../../storage/preferences' import { DEFAULT_TERMINAL_THEME } from './theme' -export const TERMINAL_HTML_FRAGMENT_02 = ` var PRIVATE_MODE_SCAN_TAIL_LIMIT = 4096; +// Also carries the scroll-indicator painter, which reads the scale state declared here. +export const TERMINAL_HTML_RUNTIME_STATE_AND_TEXT_SCALING = ` var PRIVATE_MODE_SCAN_TAIL_LIMIT = 4096; var term = null; ${TERMINAL_QUERY_REPLY_JS} ${TERMINAL_SURFACE_SWAP_JS} var scrollIndicator = document.getElementById('scroll-indicator'); diff --git a/mobile/src/terminal/terminal-webview-html/fragment-08.ts b/mobile/src/terminal/terminal-webview-html/selection-overlay.ts similarity index 97% rename from mobile/src/terminal/terminal-webview-html/fragment-08.ts rename to mobile/src/terminal/terminal-webview-html/selection-overlay.ts index 29f0d929918..335407af51a 100644 --- a/mobile/src/terminal/terminal-webview-html/fragment-08.ts +++ b/mobile/src/terminal/terminal-webview-html/selection-overlay.ts @@ -1,7 +1,8 @@ import { TERMINAL_PATH_TAP_JS } from '../terminal-path-tap-injected' import { URL_TAP_WEBVIEW_JS } from '../terminal-webview-url-tap' -export const TERMINAL_HTML_FRAGMENT_08 = ` ${TERMINAL_PATH_TAP_JS} +// Opens with the path/url tap matchers: they land at this point in the emitted document. +export const TERMINAL_HTML_SELECTION_OVERLAY = ` ${TERMINAL_PATH_TAP_JS} ${URL_TAP_WEBVIEW_JS} function seedWordSelection(col, absRow) { diff --git a/mobile/src/terminal/terminal-webview-html/selection-state-and-eviction.ts b/mobile/src/terminal/terminal-webview-html/selection-state-and-eviction.ts new file mode 100644 index 00000000000..48c05b6ad85 --- /dev/null +++ b/mobile/src/terminal/terminal-webview-html/selection-state-and-eviction.ts @@ -0,0 +1,71 @@ +export const TERMINAL_HTML_SELECTION_STATE_AND_EVICTION = ` // ============================================================ + // SELECTION MODE (long-press → handles → Copy) + // ============================================================ + var WORD_RE = /[\\p{L}\\p{N}_./:@~+=?&#%-]/u; + var LONG_PRESS_MS = 500; + var LONG_PRESS_SLOP = 10; + // Why: a tap that opens a link/path must survive small finger jitter. The + // long-press slop (10px) only cancels the press-to-select timer; reusing it + // to gate the tap dropped any URL/file tap that wandered >10px — at fit scale + // a few screen px of jitter is a normal tap. Use a wider, time-bounded tap + // window so deliberate scrolls/pans still don't fire a tap. + var TAP_SLOP = 24; + var TAP_MAX_MS = 700; + var EDGE_SCROLL_PX = 40; + var EDGE_SCROLL_INTERVAL = 60; + + var selectionOverlay = document.getElementById('selection-overlay'); + var handleStart = document.getElementById('sel-handle-start'); + var handleEnd = document.getElementById('sel-handle-end'); + var selMenu = document.getElementById('sel-menu'); + var btnCopy = document.getElementById('sel-menu-copy'); + var btnSelAll = document.getElementById('sel-menu-all'); + + // mode: 'navigate' | 'select' + var selMode = 'navigate'; + var sel = null; // { anchor:{col,row}, focus:{col,row}, activeHandle:null|'start'|'end' } + var longPressTimer = null; + var longPressOrigin = null; // {x,y, identifier} + // Why: tap detection is tracked separately from the long-press timer so a + // small jitter that cancels the press-to-select timer does not also cancel + // the tap (which opens links/paths). {x,y,t,identifier} or null once the + // gesture is disqualified as a tap (moved too far or held too long). + var tapCandidate = null; + 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 + // we mirror that by decrementing stored absolute rows. + var linesEverWritten = 0; + + function resetEvictionCounter() { linesEverWritten = 0; } + + function isBufferFull() { + if (!term) return false; + return linesEverWritten >= 5000 + (term.rows || 0); + } + + function checkEviction() { + if (selMode !== 'select' || !sel) return; + var oldest = Math.min(sel.anchor.row, sel.focus.row); + if (oldest < 0) { + notify({ type: 'selection-evicted' }); + cancelSelect(); + } + } + + function logFeedAndEvict() { + linesEverWritten++; + if (initialOscLinkEvictionReady && isBufferFull()) initialOscLinkRowOffset += 1; + if (selMode === 'select' && sel && isBufferFull()) { + sel.anchor.row -= 1; + sel.focus.row -= 1; + checkEviction(); + repositionOverlay(); + } + } + +` diff --git a/mobile/src/terminal/terminal-webview-html/fragment-07.ts b/mobile/src/terminal/terminal-webview-html/smooth-scroll-and-cell-geometry.ts similarity index 54% rename from mobile/src/terminal/terminal-webview-html/fragment-07.ts rename to mobile/src/terminal/terminal-webview-html/smooth-scroll-and-cell-geometry.ts index 22c3372b26f..de9db152fbf 100644 --- a/mobile/src/terminal/terminal-webview-html/fragment-07.ts +++ b/mobile/src/terminal/terminal-webview-html/smooth-scroll-and-cell-geometry.ts @@ -1,82 +1,4 @@ -export const TERMINAL_HTML_FRAGMENT_07 = ` var sgrCol = cell.col + 1; - var sgrRow = cell.row + 1; - if (!isSafeSgrMouseCoordinate(sgrCol) || !isSafeSgrMouseCoordinate(sgrRow)) return ''; - var sgrPress = ESC + '[<0;' + sgrCol + ';' + sgrRow + 'M'; - if (mouseTrackingMode === 'x10') return sgrPress; - return sgrPress + ESC + '[<0;' + sgrCol + ';' + sgrRow + 'm'; - } - // Why: non-SGR click coordinates use printable ASCII bytes on the mobile - // bridge; unsafe wide-terminal cells must not turn into corrupted input. - var col = cell.col + 1 + 32; - var row = cell.row + 1 + 32; - if (col > 126 || row > 126) return ''; - var press = ESC + '[M' + String.fromCharCode(32) + String.fromCharCode(col) + String.fromCharCode(row); - if (mouseTrackingMode === 'x10') return press; - return press + ESC + '[M' + String.fromCharCode(35) + String.fromCharCode(col) + String.fromCharCode(row); - } - - function isClickMouseTrackingMode(mode) { - return mode !== 'none'; - } - - function isWheelMouseTrackingMode(mode) { - return mode !== 'none' && mode !== 'x10'; - } - - function shouldRouteScrollToTerminalInput() { - return isWheelMouseTrackingMode(getMouseTrackingMode()) || isAlternateBufferActive(); - } - - function buildMouseWheelScrollInput(lines, clientX, clientY) { - var count = Math.min(Math.abs(lines), 32); - if (count === 0) return ''; - var sequence = buildMouseWheelSequence(lines, clientX, clientY); - if (!sequence) return ''; - return repeatSequence(sequence, count); - } - - function buildTuiScrollInput(lines, clientX, clientY) { - var count = Math.min(Math.abs(lines), 32); - if (count === 0) return ''; - var mouseTrackingMode = getMouseTrackingMode(); - var sequence = ''; - if (isWheelMouseTrackingMode(mouseTrackingMode)) { - sequence = buildMouseWheelSequence(lines, clientX, clientY); - } - if (!sequence) sequence = buildArrowScrollSequence(lines); - return repeatSequence(sequence, count); - } - - function routeScrollLines(lines, clientX, clientY) { - if (!term || lines === 0) return; - var mouseTrackingMode = getMouseTrackingMode(); - var alternateBufferActive = isAlternateBufferActive(); - if (isWheelMouseTrackingMode(mouseTrackingMode)) { - // Why: xterm sends wheel events to mouse-aware TUIs before considering - // scrollback, even if the app stays on the normal buffer. - var mouseInput = buildMouseWheelScrollInput(lines, clientX, clientY); - if (mouseInput) { - notify({ type: 'terminal-input', bytes: mouseInput }); - return; - } - // Why: default mouse encoding can be unrepresentable in our ASCII-safe - // RPC path on wide terminals. Send bounded arrows instead of local - // scrollback/no-op while a mouse-aware app owns scroll gestures. - var fallbackInput = buildTuiScrollInput(lines, clientX, clientY); - if (fallbackInput) notify({ type: 'terminal-input', bytes: fallbackInput }); - return; - } - if (alternateBufferActive) { - // Why: alternate-screen TUIs own their scroll state and xterm has no - // scrollback there, so mobile scroll gestures must become terminal input. - var input = buildTuiScrollInput(lines, clientX, clientY); - if (input) notify({ type: 'terminal-input', bytes: input }); - return; - } - term.scrollLines(lines); - } - - function clampNormalScrollLines(lines) { +export const TERMINAL_HTML_SMOOTH_SCROLL_AND_CELL_GEOMETRY = ` function clampNormalScrollLines(lines) { if (!term || !term.buffer || !term.buffer.active || lines === 0) return 0; var buffer = term.buffer.active; if (lines > 0) { diff --git a/mobile/src/terminal/terminal-webview-html/fragment-09.ts b/mobile/src/terminal/terminal-webview-html/surface-touch-gestures.ts similarity index 62% rename from mobile/src/terminal/terminal-webview-html/fragment-09.ts rename to mobile/src/terminal/terminal-webview-html/surface-touch-gestures.ts index 06ffa927c65..515d7fd9d26 100644 --- a/mobile/src/terminal/terminal-webview-html/fragment-09.ts +++ b/mobile/src/terminal/terminal-webview-html/surface-touch-gestures.ts @@ -2,7 +2,8 @@ import { TERMINAL_TAP_DISPATCH_JS } from '../terminal-webview-tap-dispatch-injec import { TERMINAL_WHEEL_SCROLL_JS } from '../terminal-webview-wheel-scroll-injected' import { TERMINAL_MOUSE_CLICK_DRAG_JS } from '../terminal-webview-mouse-click-drag-injected' -export const TERMINAL_HTML_FRAGMENT_09 = ` ${TERMINAL_TAP_DISPATCH_JS} +// Also wires the selection menu's Copy/Select All buttons, which sit here in the emitted document. +export const TERMINAL_HTML_SURFACE_TOUCH_GESTURES = ` ${TERMINAL_TAP_DISPATCH_JS} // External mouse / trackpad scroll: see // terminal-webview-wheel-scroll-injected.ts (extracted for max-lines). @@ -135,4 +136,93 @@ export const TERMINAL_HTML_FRAGMENT_09 = ` ${TERMINAL_TAP_DISPATCH_JS} if (term.element && term.element.scrollWidth * getTotalScale() > window.innerWidth + 1) { panX += x - ts.lastX; clampPan(); + updateTransform(); + } + + var deltaY = ts.lastY - y; + ts.lastTime = now; + if (shouldRouteScrollToTerminalInput()) { + updateTouchVelocity(deltaY, dt); + resetSmoothScrollOffset(); + var effectiveCellH = getCellHeight() * getTotalScale(); + ts.accumDelta += deltaY; + var lines = Math.trunc(ts.accumDelta / effectiveCellH); + if (lines !== 0) { + ts.accumDelta -= lines * effectiveCellH; + routeScrollLines(lines, x, y); + } + } else { + if (enqueueNormalBufferScrollDelta(deltaY)) { + updateTouchVelocity(deltaY, dt); + } else { + ts.velY = 0; + } + } + ts.lastX = x; + ts.lastY = y; + } + }, { capture: true, passive: false }); + + targetSurface.addEventListener('touchend', function(e) { + if (dispatcherShouldBlockSurface()) return; + if (!term) return; + + if (ts.isPinching && e.touches.length < 2) { + ts.isPinching = false; + // Why: a finished pinch snaps to the nearest preset and becomes the new + // font size (reflowing the grid), so pinch-to-zoom IS the in-terminal way + // to set the text size. The CSS pinch zoom (userScale) is reset; the real + // size change reflows columns and RN persists + resizes the PTY to match. + var target = snapToTextScalePreset(currentTextScale * userScale); + var changed = target !== currentTextScale; + userScale = 1; + panX = 0; panY = 0; + applyTextScale(target); + updateTransform(); + notify({ type: 'font-scale-changed', fontScale: target }); + if (changed) notify({ type: 'haptic', kind: 'selection' }); + if (e.touches.length === 1) { + ts.lastX = e.touches[0].clientX; + ts.lastY = e.touches[0].clientY; + ts.lastTime = Date.now(); + ts.velY = 0; + ts.accumDelta = 0; + } + return; + } + + if (e.touches.length === 0) { + var vel = ts.velY; + var FRICTION = 0.972; + var MIN_VEL = 0.012; + function momentumStep() { + vel *= FRICTION; + if (Math.abs(vel) < MIN_VEL) { ts.momentumId = null; return; } + var delta = vel * 16; + if (shouldRouteScrollToTerminalInput()) { + resetSmoothScrollOffset(); + var effectiveCellH = getCellHeight() * getTotalScale(); + ts.accumDelta += delta; + var lines = Math.trunc(ts.accumDelta / effectiveCellH); + if (lines !== 0) { + ts.accumDelta -= lines * effectiveCellH; + routeScrollLines(lines, ts.lastX, ts.lastY); + } + } else { + if (!applyNormalBufferScrollDelta(delta)) { + ts.momentumId = null; + return; + } + } + ts.momentumId = requestAnimationFrame(momentumStep); + } + if (Math.abs(vel) > MIN_VEL) { + ts.momentumId = requestAnimationFrame(momentumStep); + } + } + }, { capture: true, passive: true }); + } + + attachSurfaceEventHandlers(surface); + ` diff --git a/mobile/src/terminal/terminal-webview-html/term-observers-and-mode-mirroring.ts b/mobile/src/terminal/terminal-webview-html/term-observers-and-mode-mirroring.ts new file mode 100644 index 00000000000..b69547affb5 --- /dev/null +++ b/mobile/src/terminal/terminal-webview-html/term-observers-and-mode-mirroring.ts @@ -0,0 +1,67 @@ +import { TERMINAL_KEYBOARD_AVOIDANCE_METRICS_JS } from '../terminal-keyboard-avoidance-metrics-injected' + +export const TERMINAL_HTML_OBSERVERS_AND_MODE_MIRRORING = ` function emitModesIfChanged() { + if (!term) return; + var bp = !!(term.modes && term.modes.bracketedPasteMode); + var alt = false; + var mouseTrackingMode = getMouseTrackingMode(); + try { alt = term.buffer && term.buffer.active && term.buffer.active.type === 'alternate'; } catch (e) {} + if ( + bp !== lastEmittedModes.bracketedPasteMode || + alt !== lastEmittedModes.altScreen || + mouseTrackingMode !== lastEmittedModes.mouseTrackingMode || + sgrMouseMode !== lastEmittedModes.sgrMouseMode || + sgrMousePixelsMode !== lastEmittedModes.sgrMousePixelsMode + ) { + lastEmittedModes = { + bracketedPasteMode: bp, + altScreen: alt, + mouseTrackingMode: mouseTrackingMode, + sgrMouseMode: sgrMouseMode, + sgrMousePixelsMode: sgrMousePixelsMode + }; + notify({ + type: 'modes', + bracketedPasteMode: bp, + altScreen: alt, + mouseTrackingMode: mouseTrackingMode, + sgrMouseMode: sgrMouseMode, + sgrMousePixelsMode: sgrMousePixelsMode + }); + } + } + var lastEmittedModes = { + bracketedPasteMode: false, + altScreen: false, + mouseTrackingMode: 'none', + sgrMouseMode: false, + sgrMousePixelsMode: false + }; + + ${TERMINAL_KEYBOARD_AVOIDANCE_METRICS_JS} + + function attachTermObservers() { + if (!term) return; + disposeTermObservers(); + try { termObserverDisposables.push(term.onLineFeed(logFeedAndEvict)); } catch (e) {} + try { + termObserverDisposables.push(term.onScroll(function() { updateScrollIndicator(false); })); + } catch (e) {} + // Why: emit modes on every parsed write so RN's mirror stays current + // without round-trip; covers \\x1b[?2004h/l and alt-screen toggles. + try { + if (term.onWriteParsed) { + termObserverDisposables.push(term.onWriteParsed(function() { + emitModesIfChanged(); + emitKeyboardAvoidanceMetrics(); + })); + } + } catch (e) {} + // Initial emit once buffer settles. + afterWritesDrained(function() { + emitModesIfChanged(); + emitKeyboardAvoidanceMetrics(); + }); + } + +` diff --git a/mobile/src/terminal/terminal-webview-html/fragment-04.ts b/mobile/src/terminal/terminal-webview-html/terminal-init-and-write.ts similarity index 98% rename from mobile/src/terminal/terminal-webview-html/fragment-04.ts rename to mobile/src/terminal/terminal-webview-html/terminal-init-and-write.ts index c2c9863be79..90dba7cbdbc 100644 --- a/mobile/src/terminal/terminal-webview-html/fragment-04.ts +++ b/mobile/src/terminal/terminal-webview-html/terminal-init-and-write.ts @@ -1,7 +1,7 @@ import { TERMINAL_WEBGL_RECOVERY_JS } from '../terminal-webview-webgl-recovery-injected' import { MOBILE_TERMINAL_CARET_OPTIONS } from './theme' -export const TERMINAL_HTML_FRAGMENT_04 = `${TERMINAL_WEBGL_RECOVERY_JS} +export const TERMINAL_HTML_INIT_AND_WRITE = `${TERMINAL_WEBGL_RECOVERY_JS} function init(cols, rows, initialData, nextTheme, nextFontScale, preserveScroll, nextOscLinks) { if (typeof nextFontScale === 'number' && nextFontScale > 0) currentTextScale = nextFontScale; diff --git a/mobile/src/terminal/terminal-webview-scroll-routing.test.ts b/mobile/src/terminal/terminal-webview-scroll-routing.test.ts index 962cfddaa4d..9218e5d6ad9 100644 --- a/mobile/src/terminal/terminal-webview-scroll-routing.test.ts +++ b/mobile/src/terminal/terminal-webview-scroll-routing.test.ts @@ -1,5 +1,6 @@ import { readFileSync } from 'node:fs' import { describe, expect, it } from 'vitest' +import { readTerminalWebViewHtmlSource } from './terminal-webview-html-source.test-support' // The in-WebView JS lives in terminal-webview-html.ts; the RN wrapper in // TerminalWebView.tsx. Concatenate both so assertions resolve regardless of file. @@ -8,16 +9,7 @@ const source = readFileSync(new URL('./terminal-webview-pending-messages.ts', import.meta.url), 'utf8') + readFileSync(new URL('./terminal-webview-url-tap.ts', import.meta.url), 'utf8') + readFileSync(new URL('./terminal-webview-tap-dispatch-injected.ts', import.meta.url), 'utf8') + - readFileSync(new URL('./terminal-webview-html.ts', import.meta.url), 'utf8') + - Array.from({ length: 10 }, (_, index) => - readFileSync( - new URL( - `./terminal-webview-html/fragment-${String(index + 1).padStart(2, '0')}.ts`, - import.meta.url - ), - 'utf8' - ) - ).join('') + readTerminalWebViewHtmlSource() const sessionSource = readFileSync( new URL('../session/use-mobile-session-terminal-input.ts', import.meta.url), 'utf8' diff --git a/mobile/src/terminal/terminal-webview-text-zoom.test.ts b/mobile/src/terminal/terminal-webview-text-zoom.test.ts index 0deee79f670..d775237ddcc 100644 --- a/mobile/src/terminal/terminal-webview-text-zoom.test.ts +++ b/mobile/src/terminal/terminal-webview-text-zoom.test.ts @@ -11,8 +11,8 @@ const terminalHtmlModuleSource = readFileSync( new URL('./terminal-webview-html.ts', import.meta.url), 'utf8' ) -const terminalHtmlFragmentSource = readFileSync( - new URL('./terminal-webview-html/fragment-01.ts', import.meta.url), +const terminalHtmlDocumentShellSource = readFileSync( + new URL('./terminal-webview-html/document-shell.ts', import.meta.url), 'utf8' ) // Read behavior from the assembled document; the module source only contains @@ -150,7 +150,7 @@ describe('TerminalWebView text zoom', () => { }) it('loads Unicode 11 before replaying mobile terminal bytes', () => { - expect(terminalHtmlFragmentSource).toContain('XTERM_ENGINE_JS') + expect(terminalHtmlDocumentShellSource).toContain('XTERM_ENGINE_JS') expect(terminalHtmlSource).toContain('window.Unicode11Addon.Unicode11Addon') const open = terminalHtmlSource.indexOf('term.open(surface)') const unicode = terminalHtmlSource.indexOf("term.unicode.activeVersion = '11'")