✨ feat(vscode): 代码阅读预览里实参值、return 这类一行译文里跨几行写的字典 / 列表按项拆开,各项带自己的实参表
This commit is contained in:
1 parent
b2c5309d1f
commit
907c4092c8
18 files changed
+515
-102
No files matched your search
@@ -3,7 +3,7 @@ import type { ZhTable } from './zhTemplate'
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { createPythonAdapter } from '../languages/python'
|
||||
import { createTypeScriptAdapter } from '../languages/typescript'
|
||||
import { flatSegments, inlineOf, previewText, propertyLines, readFixture, tableLines, walk, WASM_DIR, withBlocks } from '../testSupport'
|
||||
import { flatSegments, inlineOf, listLines, previewText, propertyLines, readFixture, tableLines, walk, WASM_DIR, withBlocks } from '../testSupport'
|
||||
import { pythonZhTranslator } from './pythonZh'
|
||||
import { typescriptZhTranslator } from './typescriptZh'
|
||||
import { createZhTemplateTranslator } from './zhTemplate'
|
||||
@@ -11,8 +11,11 @@ import { createZhTemplateTranslator } from './zhTemplate'
|
||||
const ts = createTypeScriptAdapter(WASM_DIR)
|
||||
const py = createPythonAdapter(WASM_DIR)
|
||||
|
||||
/** 一块的译文:一行译文在 segments 里,下面摊开实参的表在 table 里;注释块是 Markdown,原文在 markdown 里,segments 为空。 */
|
||||
interface Translated { kind: string, segments: Inline[], markdown?: string, table?: PreviewTable }
|
||||
/**
|
||||
* 一块的译文:一行译文在 segments 里,下面拆开的各项在 items 里、摊开实参的表在 table 里;
|
||||
* 注释块是 Markdown,原文在 markdown 里,segments 为空。
|
||||
*/
|
||||
interface Translated { kind: string, segments: Inline[], markdown?: string, items?: PropertyItem[], table?: PreviewTable }
|
||||
|
||||
/**
|
||||
* 一行译文块、注释块(单独成块的、挂在块上的)的译文,按 header 起始行(1 起)索引;同一行有多块时取外层那块。属性表块见 describeAll。
|
||||
@@ -26,10 +29,10 @@ function translateAll(adapter: LanguageAdapter, languageId: string, translator:
|
||||
const line = item.header.startRow + 1
|
||||
if (byLine.has(line) || 'facts' in item)
|
||||
return
|
||||
const { text: translation, table } = translator.translate(item, { source, language: adapter, markers: [], ...(calleeKind && { calleeKind }) })
|
||||
const { text: translation, items, table } = translator.translate(item, { source, language: adapter, markers: [], ...(calleeKind && { calleeKind }) })
|
||||
byLine.set(line, 'markdown' in translation
|
||||
? { kind: item.kind, segments: [], markdown: translation.markdown }
|
||||
: { kind: item.kind, segments: translation, ...(table && { table }) })
|
||||
: { kind: item.kind, segments: translation, ...(items && { items }), ...(table && { table }) })
|
||||
}))
|
||||
return byLine
|
||||
})
|
||||
@@ -82,7 +85,10 @@ const CJK = /\p{Script=Han}/u
|
||||
const CJK_ONLY = /^\p{Script=Han}+$/u
|
||||
const WORD_CHAR = /[\p{Script=Han}\w]/u
|
||||
|
||||
/** 被翻的词单独成段,而且不和前后的字母、数字、汉字粘在一起;框里的词和框前后的词也一样。 */
|
||||
/**
|
||||
* 被翻的词单独成段,而且不和前后的字母、数字、汉字粘在一起;框里的词和框前后的词也一样。
|
||||
* 说明词(「对象 3 项」「· 2 个参数」)本来就带数字,只看它不和前后粘在一起。
|
||||
*/
|
||||
function expectWordsApart(map: Map<number, Translated>): void {
|
||||
for (const [line, { kind, segments }] of map) {
|
||||
if (kind === 'comment')
|
||||
@@ -92,7 +98,8 @@ function expectWordsApart(map: Map<number, Translated>): void {
|
||||
let at = 0
|
||||
for (const segment of flat) {
|
||||
if (segment.role !== 'string' && CJK.test(segment.text)) {
|
||||
expect(segment.text, `L${line} ${full}`).toMatch(CJK_ONLY)
|
||||
if (segment.role !== 'hint')
|
||||
expect(segment.text, `L${line} ${full}`).toMatch(CJK_ONLY)
|
||||
for (const neighbor of [full[at - 1], full[at + segment.text.length]])
|
||||
expect(neighbor ?? ' ', `L${line} "${full}" 里的「${segment.text}」`).not.toMatch(WORD_CHAR)
|
||||
}
|
||||
@@ -109,7 +116,8 @@ describe('typeScript 中文直译', () => {
|
||||
expect(text(env.get(26)?.segments)).toBe('返回 空数组')
|
||||
expect(text(env.get(41)?.segments)).toBe('遍历 (常量 f 取自 files)')
|
||||
expect(text(env.get(35)?.segments)).toBe('failed: { path: 字符串, reason: 字符串 }[]')
|
||||
expect(text(issue.get(20)?.segments)).toBe('返回 { locked: 真, prerequisiteNumber: prereq.number, prerequisiteColumn: prereq.column }')
|
||||
expect(text(issue.get(20)?.segments)).toBe('返回 对象 3 项')
|
||||
expect(listLines(issue.get(20)?.items)).toEqual(['locked | 真', 'prerequisiteNumber | prereq.number', 'prerequisiteColumn | prereq.column'])
|
||||
})
|
||||
|
||||
it('其余块:结构照抄,复合语句去掉结尾的 {,注释去掉注释符号、保留换行', async () => {
|
||||
@@ -169,12 +177,10 @@ describe('typeScript 中文直译', () => {
|
||||
|
||||
it('多行合成一行、去掉右括号前的尾逗号、行中注释不进译文', async () => {
|
||||
const source = [
|
||||
'list = [',
|
||||
' first, // 第一个',
|
||||
' [],',
|
||||
']',
|
||||
'list = first // 第一个',
|
||||
' || [first, [],]',
|
||||
].join('\n')
|
||||
expect(texts(await translateAll(ts, 'typescript', typescriptZhTranslator, source))[1]).toBe('list = [ first, 空数组 ]')
|
||||
expect(texts(await translateAll(ts, 'typescript', typescriptZhTranslator, source))[1]).toBe('list = first 或者 [first, 空数组]')
|
||||
})
|
||||
|
||||
it('as / in / extends / default 按所在语法节点换译法', async () => {
|
||||
@@ -1346,8 +1352,11 @@ describe('跨几行写的调用', () => {
|
||||
const { asked, calleeKind } = kinds({})
|
||||
const texts = await translateAll(ts, 'typescript', typescriptZhTranslator, calls, calleeKind)
|
||||
expect(text(texts.get(18)?.segments)).toBe('this.definitions = 新建 DefinitionSources · 1 个参数')
|
||||
// 实参是跨几行写的对象:按项拆在那一格里
|
||||
expect(tableLines(texts.get(18)?.table)).toEqual([
|
||||
'第 1 个 | { rule: 异步 reference => ruleDefinitions(等待 owner.resolveReference(reference)), language: offset => languageDefinitions(document.uri, document.positionAt(offset)) }',
|
||||
'第 1 个 | 对象 2 项',
|
||||
' rule | 异步 reference => ruleDefinitions(等待 owner.resolveReference(reference))',
|
||||
' language | offset => languageDefinitions(document.uri, document.positionAt(offset))',
|
||||
])
|
||||
expect(text(texts.get(33)?.segments)).toBe('返回 调用 concatAll · 2 个参数')
|
||||
expect(tableLines(texts.get(33)?.table)).toEqual(['第 1 个 | base', '展开 | extra'])
|
||||
@@ -1507,7 +1516,7 @@ describe('跨几行写的列表', () => {
|
||||
])
|
||||
})
|
||||
|
||||
it('实参的值是跨几行写的列表时也按项拆,嵌在那一格下面;写在一行里的列表、实参里的字典照旧一行', async () => {
|
||||
it('实参的值是跨几行写的列表时也按项拆,嵌在那一格下面;写在一行里的列表、字典照旧一行', async () => {
|
||||
const source = [
|
||||
'session.query(',
|
||||
' filters=[',
|
||||
@@ -1574,6 +1583,182 @@ describe('跨几行写的列表', () => {
|
||||
expect(text(line?.segments)).toBe('调用 register · 2 个参数')
|
||||
expect(tableLines(line?.table)).toEqual(['第 1 个 | \'x\'', '第 2 个 | 数组 2 项', ' 第 1 项 | a', ' 第 2 项 | b'])
|
||||
})
|
||||
|
||||
it('实参的值是跨几行写的字典时也按项拆,嵌在那一格下面,和跨几行的列表一样;项的值照字典拆,键标出它讲的定义', async () => {
|
||||
const source = [
|
||||
'session.query(',
|
||||
' filters=[',
|
||||
' Filter(field="a"),',
|
||||
' ],',
|
||||
' meta={',
|
||||
' "a": 1,',
|
||||
' "b": {"c": 2},',
|
||||
' **extra,',
|
||||
' },',
|
||||
' ids={"x": 1},',
|
||||
')',
|
||||
].join('\n')
|
||||
const line = (await translateAll(py, 'python', pythonZhTranslator, source, calleeKind)).get(1)
|
||||
expect(text(line?.segments)).toBe('调用 session.query · 3 个参数')
|
||||
expect(tableLines(line?.table)).toEqual([
|
||||
'filters | 列表 1 项',
|
||||
' 第 1 项 | 新建 Filter · 1 个参数',
|
||||
' field | "a"',
|
||||
'meta | 字典 3 项',
|
||||
' "a" | 1',
|
||||
' "b" | 字典 1 项',
|
||||
' "c" | 2',
|
||||
' 展开 | extra',
|
||||
'ids | {"x": 1}',
|
||||
])
|
||||
const meta = line?.table?.items[1]
|
||||
expect(meta?.table).toBeUndefined()
|
||||
const key = source.indexOf('"a": 1')
|
||||
expect(meta?.items?.[0].defines).toEqual({ startIndex: key, endIndex: key + 3 })
|
||||
expect(linked(meta?.items?.[2].cells[1])).toEqual([['extra', source.indexOf('extra')]])
|
||||
})
|
||||
|
||||
it('返回跨几行写的列表:这一行写「返回 列表 N 项」,各项排在这一行下面、各带自己的实参表,不再有挂在整行下面的表', async () => {
|
||||
const source = [
|
||||
'def list_filters(archived: bool, current_user: User):',
|
||||
...FILTERS.split('\n').slice(1).map(line => line.replace('filters = ', 'return ')),
|
||||
].join('\n')
|
||||
const line = (await translateAll(py, 'python', pythonZhTranslator, source, calleeKind)).get(2)
|
||||
expect(text(line?.segments)).toBe('返回 列表 2 项')
|
||||
expect(colors(line?.segments.at(-1))).toEqual({ text: '列表 2 项', role: 'hint' })
|
||||
expect(line?.table).toBeUndefined()
|
||||
expect(listLines(line?.items)).toEqual([
|
||||
'第 1 项 | 新建 Filter · 3 个参数',
|
||||
' field | "user_id"',
|
||||
' operator | FilterOperator.EQ',
|
||||
' value | current_user.id',
|
||||
'第 2 项 | 新建 Filter · 3 个参数',
|
||||
' field | "archived_at"',
|
||||
' operator | 如果 archived 则 FilterOperator.NE 否则 FilterOperator.EQ',
|
||||
' value | 空',
|
||||
])
|
||||
const [first, second] = line?.items ?? []
|
||||
expect(first?.table?.columns).toEqual(['参数', '值'])
|
||||
// 名字照样带源码下标:被调用的名字、按名字传的名字、值里的名字
|
||||
const multiline = source.indexOf('Filter(\n')
|
||||
expect(inlineOf(second?.cells[1] ?? []).find(piece => 'role' in piece && piece.text === 'Filter')).toEqual({ text: 'Filter', role: 'type', offset: multiline })
|
||||
expect(second?.table?.items[0].cells[0]).toEqual([{ text: 'field', role: 'variable', offset: source.indexOf('field="archived_at"') }])
|
||||
const user = source.indexOf('current_user.id')
|
||||
expect(linked(first?.table?.items[2].cells[1])).toEqual([['current_user', user], ['id', user + 13]])
|
||||
})
|
||||
|
||||
it('别的语句里跨几行写的字典、列表同样拆;一段只排先遇到的那一个(先外后里),埋在式子里的整个括起来,常量 / 变量的值也一样', async () => {
|
||||
const source = [
|
||||
'def save(self, rows):',
|
||||
' yield {',
|
||||
' "rows": [',
|
||||
' 1,',
|
||||
' 2,',
|
||||
' ],',
|
||||
' "count": len(rows),',
|
||||
' }',
|
||||
' publish({',
|
||||
' "a": 1,',
|
||||
' })',
|
||||
' rows += [',
|
||||
' make(',
|
||||
' a,',
|
||||
' ),',
|
||||
' ] + [',
|
||||
' b,',
|
||||
' ]',
|
||||
'def total(a, b):',
|
||||
' return f([',
|
||||
' a,',
|
||||
' b,',
|
||||
' ]) + 1',
|
||||
'LIMITS = [',
|
||||
' 1,',
|
||||
'] + DEFAULTS',
|
||||
].join('\n')
|
||||
const lines = await translateAll(py, 'python', pythonZhTranslator, source)
|
||||
expect(text(lines.get(2)?.segments)).toBe('产出 字典 2 项')
|
||||
expect(listLines(lines.get(2)?.items)).toEqual(['"rows" | 列表 2 项', ' 第 1 项 | 1', ' 第 2 项 | 2', '"count" | len(rows)'])
|
||||
// 调用在外面:排调用,字典是它的实参,拆在实参表那一格里
|
||||
expect(text(lines.get(9)?.segments)).toBe('调用 publish · 1 个参数')
|
||||
expect(lines.get(9)?.items).toBeUndefined()
|
||||
expect(tableLines(lines.get(9)?.table)).toEqual(['第 1 个 | 字典 1 项', ' "a" | 1'])
|
||||
// 列表埋在式子里:整个括起来;同一段里后面再有的照旧一行
|
||||
expect(text(lines.get(12)?.segments)).toBe('rows += (列表 1 项) + [ b ]')
|
||||
expect(listLines(lines.get(12)?.items)).toEqual(['第 1 项 | 调用 make · 1 个参数', ' 第 1 个 | a'])
|
||||
expect(text(lines.get(20)?.segments)).toBe('返回 (调用 f · 1 个参数) + 1')
|
||||
expect(lines.get(20)?.items).toBeUndefined()
|
||||
expect(tableLines(lines.get(20)?.table)).toEqual(['第 1 个 | 列表 2 项', ' 第 1 项 | a', ' 第 2 项 | b'])
|
||||
const rows = await describeAll(py, 'python', pythonZhTranslator, source)
|
||||
expect(propertyLines(rows.get(24) ?? []).slice(2)).toEqual(['值: (列表 1 项) + DEFAULTS', ' 第 1 项 | 1'])
|
||||
})
|
||||
|
||||
it('typeScript:实参的值是跨几行写的对象时也按项拆,嵌在那一格下面;写在一行里的对象照旧一行', async () => {
|
||||
const source = [
|
||||
'register(',
|
||||
' \'x\',',
|
||||
' {',
|
||||
' a: 1,',
|
||||
' b: { c: 2 },',
|
||||
' ...rest,',
|
||||
' },',
|
||||
' { d: 1 },',
|
||||
')',
|
||||
].join('\n')
|
||||
const line = (await translateAll(ts, 'typescript', typescriptZhTranslator, source)).get(1)
|
||||
expect(text(line?.segments)).toBe('调用 register · 3 个参数')
|
||||
expect(tableLines(line?.table)).toEqual([
|
||||
'第 1 个 | \'x\'',
|
||||
'第 2 个 | 对象 3 项',
|
||||
' a | 1',
|
||||
' b | 对象 1 项',
|
||||
' c | 2',
|
||||
' 展开 | rest',
|
||||
'第 3 个 | { d: 1 }',
|
||||
])
|
||||
const key = source.indexOf('a: 1')
|
||||
expect(line?.table?.items[1].items?.[0].defines).toEqual({ startIndex: key, endIndex: key + 1 })
|
||||
})
|
||||
|
||||
it('typeScript:返回、赋值里跨几行写的数组、对象按项拆,各项排在这一行下面;as const 包着的对象埋在式子里,整个括起来', async () => {
|
||||
const source = [
|
||||
'function filters(archived: boolean) {',
|
||||
' return [',
|
||||
' new Filter({ field: \'user_id\' }),',
|
||||
' makeFilter(',
|
||||
' \'archived_at\',',
|
||||
' archived,',
|
||||
' ),',
|
||||
' ]',
|
||||
'}',
|
||||
'module.exports = {',
|
||||
' a: 1,',
|
||||
' b: [',
|
||||
' 1,',
|
||||
' 2,',
|
||||
' ],',
|
||||
'}',
|
||||
'export const ICONS = {',
|
||||
' a: \'x\',',
|
||||
'} as const',
|
||||
].join('\n')
|
||||
const lines = await translateAll(ts, 'typescript', typescriptZhTranslator, source)
|
||||
expect(text(lines.get(2)?.segments)).toBe('返回 数组 2 项')
|
||||
expect(lines.get(2)?.table).toBeUndefined()
|
||||
expect(listLines(lines.get(2)?.items)).toEqual([
|
||||
'第 1 项 | 新建 Filter · 1 个参数',
|
||||
' 第 1 个 | { field: \'user_id\' }',
|
||||
'第 2 项 | 调用 makeFilter · 2 个参数',
|
||||
' 第 1 个 | \'archived_at\'',
|
||||
' 第 2 个 | archived',
|
||||
])
|
||||
const make = source.indexOf('makeFilter')
|
||||
expect(inlineOf(lines.get(2)?.items?.[1].cells[1] ?? []).find(piece => 'role' in piece && piece.text === 'makeFilter')).toEqual({ text: 'makeFilter', role: 'function', offset: make })
|
||||
expect(text(lines.get(10)?.segments)).toBe('module.exports = 对象 2 项')
|
||||
expect(listLines(lines.get(10)?.items)).toEqual(['a | 1', 'b | 数组 2 项', ' 第 1 项 | 1', ' 第 2 项 | 2'])
|
||||
const rows = await describeAll(ts, 'typescript', typescriptZhTranslator, source)
|
||||
expect(propertyLines(rows.get(17) ?? []).slice(-2)).toEqual(['值: (对象 1 项) 断言为 常量', ' a | \'x\''])
|
||||
})
|
||||
})
|
||||
|
||||
describe('中文模板引擎', () => {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
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 { Access, AttributeNames, BaseFacts, BlockKind, CallArgument, CallFacts, ClassFacts, CollectionFacts, 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'
|
||||
@@ -107,16 +107,17 @@ interface Token {
|
||||
}
|
||||
|
||||
/**
|
||||
* 排在这一段译文下面的那张表:跨几行写的调用的实参表(见 emitCall),或读法给的表(分情况的「情况 / 做什么」)。
|
||||
* 一段只排一张,排了的记在 table 里。
|
||||
* 排在这一段译文下面的那一样:跨几行写的调用的实参表(见 emitCall)、读法给的表(分情况的「情况 / 做什么」),
|
||||
* 或跨几行写的字面量拆开的各项(见 emitCollection)。一段只排一样,排了的记在这里。
|
||||
*/
|
||||
interface TableSlot {
|
||||
interface Slot {
|
||||
items?: PropertyItem[]
|
||||
table?: PreviewTable
|
||||
}
|
||||
|
||||
/**
|
||||
* 按译文顺序收集词:cursor 是上一个词在源码里的结尾,gap 是下一个词前面要不要空一格。
|
||||
* slot 是这一段下面能不能排一张表(跨几行写的调用、读法给的表);没有就照常翻。
|
||||
* slot 是这一段下面能不能排一样东西(跨几行写的调用、字面量,读法给的表);没有就照常翻。
|
||||
*/
|
||||
interface Collector {
|
||||
document: SourceDocument
|
||||
@@ -125,7 +126,7 @@ interface Collector {
|
||||
tokens: Token[]
|
||||
cursor: number
|
||||
gap: boolean
|
||||
slot?: TableSlot
|
||||
slot?: Slot
|
||||
}
|
||||
|
||||
const CONTEXT_SEPARATOR = ' > '
|
||||
@@ -171,6 +172,11 @@ function continued(node: Node, root: Node): boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
/** 这一段下面已经排了一样东西(见 Slot)。 */
|
||||
function taken(slot: Slot): boolean {
|
||||
return slot.items !== undefined || slot.table !== undefined
|
||||
}
|
||||
|
||||
function isComment(token: Token): boolean {
|
||||
return token.text === '' && !token.group
|
||||
}
|
||||
@@ -412,47 +418,58 @@ export function createZhTemplateTranslator(table: ZhTable): Translator {
|
||||
out.cursor = node.endIndex
|
||||
}
|
||||
|
||||
/** 要排成一行、实参摊成表的调用:实参列表跨了几行,整个落在要翻的这一段里,这一段还没排过别的调用。 */
|
||||
function expandedCall(node: Node, span: SourceSpan, slot: TableSlot, { language: { call } }: SourceDocument): CallFacts | undefined {
|
||||
if (slot.table || !call || !inside(node, span))
|
||||
/** 要排成一行、实参摊成表的调用:实参列表跨了几行,整个落在要翻的这一段里,这一段还没排过别的。 */
|
||||
function expandedCall(node: Node, span: SourceSpan, slot: Slot, { language: { call } }: SourceDocument): CallFacts | undefined {
|
||||
if (taken(slot) || !call || !inside(node, span))
|
||||
return undefined
|
||||
const facts = call(node)
|
||||
return facts && facts.argumentList.startPosition.row !== facts.argumentList.endPosition.row ? facts : undefined
|
||||
}
|
||||
|
||||
/** 要按项拆开的字面量:语言插件给了各项(LanguageAdapter.collection)、不是空的,跨了几行,整个落在要翻的这一段里,这一段还没排过别的。 */
|
||||
function expandedCollection(node: Node, span: SourceSpan, slot: Slot, { language: { collection } }: SourceDocument): CollectionFacts | undefined {
|
||||
if (taken(slot) || !collection || !inside(node, span))
|
||||
return undefined
|
||||
const facts = collection(node)
|
||||
if (!facts || (facts.kind === 'map' ? facts.entries : facts.elements).length === 0)
|
||||
return undefined
|
||||
return node.startPosition.row !== node.endPosition.row ? facts : undefined
|
||||
}
|
||||
|
||||
/**
|
||||
* node 落在 span 里的那一段翻成一行译文,里面跨几行写的第一个调用排成一行「新建 / 调用 …」,实参摊成表(见 emitCall);
|
||||
* 给了 call(node 整个就是这次调用)时不管写成几行都这样排。一行译文的块、属性表里每一格的值各翻各的,各自排自己里面的那个调用。
|
||||
* node 落在 span 里的那一段翻成一行译文,里面先遇到的(先外后里)跨几行写的调用排成一行「新建 / 调用 …」、实参摊成表(见 emitCall),
|
||||
* 或跨几行写的字面量排成一行「列表 2 项」、各项拆成子行(见 emitCollection);同一段里后面再有的照常一行。
|
||||
* 给了 call(node 整个就是这次调用)时不管写成几行都这样排。一行译文的块、属性表里每一格的值各是一段,各自排自己里面的那一个。
|
||||
*/
|
||||
function expandedText(node: Node, span: SourceSpan, document: SourceDocument, call?: CallFacts): CellValue {
|
||||
const slot: TableSlot = {}
|
||||
const slot: Slot = {}
|
||||
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 }) }
|
||||
return { text: joinTokens(out.tokens), ...slot }
|
||||
}
|
||||
|
||||
/**
|
||||
* 属性表里一格的值(常量 / 变量的值、字典一项的值、列表的一项、实参的值),只翻落在 span 里的部分:
|
||||
* 语言插件给了各项的字面量(LanguageAdapter.collection)写叫法和项数,各项拆成下一层子行(见 entryItem、elementItems)——
|
||||
* 字典 / 对象一直拆(maps 为假时照旧一行:实参的值),列表 / 元组 / 集合 / 数组跨几行写才拆;
|
||||
* 空字面量和别的值是一行译文,里面跨几行写的调用排成「新建 / 调用 …」,实参表跟着这一格(见 expandedText)。
|
||||
* 属性表里一格的值(常量 / 变量的值、字典一项的值、列表的一项、实参的值),只翻落在 span 里的部分,见 expandedText;
|
||||
* maps 为真时(实参的值以外)值整个是字典 / 对象字面量的写在一行里也拆,空字面量照旧一行(「空字典」)。
|
||||
*/
|
||||
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) }
|
||||
const collection = maps ? document.language.collection?.(node) : undefined
|
||||
if (collection?.kind === 'map' && collection.entries.length > 0)
|
||||
return collectionValue(node, collection, span, document)
|
||||
return expandedText(node, span, document)
|
||||
}
|
||||
|
||||
/** 拆开的字面量写叫法和项数(展开的也算一项):「字典 3 项」,叫法按字面量的节点类型查表(ZhTable.collections)。 */
|
||||
function itemCount(literal: Node, size: number): Segment[] {
|
||||
/**
|
||||
* 拆开的字面量:写叫法和项数(展开的也算一项),叫法按字面量的节点类型查表(ZhTable.collections):「字典 3 项」;
|
||||
* 各项是下一层子行,字典 / 对象见 entryItem,列表 / 元组 / 集合 / 数组见 elementItems。
|
||||
*/
|
||||
function collectionValue(literal: Node, facts: CollectionFacts, span: SourceSpan, document: SourceDocument): { text: Segment[], items: PropertyItem[] } {
|
||||
const items = facts.kind === 'map' ? facts.entries.map(entry => entryItem(entry, span, document)) : elementItems(facts.elements, span, document)
|
||||
const name = table.collections[literal.type]
|
||||
return [hint(name ? `${name} ${size} 项` : `${size} 项`)]
|
||||
return { text: [hint(name ? `${name} ${items.length} 项` : `${items.length} 项`)], items }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -492,8 +509,8 @@ export function createZhTemplateTranslator(table: ZhTable): Translator {
|
||||
|
||||
/**
|
||||
* 实参表:参数 | 值,每个实参一行,照源码顺序。按名字传的写名字,按位置传的写「第 N 个」(只数按位置传的),
|
||||
* 展开的写「展开」(*args、...args)、「展开关键字」(**kwargs)。值见 cellValue(字典 / 对象照旧一行):值里有跨几行写的调用时
|
||||
* 同样排成一行,它的实参表排在值那一格里(PropertyItem.table);值是跨几行写的列表时各项拆在那一格里(PropertyItem.items)。一层层往下套。
|
||||
* 展开的写「展开」(*args、...args)、「展开关键字」(**kwargs)。值见 cellValue(写在一行里的字典 / 对象照旧一行):值里有跨几行写的调用时
|
||||
* 同样排成一行,它的实参表排在值那一格里(PropertyItem.table);值里有跨几行写的字典 / 列表时各项拆在那一格里(PropertyItem.items)。一层层往下套。
|
||||
*/
|
||||
function argumentTable(facts: CallFacts, span: SourceSpan, document: SourceDocument): PreviewTable {
|
||||
let position = 0
|
||||
@@ -523,7 +540,7 @@ export function createZhTemplateTranslator(table: ZhTable): Translator {
|
||||
* 写法看不出来的问名字的定义:是类(含异常、枚举)就是新建,还没查到、查不到按调用算。
|
||||
* 调用后面还紧跟着同一个式子的别的部分(见 continued)时整行括起来:「(新建 X · 3 个参数).model_dump()」。
|
||||
*/
|
||||
function emitCall(out: Collector, slot: TableSlot, node: Node, facts: CallFacts, span: SourceSpan): void {
|
||||
function emitCall(out: Collector, slot: Slot, node: Node, facts: CallFacts, span: SourceSpan): void {
|
||||
slot.table = argumentTable(facts, span, out.document)
|
||||
const construct = facts.construct ?? CONSTRUCTED.has(out.document.calleeKind?.(facts))
|
||||
const wrapped = continued(node, out.root)
|
||||
@@ -546,6 +563,24 @@ export function createZhTemplateTranslator(table: ZhTable): Translator {
|
||||
out.cursor = node.endIndex
|
||||
}
|
||||
|
||||
/**
|
||||
* 跨几行写的字面量排成一行「列表 2 项」「字典 3 项」,各项拆成子行记在 slot 里(见 collectionValue)。
|
||||
* 后面还紧跟着同一个式子的别的部分(见 continued)时整个括起来:「(列表 2 项) + rest」。
|
||||
*/
|
||||
function emitCollection(out: Collector, slot: Slot, node: Node, facts: CollectionFacts, span: SourceSpan): void {
|
||||
const { text, items } = collectionValue(node, facts, span, out.document)
|
||||
slot.items = items
|
||||
const wrapped = continued(node, out.root)
|
||||
advance(out, node.startIndex)
|
||||
if (wrapped)
|
||||
emit(out, { text: '(', role: 'plain', translated: true, raw: '' })
|
||||
for (const piece of text)
|
||||
emit(out, { text: piece.text, role: piece.role, translated: true, raw: '' })
|
||||
if (wrapped)
|
||||
emit(out, { text: ')', role: 'plain', translated: true, raw: '' })
|
||||
out.cursor = node.endIndex
|
||||
}
|
||||
|
||||
/** 读法用的:照常翻、看节点在不在这一段里、照抄名字、读命令。 */
|
||||
function readerContext(out: Collector, span: SourceSpan): ReaderContext {
|
||||
const { document } = out
|
||||
@@ -561,10 +596,10 @@ export function createZhTemplateTranslator(table: ZhTable): Translator {
|
||||
}
|
||||
}
|
||||
|
||||
/** 读出来的几段接着前面的词输出;读法给了表、这一段还没排表时表排在这一段下面。 */
|
||||
/** 读出来的几段接着前面的词输出;读法给了表、这一段下面还空着时表排在这一段下面。 */
|
||||
function emitReading(out: Collector, node: Node, reading: Inline[] | { text: Inline[], table?: PreviewTable }): void {
|
||||
const { text, table: below } = Array.isArray(reading) ? { text: reading, table: undefined } : reading
|
||||
if (below && out.slot && !out.slot.table)
|
||||
if (below && out.slot && !taken(out.slot))
|
||||
out.slot.table = below
|
||||
emitPieces(out, node, text)
|
||||
}
|
||||
@@ -624,6 +659,11 @@ export function createZhTemplateTranslator(table: ZhTable): Translator {
|
||||
emitCall(out, slot, node, call, span)
|
||||
return
|
||||
}
|
||||
const collection = slot && expandedCollection(node, span, slot, out.document)
|
||||
if (slot && collection) {
|
||||
emitCollection(out, slot, node, collection, span)
|
||||
return
|
||||
}
|
||||
const reading = table.readers?.[node.type]?.(node, readerContext(out, span))
|
||||
if (reading) {
|
||||
emitReading(out, node, reading)
|
||||
@@ -665,13 +705,13 @@ export function createZhTemplateTranslator(table: ZhTable): Translator {
|
||||
}
|
||||
}
|
||||
|
||||
/** 翻 node 落在 span 里的那一段用的收集器;给了 slot,跨几行写的调用排成一行、实参摊成表(见 emitCall)。 */
|
||||
function collector(node: Node, span: SourceSpan, document: SourceDocument, slot?: TableSlot): Collector {
|
||||
/** 翻 node 落在 span 里的那一段用的收集器;给了 slot,先遇到的跨几行写的调用、字面量排开(见 expandedText)。 */
|
||||
function collector(node: Node, span: SourceSpan, document: SourceDocument, slot?: Slot): 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[] {
|
||||
function tokensIn(node: Node, span: SourceSpan, document: SourceDocument, slot?: Slot): Token[] {
|
||||
const out = collector(node, span, document, slot)
|
||||
collect(node, span, out)
|
||||
return out.tokens
|
||||
@@ -719,13 +759,13 @@ export function createZhTemplateTranslator(table: ZhTable): Translator {
|
||||
const group = block.group && table.groups?.[block.node.type]
|
||||
if (block.group && group)
|
||||
return group(block.group, readerContext(collector(block.node, block.header, document), block.header))
|
||||
const slot: TableSlot = {}
|
||||
const slot: Slot = {}
|
||||
const tokens = tokensIn(block.node, block.header, document, slot)
|
||||
const compound = block.children.length > 0 || block.header.endIndex < block.node.endIndex
|
||||
const last = tokens.at(-1)
|
||||
if (compound && last && !last.translated && HEADER_OPENERS.has(last.raw))
|
||||
tokens.pop()
|
||||
return { text: joinTokens(tokens), ...(slot.table && { table: slot.table }) }
|
||||
return { text: joinTokens(tokens), ...slot }
|
||||
}
|
||||
|
||||
function expression(node: Node, document: SourceDocument): Inline[] {
|
||||
|
||||
Reference in new issue
Block a user