From 2368a66a8fb68df3eab52bd63b12ede335f0cdc7 Mon Sep 17 00:00:00 2001 From: liuweitao Date: Fri, 10 Apr 2026 00:49:27 +0800 Subject: [PATCH] =?UTF-8?q?fix(terminal):=20=E9=94=81=E5=AE=9A=E7=94=A8?= =?UTF-8?q?=E6=88=B7=E6=BB=9A=E5=8A=A8=E4=BD=8D=E7=BD=AE=E9=98=B2=E6=AD=A2?= =?UTF-8?q?=20AI=20=E8=BE=93=E5=87=BA=E6=97=B6=E5=86=85=E5=AE=B9=E8=B7=B3?= =?UTF-8?q?=E5=8A=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 当用户向上滚动查看历史内容时,AI 持续输出的 TUI 控制序列 (如清屏 \x1b[2J、清滚动缓冲区 \x1b[3J)会改变 viewport 位置, 导致正在阅读的文字跳动或重叠。 修复方案:在 terminal.write() 前后锁定用户距离底部的滚动偏移量, 写入完成后用 scrollLines() 恢复 viewport 位置。 --- src/renderer/hooks/useXterm.ts | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/renderer/hooks/useXterm.ts b/src/renderer/hooks/useXterm.ts index 75e367ef..df8761f2 100644 --- a/src/renderer/hooks/useXterm.ts +++ b/src/renderer/hooks/useXterm.ts @@ -614,7 +614,27 @@ export function useXterm({ setTimeout(() => { if (writeBufferRef.current.length > 0) { const bufferedData = writeBufferRef.current; + + // If user has scrolled up to view history, lock their viewport position. + // TUI control sequences (CSI 2J, CSI 3J, alternate buffer switch) + // can reset or scroll the viewport regardless of isUserScrolling. + // We preserve the user's scroll offset from the bottom. + const buffer = terminal.buffer.active; + const offsetFromBottom = buffer.baseY - buffer.viewportY; + const shouldLockViewport = offsetFromBottom > 0; + const savedOffsetFromBottom = shouldLockViewport ? offsetFromBottom : 0; + terminal.write(bufferedData); + + // Restore viewport if it was moved by the write + if (shouldLockViewport) { + const targetViewportY = terminal.buffer.active.baseY - savedOffsetFromBottom; + const currentViewportY = terminal.buffer.active.viewportY; + if (targetViewportY !== currentViewportY) { + terminal.scrollLines(targetViewportY - currentViewportY); + } + } + // Call onData after write to avoid React re-render storm onDataRef.current?.(bufferedData); writeBufferRef.current = '';