feat(appearance): apply UI zoom level to terminal and Monaco editors

Extend the existing appearance zoom setting so it also scales xterm.js
terminals and Monaco editors (diff viewer, three-pane merge editor, file
workspace editor), which previously rendered at a hard-coded 13px
regardless of zoom.

- Terminals read zoom at init and update term.options.fontSize live on
  zoom change, refitting after a double rAF so xterm's renderer has
  recomputed cell metrics. Font size is rounded to an integer to avoid
  subpixel blur in the canvas renderer.
- Monaco editors derive fontSize from zoomLevel; three-pane merge
  editor memoizes its options object to avoid redundant updateOptions
  calls on unrelated re-renders.
This commit is contained in:
xintaofei
2026-04-19 09:07:51 +08:00
parent eeeee2141c
commit 0fa2a0895f
4 changed files with 68 additions and 21 deletions

View File

@@ -8,8 +8,15 @@ import {
terminalResize,
terminalKill,
} from "@/lib/api"
import { useZoomLevel } from "@/hooks/use-appearance"
import type { TerminalEvent } from "@/lib/types"
import type { ITheme } from "@xterm/xterm"
import type { ITheme, Terminal as XTermTerminal } from "@xterm/xterm"
const TERMINAL_BASE_FONT_SIZE = 13
function computeTerminalFontSize(zoomLevel: number): number {
return Math.round((TERMINAL_BASE_FONT_SIZE * zoomLevel) / 100)
}
const DARK_THEME: ITheme = {
background: "#1a1a1a",
@@ -108,11 +115,13 @@ export function TerminalView({
}: TerminalViewProps) {
const containerRef = useRef<HTMLDivElement>(null)
const fitAddonRef = useRef<{ fit: () => void } | null>(null)
const termRef = useRef<{ focus: () => void } | null>(null)
const termRef = useRef<XTermTerminal | null>(null)
const lastResizeRef = useRef<{ cols: number; rows: number } | null>(null)
const isActiveRef = useRef(isActive)
const isVisibleRef = useRef(isVisible)
const onProcessExitedRef = useRef(onProcessExited)
const { zoomLevel } = useZoomLevel()
const zoomLevelRef = useRef(zoomLevel)
const [loading, setLoading] = useState(true)
useEffect(() => {
@@ -140,7 +149,7 @@ export function TerminalView({
const term = new Terminal({
cursorBlink: true,
fontSize: 13,
fontSize: computeTerminalFontSize(zoomLevelRef.current),
fontFamily: "Menlo, Monaco, 'Courier New', monospace",
theme: getTerminalTheme(containerRef.current),
allowProposedApi: true,
@@ -292,6 +301,25 @@ export function TerminalView({
}
}, [isActive, isVisible])
// React to zoom level changes. Updates the ref synchronously so async init()
// always reads the latest zoom, and pushes the new font size to already-mounted
// terminals. Double rAF ensures xterm's renderer has recomputed cell metrics
// before we refit.
useEffect(() => {
zoomLevelRef.current = zoomLevel
const term = termRef.current
if (!term) return
term.options.fontSize = computeTerminalFontSize(zoomLevel)
requestAnimationFrame(() => {
requestAnimationFrame(() => {
const el = containerRef.current
if (el && el.clientWidth > 0 && el.clientHeight > 0) {
fitAddonRef.current?.fit()
}
})
})
}, [zoomLevel])
return (
<div
className="absolute inset-0 h-full w-full p-2"