From dca8b7eecd55dd99e1ebdc3442be93a6ff95e74b Mon Sep 17 00:00:00 2001 From: cye2 Date: Tue, 28 Apr 2026 14:20:46 +0800 Subject: [PATCH 1/2] =?UTF-8?q?fix(source-control):=20=E4=BF=AE=E5=A4=8D?= =?UTF-8?q?=20DiffViewer=20=E4=B8=AD=20staged/unstaged=20=E6=96=87?= =?UTF-8?q?=E4=BB=B6=E6=A8=A1=E5=9E=8B=E8=B7=AF=E5=BE=84=E5=86=B2=E7=AA=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 将非 commit 视图下的 `original/modified` 虚拟路径按 `staged/unstaged` 区分,避免同一路径导致 diff 同步异常。 --- src/renderer/components/source-control/DiffViewer.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/renderer/components/source-control/DiffViewer.tsx b/src/renderer/components/source-control/DiffViewer.tsx index 1fdae1d1..fb88fcd9 100644 --- a/src/renderer/components/source-control/DiffViewer.tsx +++ b/src/renderer/components/source-control/DiffViewer.tsx @@ -1223,13 +1223,13 @@ export function DiffViewer({ 'inmemory', isCommitView && commitHash ? `original/commit/${commitHash}/${rootPath}/${file.path}` - : `original/${rootPath}/${file.path}` + : `original/${file.staged ? 'staged' : 'unstaged'}/${rootPath}/${file.path}` )} modifiedModelPath={toMonacoVirtualUri( 'inmemory', isCommitView && commitHash ? `modified/commit/${commitHash}/${rootPath}/${file.path}` - : `modified/${rootPath}/${file.path}` + : `modified/${file.staged ? 'staged' : 'unstaged'}/${rootPath}/${file.path}` )} language={getLanguageFromPath(file.path)} theme={CUSTOM_THEME_NAME} From 7581f5048542a3f2ece7e89dee85522176374925 Mon Sep 17 00:00:00 2001 From: cye2 Date: Tue, 28 Apr 2026 15:21:30 +0800 Subject: [PATCH 2/2] =?UTF-8?q?fix(source-control):=20=E4=BF=AE=E5=A4=8D?= =?UTF-8?q?=20DiffViewer=20=E5=88=87=E6=8D=A2=E6=96=87=E4=BB=B6=E6=97=B6?= =?UTF-8?q?=E6=8A=98=E5=8F=A0=E9=80=89=E9=A1=B9=E4=B8=8E=E8=A1=8C=E5=8F=98?= =?UTF-8?q?=E6=9B=B4=E5=90=8C=E6=AD=A5=E5=BC=82=E5=B8=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 使用 diff 签名(含 staged/unstaged、commit/worktree)判断变更,避免误判导致不刷新 - 在 diff-ready 与手动拉取 lineChanges 后强制刷新 `hideUnchangedRegions`,并增加 rAF 调度与卸载清理 --- .../components/source-control/DiffViewer.tsx | 122 ++++++++++++++++-- 1 file changed, 113 insertions(+), 9 deletions(-) diff --git a/src/renderer/components/source-control/DiffViewer.tsx b/src/renderer/components/source-control/DiffViewer.tsx index fb88fcd9..ffc773a1 100644 --- a/src/renderer/components/source-control/DiffViewer.tsx +++ b/src/renderer/components/source-control/DiffViewer.tsx @@ -192,7 +192,83 @@ export function DiffViewer({ const pendingNavigationDirectionRef = useRef<'next' | 'prev' | null>(null); const navigationIdRef = useRef(0); // Increment on each new file selection const [isThemeReady, setIsThemeReady] = useState(false); - const diffContentRef = useRef(''); // Track diff content changes + const diffSignatureRef = useRef(''); // Track diff identity changes + const hideUnchangedFrameRef = useRef(null); + const lastAppliedHideUnchangedRef = useRef<{ + editor: DiffEditorInstance | null; + enabled: boolean | null; + }>({ + editor: null, + enabled: null, + }); + + const buildDiffSignature = useCallback( + (targetDiff: FileDiff | null | undefined) => { + const original = targetDiff?.original ?? ''; + const modified = targetDiff?.modified ?? ''; + const summarize = (text: string) => + `${text.length}:${text.slice(0, 64)}:${text.slice(Math.max(0, text.length - 64))}`; + + return [ + file?.path ?? '', + file?.staged ? 'staged' : 'unstaged', + isCommitView ? `commit:${commitHash ?? 'none'}` : 'worktree', + summarize(original), + summarize(modified), + ].join('\u0000'); + }, + [file?.path, file?.staged, isCommitView, commitHash] + ); + + const applyHideUnchangedRegions = useCallback( + (editor: DiffEditorInstance, forceRefresh = false) => { + const targetEnabled = hideUnchangedRegions; + + if ( + !forceRefresh && + lastAppliedHideUnchangedRef.current.editor === editor && + lastAppliedHideUnchangedRef.current.enabled === targetEnabled + ) { + return; + } + + if (forceRefresh) { + editor.updateOptions({ + hideUnchangedRegions: { + enabled: !targetEnabled, + }, + }); + } + + editor.updateOptions({ + hideUnchangedRegions: { + enabled: targetEnabled, + }, + }); + + lastAppliedHideUnchangedRef.current = { + editor, + enabled: targetEnabled, + }; + }, + [hideUnchangedRegions] + ); + + const scheduleApplyHideUnchangedRegions = useCallback( + (editor: DiffEditorInstance, forceRefresh = false) => { + if (hideUnchangedFrameRef.current !== null) { + cancelAnimationFrame(hideUnchangedFrameRef.current); + } + hideUnchangedFrameRef.current = requestAnimationFrame(() => { + hideUnchangedFrameRef.current = null; + if (editorRef.current !== editor || !editor.getModel()) { + return; + } + applyHideUnchangedRegions(editor, forceRefresh); + }); + }, + [applyHideUnchangedRegions] + ); // Line comment state const [hoveredLine, setHoveredLine] = useState(null); @@ -221,6 +297,16 @@ export function DiffViewer({ } }, [terminalTheme, isThemeReady]); + // Cleanup scheduled folding updates when component unmounts. + useEffect(() => { + return () => { + if (hideUnchangedFrameRef.current !== null) { + cancelAnimationFrame(hideUnchangedFrameRef.current); + hideUnchangedFrameRef.current = null; + } + }; + }, []); + // Handle submit comment const handleSubmitComment = useCallback( (lineNumber: number, text: string) => { @@ -727,6 +813,10 @@ export function DiffViewer({ editorFilePathRef.current = file?.path ?? null; setEditorReady(true); + // Keep option state in sync at mount. + // Diff-ready callbacks below will run the authoritative force refresh. + scheduleApplyHideUnchangedRegions(editor); + const currentModel = editor.getModel(); const mountedModels = currentModel ? { original: currentModel.original, modified: currentModel.modified } @@ -744,6 +834,8 @@ export function DiffViewer({ if (changes) { setLineChanges(changes); lineChangesRef.current = changes; + // Use diff-ready as the authoritative point to refresh folding options. + scheduleApplyHideUnchangedRegions(editor, true); performAutoNavigation(editor, changes); } }) @@ -801,7 +893,7 @@ export function DiffViewer({ } }; }, - [file?.path, performAutoNavigation, isCommitView] + [file?.path, performAutoNavigation, isCommitView, scheduleApplyHideUnchangedRegions] ); // Toggle hide unchanged regions @@ -830,7 +922,7 @@ export function DiffViewer({ } }, [navigationDirection]); - // Manually fetch lineChanges when file changes or diff content changes + // Manually fetch lineChanges when file identity changes. // This is needed because onDidUpdateDiff doesn't fire when switching back to a previously-viewed file useEffect(() => { const editor = editorRef.current; @@ -842,14 +934,14 @@ export function DiffViewer({ return; } - // Check if diff content has actually changed - const currentContent = diff ? `${diff.original}${diff.modified}` : ''; - if (currentContent === diffContentRef.current) { + // Include staged flag in signature because staged/unstaged may recreate models with same text. + const currentSignature = buildDiffSignature(diff); + if (currentSignature === diffSignatureRef.current) { // Content hasn't changed, skip return; } - diffContentRef.current = currentContent; + diffSignatureRef.current = currentSignature; // When models change, Monaco computes the diff asynchronously // We need to poll getLineChanges() until it returns a result (or times out) @@ -864,6 +956,8 @@ export function DiffViewer({ lineChangesRef.current = changes; // Perform auto-navigation with the fresh changes performAutoNavigation(editor, changes); + // Refresh folding once changes are ready. + scheduleApplyHideUnchangedRegions(editor, true); return true; // Success } return false; // Not ready yet @@ -883,7 +977,13 @@ export function DiffViewer({ }, 50); return () => clearInterval(timer); - }, [diff, file?.path, performAutoNavigation]); + }, [ + diff, + buildDiffSignature, + file?.path, + performAutoNavigation, + scheduleApplyHideUnchangedRegions, + ]); const navigateToDiff = useCallback( (direction: 'prev' | 'next') => { @@ -1033,7 +1133,11 @@ export function DiffViewer({ hasAutoNavigatedRef.current = false; setIsEditing(false); setEditedContent(null); - diffContentRef.current = ''; + diffSignatureRef.current = ''; + lastAppliedHideUnchangedRef.current = { + editor: null, + enabled: null, + }; }, [file?.path, file?.staged]); if (!file) {