✨ feat(vscode): 代码阅读差异加「只看改动」(连着没变的块折成一条、按工作区记住)、上一处 / 下一处改动、右边缘总览条

This commit is contained in:
cruldra committed 2026-09-30 08:54:23 +08:00
1 parent 707d85c324
commit 6120c85bc0
14 files changed
+533 -42

No files matched your search

+32 -6
View File
@@ -8,11 +8,13 @@ import { TriangleAlert } from 'lucide-react'
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { BlockLevel } from './BlockTree'
import { DiffBar, NewFileBanner } from './DiffBar'
import { diffTree, openedByDiff } from './diffView'
import { changeStops, diffTree, foldUnchanged, openedByDiff } from './diffView'
import { acceptNames, DisplayNameContext, documentNames } from './displayNames'
import { coveredByEditing, startEditing } from './editing'
import { applyFilter, excludedBy, keptWhileEditing } from './filter'
import { FilterBar } from './FilterBar'
import { OverviewRuler } from './OverviewRuler'
import { useChangeNav } from './useChangeNav'
import { useDefinitionLinks } from './useDefinitionLinks'
import { HighlightContext, useOccurrences } from './useOccurrences'
import { useQa } from './useQa'
@@ -56,6 +58,16 @@ export function App(): ReactElement | null {
const collapsed = useMemo(() => collapsedIds(doc?.blocks ?? [], view.folds, openedByDiff(changes)), [doc, view.folds, changes])
const filter = useMemo(() => applyFilter(doc?.blocks ?? [], excluded, keep), [doc, excluded, keep])
const covered = useMemo(() => coveredByEditing(doc?.blocks ?? [], new Set(view.editing)), [doc, view.editing])
/** 「只看改动」开着(整个工作区共用,扩展端记着);本地不改,改了发给扩展端、等它发回来 */
const [onlyChanges, setOnlyChanges] = useState(false)
/** 「只看改动」时点开过的几段没变的块(按第一块的 id),筛选一变就重新折 */
const [openedRuns, setOpenedRuns] = useState<ReadonlySet<string>>(NONE)
const folded = useMemo(() => doc && changes && onlyChanges
? foldUnchanged(doc.blocks, changes, block => !filter.hidden.has(block.id) && !covered.has(block.id), openedRuns, keep)
: undefined, [doc, changes, onlyChanges, filter, covered, openedRuns, keep])
/** 页面上没画出来的块:被筛掉的,和「只看改动」折起来的 */
const unshown = useMemo(() => folded ? new Set([...filter.hidden, ...folded.folded]) : filter.hidden, [filter, folded])
const stops = useMemo(() => doc && changes ? changeStops(doc.blocks, changes, filter.hidden) : [], [doc, changes, filter])
/** 记下这几块、属性行、子行折叠还是展开,从此不再按默认 */
const setFolded = useCallback((ids: string[], folded: boolean) => setView(prev => ({
...prev,
@@ -65,16 +77,17 @@ export function App(): ReactElement | null {
const show = useCallback((ids: string[]) => setKeep(prev => new Set([...prev, ...ids])), [])
const revealed = useCallback(() => setReveal(null), [])
useScrollPosition(doc)
useReveal(doc, reveal, collapsed, filter.hidden, covered, show, expand, revealed)
useReveal(doc, reveal, collapsed, unshown, covered, show, expand, revealed)
useDefinitionLinks(doc?.version)
const nav = useChangeNav(stops, collapsed, expand)
const occurrencePage = useMemo((): OccurrencePage | undefined => doc
? {
blocks: doc.blocks,
skip: id => filter.hidden.has(id) || covered.has(id) || view.editing.includes(id),
skip: id => unshown.has(id) || covered.has(id) || view.editing.includes(id),
collapsed,
expand,
}
: undefined, [doc, filter, covered, view.editing, collapsed, expand])
: undefined, [doc, unshown, covered, view.editing, collapsed, expand])
const highlight = useOccurrences(doc?.version, doc?.translatorId, occurrencePage)
useEffect(() => {
@@ -95,7 +108,9 @@ export function App(): ReactElement | null {
return
case 'filter':
setExcluded(msg.excluded)
setOnlyChanges(msg.onlyChanges)
setKeep(NONE)
setOpenedRuns(NONE)
return
case 'diff':
setDiff(msg.diff)
@@ -133,7 +148,9 @@ export function App(): ReactElement | null {
onToggleFold: id => setFolded([id], !collapsed.has(id)),
...(qa.state.available && { qa }),
...(changes && { diff: changes }),
}, [doc, view, collapsed, filter, covered, kindName, focusId, onFocused, setFolded, qa, changes])
...(folded && { folded }),
onOpenRun: id => setOpenedRuns(prev => new Set([...prev, id])),
}, [doc, view, collapsed, filter, covered, kindName, focusId, onFocused, setFolded, qa, changes, folded])
if (!doc || !tree)
return null
@@ -150,8 +167,17 @@ export function App(): ReactElement | null {
translators={doc.translators}
translatorId={doc.translatorId}
onSelectTranslator={id => postMessage({ type: 'selectTranslator', id })}
below={diff && doc.error === undefined && <DiffBar diff={diff} />}
below={diff && doc.error === undefined && (
<DiffBar
diff={diff}
onlyChanges={onlyChanges}
onToggleOnly={() => postMessage({ type: 'setOnlyChanges', on: !onlyChanges })}
canMove={stops.length > 0}
onMove={nav.move}
/>
)}
/>
{changes && diff && <OverviewRuler stops={stops} lineCount={diff.lineCount} onPick={nav.goTo} />}
<div className="mx-auto box-content max-w-[880px] px-6 py-5">
{diff?.newFile !== undefined && doc.error === undefined && <NewFileBanner detail={diff.newFile} />}
{doc.error !== undefined && (