diff --git a/docs/superpowers/specs/code-reader/spec.md b/docs/superpowers/specs/code-reader/spec.md index 4fa4889..ee83cf2 100644 --- a/docs/superpowers/specs/code-reader/spec.md +++ b/docs/superpowers/specs/code-reader/spec.md @@ -25,7 +25,8 @@ | 折叠 | 有子块的块、属性表里带子行的行、带下一层的子行(字典 / 对象里嵌套的字典 / 对象)能折叠;函数块(含方法、嵌套函数)、类块、枚举块、异常块和库规则的卡片默认收起主体、属性表照常显示,其余块、属性行和子行默认展开;用户点过的按他点的记住 | | 函数、类的说明 | TypeScript 紧贴在函数、类前面的 JSDoc,Python 函数、类主体开头的 docstring 是它自己的说明:进属性表「说明」一行,不再单独成一块注释;它的源码行算在函数、类块的头部里,「编辑」时和签名一起改 | | 变量、字段的说明 | Python 模块、类主体里紧跟在一条赋值后面的字符串(属性 docstring,PEP 257 / Sphinx 的写法)是这个变量的说明:常量、变量块进属性表「说明」一行(紧跟名称),字段块排在一行译文下面;不再单独成块,它的源码行算在这一块里,「编辑」时和赋值一起改 | -| 注释按 Markdown 显示 | 注释块和函数、类、变量、字段的「说明」按 Markdown 渲染,像 Kotlin 的 KDoc:去掉注释符号、引号和共同缩进后的原文原样交给 Markdown,不按语言转换(reStructuredText 的 `:func:`、JSDoc 的 `@param` 都照抄,注释以后慢慢改成 Markdown 写法);段内单个换行是软换行,空行分段;仓库内图片按原图比例显示,点了打开原图,没有就写「找不到图片」 | +| 注释挂在它讲的块上 | 注释一定是讲某块代码的:普通注释(`#`、`//`、`/* */`、没挂成说明的文档注释、Python 当注释用的字符串)不单独成块,挂到它讲的那块上,是那张卡片的「注释」一行(排在名称、说明后面);同一层里挂到后面最近的那块(中间隔空行也算),后面没有就挂到前面那块,这一层一块代码都没有就挂到父块。单独成块的只有文件开头的说明:第一块代码之前、和它隔着空行的注释,以及本来就讲整个文件的(Python 模块开头的 docstring、Rust 的 `//!`、JSDoc 的 `@file`)。块的编辑范围连同挂在它上面的注释。见「注释挂到它讲的块上」 | +| 注释按 Markdown 显示 | 卡片的「注释」一行、文件开头的说明和函数、类、变量、字段的「说明」按 Markdown 渲染,像 Kotlin 的 KDoc:去掉注释符号、引号和共同缩进后的原文原样交给 Markdown,不按语言转换(reStructuredText 的 `:func:`、JSDoc 的 `@param` 都照抄,注释以后慢慢改成 Markdown 写法);段内单个换行是软换行,空行分段;仓库内图片按原图比例显示,点了打开原图,没有就写「找不到图片」 | | 跳到定义 | 和普通编辑器一样 ⌘ / Ctrl + 点击名字跳到定义,定义由 VS Code 装的语言扩展(Pylance、TypeScript 语言服务……)查;目标语言有代码阅读就在预览里打开 | | 悬停看定义 | 和普通编辑器里的悬停一样:鼠标停在名字、注释里指到符号的链接上一会儿(VS Code 的 `editor.hover.delay`),弹出定义所在那块的卡片(别的文件的也行,和那个文件的预览画的是同一张),点卡片标题跳过去 | | 高亮名字的各处出现 | 和普通编辑器一样单击名字就高亮它在这个文件里的各处出现,由语言扩展按语义认(同名的别的东西不算),颜色用 VS Code 的单词高亮色,再用 focusBorder 色补一笔(当前那一处描框、别的画细线);看不见的(折叠、筛掉的)点的时候不自动展开,筛选栏上写「名字 · 2/5」;⌘↓ / ⌘↑(可改绑的命令)在各处之间跳,跳到折叠着的地方会展开 | @@ -152,7 +153,7 @@ ### 预览(代码块树) -- 每一句是一块:导入、函数、类、枚举、异常、类型、字段、常量、变量、条件、否则、循环、尝试、捕获、最终、返回、语句、注释;Shell 脚本另有解释器、选项、分情况、命令、管道(见「Shell 脚本」);Dockerfile 另有解析指令、阶段、执行、复制、设置、标签、健康检查、启动(见「Dockerfile」)。 +- 每一句是一块:导入、函数、类、枚举、异常、类型、字段、常量、变量、条件、否则、循环、尝试、捕获、最终、返回、语句、文件开头的说明(种类「注释」);Shell 脚本另有解释器、选项、分情况、命令、管道(见「Shell 脚本」);Dockerfile 另有解析指令、阶段、执行、复制、设置、标签、健康检查、启动(见「Dockerfile」)。 - 嵌套的块放在父块下面、往右缩进;每一层左边一条竖线(颜色 `--vscode-editorIndentGuide-background`)。 - 块的组成(从左到右):折叠箭头(只有有子块的块才显示,其余占位对齐)、概念图标、内容、右上角一排图标按钮。 - 概念图标(原型 `kindBadge`、画板「Prototype / 块种类图标」):块左边横排这块认出来的概念,各一个图标、不带字,和一行字等高(20px 的框,图形 16px,lucide 的线宽 2.2),间距紧。每个图标悬停自己的名字(`data-tooltip`,见下「悬停提示」);整排是一个 `role="img"`,读屏把名字用顿号连起来念。和折叠箭头、内容同一行顶对齐。webview 不自己用种类查图标:图标名由宿主放进预览数据的概念列表(`BlockView.concepts`),webview 只按名字画(`kindIcons.tsx` 的 `KIND_ICON_COMPONENTS`)。 @@ -185,13 +186,14 @@ 这组图标由插件自己定义,是一个封闭的集合:契约里的 `KIND_ICON_NAMES`(上表 26 个——命令、管道、执行共用一个,解释器、解析指令共用一个,选项、设置、编译属性共用一个,字段、标签共用一个——加库规则用的 14 个(React 的组件、状态、引用、数据源、派生值、副作用、事件处理函数、渲染输出的 8 个也在里面),lucide 的 kebab-case 名字),类型 `KindIconName` 是它们的联合,概念的 `icon` 只能写其中一个,写别的是类型错误。内置种类 → 图标名在 `contract.ts` 的 `KIND_ICONS`(宿主写进概念列表)。webview 给每个名字配一个逐个 import 的 lucide 图标组件(`kindIcons.tsx` 的 `KIND_ICON_COMPONENTS`,打包只带这些,预览脚本约 157 kB);webview 的 `KindIconName` 照契约手抄,`kindIconNames.test.ts` 核对两边一样。规则要新图标就把名字加进这组、在 webview 配上图标,没有按任意名字找图标的路。 - 右上角的图标按钮(原型组件 `Reader / BlockTools`,普通块、属性表块、库规则的卡片共用):只有图标、不带字,名字写在悬停提示和 `aria-label` 里;整排是 `role="group"`(`aria-label="块操作"`)。先是「问 AI」(sparkles 图标,点开 / 收起块下面的问答区,展开时带按下的底色、`aria-expanded`;文件不在工作区文件夹里时没有,见「问 AI」),再是「编辑 / 预览」切换:预览时铅笔「编辑」,编辑时眼睛「预览」、带按下的底色(`--vscode-toolbar-activeBackground`)。按钮宽 24px、高 20px(和种类图标同高),图标 14px、`descriptionForeground` 色;悬停 `--vscode-toolbar-hoverBackground`,键盘聚焦描 1px `--vscode-focusBorder`;Tab 能走到,回车 / 空格切换。 - 悬停提示(整个插件的网页只走这一条路,预览和看板共用同一个组件):带提示的元素只写 `data-tooltip="提示"`(名字照旧写在 `aria-label` 里,只有图标的按钮必须有 `aria-label`),页面根上挂一个 `TooltipHost`(`webview-ui/src/components/ui/tooltip.tsx`)统一弹,不用原生 `title`;提示里的换行照样换行(看板摘修复弹框里「迁移」列出的几个文件一行一个);注释、回答里 Markdown 链接的标题(`[文字](网址 "标题")`)也写成 `data-tooltip`。网页的 TSX 里写原生 `title` 属性是 lint 错误(`vscode/eslint.config.mjs`),只有 `iframe` 这种必须带标题的元素例外,组件自己叫 `title` 的属性不算。原因:macOS 上 VS Code 带的 Electron(1.139 是 Electron 43)只在鼠标压着带 `title` 的元素自身边缘时才弹原生提示,停在它里面的图标、文字上不弹(electron/electron#49843,Electron 38 起),种类图标 20px 的框几乎被 16px 的图形占满,基本弹不出来;样子也是系统的。样子照 VS Code 的悬停框:`--vscode-editorHoverWidget-background` 底、`--vscode-editorHoverWidget-border` 边框、`--vscode-editorHoverWidget-foreground` 字、`0 2px 8px --vscode-widget-shadow` 阴影,11px 字,圆角 4px,最宽 22rem;优先在元素上方,上方放不下翻到下方,水平对着元素中间,贴边留 8px,不接鼠标。鼠标停 500ms 才弹,在同一个元素里移动不重新计时,已经弹着时移到另一个带提示的元素上立刻换;移开、移出页面、按下鼠标、按任意键(含 Esc)、滚动就收,收了之后鼠标不离开这个元素不再弹。键盘把焦点移到带提示的元素上(`:focus-visible`)立刻弹,焦点离开就收;提示不是元素自己的名字(`aria-label` 或它的字)时用 `aria-describedby` 挂到元素上,读屏当描述念。 -- 种类图标和底色按种类区分(颜色见「颜色」);注释块虚线框。 +- 种类图标和底色按种类区分(颜色见「颜色」);文件开头的说明(种类「注释」)虚线框。 +- 「注释」一行:讲这块的普通注释不单独成块,是这张卡片上的一行(见「注释挂到它讲的块上」)。属性表卡片(语言的、翻译插件排的、库规则的卡片都一样,宿主统一插,规则、翻译插件不用管)插在第一行(名称这类讲它是谁的一行)后面,有说明(值是 Markdown 的那一行)时插在说明后面,所以是「名称、说明、注释、别的」;一行译文的卡片(条件、循环、语句、调用、返回……)排在译文、摊开实参的表、说明的下面,同属性表的一行(左边「注释」、右边 Markdown)。名字是翻译插件给种类 comment 起的名字(中文直译「注释」)。几段注释(中间隔着空行、或前后两处)各是一段 Markdown,照源码顺序,空行隔开。按「注释的 Markdown 排版」显示(仓库路径链接、不带路径的引用、仓库图片都照常能点),标差异时同属性表的行逐词标。行上右边是「按注释规范重写」按钮(见「重写注释」)。 - 内容两种状态,每块单独切: - - 预览:函数、类、枚举、异常、导入、常量、变量、解释器、选项、解析指令、阶段、健康检查、启动块是属性表(见下节),注释块是 Markdown 说明(见「注释的 Markdown 排版」),其余块是一行译文;按词着色,等宽字体(中文部分用系统中文字体回退)。一行译文里有跨几行写的调用时,摊开实参的表排在译文下面(见「调用与构造」);块带说明(Python 字段的属性 docstring)时,说明按 Markdown 再排在下面。 - - 编辑:这一块在源码里的完整代码(按语法树着色):头部连同各子孙块、收尾括号,可以直接改,改完按这整段写回;蓝色边框。按钮换成眼睛「预览」;Esc 也回到预览。 -- 复合语句(函数、条件、循环……)块的预览只讲它自己的头部那几行(主体交给子块);收尾括号、空行不成块。函数、类的说明(JSDoc / docstring)也算在函数、类块的头部里;Python 常量、变量、字段的属性 docstring 算在它那一块里(中间隔着的空行也是)。 -- 编辑范围是块的完整范围(`blockView.ts` 的 `blockSpans`,和语言无关):从头部开头,到头部、整条语句、各子孙块里最靠后的那个字符。else、elif、catch 这类展平成兄弟块的子句,整条 if / try 按各块的开头切开:每块到下一块开头之前,最后一块到语句结尾(Shell 的 `fi` 归它);`} else {` 写在一行时两块都含这一行。 -- 库规则认领的块(数据表卡片等,见「库规则」):种类的图标、颜色和名字由规则给,内容是规则给的属性表;编辑范围同别的块,是原块整条语句(类就是整个类),「编辑」时整段一起改。规则没并进卡片的子块(方法、别的成员)不管写在类主体哪儿(两个字段中间也算),都照源码顺序是卡片的子块,点它们的「编辑」只改它们自己连同子孙那几行。 + - 预览:函数、类、枚举、异常、导入、常量、变量、解释器、选项、解析指令、阶段、健康检查、启动块是属性表(见下节),文件开头的说明是 Markdown(见「注释的 Markdown 排版」),其余块是一行译文;按词着色,等宽字体(中文部分用系统中文字体回退)。一行译文里有跨几行写的调用时,摊开实参的表排在译文下面(见「调用与构造」);块带说明(Python 字段的属性 docstring)时,说明按 Markdown 再排在下面;块挂着注释时「注释」一行排在最下面。 + - 编辑:这一块在源码里的完整代码(按语法树着色):挂在它上面的注释、头部连同各子孙块、收尾括号,可以直接改,改完按这整段写回;蓝色边框。按钮换成眼睛「预览」;Esc 也回到预览。 +- 复合语句(函数、条件、循环……)块的预览只讲它自己的头部那几行(主体交给子块);收尾括号、空行不成块。挂在块上的注释算在块的完整范围里(写在块上面的从注释开头算起,挂在这一层最后一块上的、子块交上来的到注释结尾):差异、跳过去、钉住卡片、总览条、「只看改动」、按行找块都按它,注释那几行改了标的是它挂着的那块。函数、类的说明(JSDoc / docstring)也算在函数、类块的头部里;Python 常量、变量、字段的属性 docstring 算在它那一块里(中间隔着的空行也是)。 +- 编辑范围是块的完整范围(`blockView.ts` 的 `blockSpans`,和语言无关):从头部和挂在它上面的注释里最靠前的开头,到头部、整条语句、挂在它上面的注释、各子孙块(连同它们的注释)里最靠后的那个字符。else、elif、catch 这类展平成兄弟块的子句,整条 if / try 按各块的开头(连同挂在它上面的注释)切开:每块到下一块开头之前,最后一块到语句结尾(Shell 的 `fi` 归它);`} else {` 写在一行时两块都含这一行。 +- 库规则认领的块(数据表卡片等,见「库规则」):种类的图标、颜色和名字由规则给,内容是规则给的属性表(宿主再插「注释」一行);编辑范围同别的块,是原块整条语句(类就是整个类)连同挂在它上面的注释,「编辑」时整段一起改。规则没并进卡片的子块(方法、别的成员)不管写在类主体哪儿(两个字段中间也算),都照源码顺序是卡片的子块,点它们的「编辑」只改它们自己连同子孙那几行。 - 同一行不会同时在两处能改:一块在编辑时它的子孙都不画(代码都在它的编辑框里),回到预览就照原样出来;点一块的「编辑」时,编辑范围和它重叠的别的块(祖先、子孙、共用一行的兄弟块)退出编辑。跳到定义的目标块正被盖住时闪盖住它的那块。 - 折叠:函数、类、枚举、异常块和库规则的卡片打开时默认收起子块(属性表照常显示),其余块默认展开;用户点过折叠箭头的块按他点的,记在视图状态里(见「预览数据与消息」)。 - 顶部筛选栏:见「筛选」;翻译下拉框(多于一种翻译时才显示)也在栏里。 @@ -207,6 +209,7 @@ 3. webview 只按属性 id / 值 id 筛,显示翻译插件给的名字。 - 下拉框:这份文件里有两种以上值的属性各一个(只有一种值的不给——筛它等于按种类筛);只有一种值、但这个值正被去掉的也给,不然藏起来的块在栏上找不到原因。排列:通用属性按契约的顺序(种类、访问级别、异步、抽象、仅类型),语言自己的属性按在文件里第一次出现的先后排在后面。下拉菜单顶上一行「全选 · 取消全选」,分隔线下面是勾选列表,列出这份文件里出现的值(按第一次出现的先后)和各有几块(所有层的块都算,例如「私有 3 / 公共 3」)。 - 全选 / 取消全选:把这份文件里这个属性出现的值一起勾上 / 去掉,和逐个勾一样记下来;别的文件才有的值记着的去掉状态不动(筛选整个工作区共用)。取消全选后有这个属性的块全藏起来,没有这个属性的照常显示;菜单不关,可以接着勾。 +- 种类「注释」只剩文件开头的说明:挂在块上的注释跟着它的块显示、藏起来,按种类去掉「注释」只藏文件开头的说明(文件里没有时这个值不出现)。 - 规则:同一属性里勾着的值满足一个就显示,不同属性要同时满足;块没有某个属性就不受那个属性的筛选影响(条件、返回块没有访问级别,按访问级别筛时照常显示);块被筛掉,连同它的子块一起藏,不留空位,层级竖线跟着变短;子块都被筛掉的块不给折叠箭头。 - 汇总:什么都没筛掉时右边写「共 N 块」(N 是整份文件所有层的块数);筛掉了写「已隐藏 N 块 · 清除」,N 只数直接被筛掉的块,跟着藏起来的子块不算;点「清除」整份筛选清空。页面上一块都不剩时写「所有块都被筛选隐藏了。」 - 下拉框上的字:没去掉值时是属性名;去掉了(边框换 `--vscode-focusBorder`)勾着一种写「访问级别:公共」,几种写「种类:已选 11 种」,一种都不勾写「种类:未选」。 @@ -286,9 +289,9 @@ - TypeScript / JavaScript:`extends` 后面的类名(`X`、`ns.X` 取 X,泛型实参不看;`Mixin(Base)` 这类调用不看)以 `Error` 或 `Exception` 结尾。自带的 `Error`、`TypeError`、`RangeError`、`AggregateError`……都以 Error 结尾,`DOMException` 以 Exception 结尾,不用另列。 - 两种语言都算:父类是同一文件里认出来的异常类,按名字认,不论写在前面还是后面、嵌套在哪,一层层往下传(`class Deeper(Late)`、`class Late(Base)`、`class Base(Exception)` 三个都是)。 - 按命名习惯认的部分和它的限制:父类从别的文件导入时语言插件看不到那个文件,只能按名字认(PEP 8 要求异常类名以 Error 结尾,`AppException` 这种以 Exception 结尾的也常见)。所以父类名不合习惯(从别处导入的 `NotFound`、`Forbidden` 当父类)的认不出来,照常是类块;名字合习惯却不是异常的父类(普通类起名 `RenderError`)会被当成异常。同一文件里按名字认,不看作用域(同名的两个类当成一个)。 -- 类属性(进「属性」表):Python 是类主体里给一个名字赋值的语句(`x = 1`、`x: int = 1`、只写注解的 `x: int`),连着赋值(`a = b = 1`)、解构赋值不算;TypeScript / JavaScript 是类字段(`public_field_definition` / `field_definition`,含 static;值是带语句块主体的函数、类表达式的照常是方法、类块)。说明同枚举成员:紧挨在上面的注释块、这一行行尾的注释、Python 紧跟在后面的属性 docstring,原文用空格连起来。类 docstring 里谷歌写法的 `Attributes:` 一节照原文留在「说明」里,不拆到各属性的说明格子。 +- 类属性(进「属性」表):Python 是类主体里给一个名字赋值的语句(`x = 1`、`x: int = 1`、只写注解的 `x: int`),连着赋值(`a = b = 1`)、解构赋值不算;TypeScript / JavaScript 是类字段(`public_field_definition` / `field_definition`,含 static;值是带语句块主体的函数、类表达式的照常是方法、类块)。说明同枚举成员:讲它的注释(挂在它上面的)、这一行行尾的注释、Python 紧跟在后面的属性 docstring,原文用空格连起来。类 docstring 里谷歌写法的 `Attributes:` 一节照原文留在「说明」里,不拆到各属性的说明格子。 - 构造参数:Python 是 `__init__` 的参数去掉第一个(self;第一个是 `*args` 这类时不去),TypeScript / JavaScript 是 `constructor(...)` 的参数(参数属性 `public code?: string` 也是一个参数);写了几个取最后一个(重载的实现写在最后)。`*`、`/` 分隔符同函数的输入参数,不算参数。类里没写构造函数(继承父类的)时没有这一行。 -- 并进属性表的子块:各个类属性连同紧挨在它前面的注释块;构造函数、方法、别的语句、讲方法的注释、最后面的注释照常是子块(不管写在哪),默认收起。异常块的头部(编辑范围)是整个类:从说明、装饰器开始,到类连同各子块的最后一行,「编辑」时整段一起改,盖住留下的子块(同库规则的卡片)。 +- 并进属性表的子块:各个类属性(讲它的注释挂在它上面,跟着并进来,是这个属性的说明);构造函数、方法、别的语句照常是子块(不管写在哪,讲它们的注释是它们卡片的「注释」一行),默认收起。异常块的头部(编辑范围)是整个类:从说明、装饰器开始,到类连同各子块的最后一行,「编辑」时整段一起改,盖住留下的子块(同库规则的卡片)。 - 语言插件报的已经是异常块,库规则只认类块,所以异常类不交给库规则:`@dataclass` 装饰的异常类照样是异常卡片,`@dataclass` 写在「装饰器」一行,注解的字段在「属性」表里。 - 跳到定义:类名是块自己的名字(闪整块),属性名只闪「属性」表里那一行,构造函数的参数只闪「构造参数」表里那一行(构造函数自己的「输入参数」也标了同一个参数,页面上先找到的是卡片里这一行);中文名(`Annotated[…, Comment("…")]`、行尾 `# @Comment …`)照常换属性格子里的名字,行尾记号不进说明。 @@ -470,9 +473,9 @@ ### 注释的 Markdown 排版 -- 用在:注释块(`#`、`//`、`/* */`、不挂在函数、类上的 JSDoc、Python 模块开头的 docstring 和别的当注释用的单独字符串)的预览,函数、类、常量、变量属性表的「说明」,参数表的「说明」列、「异常」表、输出参数后面的 `@return`,字段译文下面的说明。视图数据里它们是 `{ markdown }`(见「通用契约」的 `Markdown`),webview 按数据决定怎么渲染,不猜。 +- 用在:卡片的「注释」一行(`#`、`//`、`/* */`、没挂成说明的 JSDoc、Python 当注释用的单独字符串)、文件开头的说明(同样的注释,和 Python 模块开头的 docstring、Rust 的 `//!`)的预览,函数、类、常量、变量属性表的「说明」,参数表的「说明」列、「异常」表、输出参数后面的 `@return`,字段译文下面的说明。视图数据里它们是 `{ markdown }`(见「通用契约」的 `Markdown`),webview 按数据决定怎么渲染,不猜。 - 原文:翻译插件去掉注释符号(`#`、`//`、`/*` `*/` 和每行开头的 `*`)、引号和中文名记号(`SourceDocument.markers`,见「中文名」),再去掉行尾空白、首尾空行和共同缩进,保留换行,不做别的转换。一个注释节点(块注释、docstring)的第一行紧跟在注释符号 / 引号后面,不参与算共同缩进(同 PEP 257);几条行注释连成的一块每行地位相同,第一行也算,缩进四格的代码块才留得住。 -- 块标签:说明(`Translator.doc` 读的:函数、类这些定义的说明,字段、成员的说明,库规则取的说明)的原文按 KDoc 拆成正文和块标签(`Doc`,解析在 `docTags.ts`,各卡片共用);单独成块的注释照原文显示,不拆。 +- 块标签:说明(`Translator.doc` 读的:函数、类这些定义的说明,字段、成员的说明,库规则取的说明)的原文按 KDoc 拆成正文和块标签(`Doc`,解析在 `docTags.ts`,各卡片共用);「注释」一行和文件开头的说明照原文显示,不拆。 - 认 `@param`、`@return`(`@returns` 同义)、`@throws`(`@exception` 同义)。一行(去掉开头空白)以标签开头算一条;后面接着的行(不是空行、不以 `@` 开头)续在这条后面(去掉开头空白、换行留着);空行结束这条,以 `@` 开头的行也结束上一条。围栏代码块里的行不认标签。 - `@param 名字 说明`;JSDoc 的 `@param {类型} 名字 - 说明`、`@param [名字] 说明`、`@param [名字=默认值] 说明` 也认(类型、横杠、默认值去掉)。`@return 说明`、`@returns {类型} 说明` 同样去掉类型。`@throws 异常 触发条件`:异常是一个名字、`[Foo]`、`[说法](路径#Foo)` 或 JSDoc 的 `{Foo}`,只是名字的写成不带路径的引用 `[Foo]`。几条 `@return` 用空行连成一段。 - 正文是拿掉这几种标签后剩下的行(标签前后的正文都留着),去掉首尾空行,连着的几个空行并成一个;只写了标签时没有正文。别的标签(`@see`、`@since`)留在正文里。 @@ -482,7 +485,7 @@ - 安全:源码里的 HTML 当文字显示;链接认 http / https / mailto,带协议的网址和邮箱自动成链接(`sample.py` 这种不带协议的不算),别的写法(`javascript:`、`command:`、`./`、`..`、绝对路径、盘符)照原文显示。http / https / mailto 由 VS Code 的 webview 自己拦下、交给宿主在外部打开,不用另发消息。http / https 图片不渲染(webview 的 CSP 不放行外部图片),`![alt](url)` 显示成 `!` 加链接。仓库内图片按原图比例显示,点了打开原图,没有就写「找不到图片」和路径。 - 仓库路径链接(`[名字](从仓库根起的路径)`,可带 `#类名` 或 `#类名.成员`)能点,点了打开文件并跳到符号,和 ⌘ / Ctrl + 点击跳到别的文件里的定义是同一条路(`editor/openTarget.ts` 的 `openingFor`,见「跳到定义」):语言有代码阅读的在预览所在的编辑器组用预览打开、滚过去、闪符号那处(不带符号的只打开预览),没有代码阅读的用 VS Code 默认的打开方式(普通编辑器选中符号的名字,图片看图);指到这份文件里的符号时在本预览里滚过去、闪一下(同下面的引用)。指到目录的在代码阅读侧栏的树上定位(展开并选中,和侧栏跟着当前文件走是同一个定位;侧栏没露着时亮出来),不切到 VS Code 资源管理器;树上没有(开着筛选时没摆它)时提示「侧栏的树上没有 …」,不退出筛选。 - 不带路径的引用(KDoc 的 `[Name]`、`[Class.member]`)能点: - - 认哪些:只在注释的 Markdown 里认(说明正文、参数说明、`@return`、「异常」表、单独成块的注释,连带增删的;问 AI 的回答不认)。方括号里是标识符(ASCII 字母、数字、下划线、`$`,点分开的几段);中文这类不是标识符的(`[会话进展]`)照原样是文字。反引号里的是代码;紧跟在名字、`]`、`)` 后面的方括号是下标(`items[i]`、`a[0][1]`、`f()[k]`);后面紧跟 `(…)` 的是完整链接、紧跟 `[` 的是参考式链接,都不算。webview 在 `markdown.ts` 加一条行内规则(排在 markdown-it 的 link 后面,渲染时 `env.symbolLinks` 为真才认),写成地址为 `symbol:名字` 的链接(作者写不出这种地址,`validateLink` 不放行)。 + - 认哪些:只在注释的 Markdown 里认(说明正文、参数说明、`@return`、「异常」表、「注释」一行、文件开头的说明,连带增删的;问 AI 的回答不认)。方括号里是标识符(ASCII 字母、数字、下划线、`$`,点分开的几段);中文这类不是标识符的(`[会话进展]`)照原样是文字。反引号里的是代码;紧跟在名字、`]`、`)` 后面的方括号是下标(`items[i]`、`a[0][1]`、`f()[k]`);后面紧跟 `(…)` 的是完整链接、紧跟 `[` 的是参考式链接,都不算。webview 在 `markdown.ts` 加一条行内规则(排在 markdown-it 的 link 后面,渲染时 `env.symbolLinks` 为真才认),写成地址为 `symbol:名字` 的链接(作者写不出这种地址,`validateLink` 不放行)。 - 指到哪(扩展端,`symbolLinks.ts` 的 `resolveReference`,按 KDoc 的规矩):① 本文件定义的:文档符号(`vscode.executeDocumentSymbolProvider`,同仓库链接找符号那一套)顶层名字对上的(类、函数、常量、变量;落在导入语句里的名字不算),`Class.member` 在它底下找成员;② 本文件导入进来的:名字(`Class.member` 的第一段)是某条导入引进来的(预览已有的导入块 facts,`ImportFacts.items`:起了别名的按别名认,问原来那个名字;整模块导入按第一节认,起了别名的问最后一节),在导入语句里这个名字的位置上问语言扩展定义在哪(`vscode.executeDefinitionProvider`,同跳到定义,几处取第一处),`Class.member` 再在目标文件的文档符号里找:定义落在哪个符号里就在它底下找,落在文件开头(导入的是模块)就从顶层找;③ 都没有就找不到。本文件定义了这个名字、却没有这个成员时也算找不到,不再去导入里找。 - 问答同仓库路径链接(`useRepoLinks`、`RepoLinks`):画出来后把页面上的仓库路径和引用一起发去问(`checkRepoLinks { paths, symbols }` → `repoLinksChecked { missing, unresolved }`),问到之前是普通链接,找不到的画成坏链接(红色波浪线,悬停「找不到」);文档换了版本(收到 `document`)再问一次,引用跟着源码变。点了发 `openRepoLink`(不带 `path`,只带 `symbol`):找到了同完整链接的后半段打开目标(语言有代码阅读的用预览、滚过去、闪,没有的用默认的打开方式选中名字),目标在这份文件里时在本预览里滚过去、闪一下(同跳到定义的闪);找不到就标坏。 - 样子:正文用界面字体(`--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 细线。 @@ -618,7 +621,7 @@ 块的位置记号(`BlockView.anchor`,`qa/anchor.ts`): -- 扩展端生成视图数据时按源码先后逐块给:有名字的块(`defines` 的来源:函数名、类名、枚举名、单个变量名,库规则卡片取规则给的定义范围)是外层各个有名字的块加自己的「种类 名字」,例如 `class ReferralRelation > function display`;没名字的块(条件、语句、返回、注释、导入、多变量声明……)是外层最近的有名字的块加「种类 @源码哈希」,哈希是这块头部那几行(库规则认领的块是整条语句)去掉所有空白后的 SHA-1 前 8 位,改它的子块不影响它的记号,例如 `function display > if @3fa1c2d0`。同一层里记号相同的(同名的两个函数、一模一样的两行语句)按源码先后在后面补 `[1]`、`[2]`。 +- 扩展端生成视图数据时按源码先后逐块给:有名字的块(`defines` 的来源:函数名、类名、枚举名、单个变量名,库规则卡片取规则给的定义范围)是外层各个有名字的块加自己的「种类 名字」,例如 `class ReferralRelation > function display`;没名字的块(条件、语句、返回、文件开头的说明、导入、多变量声明……)是外层最近的有名字的块加「种类 @源码哈希」,哈希是这块头部那几行(库规则认领的块是整条语句)去掉所有空白后的 SHA-1 前 8 位,改它的子块不影响它的记号,例如 `function display > if @3fa1c2d0`。同一层里记号相同的(同名的两个函数、一模一样的两行语句)按源码先后在后面补 `[1]`、`[2]`。 - 所以:上面插几行、改缩进空白、改别的函数的名字,记号都不变;有名字的块改了主体、签名还认得;没名字的块自己的源码改了,记号就变了,旧问答成了「找不到原来位置的」(不显示)。种类用视图数据的种类(库规则的卡片是规则的种类 id)。 - webview 只拿记号比相等,不拆开看。 @@ -630,6 +633,17 @@ - 预览要能读到 `.spx/code-qa/`:打开预览时把这份文件所在工作区文件夹的这个目录加进 webview 的 `localResourceRoots`,发给预览的问答里图片换成 `webview.asWebviewUri` 的地址。 - 在 VS Code 里改名、挪位置、删除文件 / 目录时问答跟着走(同文件注释);外部改了 `code-qa.json`(git pull、手改)文件监听重读,开着的预览跟着变。 +### 重写注释 + +按写注释的技能(`/ecc:writing-code-comments`)给一块代码重写讲它的普通注释,没有就新写一段。 + +- 按钮(`MessageSquareText` 图标,只有图标,悬停 / 读屏「按注释规范重写」,块还没有注释时是「按注释规范写注释」):每块代码都有。卡片有「注释」一行时画在这一行右边,没有时排在右上角那排按钮里(「问 AI」左边);文件开头的说明卡片排在右上角。正在重写时不能再点。定义卡片浮窗里、删掉的块没有;嵌着的代码里的块(Dockerfile RUN 里的 shell)、语言没给行注释记号(`LanguageAdapter.lineComment`)又还没有注释的块没有(`BlockView.commentTarget` 没有)。 +- 改哪儿(`BlockView.commentTarget`,扩展端 `blockView.ts` 算):块前面挂着普通注释时换掉它们(从第一段开头到最后一段结尾,几段中间隔着的空行一起换掉,写成一段);文件开头的说明卡片换掉它自己;块还没有前面的注释时新写一段,插在块的第一行(连同它的说明、装饰器)上面。说明(docstring、JSDoc、`///`)不动;挂在块后面、子块交上来的注释不动。 +- 问模型:同「问 AI」用 Claude Code 命令行、在这份文件所在的工作区文件夹里跑、用选的 profile、只读(`--tools Read,Grep,Glob`、`--allowedTools Read,Grep,Glob`、`--permission-mode dontAsk`、`--strict-mcp-config`:它能读那个文件,改不了,写回归扩展),从 stdin 发的只有一句:`/ecc:writing-code-comments <路径>:<起>-<止>`,路径相对 claude 的工作目录、用 `/` 分隔,行号 1 起,是这块代码的完整范围(连同挂在它上面的注释;文件开头的说明卡片是它自己那几行),别的不说(`commentRewrite.ts` 的 `commentRewritePrompt`)。文件有没保存的修改时不问(模型读的是磁盘上的文件),直接失败「先保存再重写注释」。 +- 结果按 JSON Schema 交(`--json-schema '{"type":"object","properties":{"body":{"type":"string"}},"required":["body"],"additionalProperties":false}'`):模型调 Claude Code 的 `StructuredOutput` 工具交结果,result 行的 `structured_output.body` 是注释正文(不带注释符号)。不读模型的自由文字:只回了一段话、`structured_output` 没有或 `body` 是空的,都算失败,文件不动。 +- 过程(`rewriteProgress`):问的那一句、正文逐字长出来(`--include-partial-messages` 时 `StructuredOutput` 的输入逐段来,按写到一半的 JSON 读出已经写出来的正文)、正在读什么(「读取 a.py」);块下面同问答区的样子显示(`RewriteThread`),能叫停。 +- 写回(扩展端,`WorkspaceEdit`,能撤销):换掉时套回原来那段注释的定界符和缩进(Python 三引号、`#` / `//` 行注释、`/* */` 块注释,`refillComment`;几段隔着空行的照第一段的写法);新写时用语言的行注释记号(`#`、`//`)一行一行写,缩进同块的第一行,空行写成只有记号的一行。写之前核对这块代码的那几行和问的时候一样,变了就不写(「这块代码在重写期间变了」);定界符认不出、编辑器没接受也不写。结束发 `rewriteEnd`(done / failed 带原因 / stopped),失败另弹一条错误提示。 + ## 通用契约 放在 `vscode/src/codeReader/contract.ts`,语法树类型用 `@vscode/tree-sitter-wasm` 自带的 web-tree-sitter 类型。 @@ -839,6 +853,8 @@ export interface BlockShape { /** 块自己显示 / 编辑的那一段:复合语句只到主体开始之前,其余是整条语句;块自己的说明(facts.doc、TextBlock.doc)也算在里面。 */ header: SourceSpan children: CodeBlock[] + /** 讲这块的普通注释(不是说明),照源码顺序;由 attachComments(languages/shared.ts)挂上,预览排成卡片的「注释」一行,块的编辑范围连同它们。见「注释挂到它讲的块上」。 */ + comments?: CommentBlock[] /** 语言自己报的筛选属性:通用词表以外的(Rust unsafe: 'true'),或 facts 表达不了的通用属性值(Kotlin access: 'internal',盖过推出来的);kind 填了不算。 */ attributes?: BlockAttributes } @@ -861,7 +877,7 @@ export type DocumentedBlock = FunctionBlock | ClassBlock | EnumBlock | Exception export type BlockFacts = FactBlock extends infer B ? B extends FactBlock ? Pick : never : never export type TextKind = Exclude -/** 预览成一行译文的块(注释块是 Markdown 说明)。 */ +/** 预览成一行译文的块(文件开头的说明是 Markdown)。 */ export interface TextBlock extends BlockShape { kind: TextKind doc?: Node // 同 VariableFacts.doc:Python 字段后面紧跟的属性 docstring;预览在译文下面按 Markdown 显示(Translator.doc) @@ -871,6 +887,9 @@ export interface TextBlock extends BlockShape { export type CodeBlock = TextBlock | FactBlock +/** 一段注释:相邻行同一写法的行注释合成的一段(node 是第一条,头部盖住全部)、一条块注释或 Python 当注释用的字符串。文件开头的说明单独成块,别的挂在讲的块上(BlockShape.comments)。 */ +export interface CommentBlock extends TextBlock { kind: 'comment' } + /** 着色用的词性,源码着色和译文着色共用。 */ export type TokenRole = | 'keyword' // 流程关键字:import from export if else for return try catch await … @@ -927,7 +946,7 @@ export interface DocThrows { exception: Markdown, when: Markdown } */ export interface Doc { body?: Markdown, params: DocParam[], returns?: Markdown, throws: DocThrows[] } -/** 预览里显示的一段文字:着色的词(一行译文、属性值),或整段 Markdown 说明(注释块、函数和类的说明)。 */ +/** 预览里显示的一段文字:着色的词(一行译文、属性值),或整段 Markdown 说明(注释、函数和类的说明)。 */ export type PreviewText = Inline[] | Markdown /** @@ -957,7 +976,7 @@ export interface PropertyRow extends Defines { label: string, value: PreviewText /** 一张带表头和边框的表:列名,每行几列对齐的格子,同 PropertyRow 给了 columns 的子行。 */ export interface PreviewTable { columns: string[], items: PropertyItem[] } -/** 一行译文的块翻出来的:一行译文(注释块是 Markdown 说明);跨几行写的调用还有摊开各个实参的表,预览排在这一行下面。 */ +/** 一行译文的块翻出来的:一行译文(注释块——文件开头的说明、挂在块上的注释——是 Markdown 原文);跨几行写的调用还有摊开各个实参的表,预览排在这一行下面。 */ export interface TextTranslation { text: PreviewText, table?: PreviewTable } /** 语言插件:语法树 → 代码块树,外加只有它懂、翻译插件翻表达式时要用的信息(叶子的词性、写在表达式里的遍历的结构)。 */ @@ -971,6 +990,8 @@ export interface LanguageAdapter { toBlocks: (root: Node, source: string, languageId: string) => CodeBlock[] /** 给叶子节点定词性:源码着色用它,译文里照抄的词也按它着色、按它认出能查定义的名字。 */ tokenRole: (leaf: Node) => TokenRole + /** 行注释的记号(`#`、`//`):「重写注释」给还没有注释的块新写一段时用它;不填就不能新写。 */ + lineComment?: string /** node 是写在表达式里的遍历(推导式、生成器表达式)时给出它的结构,别的节点没有;没有这种写法的语言不填。 */ iteration?: (node: Node) => IterationFacts | undefined /** node 是一次调用(含新建对象)时给出它的结构,别的节点没有;没给的语言不展开调用,照常一行译文。 */ @@ -1036,7 +1057,7 @@ export interface Translator { label: string // '中文直译' languageIds: string[] displayLanguage: string // 译文用的语言('zh'):库规则按它挑自己的词表 - translate: (block: TextBlock, document: SourceDocument) => TextTranslation // 只翻 header 那一段;注释块给 Markdown 原文;跨几行写的调用带实参表 + translate: (block: TextBlock, document: SourceDocument) => TextTranslation // 只翻 header 那一段;注释块(文件开头的说明、挂在块上的注释)给 Markdown 原文;跨几行写的调用带实参表 describe: (block: FactBlock, document: SourceDocument) => PropertyRow[] // 函数、类、枚举、异常、导入、常量、变量块 → 属性表 expression: (node: Node, document: SourceDocument) => Inline[] // 一个表达式 / 类型节点整个翻成一行译文,译法同属性表的值;库规则借它 doc: (node: Node, document: SourceDocument) => Doc | undefined // 说明文字(注释、docstring、当说明用的字符串)→ 正文和块标签(Doc);正文同属性表的「说明」;连着几行的行文档注释(Rust 的 `///`)node 是第一条,翻译插件自己往下连 @@ -1064,10 +1085,10 @@ export interface KindBadge { label: string, color: ThemeColor, icon: KindIconNam /** 库规则认领一块后给出的替身,下面挂着原块留下的子块:规则自己的卡片,或认成的现成属性表块;这块讲在了别的卡片里时是略去它。 */ export type RuleBlock = RuleCard | RuleFacts | RuleOmit -/** 这块讲在了别的卡片里,不再单独成块(React 的 "use client" 讲成组件卡片的「客户端组件」标签);不能有子块。 */ +/** 这块讲在了别的卡片里,不再单独成块(React 的 "use client" 讲成组件卡片的「客户端组件」标签);不能有子块,也不能挂着注释(注释没地方放)。 */ export interface RuleOmit { omit: true } -/** 规则自己的卡片:一张属性表。种类(badge)决定筛选和边框色;卡片左边的图标是语言的概念再加这个种类、以及 concepts。 */ +/** 规则自己的卡片:一张属性表。种类(badge)决定筛选和边框色;卡片左边的图标是语言的概念再加这个种类、以及 concepts。原块的「注释」一行由宿主插进 rows,规则不管。 */ export interface RuleCard { kind: string // 种类 id,「规则 id.名字」(sqlalchemy.table):筛选的 kind 值,要稳定 badge: KindBadge // 种类的名字、颜色、图标;筛选栏里这个种类也叫这个名字,卡片边框用这个颜色 @@ -1075,7 +1096,7 @@ export interface RuleCard { rows: PropertyRow[] defines?: SourceRange // 块自己的名字(类名):跳到的定义是它时闪整块 attributes?: BlockAttributes // 规则自己的筛选属性,叠在宿主从原块推出的上面;kind 填了不算;名字由 LibraryRule.attributeNames 给 - absorbs: CodeBlock[] // 并进来的子块(原块 children 里的对象本身),不再单独成块;没并的照源码顺序留作子块,编辑范围同别的块,是原块整条语句 + absorbs: CodeBlock[] // 并进来的子块(原块 children 里的对象本身,讲它的注释在它的 comments 里,跟着并进来),不再单独成块;没并的照源码顺序留作子块,编辑范围同别的块,是原块整条语句 slots?: RuleSlot[] // 挂在卡片某一行的子行上、点子行才展开的子块,一块只能放一处 } @@ -1196,7 +1217,7 @@ export interface CommandRule { - 注册:`CodeReaderApi.registerLibraryRule(rule)`,返回 `Disposable`;内置规则走同一个 API。 - 必须给:`id`、`label`、`languageIds`、`libraries`(关心的库)、`claim`(看一块,认领就返回替身 `RuleBlock`,不认返回 `undefined`)。 - 替身(`RuleBlock`)三种,按有没有 `omit`、`as` 分: - - 略去(`RuleOmit`,`{ omit: true }`):这块讲在了别的卡片里(React 文件开头的 `"use client"` 讲成组件卡片的「客户端组件」标签,组件上面没并进块头部的 JSDoc 讲成「说明」),预览不画它、不占块的位置(后面的块 id 接着往下数)。它不能有子块,有子块时宿主当替身不合约定。 + - 略去(`RuleOmit`,`{ omit: true }`):这块讲在了别的卡片里(React 文件开头的 `"use client"` 讲成组件卡片的「客户端组件」标签),预览不画它、不占块的位置(后面的块 id 接着往下数)。它不能有子块、不能挂着注释(注释没地方放),有时宿主当替身不合约定。 - 认成现成的属性表块(`RuleFacts`):`as` 是种类和 facts(`BlockFacts`,例如 `{ kind: 'enum', facts: EnumFacts }`),`absorbs` 同卡片。宿主把原块换成这种块(节点不变,头部是原块整条语句,子块是留下的),此后和语言插件报的这种块完全一样:翻译插件的 `describe` 排属性表、种类名取翻译插件的、颜色取这个种类的、筛选属性从 facts 推、块自己的名字从 facts 取。规则不给用词,也不给标签和颜色。只要检查并进来的是不是这块的子块。 - 规则自己的卡片(`RuleCard`),下面几条都说的是它。 - 卡片必须给:`kind`(写成「规则 id.名字」,宿主核对前缀;筛选记住的就是它,要稳定)、`badge`(种类的名字、暗 / 亮两种颜色和 lucide 图标名)、`rows`(用现成的 `PropertyRow`:属性名 + 着色的值或 Markdown,表格就是带 `columns` 的子行)、`absorbs`(并进来的子块)。可以给:`defines`(块自己的名字,跳到定义是它时闪整块)、`attributes`(自己的筛选属性)、`concepts`(额外认出的概念,排在种类图标后面)、`slots`(挂在卡片某一行的子行上、点子行才展开的子块,见下);表格子行填 `PropertyItem.defines`(字段名),跳到这个定义时只闪那一行。 @@ -1209,7 +1230,7 @@ export interface CommandRule { - 启用:宿主收集这份源码里各层导入块的 `ImportFacts.sources`(去掉引号),有一个等于规则的某个库、或以「库.」「库/」「库::」开头(子模块;Rust 的路径用 `::`,`use axum::routing::get` 的来源是 `axum::routing`),规则才对这份源码启用。规则不用自己读导入语句。`libraries` 是空数组的规则不看导入,`languageIds` 里这几种语言的源码都启用(React:新的 JSX 写法不用导入 react。Pydantic:子类文件常常只导入父模型、不再导入 pydantic,见「内置:Pydantic 数据模型」)。 - 认领:宿主从顶层往下每块问一遍启用的规则,按注册顺序,第一个认领的算(先注册的优先);被认领的块留下的子块照样问。 - 出错:规则抛错、或替身不合约定(种类 id 没带规则 id 前缀、并进来的不是这块的子块、属性表里的引用标的不是自己 / 规则没给 `resolve` / 引用的段没带下标、挂在子行上的块不是留下的子块 / 放了几处 / 没有那一行或子行、要略去的块还有子块)时,宿主记一条警告日志(输出面板「Superpowers」),当它没认、接着问下一个规则;都没认这块照常显示。预览不会因为规则出错而失败。 -- 并进来的子块:不再单独成块,由规则挑(`absorbs`),宿主不替规则多并。没并进来的子块(方法、规则不讲的注释)不管写在哪(字段中间也算),都照源码顺序是替身的子块,预览里照常看得见。替身的编辑范围同别的块,是原块整条语句(从头部开始,到原块连同各子块的最后一行,类就是整个类),盖住留下的子块的行:替身编辑时预览不画这些子块,同一行不会同时在两处能改(见「预览(代码块树)」)。内置规则并成员时连同紧挨在它前面的注释块一起并(讲的是这个成员),讲方法的注释、最后面的注释照常留下。 +- 并进来的子块:不再单独成块,由规则挑(`absorbs`),宿主不替规则多并。没并进来的子块(方法、别的成员)不管写在哪(字段中间也算),都照源码顺序是替身的子块,预览里照常看得见。替身的编辑范围同别的块,是原块整条语句(从头部开始,到原块连同各子块的最后一行,类就是整个类),盖住留下的子块的行:替身编辑时预览不画这些子块,同一行不会同时在两处能改(见「预览(代码块树)」)。讲成员的注释挂在成员上(`BlockShape.comments`),成员并进来时跟着并进来,内置规则拿它当成员的说明;讲方法的注释挂在方法上,照常是方法卡片的「注释」一行。 - 卡片在预览里:左边的图标是语言的概念(原来的类、函数……)再加 `badge` 这一个、以及 `concepts`(同一个 id 只留第一个),悬停各自的名字;卡片边框色取 `badge.color`(暗色、亮色主题各取一个),内容是 `rows`,挂在子行上的子块展开时画在它那一行下面(收起这一行时它们跟着收起;画成一层块,同块树),不算卡片的子块(卡片的折叠箭头只管后面留下的子块);筛选的 `kind` 是替身的种类 id,其余属性从原块推出(原来的类是公共的,数据表也是公共的),叠上规则给的(规则自己的属性叫 `LibraryRule.attributeNames` 给的名字,规则起名时抛错记日志、照写 id);筛选栏里这个种类叫 `badge.label`;和类块一样默认收起子块。多了图标不改筛选、不改边框色。 ### 规则自己查定义 @@ -1287,7 +1308,7 @@ export interface CommandRule { 说明:`comment=` 的字符串(comment 色);没写 `comment=` 时用字段的属性 docstring(`context.doc` 给的原文,comment 色;它随字段并进卡片)。关系的目标取第一个位置参数(字符串去掉引号),没写就用 `Mapped[...]` 里的类型;说明写「对方字段 X」(`back_populates` / `backref`)、「单个对象」(`uselist=False`)、「中间表 X」(`secondary`),用 ` · ` 隔开。 -并进卡片的子块:`__tablename__`、`__table_args__`、每个字段和关系的赋值,连同紧挨在它们前面的注释块;`__mapper_args__`、普通属性、方法(写在两个字段中间也算)和讲它们的注释照常是子块。 +并进卡片的子块:`__tablename__`、`__table_args__`、每个字段和关系的赋值(讲它们的注释跟着并进来);`__mapper_args__`、普通属性、方法(写在两个字段中间也算)照常是子块,讲它们的注释是它们卡片的「注释」一行。 样例(luffy `referral/models.py` 的 `ReferralRelation`,测试里逐字核对): @@ -1393,8 +1414,8 @@ export interface CommandRule { - facts:名称、说明(docstring)、访问级别、装饰器(`@enum.unique`)、父类照原来的类;成员照源码顺序。 - 成员照 Python 的规则认:类主体里给一个名字赋值的语句(`A = 1`、`A: int = 1`),名字不是 `__x__`、`_x_`(enum 保留的,如 `_ignore_`、`_order_`)、`__x`(私有),不在 `_ignore_` 列的名字里(空格隔开的字符串,或字符串的列表 / 元组),值不是 `lambda`、`property(...)` / `classmethod(...)` / `staticmethod(...)`、`nonmember(...)`、连着赋值(`A = B = 1`);解构赋值(`A, B = 1, 2`)不认。只写注解的(`_value_: int`)不是成员。 - 值:`auto()`(`enum.auto()` 也算)交给库自动取,没有 `value`(表里写「自动」);`member(x)` 的值是 `x`;别的是写的值。 -- 说明(`docs`):从上一个子块之后起紧挨在它上面的注释块(几条行注释连成的一块是几条)、它这一行行尾的注释、紧跟在它后面的属性 docstring,照源码顺序(同 Celery 定时任务的「说明」取法)。 -- 并进来的子块:各个成员连同紧挨在它前面的注释块;方法、嵌套类、不是成员的属性、讲方法的注释、最后面的注释照常是子块(不管写在哪),枚举块默认收起它们。 +- 说明(`docs`):讲它的注释(挂在成员上的 `BlockShape.comments`:它上面的,类主体最后一个子块是它时后面的也算;几条行注释连成的一段是几条)、它这一行行尾的注释、紧跟在它后面的属性 docstring,照源码顺序。 +- 并进来的子块:各个成员(讲它的注释跟着并进来);方法、嵌套类、不是成员的属性照常是子块(不管写在哪),讲它们的注释是它们卡片的「注释」一行,枚举块默认收起它们。 样例(`fixtures/enumModels.py` 的 `CommissionStatus`,测试里逐字核对): @@ -1416,7 +1437,7 @@ export interface CommandRule { - 认哪些类:装饰器是 `@dataclass`、`@dataclass(...)`、`@dataclasses.dataclass(...)`(`import dataclasses as dc` 的 `dc.dataclass`、`from dataclasses import dataclass as data` 的 `data` 也算,别名按 `context.imports` 换回)。同名的别处的东西不算(`from pydantic.dataclasses import dataclass`、`@other.dataclass`)。 - 字段照 dataclasses 的规则认:类主体里带注解的名字(`x: int`、`x: int = 1`),照源码顺序。`ClassVar[...]`(`typing.ClassVar`、只写 `ClassVar` 也算)是类变量,不是字段,照常是子块;`InitVar[...]` 是字段,类型写方括号里的,说明前面写「仅初始化时使用」;`_: KW_ONLY` 是记号,不是字段,它后面的字段(没单独写 `kw_only=` 的)在默认值一格写「只能按名字传参」。 -- 并进卡片的子块:各个字段连同紧挨在它前面的注释块、`KW_ONLY` 记号;方法(写在字段中间也算)、`ClassVar`、没注解的类属性、讲它们的注释照常是子块。 +- 并进卡片的子块:各个字段(讲它的注释跟着并进来)、`KW_ONLY` 记号;方法(写在字段中间也算)、`ClassVar`、没注解的类属性照常是子块,讲它们的注释是它们卡片的「注释」一行。 属性表(有才显示;字段总有,一个都没有时写「0 个」、不画表格): @@ -1434,7 +1455,7 @@ export interface CommandRule { - 字段:字段名(variable 色,带下标)。类型:注解照写法翻(翻译插件翻,`str` → 字符串),`InitVar[X]` 写 X。 - 默认值:`= x` 写 x(翻译插件翻,`None` → 空);`field(default=x)` 写 x;`field(default_factory=f)` 写「每次新建 f()」(`list` → 每次新建 list();不带参数的 `lambda` 写它返回的东西:`lambda: {"a": 1}` → 每次新建 {"a": 1});没写默认值时空着。后面接 `field(...)` 的别的参数,照写的顺序:`init=False` 不进构造函数、`repr=False` 不显示、`compare=False` 不参与比较、`hash=True` 参与哈希、`hash=False` 不参与哈希、`kw_only=True` 只能按名字传参、`kw_only=False` 可按位置传参,写成默认值的不讲,别的照写 `名=值`(`metadata={"unit": "px"}`);最后是 `KW_ONLY` 带来的「只能按名字传参」。各项用 ` · ` 隔开。 -- 说明:`field(doc="…")` 的字符串;没写时同枚举成员的说明取法(紧挨在上面的注释块、这一行行尾的注释、紧跟在后面的属性 docstring,原文用空格连起来),comment 色;都没有时是类说明里讲它的 `@param`(字段就是构造参数,Markdown)。类说明里 `@param` 的名字不是哪个字段时,在卡片的「说明」下面列成「对不上的参数」;`@throws` 是卡片最后的「异常」表。 +- 说明:`field(doc="…")` 的字符串;没写时同枚举成员的说明取法(讲它的注释、这一行行尾的注释、紧跟在后面的属性 docstring,原文用空格连起来),comment 色;都没有时是类说明里讲它的 `@param`(字段就是构造参数,Markdown)。类说明里 `@param` 的名字不是哪个字段时,在卡片的「说明」下面列成「对不上的参数」;`@throws` 是卡片最后的「异常」表。 样例(`fixtures/dataclassModels.py` 的 `SandboxAccess`,测试里逐字核对;说明的 Markdown 从略): @@ -1460,7 +1481,7 @@ export interface CommandRule { - 认哪些类:模块顶层的类,父类链上有 pydantic 的 `BaseModel`。`from pydantic import BaseModel`、`BaseModel as Model`、`import pydantic` 的 `pydantic.BaseModel`、`from pydantic.main import BaseModel` 都算,别名按 `context.imports` 换回。同一文件里继承另一个已经是模型的类也算(`class Child(AIGCMetadata)`,父类写在后面也认)。不认 `TypedDict`、`NamedTuple`、普通类、没导入 pydantic 时同名的 `BaseModel`,也不认 pydantic 的 `@dataclass`(那不是这张卡片,数据类规则也不认它)。 - 别的文件里的父类:父类是一个名字、又不是本文件里的类时,问索引 `pydantic.models`(`context.lookup`,见「规则认领时查别的文件里的定义」)。索引只扫源码里写了 `BaseModel` 的 `**/*.py`,记下本文件里能确定的模型(父类是 BaseModel,或本文件里另一个模型),值是 `"model"`。查到是模型就换成这张卡片;还没查到(或确定不是)就先按普通类画,查到后再重排。再往下跨一层、中间那个类自己的文件里写不出 `BaseModel` 的,索引不收,认不出来。 - 字段:类主体里带注解的赋值(`x: str`、`x: str | None = None`、`x: str = Field(...)`),照源码顺序。`ClassVar[...]` 不是字段。`model_config = ConfigDict(...)`(写成带注解的赋值也算)不是字段,是「选项」。 -- 并进卡片的子块:各个字段连同紧挨在它前面的注释块、`model_config` 那一行;方法、`@field_validator`、`ClassVar`、没注解的类属性(`model_config` 除外)、讲它们的注释照常是子块。字段并进来之后不再单独画成变量块。 +- 并进卡片的子块:各个字段(讲它的注释跟着并进来)、`model_config` 那一行;方法、`@field_validator`、`ClassVar`、没注解的类属性(`model_config` 除外)照常是子块,讲它们的注释是它们卡片的「注释」一行。字段并进来之后不再单独画成变量块。 属性表(有才显示;一个字段都没有时不画「字段」这一行): @@ -1631,7 +1652,7 @@ export interface CommandRule { `vscode/src/codeReader/rules/react.ts`(读语法树的办法在 `rules/reactTree.ts`,都是纯函数),`languageIds: ['typescriptreact', 'javascriptreact']`,`libraries: []`(新的 JSX 写法不用导入 react,TSX / JSX 文件都启用)。原型:`docs/prototype/code-reader/openpencil/react.js` 的三张画板「编辑器全貌」「卡片种类」「对照」(导出图 `exports/react-main.png`、`react-kinds.png`、`react-compare.png`);样例是 luffy 的 `chat/code/bubbles/QueuedBubble.tsx`(`fixtures/queuedBubble.tsx.txt`)和 `chat/code/PlanChecklist.tsx` 第 91–226 行(`fixtures/planChecklist.tsx.txt`,前面带上文件开头的导入),以前的 `DesktopTitlebar`、`LegalDocPage`、`DeleteAppDialog`、`DesktopSettingsDialog`、`AppVaultSummary`、`ShareButton`、`PosterDialog`、`OverviewSection` 也在 `fixtures/`。 -TSX 和 Python 一样,从上往下一块语法一张卡片,嵌套的块排在父卡片下面,缩进、竖线、折叠同函数体。规则只换掉 React 的几种写法,别的块(导入、接口、类型、常量、普通变量、if、注释、尝试……)照语言默认的卡片。组件卡片不把组件体并进来:组件体的语句照源码顺序是它的子块,照常交给各规则看。表格的格子里只写名字和位置,函数体只出现在子卡片里。 +TSX 和 Python 一样,从上往下一块语法一张卡片,嵌套的块排在父卡片下面,缩进、竖线、折叠同函数体。规则只换掉 React 的几种写法,别的块(导入、接口、类型、常量、普通变量、if、尝试……)照语言默认的卡片,注释照常挂在它讲的卡片上。组件卡片不把组件体并进来:组件体的语句照源码顺序是它的子块,照常交给各规则看。表格的格子里只写名字和位置,函数体只出现在子卡片里。 卡片种类(颜色写暗 / 亮): @@ -1652,10 +1673,10 @@ TSX 和 Python 一样,从上往下一块语法一张卡片,嵌套的块排 认领(`LibraryRule.claim` 一块一块看,组件卡片的 `absorbs` 是空的): -- 组件块(函数块、只声明一个变量的常量 / 变量块)是组件时换成组件卡片。文件开头的 `"use client"`(文件里有组件时)和组件上面没并进块头部的 JSDoc(值是 `memo(…)`、直接返回 JSX 的常量块)略去(`RuleOmit`),讲成卡片上的「客户端组件」标签和「说明」。 +- 组件块(函数块、只声明一个变量的常量 / 变量块)是组件时换成组件卡片。文件开头的 `"use client"`(文件里有组件时)略去(`RuleOmit`),讲成卡片上的「客户端组件」标签;它挂着注释时不略去(注释没地方放),照常是一张语句卡片。组件上面的 JSDoc 是组件块自己的说明(TypeScript 的 JSDoc 能当常量、变量块的说明,`memo(…)`、直接返回 JSX 的常量块也是),讲成卡片的「说明」。 - 组件体里的块:语法树往上找,块的语句直接是某个组件函数主体(语句块)的子语句才认(`enclosingComponent`:所在的函数经 `componentOfFunction` 找回它的声明,再用认组件的 `componentAt` 核对),按组件体的归类(`bodyOf`)认成状态、引用、数据源、派生值、副作用、事件处理函数。 - `return` 块:所在的最内层函数(`enclosingFunction`)是组件函数、返回的是 JSX 时认成渲染输出;写在 `if` 里的提前返回 JSX 也算,它是如果卡片下面的子块。返回 `null` 这类的照常是返回块。 -- 别的照语言默认:组件体里普通的 `const x = 表达式` 是变量卡片,`if (…) return null` 是如果卡片、下面是返回卡片,注释照常成块(不再并进卡片)。 +- 别的照语言默认:组件体里普通的 `const x = 表达式` 是变量卡片,`if (…) return null` 是如果卡片、下面是返回卡片,注释是它讲的那张卡片的「注释」一行。 - 回调、函数里的语句来自 TypeScript 语言插件本来给的子块:调用的实参是带语句块的函数时(任何调用都一样),那句语句、那个变量的子块就是函数里的语句,所以 `useEffect(() => {…})`、`useCallback(() => {…})`、`useMemo(() => {…})` 的语句排在副作用、派生值卡片下面;事件处理函数是函数块,函数体照常是子块。 组件卡片: @@ -1685,7 +1706,7 @@ TSX 和 Python 一样,从上往下一块语法一张卡片,嵌套的块排 副作用:触发时机按依赖数组读,`[a, b]` 是「a、b 变化时」,`[]` 是「仅挂载时」,没写是「每次渲染后」(keyword 色),依赖写成别的表达式时「deps 变化时」;useLayoutEffect 后面加「,浏览器绘制前」。最后返回清理函数时那句 return 并进来,多一行「清理函数」:`() => …` 翻它的主体,语句块一句句翻、「;」隔开。副作用函数里别的语句是子块。 -事件处理函数:名称是函数名(function 色,带下标);参数照常翻(`e: KeyboardEvent`),几个「, 」隔开,没有写「无」;绑定事件从 JSX 属性反查:「
(修改排队消息) 的 onKeyDown」,在属性值里调用时写传入的实参「(传入 h.id)」,在列表里一项项渲染的写「每个