✨ feat(vscode): 代码阅读页面画链式调用卡片:竖轨道、小链、收起的闭包和合成的表

链的各步排在值下面的竖轨道上,方法一列对齐、轨道线是函数色淡到 35%;实参是链的缩进成空心点小链,注释挂在步骤下;收起的闭包是能点的小框,点开那一步才画挂着的子块(折叠 id 记在属性行下面);跳到定义、查找、高度估算、只看改动都算上链里的字和步骤下的子块
This commit is contained in:
cruldra committed 2026-10-15 21:05:07 +11:00
1 parent ee595f3da9
commit 1956879aa1
13 files changed
+531 -47

No files matched your search

+35 -11
View File
@@ -3,7 +3,7 @@ import type { SourceText } from './blockSource'
import type { RewriteTool } from './BlockTools'
import type { DiffTree, FoldedRuns, PlacedRemoved } from './diffView'
import type { Place, Placement } from './placement'
import type { GridSlots } from './PropertyGrid'
import type { ChainSlots, GridSlots } from './PropertyGrid'
import type { ChatState, ChatView, QaState } from './qa'
import type { PendingImage } from './qaImages'
import type { ReadableStore } from './store'
@@ -22,7 +22,7 @@ import { chatOf, EMPTY_CHAT, sameChat, unfinished } from './qa'
import { BlockIdContext } from './SegmentText'
import { shallowEqual, staticStore, useStore } from './store'
import { TextPreview } from './TextPreview'
import { foldsByCard, listId, tableId, textKey } from './viewState'
import { chainStepId, foldsByCard, listId, tableId, textKey } from './viewState'
/** 块树里改代码的那部分状态:哪些块在编辑。页面上的块树才有;定义卡片浮窗里的块树没有,卡片只读,不画右上角的按钮 */
export interface EditState {
@@ -150,10 +150,15 @@ const NO_CHAT: ReadableStore<ChatState> = staticStore(EMPTY_CHAT)
/**
* 块下面有没有画出来的子块(删掉的子块也算):子块都被筛掉、被盖住时不留空的一层、不给折叠箭头。
* 挂在卡片子行上的子块(BlockSlot)画在卡片属性表里,不算。
* 挂在卡片子行上的子块(BlockSlot)、挂在链式调用某一步下面的子块(ChainSlot)画在卡片里,不算。
*/
function hasShownChildren(block: BlockView, state: BlockTreeState): boolean {
return block.children.some(child => !child.slot && shown(child, state)) || (!state.covered.has(block.id) && state.diff?.removed.has(block.id) === true)
return block.children.some(child => !inCard(child) && shown(child, state)) || (!state.covered.has(block.id) && state.diff?.removed.has(block.id) === true)
}
/** 子块画在父卡片里(属性表子行上的 BlockSlot、链式调用某一步下面的 ChainSlot),不排在父卡片后面那一层。 */
function inCard(block: BlockView): boolean {
return block.slot !== undefined || block.step !== undefined
}
const CHANGE_NAMES = { add: '新增', mod: '修改', del: '删除' } as const
@@ -372,12 +377,12 @@ function Item({ item, height, children }: { item: boolean, height: number, child
* 一层代码块:左边一条竖线标出这一层,每块的子块紧跟在它下面、再往右缩进一层。被筛掉、被盖住的块不画,不留空位,竖线跟着变短。
* owner 是这一层所属的块(顶层没有,记在 data-owner 上,钉住卡片时按它算卡片连同子块管到哪、套着几层),悬停它时竖线换成它的种类色。
* 标差异时每张卡片左边外侧一条改动色条,删掉的块收成一行放回原来的位置(见 RemovedCard);「只看改动」时连着没变的块折成一条(UnchangedStrip)。
* slotted:这一层是画在卡片属性表某一行下面的子块(BlockSlot),blocks 只有那几块。items:这一层每块套一层 .cr-item(只有页面上的顶层)。
* slotted:这一层是画在卡片里的子块(属性表某一行下面的 BlockSlot、链式调用某一步下面的 ChainSlot),blocks 只有那几块。items:这一层每块套一层 .cr-item(只有页面上的顶层)。
*/
export function BlockLevel({ blocks, owner, slotted = false, items = false }: { blocks: BlockView[], owner?: CardView, slotted?: boolean, items?: boolean }): ReactElement {
const tree = useBlockTree()
// 挂在卡片子行上的子块画在卡片属性表里(slotted 的那一层),这一层不画;删掉的块都放回卡片后面这一层
const listed = useMemo(() => slotted ? blocks : blocks.filter(block => !block.slot), [blocks, slotted])
// 挂在卡片子行上、链式调用某一步下面的子块画在卡片里(slotted 的那一层),这一层不画;删掉的块都放回卡片后面这一层
const listed = useMemo(() => slotted ? blocks : blocks.filter(block => !inCard(block)), [blocks, slotted])
const { removed, diff } = useStore(tree.state, state => ({
removed: slotted ? NO_REMOVED : state.diff?.removed.get(owner?.id ?? '') ?? NO_REMOVED,
diff: state.diff !== undefined,
@@ -418,12 +423,15 @@ export function BlockLevel({ blocks, owner, slotted = false, items = false }: {
const BlockPreview = memo(({ block, children = NO_CHILDREN, folds, marks }: { block: CardView, children?: BlockView[], folds: ReadonlySet<string>, marks?: BlockMarks }): ReactElement => {
const tree = useBlockTree()
const slots = useMemo(() => cardSlots(block, children, folds, tree), [block, children, folds, tree])
const chain = useMemo(() => chainSlots(block, children, folds, tree), [block, children, folds, tree])
if ('properties' in block)
return <PropertyGrid blockId={block.id} rows={block.properties} collapsed={folds} onToggleFold={tree.onToggleFold} marks={marks?.rows} slots={slots} />
return <PropertyGrid blockId={block.id} rows={block.properties} collapsed={folds} onToggleFold={tree.onToggleFold} marks={marks?.rows} slots={slots} chain={chain} />
const folding = { collapsed: folds, onToggleFold: tree.onToggleFold }
// 整句是链的块译文只剩链外面的词,什么都没剩时不画这一行
const empty = Array.isArray(block.translation) && block.translation.length === 0
return (
<>
{marks?.translation ? <DiffText diff={marks.translation} find={textKey(block.id, 'text')} /> : <TextPreview text={block.translation} find={textKey(block.id, 'text')} />}
{marks?.translation ? <DiffText diff={marks.translation} find={textKey(block.id, 'text')} /> : !empty && <TextPreview text={block.translation} find={textKey(block.id, 'text')} />}
{block.items && (
<div className="mt-[3px]">
<PropertyItems parentId={listId(block.id)} items={block.items} folding={folding} marks={marks?.items} />
@@ -436,8 +444,8 @@ const BlockPreview = memo(({ block, children = NO_CHILDREN, folds, marks }: { bl
)}
{marks?.doc ? <DiffText diff={marks.doc} className="mt-0.5" find={textKey(block.id, 'doc')} /> : block.doc && <TextPreview text={block.doc} className="mt-0.5" find={textKey(block.id, 'doc')} />}
{block.rows && (
<div className="mt-[3px]">
<PropertyGrid blockId={block.id} rows={block.rows} collapsed={folds} onToggleFold={tree.onToggleFold} marks={marks?.rows} />
<div className={empty ? undefined : 'mt-[3px]'}>
<PropertyGrid blockId={block.id} rows={block.rows} collapsed={folds} onToggleFold={tree.onToggleFold} marks={marks?.rows} chain={chain} />
</div>
)}
</>
@@ -467,6 +475,22 @@ function cardSlots(block: CardView, children: readonly BlockView[], folds: Reado
}
}
/**
* 挂在链式调用某一步下面的子块(ChainSlot)怎么画:一步下面的几块画成一层块(同块树);那一步的折叠 id(chainStepId)没折着就画,
* 点那一步收起来的闭包时切换它。卡片没有这样的子块时没有。
*/
function chainSlots(block: CardView, children: readonly BlockView[], folds: ReadonlySet<string>, tree: BlockTreeHandle): ChainSlots | undefined {
const blocks = children.filter(child => child.step)
if (blocks.length === 0)
return undefined
return {
blocks,
render: list => <BlockLevel blocks={list} owner={block} slotted />,
open: slot => !folds.has(chainStepId(block.id, slot)),
onToggle: slot => tree.onToggleFold(chainStepId(block.id, slot)),
}
}
/**
* 一块的卡片:折叠箭头、种类图标、预览内容或源码、右上角的一排图标按钮(BlockTools,只读的块树没有)。
* 卡片上记着块记号(data-anchor),说明里不带路径的引用靠它认自己写在哪块(见 commentLinkOf)。