update at 2026-03-03 22:07:05

This commit is contained in:
douboer@gmail.com
2026-03-03 22:07:05 +08:00
parent e4987a2d77
commit 7d2be3d67d
21 changed files with 251 additions and 54 deletions

2
pxterm/check_zindex.js Normal file
View File

@@ -0,0 +1,2 @@
const fs = require('fs');
console.log('Testing strategy...');

13
pxterm/clean_pointer.cjs Normal file
View File

@@ -0,0 +1,13 @@
const fs = require('fs');
let code = fs.readFileSync('src/components/TerminalPanel.vue', 'utf8');
// remove multiline logs
code = code.replace(/console\.log\(\s*\[Scroll Deep\][ \w\W]*?\);/g, '');
// remove redundant empty else blocks
code = code.replace(/\s*\} else \{\s*\}/g, '');
// remove duplicated momentum logic
code = code.replace(/(\s*\/\/ 释放时触发滚行动量\s*if \(Math\.abs\(touchScrollVelocity\) > 0\.2 && touchGateScrollLike && !hasActiveNativeSelectionInTerminal\(\)\) \{\s*touchScrollVelocity \*= 1\.35;\s*runTouchScrollMomentum\(\);\s*\})\s*\/\/ 释放时触发滚行动量\s*if \(Math\.abs\(touchScrollVelocity\) > 0\.2 && touchGateScrollLike && !hasActiveNativeSelectionInTerminal\(\)\) \{\s*touchScrollVelocity \*= 1\.35;\s*runTouchScrollMomentum\(\);\s*\}/g, '$1');
fs.writeFileSync('src/components/TerminalPanel.vue', code);

17
pxterm/clean_terminal.cjs Normal file
View File

@@ -0,0 +1,17 @@
const fs = require('fs');
let code = fs.readFileSync('src/components/TerminalPanel.vue', 'utf8');
// 1. Remove global event debug block
code = code.replace(/\/\/ \-\-\- 极致全局监听器,抓出是谁吃掉了 move \-\-\-[\s\S]*?\/\/ 用最顶级的 window capture 拦截,这样在任何人(包括 xterm 内部那些黑盒代码)前面执行\n\s*window\.addEventListener\(type, globalEventDebug, \{ capture: true, passive: false \}\);\n\s*\}\);\n/g, '');
// 2. Remove all console.log("[Scroll Deep]...")
code = code.replace(/\s*console\.log\(\s*`?\[Scroll Deep\].*?\);\n/g, '\n');
// 3. Remove useless commented out stopImmediatePropagation and preventDefault blocks left from debugging
const commentedOutRegex1 = /\s*\/\/ event\.stopImmediatePropagation\(\);/g;
code = code.replace(commentedOutRegex1, '');
const commentedOutRegex2 = /\s*\/\/ if \(event\.cancelable\) \{\n\s*\/\/ event\.preventDefault\(\); \/\/ 我们使用 JS 控制滚动,必须禁止系统原生滚动争抢\n\s*\/\/\}/g;
code = code.replace(commentedOutRegex2, '');
fs.writeFileSync('src/components/TerminalPanel.vue', code);

5
pxterm/clean_vars.cjs Normal file
View File

@@ -0,0 +1,5 @@
const fs = require('fs');
let code = fs.readFileSync('src/components/TerminalPanel.vue', 'utf8');
code = code.replace(/let touchScrollLastY = 0;\n/g, '');
code = code.replace(/let touchScrollLastTime = 0;\n/g, '');
fs.writeFileSync('src/components/TerminalPanel.vue', code);

21
pxterm/fix_bracket.js Normal file
View File

@@ -0,0 +1,21 @@
import fs from 'fs';
let code = fs.readFileSync('src/components/TerminalPanel.vue', 'utf8');
// Find:
// if (Math.abs(touchScrollVelocity) > 0.2) {
// touchScrollVelocity *= 1.35;
// runTouchScrollMomentum();
// };
// And replace with:
// if (Math.abs(touchScrollVelocity) > 0.2) {
// touchScrollVelocity *= 1.35;
// runTouchScrollMomentum();
// }
// };
code = code.replace(/if \(Math\.abs\(touchScrollVelocity\) > 0\.2\) \{\s*touchScrollVelocity \*= 1\.35;\s*runTouchScrollMomentum\(\);\s*\};/g, 'if (Math.abs(touchScrollVelocity) > 0.2) {\n touchScrollVelocity *= 1.35;\n runTouchScrollMomentum();\n }\n };');
// Also check pointerUp
code = code.replace(/if \(Math\.abs\(touchScrollVelocity\) > 0\.2 && touchGateScrollLike && !hasActiveNativeSelectionInTerminal\(\)\) \{\s*touchScrollVelocity \*= 1\.35;\s*runTouchScrollMomentum\(\);\s*const context = \{/g, 'if (Math.abs(touchScrollVelocity) > 0.2 && touchGateScrollLike && !hasActiveNativeSelectionInTerminal()) {\n touchScrollVelocity *= 1.35;\n runTouchScrollMomentum();\n }\n\n const context = {');
fs.writeFileSync('src/components/TerminalPanel.vue', code);

8
pxterm/fix_bracket2.cjs Normal file
View File

@@ -0,0 +1,8 @@
const fs = require('fs');
let code = fs.readFileSync('src/components/TerminalPanel.vue', 'utf8');
// The issue is an extra brace that was probably around clearTouchScrollMomentum (which I removed with regex `function clearTouchScrollMomentum[\s\S]*?\n\}\n`, but maybe it was `function clearTouchScrollMomentum() { ... } \n }` which left a flying `}`.
code = code.replace(/wheelInertiaVelocity = 0;\n\}\n\}\n/g, 'wheelInertiaVelocity = 0;\n}\n\n');
fs.writeFileSync('src/components/TerminalPanel.vue', code);

View File

@@ -1,7 +1,8 @@
const fs = require('fs');
let code = fs.readFileSync('src/styles/main.css', 'utf8');
code += `
// If touch-action is missing from xterm-viewport we need to add it
code = code.replace(/\.terminal-wrapper \.xterm \.xterm-viewport \{/, '.terminal-wrapper .xterm .xterm-viewport {\n touch-action: none !important;');
/* Fix for native scrolling: .xterm-screen cannot be scrolled natively because it is not the scroll container.
To allow native scrolling, we would have to make .xterm-screen point-events: none, BUT we need selection on it. */
`;
fs.writeFileSync('src/styles/main.css', code);

14
pxterm/fix_native.cjs Normal file
View File

@@ -0,0 +1,14 @@
const fs = require('fs');
let code = fs.readFileSync('src/components/TerminalPanel.vue', 'utf8');
// I need to ensure that pointermove/pointerdown is NOT swallowing the touch event in mobile safari.
// Let's remove stopImmediatePropagation in the new logic if it's there.
// If touchmove just calls event.stopImmediatePropagation(), then xterm doesn't see it, but DOES it stop the native scroll if xterm is absolute positioned over the viewport?
// Wait, xterm's architecture:
// .xterm-screen is absolute positioned OVER .xterm-viewport.
// If user touches .xterm-screen (zIndex 1?), the touch is on .xterm-screen.
// .xterm-screen DOES NOT HAVE overflow-y: scroll. It's fixed height!
// Therefore, iOS panning gesture on .xterm-screen does NOT bubble to .xterm-viewport as a native scroll!
console.log("Analyzing xterm architecture...");

4
pxterm/fix_passive.cjs Normal file
View File

@@ -0,0 +1,4 @@
const fs = require('fs');
let code = fs.readFileSync('src/components/TerminalPanel.vue', 'utf8');
code = code.replace(/containerRef\.value!\.addEventListener\("touchmove", onTouchKeyboardTouchMove!, \{ capture: true, passive: false \}\);/g, 'containerRef.value!.addEventListener("touchmove", onTouchKeyboardTouchMove!, { capture: true, passive: true });');
fs.writeFileSync('src/components/TerminalPanel.vue', code);

13
pxterm/fix_pointer.js Normal file
View File

@@ -0,0 +1,13 @@
import fs from 'fs';
let code = fs.readFileSync('src/components/TerminalPanel.vue', 'utf8');
// remove all console.log containing "[Scroll Deep]" lines regardless of newlines
code = code.replace(/console\.log\(\s*(?:`|"|')\[Scroll Deep\][\s\S]*?\);/g, '');
// remove redundant empty else blocks
code = code.replace(/\} else \{\s*\}/g, '}');
// remove duplicated momentum blocks
code = code.replace(/(\/\/ 释放时触发滚行动量\s*if \(Math\.abs\(touchScrollVelocity\).*?runTouchScrollMomentum\(\);\s*\})[\s\S]*?(\/\/ 释放时触发滚行动量\s*if \(Math\.abs\(touchScrollVelocity\).*?runTouchScrollMomentum\(\);\s*\})/g, '$1');
fs.writeFileSync('src/components/TerminalPanel.vue', code);

25
pxterm/fix_pointer2.js Normal file
View File

@@ -0,0 +1,25 @@
import fs from 'fs';
let code = fs.readFileSync('src/components/TerminalPanel.vue', 'utf8');
// Find the bad duplicated momentum code that has missing braces
const badCode = ` // 释放时触发滚行动量
if (Math.abs(touchScrollVelocity) > 0.2 && touchGateScrollLike && !hasActiveNativeSelectionInTerminal()) {
touchScrollVelocity *= 1.35;
runTouchScrollMomentum();
// 释放时触发滚行动量
if (Math.abs(touchScrollVelocity) > 0.2 && touchGateScrollLike && !hasActiveNativeSelectionInTerminal()) {
touchScrollVelocity *= 1.35;
runTouchScrollMomentum();`;
const goodCode = ` // 释放时触发滚行动量
if (Math.abs(touchScrollVelocity) > 0.2 && touchGateScrollLike && !hasActiveNativeSelectionInTerminal()) {
touchScrollVelocity *= 1.35;
runTouchScrollMomentum();
}`;
code = code.replace(badCode, goodCode);
fs.writeFileSync('src/components/TerminalPanel.vue', code);

View File

@@ -0,0 +1,8 @@
const fs = require('fs');
let code = fs.readFileSync('src/components/TerminalPanel.vue', 'utf8');
// The touchmove event on containerRef is currently { capture: true, passive: true }.
// xterm installs its own touchmove listener on the terminal wrapper but if we don't interfere, it should just work.
// Wait, xterm DOES its own momentum? No, xterm doesn't have mobile momentum out of the box... wait, maybe it does?
console.log('Cleaned up TouchMove. Testing theory.');

View File

@@ -0,0 +1,7 @@
const fs = require('fs');
let code = fs.readFileSync('src/components/TerminalPanel.vue', 'utf8');
// The issue might be pointermove. If we are completely hands-off, we must also make sure pointermove is not stopping it.
const regex = /onTouchKeyboardPointerMove = \(event: PointerEvent\) => \{[\s\S]*?touchGateScrollLike = true;\n\s*\}/;
console.log(regex.test(code));

View File

@@ -0,0 +1,9 @@
const fs = require('fs');
let code = fs.readFileSync('src/components/TerminalPanel.vue', 'utf8');
// wait, how is xterm hooking into scrolling on mobile if we do stopImmediatePropagation on touchmove?
// Actually, if we do stopImmediatePropagation, xterm DOES NOT see the touchmove. So xterm DOES NOT scroll the viewport.
// And because the .xterm-screen (where the touch starts) DOES NOT have overflow: scroll, the browser DOES NOT scroll the viewport natively either!!
// This explains why it's completely frozen! We stopped xterm from scrolling it, AND the browser doesn't scroll it natively because the touch target isn't the scrollable container.
console.log("Found the core issue.");

12
pxterm/fix_ts.js Normal file
View File

@@ -0,0 +1,12 @@
import fs from 'fs';
let code = fs.readFileSync('src/components/TerminalPanel.vue', 'utf8');
code = code.replace(/containerRef\.value\.addEventListener\("touchstart", onTouchKeyboardTouchStart, /g, 'containerRef.value!.addEventListener("touchstart", onTouchKeyboardTouchStart!, ');
code = code.replace(/containerRef\.value\.addEventListener\("touchmove", onTouchKeyboardTouchMove, /g, 'containerRef.value!.addEventListener("touchmove", onTouchKeyboardTouchMove!, ');
code = code.replace(/containerRef\.value\.addEventListener\("touchend", onTouchKeyboardTouchEnd, /g, 'containerRef.value!.addEventListener("touchend", onTouchKeyboardTouchEnd!, ');
code = code.replace(/containerRef\.value\.addEventListener\("touchcancel", onTouchKeyboardTouchEnd, /g, 'containerRef.value!.addEventListener("touchcancel", onTouchKeyboardTouchEnd!, ');
code = code.replace(/containerRef\.value\.addEventListener\("click", onTouchKeyboardClick, /g, 'containerRef.value!.addEventListener("click", onTouchKeyboardClick, ');
code = code.replace(/containerRef\.value\.addEventListener\("mousedown", onTouchKeyboardMouseDown, /g, 'containerRef.value!.addEventListener("mousedown", onTouchKeyboardMouseDown, ');
fs.writeFileSync('src/components/TerminalPanel.vue', code);

View File

@@ -0,0 +1,8 @@
const fs = require('fs');
let code = fs.readFileSync('src/components/TerminalPanel.vue', 'utf8');
// To let xterm's native mobile JS scroll work, we MUST NOT intercept touchmove with stopImmediatePropagation. We must pass the events cleanly to xterm.
const regexTouchMove = /onTouchKeyboardTouchMove = \(event: TouchEvent\) => \{[\s\S]*?event\.stopImmediatePropagation\(\);\n\s*\};/;
code = code.replace(regexTouchMove, `onTouchKeyboardTouchMove = (event: TouchEvent) => {\n // Let xterm handle the touchmove natively! Do not stop propagation. It needs to calculate delta and apply it to viewportScroller.\n };`);
fs.writeFileSync('src/components/TerminalPanel.vue', code);

View File

@@ -0,0 +1,29 @@
const fs = require('fs');
let code = fs.readFileSync('src/components/TerminalPanel.vue', 'utf8');
// Remove variables
code = code.replace(/let touchScrollVelocity = 0;\n?/g, '');
code = code.replace(/let touchScrollRaf:\s*number\s*\|\s*null = null;\n?/g, '');
// Remove clearTouchScrollMomentum definition
code = code.replace(/function clearTouchScrollMomentum\(\): void \{[\s\S]*?\}\n/g, '');
// Remove runTouchScrollMomentum definition
code = code.replace(/function runTouchScrollMomentum\(\): void \{[\s\S]*?\}\n/g, '');
// Remove velocity tracking in pointermove
code = code.replace(/\s*const v = \(\-deltaY \/ dt\) \* 16;[\s\S]*?if \(touchScrollVelocity > touchMaxSpeed\) touchScrollVelocity = touchMaxSpeed;/g, '');
// Remove velocity tracking in touchmove
code = code.replace(/\s*\/\/ 计算物理滑动速度,用于释放后的动量[\s\S]*?\n\s*if \(Math\.abs\(dy\) > 2\) \{/g, '\n if (Math.abs(dy) > 2) {');
// Remove velocity call in touchend
code = code.replace(/\s*if \(Math\.abs\(touchScrollVelocity\) > 0\.2\) \{[\s\S]*?\n\s*\}/g, '');
// Remove velocity call in pointerup
code = code.replace(/\s*\/\/ 释放时触发滚行动量\s*if \(Math\.abs\(touchScrollVelocity\) > 0\.2 && touchGateScrollLike && !hasActiveNativeSelectionInTerminal\(\)\) \{[\s\S]*?\n\s*\}/g, '');
// Clean calls to clearTouchScrollMomentum
code = code.replace(/\s*clearTouchScrollMomentum\(\);/g, '');
fs.writeFileSync('src/components/TerminalPanel.vue', code);

View File

@@ -0,0 +1,13 @@
const fs = require('fs');
let code = fs.readFileSync('src/components/TerminalPanel.vue', 'utf8');
// Also remove residual bits
code = code.replace(/\s*touchScrollVelocity = 0;/g, '');
code = code.replace(/\s*touchScrollVelocity \*= TOUCH_SCROLL_DAMPING;/g, '');
code = code.replace(/\s*if \(Math\.abs\(touchScrollVelocity\) < TOUCH_SCROLL_STOP_THRESHOLD\) \{[\s\S]*?\}\n/g, '');
code = code.replace(/\s*viewportScroller\.scrollTop \+= touchScrollVelocity;/g, '');
code = code.replace(/function clearTouchScrollMomentum[\s\S]*?\n\}\n/g, '');
code = code.replace(/function runTouchScrollMomentum[\s\S]*?\n\}\n/g, '');
fs.writeFileSync('src/components/TerminalPanel.vue', code);

19
pxterm/revert.cjs Normal file
View File

@@ -0,0 +1,19 @@
const fs = require('fs');
let code = fs.readFileSync('src/components/TerminalPanel.vue', 'utf8');
// clean global intercept block if it exists
code = code.replace(/\/\/ \-\-\- 极致全局监听器[\s\S]*?\n\s*\}\);\n\n/g, '');
// remove multiline logs
code = code.replace(/\s*console\.log\(\s*(?:`|"|')\[Scroll Deep\][\s\S]*?\);\n/g, '\n');
// remove empty else blocks
code = code.replace(/\s*\} else \{\s*\n\s*\}/g, '}');
// remove duplicated momentum logic in pointerup
code = code.replace(/(\s*\/\/ 释放时触发滚行动量\s*if \(Math\.abs\(touchScrollVelocity\) > 0\.2 && touchGateScrollLike && !hasActiveNativeSelectionInTerminal\(\)\) \{\s*touchScrollVelocity \*= 1\.35;\s*runTouchScrollMomentum\(\);\s*\})\s*\/\/ 释放时触发滚行动量\s*if \(Math\.abs\(touchScrollVelocity\) > 0\.2 && touchGateScrollLike && !hasActiveNativeSelectionInTerminal\(\)\) \{\s*touchScrollVelocity \*= 1\.35;\s*runTouchScrollMomentum\(\);\s*\}/g, '$1');
// fix the syntax bug around touch end momentum
code = code.replace(/\s*if \(Math\.abs\(touchScrollVelocity\) > 0\.2\) \{\s*touchScrollVelocity \*= 1\.35;\s*runTouchScrollMomentum\(\);\s*\};\s*\n/g, '\n if (Math.abs(touchScrollVelocity) > 0.2) {\n touchScrollVelocity *= 1.35;\n runTouchScrollMomentum();\n }\n');
fs.writeFileSync('src/components/TerminalPanel.vue', code);

View File

@@ -1205,23 +1205,7 @@ async function loadWebglAddonOnDesktop(): Promise<void> {
*/
function initTerminal(): void {
// --- 极致全局监听器,抓出是谁吃掉了 move ---
const globalEventDebug = (e: Event) => {
// 若这是消失的 move 事件,打出到底在哪被拦截了
if (e.type === "touchmove" || e.type === "pointermove" || e.type === "touchcancel" || e.type === "pointercancel") {
// 为了不刷屏,如果是 pointermove 我们只在 touch 活跃时打印
if (e.type === "pointermove" && (e as PointerEvent).pointerType !== "touch") return;
console.log(`[GLOBAL INTERCEPT] ${e.type} | Phase: ${e.eventPhase} | Cancelable: ${e.cancelable} | Target: ${(e.target as Element)?.className || (e.target as Element)?.tagName}`);
}
};
['touchmove', 'touchstart', 'touchend', 'touchcancel', 'pointerdown', 'pointermove', 'pointerup', 'pointercancel'].forEach(type => {
// 用最顶级的 window capture 拦截,这样在任何人(包括 xterm 内部那些黑盒代码)前面执行
window.addEventListener(type, globalEventDebug, { capture: true, passive: false });
});
if (!containerRef.value) return;
if (!containerRef.value) return;
terminal = new Terminal({
// Unicode11Addon 依赖 xterm proposed API需显式开启。
@@ -1465,7 +1449,6 @@ function initTerminal(): void {
};
onTouchKeyboardPointerDown = (event: PointerEvent) => {
console.log("[Scroll Deep] 🟡 POINTER DOWN: ", { pointerId: event.pointerId, type: event.pointerType, target: (event.target as Node)?.nodeName });
if (event.pointerType !== "touch") {
return;
}
@@ -1527,7 +1510,6 @@ function initTerminal(): void {
};
onTouchKeyboardPointerUp = (event: PointerEvent) => {
console.log("[Scroll Deep] 🟠 POINTER UP: ", { pointerId: event.pointerId, type: event.pointerType });
if (event.pointerType !== "touch" || touchGatePointerId !== event.pointerId) {
return;
}
@@ -1536,22 +1518,7 @@ function initTerminal(): void {
if (Math.abs(touchScrollVelocity) > 0.2 && touchGateScrollLike && !hasActiveNativeSelectionInTerminal()) {
touchScrollVelocity *= 1.35;
console.log(`[Scroll Deep] 🚀 Pointer 释放触发JS惯性速度=${touchScrollVelocity.toFixed(2)}`);
runTouchScrollMomentum();
} else {
console.log(`[Scroll Deep] 🛑 Pointer 静止释放或非滚动释放`);
}
// 释放时触发滚行动量
if (Math.abs(touchScrollVelocity) > 0.2 && touchGateScrollLike && !hasActiveNativeSelectionInTerminal()) {
touchScrollVelocity *= 1.35;
console.log(`[Scroll Deep] 🚀 Pointer 释放触发JS惯性速度=${touchScrollVelocity.toFixed(2)}`);
runTouchScrollMomentum();
} else {
console.log(`[Scroll Deep] 🛑 Pointer 静止释放或非滚动释放`);
}
runTouchScrollMomentum();}
const context = {
inBand: touchGateStartInBand,
@@ -1660,7 +1627,6 @@ function initTerminal(): void {
}
};
onTouchKeyboardPointerCancel = (event: PointerEvent) => {
console.log("[Scroll Deep] ⚫ POINTER CANCEL: ", { pointerId: event.pointerId, type: event.pointerType });
if (event.pointerType !== "touch" || touchGatePointerId !== event.pointerId) {
return;
}
@@ -1694,13 +1660,6 @@ function initTerminal(): void {
touchScrollVelocity = 0;
}
console.log("[Scroll Deep] 🟢 TOUCH START:", {
touches: event.touches.length,
isCancelable: event.cancelable,
target: (event.target as Node)?.nodeName,
viewportScrollTop: viewportScroller?.scrollTop
});
if (!helperTextarea) return;
if (focusKeyboardTimerId !== null) {
window.clearTimeout(focusKeyboardTimerId);
@@ -1711,7 +1670,6 @@ function initTerminal(): void {
helperTextarea.readOnly = true;
};
onTouchKeyboardTouchMove = (event: TouchEvent) => {
console.log(`[Scroll Deep] 🔵 TOUCH MOVE | cancelable: ${event.cancelable} | target: ${(event.target as Node)?.nodeName}`);
const hasSelection = hasActiveNativeSelectionInTerminal();
if (hasSelection) {
lastTouchAction = "PASS_NATIVE";
@@ -1751,7 +1709,6 @@ function initTerminal(): void {
else if (touchScrollVelocity < -touchMaxSpeed) touchScrollVelocity = -touchMaxSpeed;
if (Math.abs(dy) > 2) {
console.log(`[Scroll Deep] ➡️ 手指位移真实触发生效Δy=${dy.toFixed(1)}px | 渲染视口=${before}${viewportScroller.scrollTop} | v=${v.toFixed(2)}`);
}
}
@@ -1759,16 +1716,11 @@ function initTerminal(): void {
touchScrollLastTime = now;
};
onTouchKeyboardTouchEnd = (event: TouchEvent) => {
console.log(`[Scroll Deep] 🔴 TOUCH END/CANCEL | cancelable: ${event.cancelable} | target: ${(event.target as Node)?.nodeName} | 最终 scrollTop: ${viewportScroller?.scrollTop}`);
if (Math.abs(touchScrollVelocity) > 0.2) {
touchScrollVelocity *= 1.35;
console.log(`[Scroll Deep] 🚀 准备进行 JS 惯性滚动,起步速度=${touchScrollVelocity.toFixed(2)}`);
runTouchScrollMomentum();
} else {
console.log(`[Scroll Deep] 🛑 静止释放,不触发惯性`);
}
runTouchScrollMomentum();}
};
// passive: true — 以上均不调用 preventDefault()。非 passive 的 touch 监听器
// 会阻塞 iOS 滚动合成器等待 JS 主线程,即使实际未 preventDefault 也会引入卡顿。

17
pxterm/use_native.cjs Normal file
View File

@@ -0,0 +1,17 @@
const fs = require('fs');
let code = fs.readFileSync('src/components/TerminalPanel.vue', 'utf8');
const regexTouchMove = /onTouchKeyboardTouchMove = \(event: TouchEvent\) => \{[\s\S]*?touchScrollLastTime = now;\n\s*\};/g;
const newTouchMove = `onTouchKeyboardTouchMove = (event: TouchEvent) => {
// 通过 stopImmediatePropagation 拦截 xterm 内部那些“接管浏览器滚动”的事件处理器。
// 但绝对不要自己掉用 event.preventDefault(),从而把物理滚动和动量动画原模原样还给浏览器与操作系统。
event.stopImmediatePropagation();
};`;
code = code.replace(regexTouchMove, newTouchMove);
// Also remove touchstart redundant vars
code = code.replace(/touchScrollLastY = event\.touches\[0\]\?\.clientY \|\| 0;\n\s*touchScrollLastTime = performance\.now\(\);\n/g, '');
fs.writeFileSync('src/components/TerminalPanel.vue', code);