import type { Tree } from '@vscode/tree-sitter-wasm' import type { BlockAttributes, BlockConcept, BlockKind, CodeBlock, DisplayName, EmbeddedCode, EmbeddedReading, Inline, KindBadge, LanguageAdapter, LibraryRule, LocalSymbol, Markdown, Node, PreviewTable, PreviewText, PropertyRow, RuleCard, RuleContext, Segment, SourceDocument, SourceRange, SourceSpan, StepBlocks, TextBlock, TokenRole, Translator } from './contract' import type { TreeSitterRuntime } from './runtime' import type { ImportedName } from './symbolLinks' import { blockAttributes } from './attributes' import { KIND_ICONS } from './contract' import { commonIndent, splitLines } from './editText' import { explanationSpan, isCommentBlock } from './languages/shared' import { activeRules, claimBlock, importsIn } from './libraryRules' import { BlockAnchors } from './qa/anchor' import { importedNames } from './symbolLinks' interface BlockViewBase { /** 在树里的位置,例如 '6.1.0'。 */ id: string /** 块的位置记号(见 BlockAnchors):问答挂在它上面,文件别处改了照样认得回来。 */ anchor: string /** BlockKind,或库规则卡片的种类 id(RuleCard.kind,带点,例如 sqlalchemy.table)。 */ kind: string /** * 编辑范围的起止行,0 起:块在源码里的完整范围,头部连同各子孙块、结尾(见 blockSpans);库规则认领的块是原块整条语句。 * 块编辑时它的子孙都在编辑框里,webview 不画它们;点「编辑」时和它重叠的别的块退出编辑,同一行不会同时在两处能改。 */ startLine: number endLine: number /** 块自己那部分(头部)的最后一行:复合语句到主体开始之前,库规则认领的块是整条语句。问 AI 时贴外层块的开头用。 */ headerEndLine: number /** 编辑范围的整行源码,去掉共同缩进。 */ code: string /** 源码着色(拼起来等于 code)。 */ codeTokens: Segment[] /** 块自己的名字(见 ownName)在源码里的范围:跳到的定义就是它时闪整块,不闪属性表里的某一行。 */ defines?: SourceRange /** 筛选属性(含 kind),见 blockAttributes;库规则认领的块叠上规则给的,kind 是规则的种类 id。 */ attributes: BlockAttributes /** * 这块认出来的概念,从左到右:语言的种类(再加语言插件另认的),库规则的卡片再加规则的种类和规则另认的。 * 同一个 id 只留第一个。项目入口那一块的第一个换成入口(见 entries/entryMarks.ts)。webview 按这个画图标,不自己用种类查。 */ concepts: BlockConcept[] /** 「重写注释」写到哪(见 CommentTarget);不能写注释的块(嵌着的代码里的、语言没给出新写的办法又还没有注释的)没有。 */ commentTarget?: CommentTarget /** 库规则的卡片才有:筛选栏里这个种类的名字,卡片边框的颜色(RuleCard.badge)。图标在 concepts 里。预览同类块一样默认收起它的子块。 */ badge?: KindBadge /** 库规则挂在父卡片子行上的子块才有(见 RuleSlot):展开时画在父卡片属性表第 row 行下面,不排在父卡片的子块里。 */ slot?: BlockSlot children: BlockView[] } /** 子块挂在父卡片的哪儿:属性表第 row 行的第 item 个子行,点那个子行的第一格才在这一行下面展开(见 RuleSlot)。 */ export interface BlockSlot { row: number item: number } /** * 「重写注释」写到哪,行号 0 起。 * replace 换掉这几行:块的说明(docstring、JSDoc、`///`),没有说明时是块前面的普通注释,文件开头的说明卡片是它自己。 * insert 是都没有,新写一段插在 line 这一行上面。shell 是带定界符的空壳(正文占位 `_`),缩进同这一行。 * breakAt 是主体和声明写在同一行:从这一行的这一列拆开,说明插进主体里。 */ export type CommentTarget = | { kind: 'replace', startLine: number, endLine: number } | { kind: 'insert', line: number, shell: string, breakAt?: number } /** * 发给 webview 的一块(可序列化):函数、类、枚举、异常、导入、常量、变量块和库规则的卡片预览时是属性表,文件开头的说明是 Markdown,其余是一行译文。 * 一行译文下面依次是:跨几行写的调用摊开各个实参的表(TextTranslation.table),块的说明(TextBlock.doc,翻译插件给的 Markdown 原文), * 几行属性(rows:「注释」一行)。 */ export type BlockView = BlockViewBase & ({ translation: PreviewText, table?: PreviewTable, doc?: Markdown, rows?: PropertyRow[] } | { properties: PropertyRow[] }) interface Line { start: number, end: number } /** 每行在源码里的起止下标(end 不含换行符)。 */ function lineRanges(source: string): Line[] { const lines: Line[] = [] let start = 0 for (const text of splitLines(source)) { lines.push({ start, end: start + text.length }) start += text.length + (source[start + text.length] === '\r' ? 2 : 1) } return lines } function leavesIn(node: Node, from: number, to: number, out: Node[]): void { if (node.endIndex <= from || node.startIndex >= to || node.startIndex === node.endIndex) return if (node.childCount === 0) { out.push(node) return } for (const child of node.children) { if (child) leavesIn(child, from, to, out) } } /** 着色的一层:一棵语法树,和给它的叶子定词性的语言插件。嵌着的代码的树叠在外层的树上面,盖住的地方按它着色。 */ interface ColorLayer { root: Node tokenRole: (leaf: Node) => TokenRole } /** 源码每个字符归哪个叶子(-1 是不归哪个叶子的空白),和各叶子的词性:一份源码算一次,各块按行取。 */ interface Coloring { owner: Int32Array roles: TokenRole[] } /** 各着色层依次铺上去,后面的层(嵌着的代码)盖住前面的。 */ function coloring(layers: readonly ColorLayer[], source: string): Coloring { const owner = new Int32Array(source.length).fill(-1) const roles: TokenRole[] = [] for (const { root, tokenRole } of layers) { const leaves: Node[] = [] leavesIn(root, 0, source.length, leaves) for (const leaf of leaves) { roles.push(tokenRole(leaf)) owner.fill(roles.length - 1, leaf.startIndex, Math.min(leaf.endIndex, source.length)) } } return { owner, roles } } /** 按语法树叶子给 [startRow, endRow] 这几行着色:每个有词性的叶子一段,空白、标点等 plain 的连成一段。 */ function colorLines({ owner, roles }: Coloring, source: string, lines: Line[], startRow: number, endRow: number, indent: number): Segment[] { const segments: Segment[] = [] let current = -2 const put = (char: string, who: number): void => { const prev = segments.at(-1) const role = who < 0 ? 'plain' : roles[who] if (prev && (who === current || (role === 'plain' && prev.role === 'plain'))) prev.text += char else segments.push({ text: char, role }) current = who } for (let row = startRow; row <= endRow; row++) { const line = lines[row] if (row > startRow) put('\n', owner[line.start - 1]) for (let at = line.start + Math.min(indent, line.end - line.start); at < line.end; at++) put(source[at], owner[at]) } return segments } /** * 这块本身就是哪个名字的定义:函数名、类名,只声明一个变量(或一个解构模式)时的变量名。 * 导入、一条语句声明几个变量时没有,它们的名字各是属性表里的一行(PropertyRow.defines)。 */ function ownName(block: CodeBlock): Node | undefined { switch (block.kind) { case 'function': case 'class': case 'enum': case 'exception': case 'module': return block.facts.name case 'const': case 'variable': return block.facts.declarators.length === 1 ? block.facts.declarators[0].name : undefined case 'stage': return block.facts.name default: return undefined } } /** 解析好的一段嵌着的代码(见 EmbeddedCode):它那种语言的语法树、源码 / 语言插件、翻译插件,分好的几步。 */ export interface EmbeddedPart { code: EmbeddedCode root: Node document: SourceDocument translator: Translator | undefined steps: StepBlocks } /** 一份源码里嵌着的各段代码,按 EmbeddedCode.node 的起始下标找。 */ export type EmbeddedParts = ReadonlyMap /** 嵌着的代码读成的样子(SourceDocument.embedded):各步交给那种语言的翻译插件翻成一行,注释不算一步。 */ function embeddedReading({ steps, document, translator }: EmbeddedPart): EmbeddedReading { const read = (block: CodeBlock): { text: Inline[], table?: PreviewTable } => { if (!translator) return { text: [{ text: block.node.text, role: 'plain' }] } if ('facts' in block) return { text: translator.expression(block.node, document) } const { text, table } = translator.translate(block, document) return { text: 'markdown' in text ? [{ text: text.markdown, role: 'comment' }] : text, ...(table && { table }) } } const [only] = steps.blocks return { steps: steps.blocks.filter(block => block.kind !== 'comment').map(read), chained: steps.chained, whole: steps.blocks.length === 1 && !!only, } } /** * 用哪些库规则(某 languageId 注册的,按注册顺序;这份源码导入了规则关心的库才启用),规则出错时怎么记, * 规则查名字的定义时交出眼下知道的(RuleContext.lookup,见 ruleLookups.ts);没给时总是没查到。 */ export interface RuleRun { rules: readonly LibraryRule[] report: (message: string) => void lookup?: RuleContext['lookup'] } const NO_RULES: RuleRun = { rules: [], report: () => {} } /** 源码里一段的结尾:到哪个字符为止(不含),那个字符在第几行。 */ type SpanEnd = Pick function nodeEnd(node: Node): SpanEnd { return { endIndex: node.endIndex, endRow: node.endPosition.row } } function later(a: SpanEnd, b: SpanEnd): SpanEnd { return b.endIndex > a.endIndex ? b : a } /** 讲这块的注释(BlockShape.comments)里最靠后的结尾,和 end 比取靠后的:这一层最后一块代码后面的注释挂在它上面。 */ function commentsEnd(block: CodeBlock, end: SpanEnd): SpanEnd { return (block.comments ?? []).reduce((acc, comment) => later(acc, comment.header), end) } /** 块的完整范围从哪起:头部和讲它的注释里最靠前的那个开头(注释写在块上面)。 */ function blockStart(block: CodeBlock): Pick { const first = block.comments?.[0]?.header return first && first.startIndex < block.header.startIndex ? first : block.header } /** 各子孙块的头部、节点、讲它的注释里最靠后的结尾,和 end 比取靠后的:子孙一般写在块的节点里,Dockerfile 阶段的指令写在 FROM 后面。 */ function descendantsEnd(blocks: readonly CodeBlock[], end: SpanEnd): SpanEnd { return blocks.reduce((acc, child) => descendantsEnd(child.children, commentsEnd(child, later(later(acc, child.header), nodeEnd(child.node)))), end) } /** index 之前最后一个非空白字符的结尾,不早于头部开头。 */ function lastCharBefore(source: string, header: SourceSpan, index: number): SpanEnd { let end = index while (end > header.startIndex && /\s/.test(source[end - 1])) end-- return { endIndex: end, endRow: header.startRow + splitLines(source.slice(header.startIndex, end)).length - 1 } } /** 一块在源码里的完整范围(whole,编辑范围),和不算讲它的注释时这条语句的范围(statement)。 */ interface BlockSpan { whole: SourceSpan statement: SourceSpan } /** * 一串兄弟块各自在源码里的范围:语句从头部开头,到头部、所在语句、各子孙块里最靠后的那个字符;完整范围再连同讲它的注释。 * 语言插件把 else、catch 这类子句展平成紧跟着的兄弟块,它们的节点都落在打头那块的节点(整条 if / try)里: * 这条语句按各块的开头切开,每块到下一块开头(连同讲它的注释)之前最后一个非空白字符为止,最后一块到语句结尾(Shell 的 fi 归它)。 */ function blockSpans(list: readonly CodeBlock[], source: string): BlockSpan[] { let statement: Node | undefined return list.map((block, i) => { if (!statement || block.node.startIndex < statement.startIndex || block.node.endIndex > statement.endIndex) statement = block.node const next = list[i + 1] const nextStart = next && Math.min(blockStart(next).startIndex, next.node.startIndex) const until = nextStart !== undefined && nextStart < statement.endIndex ? lastCharBefore(source, block.header, nextStart) : nodeEnd(statement) const end = descendantsEnd(block.children, later(block.header, until)) const wholeEnd = commentsEnd(block, end) const start = blockStart(block) return { whole: { startIndex: start.startIndex, startRow: start.startRow, endIndex: wholeEnd.endIndex, endRow: wholeEnd.endRow }, statement: { startIndex: block.header.startIndex, startRow: block.header.startRow, endIndex: end.endIndex, endRow: end.endRow }, } }) } /** * 「注释」一行插进属性表:排在第一行(名称这类讲它是谁的一行)后面,有说明(值是 Markdown 的一行)时排在说明后面。 */ function withCommentRow(rows: readonly PropertyRow[], row: PropertyRow): PropertyRow[] { const doc = rows.findIndex(item => 'markdown' in item.value) const at = doc >= 0 ? doc + 1 : Math.min(1, rows.length) return [...rows.slice(0, at), row, ...rows.slice(at)] } /** * 代码块树 → 视图数据:块的完整范围整行源码去共同缩进、按语法树着色,调用翻译插件。 * 每块先问启用的库规则,认领了就换成规则给的替身(见 RuleBlock):规则的卡片,或认成的属性表块(交给翻译插件排,同语言插件报的这种块), * 并进去的子块不再单独成块。讲块的注释(BlockShape.comments)排成卡片上的「注释」一行(见 withCommentRow)。 */ export function toBlockViews( blocks: CodeBlock[], document: SourceDocument, translator: Translator | undefined, rules: RuleRun = NO_RULES, embedded: EmbeddedParts = new Map(), ): BlockView[] { const { source } = document const lines = lineRanges(source) const texts = splitLines(source) const outer = blocks[0]?.node.tree.rootNode const colors = coloring([ ...(outer ? [{ root: outer, tokenRole: document.language.tokenRole }] : []), ...[...embedded.values()].map(part => ({ root: part.root, tokenRole: part.document.language.tokenRole })), ], source) /** 一段占的整行源码去共同缩进,和它的着色(嵌着的代码按它那种语言着色)。 */ const sourceRows = ({ startRow, endRow }: SourceSpan): Pick => { const rows = texts.slice(startRow, endRow + 1) const indent = commonIndent(rows).length return { startLine: startRow, endLine: endRow, code: rows.map(row => row.slice(Math.min(indent, row.length))).join('\n'), codeTokens: colorLines(colors, source, lines, startRow, endRow, indent), } } /** 一段占的整行源码:没名字的块按它认(见 BlockAnchors.place)。 */ const rowsText = ({ startRow, endRow }: SourceSpan): string => texts.slice(startRow, endRow + 1).join('\n') /** 按源码先后逐块给位置记号:先序遍历,块的记号在它的子块之前要 */ const anchors = new BlockAnchors() /** * 一种语言的块怎么转:用这种语言的源码、翻译插件和库规则(嵌着的代码没有库规则)。 * 转的是一串兄弟块:各块的完整范围(编辑范围)按这一串算(见 blockSpans),id 是 prefix 接上它在这一串里的位置(从 from 数起)。 * writable 为假时(嵌着的代码)块不能写注释:新写的行注释会写进外层语言的那几行里。 */ function layer(layerBlocks: readonly CodeBlock[], layerDocument: SourceDocument, layerTranslator: Translator | undefined, layerRules: RuleRun, writable: boolean): (list: readonly CodeBlock[], prefix: string, scope: string, from?: number) => BlockView[] { const imports = importsIn(layerBlocks) const active = activeRules(layerRules.rules, imports) const context: RuleContext = { document: layerDocument, imports, ...(layerTranslator && { displayLanguage: layerTranslator.displayLanguage }), translate: node => layerTranslator ? layerTranslator.expression(node, layerDocument) : [{ text: node.text, role: 'plain' }], doc: node => layerTranslator?.doc(node, layerDocument), lookup: layerRules.lookup ?? (() => undefined), } /** 一行译文,连着摊开实参的表、块的说明(有才有)。 */ const textView = (block: TextBlock): { translation: PreviewText, table?: PreviewTable, doc?: Markdown } => { if (!layerTranslator) return { translation: [] } const { text, table } = layerTranslator.translate(block, layerDocument) const doc = block.doc && layerTranslator.doc(block.doc, layerDocument)?.body return { translation: text, ...(table && { table }), ...(doc && { doc }) } } /** 块执行的嵌着的代码分成的几步:只有一步时是它的子块,几步时是各步,交给那种语言转(见 TextBlock.embedded)。 */ const embeddedChildren = (block: CodeBlock, id: string, scope: string, from: number): BlockView[] => { const part = 'embedded' in block && block.embedded ? embedded.get(block.embedded.node.startIndex) : undefined if (!part) return [] const steps = part.steps.blocks const [only] = steps return layer(steps, part.document, part.translator, NO_RULES, false)(steps.length === 1 && only ? only.children : steps, `${id}.`, scope, from) } /** * 这块的概念,从左到右,同一个 id 只留第一个:语言的种类(图标、名字用内置种类和翻译插件起的种类名), * 语言插件另认的,库规则的卡片再加规则的种类和规则另认的。card 不填就是没有规则卡片(认成现成块的也算)。 */ function blockConcepts(block: CodeBlock, card?: RuleCard): BlockConcept[] { const seen = new Set() const concepts: BlockConcept[] = [] const add = (concept: BlockConcept): void => { if (seen.has(concept.id)) return seen.add(concept.id) concepts.push(concept) } add({ id: block.kind, label: layerTranslator?.attributeNames?.values.kind?.[block.kind] ?? block.kind, icon: KIND_ICONS[block.kind], }) for (const concept of layerDocument.language.concepts?.(block) ?? []) add(concept) if (card) { add({ id: card.kind, label: card.badge.label, icon: card.badge.icon, color: card.badge.color }) for (const concept of card.concepts ?? []) add(concept) } return concepts } /** 「注释」一行:讲这块的几段注释(BlockShape.comments)各读成一段 Markdown,空行隔开;名字是翻译插件给种类 comment 起的。没有注释、没有翻译插件时没有。 */ function commentRow(block: CodeBlock): PropertyRow | undefined { if (!layerTranslator || !block.comments) return undefined const parts = block.comments.flatMap((comment) => { const { text } = layerTranslator.translate(comment, layerDocument) return 'markdown' in text && text.markdown !== '' ? [text.markdown] : [] }) const label = layerTranslator.attributeNames?.values.kind?.comment ?? 'comment' return parts.length > 0 ? { label, value: { markdown: parts.join('\n\n') } } : undefined } /** 属性表卡片的属性表连同「注释」一行(见 withCommentRow)。 */ function cardRows(block: CodeBlock, rows: PropertyRow[]): { properties: PropertyRow[] } { const row = commentRow(block) return { properties: row ? withCommentRow(rows, row) : rows } } /** 「重写注释」写到哪(见 CommentTarget):块前面的普通注释换掉,没有就新写在块上面。 */ /** 「重写注释」写到哪:有说明就换说明,否则换块前面的普通注释,都没有就按这门语言的说明新写。 */ function commentTarget(block: CodeBlock): Pick { if (!writable) return {} if (isCommentBlock(block)) return { commentTarget: { kind: 'replace', startLine: block.header.startRow, endLine: block.header.endRow } } const leading = (block.comments ?? []).filter(comment => comment.header.startIndex < block.header.startIndex) const doc = explanationSpan(block) const covered = doc !== undefined && leading.some(comment => comment.header.startIndex <= doc.startIndex && comment.header.endIndex >= doc.endIndex) if (doc && !covered) return { commentTarget: { kind: 'replace', startLine: doc.startRow, endLine: doc.endRow } } const [first] = leading const last = leading.at(-1) if (first && last) return { commentTarget: { kind: 'replace', startLine: first.header.startRow, endLine: last.header.endRow } } const fresh = layerDocument.language.freshDoc?.(block) if (fresh) return { commentTarget: { kind: 'insert', line: fresh.line, shell: fresh.shell, ...(fresh.breakAt !== undefined && { breakAt: fresh.breakAt }) } } const marker = layerDocument.language.lineComment return marker ? { commentTarget: { kind: 'insert', line: block.header.startRow, shell: `${marker} _` } } : {} } /** 一行译文的卡片:译文、实参表、说明,有注释时下面再排「注释」一行。 */ function textCard(block: TextBlock): ReturnType & { rows?: PropertyRow[] } { const row = commentRow(block) return { ...textView(block), ...(row && { rows: [row] }) } } /** 块照它自己的样子(语言插件报的,或库规则认成的属性表块):编辑范围是 span,头部是块自己那部分,子块照常交给各规则看。scope 见 BlockAnchors.place */ function plain(block: CodeBlock, span: SourceSpan, id: string, scope: string): BlockView { const name = ownName(block) const { anchor, inner } = anchors.place(scope, block.kind, name?.text, rowsText(block.header)) return { id, anchor, kind: block.kind, ...sourceRows(span), headerEndLine: block.header.endRow, ...(name && { defines: { startIndex: name.startIndex, endIndex: name.endIndex } }), attributes: blockAttributes(block), concepts: blockConcepts(block), ...commentTarget(block), children: [ ...list(block.children, `${id}.`, inner), ...embeddedChildren(block, id, inner, block.children.length), ], ...('facts' in block ? cardRows(block, layerTranslator ? layerTranslator.describe(block, layerDocument) : []) : textCard(block)), } } /** * 块先问启用的库规则,没认领就照它自己的样子。认领了的:规则讲的是整条语句,头部就是整个范围; * 规则没并进来的子块(方法、别的成员)不管写在哪,都照源码顺序留作它的子块。规则略去的块(讲在了别的卡片里)不出块。 */ function build(block: CodeBlock, { whole: span, statement }: BlockSpan, id: string, scope: string): BlockView | undefined { const claim = active.length > 0 ? claimBlock(block, active, context, layerRules.report) : undefined if (!claim) return plain(block, span, id, scope) const replacement = claim.block if ('omit' in replacement) return undefined const kept = block.children.filter(child => !replacement.absorbs.includes(child)) if ('as' in replacement) { const { attributes, comments } = block return plain({ ...replacement.as, node: block.node, header: statement, children: kept, ...(attributes && { attributes }), ...(comments && { comments }) }, span, id, scope) } const { kind, badge, rows, defines, attributes } = replacement const slots = new Map((replacement.slots ?? []).map(({ block: child, row, item }): [CodeBlock, BlockSlot] => [child, { row, item }])) const { anchor, inner } = anchors.place(scope, kind, defines && source.slice(defines.startIndex, defines.endIndex), rowsText(statement)) return { id, anchor, kind, ...sourceRows(span), headerEndLine: statement.endRow, ...(defines && { defines: { startIndex: defines.startIndex, endIndex: defines.endIndex } }), attributes: { ...blockAttributes(block), ...attributes, kind }, concepts: blockConcepts(block, replacement), ...commentTarget(block), badge, children: list(kept, `${id}.`, inner, 0, slots), ...cardRows(block, rows), } } /** 一串兄弟块转成视图数据;略去的块不占位置,id 按出了块的往下数。slots 是父卡片挂在它属性表子行上的那几块(见 BlockSlot)。 */ function list(siblings: readonly CodeBlock[], prefix: string, scope: string, from = 0, slots?: ReadonlyMap): BlockView[] { const spans = blockSpans(siblings, source) const views: BlockView[] = [] siblings.forEach((block, i) => { const view = build(block, spans[i], `${prefix}${from + views.length}`, scope) const slot = slots?.get(block) if (view) views.push(slot ? { ...view, slot } : view) }) return views } return list } return layer(blocks, document, translator, rules, true)(blocks, '', '') } /** * 一份源码的视图数据,和它里面起了中文名的定义(预览找用到它们的各处要用,见 displayNames.ts)、 * 语言插件自己认得出的名字(LanguageAdapter.symbols,语言扩展查不到定义、各处出现时用)、 * 各条导入引进来的名字(注释里不带路径的引用按它找导入的定义,见 symbolLinks.ts)。 */ export interface DocumentViews { blocks: BlockView[] displayNames: DisplayName[] symbols: LocalSymbol[] imports: ImportedName[] } /** * 排视图数据时宿主另外交给规则、翻译插件的:用哪些库规则(RuleRun),调用的名字眼下查到定义成哪种块(SourceDocument.calleeKind), * 命令名的命令规则(SourceDocument.commandRule)。 */ export interface DocumentExtras { fileName?: SourceDocument['fileName'] rules?: RuleRun calleeKind?: SourceDocument['calleeKind'] commandRule?: SourceDocument['commandRule'] /** 嵌着的代码的那种语言用哪个语言插件、翻译插件、命令规则(见 EmbeddedCode);没有的不读,照抄。 */ embedded?: (languageId: string) => EmbeddedLanguage | undefined } /** 读嵌着的代码用的:那种语言的语言插件、选中的翻译插件、命令名的命令规则。 */ export interface EmbeddedLanguage { adapter: LanguageAdapter translator: Translator | undefined commandRule?: SourceDocument['commandRule'] } /** 解析一份源码并转成视图数据(中文名记号交给翻译插件去掉);语法树用完即释放。 */ export async function readBlockViews( runtime: Pick, adapter: LanguageAdapter, translator: Translator | undefined, languageId: string, source: string, extras: DocumentExtras = {}, ): Promise { const tree = await runtime.parse(adapter, languageId, source) const trees: Tree[] = [tree] try { const parts = new Map() for (const code of adapter.embedded?.(tree.rootNode, source, languageId) ?? []) { const language = extras.embedded?.(code.languageId) if (!language) continue const inner = await runtime.parse(language.adapter, code.languageId, source, code.ranges) trees.push(inner) const root = inner.rootNode const steps = language.adapter.steps?.(root, source, code.languageId) ?? { blocks: language.adapter.toBlocks(root, source, code.languageId), chained: false } const document: SourceDocument = { source, language: language.adapter, markers: [], ...(language.commandRule && { commandRule: language.commandRule }) } parts.set(code.node.startIndex, { code, root, document, translator: language.translator, steps }) } const { names, markers } = adapter.displayNames?.find(tree.rootNode, source, languageId) ?? { names: [], markers: [] } const { fileName, calleeKind, commandRule } = extras const readings = new Map() const embedded = (node: Node): EmbeddedReading | undefined => { const part = parts.get(node.startIndex) if (!part) return undefined const reading = readings.get(node.startIndex) ?? embeddedReading(part) readings.set(node.startIndex, reading) return reading } const document: SourceDocument = { source, language: adapter, markers, ...(fileName && { fileName }), ...(calleeKind && { calleeKind }), ...(commandRule && { commandRule }), ...(parts.size > 0 && { embedded }), } const codeBlocks = adapter.toBlocks(tree.rootNode, source, languageId) const blocks = toBlockViews(codeBlocks, document, translator, extras.rules, parts) const symbols = [ ...adapter.symbols?.(tree.rootNode, source, languageId) ?? [], ...[...parts.values()].flatMap(part => part.document.language.symbols?.(part.root, source, part.code.languageId) ?? []), ] return { blocks, displayNames: names, symbols, imports: importedNames(importsIn(codeBlocks)) } } finally { for (const parsed of trees) parsed.delete() } } /** 块树里自己的名字(见 ownName)盖住 offset 的最里层那块的种类;offset 不在哪块的名字上时没有。 */ export function blockKindAt(blocks: readonly CodeBlock[], offset: number): BlockKind | undefined { for (const block of blocks) { const inner = blockKindAt(block.children, offset) if (inner) return inner const name = ownName(block) if (name && name.startIndex <= offset && offset < name.endIndex) return block.kind } return undefined } /** 解析一份源码,offset 处的名字定义的是哪种块(见 blockKindAt);语法树用完即释放。 */ export async function readDefinitionKind( runtime: Pick, adapter: LanguageAdapter, languageId: string, source: string, offset: number, ): Promise { const tree = await runtime.parse(adapter, languageId, source) try { return blockKindAt(adapter.toBlocks(tree.rootNode, source, languageId), offset) } finally { tree.delete() } }