mirror of
https://github.com/stablyai/orca.git
synced 2026-09-21 16:02:20 +00:00
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.
This commit is contained in:
@@ -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. */
|
||||
|
||||
@@ -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 }
|
||||
|
||||
+1
-1
@@ -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 = `<!DOCTYPE html>
|
||||
export const TERMINAL_HTML_DOCUMENT_SHELL = `<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
+2
-1
@@ -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;
|
||||
@@ -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);
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>`
|
||||
+1
-42
@@ -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.
|
||||
`
|
||||
@@ -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);
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>`
|
||||
+79
-95
@@ -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);
|
||||
}
|
||||
|
||||
`
|
||||
+2
-1
@@ -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');
|
||||
+2
-1
@@ -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) {
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
`
|
||||
+1
-79
@@ -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) {
|
||||
+91
-1
@@ -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);
|
||||
|
||||
`
|
||||
@@ -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();
|
||||
});
|
||||
}
|
||||
|
||||
`
|
||||
+1
-1
@@ -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;
|
||||
@@ -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'
|
||||
|
||||
@@ -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'")
|
||||
|
||||
Reference in New Issue
Block a user