⚡ perf(vscode): 代码阅读预览的卡片位置量过就记着,内容或大小变了才重量:粘性标题、记滚动位置、上报可见行、跳转按顶层块二分找,滚动时不再每帧量全部卡片,滚动回调里也不读会逼排版的位置

This commit is contained in:
cruldra committed 2026-10-13 14:58:03 +11:00
1 parent b2e998665e
commit ffb0f147e1
13 files changed
+380 -102

No files matched your search

+21 -10
View File
@@ -1,4 +1,4 @@
import type { CSSProperties, ReactElement } from 'react'
import type { CSSProperties, ReactElement, ReactNode } from 'react'
import type { RewriteTool } from './BlockTools'
import type { DiffTree, FoldedRuns, PlacedRemoved } from './diffView'
import type { GridSlots } from './PropertyGrid'
@@ -117,11 +117,14 @@ function useBlockTree(): BlockTreeHandle {
return tree
}
/** 一棵块树:blocks 是顶层的块,tree 是它的状态和动作(见 BlockTreeHandle)。 */
export function BlockTree({ blocks, tree }: { blocks: BlockView[], tree: BlockTreeHandle }): ReactElement {
/**
* 一棵块树:blocks 是顶层的块,tree 是它的状态和动作(见 BlockTreeHandle)。
* items:顶层每块(连同它的问答区、子块)套一层 .cr-item,页面上按它分段量位置(见 pageLayout);定义卡片浮窗里只有一块,不套。
*/
export function BlockTree({ blocks, tree, items = false }: { blocks: BlockView[], tree: BlockTreeHandle, items?: boolean }): ReactElement {
return (
<BlockTreeContext value={tree}>
<BlockLevel blocks={blocks} />
<BlockLevel blocks={blocks} items={items} />
</BlockTreeContext>
)
}
@@ -287,8 +290,9 @@ function sameRewrite(a: RewriteShape, b: RewriteShape): boolean {
/**
* 一块:「只看改动」折起来的一段、卡片、下面的问答区和重写记录、子块那一层。块树、问 AI、重写注释三份状态里只订阅这一块自己的,
* 都没变就不重画;子块那一层的各块、卡片的内容各自 memo,这一块重画了它们也不跟着重画。
* item:画出来的卡片、问答区、子块那一层套一层 .cr-item(记着起始行),见 BlockTree。
*/
const BlockItem = memo(({ block }: { block: BlockView }): ReactElement => {
const BlockItem = memo(({ block, item }: { block: BlockView, item: boolean }): ReactElement => {
const tree = useBlockTree()
const { qa, rewrite } = tree
const shape = useStore(tree.state, state => shapeOf(block, state), shallowEqual)
@@ -299,7 +303,7 @@ const BlockItem = memo(({ block }: { block: BlockView }): ReactElement => {
<>
{shape.run !== undefined && <UnchangedStrip count={shape.run} onOpen={() => tree.onOpenRun(block.id)} />}
{shape.shown && (
<>
<Item item={item} line={block.startLine}>
<BlockCard block={block} shape={shape} ask={ask} rewrite={rewriting} />
{/* 输入框、展开状态跟着块的记号走,文档刷新块 id 变了也不丢 */}
{qa && ask.chat && (
@@ -333,20 +337,27 @@ const BlockItem = memo(({ block }: { block: BlockView }): ReactElement => {
/>
)}
{shape.open && <BlockLevel blocks={block.children} owner={block} />}
</>
</Item>
)}
</>
)
})
BlockItem.displayName = 'BlockItem'
/** 顶层一块的外层(见 BlockTree 的 items);不套时原样 */
function Item({ item, line, children }: { item: boolean, line: number, children: ReactNode }): ReactElement {
if (!item)
return <>{children}</>
return <div className="cr-item flex flex-col gap-2" data-line={line}>{children}</div>
}
/**
* 一层代码块:左边一条竖线标出这一层,每块的子块紧跟在它下面、再往右缩进一层。被筛掉、被盖住的块不画,不留空位,竖线跟着变短。
* owner 是这一层所属的块(顶层没有,记在 data-owner 上,钉住卡片时按它算卡片连同子块管到哪、套着几层),悬停它时竖线换成它的种类色。
* 标差异时每张卡片左边外侧一条改动色条,删掉的块收成一行放回原来的位置(见 RemovedCard);「只看改动」时连着没变的块折成一条(UnchangedStrip)。
* slotted:这一层是画在卡片属性表某一行下面的子块(BlockSlot),blocks 只有那几块。
* slotted:这一层是画在卡片属性表某一行下面的子块(BlockSlot),blocks 只有那几块。items:这一层每块套一层 .cr-item(只有页面上的顶层)。
*/
export function BlockLevel({ blocks, owner, slotted = false }: { blocks: BlockView[], owner?: BlockView, slotted?: boolean }): ReactElement {
export function BlockLevel({ blocks, owner, slotted = false, items = false }: { blocks: BlockView[], owner?: BlockView, slotted?: boolean, items?: boolean }): ReactElement {
const tree = useBlockTree()
// 挂在卡片子行上的子块画在卡片属性表里(slotted 的那一层),这一层不画;删掉的块都放回卡片后面这一层
const listed = useMemo(() => slotted ? blocks : blocks.filter(block => !block.slot), [blocks, slotted])
@@ -371,7 +382,7 @@ export function BlockLevel({ blocks, owner, slotted = false }: { blocks: BlockVi
{removedAfter(undefined)}
{listed.map(block => (
<Fragment key={block.id}>
<BlockItem block={block} />
<BlockItem block={block} item={items} />
{removedAfter(block.id)}
</Fragment>
))}