fix: debounce pty resize and skip no-op resizes (kills duplicated TUI frames on maximize/restore)
This commit is contained in:
1 parent
ab5f8c2f77
commit
acdf955918
2 files changed
+29
-3
No files matched your search
@@ -41,3 +41,8 @@ Electron + electron-vite + React 终端工具(本地终端 / SSH / SFTP)。
|
|||||||
|
|
||||||
- 终端主题由 xterm 主题派生 UI 配色:`src/renderer/src/theme/chrome.ts` 的 `applyChromeTheme` 写入 `--chrome-bg/-bg-deep/-border/-hover` CSS 变量,antd token 在 `main.tsx` ThemedConfigProvider 派生
|
- 终端主题由 xterm 主题派生 UI 配色:`src/renderer/src/theme/chrome.ts` 的 `applyChromeTheme` 写入 `--chrome-bg/-bg-deep/-border/-hover` CSS 变量,antd token 在 `main.tsx` ThemedConfigProvider 派生
|
||||||
- 标签强调色:`settings.tabAccentColor`(默认 `#3fb950`),经 `--tab-accent` CSS 变量生效
|
- 标签强调色:`settings.tabAccentColor`(默认 `#3fb950`),经 `--tab-accent` CSS 变量生效
|
||||||
|
|
||||||
|
## 终端尺寸同步
|
||||||
|
|
||||||
|
- `TerminalView.scheduleFit`:fit 后**去抖 100ms** 再把 cols/rows 发给 PTY,并跳过与上次相同的尺寸。每次 ResizeObserver 都戳 PTY 会让全屏 TUI(Claude Code 等)在最大化/还原的中间尺寸上反复重绘,留下重复帧
|
||||||
|
- 拖动窗口期间 xterm 网格立即更新,PTY 尺寸在停止后 100ms 生效
|
||||||
@@ -226,6 +226,13 @@ export const TerminalView: ForwardRefExoticComponent<TerminalViewProps & { ref?:
|
|||||||
const searchRef = useRef<SearchAddon | null>(null)
|
const searchRef = useRef<SearchAddon | null>(null)
|
||||||
const observerRef = useRef<ResizeObserver | null>(null)
|
const observerRef = useRef<ResizeObserver | null>(null)
|
||||||
const timerRef = useRef<number>(0)
|
const timerRef = useRef<number>(0)
|
||||||
|
// PTY resize bookkeeping: `ptyTimerRef` debounces the resize while a window
|
||||||
|
// drag/maximize settles, `ptySizeRef` drops the no-op resizes in between.
|
||||||
|
// Poking the pty on every intermediate layout tick makes full-screen TUIs
|
||||||
|
// (Claude Code et al.) redraw repeatedly at half-settled sizes, which is what
|
||||||
|
// leaves duplicated frames behind in the scrollback.
|
||||||
|
const ptyTimerRef = useRef<number>(0)
|
||||||
|
const ptySizeRef = useRef<{ cols: number; rows: number } | null>(null)
|
||||||
const deadRef = useRef(false)
|
const deadRef = useRef(false)
|
||||||
const searchOpenRef = useRef(false)
|
const searchOpenRef = useRef(false)
|
||||||
const searchTermRef = useRef('')
|
const searchTermRef = useRef('')
|
||||||
@@ -337,9 +344,21 @@ export const TerminalView: ForwardRefExoticComponent<TerminalViewProps & { ref?:
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
const term = termRef.current
|
const term = termRef.current
|
||||||
if (term && !deadRef.current) {
|
if (!term || deadRef.current) return
|
||||||
window.api.resizePty(sessionId, term.cols, term.rows)
|
// The xterm grid is already correct; only tell the pty once it settles.
|
||||||
}
|
const last = ptySizeRef.current
|
||||||
|
if (last && last.cols === term.cols && last.rows === term.rows) return
|
||||||
|
window.clearTimeout(ptyTimerRef.current)
|
||||||
|
ptyTimerRef.current = window.setTimeout(() => {
|
||||||
|
const live = termRef.current
|
||||||
|
if (!live || deadRef.current) return
|
||||||
|
if (live.cols === ptySizeRef.current?.cols && live.rows === ptySizeRef.current?.rows) return
|
||||||
|
ptySizeRef.current = { cols: live.cols, rows: live.rows }
|
||||||
|
window.api.resizePty(sessionId, live.cols, live.rows)
|
||||||
|
// 100ms mirrors what other Electron terminals use: long enough to swallow
|
||||||
|
// a maximize/restore animation tick storm, short enough that a plain
|
||||||
|
// window drag does not visibly wrap against a stale width.
|
||||||
|
}, 100)
|
||||||
})
|
})
|
||||||
}, [sessionId])
|
}, [sessionId])
|
||||||
|
|
||||||
@@ -616,6 +635,7 @@ export const TerminalView: ForwardRefExoticComponent<TerminalViewProps & { ref?:
|
|||||||
// M5: reset per-session state on (re)bind — fresh line buffer, no leftovers
|
// M5: reset per-session state on (re)bind — fresh line buffer, no leftovers
|
||||||
// from a template apply or panel reuse; recording resumes idle.
|
// from a template apply or panel reuse; recording resumes idle.
|
||||||
lineBufRef.current = ''
|
lineBufRef.current = ''
|
||||||
|
ptySizeRef.current = null
|
||||||
setSuggestions([])
|
setSuggestions([])
|
||||||
setSuggestionIndex(0)
|
setSuggestionIndex(0)
|
||||||
if (recordingRef.current) {
|
if (recordingRef.current) {
|
||||||
@@ -746,6 +766,7 @@ export const TerminalView: ForwardRefExoticComponent<TerminalViewProps & { ref?:
|
|||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
if (carryTimer !== undefined) window.clearTimeout(carryTimer)
|
if (carryTimer !== undefined) window.clearTimeout(carryTimer)
|
||||||
|
if (ptyTimerRef.current) window.clearTimeout(ptyTimerRef.current)
|
||||||
for (const unsubscribe of unsubscribes) unsubscribe()
|
for (const unsubscribe of unsubscribes) unsubscribe()
|
||||||
for (const disposable of disposables) disposable.dispose()
|
for (const disposable of disposables) disposable.dispose()
|
||||||
observer?.disconnect()
|
observer?.disconnect()
|
||||||
|
|||||||
Reference in new issue
Block a user