✨ feat(vscode): 代码阅读的注释和函数说明按 Markdown 渲染

This commit is contained in:
cruldra committed 2026-09-25 14:28:23 +08:00
1 parent aae9012f14
commit a53c2a6aac
18 files changed
+394 -66

No files matched your search

+18 -17
View File
@@ -1,4 +1,4 @@
import type { Access, Defines, FactBlock, FunctionFacts, ImportFacts, ImportItem, Node, PropertyRow, Segment, SourceSpan, TextBlock, TokenRole, Translator, VariableBlock } from '../contract'
import type { Access, Defines, FactBlock, FunctionFacts, ImportFacts, ImportItem, Markdown, Node, PreviewText, PropertyRow, Segment, SourceSpan, TextBlock, TokenRole, Translator, VariableBlock } from '../contract'
import { commonIndent, splitLines } from '../editText'
/** 按所在语法节点区分译法的关键字,例如 Python 的 else:if 后「否则」,for / while 后「未中断时」。 */
@@ -102,13 +102,15 @@ function oneLine(text: string): string {
}
/**
* 说明文字排版:去掉行尾空白、首尾空行和共同缩进,保留换行(段落之间的空行也留着)。
* 第一行紧跟在引号 / 注释符号后面,本来就没有缩进,不参与算共同缩进(同 PEP 257 的做法)。
* 注释的 Markdown 原文:各个注释节点去掉注释符号后(texts)拼起来,去掉行尾空白、首尾空行和共同缩进,保留换行(段落之间的空行也留着)。
* 只有一个节点(块注释、docstring)时第一行紧跟在引号 / 注释符号后面,本来就没有缩进,不参与算共同缩进(同 PEP 257 的做法);
* 几条行注释连成的一块每行地位相同,第一行也算,缩进四格的代码块才留得住。
*/
function docText(raw: string): string {
const [first = '', ...rest] = splitLines(raw).map(line => line.trimEnd())
const indent = commonIndent(rest).length
return [first.trim(), ...rest.map(line => line.slice(indent))].join('\n').replace(/^\n+|\n+$/g, '')
function docText(texts: string[]): string {
const lines = splitLines(texts.join('\n')).map(line => line.trimEnd())
const quoted = texts.length === 1
const indent = commonIndent(quoted ? lines.slice(1) : lines).length
return lines.map((line, i) => quoted && i === 0 ? line.trim() : line.slice(indent)).join('\n').replace(/^\n+|\n+$/g, '')
}
/** 模板槽位引用的子节点;注释是语法之外的节点,不占具名子节点序号。 */
@@ -261,15 +263,14 @@ export function createZhTemplateTranslator(table: ZhTable): Translator {
return segments
}
function translateComment(block: TextBlock): Segment[] {
function translateComment(block: TextBlock): Markdown {
const { startIndex, endIndex } = block.header
const root = block.node.tree.rootNode
const nodes = block.node.type === 'comment' ? commentsWithin(root.descendantForIndex(startIndex, endIndex) ?? root, block.header) : [block.node]
const text = nodes.map(table.commentText).join('\n').split('\n').map(line => line.trim()).filter(Boolean).join(' ')
return text ? [{ text, role: 'comment' }] : []
return { markdown: docText(nodes.map(table.commentText)) }
}
function translate(block: TextBlock, source: string): Segment[] {
function translate(block: TextBlock, source: string): PreviewText {
if (block.kind === 'comment')
return translateComment(block)
const tokens = tokensIn(block.node, block.header, source)
@@ -299,8 +300,8 @@ export function createZhTemplateTranslator(table: ZhTable): Translator {
module: node => stringNodes.has(node.type) ? [segment(node.text, 'string', offsetOf(node))] : verbatim(node, source, 'type'),
name: (node, role) => [segment(node.text, role, offsetOf(node))],
doc: (node) => {
const text = docText(table.commentText(node))
return text ? [{ text, role: 'comment' }] : []
const markdown = docText([table.commentText(node)])
return markdown ? { markdown } : undefined
},
}
switch (block.kind) {
@@ -362,8 +363,8 @@ interface Phrases {
module: (node: Node) => Segment[]
/** 源码里的一个名字原样照抄、按 role 着色(函数名称)。 */
name: (node: Node, role: TokenRole) => Segment[]
/** 函数的说明(JSDoc、docstring):去掉注释符号、引号和共同缩进,保留换行,按注释着色;没有文字时是空的。 */
doc: (node: Node) => Segment[]
/** 函数的说明(JSDoc、docstring):去掉注释符号、引号和共同缩进的 Markdown 原文;没有文字时没有。 */
doc: (node: Node) => Markdown | undefined
}
const ACCESS_ZH: Record<Access, string> = { public: '公共', private: '私有', protected: '受保护' }
@@ -416,10 +417,10 @@ function aliased(item: Segment[], alias: Node | undefined, say: Phrases): Segmen
/** 函数:名称 / 访问级别 / 异步 / 装饰器 / 输入参数(每个参数一行:名称、类型、默认值)/ 输出参数。 */
function functionRows(facts: FunctionFacts, say: Phrases): PropertyRow[] {
const decorators = facts.decorators.map(say.verbatim)
const doc = facts.doc ? say.doc(facts.doc) : []
const doc = facts.doc && say.doc(facts.doc)
return [
{ label: '名称', value: facts.name ? say.name(facts.name, 'function') : [hint('匿名')] },
...(doc.length > 0 ? [{ label: '说明', value: doc }] : []),
...(doc ? [{ label: '说明', value: doc }] : []),
...accessRows(facts.access),
{ label: '异步', value: yesNo(facts.async) },
...(decorators.length > 0 ? [{ label: '装饰器', value: joined(decorators, '\n') }] : []),