⚡ perf(vscode): 代码阅读预览发给页面的块只记相对位置:块在源码里占的地方记成离前一块多远、自己多长,块里名字的下标从块的开头算,页面从文件开头累加回文件里的位置、和扩展端打交道的地方都在一处换算;靠前改了代码,后面内容没变的块发过去一模一样,扩展端自己的块和发给页面的块分成两种类型
This commit is contained in:
1 parent
3a2815c19c
commit
ad5f559e92
43 files changed
+980
-424
No files matched your search
@@ -2,11 +2,12 @@ import type { CSSProperties, ReactElement, ReactNode } from 'react'
|
||||
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 { ChatState, ChatView, QaState } from './qa'
|
||||
import type { PendingImage } from './qaImages'
|
||||
import type { ReadableStore } from './store'
|
||||
import type { BlockMarks, BlockView, ChangedBlock } from './types'
|
||||
import type { BlockMarks, BlockView, CardView, ChangedBlock } from './types'
|
||||
import { ChevronDown, ChevronRight, ChevronsUpDown, EyeOff } from 'lucide-react'
|
||||
import { createContext, Fragment, memo, useContext, useMemo, useState } from 'react'
|
||||
import { AskThread, REWRITE_THREAD } from './AskThread'
|
||||
@@ -18,6 +19,7 @@ import { estimatedHeight } from './itemHeight'
|
||||
import { KindIcon } from './kindIcons'
|
||||
import { PropertyGrid, PropertyItems, PropertyTable } from './PropertyGrid'
|
||||
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 } from './viewState'
|
||||
@@ -27,6 +29,8 @@ export interface EditState {
|
||||
version: number
|
||||
/** 这一版文档的源码和着色:编辑框按块的编辑范围从里面取 */
|
||||
sourceText: SourceText
|
||||
/** 这一版文档里各块在文件里的位置:编辑范围、发回改动时的行号按它算 */
|
||||
placement: Placement
|
||||
editing: ReadonlySet<string>
|
||||
/** 刚点了「编辑」、要把光标放进去的块 */
|
||||
focusId: string | null
|
||||
@@ -139,6 +143,7 @@ export function treeState(state: Omit<BlockTreeState, 'folds'>, previous?: Block
|
||||
}
|
||||
|
||||
const NO_FOLDS: ReadonlySet<string> = new Set()
|
||||
const NO_CHILDREN: BlockView[] = []
|
||||
const NO_REMOVED: readonly PlacedRemoved[] = []
|
||||
/** 没有问 AI、重写注释的块树(定义卡片浮窗):一直不能用 */
|
||||
const NO_CHAT: ReadableStore<ChatState> = staticStore(EMPTY_CHAT)
|
||||
@@ -211,8 +216,9 @@ interface BlockShape {
|
||||
focus: boolean
|
||||
/** 编辑框每来一份新文档都要对一次内容;不在编辑的卡片不跟着版本重画 */
|
||||
version?: number
|
||||
/** 编辑着的卡片才有:这一版文档的源码和着色 */
|
||||
/** 编辑着的卡片才有:这一版文档的源码和着色,这块在文件里的位置 */
|
||||
sourceText?: SourceText
|
||||
place?: Place
|
||||
/** 调用树:扩展端标了能看调用树(BlockView.callTree)的块才有棵数 */
|
||||
calls?: number
|
||||
callOpen: boolean
|
||||
@@ -235,6 +241,7 @@ function shapeOf(block: BlockView, state: BlockTreeState): BlockShape {
|
||||
focus: state.edit?.focusId === block.id,
|
||||
version: editing ? state.edit?.version : undefined,
|
||||
sourceText: editing ? state.edit?.sourceText : undefined,
|
||||
place: editing ? state.edit?.placement.of(block.id) : undefined,
|
||||
calls: state.callTree && block.callTree ? state.callTree.counts.get(block.anchor) ?? 0 : undefined,
|
||||
callOpen: state.callTree?.openAnchor === block.anchor,
|
||||
}
|
||||
@@ -273,7 +280,7 @@ function rewriteOf(block: BlockView, state: ChatState): RewriteShape {
|
||||
return {
|
||||
...(block.commentTarget && {
|
||||
tool: {
|
||||
fresh: block.commentTarget.kind === 'insert',
|
||||
fresh: block.commentTarget === 'insert',
|
||||
rewriting: chat.open && chat.asks.some(unfinished),
|
||||
reopen: !chat.open && (chat.asks.length > 0 || chat.history.length > 0),
|
||||
},
|
||||
@@ -310,7 +317,7 @@ const BlockItem = memo(({ block, item }: { block: BlockView, item: boolean }): R
|
||||
<>
|
||||
{shape.run !== undefined && <UnchangedStrip count={shape.run} onOpen={() => tree.onOpenRun(block.id)} />}
|
||||
{shape.shown && (
|
||||
<Item item={item} line={block.startLine} height={item ? estimatedHeight(block, shape.open) : 0}>
|
||||
<Item item={item} height={item ? estimatedHeight(block, shape.open) : 0}>
|
||||
<BlockCard block={block} shape={shape} ask={ask} rewrite={rewriting} />
|
||||
{/* 输入框、展开状态跟着块的记号走,文档刷新块 id 变了也不丢 */}
|
||||
{qa && ask.chat && (
|
||||
@@ -355,10 +362,10 @@ BlockItem.displayName = 'BlockItem'
|
||||
* 顶层一块的外层(见 BlockTree 的 items);不套时原样。离视口远时浏览器跳过它里面的样式、排版和绘制(.cr-item 的 content-visibility),
|
||||
* 没画过时按估的高度(height,见 estimatedHeight)占位,画过一次就记住真实高度。
|
||||
*/
|
||||
function Item({ item, line, height, children }: { item: boolean, line: number, height: number, children: ReactNode }): ReactElement {
|
||||
function Item({ item, height, children }: { item: boolean, height: number, children: ReactNode }): ReactElement {
|
||||
if (!item)
|
||||
return <>{children}</>
|
||||
return <div className="cr-item flex flex-col gap-2" data-line={line} style={{ containIntrinsicBlockSize: `auto ${height}px` }}>{children}</div>
|
||||
return <div className="cr-item flex flex-col gap-2" style={{ containIntrinsicBlockSize: `auto ${height}px` }}>{children}</div>
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -367,7 +374,7 @@ function Item({ item, line, height, children }: { item: boolean, line: number, h
|
||||
* 标差异时每张卡片左边外侧一条改动色条,删掉的块收成一行放回原来的位置(见 RemovedCard);「只看改动」时连着没变的块折成一条(UnchangedStrip)。
|
||||
* slotted:这一层是画在卡片属性表某一行下面的子块(BlockSlot),blocks 只有那几块。items:这一层每块套一层 .cr-item(只有页面上的顶层)。
|
||||
*/
|
||||
export function BlockLevel({ blocks, owner, slotted = false, items = false }: { blocks: BlockView[], owner?: BlockView, slotted?: boolean, items?: boolean }): ReactElement {
|
||||
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])
|
||||
@@ -406,10 +413,11 @@ export function BlockLevel({ blocks, owner, slotted = false, items = false }: {
|
||||
* 预览状态的内容:函数、类、枚举、导入、常量、变量块和库规则的卡片是属性表,其余是一行译文;
|
||||
* 译文下面依次接着拆开的各项(跨几行写的字面量,排法同变量初始值下面的)、摊开实参的表(跨几行写的调用)、说明、几行属性(「注释」一行)。
|
||||
* folds 是这张卡片里折叠着的(见 foldsByCard);标差异时 marks 是这块自己的内容改了哪些(页面上正是差异算的那一版才给)。
|
||||
* children 是这块的子块,挂在子行上的画在属性表里(删掉的块不画子块,没有)。
|
||||
*/
|
||||
const BlockPreview = memo(({ block, folds, marks }: { block: BlockView, folds: ReadonlySet<string>, marks?: BlockMarks }): ReactElement => {
|
||||
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, folds, tree), [block, folds, tree])
|
||||
const slots = useMemo(() => cardSlots(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} />
|
||||
const folding = { collapsed: folds, onToggleFold: tree.onToggleFold }
|
||||
@@ -441,8 +449,8 @@ BlockPreview.displayName = 'BlockPreview'
|
||||
* 挂在卡片子行上的子块(BlockSlot)怎么画:一行下面展开着的几块画成一层块(同块树);点那个子行的第一格展开它、
|
||||
* 同一行里别的收起,展开着的再点收起。卡片没有挂在子行上的子块时没有。
|
||||
*/
|
||||
function cardSlots(block: BlockView, folds: ReadonlySet<string>, tree: BlockTreeHandle): GridSlots | undefined {
|
||||
const blocks = block.children.filter(child => child.slot)
|
||||
function cardSlots(block: CardView, children: readonly BlockView[], folds: ReadonlySet<string>, tree: BlockTreeHandle): GridSlots | undefined {
|
||||
const blocks = children.filter(child => child.slot)
|
||||
if (blocks.length === 0)
|
||||
return undefined
|
||||
return {
|
||||
@@ -473,62 +481,63 @@ function BlockCard({ block, shape, ask, rewrite }: { block: BlockView, shape: Bl
|
||||
const { change } = shape
|
||||
const { tool } = rewrite
|
||||
return (
|
||||
<div
|
||||
className="cr-block relative flex items-start gap-2.5 rounded-lg py-2 pr-2.5 pl-2"
|
||||
{...kindProps(block)}
|
||||
data-id={block.id}
|
||||
data-anchor={anchor}
|
||||
data-line={block.startLine}
|
||||
data-editing={shape.editing ? '' : undefined}
|
||||
data-change={change?.change}
|
||||
>
|
||||
{shape.diff && <ChangeBar change={change?.change} />}
|
||||
{change?.outermost && <span className="cr-added-badge">新增</span>}
|
||||
{shape.hasChildren
|
||||
? (
|
||||
<button
|
||||
type="button"
|
||||
className="flex h-5 w-3.5 shrink-0 items-center justify-center rounded-sm text-[var(--vscode-descriptionForeground)] hover:text-[var(--vscode-foreground)]"
|
||||
aria-label={toggled ? '收起' : collapsed ? '展开子块' : '折叠子块'}
|
||||
aria-expanded={toggled || !collapsed}
|
||||
onClick={() => tree.onToggleFold(block.id)}
|
||||
>
|
||||
<Chevron size={14} strokeWidth={1.8} />
|
||||
</button>
|
||||
)
|
||||
: <span className="w-3.5 shrink-0" />}
|
||||
<KindIcon concepts={block.concepts} />
|
||||
<div className="min-w-0 flex-1">
|
||||
{edit && shape.version !== undefined && shape.sourceText
|
||||
<BlockIdContext value={block.id}>
|
||||
<div
|
||||
className="cr-block relative flex items-start gap-2.5 rounded-lg py-2 pr-2.5 pl-2"
|
||||
{...kindProps(block)}
|
||||
data-id={block.id}
|
||||
data-anchor={anchor}
|
||||
data-editing={shape.editing ? '' : undefined}
|
||||
data-change={change?.change}
|
||||
>
|
||||
{shape.diff && <ChangeBar change={change?.change} />}
|
||||
{change?.outermost && <span className="cr-added-badge">新增</span>}
|
||||
{shape.hasChildren
|
||||
? (
|
||||
<CodeEditor
|
||||
block={block}
|
||||
sourceText={shape.sourceText}
|
||||
version={shape.version}
|
||||
autoFocus={shape.focus}
|
||||
onAutoFocused={edit.onFocused}
|
||||
onExit={() => edit.onToggleEdit(block.id)}
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
className="flex h-5 w-3.5 shrink-0 items-center justify-center rounded-sm text-[var(--vscode-descriptionForeground)] hover:text-[var(--vscode-foreground)]"
|
||||
aria-label={toggled ? '收起' : collapsed ? '展开子块' : '折叠子块'}
|
||||
aria-expanded={toggled || !collapsed}
|
||||
onClick={() => tree.onToggleFold(block.id)}
|
||||
>
|
||||
<Chevron size={14} strokeWidth={1.8} />
|
||||
</button>
|
||||
)
|
||||
: <BlockPreview block={block} folds={shape.folds} marks={shape.current ? change?.marks : undefined} />}
|
||||
: <span className="w-3.5 shrink-0" />}
|
||||
<KindIcon concepts={block.concepts} />
|
||||
<div className="min-w-0 flex-1">
|
||||
{edit && shape.version !== undefined && shape.sourceText && shape.place
|
||||
? (
|
||||
<CodeEditor
|
||||
sourceText={shape.sourceText}
|
||||
place={shape.place}
|
||||
version={shape.version}
|
||||
autoFocus={shape.focus}
|
||||
onAutoFocused={edit.onFocused}
|
||||
onExit={() => edit.onToggleEdit(block.id)}
|
||||
/>
|
||||
)
|
||||
: <BlockPreview block={block} children={block.children} folds={shape.folds} marks={shape.current ? change?.marks : undefined} />}
|
||||
</div>
|
||||
{shape.temporary && (
|
||||
<span className="cr-temporary flex h-5 shrink-0 items-center gap-1 rounded px-1.5 text-[11px] whitespace-nowrap">
|
||||
<EyeOff size={12} strokeWidth={1.8} aria-hidden />
|
||||
已被筛选隐藏,临时显示
|
||||
</span>
|
||||
)}
|
||||
{edit && (
|
||||
<BlockTools
|
||||
editing={shape.editing}
|
||||
onToggleEdit={() => edit.onToggleEdit(block.id)}
|
||||
asking={ask.asking}
|
||||
onToggleAsk={tree.qa && ask.asking !== undefined ? () => tree.qa?.onToggle(anchor) : undefined}
|
||||
rewrite={tree.rewrite && tool && { ...tool, onRewrite: () => tool.reopen ? tree.rewrite?.onOpen(anchor) : tree.rewrite?.onRewrite(anchor) }}
|
||||
callTree={onOpenCall && shape.calls !== undefined ? { anchor, count: shape.calls, open: shape.callOpen, onOpen: () => onOpenCall(anchor) } : undefined}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
{shape.temporary && (
|
||||
<span className="cr-temporary flex h-5 shrink-0 items-center gap-1 rounded px-1.5 text-[11px] whitespace-nowrap">
|
||||
<EyeOff size={12} strokeWidth={1.8} aria-hidden />
|
||||
已被筛选隐藏,临时显示
|
||||
</span>
|
||||
)}
|
||||
{edit && (
|
||||
<BlockTools
|
||||
editing={shape.editing}
|
||||
onToggleEdit={() => edit.onToggleEdit(block.id)}
|
||||
asking={ask.asking}
|
||||
onToggleAsk={tree.qa && ask.asking !== undefined ? () => tree.qa?.onToggle(anchor) : undefined}
|
||||
rewrite={tree.rewrite && tool && { ...tool, onRewrite: () => tool.reopen ? tree.rewrite?.onOpen(anchor) : tree.rewrite?.onRewrite(anchor) }}
|
||||
callTree={onOpenCall && shape.calls !== undefined ? { anchor, count: shape.calls, open: shape.callOpen, onOpen: () => onOpenCall(anchor) } : undefined}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</BlockIdContext>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user