🐛 fix(vscode): 代码阅读属性表的输入参数、导入项可以折叠

This commit is contained in:
cruldra committed 2026-09-25 12:20:37 +08:00
1 parent 846fba92e1
commit e8a6a795f5
5 files changed
+55 -15

No files matched your search

@@ -29,6 +29,7 @@ const KIND_LABEL: Record<BlockKind, string> = {
export interface BlockTreeState {
version: number
editing: ReadonlySet<string>
/** 折叠的块 id 和属性行 id(propertyRowId),默认全部展开 */
collapsed: ReadonlySet<string>
/** 刚点了「编辑」、要把光标放进去的块 */
focusId: string | null
@@ -62,9 +63,9 @@ export function BlockLevel({ blocks, kind, tree }: { blocks: BlockView[], kind?:
}
/** 预览状态的内容:函数、导入、常量、变量块是属性表,其余是一行译文。 */
function BlockPreview({ block }: { block: BlockView }): ReactElement {
function BlockPreview({ block, tree }: { block: BlockView, tree: BlockTreeState }): ReactElement {
if ('properties' in block)
return <PropertyGrid rows={block.properties} />
return <PropertyGrid blockId={block.id} rows={block.properties} collapsed={tree.collapsed} onToggleFold={tree.onToggleFold} />
return (
<p className="cr-code">
<SegmentText segments={block.translation} />
@@ -112,7 +113,7 @@ function BlockCard({ block, tree }: { block: BlockView, tree: BlockTreeState }):
onExit={() => tree.onToggleEdit(block.id)}
/>
)
: <BlockPreview block={block} />}
: <BlockPreview block={block} tree={tree} />}
</div>
<button
type="button"
@@ -1,8 +1,9 @@
import type { ReactElement } from 'react'
import type { PropertyItem, PropertyRow } from './types'
import { ChevronDown } from 'lucide-react'
import { ChevronDown, ChevronRight } from 'lucide-react'
import { Fragment } from 'react'
import { SegmentText } from './SegmentText'
import { propertyRowId } from './viewState'
/** 子行里除最后一列以外各列的宽度(参数的名称、类型),最后一列占满剩下的;编辑区窄时按比例收,不把最后一列挤没 */
const CELL_WIDTHS = ['min(150px, 28%)', 'min(220px, 38%)']
@@ -33,23 +34,41 @@ function PropertyItems({ items }: { items: PropertyItem[] }): ReactElement {
}
/** 函数、导入、常量、变量块的预览:属性名一列对齐,值按词性着色;输入参数、导入项下面展开子行。 */
export function PropertyGrid({ rows }: { rows: PropertyRow[] }): ReactElement {
export function PropertyGrid({ blockId, rows, collapsed, onToggleFold }: {
blockId: string
rows: PropertyRow[]
collapsed: ReadonlySet<string>
onToggleFold: (id: string) => void
}): ReactElement {
return (
<div className="flex flex-col gap-[3px]">
{rows.map((row, index) => {
const items = row.items ?? []
const id = propertyRowId(blockId, index)
const folded = collapsed.has(id)
const Chevron = folded ? ChevronRight : ChevronDown
return (
<Fragment key={index}>
<div className="flex gap-3">
<span className="cr-prop-label flex h-5 w-[76px] shrink-0 items-center gap-1">
{items.length > 0 && <ChevronDown size={12} strokeWidth={1.8} className="shrink-0" />}
{row.label}
</span>
{items.length > 0
? (
<button
type="button"
className="cr-prop-label group flex h-5 w-[76px] shrink-0 items-center gap-1 rounded-sm"
aria-label={`${folded ? '展开' : '折叠'}${row.label}`}
aria-expanded={!folded}
onClick={() => onToggleFold(id)}
>
<Chevron size={14} strokeWidth={1.8} className="shrink-0 text-[var(--vscode-descriptionForeground)] group-hover:text-[var(--vscode-foreground)]" />
{row.label}
</button>
)
: <span className="cr-prop-label flex h-5 w-[76px] shrink-0 items-center">{row.label}</span>}
<p className="cr-code min-w-0 flex-1">
<SegmentText segments={row.value} />
</p>
</div>
{items.length > 0 && <PropertyItems items={items} />}
{items.length > 0 && !folded && <PropertyItems items={items} />}
</Fragment>
)
})}
@@ -1,6 +1,6 @@
import type { ViewState, ViewStateStorage } from './viewState'
import { describe, expect, it } from 'vitest'
import { createViewStateStore } from './viewState'
import { createViewStateStore, propertyRowId } from './viewState'
/** 模拟 VS Code 的 webview 状态:存进去的要能序列化,页面重新加载后原样取回 */
function memoryStorage(): ViewStateStorage {
@@ -34,4 +34,16 @@ describe('webview 状态', () => {
store.update({ scroll: { line: 40, offset: -6, scrollTop: 1210 } })
expect(createViewStateStore(storage).get()).toEqual({ editing: [], collapsed: ['3'], scroll: { line: 40, offset: -6, scrollTop: 1210 } })
})
it('属性行的折叠 id 是块 id 加行序号,不会和块 id 撞上', () => {
expect(propertyRowId('6.1.0', 3)).toBe('6.1.0/p3')
expect(propertyRowId('6', 1)).not.toBe('6.1')
expect(propertyRowId('6.1', 0)).not.toBe(propertyRowId('6', 10))
})
it('属性行和块的折叠状态存在同一份 collapsed 里,页面重新加载后都还在', () => {
const storage = memoryStorage()
createViewStateStore(storage).update({ collapsed: ['6.1', propertyRowId('6.1.0', 3)] })
expect(createViewStateStore(storage).get().collapsed).toEqual(['6.1', '6.1.0/p3'])
})
})
@@ -2,7 +2,7 @@ import type { ScrollAnchor } from './scrollPosition'
/**
* 标签页切走时 VS Code 会销毁 webview、切回来重新加载,靠它恢复:哪些块停在「编辑」、
* 哪些块折叠了(块 id 是它在树里的位置,文档刷新后照旧对得上),以及滚到了哪。
* 哪些块和属性行折叠了(块 id 是它在树里的位置,文档刷新后照旧对得上;属性行 id 见 propertyRowId),以及滚到了哪。
*/
export interface ViewState {
editing: string[]
@@ -32,3 +32,11 @@ export function createViewStateStore(storage: ViewStateStorage): ViewStateStore
},
}
}
/**
* 属性表里带子行的那一行(输入参数、导入项)的折叠 id,和块 id 放在同一个 collapsed 里:
* `<块 id>/p<行序号>`,例如 '6.1.0/p3'。块 id 只由数字和点组成,带 '/' 的不会和任何块 id 撞上。
*/
export function propertyRowId(blockId: string, rowIndex: number): string {
return `${blockId}/p${rowIndex}`
}