✨ feat(vscode): 代码阅读预览里跨几行写的列表按项拆开,列表里整项是调用的带着自己的实参表,一层层能往下套
This commit is contained in:
1 parent
7d6e7592c9
commit
2c490cac2d
22 files changed
+848
-314
No files matched your search
@@ -1,4 +1,4 @@
|
||||
import type { Access, AttributeNames, BaseFacts, BlockKind, CallArgument, CallFacts, ClassFacts, CommandContext, CommandFacts, CommandForm, CommandReading, CommandRule, CommandWord, CommonAttribute, DeclaratorFacts, Defines, DirectiveFacts, Doc, EmbeddedReading, EntryFacts, EnumFacts, ExceptionFacts, FactBlock, FunctionFacts, HealthFacts, ImportFacts, ImportItem, Inline, InstructionFlag, InterpreterFacts, IterationClause, IterationFacts, IterationResult, LanguageAdapter, Markdown, Node, OptionsFacts, ParameterFacts, PositionalParameter, PreviewTable, PropertyItem, PropertyRow, Redirect, Segment, SegmentGroup, SourceDocument, SourceRange, SourceSpan, StageFacts, StartFacts, TextBlock, TextTranslation, TokenRole, Translator, VariableBlock } from '../contract'
|
||||
import type { Access, AttributeNames, BaseFacts, BlockKind, CallArgument, CallFacts, ClassFacts, CommandContext, CommandFacts, CommandForm, CommandReading, CommandRule, CommandWord, CommonAttribute, DeclaratorFacts, Defines, DirectiveFacts, Doc, ElementFacts, EmbeddedReading, EntryFacts, EnumFacts, ExceptionFacts, FactBlock, FunctionFacts, HealthFacts, ImportFacts, ImportItem, Inline, InstructionFlag, InterpreterFacts, IterationClause, IterationFacts, IterationResult, LanguageAdapter, Markdown, Node, OptionsFacts, ParameterFacts, PositionalParameter, PreviewTable, PropertyItem, PropertyRow, Redirect, Segment, SegmentGroup, SourceDocument, SourceRange, SourceSpan, StageFacts, StartFacts, TextBlock, TextTranslation, TokenRole, Translator, VariableBlock } from '../contract'
|
||||
import type { ParamDocs } from '../rules/docRows'
|
||||
import { readDoc } from '../docTags'
|
||||
import { commonIndent, splitLines } from '../editText'
|
||||
@@ -43,8 +43,8 @@ export interface ZhTable {
|
||||
operatorParents: string[]
|
||||
/** 空字面量(没有具名子节点)整个换成一个词:空数组 / 空列表 / 空字典 / 空对象。 */
|
||||
emptyLiterals: Record<string, string>
|
||||
/** 字典 / 对象字面量(语言插件给了 entries 的值)的叫法,按节点类型查:属性表里的值写成「字典 3 项」,各项展开成子行。 */
|
||||
mapLiterals: Record<string, string>
|
||||
/** 写出各项的字面量(语言插件给了各项,见 LanguageAdapter.collection)的叫法,按节点类型查:属性表里拆开的值写成「字典 3 项」「列表 2 项」。 */
|
||||
collections: Record<string, string>
|
||||
/** 整段原样照抄、按字符串着色的节点。 */
|
||||
stringNodes: string[]
|
||||
/** 注释(或 docstring)节点去掉注释符号后的原文,可以多行;text 是节点的源码去掉代码阅读的记号(SourceDocument.markers)以后剩下的。 */
|
||||
@@ -310,7 +310,7 @@ export function createZhTemplateTranslator(table: ZhTable): Translator {
|
||||
/** 节点命中模板时,模板各项换成译词或子节点;不该重排时返回 undefined。 */
|
||||
function templateParts(node: Node, span: SourceSpan): (string | Node)[] | undefined {
|
||||
const template = table.nodeTemplates[node.type]
|
||||
if (!template || node.startIndex < span.startIndex || node.endIndex > span.endIndex)
|
||||
if (!template || !inside(node, span))
|
||||
return undefined
|
||||
const parts = template.map(part => typeof part === 'string' ? part : slotChild(node, part))
|
||||
return parts.every((part): part is string | Node => part != null) ? parts : undefined
|
||||
@@ -340,7 +340,7 @@ export function createZhTemplateTranslator(table: ZhTable): Translator {
|
||||
|
||||
/** 语言插件认出的写在表达式里的遍历;没整个落在要翻的这一段里就不重排、照常翻。 */
|
||||
function iterationIn(node: Node, span: SourceSpan, language: LanguageAdapter): IterationFacts | undefined {
|
||||
if (!language.iteration || node.startIndex < span.startIndex || node.endIndex > span.endIndex)
|
||||
if (!language.iteration || !inside(node, span))
|
||||
return undefined
|
||||
return language.iteration(node)
|
||||
}
|
||||
@@ -414,26 +414,86 @@ export function createZhTemplateTranslator(table: ZhTable): Translator {
|
||||
|
||||
/** 要排成一行、实参摊成表的调用:实参列表跨了几行,整个落在要翻的这一段里,这一段还没排过别的调用。 */
|
||||
function expandedCall(node: Node, span: SourceSpan, slot: TableSlot, { language: { call } }: SourceDocument): CallFacts | undefined {
|
||||
if (slot.table || !call || node.startIndex < span.startIndex || node.endIndex > span.endIndex)
|
||||
if (slot.table || !call || !inside(node, span))
|
||||
return undefined
|
||||
const facts = call(node)
|
||||
return facts && facts.argumentList.startPosition.row !== facts.argumentList.endPosition.row ? facts : undefined
|
||||
}
|
||||
|
||||
/**
|
||||
* node 落在 span 里的那一段翻成一行译文,里面跨几行写的第一个调用排成一行「新建 / 调用 …」,实参摊成表(见 emitCall)。
|
||||
* 一行译文的块、常量 / 变量的值、实参表里每个值各翻各的,各自排自己里面的那个调用。
|
||||
* node 落在 span 里的那一段翻成一行译文,里面跨几行写的第一个调用排成一行「新建 / 调用 …」,实参摊成表(见 emitCall);
|
||||
* 给了 call(node 整个就是这次调用)时不管写成几行都这样排。一行译文的块、属性表里每一格的值各翻各的,各自排自己里面的那个调用。
|
||||
*/
|
||||
function expandedText(node: Node, span: SourceSpan, document: SourceDocument): { text: Inline[], table?: PreviewTable } {
|
||||
function expandedText(node: Node, span: SourceSpan, document: SourceDocument, call?: CallFacts): CellValue {
|
||||
const slot: TableSlot = {}
|
||||
const text = joinTokens(tokensIn(node, span, document, slot))
|
||||
return { text, ...(slot.table && { table: slot.table }) }
|
||||
const out = collector(node, span, document, slot)
|
||||
if (call)
|
||||
emitCall(out, slot, node, call, span)
|
||||
else
|
||||
collect(node, span, out)
|
||||
return { text: joinTokens(out.tokens), ...(slot.table && { table: slot.table }) }
|
||||
}
|
||||
|
||||
/**
|
||||
* 属性表里一格的值(常量 / 变量的值、字典一项的值、列表的一项、实参的值),只翻落在 span 里的部分:
|
||||
* 语言插件给了各项的字面量(LanguageAdapter.collection)写叫法和项数,各项拆成下一层子行(见 entryItem、elementItems)——
|
||||
* 字典 / 对象一直拆(maps 为假时照旧一行:实参的值),列表 / 元组 / 集合 / 数组跨几行写才拆;
|
||||
* 空字面量和别的值是一行译文,里面跨几行写的调用排成「新建 / 调用 …」,实参表跟着这一格(见 expandedText)。
|
||||
*/
|
||||
function cellValue(node: Node, span: SourceSpan, document: SourceDocument, maps = true): CellValue {
|
||||
const collection = document.language.collection?.(node)
|
||||
if (collection?.kind === 'map' && maps && collection.entries.length > 0)
|
||||
return { text: itemCount(node, collection.entries.length), items: collection.entries.map(entry => entryItem(entry, span, document)) }
|
||||
if (collection?.kind === 'sequence' && collection.elements.length > 0 && node.startPosition.row !== node.endPosition.row)
|
||||
return { text: itemCount(node, collection.elements.length), items: elementItems(collection.elements, span, document) }
|
||||
return expandedText(node, span, document)
|
||||
}
|
||||
|
||||
/** 拆开的字面量写叫法和项数(展开的也算一项):「字典 3 项」,叫法按字面量的节点类型查表(ZhTable.collections)。 */
|
||||
function itemCount(literal: Node, size: number): Segment[] {
|
||||
const name = table.collections[literal.type]
|
||||
return [hint(name ? `${name} ${size} 项` : `${size} 项`)]
|
||||
}
|
||||
|
||||
/**
|
||||
* 字典 / 对象的一项是一个子行,两列:键 | 值。键照源码写法(字符串带引号,计算属性名 `[k]` 照常翻),值见 cellValue;
|
||||
* `**x`、`...x` 的键写「展开」、值是展开的表达式,方法的值是它的头部。讲的定义是键(对象的属性名)。
|
||||
*/
|
||||
function entryItem(entry: EntryFacts, span: SourceSpan, document: SourceDocument): PropertyItem {
|
||||
const say = (node: Node, part = span): Inline[] => joinTokens(tokensIn(node, part, document))
|
||||
switch (entry.kind) {
|
||||
case 'pair': {
|
||||
const { text, ...nested } = cellValue(entry.value, span, document)
|
||||
return { cells: [say(entry.key), text], ...nested, ...defining(entry.key) }
|
||||
}
|
||||
case 'spread':
|
||||
return { cells: [[hint('展开')], say(entry.value)] }
|
||||
case 'method':
|
||||
return { cells: [say(entry.name), say(entry.node, clip(entry.header, span))], ...defining(entry.name) }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 列表的各项各是一个子行,两列:第 N 项 | 值(只数不是展开的项),`*x`、`...x` 写「展开」、值是展开的表达式。
|
||||
* 整项是一次调用的不管写成几行都排成「新建 / 调用 …」,实参表跟着这一项,同一个列表里样子一致;别的值见 cellValue。
|
||||
*/
|
||||
function elementItems(elements: ElementFacts[], span: SourceSpan, document: SourceDocument): PropertyItem[] {
|
||||
let position = 0
|
||||
return elements.map((element): PropertyItem => {
|
||||
if (element.kind === 'spread')
|
||||
return { cells: [[hint('展开')], joinTokens(tokensIn(element.value, span, document))] }
|
||||
position++
|
||||
const { value } = element
|
||||
const call = inside(value, span) ? document.language.call?.(value) : undefined
|
||||
const { text, ...nested } = call ? expandedText(value, span, document, call) : cellValue(value, span, document)
|
||||
return { cells: [[hint(`第 ${position} 项`)], text], ...nested }
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 实参表:参数 | 值,每个实参一行,照源码顺序。按名字传的写名字,按位置传的写「第 N 个」(只数按位置传的),
|
||||
* 展开的写「展开」(*args、...args)、「展开关键字」(**kwargs)。值照常翻;值里有跨几行写的调用时同样排成一行,
|
||||
* 它的实参表排在值那一格里(PropertyItem.table),一层层往下套。
|
||||
* 展开的写「展开」(*args、...args)、「展开关键字」(**kwargs)。值见 cellValue(字典 / 对象照旧一行):值里有跨几行写的调用时
|
||||
* 同样排成一行,它的实参表排在值那一格里(PropertyItem.table);值是跨几行写的列表时各项拆在那一格里(PropertyItem.items)。一层层往下套。
|
||||
*/
|
||||
function argumentTable(facts: CallFacts, span: SourceSpan, document: SourceDocument): PreviewTable {
|
||||
let position = 0
|
||||
@@ -451,8 +511,8 @@ export function createZhTemplateTranslator(table: ZhTable): Translator {
|
||||
}
|
||||
}
|
||||
const row = (arg: CallArgument): PropertyItem => {
|
||||
const { text, table } = expandedText(arg.value, span, document)
|
||||
return { cells: [label(arg), text], ...(table && { table }) }
|
||||
const { text, ...nested } = cellValue(arg.value, span, document, false)
|
||||
return { cells: [label(arg), text], ...nested }
|
||||
}
|
||||
return { columns: ARGUMENT_COLUMNS, items: facts.arguments.map(row) }
|
||||
}
|
||||
@@ -492,7 +552,7 @@ export function createZhTemplateTranslator(table: ZhTable): Translator {
|
||||
return {
|
||||
document,
|
||||
say: node => joinTokens(tokensIn(node, span, document)),
|
||||
whole: node => node.startIndex >= span.startIndex && node.endIndex <= span.endIndex,
|
||||
whole: node => inside(node, span),
|
||||
name: (node, role) => segment(node.text, role ?? document.language.tokenRole(node), node.startIndex),
|
||||
command: (node) => {
|
||||
const facts = document.language.command?.(node)
|
||||
@@ -605,9 +665,14 @@ export function createZhTemplateTranslator(table: ZhTable): Translator {
|
||||
}
|
||||
}
|
||||
|
||||
/** 按译文顺序收集 node 落在 span 里的词;给了 slot,跨几行写的调用排成一行、实参摊成表(见 emitCall)。 */
|
||||
/** 翻 node 落在 span 里的那一段用的收集器;给了 slot,跨几行写的调用排成一行、实参摊成表(见 emitCall)。 */
|
||||
function collector(node: Node, span: SourceSpan, document: SourceDocument, slot?: TableSlot): Collector {
|
||||
return { document, root: node, tokens: [], cursor: span.startIndex, gap: false, ...(slot && { slot }) }
|
||||
}
|
||||
|
||||
/** 按译文顺序收集 node 落在 span 里的词;slot 见 collector。 */
|
||||
function tokensIn(node: Node, span: SourceSpan, document: SourceDocument, slot?: TableSlot): Token[] {
|
||||
const out: Collector = { document, root: node, tokens: [], cursor: span.startIndex, gap: false, ...(slot && { slot }) }
|
||||
const out = collector(node, span, document, slot)
|
||||
collect(node, span, out)
|
||||
return out.tokens
|
||||
}
|
||||
@@ -652,10 +717,8 @@ export function createZhTemplateTranslator(table: ZhTable): Translator {
|
||||
if (block.kind === 'comment')
|
||||
return { text: translateComment(block, document) }
|
||||
const group = block.group && table.groups?.[block.node.type]
|
||||
if (block.group && group) {
|
||||
const out: Collector = { document, root: block.node, tokens: [], cursor: block.header.startIndex, gap: false }
|
||||
return group(block.group, readerContext(out, block.header))
|
||||
}
|
||||
if (block.group && group)
|
||||
return group(block.group, readerContext(collector(block.node, block.header, document), block.header))
|
||||
const slot: TableSlot = {}
|
||||
const tokens = tokensIn(block.node, block.header, document, slot)
|
||||
const compound = block.children.length > 0 || block.header.endIndex < block.node.endIndex
|
||||
@@ -677,22 +740,15 @@ export function createZhTemplateTranslator(table: ZhTable): Translator {
|
||||
function describe(block: FactBlock, document: SourceDocument): PropertyRow[] {
|
||||
const { header } = block
|
||||
const { language } = document
|
||||
// 值里带语句块主体的回调(useMemo(() => { … }))只翻到主体开始之前,和块头部一致
|
||||
const clipped = (part: SourceSpan): SourceSpan => part.endIndex > header.endIndex ? { ...part, endIndex: header.endIndex, endRow: header.endRow } : part
|
||||
const within = (node: Node, part: SourceSpan): Inline[] => joinTokens(tokensIn(node, clipped(part), document))
|
||||
const clipped = (part: SourceSpan): SourceSpan => clip(part, header)
|
||||
const phrases: Phrases = {
|
||||
translated: node => within(node, nodeSpan(node)),
|
||||
within,
|
||||
expanded: node => expandedText(node, clipped(nodeSpan(node)), document),
|
||||
translated: node => joinTokens(tokensIn(node, clipped(nodeSpan(node)), document)),
|
||||
value: node => cellValue(node, clipped(nodeSpan(node)), document),
|
||||
body: node => expression(node, document),
|
||||
returned: (node) => {
|
||||
const [value] = language.command?.(node)?.arguments ?? []
|
||||
return value ? expression(value.node, document) : []
|
||||
},
|
||||
entryCount: (literal, size) => {
|
||||
const name = table.mapLiterals[literal.type]
|
||||
return [hint(name ? `${name} ${size} 项` : `${size} 项`)]
|
||||
},
|
||||
verbatim: node => verbatim(node, document),
|
||||
// 点分模块名逐节照抄,每一节各自能跳到定义,和 VS Code 里一样
|
||||
module: node => stringNodes.has(node.type) ? [segment(node.text, 'string', offsetOf(node, language))] : verbatim(node, document, 'type'),
|
||||
@@ -756,6 +812,16 @@ function nodeSpan(node: Node): SourceSpan {
|
||||
return { startIndex: node.startIndex, endIndex: node.endIndex, startRow: node.startPosition.row, endRow: node.endPosition.row }
|
||||
}
|
||||
|
||||
/** node 整个落在 span 里。 */
|
||||
function inside(node: Node, span: SourceSpan): boolean {
|
||||
return node.startIndex >= span.startIndex && node.endIndex <= span.endIndex
|
||||
}
|
||||
|
||||
/** part 超出 limit 结尾的部分截掉:值里带语句块主体的回调(useMemo(() => { … }))只翻到主体开始之前,和块头部一致。 */
|
||||
function clip(part: SourceSpan, limit: SourceSpan): SourceSpan {
|
||||
return part.endIndex > limit.endIndex ? { ...part, endIndex: limit.endIndex, endRow: limit.endRow } : part
|
||||
}
|
||||
|
||||
function segment(text: string, role: TokenRole, offset: number | undefined): Segment {
|
||||
return offset === undefined ? { text, role } : { text, role, offset }
|
||||
}
|
||||
@@ -805,20 +871,23 @@ function joinTokens(tokens: Token[]): Inline[] {
|
||||
|
||||
// ─── 属性表 ───────────────────────────────────────────────────────────────────
|
||||
|
||||
/** 属性表里一格的值:一行译文,带下一层子行(拆开的字面量的各项)或那次调用的实参表。 */
|
||||
interface CellValue {
|
||||
text: Inline[]
|
||||
items?: PropertyItem[]
|
||||
table?: PreviewTable
|
||||
}
|
||||
|
||||
/** 属性表里的值怎么写。 */
|
||||
interface Phrases {
|
||||
/** 按表翻译,只翻落在块头部里的那一段。 */
|
||||
translated: (node: Node) => Inline[]
|
||||
/** 同 translated,只翻 node 落在 span 里的那一段(对象字面量里方法的头部)。 */
|
||||
within: (node: Node, span: SourceSpan) => Inline[]
|
||||
/** 同 translated,里面跨几行写的第一个调用排成一行「新建 / 调用 …」,实参摊成表(见 emitCall)。 */
|
||||
expanded: (node: Node) => { text: Inline[], table?: PreviewTable }
|
||||
/** 一格里的值,只翻落在块头部里的那一段:字面量的各项拆成下一层子行,跨几行写的调用排成「新建 / 调用 …」带实参表(见 cellValue)。 */
|
||||
value: (node: Node) => CellValue
|
||||
/** 函数主体里的一句(不在块头部里)整句照常翻:函数打印的那几句。 */
|
||||
body: (node: Node) => Inline[]
|
||||
/** 一句 return 返回的值(语言插件读出的命令的第一个词);只写了 return 时是空的。 */
|
||||
returned: (node: Node) => Inline[]
|
||||
/** 字典 / 对象字面量的项数:「字典 3 项」,叫法按字面量的节点类型查表。 */
|
||||
entryCount: (literal: Node, size: number) => Segment[]
|
||||
/** 源码原样照抄、按源码着色(装饰器)。 */
|
||||
verbatim: (node: Node) => Segment[]
|
||||
/** 导入来源:字符串字面量按字符串着色,模块名按类型着色。 */
|
||||
@@ -1271,44 +1340,16 @@ function variableRows(block: VariableBlock, say: Phrases): PropertyRow[] {
|
||||
...(i === 0 ? docBodyRows(block.facts.doc && say.doc(block.facts.doc)) : []),
|
||||
...accessRows(block.facts.access),
|
||||
...(declarator.type ? [{ label: '类型', value: say.translated(declarator.type) }] : []),
|
||||
{ label: valueLabel, ...(declarator.value ? declaredValue(declarator.value, declarator.entries, say) : { value: [hint('无')] }) },
|
||||
{ label: valueLabel, ...(declarator.value ? declaredValue(declarator.value, say) : { value: [hint('无')] }) },
|
||||
])
|
||||
}
|
||||
|
||||
/**
|
||||
* 声明的值:字典 / 对象字面量见 literalValue;别的值是一行译文,里面跨几行写的调用排成「新建 / 调用 …」,
|
||||
* 实参摊成表(参数 | 值)展开在这一行下面。
|
||||
* 声明的值(见 Phrases.value):拆开的字面量的各项、跨几行写的调用的实参表(参数 | 值,带列名)都展开在这一行下面。
|
||||
*/
|
||||
function declaredValue(value: Node, entries: EntryFacts[] | undefined, say: Phrases): Pick<PropertyRow, 'value' | 'items' | 'columns'> {
|
||||
if (entries?.length)
|
||||
return literalValue(value, entries, say)
|
||||
const { text, table } = say.expanded(value)
|
||||
return table ? { value: text, ...table } : { value: text }
|
||||
}
|
||||
|
||||
/**
|
||||
* 值:字典 / 对象字面量(语言插件给了 entries)写项数,各项展开成下一层子行;
|
||||
* 空字面量和别的值是一行译文(空的译成「空字典」「空对象」)。
|
||||
*/
|
||||
function literalValue(value: Node, entries: EntryFacts[] | undefined, say: Phrases): { value: Inline[], items?: PropertyItem[] } {
|
||||
if (!entries?.length)
|
||||
return { value: say.translated(value) }
|
||||
return { value: say.entryCount(value, entries.length), items: entries.map(entry => entryItem(entry, say)) }
|
||||
}
|
||||
|
||||
/**
|
||||
* 字典 / 对象字面量的一项是一个子行,两列:键 | 值。键照源码写法(字符串带引号,计算属性名 `[k]` 照常翻),
|
||||
* `**x`、`...x` 的键写「展开」、值是展开的表达式,方法的值是它的头部。讲的定义是键(对象的属性名)。
|
||||
*/
|
||||
function entryItem(entry: EntryFacts, say: Phrases): PropertyItem {
|
||||
switch (entry.kind) {
|
||||
case 'pair': {
|
||||
const { value, ...nested } = literalValue(entry.value, entry.entries, say)
|
||||
return { cells: [say.translated(entry.key), value], ...nested, ...defining(entry.key) }
|
||||
}
|
||||
case 'spread':
|
||||
return { cells: [[hint('展开')], say.translated(entry.value)] }
|
||||
case 'method':
|
||||
return { cells: [say.translated(entry.name), say.within(entry.node, entry.header)], ...defining(entry.name) }
|
||||
}
|
||||
function declaredValue(value: Node, say: Phrases): Pick<PropertyRow, 'value' | 'items' | 'columns'> {
|
||||
const { text, items, table } = say.value(value)
|
||||
if (table)
|
||||
return { value: text, ...table }
|
||||
return items ? { value: text, items } : { value: text }
|
||||
}
|
||||
Reference in new issue
Block a user