✨ feat(vscode): 代码阅读的解析与模板直译核心

This commit is contained in:
cruldra committed 2026-09-24 21:16:21 +08:00
1 parent af56985d9a
commit 4d34b8c715
24 files changed
+2294

No files matched your search

+122
View File
@@ -0,0 +1,122 @@
import type { BlockKind, CodeBlock, Node, SourceSpan } from '../contract'
/** 语言插件共用的建树工具:取子节点、算头部、把一串语句切成块。 */
export function named(node: Node | null | undefined): Node[] {
return node ? node.namedChildren.filter((child): child is Node => child !== null) : []
}
export function children(node: Node): Node[] {
return node.children.filter((child): child is Node => child !== null)
}
export function nodeSpan(node: Node): SourceSpan {
return {
startIndex: node.startIndex,
endIndex: node.endIndex,
startRow: node.startPosition.row,
endRow: node.endPosition.row,
}
}
export function leafBlock(kind: BlockKind, node: Node): CodeBlock {
return { kind, node, header: nodeSpan(node), children: [] }
}
/** 头部往前扩到 from(装饰器)。 */
export function extendHeader(block: CodeBlock, from: Node): CodeBlock {
return { ...block, header: { ...block.header, startIndex: from.startIndex, startRow: from.startPosition.row } }
}
function lastLeafBefore(node: Node, limit: number): Node | undefined {
for (let i = node.childCount - 1; i >= 0; i--) {
const child = node.child(i)
if (!child || child.startIndex >= limit || child.type === 'comment' || child.startIndex === child.endIndex)
continue
if (child.childCount === 0)
return child
const found = lastLeafBefore(child, limit)
if (found)
return found
}
return undefined
}
/** 复合语句的头部:从 outer 开头到主体开始之前最后一个非注释的词为止。 */
export function headerSpan(outer: Node, bodyStart: number): SourceSpan {
const last = lastLeafBefore(outer, bodyStart)
if (!last)
return nodeSpan(outer)
return {
startIndex: outer.startIndex,
endIndex: last.endIndex,
startRow: outer.startPosition.row,
endRow: last.endPosition.row,
}
}
/** node 里完全落在 [from, to) 之间的注释。 */
function commentsIn(node: Node, from: number, to: number): Node[] {
if (node.endIndex <= from || node.startIndex >= to)
return []
if (node.type === 'comment')
return node.startIndex >= from && node.endIndex <= to ? [node] : []
return children(node).flatMap(child => commentsIn(child, from, to))
}
export type ListBlocks = (items: Node[], afterRow: number) => CodeBlock[]
/**
* 复合语句块:头部到 bodyStart 之前,子块来自 items。
* 头部和主体之间的注释(例如 Python 冒号后、主体前的注释)也归子块。
*/
export function compoundBlock(kind: BlockKind, outer: Node, bodyStart: number, items: Node[], list: ListBlocks): CodeBlock {
const header = headerSpan(outer, bodyStart)
const between = commentsIn(outer, header.endIndex, bodyStart)
return { kind, node: outer, header, children: list([...between, ...items], header.endRow) }
}
/**
* 一串语句 → 块。注释单独成块,相邻行的行注释合成一块;
* 和前一条语句(或头部)同一行的注释是行尾注释,跟着那一行显示,不单独成块。
* afterRow 是这串语句之前的最后一行(头部结尾行),模块顶层传 -1;
* toBlocks 的 first 表示它是这串里第一条非注释语句(Python 认 docstring 用)。
*/
export function listBlocks(
items: Node[],
afterRow: number,
toBlocks: (item: Node, first: boolean) => CodeBlock[],
isLineComment: (comment: Node) => boolean,
): CodeBlock[] {
const out: CodeBlock[] = []
let lastRow = afterRow
let lineRun: CodeBlock | undefined
let first = true
for (const item of items) {
if (item.type === 'comment') {
if (item.startPosition.row === lastRow)
continue
const line = isLineComment(item)
if (line && lineRun && item.startPosition.row === lineRun.header.endRow + 1) {
lineRun.header = { ...lineRun.header, endIndex: item.endIndex, endRow: item.endPosition.row }
}
else {
const block = leafBlock('comment', item)
out.push(block)
lineRun = line ? block : undefined
}
}
else {
out.push(...toBlocks(item, first))
first = false
lineRun = undefined
}
lastRow = item.endPosition.row
}
return out
}
/** 同一个节点:web-tree-sitter 每次取子节点都会新建对象,只能按位置和类型比。 */
export function sameNode(a: Node | null | undefined, b: Node): boolean {
return !!a && a.startIndex === b.startIndex && a.endIndex === b.endIndex && a.type === b.type
}