Files
superwork/vscode/src/codeReader/contract.ts
T

1196 lines
65 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import type { Node } from '@vscode/tree-sitter-wasm'
import type { Disposable } from 'vscode'
export type { Node }
/**
* 块的种类。interpreter 到 pipeline 给脚本语言(shell):interpreter 第一行 `#!` 写的解释器,options 打开 / 关掉解释器的开关
* (`set -euo pipefail`),case 按一个值分情况(整条语句,和它的每个情况),command 执行一条命令,pipeline 几条命令用管道串起来。
* directive 往后给构建文件(Dockerfile):directive 文件开头给构建工具看的解析指令(`# syntax=…`),stage 一个构建阶段(FROM 和它后面的指令),
* run 构建时执行的命令,copy 往镜像里复制文件,setting 一条设置(工作目录、运行身份、端口……),label 镜像的标签,
* health 健康检查,start 容器启动时执行的命令。
* comment 是讲整个文件的注释(文件开头的说明);别的注释不单独成块,挂在它讲的块上(见 BlockShape.comments)。
*/
export type BlockKind
= | 'import' | 'function' | 'class' | 'enum' | 'exception' | 'type' | 'field' | 'const' | 'variable'
| 'trait' | 'impl' | 'module' | 'attribute'
| 'if' | 'else' | 'loop' | 'try' | 'catch' | 'finally' | 'return' | 'statement' | 'comment'
| 'interpreter' | 'options' | 'case' | 'command' | 'pipeline'
| 'directive' | 'stage' | 'run' | 'copy' | 'setting' | 'label' | 'health' | 'start'
/** 源码里一段的下标:源码字符串下标(web-tree-sitter 的 startIndex / endIndex),end 为开区间。 */
export interface SourceRange { startIndex: number, endIndex: number }
/** 源码里的一段,带起止行号(从 0 开始)。 */
export interface SourceSpan extends SourceRange { startRow: number, endRow: number }
/** 访问级别。 */
export type Access = 'public' | 'private' | 'protected'
/** 函数的一个参数;节点都指向源码。 */
export interface ParameterFacts {
/** 参数名;解构参数是整个模式,`...rest`、`*args`、`**kwargs` 带着前缀。 */
name: Node
/** TypeScript 的可省略参数 `x?`。 */
optional: boolean
type?: Node
default?: Node
}
export interface FunctionFacts {
/** 匿名函数(`export default function () {}`)没有。 */
name?: Node
/**
* 函数自己的说明:TypeScript 是紧贴在声明前面的 JSDoc 注释(以 `/**` 开头),Python 是主体开头的 docstring(字符串节点)。
* 它算在块的头部里,不再单独成一块注释。
*/
doc?: Node
/** 嵌套在函数体、条件等语句块里的函数没有。 */
access?: Access
/** 异步函数;没有这个概念的语言不填。 */
async?: boolean
decorators: Node[]
parameters: ParameterFacts[]
returnType?: Node
/**
* 不写参数表、按位置取参数的语言(shell 的 `$1`):函数里用到的第几个参数,照位置排;
* 函数里给它起了名字(`local url="$1"`)时 name 是那个名字。按参数表取参数的语言不填,填了 parameters 就是空的。
*/
positional?: PositionalParameter[]
/** 函数打印到输出的那几句(shell 的 echo / printf,没重定向到别处):调用处拿到的就是它们打印的;没有这个概念的语言不填。 */
prints?: Node[]
/** 函数的输出被调用处拿去用了(shell 同一文件里有 `$(函数名 …)`)。 */
captured?: boolean
/**
* 函数的结果怎么来(shell 这种没有返回值、只有成不成功的语言):写了 return 的是那几句(return 语句的节点),
* 没写的是空数组,意思是最后一句成不成功。别的语言不填。
*/
returns?: Node[]
}
/** 按位置取的一个参数:第几个(1 起),函数里给它起的名字。 */
export interface PositionalParameter {
position: number
name?: Node
}
/** 第一行 `#!` 写的解释器。 */
export interface InterpreterFacts {
/** 解释器叫什么:`#!/usr/bin/env bash` 和 `#!/bin/bash` 都是 bash。 */
program: string
/** 怎么找到它:交给 env 按 PATH 找,还是写死的路径。 */
lookup: 'env' | 'path'
/** `#!` 后面写的整句(`/usr/bin/env bash`、`/bin/bash -e`)。 */
command: string
}
/** 打开 / 关掉解释器的一个开关(shell 的 `set -e`、`set -o pipefail`)。 */
export interface SwitchFacts {
/** 开关的 id:语言自己的叫法(bash 是长名字 errexit、nounset、pipefail……),翻译插件按它起名。 */
id: string
on: boolean
/** 源码里怎么写的(`-e`、`-o pipefail`)。 */
flag: string
}
export interface OptionsFacts {
/** 照源码顺序。 */
switches: SwitchFacts[]
}
/** 文件开头给构建工具看的一条解析指令(Dockerfile 的 `# syntax=docker/dockerfile:1.7`):名字和值照写。 */
export interface ParserDirective {
name: string
value: string
}
export interface DirectiveFacts {
/** 照源码顺序,一行一条。 */
directives: ParserDirective[]
}
/**
* 写在指令后面的一个选项(Dockerfile 的 `--from=deps`、`--chown=u:g`、`--interval=30s`):名字、值,值在源码里从哪开始
* (值里的阶段名、变量能跳到定义)。node 是整个选项。
*/
export interface InstructionFlag {
node: Node
name: string
value: string
valueStart: number
}
/** 一个构建阶段(Dockerfile 的 FROM),它后面到下一个 FROM 之前的指令是它的子块。 */
export interface StageFacts {
/** 阶段名(`AS deps` 的 deps);没起名的没有。 */
name?: Node
/** 第几个阶段,从 0 数:没起名的阶段按它引用(`COPY --from=0`)。 */
index: number
/** 紧贴在 FROM 上面的几行注释(中间没空行):阶段的说明,算在块的头部里。 */
doc?: Node[]
/** 基础镜像的名字(`node:22-alpine` 的 node);stage 为真时是前面某个阶段的名字。 */
image: Node
/** 前面的阶段名(`FROM builder`):从那个阶段接着往下建。 */
stage: boolean
/** 标签(`:22-alpine`,带冒号)、摘要(`@sha256:…`,带 @),写了才有。 */
tag?: Node
digest?: Node
/** FROM 后面的选项(`--platform=linux/amd64`)。 */
flags: InstructionFlag[]
}
/** 一条要执行的命令的两种写法:数组(`["node", "server.js"]`,不经过 shell,node 是整个数组),或交给 shell 执行的一段代码。 */
export type CommandForm
= | { form: 'exec', node: Node }
| { form: 'shell', code: EmbeddedCode }
/** 健康检查(Dockerfile 的 HEALTHCHECK):none 是关掉健康检查(`HEALTHCHECK NONE`);否则有选项(间隔、超时……)和检查命令。 */
export interface HealthFacts {
none: boolean
flags: InstructionFlag[]
command?: CommandForm
}
/** 容器启动时执行的命令:CMD(默认命令,`docker run` 后面写了命令就换掉它)或 ENTRYPOINT(入口,CMD 成了它的参数)。 */
export interface StartFacts {
instruction: 'cmd' | 'entrypoint'
command: CommandForm
}
/**
* 写在这门语言里的另一种语言的代码(Dockerfile 的 RUN、shell 写法的 CMD、HEALTHCHECK 的检查命令是 shell)。
* ranges 是源码里属于它的几段,去掉了外层语言的记号(续行的 `\`),拼起来是一份那种语言的代码;宿主按 languageId 找语言插件,
* 只解析这几段(下标照旧是这份源码的),块交给那种语言的翻译插件。node 是外层语法树里装着它的节点,翻译插件拿它问 SourceDocument.embedded。
*/
export interface EmbeddedCode {
node: Node
languageId: string
ranges: SourceRange[]
}
/**
* 一段嵌着的代码被当成一串步骤执行时(Dockerfile 的 RUN)怎么分块:一步一块;chained 表示前一步失败就不往下走(shell 用 && 连着的一串)。
* 见 LanguageAdapter.steps。
*/
export interface StepBlocks {
blocks: CodeBlock[]
chained: boolean
}
/**
* 宿主读嵌着的代码读出来的(SourceDocument.embedded):各步读成的一行(那种语言的翻译插件翻的,可以带一张表;注释不算一步),
* 是不是一步失败就停。whole 表示整段就是一步(没有别的块):这一步就是整段,它的子块归嵌着它的块;
* 否则各块是嵌着它的块的子块(见 TextBlock.embedded)。连词、「N 条命令」这类叫法归本语言的翻译插件。
*/
export interface EmbeddedReading {
steps: { text: Inline[], table?: PreviewTable }[]
chained: boolean
whole: boolean
}
/**
* 父类的一项:TypeScript `extends Base<T>` 里的 Base(一个表达式)和跟在它后面的泛型实参 `<T>`(语法里是两个节点);
* Python 类参数列表里的一项,`metaclass=ABCMeta` 这类关键字参数、`*bases` 也算。
*/
export interface BaseFacts {
value: Node
typeArguments?: Node
}
export interface ClassFacts {
/** 匿名类(`export default class {}`)没有;赋给变量、类字段的类表达式取变量名、字段名。 */
name?: Node
/** 类自己的说明,同 FunctionFacts.doc:TypeScript 是紧贴在前面的 JSDoc,Python 是主体开头的 docstring。 */
doc?: Node
/** 同函数:嵌套在函数体、条件等语句块里的类没有。 */
access?: Access
/** 抽象类(TypeScript 的 `abstract class`);没有这个概念的语言不填。 */
abstract?: boolean
decorators: Node[]
/** 泛型参数,每个参数一项(TypeScript `<T extends X = Y>` 里的 `T extends X = Y`)。 */
typeParameters: Node[]
/** 按源码顺序。 */
bases: BaseFacts[]
/** 实现的接口(TypeScript 的 `implements A, B<T>`),每个接口一项。 */
implements: Node[]
}
/** Rust 的内部属性 `#![…]`:`cfg_attr(条件, a, b)` 拆成生效条件和 a、b 两个属性,别的属性就是它自己一个。 */
export interface AttributeFacts {
/** 生效的各个属性,照写的顺序。 */
attributes: AttributeItem[]
/** `cfg_attr` 的生效条件;别的属性没有。 */
condition?: CfgPredicate
/** 写在哪一层:文件顶层、`mod` 身体、`impl` 身体;别处(`extern` 块里)没有。作用范围按它定,文件顶层还要看是不是 crate 根文件。 */
placement?: 'file' | 'module' | 'impl'
}
/** 一个属性:`名字`、`名字 = 值` 或 `名字(各项)`。 */
export interface AttributeItem {
/** 整个属性(名字连参数)在源码里的范围,预览照原文着色。 */
span: SourceSpan
/** 名字,路径照写(`rustfmt::skip`)。 */
name: string
/** `名字 = 值` 的值:字符串去掉引号,别的照原文。 */
value?: string
/** `名字(…)` 括号里逗号隔开的各项,照原文(`clippy::too_many_arguments` 是一项);没写括号是空的。 */
arguments: string[]
}
/** `cfg` 条件:`not(…)`、`all(…)`、`any(…)`,一个配置项(`test`、`target_os = "windows"`),认不出的写法照原文。 */
export type CfgPredicate
= | { kind: 'not', operand: CfgPredicate }
| { kind: 'all' | 'any', operands: CfgPredicate[] }
| { kind: 'option', name: string, value?: string }
| { kind: 'other', text: string }
/** 枚举的一个成员;节点都指向源码。 */
export interface EnumMemberFacts {
name: Node
/** 源码里写的值;没写(TypeScript 的自增成员)、或值交给库自动取(Python 的 `auto()`)时没有。 */
value?: Node
/** 没写值、语言算得出来时的值(TypeScript 数值枚举自增:0、1、2……);算不出来时没有。 */
computed?: number
/**
* 成员的说明文字节点(注释、JSDoc、属性 docstring),照源码顺序:紧挨在它上面(从上一个成员之后起)单独成行的、
* 写在它这一行行尾的、Python 紧跟在它后面的属性 docstring。翻译插件逐个取原文连起来。
*/
docs: Node[]
}
/**
* 枚举:TypeScript 的 `enum` 由语言插件报;Python 没有枚举语法,自带 enum 库的写法(`class X(enum.Enum)`)由库规则认成这种块。
* 两边同一种块、同一张属性表。
*/
export interface EnumFacts {
name: Node
/** 枚举自己的说明,同 ClassFacts.doc。 */
doc?: Node
/** 同 ClassFacts.access。 */
access?: Access
decorators: Node[]
/** 照源码写的父类(Python 的 `str, enum.Enum`);TypeScript 没有。 */
bases: BaseFacts[]
/** 照源码顺序。 */
members: EnumMemberFacts[]
}
/** 异常类的一个类属性:Python 类主体里给一个名字赋值的语句(带不带注解、有没有值),TypeScript 的类字段(含 static);节点都指向源码。 */
export interface FieldFacts {
name: Node
type?: Node
value?: Node
/** 属性的说明文字节点,同 EnumMemberFacts.docs:紧挨在上面单独成行的注释 / JSDoc、这一行行尾的注释、Python 紧跟在后面的属性 docstring。 */
docs: Node[]
}
/**
* 异常类:父类是语言自带的异常类、同一文件里认出来的异常类,或名字按命名习惯以 Error / Exception(Python 还有 Warning)结尾。
* Python、TypeScript 都由语言插件报,同一种块、同一张属性表。块的头部是整个类(类属性在属性表里讲),留下的方法等是子块。
*/
export interface ExceptionFacts {
/** 同 ClassFacts.name。 */
name?: Node
/** 同 ClassFacts.doc。 */
doc?: Node
/** 同 ClassFacts.access。 */
access?: Access
decorators: Node[]
/** 照源码写的父类,同 ClassFacts.bases。 */
bases: BaseFacts[]
/** 类属性,照源码顺序。 */
fields: FieldFacts[]
/** 构造函数的参数(Python 的 `__init__` 去掉 self,TypeScript 的 `constructor(...)`);类里没写构造函数时没有。 */
parameters?: ParameterFacts[]
}
/** 导入的一项。 */
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 {
/** 来源模块,节点就是源码里的写法:TypeScript 是字符串字面量,Python 是点分模块名;Python 的 `import a, b` 有多个。 */
sources: Node[]
/** 只导入类型(TypeScript 的 `import type`);没有这个概念的语言不填。 */
typeOnly?: boolean
/** 副作用导入(`import './x'`)没有。 */
items: ImportItem[]
}
/** 字典 / 对象字面量里的一项,照源码顺序;注释不算。 */
export type EntryFacts
= | { kind: 'pair', key: Node, value: Node } // 'a': 1、a: 1、[k]: v;TypeScript 的简写 { a } 键和值是同一个节点
| { kind: 'spread', value: Node } // **other、...other:value 是展开的那个表达式
| { kind: 'method', name: Node, node: Node, header: SourceSpan } // 对象字面量里的方法、getter、setter:node 是整个方法,header 同 BlockShape.header,只到主体开始之前
/** 列表 / 元组 / 集合 / 数组字面量里的一项,照源码顺序;注释不算。 */
export type ElementFacts
= | { kind: 'item', value: Node } // 写出来的一项
| { kind: 'spread', value: Node } // *other、...other:value 是展开的那个表达式
/**
* 写出各项的字面量:语言插件从语法树读出结构(LanguageAdapter.collection),翻译插件把各项拆成子行。
* map 是字典 / 对象,sequence 是列表 / 元组 / 集合 / 数组;空字面量的各项是空数组。节点都指向源码,项的值里还能再套字面量。
*/
export type CollectionFacts
= | { kind: 'map', entries: EntryFacts[] }
| { kind: 'sequence', elements: ElementFacts[] }
/** 写在表达式里的遍历得到什么:逐个产出的生成器,或一次建好的列表、集合、字典。 */
export type IterationResult = 'generator' | 'list' | 'set' | 'dict'
/** 遍历的一节,照源码顺序:从来源里逐个取出变量(async:异步地取),或只留下满足条件的。 */
export type IterationClause
= | { kind: 'for', variables: Node, source: Node, async: boolean }
| { kind: 'if', condition: Node }
/**
* 写在表达式里的遍历(Python 的列表 / 集合 / 字典推导式、生成器表达式):语言插件从语法树读出结构(LanguageAdapter.iteration),
* 翻译插件按自己的语序重排。节点都指向源码;各节、每一项取的值里还能再套遍历。
*/
export interface IterationFacts {
result: IterationResult
/** 至少一节,第一节是 for。 */
clauses: IterationClause[]
/** 每一项取的值。 */
value: Node
/** 结果是字典时每一项的键。 */
key?: Node
/**
* 遍历自己的一对括号同时是外面调用的括号时(Python `f(x for x in y)` 只写一对括号)是这对括号:
* 它们属于外面的调用,照常写在遍历外面;别的写法里遍历自己的括号、方括号、花括号不写出来。
*/
sharedParentheses?: { open: Node, close: Node }
}
/** 调用里的一个实参,照源码顺序;注释不算。 */
export type CallArgument
= | { kind: 'positional', value: Node } // 按位置传的
| { kind: 'keyword', name: Node, value: Node } // 按名字传的:Python 的 name=value
| { kind: 'spread', value: Node } // 展开成按位置传的几个:*args、...args;value 是展开的那个表达式
| { kind: 'keywordSpread', value: Node } // 展开成按名字传的几个:Python 的 **kwargs
/**
* 一次调用(含新建对象):语言插件从语法树读出结构(LanguageAdapter.call),翻译插件把跨几行写的调用(拆开的列表里整项是调用的,
* 写成一行也算)排成「新建 / 调用 被调用的名字 · N 个参数」一行,实参摊成一张表。节点都指向源码。
*/
export interface CallFacts {
/** 被调用的表达式:`user_service.update_user`、`UserDetailResponse`;TypeScript 的 new 不算在里面。 */
callee: Node
/** callee 里最后那个名字(点分名字的最后一节):着色、查定义用它;callee 不是名字(`f()()`、`handlers[k]()`)时没有。 */
name?: Node
/**
* 写法本身说明了是不是新建对象时给:TypeScript 的 `new X()` 是,不带 new 的调用不是。
* Python 新建对象和调用函数写法一样,不给,翻译插件问 name 的定义是哪种块(SourceDocument.calleeKind)。
*/
construct?: boolean
/** 括号连同里面的实参:它跨了几行,这次调用就是跨几行写的。 */
argumentList: Node
arguments: CallArgument[]
}
/**
* 命令的一个重定向:把输出(1)、错误输出(2)或两样都写到 / 追加到一个文件,并进另一路,或从文件读输入。
* `>&2` 是输出并进错误输出,`2>/dev/null` 是错误输出写到 /dev/null。
*/
export type Redirect
= | { kind: 'write' | 'append', stream: 'output' | 'error' | 'both', target: Node }
| { kind: 'merge', stream: 'output' | 'error', into: 'output' | 'error' }
| { kind: 'read', source: Node }
/** 命令的一个词:节点,和它写死的文字(去掉引号、转义;里面有展开 `$x`、`$(…)` 的没有),规则认选项、选项的值用它。 */
export interface CommandWord {
node: Node
literal?: string
}
/** 执行一条命令(shell):语言插件从语法树读出结构(LanguageAdapter.command),命令规则按命令名读成译文(CommandRule)。 */
export interface CommandFacts {
/** 命令名(`rm`、`curl`、同一文件里定义的函数名)。 */
name: CommandWord
/** 命令名后面的各个词,照源码顺序:选项、选项的值、要处理的东西都在里面,规则自己认。 */
arguments: CommandWord[]
redirects: Redirect[]
}
/**
* 命令规则读出来的:一行译文;value 表示读出来的是一个东西(`dirname "$0"` 是脚本所在目录),不是一个动作;
* table 是排在这一行下面的表(选项写得多的命令:「选项 | 意思」)。
*/
export interface CommandReading {
text: Inline[]
value?: boolean
table?: PreviewTable
}
/** 命令规则读命令时能用的。 */
export interface CommandContext {
/** 当前翻译插件的 displayLanguage;规则按它挑自己的词表,没有这种语言的词表就不读(照抄源码)。 */
displayLanguage: string
/** 一个词照常翻(变量、字符串、展开读成译文,名字带源码下标,能跳到定义、高亮各处出现)。 */
translate: (word: Node) => Inline[]
/** 读另一条写在实参里的命令(`find … -exec rm -rf {} +` 里的 rm):name、args 是那条命令的词;没有它的规则时没有。 */
command: (name: CommandWord, args: CommandWord[]) => CommandReading | undefined
}
/**
* 命令规则:按命令名注册,像库规则一样把一条命令(它的选项)读成译文:`rm -rf dest` → 删除 dest(连同里面的东西,不提示)。
* 认不出的选项照抄;读不了(不认识的写法)返回 undefined,宿主照抄命令名、实参照常翻。没有规则的命令同样照抄。
*/
export interface CommandRule {
id: string // 'shell.rm'
label: string // 'rm'
languageIds: string[]
/** 管哪些命令名;几条规则都管同一个名字时先注册的算。'*' 管没有自己的规则的命令(`pg_repack --version` → 打印 pg_repack 的版本)。 */
commands: string[]
read: (command: CommandFacts, context: CommandContext) => CommandReading | undefined
}
/**
* 一份源码里自己认得出的一个名字(语言插件认,不问语言扩展):定义处,和用到它的各处(含定义处)。
* VS Code 里没有这种语言的定义提供者、高亮提供者时(shell),宿主拿它跳到定义、高亮各处出现:跳到的是用到的那处之前最近的定义。
*/
export interface LocalSymbol {
definitions: SourceRange[]
occurrences: SourceRange[]
}
/**
* 一份源码里一个名字的一个绑定(参数、变量、函数、类、导入的名字……),照这门语言的作用域规则认:各处出现(名字的范围,含定义处)
* 指的是同一个定义,内层同名的参数、推导式变量这类遮住外层的是另一个绑定。local 是这份源码自己定义的(参数、赋值、def、class……),
* 定义一定在这份源码里;导入的、哪儿都没定义的(内置的)不是。
*/
export interface NameBinding {
occurrences: SourceRange[]
local: boolean
}
export interface DeclaratorFacts {
/** 变量名;解构声明是整个模式。 */
name: Node
type?: Node
value?: Node
}
export interface VariableFacts {
/** 只有模块顶层(Python 还有类主体)的声明有。 */
access?: Access
/**
* 变量自己的说明,同 FunctionFacts.doc:Python 是模块、类主体里紧跟在这条赋值后面的字符串(属性 docstring,字符串节点;
* 中间可以隔空行和赋值行尾的注释)。它算在块的头部里,不再单独成一块。
*/
doc?: Node
/** 一条语句声明几个变量(`let a = 1, b = 2`)就有几项;几条连着写的声明并成一块时(Dockerfile 连着的 ENV)是它们的全部。 */
declarators: DeclaratorFacts[]
/**
* 变量在哪儿有效,语言分这个才填:build 是构建参数(Dockerfile 的 ARG,只在构建时有,值是默认值、构建时可以另传),
* environment 是环境变量(Dockerfile 的 ENV,构建时和容器运行时都有)。
*/
scope?: 'build' | 'environment'
}
/**
* 筛选属性的通用词表,筛选栏按这个顺序排,语言自己的属性排在后面:
* - kind:块的种类(BlockKind);
* - access:访问级别,'public' | 'private' | 'protected',语言可以加自己的值(Kotlin 的 'internal',见 BlockShape.attributes);
* - async、abstract、typeOnly:异步、抽象、仅类型,值是 'true' | 'false'。
* 宿主从块的 kind 和 facts 推出它们(FunctionFacts.access / async、ClassFacts.access / abstract、EnumFacts.access、ExceptionFacts.access、ImportFacts.typeOnly、VariableFacts.access),
* 语言插件不用另报;facts 里没填的(语言没有这个概念、块没有这一项),块就没有这个属性,也不受这个属性的筛选影响。
*/
export const COMMON_ATTRIBUTES = ['kind', 'access', 'async', 'abstract', 'typeOnly'] as const
export type CommonAttribute = (typeof COMMON_ATTRIBUTES)[number]
/** 块的筛选属性:属性 id → 值 id,都是稳定的英文 id,不放显示文字(显示名由翻译插件给,见 AttributeNames)。 */
export type BlockAttributes = Record<string, string>
export interface BlockShape {
/** 这一块对应的语法树节点(整条语句)。 */
node: Node
/**
* 块自己那一段(预览讲的就是它):复合语句只到主体开始之前,其余是整条语句。编辑时宿主给的是整块:头部连同各子块、讲它的注释、结尾。
* 块自己的说明(facts.doc、TextBlock.doc)也算在里面:JSDoc 往上延到注释开头,docstring、属性 docstring 往下延到字符串结尾。
*/
header: SourceSpan
children: CodeBlock[]
/**
* 讲这块的普通注释(不是说明),照源码顺序:同一层里它前面的注释(中间隔空行也算),这一层最后一块代码后面的注释,
* 子块一块代码都没有时子块里的注释。由 attachComments(languages/shared.ts)挂上;预览排成卡片上的「注释」一行,
* 块的编辑范围连同这几段注释。只有文件开头的说明单独成块(kind 'comment')。
*/
comments?: CommentBlock[]
/**
* 语言自己报的筛选属性,和宿主推出的通用属性(见 COMMON_ATTRIBUTES)合在一起:通用词表以外的属性(Rust 的 unsafe: 'true'),
* 或 facts 表达不了的通用属性值(Kotlin 的 access: 'internal',盖过从 facts 推出的)。kind 总是块的种类,这里填了不算。
*/
attributes?: BlockAttributes
}
/** 预览成属性表的块:语言插件从语法树读出结构化信息(facts),翻译插件把它排成属性表。 */
export interface FunctionBlock extends BlockShape { kind: 'function', facts: FunctionFacts }
export interface ClassBlock extends BlockShape { kind: 'class', facts: ClassFacts }
/** Rust 的 trait:方法只列签名,事实和类一样(名称、说明、可见性)。 */
export interface TraitBlock extends BlockShape { kind: 'trait', facts: ClassFacts }
/** Rust 的 impl:标题是「某类型 的方法」,方法是子块,放进方法表的某一行。 */
export interface ImplBlock extends BlockShape { kind: 'impl', facts: ClassFacts }
export interface ModuleBlock extends BlockShape { kind: 'module', facts: ClassFacts }
/** Rust 的内部属性 `#![…]`:写给所在 crate 或模块的编译属性。 */
export interface AttributeBlock extends BlockShape { kind: 'attribute', facts: AttributeFacts }
/** 枚举块的头部是整个枚举(成员都在属性表里讲),没有子块;库规则认出来的枚举(Python)是整个类,留下的方法等是子块。 */
export interface EnumBlock extends BlockShape { kind: 'enum', facts: EnumFacts }
/** 异常块的头部是整个类(类属性都在属性表里讲),留下的方法(含构造函数)等是子块。 */
export interface ExceptionBlock extends BlockShape { kind: 'exception', facts: ExceptionFacts }
export interface ImportBlock extends BlockShape { kind: 'import', facts: ImportFacts }
export interface VariableBlock extends BlockShape { kind: 'const' | 'variable', facts: VariableFacts }
/** 解释器块只有 `#!` 那一行。 */
export interface InterpreterBlock extends BlockShape { kind: 'interpreter', facts: InterpreterFacts }
/** 开关块是一整条 `set …`,每个开关一行。 */
export interface OptionsBlock extends BlockShape { kind: 'options', facts: OptionsFacts }
/** 解析指令块是文件开头连着的几行 `# 名字=值`,一条一行。 */
export interface DirectiveBlock extends BlockShape { kind: 'directive', facts: DirectiveFacts }
/** 阶段块的头部是 FROM 那一行(连同上面的说明),它后面的指令是子块。 */
export interface StageBlock extends BlockShape { kind: 'stage', facts: StageFacts }
export interface HealthBlock extends BlockShape { kind: 'health', facts: HealthFacts }
export interface StartBlock extends BlockShape { kind: 'start', facts: StartFacts }
export type FactBlock
= | FunctionBlock | ClassBlock | TraitBlock | ImplBlock | ModuleBlock | AttributeBlock | EnumBlock | ExceptionBlock | ImportBlock | VariableBlock | InterpreterBlock | OptionsBlock
| DirectiveBlock | StageBlock | HealthBlock | StartBlock
/** 带说明(facts.doc)的块。 */
export type DocumentedBlock = FunctionBlock | ClassBlock | TraitBlock | ImplBlock | ModuleBlock | EnumBlock | ExceptionBlock | VariableBlock
/** 属性表块的种类和 facts,不带块的形状(节点、头部、子块):库规则把一块认成现成的一种属性表块时给它(RuleFacts)。 */
export type BlockFacts = FactBlock extends infer B ? B extends FactBlock ? Pick<B, 'kind' | 'facts'> : never : never
export type TextKind = Exclude<BlockKind, FactBlock['kind']>
/** 预览成一行译文的块。 */
export interface TextBlock extends BlockShape {
kind: TextKind
/**
* 块自己的说明,同 VariableFacts.doc:Python 字段(类主体里带注解的赋值)后面紧跟的属性 docstring(字符串节点)。
* 算在块的头部里;预览在一行译文下面按 Markdown 显示(Translator.doc)。
*/
doc?: Node
/**
* 几条连着写的同种语句并成的一块(Dockerfile 连着的 COPY、LABEL):照源码顺序的各条,node 是第一条,头部盖住全部。
* 翻译插件排成一行字加一张表。
*/
group?: Node[]
/**
* 这块执行的是嵌着的另一种语言的代码(Dockerfile 的 RUN):宿主交给那种语言的语言插件分成几步(LanguageAdapter.steps),
* 只有一步时这一步的子块是这块的子块,几步时各步是这块的子块,都交给那种语言的翻译插件;这块自己的译文照旧归本语言的翻译插件
* (它从 SourceDocument.embedded 拿到读出来的样子)。
*/
embedded?: EmbeddedCode
}
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 …
| 'storage' // 声明关键字与字面量关键字:const function async class interface new true false null …
| 'operator' // 被翻成文字的运算符:全等 并且 或者
| 'hint' // 属性表里的说明词:未标注 无 N 个 默认导出 …
| 'constant' // 按习惯全大写、当常量用的名字(shell 的 PY_VERSION、HOME)
| 'variable' | 'function' | 'type' | 'string' | 'number' | 'literal' | 'comment' | 'plain'
/**
* 一段着色的文字。offset:这段照抄自源码里的名字(标识符、属性名、类型名、点分模块名的一节)或字符串时,
* 它在源码里的起始下标(同 SourceSpan 的下标),预览拿它向 VS Code 查定义、跳过去;译词、提示词没有。
* reference:只有库规则给的段有,这段讲的东西由规则自己查定义(LibraryRule.resolve),不问语言扩展;这样的段必须带 offset。
* color:这段自己的颜色(暗色、亮色主题各一个),盖过词性的颜色:库规则按库自己的配色上色(FastAPI 的 HTTP 方法用 Swagger UI 的颜色)。
* refers:这段讲的是源码里别处的一个名字(那个名字的范围),语言扩展不认得这段自己的位置:写在字符串里的名字
* (FastAPI 路径 `"/{user_id}"` 里的 user_id 讲的是函数的参数 user_id)。预览把这段当那个名字:⌘ / Ctrl + 点击跳到它的定义,
* 单击高亮它的各处出现,那个名字的某处出现盖住 refers 时这段也跟着高亮。offset 照旧是这段照抄的位置。
* tooltip:悬停这段时看的字(说明里写了、参数表里没有的参数名:「没有这个参数」)。
*/
export interface Segment { text: string, role: TokenRole, offset?: number, reference?: RuleReference, color?: ThemeColor, refers?: SourceRange, tooltip?: string }
/**
* 框的样式,决定框头的图标和框的颜色:
* - iteration:写在表达式里的遍历(渲染大纲里 `.map` 的「对 … 的每一项」也是),图标、颜色同「循环」块;
* - condition:按条件(渲染大纲里 `cond &&` 的「当 … 时」、三元的「如果 …」),图标、颜色同「条件」块;
* - branch:条件的一支(三元的「是」「否」),颜色同「条件」块,不带图标;
* - tag:小标签(名称后面的「具名导出」、属性名后面的「回调」),只有框头的字,颜色是 SegmentGroup.color;
* - classes:收起来的一串类名(「样式 N 个类」),中性色,点开、悬停看 SegmentGroup.detail。
*/
export type GroupStyle = 'iteration' | 'condition' | 'branch' | 'tag' | 'classes'
/**
* 框起来的一组词:译文里自成一体的一段(写在表达式里的遍历、渲染大纲一行前面的条件),预览把它画成嵌在前后文字里的一个小框,
* 框头是图标和 label。框里的词照样带源码下标,还能再套框。
*/
export interface SegmentGroup {
style: GroupStyle
/** 框头的文字(遍历的结果:生成器 / 列表 …,小标签的字),翻译插件、库规则给;只有图标、没有字时是空的。 */
label: string
segments: Inline[]
/** 这个框自己的颜色(暗色、亮色主题各一个),盖过样式的颜色:tag 按标签的意思上色。 */
color?: ThemeColor
/** 点开、悬停时看的全文,一行一条:classes 的全部类名(按条件切换的写成「条件 时:类」)。 */
detail?: string
}
/** 译文里的一段:着色的词,或框起来的一组词。 */
export type Inline = Segment | SegmentGroup
/**
* 一段 Markdown 说明文字:注释、JSDoc、docstring 去掉注释符号、引号和共同缩进后的原文,不做别的转换
* (reStructuredText 的 `:func:` 照抄)。预览按 Markdown 渲染成注释色的段落、列表、代码块,源码里的 HTML 当文字显示;
* KDoc 写法的不带路径的引用(`[名字]`、`[类.成员]`)由预览向宿主问它指的定义,问到了能点。
*/
export interface Markdown { markdown: string }
/** 说明里讲的一个参数(`@param 名字 说明`):名字照写(`*args`、`...rest` 带着前缀),说明是 Markdown。 */
export interface DocParam { name: string, text: Markdown }
/**
* 说明里讲的一种异常(`@throws 异常 什么时候抛`)。异常写成 Markdown:只写了名字的(`ValueError`、JSDoc 的 `{ValueError}`)
* 写成不带路径的引用 `[ValueError]`,`[Foo]`、`[说法](路径#Foo)` 照写;触发条件是 Markdown。
*/
export interface DocThrows { exception: Markdown, when: Markdown }
/**
* 一段说明(函数、类这些定义的说明,注释,当说明用的字符串):原文(同 Markdown)按 KDoc 拆成正文和块标签。
* 一行以 `@param`、`@return`(`@returns` 同义)、`@throws`(`@exception` 同义)开头算一条标签,后面接着的行(不是空行、不以 `@` 开头)
* 续在这条后面,空行结束这条;JSDoc 的 `@param {类型} 名字 - 说明`、`@param [名字] 说明` 也认(类型、横杠去掉)。
* 别的标签(`@see`、`@since`)、围栏代码块里的行留在正文里。
*/
export interface Doc {
/** 摘要和正文:拿掉这几种标签以后的原文;只写了标签时没有。 */
body?: Markdown
/** 照写的顺序。 */
params: DocParam[]
/** `@return` 的说明;写了几条时连成一段。 */
returns?: Markdown
/** 照写的顺序。 */
throws: DocThrows[]
}
/** 预览里显示的一段文字:着色的词(一行译文、属性值),或整段 Markdown 说明(注释块、函数和类的说明)。 */
export type PreviewText = Inline[] | Markdown
/**
* 属性表里的一行或一个子行讲的是哪个定义:定义出来的那个名字的节点(取自 facts,例如参数名、导入项的名称和别名、
* 声明的变量名、类的泛型参数、字典 / 对象字面量一项的键)在源码里的范围。跳到定义时,定义处落在这个范围里就只闪这一行;不填(第三方翻译插件可以不填)就闪整块。
*/
export interface Defines { defines?: SourceRange }
/**
* 属性表里展开的一个子行:几列对齐的格子,例如参数的名称 / 类型 / 默认值、字典一项的键 / 值、列表的第 N 项 / 值。
* 格子是着色的词,或一段 Markdown(参数的说明、异常的触发条件)。
*/
export interface PropertyItem extends Defines {
cells: PreviewText[]
/**
* 最后一格的值拆开的下一层子行(值是字典 / 对象、跨几行写的列表时,它的各项):排成列表的子行把它展开在自己下面,能折叠;
* 表格的行把它排在最后一格里、那格文字下面,总是展开。一层层可以一直往下套。
*/
items?: PropertyItem[]
/**
* 最后一格的值里那次调用的实参表(跨几行写的调用;列表里整项是调用的,写成一行也算):表格的行把它排在最后一格里、
* 那格文字下面,排成列表的子行把它排在自己下面。总是展开的;表里的行又可以带 items、table,一层层往下套。
*/
table?: PreviewTable
}
/**
* 属性表的一行:左列属性名,右列着色的值或 Markdown 说明;items 是展开在这一行下面的子行(输入参数、导入项、字典 / 对象、列表的各项)。
* columns 是子行各列的列名(输入参数的名称 / 类型 / 默认值):给了列名的子行排成带表头和边框的表格,没给的排成列表;
* 两种子行都能再带下一层(PropertyItem.items、table)。
*/
export interface PropertyRow extends Defines {
label: string
value: PreviewText
/** 跟在值后面的一段说明(Markdown,注释色):函数输出参数那一行,返回类型后面是说明里的 `@return`。 */
note?: Markdown
items?: PropertyItem[]
columns?: string[]
}
/** 一张带表头和边框的表:列名,每行几列对齐的格子,同 PropertyRow 给了 columns 的子行。 */
export interface PreviewTable {
columns: string[]
items: PropertyItem[]
}
/**
* 一行译文的块翻出来的:一行译文(注释块是 Markdown 说明);预览排在这一行下面的,跨几行写的字面量是拆开的各项(items,同 PropertyItem.items),
* 跨几行写的调用是摊开各个实参的表(table)。
*/
export interface TextTranslation {
text: PreviewText
items?: PropertyItem[]
table?: PreviewTable
}
/**
* 语言插件:语法树 → 代码块树,外加只有它懂、翻译插件翻表达式时要用的信息(叶子的词性、写在表达式里的遍历的结构)。
* 翻译插件翻一块时从 SourceDocument.language 拿到解析这份源码的语言插件,不直接依赖某个语言插件。
*/
export interface LanguageAdapter {
id: string
/** 覆盖的 VS Code languageId,例如 ['typescript', 'typescriptreact', 'javascript', 'javascriptreact']。 */
languageIds: string[]
/** 该 languageId 用的语法包 .wasm 的绝对路径(宿主负责加载,保证和解析器运行时是同一份)。 */
grammarPath: (languageId: string) => string
/** languageId 是这份源码的 languageId:一个插件覆盖几种方言时按它区分(JavaScript 没有 TypeScript 的抽象类、仅类型导入)。 */
toBlocks: (root: Node, source: string, languageId: string) => CodeBlock[]
/** 给语法树叶子节点定词性:源码着色用它,译文里照抄的词也按它着色、按它认出能查定义的名字。 */
tokenRole: (leaf: Node) => TokenRole
/**
* 行注释的记号(`#`、`//`):块还没有说明、也没有前面的普通注释,而这门语言的说明就是行注释时(shell、Dockerfile),
* 「写注释」用它在块上面新写一段。不填、又没有 freshDoc,就不能新写。
*/
lineComment?: string
/**
* 还没有说明、也没有块前面的普通注释时,「写注释」新写的说明插在哪。
* shell 是带定界符的空壳,正文用 `_` 占位。breakAt 是主体和声明写在同一行:从这一行的这一列拆开,说明插进主体里。
* 不填就用 lineComment 写在块的第一行上面。
*/
freshDoc?: (block: CodeBlock) => { line: number, shell: string, breakAt?: number } | undefined
/** node 是写在表达式里的遍历(推导式、生成器表达式)时给出它的结构,别的节点没有;没有这种写法的语言不填。 */
iteration?: (node: Node) => IterationFacts | undefined
/** node 是一次调用(含新建对象)时给出它的结构,别的节点没有;没给的语言不展开调用,照常一行译文。 */
call?: (node: Node) => CallFacts | undefined
/** node 是写出各项的字面量(字典、列表……)时给出它的各项,别的节点没有;没给的语言不拆字面量,照常一行译文。 */
collection?: (node: Node) => CollectionFacts | undefined
/** node 是执行一条命令(shell)时给出它的结构,交给命令规则读;别的节点没有,没有命令的语言不填。 */
command?: (node: Node) => CommandFacts | undefined
/** 一份源码里自己认得出的名字(见 LocalSymbol);VS Code 有这种语言的定义、高亮提供者时用不上,不填。 */
symbols?: (root: Node, source: string, languageId: string) => LocalSymbol[]
/**
* 一份源码里各个名字的绑定(见 NameBinding):宿主问语言扩展名字的定义时,同一个绑定只问一处,这份源码自己定义的不问。
* 只报认得准的:拿不准的(模式匹配捕获的、跳过不看的声明里的、同一个名字既导入又赋值的……)不报,宿主照旧逐处问。不填就逐处问。
*/
bindings?: (root: Node, source: string, languageId: string) => NameBinding[]
/**
* 这门语言自带、不用导入就能用的名字(Python 的 `ValueError`、`str`、`len`、`None`,TypeScript 的 `Error`、`Promise`、`JSON`),
* 注释里引用了它、预览却找不到定义时靠它认出来,当普通文字显示,不标成找不到;没有的语言不填。
*/
builtins?: ReadonlySet<string>
/** 这份源码里嵌着的别的语言的代码(见 EmbeddedCode),块里的(TextBlock.embedded)、属性表里的(HealthFacts.command)都算;没有的语言不填。 */
embedded?: (root: Node, source: string, languageId: string) => EmbeddedCode[]
/** 这门语言的代码被别的语言嵌着、当一串步骤执行时怎么分块(见 StepBlocks);不填就按 toBlocks 分、不算一步失败就停。 */
steps?: (root: Node, source: string, languageId: string) => StepBlocks
/** 认代码里给定义起的中文名(见「中文名」);不填就没有中文名。 */
displayNames?: DisplayNameReader
/**
* 这块除了自己的种类,还认得出的概念(异步、抽象)。认到才返回;不填就只有种类那一个。
* 种类那一个由宿主按内置种类的图标和翻译插件起的种类名补在最前面,这里不要再报种类。
*/
concepts?: (block: CodeBlock) => BlockConcept[]
}
// ── 中文名(见「中文名」)──
/** 代码里给一个定义起的中文名:预览里这个定义和用到它的各处都显示中文名,悬停看原名。 */
export interface DisplayName {
/** 定义处那个名字(函数名、类名、参数名、变量名、字段名、枚举成员名……) */
name: SourceRange
/** 中文名 */
text: string
}
/** 一份源码里认出的中文名。 */
export interface DisplayNameFacts {
/** 起了中文名的定义,照源码顺序;一个定义几处都写了时语言插件定取哪个(明写的记号优先于说明文字) */
names: DisplayName[]
/** 写在注释里给代码阅读看的记号(`# @Comment 当前用户` 里的 `@Comment 当前用户`):宿主交给翻译插件,翻注释、说明时去掉 */
markers: SourceRange[]
}
/** 语言插件认中文名:写法(记号、说明文字里的哪一句)归语言插件;找用到它的各处、显示归宿主和预览。 */
export interface DisplayNameReader {
/** languageId → 工作区里这种语言的文件(glob,Python 是所有 .py 文件):宿主建工作区索引,认出别的文件里定义的中文名 */
files: Record<string, string>
/** 便宜的文字预筛:可能写了中文名的源码才解析 */
mentions: (source: string) => boolean
find: (root: Node, source: string, languageId: string) => DisplayNameFacts
}
/** 翻译插件翻一块时拿到的这份源码:源码字符串,和解析它的语言插件。 */
export interface SourceDocument {
source: string
language: LanguageAdapter
/** 文件路径(VS Code 的 TextDocument.fileName);宿主没给(嵌着的代码、只读一段源码)时没有,按文件名定的说法写成判断不了的那种。 */
fileName?: string
/** 写在注释里给代码阅读看的记号(中文名记号,见 DisplayNameFacts.markers):不是说明文字,翻注释、说明时去掉 */
markers: SourceRange[]
/**
* 调用的名字(CallFacts.name)的定义是哪种块:宿主问语言扩展定义在哪,用那个文件的语言插件分块认出来。
* 排视图数据是同步的,这里只交出已经查到的;还没查到、查不到、宿主不查(没给)都是 undefined,查到了宿主重排、重发。
*/
calleeKind?: (call: CallFacts) => BlockKind | undefined
/** 这个命令名的命令规则(这种语言注册的,先注册的算);没有、或宿主不给时没有,命令照抄。规则出错由宿主记下、当它没读。 */
commandRule?: (name: string) => CommandRule | undefined
/**
* 嵌着的代码(EmbeddedCode.node 是 node 的那段)读出来的样子:宿主用那种语言的语言插件、翻译插件读好了交出来。
* 那种语言没有注册、宿主不给时没有,翻译插件照抄。
*/
embedded?: (node: Node) => EmbeddedReading | undefined
}
/** 筛选属性和值的显示名,筛选栏、块的种类(图标的悬停提示)用它;没给名字的显示 id。 */
export interface AttributeNames {
/** 属性 id → 属性名,例如 access → 访问级别。 */
attributes: Record<string, string>
/** 属性 id → 值 id → 值名,例如 access → public → 公共、async → true → 是、kind → function → 函数。 */
values: Record<string, Record<string, string>>
}
/** 翻译插件:代码块 → 一行译文或属性表。 */
export interface Translator {
id: string // 'typescript.zh-template'
label: string // '中文直译'
languageIds: string[]
/** 译文用的语言(BCP 47 的语言部分,例如 'zh'):库规则按它挑自己的词表。 */
displayLanguage: string
/**
* 只翻 header 那一段;注释块(文件开头的说明、挂在块上的注释 BlockShape.comments)给去掉注释符号的 Markdown 原文,
* 跨几行写的调用连着摊开实参的表。
*/
translate: (block: TextBlock, document: SourceDocument) => TextTranslation
/** 函数、类、枚举、导入、常量、变量块 → 属性表。 */
describe: (block: FactBlock, document: SourceDocument) => PropertyRow[]
/** 一个表达式或类型节点整个翻成一行译文,译法同属性表里的值:库规则翻默认值、类型时借它。 */
expression: (node: Node, document: SourceDocument) => Inline[]
/**
* 一个类型、签名节点照抄成一行原文,不翻关键字:叶子按源码着色(给了 `role` 就都按它),名字各自带源码下标,
* 跨几行写的压成一行、连着的空白收成一个空格。库规则照抄类型、签名时借它。
*/
verbatim: (node: Node, document: SourceDocument, role?: TokenRole) => Inline[]
/**
* 一段说明文字(注释、docstring、当说明用的字符串)读成说明(见 Doc):去掉注释符号、引号和共同缩进后的 Markdown 原文,
* 拆出块标签;正文同属性表的「说明」。几条行文档注释连成一段的(Rust 的 `///`),node 是第一条,翻译插件自己往下连。没有文字时没有。
*/
doc: (node: Node, document: SourceDocument) => Doc | undefined
/** 筛选属性和值叫什么,和属性表里的属性名、值用同一套叫法;不给就都显示 id。 */
attributeNames?: AttributeNames
}
/** 一种颜色在暗色、亮色主题下各用哪个(CSS 颜色值);高对比主题跟着暗 / 亮走。 */
export interface ThemeColor { dark: string, light: string }
/**
* 种类图标:插件自己定义的一组 lucide 图标(https://lucide.dev/icons 上的 kebab-case 名字)。前 26 个给内置种类(几种共用一个:
* 命令、管道、执行共用 square-terminal,解释器、解析指令共用 terminal,选项、设置、编译属性共用 sliders-horizontal,字段、标签共用 tag),
* 宿主标的入口块也用这组里的 play(entries/entryMarks.ts 的 ENTRY_CONCEPT),
* 后面的给库规则的种类(React 的组件、状态、引用、数据源、派生值、副作用、事件处理函数、渲染输出也在里面);库规则的种类(KindBadge.icon)只能从这组里挑,要新图标就加进这组(webview 的 kindIcons.tsx 同时给它配上图标),
* 没有按任意名字找图标的路。
*/
export const KIND_ICON_NAMES = [
'import',
'square-function',
'box',
'braces',
'list-ordered',
'triangle-alert',
'tag',
'lock',
'variable',
'split',
'corner-down-right',
'repeat',
'shield',
'shield-alert',
'shield-check',
'corner-down-left',
'code',
'message-square-text',
'terminal',
'sliders-horizontal',
'signpost',
'square-terminal',
'layers',
'copy',
'activity',
'play',
'database',
'package',
'server-cog',
'calendar-clock',
'globe',
'network',
'atom',
'zap',
'mouse-pointer-click',
'toggle-right',
'anchor',
'download',
'sigma',
'layout-template',
] as const
export type KindIconName = (typeof KIND_ICON_NAMES)[number]
/**
* 内置种类用哪个图标。宿主排预览时写进概念列表,webview 只按名字画。
* 同一类语义共用一个颜色(codeReader.css 的 --cr-kind-*:常量 / 变量 / 字段都是数据,尝试 / 捕获都是异常处理;
* 按当前颜色主题取色,见 theme/codeColors.ts,主题给几类配了同一个色就同色),图标区分具体是哪种。
*/
export const KIND_ICONS: Record<BlockKind, KindIconName> = {
import: 'import',
function: 'square-function',
class: 'box',
trait: 'network',
impl: 'square-function',
module: 'layers',
attribute: 'sliders-horizontal',
type: 'braces',
enum: 'list-ordered',
exception: 'triangle-alert',
field: 'tag',
const: 'lock',
variable: 'variable',
if: 'split',
else: 'corner-down-right',
loop: 'repeat',
try: 'shield',
catch: 'shield-alert',
finally: 'shield-check',
return: 'corner-down-left',
statement: 'code',
comment: 'message-square-text',
interpreter: 'terminal',
options: 'sliders-horizontal',
case: 'signpost',
command: 'square-terminal',
pipeline: 'square-terminal',
directive: 'terminal',
stage: 'layers',
run: 'square-terminal',
copy: 'copy',
setting: 'sliders-horizontal',
label: 'tag',
health: 'activity',
start: 'play',
}
/**
* 一块上认出的一个概念:稳定 id、悬停名、图标名(只能从 KIND_ICON_NAMES 里挑)、颜色。
* 语言的种类不填颜色,图标按 id 用 CSS 里这种种类的颜色;规则声明的概念填颜色,只给这个图标上色。
*/
export interface BlockConcept {
/** 稳定 id。语言的种类就是块的种类(class);规则的种类是 RuleCard.kind;规则另认的由规则起;宿主标的入口是 entry。 */
id: string
/** 悬停提示和读屏念的名字。 */
label: string
icon: KindIconName
/** 不填是语言的种类(颜色在 CSS)。规则的概念填规则给的颜色,不改卡片边框。 */
color?: ThemeColor
}
/** 库规则的种类:筛选栏里叫 label,卡片边框用 color,icon 进概念列表。卡片左边不只这一个图标(见 BlockConcept)。 */
export interface KindBadge { label: string, color: ThemeColor, icon: KindIconName }
/**
* 库规则认领一块后给出的替身,预览里它代替原来那块,下面挂着原块留下的子块:
* 规则自己的卡片(RuleCard),或认成契约里现成的一种属性表块(RuleFacts);
* 这块讲在了别的卡片里时是略去它(RuleOmit)。
*/
export type RuleBlock = RuleCard | RuleFacts | RuleOmit
/**
* 这块讲在了别的卡片里,不再单独成块:React 文件开头的 "use client" 讲成了组件卡片上的「客户端组件」标签。
* 预览不画它;它不能有子块,也不能挂着注释(BlockShape.comments,注释没地方放),有时宿主当规则没认。
*/
export interface RuleOmit { omit: true }
/**
* 规则自己的卡片:一张属性表。种类(badge)决定筛选和边框色;卡片左边的图标是语言的概念再加这个种类、以及 concepts。用词归规则。
* 原块的「注释」一行(BlockShape.comments)由宿主插进 rows,规则不用管。
*/
export interface RuleCard {
/** 种类 id,写成「规则 id.名字」(sqlalchemy.table):筛选的 kind 属性值就是它,筛选记住的也是它,要稳定。 */
kind: string
/** 种类的名字、颜色和图标:筛选栏里这个种类也叫这个名字,卡片边框用这个颜色。图标进概念列表,不换掉语言的概念。 */
badge: KindBadge
/**
* 认领时额外认出的概念,排在这个种类(badge)后面。语言认的概念仍在更前面;同一个 id 只留第一个。
* 不改筛选的种类,也不改卡片边框色(仍是 badge)。
*/
concepts?: BlockConcept[]
rows: PropertyRow[]
/** 块自己的名字(类名)在源码里的范围:跳到的定义就是它时闪整块。 */
defines?: SourceRange
/**
* 规则自己的筛选属性,叠在宿主从原块推出的属性上(同 BlockShape.attributes);kind 总是上面的 kind,这里填了不算。
* 属性和值的名字由规则给(LibraryRule.attributeNames)。
*/
attributes?: BlockAttributes
/**
* 并进这一块的子块(原块 children 里的对象本身):不再单独成块,内容由 rows 讲(讲子块的注释在子块的 comments 里,跟着并进来)。
* 没并进来的子块(方法、别的成员)不管写在哪,都照源码顺序照常是这一块的子块,也照常交给各规则看。
* 这一块的编辑范围同别的块,是原块整条语句(类就是整个类),盖住留下的子块:它编辑时预览不画这些子块,同一行不会同时在两处能改。
*/
absorbs: CodeBlock[]
/** 挂在卡片某一行的子行上、点那个子行才展开的子块(见 RuleSlot);一块只能放一处。 */
slots?: RuleSlot[]
/**
* 这张卡片讲的是一个 HTTP 接口(要有 defines):卡片能看调用树,宿主拿它到工作区的接口契约文件(OpenAPI)里对出完整网址,
* 把前端发这个请求的地方并进调用者。
*/
endpoint?: HttpEndpoint
}
/** 一个 HTTP 接口在这份文件里的写法。 */
export interface EndpointRoute {
/** 大写的 HTTP 方法 */
method: string
/** 这份文件里写出来的路径,`{参数}` 照写;挂到应用上时别处再叠的前缀不算,宿主按结尾去对完整网址 */
path: string
}
/** 卡片讲的 HTTP 接口。 */
export interface HttpEndpoint extends EndpointRoute {
/** 契约里结尾对上几条、挂载前缀也定不下来时,operationId 以它开头的那条才是(FastAPI 默认的 operationId 以「函数名_」开头) */
operationIdPrefix?: string
/** 同一个路由对象上的全部接口(含这一个):它们挂在同一个前缀下,契约里结尾对上几条时宿主拿它们一起定挂载前缀 */
sameRouter?: EndpointRoute[]
}
/**
* 卡片里的一处子块:block(原块 children 里的对象,没并进来的)不排在卡片后面,挂在卡片第 row 行的第 item 个子行上(都从 0 起),
* 照常转成块(翻译、库规则、编辑、问 AI、差异都照常)。它默认收着,点那个子行的第一格才在这一行下面展开当子卡片,
* 同一行一次只展开一个(Rust 实现块的方法表点方法名展开那张函数卡)。
*/
export interface RuleSlot {
block: CodeBlock
row: number
item: number
}
/**
* 把这块认成契约里现成的一种属性表块(Python 自带 enum 库的类 → 枚举块):宿主当语言插件报的这种块处理,
* 种类、种类名、颜色、属性表、筛选属性都和语言自带的这种块一样,由翻译插件排;规则只给 facts(库的知识),不给用词。
* 块的头部是原块整条语句(facts 里的节点都在里面),absorbs 同 RuleCard.absorbs。
*/
export interface RuleFacts {
as: BlockFacts
absorbs: CodeBlock[]
}
/** 库规则看一块时宿主给的:这份源码、它的导入,借当前翻译插件翻表达式、取说明的办法,和查名字的定义在工作区索引里是哪一项的办法。 */
export interface RuleContext {
document: SourceDocument
/** 这份源码里各处导入的 facts,照源码顺序:规则拿它认出库里名字的别名(`from sqlalchemy import Enum as SQLEnum`)。 */
imports: ImportFacts[]
/** 当前翻译插件的 displayLanguage;没有翻译插件时没有。规则按它挑自己的词表,没有这种语言的词表就用自己默认的那份。 */
displayLanguage?: string
/** 用当前翻译插件把一个表达式或类型节点翻成一行译文(Translator.expression):名字带源码下标,能跳到定义、高亮各处出现;没有翻译插件时照抄源码。 */
translate: (node: Node) => Inline[]
/** 用当前翻译插件把一个类型、签名节点照抄成一行原文([Translator.verbatim]):名字各自带源码下标,能跳到定义、高亮各处出现;没有翻译插件时照抄源码。 */
verbatim: (node: Node, role?: TokenRole) => Inline[]
/** 用当前翻译插件读一段说明文字(Translator.doc):正文和块标签;没有翻译插件、没有文字时没有。 */
doc: (node: Node) => Doc | undefined
/**
* 源码里 name 这个名字(例如 raise 后面的异常类名)指的定义在 index 里认出的那一项,规则从定义处读出的值在 IndexEntry.value 里:
* 定义由语言扩展查(同 ⌘ + 点击),落在 index 认出的某一项的名字上才算;同一份源码里同名的名字按一处查。
* 认领是同步的,查要等工作区索引建好、语言扩展答话:宿主先当没查到(undefined)交给规则排,发出预览后再查,
* 查到的和上次不一样就按新结果重排一遍、重发;文档改了先用上次查到的,再重查。没有工作区时(单元测试)总是 undefined。
*/
lookup: (index: WorkspaceIndex, name: Node) => IndexEntry | undefined
}
/** 规则自己查得到定义的一个名字:哪个规则(LibraryRule.id),和规则自己认的键(Celery 的任务名)。 */
export interface RuleReference { rule: string, key: string }
/** 文件里的一个位置:行、列从 0 起,列按 UTF-16 计(同 VS Code)。 */
export interface RulePosition { line: number, character: number }
/** 规则查到的一处定义:文件(uri 字符串)和定义处那个名字的起止位置。 */
export interface RuleLocation { uri: string, start: RulePosition, end: RulePosition }
/** 建索引时交给规则的一份文件。 */
export interface IndexedFile {
/** 相对它所在的工作区文件夹的路径,用 / 分隔。 */
path: string
source: string
/** 同一个工作区文件夹里有没有这个文件(相对路径);只认按 WorkspaceIndex.include 找到的文件。算 Python 包路径时问 `__init__.py` 用。 */
exists: (path: string) => boolean
}
/**
* 规则从一份文件里认出的一项:键,定义处那个名字在源码里的范围,和规则从定义处读出、认领别的文件时要用的值
* (FastAPI 规则:异常类的 status_code;见 RuleContext.lookup)。
*/
export interface IndexEntry { key: string, name: SourceRange, value?: string }
/**
* 规则在整个工作区里建的索引(键 → 定义处):规则只说看哪些文件、从一份源码里认出什么;
* 找文件、预筛、解析、缓存、跟着文件改动更新都归宿主。第一次查时才建,之后只重看改了的文件。
*/
export interface WorkspaceIndex {
/** 同一个 id 宿主只建一份,写成「规则 id.名字」(celery.tasks)。 */
id: string
/** 按哪种语言解析:宿主用这个 languageId 的语言插件。 */
languageId: string
/**
* 看哪些文件:相对各工作区文件夹的 glob('**\/*.py');files.exclude、search.exclude 排除的,.venv、node_modules、__pycache__ 里的,
* 嵌套的 checkout(子仓库、git worktree)里的,和忽略文件(.gitignore、.ignore,按 search.useIgnoreFiles)忽略的不看。
*/
include: string
/** 便宜的文字预筛:源码里不可能有要找的东西就不解析。 */
mentions: (source: string) => boolean
/** 从一份文件的语法树里认出的各项;语法树只在调用期间有效。 */
entries: (root: Node, file: IndexedFile) => IndexEntry[]
}
/** 宿主给规则查定义用的工作区。 */
export interface RuleWorkspace {
/** 在 index 建的索引里查 key 的各处定义,照文件、行排好。 */
lookup: (index: WorkspaceIndex, key: string) => Promise<RuleLocation[]>
}
/**
* 库规则:认出某个库的一种写法(SQLAlchemy 的模型类),把那块整个换成一张专门的卡片,或认成现成的一种属性表块
* (enum 库的类 → 枚举块);文件导入了它关心的库才启用。
* 规则只经契约拿信息:块(kind、node、header、children、facts)和 RuleContext;卡片的用词(属性名、种类名、类型的译名)属于规则自己。
*/
export interface LibraryRule {
id: string // 'sqlalchemy'
label: string // 'SQLAlchemy 数据表'
languageIds: string[]
/**
* 关心的库(模块名):这份源码里有一条导入(ImportFacts.sources,去掉引号)等于它、或以「它.」「它/」「它::」开头(它的子模块;Rust 的路径用 ::)才启用。
* 宿主按语言插件给的导入判断,规则不用自己读导入语句。空数组是不看导入、languageIds 里这几种语言的源码都启用
* (React 组件:新的 JSX 写法不用导入 react,TSX / JSX 文件里就是 React 组件)。
*/
libraries: string[]
/**
* 看一块:认领就返回替身,不认就返回 undefined。宿主从顶层往下每块都问(被认领的块留下的子块照样问)。
* 几个规则都认同一块时先注册的算;规则抛错、或替身不合上面的约定时,宿主记日志、当它没认,接着问下一个规则。
*/
claim: (block: CodeBlock, context: RuleContext) => RuleBlock | undefined
/**
* 查规则自己标的引用(Segment.reference 里 rule 是这个规则的)的定义:预览里 ⌘ / Ctrl 悬停、点击这样的名字时宿主问它,不问语言扩展;
* 查到几处就跳、挑,和语言扩展查到的一样。没给就不能在段上标引用。
*/
resolve?: (key: string, workspace: RuleWorkspace) => Promise<RuleLocation[]>
/**
* 规则自己报的筛选属性(RuleCard.attributes,例如 FastAPI 接口的 httpMethod)和值叫什么,同 Translator.attributeNames:
* 用词归规则,按翻译插件的 displayLanguage 挑(没有翻译插件时是 undefined),没有这种语言的词表就用规则默认的那份。
* 筛选栏的名字先取翻译插件给的,没给的再取规则给的;都没给的显示 id。
*/
attributeNames?: (displayLanguage: string | undefined) => AttributeNames
}
export interface CodeReaderApi {
registerLanguage: (adapter: LanguageAdapter) => Disposable
registerTranslator: (translator: Translator) => Disposable
registerLibraryRule: (rule: LibraryRule) => Disposable
registerCommandRule: (rule: CommandRule) => Disposable
}