✨ feat(vscode): 代码阅读预览 Rust 的语言卡片
This commit is contained in:
1 parent
4480f936f3
commit
6d58b681cf
15 files changed
+1016
-6
No files matched your search
@@ -32,6 +32,8 @@ const TREE_SITTER_WASM = [
|
|||||||
'tree-sitter-javascript.wasm',
|
'tree-sitter-javascript.wasm',
|
||||||
'tree-sitter-python.wasm',
|
'tree-sitter-python.wasm',
|
||||||
'tree-sitter-bash.wasm',
|
'tree-sitter-bash.wasm',
|
||||||
|
// 包入口就在 wasm/(main 指向 wasm/tree-sitter.js),rust 的语法包和 python 的在同一层
|
||||||
|
'tree-sitter-rust.wasm',
|
||||||
]
|
]
|
||||||
|
|
||||||
// @vscode/tree-sitter-wasm 没有的语法包,从 tree-sitter-wasm(预编译、签过名的各家语法包)拿:语言名 → 它的 wasm
|
// @vscode/tree-sitter-wasm 没有的语法包,从 tree-sitter-wasm(预编译、签过名的各家语法包)拿:语言名 → 它的 wasm
|
||||||
|
|||||||
+2
-2
@@ -379,7 +379,7 @@
|
|||||||
"editor/title": [
|
"editor/title": [
|
||||||
{
|
{
|
||||||
"command": "superpowers.codeReader.showPreview",
|
"command": "superpowers.codeReader.showPreview",
|
||||||
"when": "activeEditor == workbench.editors.files.textFileEditor && (resourceLangId =~ /^(typescript|typescriptreact|javascript|javascriptreact|python|shellscript|dockerfile)$/ || resourceLangId in superpowers.codeReader.languageIds)",
|
"when": "activeEditor == workbench.editors.files.textFileEditor && (resourceLangId =~ /^(typescript|typescriptreact|javascript|javascriptreact|python|shellscript|dockerfile|rust)$/ || resourceLangId in superpowers.codeReader.languageIds)",
|
||||||
"group": "navigation"
|
"group": "navigation"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -411,7 +411,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"command": "superpowers.codeReader.showPreview",
|
"command": "superpowers.codeReader.showPreview",
|
||||||
"when": "activeEditor == workbench.editors.files.textFileEditor && (resourceLangId =~ /^(typescript|typescriptreact|javascript|javascriptreact|python|shellscript|dockerfile)$/ || resourceLangId in superpowers.codeReader.languageIds)"
|
"when": "activeEditor == workbench.editors.files.textFileEditor && (resourceLangId =~ /^(typescript|typescriptreact|javascript|javascriptreact|python|shellscript|dockerfile|rust)$/ || resourceLangId in superpowers.codeReader.languageIds)"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"command": "superpowers.codeReader.showSource",
|
"command": "superpowers.codeReader.showSource",
|
||||||
|
|||||||
@@ -26,6 +26,9 @@ function factAttributes(block: CodeBlock): Partial<Record<CommonAttribute, strin
|
|||||||
return { access: block.facts.access, async: flag(block.facts.async) }
|
return { access: block.facts.access, async: flag(block.facts.async) }
|
||||||
case 'class':
|
case 'class':
|
||||||
return { access: block.facts.access, abstract: flag(block.facts.abstract) }
|
return { access: block.facts.access, abstract: flag(block.facts.abstract) }
|
||||||
|
case 'trait':
|
||||||
|
case 'impl':
|
||||||
|
return { access: block.facts.access }
|
||||||
case 'enum':
|
case 'enum':
|
||||||
case 'exception':
|
case 'exception':
|
||||||
case 'const':
|
case 'const':
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ export type { Node }
|
|||||||
*/
|
*/
|
||||||
export type BlockKind
|
export type BlockKind
|
||||||
= | 'import' | 'function' | 'class' | 'enum' | 'exception' | 'type' | 'field' | 'const' | 'variable'
|
= | 'import' | 'function' | 'class' | 'enum' | 'exception' | 'type' | 'field' | 'const' | 'variable'
|
||||||
|
| 'trait' | 'impl'
|
||||||
| 'if' | 'else' | 'loop' | 'try' | 'catch' | 'finally' | 'return' | 'statement' | 'comment'
|
| 'if' | 'else' | 'loop' | 'try' | 'catch' | 'finally' | 'return' | 'statement' | 'comment'
|
||||||
| 'interpreter' | 'options' | 'case' | 'command' | 'pipeline'
|
| 'interpreter' | 'options' | 'case' | 'command' | 'pipeline'
|
||||||
| 'directive' | 'stage' | 'run' | 'copy' | 'setting' | 'label' | 'health' | 'start'
|
| 'directive' | 'stage' | 'run' | 'copy' | 'setting' | 'label' | 'health' | 'start'
|
||||||
@@ -474,6 +475,10 @@ export interface BlockShape {
|
|||||||
/** 预览成属性表的块:语言插件从语法树读出结构化信息(facts),翻译插件把它排成属性表。 */
|
/** 预览成属性表的块:语言插件从语法树读出结构化信息(facts),翻译插件把它排成属性表。 */
|
||||||
export interface FunctionBlock extends BlockShape { kind: 'function', facts: FunctionFacts }
|
export interface FunctionBlock extends BlockShape { kind: 'function', facts: FunctionFacts }
|
||||||
export interface ClassBlock extends BlockShape { kind: 'class', facts: ClassFacts }
|
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 }
|
||||||
/** 枚举块的头部是整个枚举(成员都在属性表里讲),没有子块;库规则认出来的枚举(Python)是整个类,留下的方法等是子块。 */
|
/** 枚举块的头部是整个枚举(成员都在属性表里讲),没有子块;库规则认出来的枚举(Python)是整个类,留下的方法等是子块。 */
|
||||||
export interface EnumBlock extends BlockShape { kind: 'enum', facts: EnumFacts }
|
export interface EnumBlock extends BlockShape { kind: 'enum', facts: EnumFacts }
|
||||||
/** 异常块的头部是整个类(类属性都在属性表里讲),留下的方法(含构造函数)等是子块。 */
|
/** 异常块的头部是整个类(类属性都在属性表里讲),留下的方法(含构造函数)等是子块。 */
|
||||||
@@ -491,11 +496,11 @@ export interface StageBlock extends BlockShape { kind: 'stage', facts: StageFact
|
|||||||
export interface HealthBlock extends BlockShape { kind: 'health', facts: HealthFacts }
|
export interface HealthBlock extends BlockShape { kind: 'health', facts: HealthFacts }
|
||||||
export interface StartBlock extends BlockShape { kind: 'start', facts: StartFacts }
|
export interface StartBlock extends BlockShape { kind: 'start', facts: StartFacts }
|
||||||
export type FactBlock
|
export type FactBlock
|
||||||
= | FunctionBlock | ClassBlock | EnumBlock | ExceptionBlock | ImportBlock | VariableBlock | InterpreterBlock | OptionsBlock
|
= | FunctionBlock | ClassBlock | TraitBlock | ImplBlock | EnumBlock | ExceptionBlock | ImportBlock | VariableBlock | InterpreterBlock | OptionsBlock
|
||||||
| DirectiveBlock | StageBlock | HealthBlock | StartBlock
|
| DirectiveBlock | StageBlock | HealthBlock | StartBlock
|
||||||
|
|
||||||
/** 带说明(facts.doc)的块。 */
|
/** 带说明(facts.doc)的块。 */
|
||||||
export type DocumentedBlock = FunctionBlock | ClassBlock | EnumBlock | ExceptionBlock | VariableBlock
|
export type DocumentedBlock = FunctionBlock | ClassBlock | TraitBlock | ImplBlock | EnumBlock | ExceptionBlock | VariableBlock
|
||||||
|
|
||||||
/** 属性表块的种类和 facts,不带块的形状(节点、头部、子块):库规则把一块认成现成的一种属性表块时给它(RuleFacts)。 */
|
/** 属性表块的种类和 facts,不带块的形状(节点、头部、子块):库规则把一块认成现成的一种属性表块时给它(RuleFacts)。 */
|
||||||
export type BlockFacts = FactBlock extends infer B ? B extends FactBlock ? Pick<B, 'kind' | 'facts'> : never : never
|
export type BlockFacts = FactBlock extends infer B ? B extends FactBlock ? Pick<B, 'kind' | 'facts'> : never : never
|
||||||
@@ -790,6 +795,8 @@ export const KIND_ICONS: Record<BlockKind, KindIconName> = {
|
|||||||
import: 'import',
|
import: 'import',
|
||||||
function: 'square-function',
|
function: 'square-function',
|
||||||
class: 'box',
|
class: 'box',
|
||||||
|
trait: 'network',
|
||||||
|
impl: 'square-function',
|
||||||
type: 'braces',
|
type: 'braces',
|
||||||
enum: 'list-ordered',
|
enum: 'list-ordered',
|
||||||
exception: 'triangle-alert',
|
exception: 'triangle-alert',
|
||||||
|
|||||||
@@ -0,0 +1,45 @@
|
|||||||
|
use serde::Serialize;
|
||||||
|
use std::sync::Arc;
|
||||||
|
use foo::{Bar as Baz, Qux};
|
||||||
|
|
||||||
|
/// 桌面端列表里的一行代理。
|
||||||
|
/// 后面还有一句。
|
||||||
|
pub struct AgentRow {
|
||||||
|
pub id: Uuid,
|
||||||
|
name: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) struct CrateOnly {
|
||||||
|
pub(super) up: u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 这个代理现在处在哪一步。
|
||||||
|
pub enum AgentState {
|
||||||
|
Idle,
|
||||||
|
Running { pid: u32 },
|
||||||
|
Failed(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AgentRow {
|
||||||
|
fn rename(&mut self, name: String) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 在本机拉起接口服务。
|
||||||
|
pub async fn start_server(addr: SocketAddr) -> Result<(), StartError> {
|
||||||
|
match result {
|
||||||
|
Ok(listener) => {
|
||||||
|
// 记下监听地址
|
||||||
|
self.listener = Some(listener);
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
// 返回 StartError
|
||||||
|
return Err(StartError);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 按 id 读写代理。
|
||||||
|
pub trait Store {
|
||||||
|
fn get(&self, id: Uuid) -> Result<AgentRow, StoreError>;
|
||||||
|
fn save(&mut self, row: AgentRow) -> Result<(), StoreError>;
|
||||||
|
}
|
||||||
@@ -19,6 +19,7 @@ import { TreeFilters } from './explorer/treeFilters'
|
|||||||
import { createBashAdapter } from './languages/bash'
|
import { createBashAdapter } from './languages/bash'
|
||||||
import { createDockerfileAdapter } from './languages/dockerfile'
|
import { createDockerfileAdapter } from './languages/dockerfile'
|
||||||
import { createPythonAdapter } from './languages/python'
|
import { createPythonAdapter } from './languages/python'
|
||||||
|
import { createRustAdapter } from './languages/rust'
|
||||||
import { createTypeScriptAdapter } from './languages/typescript'
|
import { createTypeScriptAdapter } from './languages/typescript'
|
||||||
import { resolveReference } from './libraryRules'
|
import { resolveReference } from './libraryRules'
|
||||||
import { indexedNames } from './nameResolution'
|
import { indexedNames } from './nameResolution'
|
||||||
@@ -33,11 +34,13 @@ import { enumRule } from './rules/enum'
|
|||||||
import { fastapiRule } from './rules/fastapi'
|
import { fastapiRule } from './rules/fastapi'
|
||||||
import { pydanticRule } from './rules/pydantic'
|
import { pydanticRule } from './rules/pydantic'
|
||||||
import { reactRule } from './rules/react'
|
import { reactRule } from './rules/react'
|
||||||
|
import { rustImplRule } from './rules/rustImpl'
|
||||||
import { sqlalchemyRule } from './rules/sqlalchemy'
|
import { sqlalchemyRule } from './rules/sqlalchemy'
|
||||||
import { bundledWasmDir, TreeSitterRuntime } from './runtime'
|
import { bundledWasmDir, TreeSitterRuntime } from './runtime'
|
||||||
import { bashZhTranslator } from './translators/bashZh'
|
import { bashZhTranslator } from './translators/bashZh'
|
||||||
import { dockerfileZhTranslator } from './translators/dockerfileZh'
|
import { dockerfileZhTranslator } from './translators/dockerfileZh'
|
||||||
import { pythonZhTranslator } from './translators/pythonZh'
|
import { pythonZhTranslator } from './translators/pythonZh'
|
||||||
|
import { rustZhTranslator } from './translators/rustZh'
|
||||||
import { typescriptZhTranslator } from './translators/typescriptZh'
|
import { typescriptZhTranslator } from './translators/typescriptZh'
|
||||||
import { UncommittedMode } from './uncommitted/uncommittedMode'
|
import { UncommittedMode } from './uncommitted/uncommittedMode'
|
||||||
import { workspaceIndexHost } from './workspaceFiles'
|
import { workspaceIndexHost } from './workspaceFiles'
|
||||||
@@ -147,10 +150,12 @@ export function registerCodeReader(context: ExtensionContext): CodeReaderApi {
|
|||||||
api.registerLanguage(createPythonAdapter(wasmDir)),
|
api.registerLanguage(createPythonAdapter(wasmDir)),
|
||||||
api.registerLanguage(createBashAdapter(wasmDir)),
|
api.registerLanguage(createBashAdapter(wasmDir)),
|
||||||
api.registerLanguage(createDockerfileAdapter(wasmDir)),
|
api.registerLanguage(createDockerfileAdapter(wasmDir)),
|
||||||
|
api.registerLanguage(createRustAdapter(wasmDir)),
|
||||||
api.registerTranslator(typescriptZhTranslator),
|
api.registerTranslator(typescriptZhTranslator),
|
||||||
api.registerTranslator(pythonZhTranslator),
|
api.registerTranslator(pythonZhTranslator),
|
||||||
api.registerTranslator(bashZhTranslator),
|
api.registerTranslator(bashZhTranslator),
|
||||||
api.registerTranslator(dockerfileZhTranslator),
|
api.registerTranslator(dockerfileZhTranslator),
|
||||||
|
api.registerTranslator(rustZhTranslator),
|
||||||
...[...shellCommandRules, ...systemCommandRules, ...packageCommandRules].map(rule => api.registerCommandRule(rule)),
|
...[...shellCommandRules, ...systemCommandRules, ...packageCommandRules].map(rule => api.registerCommandRule(rule)),
|
||||||
api.registerLibraryRule(sqlalchemyRule),
|
api.registerLibraryRule(sqlalchemyRule),
|
||||||
api.registerLibraryRule(celeryRule),
|
api.registerLibraryRule(celeryRule),
|
||||||
@@ -159,6 +164,7 @@ export function registerCodeReader(context: ExtensionContext): CodeReaderApi {
|
|||||||
api.registerLibraryRule(pydanticRule),
|
api.registerLibraryRule(pydanticRule),
|
||||||
api.registerLibraryRule(fastapiRule),
|
api.registerLibraryRule(fastapiRule),
|
||||||
api.registerLibraryRule(reactRule),
|
api.registerLibraryRule(reactRule),
|
||||||
|
api.registerLibraryRule(rustImplRule),
|
||||||
)
|
)
|
||||||
return api
|
return api
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,112 @@
|
|||||||
|
import type { BlockView } from '../blockView'
|
||||||
|
import { describe, expect, it } from 'vitest'
|
||||||
|
import { toBlockViews } from '../blockView'
|
||||||
|
import { blockMarks } from '../diff/viewDiff'
|
||||||
|
import { rustImplRule } from '../rules/rustImpl'
|
||||||
|
import { readFixture, viewLines, WASM_DIR, withBlocks } from '../testSupport'
|
||||||
|
import { rustZhTranslator } from '../translators/rustZh'
|
||||||
|
import { createRustAdapter } from './rust'
|
||||||
|
|
||||||
|
const adapter = createRustAdapter(WASM_DIR)
|
||||||
|
const sample = readFixture('rustLang.rs')
|
||||||
|
|
||||||
|
function views(source: string): Promise<BlockView[]> {
|
||||||
|
return withBlocks(adapter, 'rust', source, blocks =>
|
||||||
|
toBlockViews(blocks, { source, language: adapter, markers: [] }, rustZhTranslator, {
|
||||||
|
rules: [rustImplRule],
|
||||||
|
report: () => {},
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
function card(list: BlockView[], kind: string): BlockView {
|
||||||
|
const found = list.find(block => block.kind === kind)
|
||||||
|
if (!found)
|
||||||
|
throw new Error(`没有 ${kind}`)
|
||||||
|
return found
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('rust 语言卡片', () => {
|
||||||
|
it('use、结构体、枚举、实现、函数、特质', async () => {
|
||||||
|
expect(viewLines(await views(sample))).toEqual([
|
||||||
|
'import 1',
|
||||||
|
' 来源: serde',
|
||||||
|
' 导入项: 1 个',
|
||||||
|
' Serialize | —',
|
||||||
|
'import 2',
|
||||||
|
' 来源: std::sync',
|
||||||
|
' 导入项: 1 个',
|
||||||
|
' Arc | —',
|
||||||
|
'import 3',
|
||||||
|
' 来源: foo',
|
||||||
|
' 导入项: 2 个',
|
||||||
|
' Bar | Baz',
|
||||||
|
' Qux | —',
|
||||||
|
'class 5',
|
||||||
|
' 名称: AgentRow',
|
||||||
|
' 说明: 桌面端列表里的一行代理。',
|
||||||
|
' 可见性: 公开',
|
||||||
|
' 字段: 2 个',
|
||||||
|
' id | Uuid | 公开',
|
||||||
|
' name | String | 本模块',
|
||||||
|
'class 12',
|
||||||
|
' 名称: CrateOnly',
|
||||||
|
' 可见性: 本 crate',
|
||||||
|
' 字段: 1 个',
|
||||||
|
' up | u8 | 父模块',
|
||||||
|
'enum 16',
|
||||||
|
' 名称: AgentState',
|
||||||
|
' 说明: 这个代理现在处在哪一步。',
|
||||||
|
' 变体: 3 个',
|
||||||
|
' Idle | —',
|
||||||
|
' Running | { pid: u32 }',
|
||||||
|
' Failed | String',
|
||||||
|
'rust.impl 23',
|
||||||
|
' 名称: AgentRow 的方法',
|
||||||
|
' 方法: 1 个',
|
||||||
|
' rename | &mut self, name: String | ()',
|
||||||
|
' function 24',
|
||||||
|
' 名称: rename',
|
||||||
|
' 可见性: 本模块',
|
||||||
|
' 参数: 2 个',
|
||||||
|
' self | &mut self',
|
||||||
|
' name | String',
|
||||||
|
' 返回: ()',
|
||||||
|
'function 27',
|
||||||
|
' 名称: start_server',
|
||||||
|
' 说明: 在本机拉起接口服务。',
|
||||||
|
' 异步: 是',
|
||||||
|
' 可见性: 公开',
|
||||||
|
' 输入参数: 1 个',
|
||||||
|
' addr | SocketAddr',
|
||||||
|
' 返回: Result<(), StartError>',
|
||||||
|
' 分情况: 2 支',
|
||||||
|
' Ok(listener) | 记下监听地址',
|
||||||
|
' Err(err) | 返回 StartError',
|
||||||
|
'trait 41',
|
||||||
|
' 名称: Store',
|
||||||
|
' 说明: 按 id 读写代理。',
|
||||||
|
' 方法: 2 个',
|
||||||
|
' get | &self, id: Uuid | Result<AgentRow, StoreError>',
|
||||||
|
' save | &mut self, row: AgentRow | Result<(), StoreError>',
|
||||||
|
])
|
||||||
|
})
|
||||||
|
|
||||||
|
it('实现的方法放进方法表那一行', async () => {
|
||||||
|
const impl = card(await views(sample), 'rust.impl')
|
||||||
|
const [method] = impl.children
|
||||||
|
expect(method?.slot).toEqual({ row: 1, item: 0 })
|
||||||
|
})
|
||||||
|
|
||||||
|
it('给结构体加一个字段,多出来的那一行标成新增', async () => {
|
||||||
|
const before = 'pub struct AgentRow {\n name: String,\n}\n'
|
||||||
|
const after = 'pub struct AgentRow {\n name: String,\n id: Uuid,\n}\n'
|
||||||
|
const [oldCard] = await views(before)
|
||||||
|
const [newCard] = await views(after)
|
||||||
|
const marks = oldCard && newCard ? blockMarks(oldCard, newCard) : undefined
|
||||||
|
const field = newCard && 'properties' in newCard ? newCard.properties.findIndex(row => row.label === '字段') : -1
|
||||||
|
expect(field).toBeGreaterThanOrEqual(0)
|
||||||
|
expect(marks?.rows?.changed[field]).toEqual({
|
||||||
|
items: { changed: { 1: { added: true } }, removed: [] },
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -0,0 +1,503 @@
|
|||||||
|
import type { Access, BlockAttributes, ClassBlock, ClassFacts, CodeBlock, EnumBlock, FunctionBlock, ImplBlock, ImportBlock, ImportItem, LanguageAdapter, Node, ParameterFacts, TokenRole, TraitBlock } from '../contract'
|
||||||
|
import * as path from 'node:path'
|
||||||
|
import { compoundShape, extendHeader, headerSpan, leafBlock, leafShape, named } from './shared'
|
||||||
|
|
||||||
|
/** 可见性:pub 是公开,没写是本模块,pub(super) 是父模块,pub(crate) 是本 crate。 */
|
||||||
|
export type RustVisibility = 'public' | 'module' | 'super' | 'crate'
|
||||||
|
|
||||||
|
const STORAGE = new Set(['pub', 'async', 'const', 'static', 'mut', 'let'])
|
||||||
|
|
||||||
|
/** 一条说明的第一句,句号留着。 */
|
||||||
|
export function firstSentence(text: string): string {
|
||||||
|
const trimmed = text.trim()
|
||||||
|
const sentence = trimmed.match(/^[\s\S]*?[。!?.!?]/)
|
||||||
|
return (sentence ? sentence[0] : trimmed.split('\n')[0] ?? '').trim()
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 文档注释去掉记号后的第一句;普通注释是整段去掉记号。 */
|
||||||
|
export function commentPlain(node: Node): string {
|
||||||
|
const doc = node.childForFieldName('doc')
|
||||||
|
const raw = doc ? doc.text : node.text.replace(/^\/\/[/!]?\s?/, '').replace(/^\/\*[*!]?\s?/, '').replace(/\s*\*\/$/, '')
|
||||||
|
return raw.replace(/\n/g, ' ').trim()
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 说明行用的第一句。没有文字时没有。 */
|
||||||
|
export function docSentence(node: Node | undefined): string | undefined {
|
||||||
|
if (!node)
|
||||||
|
return undefined
|
||||||
|
const sentence = firstSentence(commentPlain(node))
|
||||||
|
return sentence || undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 空白收成一个空格,方便属性表里的类型、签名。 */
|
||||||
|
export function oneLine(text: string): string {
|
||||||
|
return text.replace(/\s+/g, ' ').trim()
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 节点上的可见性。pub(in …) 不在这四档里,按本 crate 算(只本包能用)。 */
|
||||||
|
export function rustVisibility(node: Node): RustVisibility {
|
||||||
|
const vis = named(node).find(child => child.type === 'visibility_modifier')
|
||||||
|
if (!vis)
|
||||||
|
return 'module'
|
||||||
|
const words = new Set(vis.children.map(child => child?.type))
|
||||||
|
if (words.has('super'))
|
||||||
|
return 'super'
|
||||||
|
if (words.has('crate') || words.has('in'))
|
||||||
|
return 'crate'
|
||||||
|
return 'public'
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 公开写在 facts.access 上;其余写在块的 access 属性上,盖过 facts。 */
|
||||||
|
export function visibilityFacts(node: Node): { access?: Access, attributes?: BlockAttributes } {
|
||||||
|
const visibility = rustVisibility(node)
|
||||||
|
return visibility === 'public' ? { access: 'public' } : { attributes: { access: visibility } }
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 参数表括号里的原文,空白收成一行。 */
|
||||||
|
export function signatureParams(fn: Node): string {
|
||||||
|
const params = fn.childForFieldName('parameters')
|
||||||
|
if (!params)
|
||||||
|
return ''
|
||||||
|
return oneLine(params.text.replace(/^\(/, '').replace(/\)$/, ''))
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 返回类型原文;没写是 ()。 */
|
||||||
|
export function returnText(fn: Node): string {
|
||||||
|
const returned = fn.childForFieldName('return_type')
|
||||||
|
return returned ? oneLine(returned.text) : '()'
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 函数体里直接写的 match,每一支的模式和「做什么」。 */
|
||||||
|
export function matchArms(fn: Node): { pattern: string, does: string }[] {
|
||||||
|
const body = fn.childForFieldName('body')
|
||||||
|
if (!body)
|
||||||
|
return []
|
||||||
|
const arms: { pattern: string, does: string }[] = []
|
||||||
|
for (const statement of named(body)) {
|
||||||
|
const expr = statement.type === 'expression_statement' ? named(statement)[0] : statement
|
||||||
|
if (expr?.type !== 'match_expression')
|
||||||
|
continue
|
||||||
|
const block = expr.childForFieldName('body')
|
||||||
|
for (const arm of named(block).filter(child => child.type === 'match_arm')) {
|
||||||
|
const pattern = arm.childForFieldName('pattern')
|
||||||
|
if (pattern)
|
||||||
|
arms.push({ pattern: oneLine(pattern.text), does: armDoes(arm) })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return arms
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 变体携带的数据:没带是空的,元组变体是类型,结构变体是字段原文。body 是变体的 body。 */
|
||||||
|
export function variantPayload(body: Node | undefined): string | undefined {
|
||||||
|
if (!body)
|
||||||
|
return undefined
|
||||||
|
if (body.type === 'ordered_field_declaration_list') {
|
||||||
|
const types = named(body).filter(child => child.type !== 'attribute_item').map(child => oneLine(child.text))
|
||||||
|
return types.join(', ') || undefined
|
||||||
|
}
|
||||||
|
return oneLine(body.text)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 写在这条前面、紧挨着的属性(#[…]),从外往里。 */
|
||||||
|
export function leadingAttributes(node: Node): Node[] {
|
||||||
|
const attrs: Node[] = []
|
||||||
|
let prev = node.previousNamedSibling
|
||||||
|
while (prev?.type === 'attribute_item') {
|
||||||
|
attrs.unshift(prev)
|
||||||
|
prev = prev.previousNamedSibling
|
||||||
|
}
|
||||||
|
return attrs
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 属性的名字:get、serde、axum::post、tauri::command。 */
|
||||||
|
export function attributeName(attributeItem: Node): string | undefined {
|
||||||
|
const attribute = named(attributeItem).find(child => child.type === 'attribute')
|
||||||
|
const head = attribute ? named(attribute)[0] : undefined
|
||||||
|
return head?.text
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 这条自己的属性,含写在它前面的。 */
|
||||||
|
export function itemAttributes(node: Node): Node[] {
|
||||||
|
return [...leadingAttributes(node), ...named(node).filter(child => child.type === 'attribute_item')]
|
||||||
|
}
|
||||||
|
|
||||||
|
/** derive(...) 里列出的名字。 */
|
||||||
|
export function deriveNames(node: Node): string[] {
|
||||||
|
const names: string[] = []
|
||||||
|
for (const item of itemAttributes(node)) {
|
||||||
|
if (attributeName(item) !== 'derive')
|
||||||
|
continue
|
||||||
|
const attribute = named(item).find(child => child.type === 'attribute')
|
||||||
|
const tree = attribute?.children.find(child => child?.type === 'token_tree')
|
||||||
|
if (!tree)
|
||||||
|
continue
|
||||||
|
for (const name of named(tree)) {
|
||||||
|
if (name.type === 'identifier')
|
||||||
|
names.push(name.text)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return names
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 字符串字面量里的原文,不含引号。 */
|
||||||
|
export function stringContent(node: Node | undefined): string | undefined {
|
||||||
|
if (!node)
|
||||||
|
return undefined
|
||||||
|
if (node.type === 'string_content')
|
||||||
|
return node.text
|
||||||
|
const content = node.children.find(child => child?.type === 'string_content')
|
||||||
|
return content?.text
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 宏调用的名字和括号里的具名实参。 */
|
||||||
|
export function macroInvocation(node: Node): { name: string, args: Node[] } | undefined {
|
||||||
|
if (node.type !== 'macro_invocation')
|
||||||
|
return undefined
|
||||||
|
const macro = node.childForFieldName('macro')
|
||||||
|
const args = node.children.find(child => child?.type === 'token_tree')
|
||||||
|
if (!macro || !args)
|
||||||
|
return undefined
|
||||||
|
return { name: macro.text, args: named(args) }
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 函数写在 impl 或 trait 里面,参数表叫「参数」而不是「输入参数」。 */
|
||||||
|
export function isMethod(node: Node): boolean {
|
||||||
|
const parent = node.parent
|
||||||
|
return parent?.type === 'declaration_list' && (parent.parent?.type === 'impl_item' || parent.parent?.type === 'trait_item')
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 结构体的字段:名字、类型、可见性。元组结构体没有名字,不进这张表。 */
|
||||||
|
export function structFields(node: Node): { name: Node, type: Node, visibility: RustVisibility }[] {
|
||||||
|
const body = node.childForFieldName('body')
|
||||||
|
if (body?.type !== 'field_declaration_list')
|
||||||
|
return []
|
||||||
|
return named(body).flatMap((field) => {
|
||||||
|
if (field.type !== 'field_declaration')
|
||||||
|
return []
|
||||||
|
const name = field.childForFieldName('name')
|
||||||
|
const type = field.childForFieldName('type')
|
||||||
|
return name && type ? [{ name, type, visibility: rustVisibility(field) }] : []
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/** trait 或 impl 主体里的函数(含只写签名的)。 */
|
||||||
|
export function bodyFunctions(node: Node): Node[] {
|
||||||
|
const body = node.childForFieldName('body')
|
||||||
|
return named(body).filter(child => child.type === 'function_item' || child.type === 'function_signature_item')
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createRustAdapter(wasmDir: string): LanguageAdapter {
|
||||||
|
return {
|
||||||
|
id: 'rust',
|
||||||
|
languageIds: ['rust'],
|
||||||
|
grammarPath: () => path.join(wasmDir, 'tree-sitter-rust.wasm'),
|
||||||
|
toBlocks: root => topBlocks(named(root)),
|
||||||
|
tokenRole: rustTokenRole,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function topBlocks(items: Node[]): CodeBlock[] {
|
||||||
|
const blocks: CodeBlock[] = []
|
||||||
|
let index = 0
|
||||||
|
while (index < items.length) {
|
||||||
|
const item = items[index]
|
||||||
|
if (isDoc(item)) {
|
||||||
|
const docs = [item]
|
||||||
|
let next = index + 1
|
||||||
|
while (next < items.length && isDoc(items[next]) && items[next].startPosition.row <= docs[docs.length - 1].endPosition.row + 1)
|
||||||
|
docs.push(items[next++])
|
||||||
|
while (next < items.length && items[next].type === 'attribute_item')
|
||||||
|
next++
|
||||||
|
const target = items[next]
|
||||||
|
if (target && target.type !== 'line_comment' && target.type !== 'block_comment') {
|
||||||
|
blocks.push(withPreamble(itemBlock(target), docs))
|
||||||
|
index = next + 1
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (item.type === 'attribute_item') {
|
||||||
|
index++
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (item.type === 'line_comment' || item.type === 'block_comment')
|
||||||
|
blocks.push(leafBlock('comment', item))
|
||||||
|
else
|
||||||
|
blocks.push(withPreamble(itemBlock(item), []))
|
||||||
|
index++
|
||||||
|
}
|
||||||
|
return blocks
|
||||||
|
}
|
||||||
|
|
||||||
|
function itemBlock(item: Node): CodeBlock {
|
||||||
|
switch (item.type) {
|
||||||
|
case 'use_declaration':
|
||||||
|
return useBlock(item)
|
||||||
|
case 'struct_item':
|
||||||
|
return structBlock(item)
|
||||||
|
case 'enum_item':
|
||||||
|
return enumBlock(item)
|
||||||
|
case 'impl_item':
|
||||||
|
return implBlock(item)
|
||||||
|
case 'trait_item':
|
||||||
|
return traitBlock(item)
|
||||||
|
case 'function_item':
|
||||||
|
return functionBlock(item)
|
||||||
|
default:
|
||||||
|
return leafBlock('statement', item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 文档注释和写在前面的属性算进头部。文档注释的第一句是说明。 */
|
||||||
|
function withPreamble(block: CodeBlock, docs: Node[]): CodeBlock {
|
||||||
|
const [doc] = docs
|
||||||
|
const attribute = leadingAttributes(block.node)[0]
|
||||||
|
let next = block
|
||||||
|
if (doc && isDocumentedKind(next))
|
||||||
|
next = { ...extendHeader(next, doc), facts: { ...next.facts, doc } } as typeof next
|
||||||
|
const from = doc ?? attribute
|
||||||
|
if (from && next.header.startIndex > from.startIndex)
|
||||||
|
next = extendHeader(next, from)
|
||||||
|
return next
|
||||||
|
}
|
||||||
|
|
||||||
|
function isDocumentedKind(block: CodeBlock): block is FunctionBlock | ClassBlock | TraitBlock | ImplBlock | EnumBlock {
|
||||||
|
return block.kind === 'function' || block.kind === 'class' || block.kind === 'trait' || block.kind === 'impl' || block.kind === 'enum'
|
||||||
|
}
|
||||||
|
|
||||||
|
function isDoc(node: Node): boolean {
|
||||||
|
return (node.type === 'line_comment' || node.type === 'block_comment') && named(node).some(child => child.type === 'outer_doc_comment_marker')
|
||||||
|
}
|
||||||
|
|
||||||
|
function useBlock(node: Node): ImportBlock {
|
||||||
|
const argument = node.childForFieldName('argument')
|
||||||
|
const { source, items } = argument ? useParts(argument) : { items: [] }
|
||||||
|
return { kind: 'import', ...leafShape(node), facts: { sources: source ? [source] : [], items } }
|
||||||
|
}
|
||||||
|
|
||||||
|
function useParts(node: Node): { source?: Node, items: ImportItem[] } {
|
||||||
|
switch (node.type) {
|
||||||
|
case 'scoped_identifier': {
|
||||||
|
const source = node.childForFieldName('path') ?? undefined
|
||||||
|
const name = node.childForFieldName('name')
|
||||||
|
return { source, items: name ? [{ kind: 'named', name }] : [] }
|
||||||
|
}
|
||||||
|
case 'scoped_use_list': {
|
||||||
|
const source = node.childForFieldName('path') ?? undefined
|
||||||
|
const list = node.childForFieldName('list')
|
||||||
|
return { source, items: list ? useList(list) : [] }
|
||||||
|
}
|
||||||
|
case 'use_list':
|
||||||
|
return { items: useList(node) }
|
||||||
|
case 'use_as_clause':
|
||||||
|
return { items: asItem(node) }
|
||||||
|
case 'use_wildcard':
|
||||||
|
return { items: [{ kind: 'namespace' }] }
|
||||||
|
case 'identifier':
|
||||||
|
return { items: [{ kind: 'named', name: node }] }
|
||||||
|
default:
|
||||||
|
return { items: [] }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function useList(list: Node): ImportItem[] {
|
||||||
|
return named(list).flatMap((child) => {
|
||||||
|
if (child.type === 'identifier')
|
||||||
|
return [{ kind: 'named' as const, name: child }]
|
||||||
|
if (child.type === 'use_as_clause')
|
||||||
|
return asItem(child)
|
||||||
|
if (child.type === 'use_wildcard')
|
||||||
|
return [{ kind: 'namespace' as const }]
|
||||||
|
if (child.type === 'scoped_identifier') {
|
||||||
|
const name = child.childForFieldName('name')
|
||||||
|
return name ? [{ kind: 'named' as const, name }] : []
|
||||||
|
}
|
||||||
|
return []
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function asItem(node: Node): ImportItem[] {
|
||||||
|
const name = node.childForFieldName('path')
|
||||||
|
const alias = node.childForFieldName('alias') ?? undefined
|
||||||
|
return name ? [{ kind: 'named', name, alias }] : []
|
||||||
|
}
|
||||||
|
|
||||||
|
function structBlock(node: Node): CodeBlock {
|
||||||
|
const { access, attributes } = visibilityFacts(node)
|
||||||
|
return {
|
||||||
|
kind: 'class',
|
||||||
|
...shape(node),
|
||||||
|
...(attributes && { attributes }),
|
||||||
|
facts: classFacts(node, access),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function enumBlock(node: Node): EnumBlock {
|
||||||
|
const { access, attributes } = visibilityFacts(node)
|
||||||
|
const body = node.childForFieldName('body')
|
||||||
|
const members = named(body).flatMap((variant) => {
|
||||||
|
if (variant.type !== 'enum_variant')
|
||||||
|
return []
|
||||||
|
const name = variant.childForFieldName('name')
|
||||||
|
if (!name)
|
||||||
|
return []
|
||||||
|
const payload = variant.childForFieldName('body') ?? undefined
|
||||||
|
return [{ name, ...(payload && { value: payload }), docs: [] }]
|
||||||
|
})
|
||||||
|
return {
|
||||||
|
kind: 'enum',
|
||||||
|
...shape(node),
|
||||||
|
...(attributes && { attributes }),
|
||||||
|
facts: { name: node.childForFieldName('name') ?? node, doc: undefined, access, decorators: [], bases: [], members },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function traitBlock(node: Node): TraitBlock {
|
||||||
|
const { access, attributes } = visibilityFacts(node)
|
||||||
|
return { kind: 'trait', ...shape(node), ...(attributes && { attributes }), facts: classFacts(node, access) }
|
||||||
|
}
|
||||||
|
|
||||||
|
function implBlock(node: Node): ImplBlock {
|
||||||
|
const { access, attributes } = visibilityFacts(node)
|
||||||
|
const body = node.childForFieldName('body')
|
||||||
|
const children: CodeBlock[] = []
|
||||||
|
let docs: Node[] = []
|
||||||
|
for (const child of named(body)) {
|
||||||
|
if (isDoc(child)) {
|
||||||
|
docs.push(child)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (child.type === 'attribute_item')
|
||||||
|
continue
|
||||||
|
if (child.type === 'function_item')
|
||||||
|
children.push(withPreamble(functionBlock(child), docs))
|
||||||
|
else if (child.type === 'const_item' || child.type === 'type_item')
|
||||||
|
children.push(leafBlock('statement', child))
|
||||||
|
docs = []
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
kind: 'impl',
|
||||||
|
...compoundShape(node, body?.startIndex ?? node.endIndex, [], () => children),
|
||||||
|
...(attributes && { attributes }),
|
||||||
|
facts: classFacts(node, access, implType(node)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function functionBlock(node: Node): FunctionBlock {
|
||||||
|
const { access, attributes } = visibilityFacts(node)
|
||||||
|
const body = node.childForFieldName('body')
|
||||||
|
const headerEnd = body?.startIndex ?? node.endIndex
|
||||||
|
return {
|
||||||
|
kind: 'function',
|
||||||
|
node,
|
||||||
|
header: headerSpan(node, headerEnd),
|
||||||
|
children: [],
|
||||||
|
...(attributes && { attributes }),
|
||||||
|
facts: {
|
||||||
|
name: node.childForFieldName('name') ?? undefined,
|
||||||
|
access,
|
||||||
|
...(isAsync(node) && { async: true }),
|
||||||
|
decorators: leadingAttributes(node),
|
||||||
|
parameters: parametersOf(node.childForFieldName('parameters')),
|
||||||
|
returnType: node.childForFieldName('return_type') ?? undefined,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function classFacts(node: Node, access: Access | undefined, name = node.childForFieldName('name') ?? undefined): ClassFacts {
|
||||||
|
return { name, access, decorators: leadingAttributes(node), typeParameters: [], bases: [], implements: [] }
|
||||||
|
}
|
||||||
|
|
||||||
|
/** impl 的自己的类型:impl Trait for Type 取 Type,impl Type 取 Type。 */
|
||||||
|
function implType(node: Node): Node | undefined {
|
||||||
|
return node.childForFieldName('type') ?? node.childForFieldName('trait') ?? undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
function shape(node: Node): { node: Node, header: { startIndex: number, endIndex: number, startRow: number, endRow: number }, children: CodeBlock[] } {
|
||||||
|
const body = node.childForFieldName('body')
|
||||||
|
return body ? { node, header: headerSpan(node, body.startIndex), children: [] } : { ...leafShape(node) }
|
||||||
|
}
|
||||||
|
|
||||||
|
function parametersOf(list: Node | null): ParameterFacts[] {
|
||||||
|
if (!list)
|
||||||
|
return []
|
||||||
|
return named(list).flatMap((child) => {
|
||||||
|
if (child.type === 'self_parameter') {
|
||||||
|
const name = [...child.children].reverse().find(token => token?.type === 'self')
|
||||||
|
return name ? [{ name, optional: false, type: child }] : []
|
||||||
|
}
|
||||||
|
if (child.type !== 'parameter')
|
||||||
|
return []
|
||||||
|
const name = child.childForFieldName('pattern')
|
||||||
|
const type = child.childForFieldName('type') ?? undefined
|
||||||
|
return name ? [{ name, optional: false, type }] : []
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function isAsync(node: Node): boolean {
|
||||||
|
const modifiers = named(node).find(child => child.type === 'function_modifiers')
|
||||||
|
return modifiers?.children.some(child => child?.type === 'async') ?? false
|
||||||
|
}
|
||||||
|
|
||||||
|
function armDoes(arm: Node): string {
|
||||||
|
const comment = firstComment(arm)
|
||||||
|
if (comment)
|
||||||
|
return firstSentence(commentPlain(comment)) || '—'
|
||||||
|
const value = arm.childForFieldName('value')
|
||||||
|
if (!value)
|
||||||
|
return '—'
|
||||||
|
if (value.type === 'return_expression') {
|
||||||
|
const returned = named(value)[0]
|
||||||
|
return returned ? `返回 ${oneLine(returned.text)}` : '返回'
|
||||||
|
}
|
||||||
|
if (value.type === 'block') {
|
||||||
|
const inner = firstComment(value)
|
||||||
|
if (inner)
|
||||||
|
return firstSentence(commentPlain(inner)) || '—'
|
||||||
|
const statement = named(value)[0]
|
||||||
|
return statement ? oneLine(statement.text) : '—'
|
||||||
|
}
|
||||||
|
return oneLine(value.text)
|
||||||
|
}
|
||||||
|
|
||||||
|
function firstComment(node: Node): Node | undefined {
|
||||||
|
if (node.type === 'line_comment' || node.type === 'block_comment')
|
||||||
|
return node
|
||||||
|
for (const child of node.children) {
|
||||||
|
if (!child)
|
||||||
|
continue
|
||||||
|
const found = firstComment(child)
|
||||||
|
if (found)
|
||||||
|
return found
|
||||||
|
}
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
function rustTokenRole(leaf: Node): TokenRole {
|
||||||
|
if (leaf.type === 'line_comment' || leaf.type === 'block_comment' || inside(leaf, 'line_comment') || inside(leaf, 'block_comment'))
|
||||||
|
return 'comment'
|
||||||
|
if (inside(leaf, 'string_literal') || leaf.type === 'string_content')
|
||||||
|
return 'string'
|
||||||
|
if (leaf.type === 'integer_literal' || leaf.type === 'float_literal' || leaf.type === 'char_literal')
|
||||||
|
return 'number'
|
||||||
|
if (leaf.type === 'primitive_type' || leaf.type === 'type_identifier')
|
||||||
|
return 'type'
|
||||||
|
if (leaf.type === 'mutable_specifier')
|
||||||
|
return 'keyword'
|
||||||
|
if (!leaf.isNamed)
|
||||||
|
return STORAGE.has(leaf.type) ? 'storage' : /^[a-z]+$/.test(leaf.type) ? 'keyword' : 'plain'
|
||||||
|
if (leaf.type === 'identifier' || leaf.type === 'field_identifier' || leaf.type === 'self') {
|
||||||
|
const parent = leaf.parent?.type
|
||||||
|
return parent === 'function_item' || parent === 'function_signature_item' ? 'function' : 'variable'
|
||||||
|
}
|
||||||
|
return 'plain'
|
||||||
|
}
|
||||||
|
|
||||||
|
function inside(node: Node, type: string): boolean {
|
||||||
|
let current = node.parent
|
||||||
|
while (current) {
|
||||||
|
if (current.type === type)
|
||||||
|
return true
|
||||||
|
current = current.parent
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
@@ -209,6 +209,8 @@ function ownDoc(block: CodeBlock): Node | undefined {
|
|||||||
switch (block.kind) {
|
switch (block.kind) {
|
||||||
case 'function':
|
case 'function':
|
||||||
case 'class':
|
case 'class':
|
||||||
|
case 'trait':
|
||||||
|
case 'impl':
|
||||||
case 'enum':
|
case 'enum':
|
||||||
case 'exception':
|
case 'exception':
|
||||||
case 'const':
|
case 'const':
|
||||||
|
|||||||
@@ -0,0 +1,36 @@
|
|||||||
|
import type { ImplBlock, KindBadge, LibraryRule, RuleCard, ThemeColor } from '../contract'
|
||||||
|
import { implRows, plainSay } from '../rustRows'
|
||||||
|
|
||||||
|
/** 和函数卡同一个黄,实现块看起来仍是函数那一挂。 */
|
||||||
|
const COLOR: ThemeColor = { dark: '#dcdcaa', light: '#795e26' }
|
||||||
|
const BADGE: KindBadge = { label: '实现', color: COLOR, icon: 'square-function' }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* impl 的方法表:点方法名展开那张函数卡。
|
||||||
|
* 子块放进属性表某一行,和 React 事件处理同一条路。不看导入,Rust 文件都启用。
|
||||||
|
*/
|
||||||
|
export const rustImplRule: LibraryRule = {
|
||||||
|
id: 'rust',
|
||||||
|
label: 'Rust 实现',
|
||||||
|
languageIds: ['rust'],
|
||||||
|
libraries: [],
|
||||||
|
claim: block => block.kind === 'impl' ? implCard(block) : undefined,
|
||||||
|
attributeNames: () => ({
|
||||||
|
attributes: {},
|
||||||
|
values: { kind: { 'rust.impl': '实现' } },
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
|
||||||
|
function implCard(block: ImplBlock): RuleCard {
|
||||||
|
const rows = implRows(block, plainSay())
|
||||||
|
const row = rows.findIndex(item => item.label === '方法')
|
||||||
|
const methods = block.children.filter(child => child.kind === 'function')
|
||||||
|
return {
|
||||||
|
kind: 'rust.impl',
|
||||||
|
badge: BADGE,
|
||||||
|
rows,
|
||||||
|
...(block.facts.name && { defines: { startIndex: block.facts.name.startIndex, endIndex: block.facts.name.endIndex } }),
|
||||||
|
absorbs: [],
|
||||||
|
...(row >= 0 && { slots: methods.map((child, item) => ({ block: child, row, item })) }),
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,182 @@
|
|||||||
|
import type { ClassBlock, CodeBlock, EnumBlock, FunctionBlock, ImplBlock, ImportBlock, Inline, PropertyItem, PropertyRow, TokenRole, TraitBlock } from './contract'
|
||||||
|
import type { RustVisibility } from './languages/rust'
|
||||||
|
import { bodyFunctions, docSentence, isMethod, matchArms, oneLine, returnText, signatureParams, structFields, variantPayload } from './languages/rust'
|
||||||
|
|
||||||
|
/** 把类型、名字画成原文。翻译插件传入时,名字带源码下标。 */
|
||||||
|
export interface RustSay {
|
||||||
|
name: (node: import('./contract').Node, role: TokenRole) => Inline[]
|
||||||
|
source: (node: import('./contract').Node, role?: TokenRole) => Inline[]
|
||||||
|
}
|
||||||
|
|
||||||
|
const VISIBILITY: Record<RustVisibility, string> = {
|
||||||
|
public: '公开',
|
||||||
|
module: '本模块',
|
||||||
|
super: '父模块',
|
||||||
|
crate: '本 crate',
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 块上的可见性:属性盖过 facts,没写是本模块。 */
|
||||||
|
export function visibilityWord(block: CodeBlock): string {
|
||||||
|
const overridden = block.attributes?.access
|
||||||
|
if (overridden && overridden in VISIBILITY)
|
||||||
|
return VISIBILITY[overridden as RustVisibility]
|
||||||
|
if ('facts' in block && 'access' in block.facts && block.facts.access === 'public')
|
||||||
|
return VISIBILITY.public
|
||||||
|
return VISIBILITY.module
|
||||||
|
}
|
||||||
|
|
||||||
|
export function importRows(block: ImportBlock, say: RustSay): PropertyRow[] {
|
||||||
|
const [source] = block.facts.sources
|
||||||
|
return [
|
||||||
|
{ label: '来源', value: source ? say.source(source, 'type') : [hint('—')] },
|
||||||
|
{
|
||||||
|
label: '导入项',
|
||||||
|
value: [hint(`${block.facts.items.length} 个`)],
|
||||||
|
columns: ['名字', '别名'],
|
||||||
|
items: block.facts.items.map((item) => {
|
||||||
|
if (item.kind === 'namespace')
|
||||||
|
return { cells: [[hint('*')], [hint('—')]] }
|
||||||
|
if (item.kind !== 'named')
|
||||||
|
return { cells: [[hint('—')], [hint('—')]] }
|
||||||
|
return {
|
||||||
|
cells: [say.name(item.name, 'type'), item.alias ? say.name(item.alias, 'type') : [hint('—')]],
|
||||||
|
defines: range(item.name),
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
export function structRows(block: ClassBlock, say: RustSay): PropertyRow[] {
|
||||||
|
const fields = structFields(block.node)
|
||||||
|
return [
|
||||||
|
nameRow(block.facts.name, say, 'type'),
|
||||||
|
...docRow(block.facts.doc),
|
||||||
|
visibilityRow(block),
|
||||||
|
...tableRow('字段', ['字段', '类型', '可见性'], fields.map(field => ({
|
||||||
|
cells: [
|
||||||
|
say.name(field.name, 'variable'),
|
||||||
|
say.source(field.type, 'type'),
|
||||||
|
[keyword(VISIBILITY[field.visibility])],
|
||||||
|
],
|
||||||
|
defines: range(field.name),
|
||||||
|
}))),
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
export function enumRows(block: EnumBlock, say: RustSay): PropertyRow[] {
|
||||||
|
return [
|
||||||
|
nameRow(block.facts.name, say, 'type'),
|
||||||
|
...docRow(block.facts.doc),
|
||||||
|
...tableRow('变体', ['变体', '携带的数据'], block.facts.members.map(member => ({
|
||||||
|
cells: [say.name(member.name, 'type'), [hint(variantPayload(member.value) ?? '—')]],
|
||||||
|
defines: range(member.name),
|
||||||
|
}))),
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
export function functionRows(block: FunctionBlock, say: RustSay): PropertyRow[] {
|
||||||
|
const arms = matchArms(block.node)
|
||||||
|
return [
|
||||||
|
nameRow(block.facts.name, say, 'function'),
|
||||||
|
...docRow(block.facts.doc),
|
||||||
|
...(block.facts.async ? [{ label: '异步', value: [keyword('是')] }] : []),
|
||||||
|
visibilityRow(block),
|
||||||
|
...tableRow(isMethod(block.node) ? '参数' : '输入参数', ['名称', '类型'], block.facts.parameters.map(param => ({
|
||||||
|
cells: [say.name(param.name, 'variable'), param.type ? say.source(param.type, 'type') : [hint('—')]],
|
||||||
|
defines: range(param.name),
|
||||||
|
}))),
|
||||||
|
{ label: '返回', value: [plain(returnText(block.node))] },
|
||||||
|
...tableRow('分情况', ['分支', '做什么'], arms.map(arm => ({
|
||||||
|
cells: [[plain(arm.pattern)], [hint(arm.does)]],
|
||||||
|
})), '支'),
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
export function traitRows(block: TraitBlock, say: RustSay): PropertyRow[] {
|
||||||
|
const methods = bodyFunctions(block.node)
|
||||||
|
return [
|
||||||
|
nameRow(block.facts.name, say, 'type'),
|
||||||
|
...docRow(block.facts.doc),
|
||||||
|
...tableRow('方法', ['名称', '参数', '返回'], methods.map((method) => {
|
||||||
|
const name = method.childForFieldName('name')
|
||||||
|
return {
|
||||||
|
cells: [
|
||||||
|
name ? say.name(name, 'function') : [hint('匿名')],
|
||||||
|
[plain(signatureParams(method))],
|
||||||
|
[plain(returnText(method))],
|
||||||
|
],
|
||||||
|
...(name && { defines: range(name) }),
|
||||||
|
}
|
||||||
|
})),
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
export function implRows(block: ImplBlock, say: RustSay): PropertyRow[] {
|
||||||
|
const methods = block.children.filter((child): child is FunctionBlock => child.kind === 'function')
|
||||||
|
const title: Inline[] = block.facts.name
|
||||||
|
? [...say.name(block.facts.name, 'type'), plain(' 的方法')]
|
||||||
|
: [hint('实现')]
|
||||||
|
return [
|
||||||
|
{ label: '名称', value: title, ...(block.facts.name && { defines: range(block.facts.name) }) },
|
||||||
|
...docRow(block.facts.doc),
|
||||||
|
...tableRow('方法', ['名称', '参数', '返回'], methods.map((method) => {
|
||||||
|
const name = method.facts.name
|
||||||
|
return {
|
||||||
|
cells: [
|
||||||
|
name ? say.name(name, 'function') : [hint('匿名')],
|
||||||
|
[plain(signatureParams(method.node))],
|
||||||
|
[plain(returnText(method.node))],
|
||||||
|
],
|
||||||
|
...(name && { defines: range(name) }),
|
||||||
|
}
|
||||||
|
})),
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
function nameRow(name: import('./contract').Node | undefined, say: RustSay, role: TokenRole): PropertyRow {
|
||||||
|
return {
|
||||||
|
label: '名称',
|
||||||
|
value: name ? say.name(name, role) : [hint('匿名')],
|
||||||
|
...(name && { defines: range(name) }),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function docRow(doc: import('./contract').Node | undefined): PropertyRow[] {
|
||||||
|
const sentence = docSentence(doc)
|
||||||
|
return sentence ? [{ label: '说明', value: { markdown: sentence } }] : []
|
||||||
|
}
|
||||||
|
|
||||||
|
function visibilityRow(block: CodeBlock): PropertyRow {
|
||||||
|
return { label: '可见性', value: [keyword(visibilityWord(block))] }
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 没有行就不占一行。countUnit 默认「个」,分情况用「支」。 */
|
||||||
|
function tableRow(label: string, columns: string[], items: PropertyItem[], countUnit = '个'): PropertyRow[] {
|
||||||
|
if (items.length === 0)
|
||||||
|
return []
|
||||||
|
return [{ label, value: [hint(`${items.length} ${countUnit}`)], columns, items }]
|
||||||
|
}
|
||||||
|
|
||||||
|
function hint(text: string): Inline {
|
||||||
|
return { text, role: 'hint' }
|
||||||
|
}
|
||||||
|
|
||||||
|
function plain(text: string): Inline {
|
||||||
|
return { text, role: 'plain' }
|
||||||
|
}
|
||||||
|
|
||||||
|
function keyword(text: string): Inline {
|
||||||
|
return { text, role: 'keyword' }
|
||||||
|
}
|
||||||
|
|
||||||
|
function range(node: import('./contract').Node): { startIndex: number, endIndex: number } {
|
||||||
|
return { startIndex: node.startIndex, endIndex: node.endIndex }
|
||||||
|
}
|
||||||
|
/** 名字、类型照抄源码,名字带上下标,点了能跳到定义。 */
|
||||||
|
export function plainSay(): RustSay {
|
||||||
|
return {
|
||||||
|
name: (node, role) => [{ text: node.text, role, offset: node.startIndex }],
|
||||||
|
source: (node, role = 'type') => [{ text: oneLine(node.text), role, offset: node.startIndex }],
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,105 @@
|
|||||||
|
import type { FactBlock, Node, PropertyRow, SourceDocument, Translator } from '../contract'
|
||||||
|
import { enumRows, functionRows, implRows, importRows, plainSay, structRows, traitRows } from '../rustRows'
|
||||||
|
import { createZhTemplateTranslator, KIND_ZH, ZH_ATTRIBUTE_NAMES } from './zhTemplate'
|
||||||
|
|
||||||
|
const say = plainSay()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rust 的中文直译。属性表的用词按代码阅读的 Rust 原型:结构体、可见性、变体、分情况。
|
||||||
|
* 关键字和注释仍走中文直译那一套。
|
||||||
|
*/
|
||||||
|
export const rustZhTranslator: Translator = {
|
||||||
|
...createZhTemplateTranslator({
|
||||||
|
id: 'rust.zh-template',
|
||||||
|
label: '中文直译',
|
||||||
|
languageIds: ['rust'],
|
||||||
|
keywords: {
|
||||||
|
fn: '函数',
|
||||||
|
let: '令',
|
||||||
|
mut: '可变',
|
||||||
|
pub: '公开',
|
||||||
|
struct: '结构体',
|
||||||
|
enum: '枚举',
|
||||||
|
impl: '实现',
|
||||||
|
trait: '特质',
|
||||||
|
use: '导入',
|
||||||
|
mod: '模块',
|
||||||
|
match: '匹配',
|
||||||
|
if: '如果',
|
||||||
|
else: '否则',
|
||||||
|
for: '遍历',
|
||||||
|
while: '当',
|
||||||
|
loop: '循环',
|
||||||
|
return: '返回',
|
||||||
|
async: '异步',
|
||||||
|
await: '等待',
|
||||||
|
break: '跳出',
|
||||||
|
continue: '继续',
|
||||||
|
where: '其中',
|
||||||
|
type: '类型',
|
||||||
|
const: '常量',
|
||||||
|
static: '静态',
|
||||||
|
unsafe: '不安全',
|
||||||
|
as: '作为',
|
||||||
|
in: '在',
|
||||||
|
ref: '引用',
|
||||||
|
move: '移动',
|
||||||
|
dyn: '动态',
|
||||||
|
crate: '本 crate',
|
||||||
|
super: '父模块',
|
||||||
|
self: '自己',
|
||||||
|
Self: '本类型',
|
||||||
|
},
|
||||||
|
literals: { true: '真', false: '假' },
|
||||||
|
builtinTypes: {},
|
||||||
|
isTypePosition: () => false,
|
||||||
|
operators: {},
|
||||||
|
operatorParents: [],
|
||||||
|
emptyLiterals: {},
|
||||||
|
mapLiterals: {},
|
||||||
|
stringNodes: ['string_literal'],
|
||||||
|
commentText,
|
||||||
|
nodeTemplates: {},
|
||||||
|
}),
|
||||||
|
attributeNames: {
|
||||||
|
attributes: { ...ZH_ATTRIBUTE_NAMES.attributes, access: '可见性' },
|
||||||
|
values: {
|
||||||
|
...ZH_ATTRIBUTE_NAMES.values,
|
||||||
|
kind: { ...KIND_ZH, class: '结构体' },
|
||||||
|
access: {
|
||||||
|
...ZH_ATTRIBUTE_NAMES.values.access,
|
||||||
|
public: '公开',
|
||||||
|
module: '本模块',
|
||||||
|
super: '父模块',
|
||||||
|
crate: '本 crate',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
describe,
|
||||||
|
}
|
||||||
|
|
||||||
|
function describe(block: FactBlock, _document: SourceDocument): PropertyRow[] {
|
||||||
|
switch (block.kind) {
|
||||||
|
case 'import':
|
||||||
|
return importRows(block, say)
|
||||||
|
case 'class':
|
||||||
|
return structRows(block, say)
|
||||||
|
case 'enum':
|
||||||
|
return enumRows(block, say)
|
||||||
|
case 'function':
|
||||||
|
return functionRows(block, say)
|
||||||
|
case 'trait':
|
||||||
|
return traitRows(block, say)
|
||||||
|
case 'impl':
|
||||||
|
return implRows(block, say)
|
||||||
|
default:
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function commentText(node: Node, text: string): string {
|
||||||
|
const doc = node.childForFieldName('doc')
|
||||||
|
if (doc)
|
||||||
|
return doc.text.trim()
|
||||||
|
return text.replace(/^\/\/[/!]?\s?/, '').replace(/^\/\*[*!]?\s?/, '').replace(/\s*\*\/$/, '').trim()
|
||||||
|
}
|
||||||
@@ -208,10 +208,12 @@ function slotChild(node: Node, slot: Exclude<TemplatePart, string>): Node | null
|
|||||||
}
|
}
|
||||||
|
|
||||||
const ATTRIBUTE_ZH: Record<CommonAttribute, string> = { kind: '种类', access: '访问级别', async: '异步', abstract: '抽象', typeOnly: '仅类型' }
|
const ATTRIBUTE_ZH: Record<CommonAttribute, string> = { kind: '种类', access: '访问级别', async: '异步', abstract: '抽象', typeOnly: '仅类型' }
|
||||||
const KIND_ZH: Record<BlockKind, string> = {
|
export const KIND_ZH: Record<BlockKind, string> = {
|
||||||
import: '导入',
|
import: '导入',
|
||||||
function: '函数',
|
function: '函数',
|
||||||
class: '类',
|
class: '类',
|
||||||
|
trait: '特质',
|
||||||
|
impl: '实现',
|
||||||
enum: '枚举',
|
enum: '枚举',
|
||||||
exception: '异常',
|
exception: '异常',
|
||||||
type: '类型',
|
type: '类型',
|
||||||
@@ -249,7 +251,7 @@ const ARGUMENT_COLUMNS = ['参数', '值']
|
|||||||
const YES_NO: Record<'true' | 'false', string> = { true: '是', false: '否' }
|
const YES_NO: Record<'true' | 'false', string> = { true: '是', false: '否' }
|
||||||
|
|
||||||
/** 筛选属性和值的中文叫法;属性表里的访问级别、异步、抽象、仅类型这几行,属性名和值也用这一套。 */
|
/** 筛选属性和值的中文叫法;属性表里的访问级别、异步、抽象、仅类型这几行,属性名和值也用这一套。 */
|
||||||
const ZH_ATTRIBUTE_NAMES: AttributeNames = {
|
export const ZH_ATTRIBUTE_NAMES: AttributeNames = {
|
||||||
attributes: ATTRIBUTE_ZH,
|
attributes: ATTRIBUTE_ZH,
|
||||||
values: { kind: KIND_ZH, access: ACCESS_ZH, async: YES_NO, abstract: YES_NO, typeOnly: YES_NO },
|
values: { kind: KIND_ZH, access: ACCESS_ZH, async: YES_NO, abstract: YES_NO, typeOnly: YES_NO },
|
||||||
}
|
}
|
||||||
@@ -704,6 +706,8 @@ export function createZhTemplateTranslator(table: ZhTable): Translator {
|
|||||||
case 'function':
|
case 'function':
|
||||||
return functionRows(block.facts, phrases)
|
return functionRows(block.facts, phrases)
|
||||||
case 'class':
|
case 'class':
|
||||||
|
case 'trait':
|
||||||
|
case 'impl':
|
||||||
return classRows(block.facts, phrases)
|
return classRows(block.facts, phrases)
|
||||||
case 'enum':
|
case 'enum':
|
||||||
return enumRows(block.facts, phrases)
|
return enumRows(block.facts, phrases)
|
||||||
|
|||||||
@@ -68,6 +68,8 @@ body.vscode-high-contrast-light {
|
|||||||
:is([data-kind="import"], [data-kind="copy"]) { --kind: var(--cr-kind-import); }
|
:is([data-kind="import"], [data-kind="copy"]) { --kind: var(--cr-kind-import); }
|
||||||
[data-kind="function"] { --kind: var(--cr-kind-function); }
|
[data-kind="function"] { --kind: var(--cr-kind-function); }
|
||||||
:is([data-kind="class"], [data-kind="type"]) { --kind: var(--cr-kind-type); }
|
:is([data-kind="class"], [data-kind="type"]) { --kind: var(--cr-kind-type); }
|
||||||
|
[data-kind="trait"] { --kind: var(--cr-storage); }
|
||||||
|
[data-kind="impl"] { --kind: var(--cr-kind-function); }
|
||||||
[data-kind="enum"] { --kind: var(--cr-kind-enum); }
|
[data-kind="enum"] { --kind: var(--cr-kind-enum); }
|
||||||
[data-kind="exception"] { --kind: var(--cr-kind-exception); }
|
[data-kind="exception"] { --kind: var(--cr-kind-exception); }
|
||||||
:is([data-kind="field"], [data-kind="const"], [data-kind="variable"], [data-kind="label"]) { --kind: var(--cr-kind-data); }
|
:is([data-kind="field"], [data-kind="const"], [data-kind="variable"], [data-kind="label"]) { --kind: var(--cr-kind-data); }
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
export type BlockKind
|
export type BlockKind
|
||||||
= | 'import' | 'function' | 'class' | 'enum' | 'exception' | 'type' | 'field' | 'const' | 'variable'
|
= | 'import' | 'function' | 'class' | 'enum' | 'exception' | 'type' | 'field' | 'const' | 'variable'
|
||||||
|
| 'trait' | 'impl'
|
||||||
| 'if' | 'else' | 'loop' | 'try' | 'catch' | 'finally' | 'return' | 'statement' | 'comment'
|
| 'if' | 'else' | 'loop' | 'try' | 'catch' | 'finally' | 'return' | 'statement' | 'comment'
|
||||||
| 'interpreter' | 'options' | 'case' | 'command' | 'pipeline'
|
| 'interpreter' | 'options' | 'case' | 'command' | 'pipeline'
|
||||||
| 'directive' | 'stage' | 'run' | 'copy' | 'setting' | 'label' | 'health' | 'start'
|
| 'directive' | 'stage' | 'run' | 'copy' | 'setting' | 'label' | 'health' | 'start'
|
||||||
|
|||||||
Reference in new issue
Block a user