✨ feat(vscode): 代码阅读把推导式换成按读的顺序重排的遍历小框

This commit is contained in:
cruldra committed 2026-09-26 16:15:09 +08:00
1 parent 677fb7da1f
commit 6416cdf714
16 files changed
+641 -91

No files matched your search

+58 -8
View File
@@ -30,6 +30,7 @@
| 译文用词 | 结构照抄原语言,只翻关键字、内置类型、运算符;用专业术语(`export` → 公共,`catch` → 捕获,`===` → 全等) |
| 函数、类、导入、常量、变量块 | 预览不显示一行译文,显示树形属性表;属性值里的类型、表达式照常直译 |
| 字典 / 对象字面量 | 常量、变量的值是 Python 字典 / TypeScript 对象字面量时不挤成一长行:值写「字典 N 项」/「对象 N 项」,各项展开成「键 \| 值」两列的子行,值又是字典 / 对象时一层层往下展开;参数默认值照旧一行 |
| 推导式(写在表达式里的遍历) | Python 的列表 / 集合 / 字典推导式、生成器表达式出现在译文任何地方(一行译文、属性表的值、默认值、字典项的值),都原地换成一个带循环图标和结果类型(生成器 / 列表 / 集合 / 字典)的小框,颜色同「循环」块,框里按读的顺序重排:「遍历 来源 的每个 变量,仅当 条件,取 值」。语言插件报结构,翻译插件定用词,webview 只认「框」不认语言 |
## 用户可见行为
@@ -98,6 +99,7 @@
卡片外观(折叠箭头、种类标签、底色、悬停高亮、「编辑 / 预览」切换)和其他块一样,只是内容换成一张属性表:左列属性名(界面字体、`descriptionForeground` 色,宽 76px),右列是值(等宽字体,按词性着色,长了换行),两列间距 12px,行高 20px。「输入参数」「导入项」和值是字典 / 对象字面量的「值」「初始值」下面展开子行,属性名前带折叠箭头(图标、大小、颜色同块的折叠箭头;点箭头或属性名切换,默认展开,折叠后值「N 个」「字典 N 项」照常显示,只收起子行);子行往右缩进 5px,左边一条 1px 竖线(`editorIndentGuide-background1`)。输入参数的子行分名称 / 类型 / 默认值三列对齐,前两列宽 150px / 220px,编辑区窄时按 28% / 38% 收,默认值列占剩下的;字典 / 对象的子行分键 / 值两列,键列同名称列。子行下面还能再展开一层(嵌套的字典 / 对象):带下一层的子行第一列前面有折叠箭头(点箭头或第一列切换,默认展开),同一层里其余子行在同样的位置留空、第一列对齐;下一层同样往右缩进 5px、左边一条竖线,竖线对着上一层的箭头,每层自己的两列对齐。同一层里没有带下一层的子行时不留箭头的位置(和参数、导入项的子行一样)。
| 种类 | 行 |
| --- | --- |
| 函数 | 名称(函数色;匿名函数写「匿名」)/ 说明(有说明才显示:JSDoc 去掉 `/**`、`*/` 和每行开头的 `*`,docstring 去掉引号;再去掉共同缩进和首尾空行,`@param` 这类标签行照抄;得到的原文按 Markdown 渲染,见「注释的 Markdown 排版」;里面的名字不能点)/ 访问级别 / 异步(是 / 否)/ 装饰器(有才显示,源码原样照抄、按源码着色,多个换行)/ 输入参数(`N 个` 或「无」,每个参数一行:名称 \| 类型(没标注写「未标注」)\| 默认值(`默认 ` + 译文,没有留空))/ 输出参数(返回类型的译文,没标注写「未标注」) |
@@ -123,6 +125,25 @@
- 空字面量不展开:值是一行 `空字典` / `空对象`(一行译文里也这样翻,同 `空数组`)。
- `as const`、`satisfies` 包着的对象(`{ … } as const`)不算字面量本身,照旧一行译文。
### 推导式(写在表达式里的遍历)
原型画板「遍历 · 推导式」。Python 的列表 / 集合 / 字典推导式、生成器表达式(含 `async for`、几节 `for`、`if`、套着的推导式)不管出现在哪(一行译文、属性表的值 / 初始值 / 默认值、字典项的值),都原地换成一个小框:
- 框:嵌在前后文字里(`display: inline-block`),框头是循环图标(lucide `Repeat`,12px)和结果类型,后面是重排后的译文。边框、底色、框头字的颜色都取「循环」块的种类色(`--cr-kind-loop`:边框 45%、底色 14%,圆角 4px,左右留 5px);框头界面字体 11px、500 字重。上下边框算在 20px 行高里,框和前后文字基线对齐、不把一行撑高;框里套的框行高再减 2px。整个框放不下时挪到下一行,比一行还长时占满一行、在框里换行。
- 结果类型:生成器 / 列表 / 集合 / 字典(`[…]` 列表、`{…}` 集合、`{k: v …}` 字典、`(…)` 生成器)。
- 框里按读的顺序重排:每节 `for` 写「遍历 <来源> 的每个 <变量>」,紧挨着的几节 `for` 用顿号接着写(「遍历 xss 的每个 xs、xs 的每个 x」),`async for` 写「异步遍历」(同步、异步换了就另起一句「,异步遍历 …」),`if` 在它所在的位置写「,仅当 <条件>」(后面再有 `for` 就另起一句「,遍历 …」),最后「,取 <值>」,字典是「<键>: <值>」。来源、变量、条件、值照常直译,里面还有推导式就是框里的框。「遍历」「的每个」「仅当」「取」「异步遍历」按关键字着色,逗号、顿号、空格按普通文字。
- 括号:框代替了推导式自己的方括号 / 花括号 / 圆括号,不再写出来;Python 调用唯一的参数是生成器时只写一对括号(`f(x for x in y)`),这对括号属于调用,照常写在框外,紧贴着框:`"".join(` 框 `)`。生成器是几个参数之一时(`f(a, (x for x in y))`)括号是它自己的,不写。
- 框里的名字照样带源码下标:⌘ / Ctrl + 点击跳到定义、单击高亮各处出现都照旧;跳到定义的目标只显示在框里(推导式的变量)时,目标是框所在的块。
- 样例(必须逐字一致;框写成 `⟦类型|框里的文字⟧`):
| 源码 | 译文 |
| --- | --- |
| `return "".join(secrets.choice(ALPHABET) for _ in range(length))` | `返回 "".join(⟦生成器|遍历 range(length) 的每个 _,取 secrets.choice(ALPHABET)⟧)` |
| `return [u.id for u in users if u.is_active]` | `返回 ⟦列表|遍历 users 的每个 u,仅当 u.is_active,取 u.id⟧` |
| `return {u.id: u for u in users}` | `返回 ⟦字典|遍历 users 的每个 u,取 u.id: u⟧` |
- TypeScript 没有这种写法,不涉及。几种读不成遍历的写法(语法树认、Python 3 不认的 `for x in a, b`,写到一半缺了变量 / 来源 / 条件 / 值的)照常逐词直译。
### 注释的 Markdown 排版
- 用在:注释块(`#`、`//`、`/* */`、不挂在函数、类上的 JSDoc、Python 模块开头的 docstring)的预览,函数、类属性表的「说明」。视图数据里它们是 `{ markdown }`(见「通用契约」的 `Markdown`),webview 按数据决定怎么渲染,不猜。
@@ -229,6 +250,23 @@ export type EntryFacts =
| { kind: 'spread', value: Node } // **other、...other:value 是展开的表达式
| { kind: 'method', name: Node, node: Node, header: SourceSpan } // 对象里的方法、getter、setter:header 同 BlockShape.header,只到主体开始之前
/** 写在表达式里的遍历(Python 推导式、生成器表达式)得到什么。 */
export type IterationResult = 'generator' | 'list' | 'set' | 'dict'
/** 遍历的一节,照源码顺序:从来源里逐个取出变量(async:异步地取),或只留下满足条件的。 */
export type IterationClause =
| { kind: 'for', variables: Node, source: Node, async: boolean }
| { kind: 'if', condition: Node }
/** 写在表达式里的遍历的结构:语言插件读出,翻译插件按自己的语序重排;各节、取的值里还能再套遍历。 */
export interface IterationFacts {
result: IterationResult
clauses: IterationClause[] // 至少一节,第一节是 for
value: Node // 每一项取的值
key?: Node // 结果是字典时每一项的键
sharedParentheses?: { open: Node, close: Node } // 遍历的括号同时是外面调用的括号(Python f(x for x in y)):写在遍历外面
}
export interface DeclaratorFacts {
name: Node
type?: Node
@@ -291,6 +329,15 @@ export type TokenRole =
*/
export interface Segment { text: string, role: TokenRole, offset?: number }
/** 框的样式,决定框头图标和框的颜色:'iteration' 是写在表达式里的遍历,图标、颜色同「循环」块。 */
export type GroupStyle = 'iteration'
/** 框起来的一组词:预览画成嵌在前后文字里的小框,框头是图标和 label(翻译插件给);框里的词照样带下标,还能再套框。 */
export interface SegmentGroup { style: GroupStyle, label: string, segments: Inline[] }
/** 译文里的一段:着色的词,或框起来的一组词。 */
export type Inline = Segment | SegmentGroup
/**
* 一段 Markdown 说明文字:注释、JSDoc、docstring 去掉注释符号、引号和共同缩进后的原文,不做别的转换。
* 预览按 Markdown 渲染成注释色的段落、列表、代码块(见「注释的 Markdown 排版」);第三方翻译插件给注释也填它。
@@ -298,7 +345,7 @@ export interface Segment { text: string, role: TokenRole, offset?: number }
export interface Markdown { markdown: string }
/** 预览里显示的一段文字:着色的词(一行译文、属性值),或整段 Markdown 说明(注释块、函数和类的说明)。 */
export type PreviewText = Segment[] | Markdown
export type PreviewText = Inline[] | Markdown
/**
* 属性表里的一行或一个子行讲的是哪个定义:定义出来的那个名字的节点(取自 facts)在源码里的范围——
@@ -308,7 +355,7 @@ export type PreviewText = Segment[] | Markdown
export interface Defines { defines?: SourceRange }
/** 属性表里展开的一个子行:几列对齐的格子;items 是展开在它下面的下一层子行(嵌套的字典 / 对象),一层层可以一直往下套。 */
export interface PropertyItem extends Defines { cells: Segment[][], items?: PropertyItem[] }
export interface PropertyItem extends Defines { cells: Inline[][], items?: PropertyItem[] }
/** 属性表的一行:属性名、着色的值或 Markdown 说明(函数、类的「说明」)、展开在下面的子行(输入参数、导入项、字典 / 对象的各项)。 */
export interface PropertyRow extends Defines { label: string, value: PreviewText, items?: PropertyItem[] }
@@ -351,7 +398,7 @@ export interface CodeReaderApi {
- `activate()` 返回 `{ codeReader: CodeReaderApi }`;其他扩展声明 `extensionDependencies` 后用 `extensions.getExtension('clurdra.superpowers-vscode-clurdra')!.exports.codeReader` 注册。
- 内置的 TypeScript、Python 语言插件和两个中文直译翻译插件在 `activate()` 里通过同一个 API 注册,没有旁路。
- 注册表变化时更新 `superpowers.codeReader.languageIds` 上下文,并让已打开的预览刷新。
- 分工:语言插件只从语法树读出和语言无关的事实(facts,引用源码节点,不做正则匹配文字);翻译插件决定属性名、枚举词(公共 / 是 / 默认导出)和值怎么译,也给筛选属性和值起名(`attributeNames`)。筛选属性的通用部分由宿主从 facts 推出(`attributes.ts` 的 `blockAttributes`),语言插件只在 `BlockShape.attributes` 里补自己语言特有的。第三方语言插件给函数、类、导入、常量、变量块填 facts 就能用上任何翻译插件的属性表;自己语言的字典 / 映射字面量填进 `DeclaratorFacts.entries` 就能展开成子行(中文模板直译按字面量的节点类型查叫法,见 `ZhTable.mapLiterals`)。
- 分工:语言插件只从语法树读出和语言无关的事实(facts,引用源码节点,不做正则匹配文字);翻译插件决定属性名、枚举词(公共 / 是 / 默认导出)和值怎么译,也给筛选属性和值起名(`attributeNames`)。筛选属性的通用部分由宿主从 facts 推出(`attributes.ts` 的 `blockAttributes`),语言插件只在 `BlockShape.attributes` 里补自己语言特有的。第三方语言插件给函数、类、导入、常量、变量块填 facts 就能用上任何翻译插件的属性表;自己语言的字典 / 映射字面量填进 `DeclaratorFacts.entries` 就能展开成子行(中文模板直译按字面量的节点类型查叫法,见 `ZhTable.mapLiterals`)。写在表达式里的遍历(推导式这类)由语言插件给一个读结构的函数(节点 → `IterationFacts`,Python 是 `languages/python.ts` 的 `pythonIteration`),中文模板直译通过 `ZhTable.iteration` 接上,排成一个 `SegmentGroup`(样式 `iteration`,框头是结果类型的译名);webview 只认框的样式 id,不认语言。
## 解析运行时
@@ -426,6 +473,8 @@ export interface CodeReaderApi {
| TypeScript | `ternary_expression`(`c ? a : b`) | 如果 〈condition〉 则 〈consequence〉 否则 〈alternative〉 |
| Python | `conditional_expression`(`a if c else b`,没有字段名,具名子节点依次是 a、c、b) | 如果 〈1 号〉 则 〈0 号〉 否则 〈2 号〉 |
写在表达式里的遍历(推导式)不走节点模板:它的节数不定、要换成一个框,由表里的 `iteration`(语言插件给的读结构的函数)认出来,引擎按「推导式」一节的规则排进框里;和节点模板一样,没整个落在要翻的这一段里就不重排、照常翻。
TypeScript 关键字表(流程 = keyword,声明 = storage):
| 流程 | 声明 / 字面量 |
@@ -434,7 +483,7 @@ TypeScript 关键字表(流程 = keyword,声明 = storage):
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 关键字: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 头部)取自(推导式里的 for / in 不逐词翻,整个重排,见「推导式」)、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 字节串。
@@ -525,7 +574,7 @@ type BlockView = BlockViewBase & ({ translation: PreviewText } | { properties: P
| hint | `--vscode-descriptionForeground` | 同左 |
| plain | `--vscode-editor-foreground` | 同左 |
属性表里:说明按 comment,访问级别(公共 / 私有 / 受保护)和「作为」按 keyword,是 / 否按 storage,函数名称按 function,类名称按 type,未标注、无、N 个、字典 N 项、对象 N 项、展开、默认、默认导出、全部、整个模块、匿名按 hint。父类名(TypeScript `extends` 后面、JavaScript `class_heritage` 里、Python 类名后面括号里直接写的名字)源码着色和属性表里都按 type。
属性表里:说明按 comment,访问级别(公共 / 私有 / 受保护)和「作为」按 keyword,是 / 否按 storage,函数名称按 function,类名称按 type,未标注、无、N 个、字典 N 项、对象 N 项、展开、默认、默认导出、全部、整个模块、匿名按 hint。推导式框里的「遍历」「的每个」「仅当」「取」「异步遍历」按 keyword,框头按「循环」块的种类色。父类名(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`。
@@ -539,8 +588,8 @@ vscode/src/codeReader/
blockView.ts CodeBlock → BlockView(源码着色、去缩进、调用翻译)
attributes.ts 块的筛选属性(从 facts 推出通用属性 + 语言自己报的)、文件里出现过的属性和块数
languages/typescript.ts
languages/python.ts
translators/zhTemplate.ts 模板直译引擎(按语言的表驱动)
languages/python.ts (pythonIteration:推导式的结构)
translators/zhTemplate.ts 模板直译引擎(按语言的表驱动;推导式排进框见 iterationGroup)
translators/typescriptZh.ts TS 表
translators/pythonZh.ts Python 表
explorer/fileTree.ts 侧栏文件树(FileTreeDecorator:给行添信息的接口)
@@ -556,7 +605,7 @@ vscode/src/codeReader/
editor/definitionAction.ts 查到的定义 → 怎么跳(纯函数)
editor/occurrences.ts 查名字的各处出现(vscode.executeDocumentHighlights)
index.ts registerCodeReader(context): CodeReaderApi
vscode/webview-ui/code-reader.html + src/codeReader/ 预览界面(React,Vite 第二个入口;属性表在 PropertyGrid.tsx,Markdown 渲染在 markdown.ts / TextPreview.tsx,筛选栏在 FilterBar.tsx,筛选规则在 filter.ts,高亮各处出现的规则在 occurrences.ts、接到页面上在 useOccurrences.ts)
vscode/webview-ui/code-reader.html + src/codeReader/ 预览界面(React,Vite 第二个入口;属性表在 PropertyGrid.tsx,着色的词和框在 SegmentText.tsx,Markdown 渲染在 markdown.ts / TextPreview.tsx,筛选栏在 FilterBar.tsx,筛选规则在 filter.ts,高亮各处出现的规则在 occurrences.ts、接到页面上在 useOccurrences.ts)
```
## 测试
@@ -566,6 +615,7 @@ vscode/webview-ui/code-reader.html + src/codeReader/ 预览界面(React,Vit
- 折叠:默认函数、类块折叠、其余展开;用户的选择盖过默认、重新加载后还在(嵌套子行的也是);各层子行的 id 互不相撞;旧版本存的状态不报错;跳到定义时默认折叠的函数、类也会展开。
- 属性表信息:`envLock.ts`、`sample.py`、`routes.py`(原型里的 FastAPI 路由)加内联片段,覆盖各种导入写法、形参写法、类成员访问修饰符、解构和多变量声明、Python 下划线规则;字典 / 对象字面量的各项:Python 字符串键、名字键、嵌套字典、`**` 展开、空字典、类属性,TypeScript 名字键、字符串键、计算属性名、简写、展开、方法和 getter(头部到主体之前)、嵌套对象、空对象,不是字面量的值没有 entries。
- 字典 / 对象的属性表:项数、键 | 值两列、嵌套一层层展开、「展开」、方法头部、空的一行;键和值里的名字、字符串带下标(嵌套的也是);每项标的定义是键,展开项不标;跳到定义落在对象属性名上只闪那一项、连外层各层一起展开,只是显示着的名字闪整块、展开到它那一层。
- 推导式:Python 语言插件读出的结构(调用唯一参数的生成器带 sharedParentheses、几个参数之一的不带,列表带条件,字典分出键和值,集合的解构变量,几节 for / if 照源码顺序,async for,套着的各自认,字面量和 for 语句不算);中文直译「推导式」一节的样例表逐字一致,框的样式、框头、关键字着色,顿号连写、条件位置、另起一句、异步遍历、集合、框里的框、多个参数旁边的生成器、跨行带注释的生成器,属性表的默认值和字典项的值里也换;框里的名字带下标且指回源码原文,高亮各处出现、跳到定义都认框里的名字(只显示在框里的推导式变量 → 框所在的块);TypeScript 不认。
- 翻译:上面两张样例表逐字一致;属性名不被翻;每个被翻的词单独成段;节点模板嵌套时正确、槽位里照样翻、没命中模板的节点照抄;属性表里的值和一行译文用同一套表和模板。
- `blockView`:`codeTokens` 拼起来等于 `code`;去缩进正确;函数、类、导入、常量、变量块只带属性表,其余块只带译文。
- 注释的 Markdown:注释块、函数和类「说明」的视图数据是 `{ markdown }`,原文照抄(reST 角色、双反引号、JSDoc 标签、HTML 都不转换),行注释连成的块留得住缩进四格的代码块;渲染时 HTML 当文字、带协议的网址和邮箱成链接、不带协议的文件名不成链接,`javascript:` / `command:` / 相对路径的链接和图片不渲染;跳到定义不认 Markdown 里的名字。