✨ feat(vscode): 代码阅读函数、导入、常量、变量块预览改成属性表
This commit is contained in:
1 parent
0c4c9b0fba
commit
dd9b39ac5d
19 files changed
+1458
-213
No files matched your search
@@ -22,6 +22,7 @@
|
||||
| 折叠 | 有子块的块能折叠,默认全部展开 |
|
||||
| 侧栏文件树 | 只看和打开:展开目录、打开文件、刷新、全部折叠;不做新建 / 重命名 / 删除 |
|
||||
| 译文用词 | 结构照抄原语言,只翻关键字、内置类型、运算符;用专业术语(`export` → 公共,`catch` → 捕获,`===` → 全等) |
|
||||
| 函数、导入、常量、变量块 | 预览不显示一行译文,显示树形属性表;属性值里的类型、表达式照常直译 |
|
||||
|
||||
## 用户可见行为
|
||||
|
||||
@@ -46,12 +47,30 @@
|
||||
- 块的组成(从左到右):折叠箭头(只有有子块的块才显示,其余占位对齐)、种类标签、内容、右侧切换按钮。
|
||||
- 种类标签和底色按种类区分(颜色见下表);注释块虚线框。
|
||||
- 内容两种状态,每块单独切:
|
||||
- 预览:译文,按词着色,等宽字体(中文部分用系统中文字体回退)。
|
||||
- 预览:函数、导入、常量、变量块是属性表(见下节),其余块是一行译文;按词着色,等宽字体(中文部分用系统中文字体回退)。
|
||||
- 编辑:这一块的源码(按语法树着色),可以直接改;蓝色边框。按钮文字「预览」;Esc 也回到预览。
|
||||
- 复合语句(函数、条件、循环……)块里只放它自己的头部那几行(主体交给子块);收尾括号、空行不成块。
|
||||
- 顶部工具栏:翻译下拉框(多于一种翻译时才显示)。
|
||||
- 解析失败:显示错误原因,并提示用「编辑」查看源码。
|
||||
|
||||
### 属性表(函数、导入、常量、变量块)
|
||||
|
||||
卡片外观(折叠箭头、种类标签、底色、悬停高亮、「编辑 / 预览」切换)和其他块一样,只是内容换成一张属性表:左列属性名(界面字体、`descriptionForeground` 色,宽 76px),右列是值(等宽字体,按词性着色,长了换行),两列间距 12px,行高 20px。「输入参数」「导入项」下面展开子行,属性名前带一个向下的箭头(固定展开,不单独折叠);子行往右缩进 5px,左边一条 1px 竖线(`editorIndentGuide-background1`)。输入参数的子行分名称 / 类型 / 默认值三列对齐,前两列宽 150px / 220px,编辑区窄时按 28% / 38% 收,默认值列占剩下的。
|
||||
|
||||
| 种类 | 行 |
|
||||
| --- | --- |
|
||||
| 函数 | 名称(函数色;匿名函数写「匿名」)/ 访问级别 / 异步(是 / 否)/ 装饰器(有才显示,源码原样照抄、按源码着色,多个换行)/ 输入参数(`N 个` 或「无」,每个参数一行:名称 \| 类型(没标注写「未标注」)\| 默认值(`默认 ` + 译文,没有留空))/ 输出参数(返回类型的译文,没标注写「未标注」) |
|
||||
| 导入 | 来源(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` 是私有;嵌套在函数体、条件等语句块里的没有这一行。
|
||||
- TypeScript 类成员:写了 `public` / `private` / `protected` 按写的,`#name` 是私有,都没写是公共。
|
||||
- Python:只有模块顶层和类主体里的声明有;名字单下划线开头(`_x`、`__x`)是私有,前后双下划线(`__init__`)是公共,其余公共;解构赋值里的名字全是私有才算私有;赋值目标是属性、下标(`self.x = …`)时没有这一行。
|
||||
|
||||
值里的表达式只翻落在块头部里的那一段:`const v = useMemo(() => { … })` 这种值带语句块主体回调的块,回调主体是子块,值只翻到 `useMemo(() =>`,和块头部一致。
|
||||
|
||||
## 通用契约
|
||||
|
||||
放在 `vscode/src/codeReader/contract.ts`,语法树类型用 `@vscode/tree-sitter-wasm` 自带的 web-tree-sitter 类型。
|
||||
@@ -64,8 +83,46 @@ export type BlockKind =
|
||||
/** 源码里的一段,行号从 0 开始,end 为开区间的字节偏移。 */
|
||||
export interface SourceSpan { startIndex: number, endIndex: number, startRow: number, endRow: number }
|
||||
|
||||
export interface CodeBlock {
|
||||
kind: BlockKind
|
||||
// ── 属性表块的结构化信息(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 // 匿名函数没有
|
||||
access?: Access // 嵌套在语句块里的函数没有
|
||||
async: boolean
|
||||
decorators: Node[]
|
||||
parameters: ParameterFacts[]
|
||||
returnType?: Node
|
||||
}
|
||||
|
||||
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
|
||||
/** 块自己显示 / 编辑的那一段:复合语句只到主体开始之前,其余是整条语句。 */
|
||||
@@ -73,15 +130,34 @@ export interface CodeBlock {
|
||||
children: CodeBlock[]
|
||||
}
|
||||
|
||||
/** 预览成属性表的块:种类和 facts 绑在一起,语言插件漏给 facts 编译不过。 */
|
||||
export interface FunctionBlock extends BlockShape { kind: 'function', facts: FunctionFacts }
|
||||
export interface ImportBlock extends BlockShape { kind: 'import', facts: ImportFacts }
|
||||
export interface VariableBlock extends BlockShape { kind: 'const' | 'variable', facts: VariableFacts }
|
||||
export type FactBlock = FunctionBlock | ImportBlock | VariableBlock
|
||||
|
||||
export type TextKind = Exclude<BlockKind, FactBlock['kind']>
|
||||
/** 预览成一行译文的块。 */
|
||||
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'
|
||||
|
||||
export interface Segment { text: string, role: TokenRole }
|
||||
|
||||
/** 属性表里展开的一个子行:几列对齐的格子。 */
|
||||
export interface PropertyItem { cells: Segment[][] }
|
||||
|
||||
/** 属性表的一行:属性名、着色的值、展开在下面的子行(输入参数、导入项)。 */
|
||||
export interface PropertyRow { label: string, value: Segment[], items?: PropertyItem[] }
|
||||
|
||||
/** 语言插件:语法树 → 代码块树。 */
|
||||
export interface LanguageAdapter {
|
||||
id: string
|
||||
@@ -94,12 +170,13 @@ export interface LanguageAdapter {
|
||||
tokenRole: (leaf: Node) => TokenRole
|
||||
}
|
||||
|
||||
/** 翻译插件:代码块 → 译文。 */
|
||||
/** 翻译插件:代码块 → 一行译文或属性表。 */
|
||||
export interface Translator {
|
||||
id: string // 'typescript.zh-template'
|
||||
label: string // '中文直译'
|
||||
languageIds: string[]
|
||||
translate: (block: CodeBlock, source: string) => Segment[]
|
||||
translate: (block: TextBlock, source: string) => Segment[] // 只翻 header 那一段
|
||||
describe: (block: FactBlock, source: string) => PropertyRow[] // 函数、导入、常量、变量块 → 属性表
|
||||
}
|
||||
|
||||
export interface CodeReaderApi {
|
||||
@@ -111,6 +188,7 @@ export interface CodeReaderApi {
|
||||
- `activate()` 返回 `{ codeReader: CodeReaderApi }`;其他扩展声明 `extensionDependencies` 后用 `extensions.getExtension('clurdra.superpowers-vscode-clurdra')!.exports.codeReader` 注册。
|
||||
- 内置的 TypeScript、Python 语言插件和两个中文直译翻译插件在 `activate()` 里通过同一个 API 注册,没有旁路。
|
||||
- 注册表变化时更新 `superpowers.codeReader.languageIds` 上下文,并让已打开的预览刷新。
|
||||
- 分工:语言插件只从语法树读出和语言无关的事实(facts,引用源码节点,不做正则匹配文字);翻译插件决定属性名、枚举词(公共 / 是 / 默认导出)和值怎么译。第三方语言插件给函数、导入、常量、变量块填 facts 就能用上任何翻译插件的属性表。
|
||||
|
||||
## 解析运行时
|
||||
|
||||
@@ -151,6 +229,15 @@ export interface CodeReaderApi {
|
||||
- `return_statement` → return;其余 → statement。
|
||||
- 复合语句头部到主体开始为止(包含行尾冒号,翻译时去掉)。
|
||||
|
||||
### 属性表信息(facts)从哪来
|
||||
|
||||
- TypeScript 函数:`function_declaration` / `generator_function_declaration` / `function_signature`、类里的 `method_definition` / 方法签名、值是带语句块主体函数的变量和类字段、`export default` 的函数值。名称取声明名 / 变量名 / 字段名;参数来自 `formal_parameters`(`required_parameter` / `optional_parameter` 的 pattern、type、value,JavaScript 的 `assignment_pattern`),不带括号的单参数箭头函数取 `parameter`;返回类型取 `return_type` 里的类型;异步看有没有 `async`;类成员的装饰器是类主体里排在它前面的 `decorator` 兄弟节点。
|
||||
- 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` 子节点;装饰器是 `decorated_definition` 里的 `decorator`;参数 `identifier`、`typed_parameter`(名字是第一个具名子节点)、`default_parameter`、`typed_default_parameter`、`*args` / `**kwargs`,`*` 和 `/` 分隔符不算参数。
|
||||
- Python 导入:`import_statement` 每个 name 一个来源、导入项是整个模块;`import_from_statement` 的 module_name 和各 name(`aliased_import` 拆出 alias),`wildcard_import` 是全部;`future_import_statement` 的来源是 `__future__`。
|
||||
- Python 变量:`assignment` 的 left / type / right。
|
||||
|
||||
## 中文模板直译
|
||||
|
||||
结构照抄源码(按节点模板重排的语法结构除外,见下文),只替换三类东西,其余(标识符、属性名、字面量、标点)原样保留并按词性着色:
|
||||
@@ -159,6 +246,8 @@ export interface CodeReaderApi {
|
||||
2. 内置类型:只在类型位置翻(TS 的 `predefined_type`、Python 注解里的内置类型名)。
|
||||
3. 运算符:`===` 全等、`!==` 不全等、`==` 等于、`!=` 不等于、`&&` 并且、`||` 或者、`!` 非(Python 的 `not` / `and` / `or` 本身是关键字)。
|
||||
|
||||
同一套表和节点模板也用来翻属性表里的值:引擎能翻任意一个节点(只取它落在块头部里的那一段),参数类型、默认值、返回类型、变量类型和初始值都走这条路。
|
||||
|
||||
其他规则:空数组字面量 → 空数组(Python 空列表 → 空列表、空字典 → 空字典);复合语句头部去掉结尾的 `{` 或 `:`;多行合成一行(连续空白压成一个空格,右括号前的尾逗号去掉);注释去掉注释符号,原文照搬。关键字与前后内容之间保留源码里的空格(`!` 翻成「非」后补一个空格)。
|
||||
|
||||
关键字按所在语法节点选择译法:同一个关键字在不同语法结构里意思不同时,表里写一个默认译法,再加若干「语境 → 译法」。语境是从关键字节点往上的节点类型链,外层在前:`raise_statement` 指父节点是 raise_statement,`for_statement > else_clause` 指父节点是 else_clause、祖父节点是 for_statement(关键字包在一层子句节点里时要看到祖父节点)。几条语境同时匹配时取最长的,都不匹配用默认译法。节点类型以 tree-sitter 语法为准。下面两张表里写成「(语境)译法 / (语境)译法」的关键字就是按这条规则区分的,排第一个的是默认译法。
|
||||
@@ -182,38 +271,48 @@ Python 关键字:import 导入、from(导入)从 / (`raise … from`)
|
||||
|
||||
Python 内置类型(仅注解里):str 字符串、int 整数、float 浮点数、bool 布尔、list 列表、dict 字典、set 集合、tuple 元组、bytes 字节串。
|
||||
|
||||
样例(必须逐字一致;条件表达式两行以外来自原型 `test.js`):
|
||||
一行译文样例(必须逐字一致):
|
||||
|
||||
| 源码 | 译文 |
|
||||
| --- | --- |
|
||||
| `export async function findEnvFiles(workspaceRoot: string): Promise<string[]> {` | `公共 异步 函数 findEnvFiles(workspaceRoot: 字符串): Promise<字符串[]>` |
|
||||
| `async function chmodAll(workspaceRoot: string, mode: number): Promise<EnvLockResult> {` | `异步 函数 chmodAll(workspaceRoot: 字符串, mode: 数值): Promise<EnvLockResult>` |
|
||||
| `import { promises as fsp } from 'node:fs'` | `导入 { promises 作为 fsp } 来自 'node:fs'` |
|
||||
| `if (!folder)` | `如果 (非 folder)` |
|
||||
| `return []` | `返回 空数组` |
|
||||
| `for (const f of files) {` | `遍历 (常量 f 取自 files)` |
|
||||
| `failed: { path: string, reason: string }[]` | `failed: { path: 字符串, reason: 字符串 }[]` |
|
||||
| `const issue = issues.find(i => i.number === issueNumber)` | `常量 issue = issues.find(i => i.number 全等 issueNumber)` |
|
||||
| `return {⏎ locked: true,⏎ … }`(issueLock.ts 20–24 行) | `返回 { locked: 真, prerequisiteNumber: prereq.number, prerequisiteColumn: prereq.column }` |
|
||||
| `def find_env_files(root: str) -> list[str]:` | `函数 find_env_files(root: 字符串) -> 列表[字符串]` |
|
||||
| `err instanceof Error ? err.message : String(err)` | `如果 err 属于 Error 则 err.message 否则 String(err)` |
|
||||
| `_default_port = 5680 if _role == "beat" else 5679` | `_default_port = 如果 _role 等于 "beat" 则 5680 否则 5679` |
|
||||
|
||||
属性表样例(必须逐字一致;子行各列用 ` | ` 隔开):
|
||||
|
||||
| 源码 | 属性表 |
|
||||
| --- | --- |
|
||||
| `export async function findEnvFiles(workspaceRoot: string): Promise<string[]> {` | 名称 findEnvFiles / 访问级别 公共 / 异步 是 / 输入参数 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]:` | 名称 find_env_files / 访问级别 公共 / 异步 否 / 输入参数 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` |
|
||||
|
||||
## 预览数据与消息
|
||||
|
||||
扩展端把代码块树转成可序列化的视图数据发给 webview:
|
||||
|
||||
```ts
|
||||
interface BlockView {
|
||||
interface BlockViewBase {
|
||||
id: string // 在树里的位置,例如 '6.1.0'
|
||||
kind: BlockKind
|
||||
startLine: number // header 的起止行,0 起
|
||||
endLine: number
|
||||
code: string // header 源码,去掉共同缩进
|
||||
codeTokens: Segment[] // 源码着色(拼起来等于 code)
|
||||
translation: Segment[]
|
||||
children: BlockView[]
|
||||
}
|
||||
|
||||
/** 属性表块(有 facts)只带 properties,其余块只带 translation;没有翻译插件时是空数组。 */
|
||||
type BlockView = BlockViewBase & ({ translation: Segment[] } | { properties: PropertyRow[] })
|
||||
```
|
||||
|
||||
- 扩展 → webview:`{ type: 'document', version, languageId, blocks: BlockView[], translators: { id, label }[], translatorId, error?: string }`,文档每次变化(防抖 150ms)重发。
|
||||
@@ -237,8 +336,11 @@ interface BlockView {
|
||||
| string | #ce9178 | #a31515 |
|
||||
| number / literal | #b5cea8 | #098658 |
|
||||
| comment | #6a9955 | #008000 |
|
||||
| hint | `--vscode-descriptionForeground` | 同左 |
|
||||
| plain | `--vscode-editor-foreground` | 同左 |
|
||||
|
||||
属性表里:访问级别(公共 / 私有 / 受保护)和「作为」按 keyword,是 / 否按 storage,函数名称按 function,未标注、无、N 个、默认、默认导出、全部、整个模块、匿名按 hint。
|
||||
|
||||
种类(标签字色 + 标签底 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`。
|
||||
|
||||
## 代码位置
|
||||
@@ -258,14 +360,15 @@ vscode/src/codeReader/
|
||||
editor/previewEditor.ts CustomTextEditorProvider
|
||||
editor/messages.ts 消息类型
|
||||
index.ts registerCodeReader(context): CodeReaderApi
|
||||
vscode/webview-ui/code-reader.html + src/codeReader/ 预览界面(React,Vite 第二个入口)
|
||||
vscode/webview-ui/code-reader.html + src/codeReader/ 预览界面(React,Vite 第二个入口;属性表在 PropertyGrid.tsx)
|
||||
```
|
||||
|
||||
## 测试
|
||||
|
||||
- 语言插件:`envLock.ts`、`issueLock.ts` 的代码块树(种类、header 行号、层级)与原型 `CODE_TREE` 一致;Python 样例覆盖 import / def / docstring / if-elif-else / for / try-except / with / 推导式。
|
||||
- 翻译:上面样例表逐字一致;属性名不被翻;每个被翻的词单独成段;节点模板嵌套时正确、槽位里照样翻、没命中模板的节点照抄。
|
||||
- `blockView`:`codeTokens` 拼起来等于 `code`;去缩进正确。
|
||||
- 属性表信息:`envLock.ts`、`sample.py`、`routes.py`(原型里的 FastAPI 路由)加内联片段,覆盖各种导入写法、形参写法、类成员访问修饰符、解构和多变量声明、Python 下划线规则。
|
||||
- 翻译:上面两张样例表逐字一致;属性名不被翻;每个被翻的词单独成段;节点模板嵌套时正确、槽位里照样翻、没命中模板的节点照抄;属性表里的值和一行译文用同一套表和模板。
|
||||
- `blockView`:`codeTokens` 拼起来等于 `code`;去缩进正确;函数、导入、常量、变量块只带属性表,其余块只带译文。
|
||||
- 行替换:多行块替换后缩进正确。
|
||||
- 注册表:注册 / 注销后 `languageIds` 与可选翻译正确;同一语言多个翻译时按 `globalState` 选中。
|
||||
|
||||
|
||||
Reference in new issue
Block a user