diff --git a/docs/superpowers/specs/code-reader/spec.md b/docs/superpowers/specs/code-reader/spec.md index dc840de..a73ed59 100644 --- a/docs/superpowers/specs/code-reader/spec.md +++ b/docs/superpowers/specs/code-reader/spec.md @@ -19,7 +19,7 @@ | 多种翻译怎么选 | 预览顶部下拉框,整个文件一起换,按语言记住选择;只有一种时不显示 | | 侧栏点文件 | 支持的语言默认打开成「预览」;不支持的语言用普通编辑器打开 | | 块内修改 | 改的就是文件本身:未保存标记、⌘S 保存、⌘Z 撤销都照常,切回「预览」按新代码重新翻译 | -| 折叠 | 有子块的块能折叠,默认全部展开 | +| 折叠 | 有子块的块、属性表里带子行的行能折叠,默认全部展开 | | 侧栏文件树 | 只看和打开:展开目录、打开文件、刷新、全部折叠;不做新建 / 重命名 / 删除 | | 译文用词 | 结构照抄原语言,只翻关键字、内置类型、运算符;用专业术语(`export` → 公共,`catch` → 捕获,`===` → 全等) | | 函数、导入、常量、变量块 | 预览不显示一行译文,显示树形属性表;属性值里的类型、表达式照常直译 | @@ -55,7 +55,7 @@ ### 属性表(函数、导入、常量、变量块) -卡片外观(折叠箭头、种类标签、底色、悬停高亮、「编辑 / 预览」切换)和其他块一样,只是内容换成一张属性表:左列属性名(界面字体、`descriptionForeground` 色,宽 76px),右列是值(等宽字体,按词性着色,长了换行),两列间距 12px,行高 20px。「输入参数」「导入项」下面展开子行,属性名前带一个向下的箭头(固定展开,不单独折叠);子行往右缩进 5px,左边一条 1px 竖线(`editorIndentGuide-background1`)。输入参数的子行分名称 / 类型 / 默认值三列对齐,前两列宽 150px / 220px,编辑区窄时按 28% / 38% 收,默认值列占剩下的。 +卡片外观(折叠箭头、种类标签、底色、悬停高亮、「编辑 / 预览」切换)和其他块一样,只是内容换成一张属性表:左列属性名(界面字体、`descriptionForeground` 色,宽 76px),右列是值(等宽字体,按词性着色,长了换行),两列间距 12px,行高 20px。「输入参数」「导入项」下面展开子行,属性名前带折叠箭头(图标、大小、颜色同块的折叠箭头;点箭头或属性名切换,默认展开,折叠后值「N 个」照常显示,只收起子行);子行往右缩进 5px,左边一条 1px 竖线(`editorIndentGuide-background1`)。输入参数的子行分名称 / 类型 / 默认值三列对齐,前两列宽 150px / 220px,编辑区窄时按 28% / 38% 收,默认值列占剩下的。 | 种类 | 行 | | --- | --- | @@ -320,7 +320,7 @@ type BlockView = BlockViewBase & ({ translation: Segment[] } | { properties: Pro - `{ type: 'ready' }` - `{ type: 'edit', startLine, endLine, text }`:用 `WorkspaceEdit` 把这几行整行替换成 `text`(每行补回原来第一行的缩进)。 - `{ type: 'selectTranslator', id }`:按 `languageId` 记到 `globalState`,重发文档。 -- 块的编辑 / 折叠状态在 webview 里按块 id 记(`vscode.setState`),文档刷新后保留;正在编辑且有焦点的块,刷新时不覆盖输入框内容。 +- 块的编辑 / 折叠状态在 webview 里按块 id 记(`vscode.setState`),属性行的折叠状态记在同一份折叠集合里,id 是 `<块 id>/p<行序号>`(例如 `6.1.0/p3`);文档刷新后保留;正在编辑且有焦点的块,刷新时不覆盖输入框内容。 ## 颜色 diff --git a/vscode/webview-ui/src/codeReader/BlockTree.tsx b/vscode/webview-ui/src/codeReader/BlockTree.tsx index 28f7c9d..5ad4282 100644 --- a/vscode/webview-ui/src/codeReader/BlockTree.tsx +++ b/vscode/webview-ui/src/codeReader/BlockTree.tsx @@ -29,6 +29,7 @@ const KIND_LABEL: Record = { export interface BlockTreeState { version: number editing: ReadonlySet + /** 折叠的块 id 和属性行 id(propertyRowId),默认全部展开 */ collapsed: ReadonlySet /** 刚点了「编辑」、要把光标放进去的块 */ 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 + return return (

@@ -112,7 +113,7 @@ function BlockCard({ block, tree }: { block: BlockView, tree: BlockTreeState }): onExit={() => tree.onToggleEdit(block.id)} /> ) - : } + : } + ) + : {row.label}}

- {items.length > 0 && } + {items.length > 0 && !folded && } ) })} diff --git a/vscode/webview-ui/src/codeReader/viewState.test.ts b/vscode/webview-ui/src/codeReader/viewState.test.ts index 0c92151..74e634f 100644 --- a/vscode/webview-ui/src/codeReader/viewState.test.ts +++ b/vscode/webview-ui/src/codeReader/viewState.test.ts @@ -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']) + }) }) diff --git a/vscode/webview-ui/src/codeReader/viewState.ts b/vscode/webview-ui/src/codeReader/viewState.ts index 7ce8bc3..ff2c725 100644 --- a/vscode/webview-ui/src/codeReader/viewState.ts +++ b/vscode/webview-ui/src/codeReader/viewState.ts @@ -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}` +}