495 lines
54 KiB
Markdown
495 lines
54 KiB
Markdown
# 代码阅读(Code Reader)设计
|
||
|
||
## 目标
|
||
|
||
代码几乎全由 agent 写,逐行读太慢。给插件加一个独立的「代码阅读」入口:打开一个源码文件时,可以切到「预览」,把代码拆成一棵代码块树,每块用模板直译成中文,扫一眼就知道这个文件在干什么;要看细节时单块切回源码,并且可以直接改。
|
||
|
||
它和现有的 Superpowers 看板(活动栏小火箭)是两套东西,只是放在同一个插件(`vscode/`)里。
|
||
|
||
视觉和交互以 Figma 原型为准:`docs/prototype/code-reader/figma-plugin/`(`code.js` 里的数据和 `test.js` 里的期望值就是本文的样例)。
|
||
|
||
## 已确认的决定
|
||
|
||
| 事项 | 决定 |
|
||
| --- | --- |
|
||
| 流程 | 源码 →(tree-sitter)语法树 → 通用代码块树 → 译文 |
|
||
| 第一版翻译 | 只做中文模板直译;AI 解读以后作为另一个翻译插件加 |
|
||
| 第一版语言 | TypeScript / TSX(含 JavaScript / JSX)、Python |
|
||
| 扩展方式 | 写代码注册:语言插件、翻译插件都实现约定接口,通过本插件 `activate()` 导出的 API 注册;内置实现也走同一个 API |
|
||
| 多种翻译怎么选 | 预览顶部下拉框,整个文件一起换,按语言记住选择;只有一种时不显示 |
|
||
| 侧栏点文件 | 支持的语言默认打开成「预览」;不支持的语言用普通编辑器打开 |
|
||
| 块内修改 | 改的就是文件本身:未保存标记、⌘S 保存、⌘Z 撤销都照常,切回「预览」按新代码重新翻译 |
|
||
| 折叠 | 有子块的块、属性表里带子行的行能折叠;函数块(含方法、嵌套函数)和类块默认收起主体、属性表照常显示,其余块和属性行默认展开;用户点过的按他点的记住 |
|
||
| 函数、类的说明 | TypeScript 紧贴在函数、类前面的 JSDoc,Python 函数、类主体开头的 docstring 是它自己的说明:进属性表「说明」一行,不再单独成一块注释;它的源码行算在函数、类块的头部里,「编辑」时和签名一起改 |
|
||
| 注释按 Markdown 显示 | 注释块和函数、类的「说明」按 Markdown 渲染,像 Kotlin 的 KDoc:去掉注释符号、引号和共同缩进后的原文原样交给 Markdown,不按语言转换(reStructuredText 的 `:func:`、JSDoc 的 `@param` 都照抄,注释以后慢慢改成 Markdown 写法);段内单个换行是软换行,空行分段 |
|
||
| 跳到定义 | 和普通编辑器一样 ⌘ / Ctrl + 点击名字跳到定义,定义由 VS Code 装的语言扩展(Pylance、TypeScript 语言服务……)查;目标语言有代码阅读就在预览里打开 |
|
||
| 侧栏文件树 | 只看和打开:展开目录、打开文件、刷新、全部折叠,外加给目录 / 文件记注释;不做新建 / 重命名 / 删除 |
|
||
| 文件注释 | 读代码时给目录 / 文件随手记一句:存在各工作区文件夹的 `.spx/code-notes.json`,跟着仓库走;在 VS Code 里改名、挪位置、删除时注释跟着变 |
|
||
| 译文用词 | 结构照抄原语言,只翻关键字、内置类型、运算符;用专业术语(`export` → 公共,`catch` → 捕获,`===` → 全等) |
|
||
| 函数、类、导入、常量、变量块 | 预览不显示一行译文,显示树形属性表;属性值里的类型、表达式照常直译 |
|
||
|
||
## 用户可见行为
|
||
|
||
### 活动栏与侧栏
|
||
|
||
- 活动栏新增图标(书本,`book-open-text` 样式),视图容器标题「代码阅读」,里面一个视图「代码阅读」。
|
||
- 视图是工作区文件树:每个工作区文件夹一个根节点(默认展开),目录在前、文件在后,名字不区分大小写排序;遵守 `files.exclude`。
|
||
- 树节点用 `resourceUri`,图标跟随用户的文件图标主题,git 改动颜色(M / U)由 VS Code 自动加上,外观与自带资源管理器一致。
|
||
- 视图标题栏:刷新、全部折叠(`showCollapseAll`)。文件变化时自动刷新(防抖)。
|
||
- 点文件:语言受支持 → 用「代码阅读预览」自定义编辑器打开;否则 `vscode.open`。
|
||
|
||
### 文件注释
|
||
|
||
读代码时给目录或文件随手记一句(原型画板「文件注释 · 悬停出按钮 / 填写 / 已保存」)。
|
||
|
||
- 鼠标停在目录 / 文件行上,行尾出现按钮(`view/item/context` 的 `inline` 组):没注释时「添加注释」(`$(comment)`),有注释时「编辑注释」(`$(edit)`)。右键菜单里也有同一个命令。工作区根节点没有。
|
||
- 两个命令只挂在侧栏行上,命令面板里藏起来(`when: false`):面板里没有「哪一行」可言,按当前文件记还得另判断它有没有注释,没有必要。
|
||
- 点按钮弹 VS Code 顶部的单行输入框(`window.showInputBox`):标题 `添加注释 · <名字>` / `编辑注释 · <名字>`,编辑时预填原注释(整段选中),提示 `按 Enter 保存,按 Esc 取消;清空后保存即删除注释`。回车保存(去掉首尾空白),Esc 或点别处不改,清空后保存即删除。VS Code 会在提示后面自己再补一句「(Press 'Enter' to confirm or 'Escape' to cancel)」,关不掉。
|
||
- 显示:有注释的行名字后面灰字是注释(`TreeItem.description`,放不下 VS Code 自己截断);悬停提示是 Markdown,第一行粗体的相对路径,下一行注释全文,注释里的 Markdown 符号原样显示。没注释的行和原来一样。
|
||
- contextValue:工作区根 `root`,目录 `folder`,文件 `file`;有注释的后面加 ` noted`。菜单按 `viewItem =~ /^(file|folder)$/`(添加)和 `/^(file|folder) noted$/`(编辑)切换。
|
||
- 存储:每个工作区文件夹一份 `<工作区文件夹>/.spx/code-notes.json`,`{ "相对路径": "注释" }`,路径用 `/` 分隔、相对这个工作区文件夹;和 `.spx` 里别的文件同一套写法:文件缺失或解析失败当作没有注释,写时按需建 `.spx`,两格缩进、键排好序、末尾换行。存第一条注释前不生成文件;注释删光后文件留着(`{}`)。
|
||
- 在 VS Code 里改名、挪位置(资源管理器拖动、`WorkspaceEdit` 等,走 `workspace.onDidRenameFiles`):注释跟着走,目录连带里面各层的注释一起走;挪到另一个工作区文件夹就搬到那边的文件里,挪出工作区就不要了。删除(`onDidDeleteFiles`):连带目录里各层的注释一起删。在 VS Code 外面(终端、git)改名删除不跟。
|
||
- 外部改了 `code-notes.json`(git pull、手改):文件监听重读,树上跟着变。
|
||
- 刷新:只重画注释变了的那几行(文件树按 Uri 记住交给 VS Code 的节点对象,VS Code 按对象认行);还没展开过的行不用管。
|
||
- 注释和代码阅读的解析、翻译无关,单独放在 `notes/`;文件树只留一个「给行添信息」的接口(`FileTreeDecorator`),由 `index.ts` 接上。
|
||
|
||
### 编辑器右上角切换
|
||
|
||
- 受支持语言的文件:普通编辑器里标题栏显示「预览」,预览里显示「编辑」。点一下在**同一个标签页位置**换成另一种:用 VS Code 内部的 `reopenActiveEditorWith`(「重新打开编辑器的方式」背后的机制)原位替换。不能用「另开再关原标签」:文件有未保存改动时关原标签会弹保存框,选「不保存」会丢改动。
|
||
- 用 `editor/title` 的 `navigation` 组、不带图标的命令,让按钮显示文字。若实测 VS Code 不渲染文字,改成带图标(`$(open-preview)` / `$(go-to-file)`),标题保持「预览」「编辑」。VS Code 标题栏做不出原型里的分段控件,这是和原型唯一的有意差异。
|
||
- 受支持语言 = 当前注册的语言插件覆盖的 `languageId`,通过 `setContext('superpowers.codeReader.languageIds', [...])` 提供给 when 子句(`resourceLangId in superpowers.codeReader.languageIds`)。
|
||
|
||
### 预览(代码块树)
|
||
|
||
- 每一句是一块:导入、函数、类、类型、字段、常量、变量、条件、否则、循环、尝试、捕获、最终、返回、语句、注释。
|
||
- 嵌套的块放在父块下面、往右缩进;每一层左边一条竖线(颜色 `--vscode-editorIndentGuide-background`)。
|
||
- 块的组成(从左到右):折叠箭头(只有有子块的块才显示,其余占位对齐)、种类标签、内容、右侧切换按钮。
|
||
- 种类标签和底色按种类区分(颜色见下表);注释块虚线框。
|
||
- 内容两种状态,每块单独切:
|
||
- 预览:函数、类、导入、常量、变量块是属性表(见下节),注释块是 Markdown 说明(见「注释的 Markdown 排版」),其余块是一行译文;按词着色,等宽字体(中文部分用系统中文字体回退)。
|
||
- 编辑:这一块的源码(按语法树着色),可以直接改;蓝色边框。按钮文字「预览」;Esc 也回到预览。
|
||
- 复合语句(函数、条件、循环……)块里只放它自己的头部那几行(主体交给子块);收尾括号、空行不成块。函数、类的说明(JSDoc / docstring)也算在函数、类块的头部里。
|
||
- 折叠:函数、类块打开时默认收起子块(属性表照常显示),其余块默认展开;用户点过折叠箭头的块按他点的,记在视图状态里(见「预览数据与消息」)。
|
||
- 顶部工具栏:翻译下拉框(多于一种翻译时才显示)。
|
||
- 解析失败:显示错误原因,并提示用「编辑」查看源码。
|
||
|
||
### 属性表(函数、类、导入、常量、变量块)
|
||
|
||
卡片外观(折叠箭头、种类标签、底色、悬停高亮、「编辑 / 预览」切换)和其他块一样,只是内容换成一张属性表:左列属性名(界面字体、`descriptionForeground` 色,宽 76px),右列是值(等宽字体,按词性着色,长了换行),两列间距 12px,行高 20px。「输入参数」「导入项」下面展开子行,属性名前带折叠箭头(图标、大小、颜色同块的折叠箭头;点箭头或属性名切换,默认展开,折叠后值「N 个」照常显示,只收起子行);子行往右缩进 5px,左边一条 1px 竖线(`editorIndentGuide-background1`)。输入参数的子行分名称 / 类型 / 默认值三列对齐,前两列宽 150px / 220px,编辑区窄时按 28% / 38% 收,默认值列占剩下的。
|
||
|
||
| 种类 | 行 |
|
||
| --- | --- |
|
||
| 函数 | 名称(函数色;匿名函数写「匿名」)/ 说明(有说明才显示:JSDoc 去掉 `/**`、`*/` 和每行开头的 `*`,docstring 去掉引号;再去掉共同缩进和首尾空行,`@param` 这类标签行照抄;得到的原文按 Markdown 渲染,见「注释的 Markdown 排版」;里面的名字不能点)/ 访问级别 / 异步(是 / 否)/ 装饰器(有才显示,源码原样照抄、按源码着色,多个换行)/ 输入参数(`N 个` 或「无」,每个参数一行:名称 \| 类型(没标注写「未标注」)\| 默认值(`默认 ` + 译文,没有留空))/ 输出参数(返回类型的译文,没标注写「未标注」) |
|
||
| 类 | 名称(类型色;匿名类 `export default class {}` 写「匿名」;赋给变量、类字段的类表达式写变量名、字段名)/ 说明(有说明才显示,规则同函数)/ 访问级别 / 抽象(是 / 否,只有 TypeScript 有,看 `abstract class`,TypeScript 的类都有这一行)/ 装饰器(同函数)/ 类型参数(有才显示:TypeScript `<K, T extends X = Y>` 每个参数的译文,逗号隔开,如 `K, T 限定为 X = Y`;Python 3.12 的 `class G[T]` 同样)/ 父类(有才显示:TypeScript `extends` 后面的表达式连着泛型实参的译文,如 `Base<字符串>`;Python 类名后面括号里的参数列表照写的顺序逐项翻,关键字参数、`*bases` 也在这一行,如 `Base, metaclass=ABCMeta`;逗号隔开)/ 实现的接口(有才显示:TypeScript `implements` 后面每个类型的译文,逗号隔开) |
|
||
| 导入 | 来源(TypeScript 是字符串字面量原样,如 `'node:fs'`,字符串色;Python 是点分模块名,如 `fastapi`、`.models`,类型色)/ 仅类型(是 / 否,只有 TypeScript 有,看 `import type`)/ 导入项(`N 个` 或「无」,每项一行:`name`、`name 作为 alias`;默认导入写「默认导出 作为 x」,`* as x` 写「全部 作为 x」,Python `from m import *` 写「全部」,Python `import a.b as c` 写「整个模块 作为 c」,一条 `import a, b` 导入多个模块时写模块名) |
|
||
| 常量 / 变量 | 名称(解构声明写整个模式,如 `{ p, q }`)/ 访问级别 / 类型(写了标注才有)/ 值(常量)或初始值(变量):初始化表达式的译文,没有写「无」。一条语句声明几个变量(`let a = 1, b = 2`)就重复几组 |
|
||
|
||
访问级别(公共 / 私有 / 受保护):
|
||
|
||
- TypeScript 模块里的声明(函数、类、变量):`export` 了是公共(含 `export default`),顶层没 `export` 是私有;嵌套在函数体、条件等语句块里的没有这一行。
|
||
- TypeScript 类成员(方法、函数值字段、类表达式字段):写了 `public` / `private` / `protected` 按写的,`#name` 是私有,都没写是公共。
|
||
- Python:只有模块顶层和类主体里的声明(函数、类、变量)有;名字单下划线开头(`_x`、`__x`)是私有,前后双下划线(`__init__`)是公共,其余公共;解构赋值里的名字全是私有才算私有;赋值目标是属性、下标(`self.x = …`)时没有这一行。
|
||
|
||
值里的表达式只翻落在块头部里的那一段:`const v = useMemo(() => { … })` 这种值带语句块主体回调的块,回调主体是子块,值只翻到 `useMemo(() =>`,和块头部一致。
|
||
|
||
### 注释的 Markdown 排版
|
||
|
||
- 用在:注释块(`#`、`//`、`/* */`、不挂在函数、类上的 JSDoc、Python 模块开头的 docstring)的预览,函数、类属性表的「说明」。视图数据里它们是 `{ markdown }`(见「通用契约」的 `Markdown`),webview 按数据决定怎么渲染,不猜。
|
||
- 原文:翻译插件去掉注释符号(`#`、`//`、`/*` `*/` 和每行开头的 `*`)和引号,再去掉行尾空白、首尾空行和共同缩进,保留换行,不做别的转换。一个注释节点(块注释、docstring)的第一行紧跟在注释符号 / 引号后面,不参与算共同缩进(同 PEP 257);几条行注释连成的一块每行地位相同,第一行也算,缩进四格的代码块才留得住。
|
||
- 渲染:webview 用 markdown-it(`html: false`、`linkify: true`),CommonMark 加 GFM 的表格、删除线:段落、强调、行内代码(含双反引号)、代码块(围栏、缩进四格)、列表、链接、标题、引用、表格。段内单个换行是软换行,空行分段。同一段文字只渲染一次(缓存最近 500 段)。
|
||
- 安全:源码里的 HTML 当文字显示;链接只认 http / https / mailto,带协议的网址和邮箱自动成链接(`sample.py` 这种不带协议的不算),别的写法(`javascript:`、`command:`、相对路径)照原文显示。点链接由 VS Code 的 webview 自己拦下、交给宿主在外部打开,不用另发消息。图片不渲染(webview 的 CSP 不放行外部图片),`` 显示成 `!` 加链接。
|
||
- 样子:正文用界面字体(`--vscode-font-family`、`--vscode-font-size`)、comment 色、行高 20px,一行注释和一行译文一样高;行内代码用编辑器字体、`--vscode-textCodeBlock-background` 底;围栏代码块用编辑器字体和字号、同样的底,长行折行;段落、列表、代码块、表格之间 6px;标题只比正文稍大(h1 1.15em、h2 1.08em,其余同正文,加粗);链接 `--vscode-textLink-foreground`;引用左边 3px `--vscode-textBlockQuote-border` 竖线、`--vscode-textBlockQuote-background` 底;表格 1px 细线。
|
||
- 「编辑」状态照旧是源码,编辑写回不变;Markdown 里的名字不能点,不参与跳到定义。
|
||
|
||
### 跳到定义
|
||
|
||
- 能点的:预览里(属性表的名称、类型、来源、导入项、默认值、值、类型参数、父类、实现的接口,以及一行译文)照抄自源码的名字——标识符、属性名、类型名、点分模块名的每一节(`luffy_agent.core.db` 三节各自能点,和 VS Code 一样)、字符串(TypeScript 的模块路径 `'./x'` 就是字符串)。翻出来的关键字、运算符、说明词不能点。
|
||
- 链接和编辑器里一样:只有按住修饰键(macOS ⌘,其余平台 Ctrl)、鼠标停在**查得到定义**的名字上时,名字加下划线、换成链接色(`--vscode-editorLink-activeForeground`)、光标变手形;松开修饰键或移开就恢复。有没有定义问扩展端(`vscode.executeDefinitionProvider`,文档 uri + 名字在源码里的位置);和编辑器一样,每回指到一个名字上(或指着名字按下修饰键、换了文档版本)都现问,回答不留到下次用——Pylance、tsserver 刚启动那几秒会答「没有」;只认最近一次询问的回答,迟到的丢掉。
|
||
- ⌘ / Ctrl + 点击:
|
||
- 一处定义,在当前文件:原地滚过去;
|
||
- 一处定义,另一个文件,语言有代码阅读:在同一个编辑器组用预览打开(`vscode.openWith`),滚过去;这个文件的预览已经在这一组开着就切过去、发给它,不重新打开;
|
||
- 一处定义,语言没有代码阅读:在同一个编辑器组用普通编辑器打开,选中定义处的名字;
|
||
- 几处定义:先弹挑选框(每项「相对路径:行号」,下面一行是那行源码),挑一处再按上面跳;
|
||
- 没有定义:不动。
|
||
- 和普通编辑器里 ⌘ + 点击一样:目标是否按「预览标签」打开听 `workbench.editor.enablePreview`;`workbench.editor.enablePreviewFromCodeNavigation` 为假(默认)时先把当前预览标签固定住,免得被目标替掉。
|
||
- 闪哪一处:页面上**就是这处定义**的最具体的那一处,只按源码位置判断(定义提供者给的定义处下标,对上块和属性行带的源码范围),不看属性名文字:
|
||
1. 定义是块自己的名字(函数名、类名;只声明一个变量时的变量名,解构模式整个算)→ 整块。块的这段范围由扩展端从 facts 取(`BlockView.defines`),和翻译插件无关。
|
||
2. 定义是属性表里某一行 / 子行讲的东西(参数、导入项、一条语句声明几个变量时的某一个、类的泛型参数)→ 只闪这一行 / 子行。范围是翻译插件从 facts 取的节点(`PropertyRow.defines`、`PropertyItem.defines`),定义处落在范围里就算(`*args` 的定义在 `args` 上、解构参数里的名字都对得上)。
|
||
3. 其余(定义在一行译文里,例如循环变量;翻译插件没填 `defines`)→ 显示着定义处那个名字的块,名字在属性行的子行里时展开那一行;页面上找不到这个名字就取包含定义那一行的块。
|
||
- 同一文件原地跳、跳进另一个文件的预览,规则一样。
|
||
- 滚过去:目标所在块的祖先块折叠着就展开,目标在折叠的属性行子行里(参数、导入项)就展开那一行。目标已在视口里不滚,否则滚到视口正中(比视口高就顶部对齐)。然后目标的边框和底色往聚焦色(`--vscode-focusBorder`)闪三下,共约 1.5 秒后复原;同一处再跳一次从头闪;系统开了「减少动态效果」时改成静止高亮 1.5 秒。目标是属性行 / 子行但块正在编辑(页面上没有属性表)时闪整块。
|
||
- 不按修饰键的点击照旧;⌘ / Ctrl + 点击名字不会触发块的编辑切换、折叠。
|
||
|
||
## 通用契约
|
||
|
||
放在 `vscode/src/codeReader/contract.ts`,语法树类型用 `@vscode/tree-sitter-wasm` 自带的 web-tree-sitter 类型。
|
||
|
||
```ts
|
||
export type BlockKind =
|
||
| 'import' | 'function' | 'class' | 'type' | 'field' | 'const' | 'variable'
|
||
| 'if' | 'else' | 'loop' | 'try' | 'catch' | 'finally' | 'return' | 'statement' | 'comment'
|
||
|
||
/** 源码里一段的下标(源码字符串下标),end 为开区间。 */
|
||
export interface SourceRange { startIndex: number, endIndex: number }
|
||
|
||
/** 源码里的一段,带起止行号(从 0 开始)。 */
|
||
export interface SourceSpan extends SourceRange { startRow: number, endRow: number }
|
||
|
||
// ── 属性表块的结构化信息(facts):语言插件从语法树读出,节点都指向源码 ──
|
||
|
||
export type Access = 'public' | 'private' | 'protected'
|
||
|
||
export interface ParameterFacts {
|
||
name: Node // 解构参数是整个模式,...rest / *args / **kwargs 带前缀
|
||
optional: boolean // TypeScript 的 x?
|
||
type?: Node
|
||
default?: Node
|
||
}
|
||
|
||
export interface FunctionFacts {
|
||
name?: Node // 匿名函数没有
|
||
doc?: Node // 函数的说明:紧贴在前面的 JSDoc 注释节点(TS)、主体开头的 docstring 字符串节点(Python)
|
||
access?: Access // 嵌套在语句块里的函数没有
|
||
async: boolean
|
||
decorators: Node[]
|
||
parameters: ParameterFacts[]
|
||
returnType?: Node
|
||
}
|
||
|
||
/** 父类的一项:TS `extends Base<T>` 的 Base(表达式)和它后面的泛型实参 <T>;Python 类参数列表的一项(含 metaclass=…、*bases)。 */
|
||
export interface BaseFacts { value: Node, typeArguments?: Node }
|
||
|
||
export interface ClassFacts {
|
||
name?: Node // 匿名类没有;赋给变量、类字段的类表达式取变量名、字段名
|
||
doc?: Node // 同 FunctionFacts.doc
|
||
access?: Access // 嵌套在语句块里的类没有
|
||
abstract?: boolean // 只有 TypeScript 填
|
||
decorators: Node[]
|
||
typeParameters: Node[] // 每个泛型参数一项
|
||
bases: BaseFacts[]
|
||
implements: Node[] // TypeScript 的 implements,每个接口一项
|
||
}
|
||
|
||
export type ImportItem =
|
||
| { kind: 'named', name: Node, alias?: Node } // import { a as b }、from m import a as b
|
||
| { kind: 'default', alias: Node } // import x from 'm'
|
||
| { kind: 'namespace', alias?: Node } // import * as x、from m import *
|
||
| { kind: 'module', module: Node, alias?: Node } // import a.b as c、import x = require('m')
|
||
|
||
export interface ImportFacts {
|
||
sources: Node[] // 来源模块的写法;Python 的 import a, b 有多个
|
||
typeOnly?: boolean // 只有 TypeScript 填
|
||
items: ImportItem[]
|
||
}
|
||
|
||
export interface DeclaratorFacts { name: Node, type?: Node, value?: Node }
|
||
|
||
export interface VariableFacts {
|
||
access?: Access
|
||
declarators: DeclaratorFacts[]
|
||
}
|
||
|
||
export interface BlockShape {
|
||
/** 这一块对应的语法树节点(整条语句)。 */
|
||
node: Node
|
||
/** 块自己显示 / 编辑的那一段:复合语句只到主体开始之前,其余是整条语句;函数、类的说明(doc)也算在里面。 */
|
||
header: SourceSpan
|
||
children: CodeBlock[]
|
||
}
|
||
|
||
/** 预览成属性表的块:种类和 facts 绑在一起,语言插件漏给 facts 编译不过。 */
|
||
export interface FunctionBlock extends BlockShape { kind: 'function', facts: FunctionFacts }
|
||
export interface ClassBlock extends BlockShape { kind: 'class', facts: ClassFacts }
|
||
export interface ImportBlock extends BlockShape { kind: 'import', facts: ImportFacts }
|
||
export interface VariableBlock extends BlockShape { kind: 'const' | 'variable', facts: VariableFacts }
|
||
export type FactBlock = FunctionBlock | ClassBlock | ImportBlock | VariableBlock
|
||
/** 带说明(facts.doc)的块。 */
|
||
export type DocumentedBlock = FunctionBlock | ClassBlock
|
||
|
||
export type TextKind = Exclude<BlockKind, FactBlock['kind']>
|
||
/** 预览成一行译文的块(注释块是 Markdown 说明)。 */
|
||
export interface TextBlock extends BlockShape { kind: TextKind }
|
||
|
||
export type CodeBlock = TextBlock | FactBlock
|
||
|
||
/** 着色用的词性,源码着色和译文着色共用。 */
|
||
export type TokenRole =
|
||
| 'keyword' // 流程关键字:import from export if else for return try catch await …
|
||
| 'storage' // 声明关键字与字面量关键字:const function async class interface new true false null …
|
||
| 'operator' // 被翻成文字的运算符:全等 并且 或者 非
|
||
| 'hint' // 属性表里的说明词:未标注 无 N 个 默认导出 …
|
||
| 'variable' | 'function' | 'type' | 'string' | 'number' | 'literal' | 'comment' | 'plain'
|
||
|
||
/**
|
||
* offset:这段照抄自源码里的名字(标识符、属性名、类型名、点分模块名的一节)或字符串时,它在源码里的起始下标,
|
||
* 预览拿它向 VS Code 查定义、跳过去;译词、提示词没有。由从语法树叶子生成这段的一方填(模板直译引擎的译文和属性表);
|
||
* 第三方翻译插件可以不填,不填就不能跳。
|
||
*/
|
||
export interface Segment { text: string, role: TokenRole, offset?: number }
|
||
|
||
/**
|
||
* 一段 Markdown 说明文字:注释、JSDoc、docstring 去掉注释符号、引号和共同缩进后的原文,不做别的转换。
|
||
* 预览按 Markdown 渲染成注释色的段落、列表、代码块(见「注释的 Markdown 排版」);第三方翻译插件给注释也填它。
|
||
*/
|
||
export interface Markdown { markdown: string }
|
||
|
||
/** 预览里显示的一段文字:着色的词(一行译文、属性值),或整段 Markdown 说明(注释块、函数和类的说明)。 */
|
||
export type PreviewText = Segment[] | Markdown
|
||
|
||
/**
|
||
* 属性表里的一行或一个子行讲的是哪个定义:定义出来的那个名字的节点(取自 facts)在源码里的范围——
|
||
* 参数名(连 `*` 前缀、整个解构模式)、导入项(名称到别名)、声明的变量名、类的泛型参数(整串)。跳到的定义落在里面就只闪这一行;
|
||
* 第三方翻译插件可以不填,不填就闪整块。
|
||
*/
|
||
export interface Defines { defines?: SourceRange }
|
||
|
||
/** 属性表里展开的一个子行:几列对齐的格子。 */
|
||
export interface PropertyItem extends Defines { cells: Segment[][] }
|
||
|
||
/** 属性表的一行:属性名、着色的值或 Markdown 说明(函数、类的「说明」)、展开在下面的子行(输入参数、导入项)。 */
|
||
export interface PropertyRow extends Defines { label: string, value: PreviewText, items?: PropertyItem[] }
|
||
|
||
/** 语言插件:语法树 → 代码块树。 */
|
||
export interface LanguageAdapter {
|
||
id: string
|
||
/** 覆盖的 VS Code languageId,例如 ['typescript', 'typescriptreact', 'javascript', 'javascriptreact']。 */
|
||
languageIds: string[]
|
||
/** 该 languageId 用的语法包 .wasm 的绝对路径(宿主负责加载,保证和解析器运行时是同一份)。 */
|
||
grammarPath: (languageId: string) => string
|
||
toBlocks: (root: Node, source: string) => CodeBlock[]
|
||
/** 源码着色:给语法树叶子节点定词性。 */
|
||
tokenRole: (leaf: Node) => TokenRole
|
||
}
|
||
|
||
/** 翻译插件:代码块 → 一行译文或属性表。 */
|
||
export interface Translator {
|
||
id: string // 'typescript.zh-template'
|
||
label: string // '中文直译'
|
||
languageIds: string[]
|
||
translate: (block: TextBlock, source: string) => PreviewText // 只翻 header 那一段;注释块给 Markdown 原文
|
||
describe: (block: FactBlock, source: string) => PropertyRow[] // 函数、类、导入、常量、变量块 → 属性表
|
||
}
|
||
|
||
export interface CodeReaderApi {
|
||
registerLanguage: (adapter: LanguageAdapter) => Disposable
|
||
registerTranslator: (translator: Translator) => Disposable
|
||
}
|
||
```
|
||
|
||
- `activate()` 返回 `{ codeReader: CodeReaderApi }`;其他扩展声明 `extensionDependencies` 后用 `extensions.getExtension('clurdra.superpowers-vscode-clurdra')!.exports.codeReader` 注册。
|
||
- 内置的 TypeScript、Python 语言插件和两个中文直译翻译插件在 `activate()` 里通过同一个 API 注册,没有旁路。
|
||
- 注册表变化时更新 `superpowers.codeReader.languageIds` 上下文,并让已打开的预览刷新。
|
||
- 分工:语言插件只从语法树读出和语言无关的事实(facts,引用源码节点,不做正则匹配文字);翻译插件决定属性名、枚举词(公共 / 是 / 默认导出)和值怎么译。第三方语言插件给函数、类、导入、常量、变量块填 facts 就能用上任何翻译插件的属性表。
|
||
|
||
## 解析运行时
|
||
|
||
- 依赖 `@vscode/tree-sitter-wasm`(VS Code 团队维护,包含 web-tree-sitter 运行时和 TypeScript / TSX / JavaScript / Python 语法包,版本配套)。
|
||
- 打包:`esbuild.mjs` 构建时把 `tree-sitter.wasm` 和用到的语法包复制到 `dist/tree-sitter/`(`package.json` 的 `files` 已包含 `dist/**/*`)。
|
||
- 运行时初始化一次 `Parser.init({ locateFile })` 指向 `dist/tree-sitter/tree-sitter.wasm`;语法包按路径懒加载并缓存。
|
||
- 文件扩展名到语法包:`.ts` → typescript;`.tsx` → tsx;`.js/.jsx/.mjs/.cjs` → javascript(tree-sitter-javascript 支持 JSX);`.py` → python。以 `languageId` 为准。
|
||
|
||
## 代码块树规则
|
||
|
||
### TypeScript / JavaScript
|
||
|
||
- 顶层与语句块内的每条语句一块;连续的行注释合成一块注释。
|
||
- JSDoc(以 `/**` 开头的块注释,`/**/` 不算)和下一条语句之间只有空白(空行也算,中间隔着装饰器也算)、那条语句是函数或类块时,它是这个函数、类的说明:进 `facts.doc`,头部往上延到注释开头,不单独成块。其余情况(后面不是函数、类块,中间隔着别的注释,在上一条语句的行尾)照旧是注释块。`//` 行注释和普通 `/* */` 块注释不变。
|
||
- `import_statement` → import。
|
||
- `export_statement` 包着声明 → 按声明定种类,节点取整个 `export_statement`(头部包含 `export`)。
|
||
- `function_declaration` / `generator_function_declaration` / `method_definition` → function,子块来自主体语句。
|
||
- 变量声明的唯一声明值是带语句块主体的箭头函数 / 函数表达式 → function(React 组件、回调常见写法),子块来自主体;是类表达式(`const A = class {}`)→ class。
|
||
- `lexical_declaration` / `variable_declaration`:模块顶层的 `const` → const,其余 → variable。
|
||
- `class_declaration` / `abstract_class_declaration`、`export default class {}` → class,子块:方法 → function,字段 → field(值是带语句块主体的函数 → function,值是类表达式 → class),注释 → comment。
|
||
- `interface_declaration` → type,子块:属性 / 方法签名 → field;`type_alias_declaration` → type;`enum_declaration` → type,成员 → field。
|
||
- `if_statement` → if,子块来自 consequence;`else` / `else if` 展平成紧跟其后的兄弟块 else(`else if` 的头部是 `else if (...)`,子块来自它的 consequence)。
|
||
- `for` / `for…in` / `for…of` / `while` / `do` → loop。
|
||
- `try_statement` → try;`catch_clause` → 兄弟块 catch;`finally_clause` → 兄弟块 finally。
|
||
- `switch_statement` → if,每个 `case` / `default` → 子块 if。
|
||
- `return_statement` → return;其余语句(含 `throw`)→ statement。
|
||
- 表达式语句的参数里带语句块主体的函数(`useEffect(() => { … })`、`describe('x', () => { … })`)→ statement,子块来自那个函数主体,头部到主体开始为止。
|
||
|
||
### Python
|
||
|
||
- `import_statement` / `import_from_statement` / `future_import_statement` → import。
|
||
- `function_definition` → function;`decorated_definition` 按内部定义定种类,头部包含装饰器。
|
||
- `class_definition` → class;类体里的带注解赋值 → field。
|
||
- 模块开头的字符串表达式(docstring)和 `comment` → comment。
|
||
- 函数、类主体的第一条语句(注释不算)是单独一个字符串表达式时,它是这个函数、类的 docstring:进 `facts.doc`,头部往下延到字符串结尾(它和冒号之间的注释也在头部里),子块从它后面开始,不单独成块。
|
||
- 赋值:模块顶层且变量名全大写 → const,其余 → variable。
|
||
- `if_statement` → if;`elif_clause` / `else_clause` → 兄弟块 else;`for` / `while` → loop(循环的 `else` → 兄弟块 else)。
|
||
- `try_statement` → try;`except_clause` → catch;`finally_clause` → finally。
|
||
- `with_statement` → statement,子块来自主体;`match_statement` → if,`case` → 子块 if。
|
||
- `return_statement` → return;其余 → statement。
|
||
- 复合语句头部到主体开始为止(包含行尾冒号,翻译时去掉)。
|
||
|
||
### 属性表信息(facts)从哪来
|
||
|
||
- TypeScript 函数:`function_declaration` / `generator_function_declaration` / `function_signature`、类里的 `method_definition` / 方法签名、值是带语句块主体函数的变量和类字段、`export default` 的函数值。名称取声明名 / 变量名 / 字段名;说明取紧贴在前面的 JSDoc 注释节点(见上面代码块树规则);参数来自 `formal_parameters`(`required_parameter` / `optional_parameter` 的 pattern、type、value,JavaScript 的 `assignment_pattern`),不带括号的单参数箭头函数取 `parameter`;返回类型取 `return_type` 里的类型;异步看有没有 `async`;类成员的装饰器是类主体里排在它前面的 `decorator` 兄弟节点。
|
||
- TypeScript 类:`class_declaration` / `abstract_class_declaration`、类表达式 `class`(`export default class {}`、变量的值、类字段的值)。名称取类名 / 变量名 / 字段名;说明同函数;访问级别同函数(类字段按类成员);抽象看节点类型是不是 `abstract_class_declaration`;装饰器是 `export_statement` 和类声明自己的 `decorator` 子节点(`@d export class`、`export @d class`、`@d class` 都算),类字段的值是类表达式时还有类主体里排在它前面的 `decorator`;泛型参数是 `type_parameters` 里每个 `type_parameter`;父类、实现的接口在 `class_heritage` 里:`extends_clause` 的每个 value 一项,跟在它后面的 `type_arguments` 归它,`implements_clause` 里每个类型一项;JavaScript 的 `class_heritage` 直接是 `extends` 后面的表达式。
|
||
- TypeScript 导入:`import_statement` 的 `source`(`import x = require()` 取 `import_require_clause` 里的);`import_clause` 里的标识符是默认导入,`namespace_import` 是 `* as`,`named_imports` 里每个 `import_specifier` 的 name / alias;语句本身带 `type` 是仅类型。
|
||
- TypeScript 变量:每个 `variable_declarator` 的 name / type / value。
|
||
- Python 函数:`function_definition` 的 name / parameters / return_type,`async` 子节点;说明是主体开头 docstring 的字符串节点(`string` / `concatenated_string`);装饰器是 `decorated_definition` 里的 `decorator`;参数 `identifier`、`typed_parameter`(名字是第一个具名子节点)、`default_parameter`、`typed_default_parameter`、`*args` / `**kwargs`,`*` 和 `/` 分隔符不算参数。
|
||
- Python 类:`class_definition` 的 name;说明是主体开头 docstring 的字符串节点;装饰器同函数;泛型参数是 Python 3.12 `type_parameters` 里的每一项;父类是 `superclasses`(`argument_list`)里的每个具名子节点,照写的顺序,关键字参数(`metaclass=ABCMeta`)、`*bases`、`**kw` 也算;没有抽象、实现的接口。
|
||
- Python 导入:`import_statement` 每个 name 一个来源、导入项是整个模块;`import_from_statement` 的 module_name 和各 name(`aliased_import` 拆出 alias),`wildcard_import` 是全部;`future_import_statement` 的来源是 `__future__`。
|
||
- Python 变量:`assignment` 的 left / type / right。
|
||
|
||
## 中文模板直译
|
||
|
||
结构照抄源码(按节点模板重排的语法结构除外,见下文),只替换三类东西,其余(标识符、属性名、字面量、标点)原样保留并按词性着色:
|
||
|
||
1. 关键字:只认语法树里的关键字节点(匿名节点),所以属性名 `i.number`、对象键不会被误翻。
|
||
2. 内置类型:只在类型位置翻(TS 的 `predefined_type`、Python 注解里的内置类型名)。
|
||
3. 运算符:`===` 全等、`!==` 不全等、`==` 等于、`!=` 不等于、`&&` 并且、`||` 或者、`!` 非(Python 的 `not` / `and` / `or` 本身是关键字)。
|
||
|
||
同一套表和节点模板也用来翻属性表里的值:引擎能翻任意一个节点(只取它落在块头部里的那一段),参数类型、默认值、返回类型、变量类型和初始值都走这条路。
|
||
|
||
其他规则:空数组字面量 → 空数组(Python 空列表 → 空列表、空字典 → 空字典);复合语句头部去掉结尾的 `{` 或 `:`;多行合成一行(连续空白压成一个空格,右括号前的尾逗号去掉);注释块给去掉注释符号的 Markdown 原文(见「注释的 Markdown 排版」)。关键字与前后内容之间保留源码里的空格(`!` 翻成「非」后补一个空格)。
|
||
|
||
关键字按所在语法节点选择译法:同一个关键字在不同语法结构里意思不同时,表里写一个默认译法,再加若干「语境 → 译法」。语境是从关键字节点往上的节点类型链,外层在前:`raise_statement` 指父节点是 raise_statement,`for_statement > else_clause` 指父节点是 else_clause、祖父节点是 for_statement(关键字包在一层子句节点里时要看到祖父节点)。几条语境同时匹配时取最长的,都不匹配用默认译法。节点类型以 tree-sitter 语法为准。下面两张表里写成「(语境)译法 / (语境)译法」的关键字就是按这条规则区分的,排第一个的是默认译法。
|
||
|
||
节点模板:照抄源码结构读不通的语法结构,按语法节点类型给一个模板,把译文重排成中文语序。模板是一串项,每项要么是插入的译词,要么是子节点槽位:槽位按 tree-sitter 字段名引用子节点;语法没给字段名的,按具名子节点序号(0 起,注释不占序号)引用。命中模板的节点只输出模板里的项,节点自己的关键字和标点(`if` / `else`、`?` / `:`)不再输出;插入的译词一律按关键字(keyword)着色,模板各项之间空一格;槽位里的子节点照常递归翻译,里面的关键字、运算符、嵌套的同类结构照样处理。两种情况不重排、照常翻:模板引用的子节点不存在;节点没整个落在要翻的这一段里。目前的模板:
|
||
|
||
| 语言 | 节点类型 | 模板 |
|
||
| --- | --- | --- |
|
||
| TypeScript | `ternary_expression`(`c ? a : b`) | 如果 〈condition〉 则 〈consequence〉 否则 〈alternative〉 |
|
||
| Python | `conditional_expression`(`a if c else b`,没有字段名,具名子节点依次是 a、c、b) | 如果 〈1 号〉 则 〈0 号〉 否则 〈2 号〉 |
|
||
|
||
TypeScript 关键字表(流程 = keyword,声明 = storage):
|
||
|
||
| 流程 | 声明 / 字面量 |
|
||
| --- | --- |
|
||
| import 导入、from 来自、as(导入 / 导出别名)作为 / (类型断言 `x as T`、`as const`)断言为、export 公共、default(`export default`)默认 / (`switch` 里的 `default:`)默认情况、if 如果、else 否则、for 遍历、of 取自、in(二元运算 `'k' in obj`)在 / (`for…in` 头部)取自、while 当、do 执行、switch 分支、case 情况、break 跳出、continue 继续、return 返回、throw 抛出、try 尝试、catch 捕获、finally 最终、await 等待、yield 产出 | const 常量、let 变量、var 变量、function 函数、async 异步、class 类、extends(类 / 接口继承)继承 / (泛型约束 `T extends X`、`infer U extends X`)限定为 / (条件类型 `A extends B ? … : …`)符合、implements 实现、interface 接口、type 类型、enum 枚举、new 新建、instanceof 属于、typeof 类型为、keyof 键名、readonly 只读、public 公共、private 私有、protected 受保护、static 静态、abstract 抽象、delete 删除、true 真、false 假、null 空、undefined 未定义 |
|
||
|
||
TypeScript 内置类型:string 字符串、number 数值、boolean 布尔、any 任意、unknown 未知、never 永不、void 无、object 对象、bigint 大整数、symbol 符号。`Promise` 等库类型不翻。
|
||
|
||
Python 关键字:import 导入、from(导入)从 / (`raise … from`)源自 / (`yield from`)自、as(导入别名、`with … as`、`except … as`)作为、def 函数、async 异步、await 等待、class 类、return 返回、if 如果、elif 否则如果、else(`if` 后)否则 / (`for` / `while` 后,循环没被 break 才执行)未中断时 / (`try` 后,没出错才执行)未出错时、for 遍历、in(比较里)在 / (for 头部与推导式里)取自、while 当、try 尝试、except 捕获、finally 最终、raise 抛出、with 使用、yield 产出、lambda 匿名函数、pass 跳过、break 跳出、continue 继续、global 全局、nonlocal 非局部、del 删除、assert 断言、not 非、and 并且、or 或者、is 是、not in 不在、is not 不是、None 空、True 真、False 假、match 匹配、case 情况。
|
||
|
||
Python 内置类型(仅注解里):str 字符串、int 整数、float 浮点数、bool 布尔、list 列表、dict 字典、set 集合、tuple 元组、bytes 字节串。
|
||
|
||
一行译文样例(必须逐字一致):
|
||
|
||
| 源码 | 译文 |
|
||
| --- | --- |
|
||
| `if (!folder)` | `如果 (非 folder)` |
|
||
| `return []` | `返回 空数组` |
|
||
| `for (const f of files) {` | `遍历 (常量 f 取自 files)` |
|
||
| `failed: { path: string, reason: string }[]` | `failed: { path: 字符串, reason: 字符串 }[]` |
|
||
| `return {⏎ locked: true,⏎ … }`(issueLock.ts 20–24 行) | `返回 { locked: 真, prerequisiteNumber: prereq.number, prerequisiteColumn: prereq.column }` |
|
||
| `err instanceof Error ? err.message : String(err)` | `如果 err 属于 Error 则 err.message 否则 String(err)` |
|
||
|
||
属性表样例(必须逐字一致;子行各列用 ` | ` 隔开):
|
||
|
||
| 源码 | 属性表 |
|
||
| --- | --- |
|
||
| `export async function findEnvFiles(workspaceRoot: string): Promise<string[]> {`(envLock.ts 18–23 行,连同前面的 JSDoc) | 名称 findEnvFiles / 说明 `Scan the workspace folder rooted at …`(三行原文照留,按 Markdown 排成一段)/ 访问级别 公共 / 异步 是 / 输入参数 1 个:`workspaceRoot \| 字符串 \|` / 输出参数 `Promise<字符串[]>` |
|
||
| `async function chmodAll(workspaceRoot: string, mode: number): Promise<EnvLockResult> {` | 名称 chmodAll / 访问级别 私有 / 异步 是 / 输入参数 2 个:`workspaceRoot \| 字符串`、`mode \| 数值` / 输出参数 `Promise<EnvLockResult>` |
|
||
| `import { promises as fsp } from 'node:fs'` | 来源 `'node:fs'` / 仅类型 否 / 导入项 1 个:`promises 作为 fsp` |
|
||
| `const ENV_PATTERN = '**/.env*'` | 名称 ENV_PATTERN / 访问级别 私有 / 值 `'**/.env*'` |
|
||
| `const issue = issues.find(i => i.number === issueNumber)` | 名称 issue / 初始值 `issues.find(i => i.number 全等 issueNumber)` |
|
||
| `def find_env_files(root: str) -> list[str]:`(sample.py,连同下一行的 docstring) | 名称 find_env_files / 说明 `Return every .env file under root.` / 访问级别 公共 / 异步 否 / 输入参数 1 个:`root \| 字符串` / 输出参数 `列表[字符串]` |
|
||
| `from .schemas import (⏎ UserAvatarUploadResponse, … )`(routes.py 19–23 行) | 来源 `.schemas` / 导入项 3 个:UserAvatarUploadResponse、UserDetailResponse、UserUpdateRequest |
|
||
| `payload = await file.read()`(routes.py,函数体里) | 名称 payload / 初始值 `等待 file.read()` |
|
||
| `_default_port = 5680 if _role == "beat" else 5679` | 名称 _default_port / 访问级别 私有 / 初始值 `如果 _role 等于 "beat" 则 5680 否则 5679` |
|
||
| `export abstract class Repository<K, T extends Entity = Entity> extends Base<string> implements Loader<T>, Disposable {`(classes.ts 3–9 行,连同前面的 JSDoc 和 `@Injectable()`) | 名称 Repository / 说明 `仓库基类。…` / 访问级别 公共 / 抽象 是 / 装饰器 `@Injectable()` / 类型参数 `K, T 限定为 Entity = Entity` / 父类 `Base<字符串>` / 实现的接口 `Loader<T>, Disposable` |
|
||
| `export default class {` | 名称 匿名 / 访问级别 公共 / 抽象 否 |
|
||
| `class Meta(Base, metaclass=ABCMeta):`(classes.py,带 `@dataclass(frozen=True)`) | 名称 Meta / 访问级别 公共 / 装饰器 `@dataclass(frozen=True)` / 父类 `Base, metaclass=ABCMeta` |
|
||
| `class NotFound(AppException):`(classes.py,连同 docstring) | 名称 NotFound / 说明 `找不到资源。` / 访问级别 公共 / 父类 `AppException` |
|
||
|
||
## 预览数据与消息
|
||
|
||
扩展端把代码块树转成可序列化的视图数据发给 webview:
|
||
|
||
```ts
|
||
interface BlockViewBase {
|
||
id: string // 在树里的位置,例如 '6.1.0'
|
||
kind: BlockKind
|
||
startLine: number // header 的起止行,0 起
|
||
endLine: number
|
||
code: string // header 源码,去掉共同缩进
|
||
codeTokens: Segment[] // 源码着色(拼起来等于 code)
|
||
defines?: SourceRange // 块自己的名字(函数名、类名、只声明一个变量时的变量名)在源码里的范围,扩展端从 facts 取;跳到的定义是它时闪整块
|
||
children: BlockView[]
|
||
}
|
||
|
||
/** 属性表块(有 facts)只带 properties,其余块只带 translation(注释块是 `{ markdown }`);没有翻译插件时是空数组。 */
|
||
type BlockView = BlockViewBase & ({ translation: PreviewText } | { properties: PropertyRow[] })
|
||
```
|
||
|
||
- 扩展 → webview:
|
||
- `{ type: 'document', version, languageId, blocks: BlockView[], translators: { id, label }[], translatorId, error?: string, reveal?: Reveal }`,文档每次变化(防抖 150ms)重发。`reveal = { line, definition? }` 只随新预览的第一份文档走:从文本编辑器切过来时是它顶部那一行(滚到包含这行的块、顶到顶部,折叠的不展开);跳到定义时还带 `definition`(定义处名字在源码里的下标),按「跳到定义」一节展开、滚动、闪。
|
||
- `{ type: 'reveal', line, definition? }`:同上,发给已经开着的预览。页面正在重新加载(标签页切回来)时收不到,扩展端改成随它 `ready` 后的第一份文档带过去。
|
||
- `{ type: 'definitionProbed', id, found }`:回答第 `id` 次 `probeDefinition`。
|
||
- webview → 扩展:
|
||
- `{ type: 'probeDefinition', id, version, offset }`:按住修饰键指到带 `offset` 的名字上时问有没有定义,`id` 每问一次加一;`version` 不是当前文档版本就不答。
|
||
- `{ type: 'goToDefinition', version, offset }`:修饰键 + 点击,按「跳到定义」一节跳。
|
||
- `{ type: 'ready' }`
|
||
- `{ type: 'edit', startLine, endLine, text }`:用 `WorkspaceEdit` 把这几行整行替换成 `text`(每行补回原来第一行的缩进)。
|
||
- `{ type: 'selectTranslator', id }`:按 `languageId` 记到 `globalState`,重发文档。
|
||
- 块的编辑状态和用户的折叠选择在 webview 里按块 id 记(`vscode.setState`):`{ editing: string[], folds: Record<id, boolean>, scroll? }`。`folds` 只记用户点过的(id → 折叠着没有),属性行也记在里面,id 是 `<块 id>/p<行序号>`(例如 `6.1.0/p3`);没记的按默认:函数、类块折叠,其余块和属性行展开。眼下折叠着哪些由「默认 + folds」算出来,界面、跳到定义的展开都只看这一份。跳到定义展开祖先块、属性行时也记进 `folds`(记成展开)。读回来的状态逐项核对,旧版本存的、形状不对的项当没存过。文档刷新后保留;正在编辑且有焦点的块,刷新时不覆盖输入框内容。
|
||
|
||
## 颜色
|
||
|
||
词性(暗色取 VS Code Dark+,亮色取 Light+):
|
||
|
||
| 词性 | 暗色 | 亮色 |
|
||
| --- | --- | --- |
|
||
| keyword | #c586c0 | #af00db |
|
||
| storage / operator | #569cd6 | #0000ff |
|
||
| variable | #9cdcfe | #001080 |
|
||
| function | #dcdcaa | #795e26 |
|
||
| type | #4ec9b0 | #267f99 |
|
||
| string | #ce9178 | #a31515 |
|
||
| number / literal | #b5cea8 | #098658 |
|
||
| comment | #6a9955 | #008000 |
|
||
| hint | `--vscode-descriptionForeground` | 同左 |
|
||
| plain | `--vscode-editor-foreground` | 同左 |
|
||
|
||
属性表里:说明按 comment,访问级别(公共 / 私有 / 受保护)和「作为」按 keyword,是 / 否按 storage,函数名称按 function,类名称按 type,未标注、无、N 个、默认、默认导出、全部、整个模块、匿名按 hint。父类名(TypeScript `extends` 后面、JavaScript `class_heritage` 里、Python 类名后面括号里直接写的名字)源码着色和属性表里都按 type。
|
||
|
||
种类(标签字色 + 标签底 18% + 块底 7% + 边框 35%;亮色主题同色系):import 导入 #c586c0;function 函数 #dcdcaa;class 类、type 类型 #4ec9b0;field 字段、const 常量、variable 变量 #9cdcfe;if 条件、else 否则 #ce9178;loop 循环 #8f9cff;try 尝试、catch 捕获、finally 最终 #f48771;return 返回 #89d185;statement 语句 #9d9d9d;comment 注释 #6a9955(虚线框)。编辑状态:底色 `--vscode-editor-background`,边框 `--vscode-focusBorder`。
|
||
|
||
## 代码位置
|
||
|
||
```
|
||
vscode/src/codeReader/
|
||
contract.ts 通用契约
|
||
registry.ts 注册表 + CodeReaderApi
|
||
runtime.ts web-tree-sitter 初始化、语法包加载、解析
|
||
blockView.ts CodeBlock → BlockView(源码着色、去缩进、调用翻译)
|
||
languages/typescript.ts
|
||
languages/python.ts
|
||
translators/zhTemplate.ts 模板直译引擎(按语言的表驱动)
|
||
translators/typescriptZh.ts TS 表
|
||
translators/pythonZh.ts Python 表
|
||
explorer/fileTree.ts 侧栏文件树(FileTreeDecorator:给行添信息的接口)
|
||
notes/noteMap.ts 注释的键、保存 / 改名 / 删除时怎么改(纯函数)
|
||
notes/store.ts .spx/code-notes.json 读写
|
||
notes/noteItem.ts 有注释的行怎么显示:描述、悬停提示、contextValue(纯函数)
|
||
notes/fileNotes.ts FileNotes:内存里的注释、文件监听、跟着改名 / 删除走、给树画行
|
||
notes/commands.ts 「添加注释 / 编辑注释」输入框
|
||
editor/previewEditor.ts CustomTextEditorProvider
|
||
editor/messages.ts 消息类型
|
||
editor/definitions.ts 查定义(vscode.executeDefinitionProvider)、几处定义的挑选框
|
||
editor/definitionAction.ts 查到的定义 → 怎么跳(纯函数)
|
||
index.ts registerCodeReader(context): CodeReaderApi
|
||
vscode/webview-ui/code-reader.html + src/codeReader/ 预览界面(React,Vite 第二个入口;属性表在 PropertyGrid.tsx,Markdown 渲染在 markdown.ts / TextPreview.tsx)
|
||
```
|
||
|
||
## 测试
|
||
|
||
- 语言插件:`envLock.ts`、`issueLock.ts` 的代码块树(种类、header 行号、层级)与原型 `CODE_TREE` 一致,只是紧贴函数的 JSDoc 算进了函数头部;Python 样例覆盖 import / def / docstring / if-elif-else / for / try-except / with / 推导式;函数的说明:JSDoc 各种挂法(export、默认导出、方法隔着装饰器、函数值的变量、嵌套函数)和不挂的情况,docstring 连同它前面的注释进头部。类:`classes.ts`、`classes.py` 覆盖 export + 抽象 + 泛型 + extends / implements + 装饰器 + JSDoc、匿名默认导出、赋给变量 / 类字段的类表达式、函数里的类、类里的类、docstring、metaclass 关键字参数、下划线私有。
|
||
- 块头部:各样例里块的头部行互不重叠;函数、类说明所在的行在函数、类块里,「编辑」写回原样不变。
|
||
- 折叠:默认函数、类块折叠、其余展开;用户的选择盖过默认、重新加载后还在;旧版本存的状态不报错;跳到定义时默认折叠的函数、类也会展开。
|
||
- 属性表信息:`envLock.ts`、`sample.py`、`routes.py`(原型里的 FastAPI 路由)加内联片段,覆盖各种导入写法、形参写法、类成员访问修饰符、解构和多变量声明、Python 下划线规则。
|
||
- 翻译:上面两张样例表逐字一致;属性名不被翻;每个被翻的词单独成段;节点模板嵌套时正确、槽位里照样翻、没命中模板的节点照抄;属性表里的值和一行译文用同一套表和模板。
|
||
- `blockView`:`codeTokens` 拼起来等于 `code`;去缩进正确;函数、类、导入、常量、变量块只带属性表,其余块只带译文。
|
||
- 注释的 Markdown:注释块、函数和类「说明」的视图数据是 `{ markdown }`,原文照抄(reST 角色、双反引号、JSDoc 标签、HTML 都不转换),行注释连成的块留得住缩进四格的代码块;渲染时 HTML 当文字、带协议的网址和邮箱成链接、不带协议的文件名不成链接,`javascript:` / `command:` / 相对路径的链接和图片不渲染;跳到定义不认 Markdown 里的名字。
|
||
- 行替换:多行块替换后缩进正确。
|
||
- 跳到定义:TS / Python 译文和属性表里的名字、点分模块名每一节、模块字符串带下标且指回源码原文,关键字、运算符、说明词不带(前面有中文注释也对得上);查到的定义 → 不动 / 原地 / 预览 / 普通编辑器 / 挑选框;webview 里按下标找目标块、要展开的祖先块和属性行,滚动位置。闪哪一处(TS、Python 源码走完整的语言插件 + 翻译插件 + 视图数据):参数 → 它那个子行,导入项 → 它那一项,多变量声明里的一个 → 它的名称行,类的泛型参数 → 「类型参数」那一行,函数名 / 类名 / 单个变量名(含解构模式)→ 整块,循环变量 → 所在的块,翻译插件没填 `defines` → 整块(照样展开属性行);翻译插件给参数、导入项、声明的变量、泛型参数标的范围指回源码原文;块自己的名字只有函数、类和单变量声明有。
|
||
- 注册表:注册 / 注销后 `languageIds` 与可选翻译正确;同一语言多个翻译时按 `globalState` 选中。
|
||
- 文件注释:键相对工作区文件夹、`/` 分隔,根和外面的路径没有键,手写的 `./`、结尾 `/` 规范掉;保存去空白、清空即删;文件改名、目录挪位置里面各层一起走、名字只是前缀相同的不动、跨工作区文件夹先取出再放进去;删目录连带里面的;读写:文件缺失 / 解析失败 / 不是对象返回空表、跳过不合法条目、建 `.spx`、键排序、删光留 `{}`;有注释 / 没注释的行的描述、悬停提示(Markdown 转义)、contextValue,和 `package.json` 里菜单的 when 子句对得上。
|
||
|
||
## 收尾
|
||
|
||
按仓库约定:`package.json` 版本 patch +1 → `pnpm ext:package` → 版本号改动一并 commit → 本机 `code --install-extension <vsix> --profile augment`。
|