import type { BlockView } from '../blockView' import type { Inline, PreviewTable, PreviewText, PropertyItem, PropertyRow, Segment, SourceRange } from '../contract' import type { BlockMarks, ItemDiff, ItemsDiff, RowDiff, RowsDiff, TextDiff } from '../diff/viewDiff' import type { BlockSpan, PageBlock, PageCard } from './messages' /** * 扩展端的块(位置都是文件里的)→ 发给页面的块(见 PageBlock):块在源码里占的地方按相对的量记(BlockSpan), * 块里的下标从块的开头(第一行的行首)算。内容没变、只是前面的代码变长变短挪了位置的块,发出去一模一样,页面认得出它没变。 * 页面那边从文件开头把各块的 span 累加回文件里的位置(webview-ui 的 placement.ts),两边的算法互逆。 */ /** 一份源码各行行首、行尾(不含换行符)的下标 */ export interface SourceLines { start: (line: number) => number end: (line: number) => number } export function sourceLines(source: string): SourceLines { const starts = [0] for (let at = source.indexOf('\n'); at >= 0; at = source.indexOf('\n', at + 1)) starts.push(at + 1) const start = (line: number): number => starts[Math.min(Math.max(line, 0), starts.length - 1)] const end = (line: number): number => { const next = line + 1 < starts.length ? starts[line + 1] - 1 : source.length return next > start(line) && source[next - 1] === '\r' ? next - 1 : next } return { start, end } } function shiftRange(range: SourceRange, base: number): SourceRange { return { startIndex: range.startIndex - base, endIndex: range.endIndex - base } } function shiftSegment(segment: Segment, base: number): Segment { if (segment.offset === undefined && !segment.refers) return segment return { ...segment, ...(segment.offset !== undefined && { offset: segment.offset - base }), ...(segment.refers && { refers: shiftRange(segment.refers, base) }), } } function shiftInline(pieces: readonly Inline[], base: number): Inline[] { return pieces.map(piece => 'role' in piece ? shiftSegment(piece, base) : { ...piece, segments: shiftInline(piece.segments, base) }) } function shiftText(text: PreviewText, base: number): PreviewText { return 'markdown' in text ? text : shiftInline(text, base) } function shiftItem(item: PropertyItem, base: number): PropertyItem { return { ...item, cells: item.cells.map(cell => shiftText(cell, base)), ...(item.defines && { defines: shiftRange(item.defines, base) }), ...(item.items && { items: item.items.map(inner => shiftItem(inner, base)) }), ...(item.table && { table: shiftTable(item.table, base) }), } } function shiftTable(table: PreviewTable, base: number): PreviewTable { return { ...table, items: table.items.map(item => shiftItem(item, base)) } } function shiftRow(row: PropertyRow, base: number): PropertyRow { return { ...row, value: shiftText(row.value, base), ...(row.defines && { defines: shiftRange(row.defines, base) }), ...(row.items && { items: row.items.map(item => shiftItem(item, base)) }), } } /** 一块卡片画的内容(见 PageCard):下标从 base(块第一行的行首在文件里的下标)算,不带行号、子块。 */ export function pageCard(block: BlockView, base: number): PageCard { const card = { id: block.id, anchor: block.anchor, kind: block.kind, ...(block.defines && { defines: shiftRange(block.defines, base) }), attributes: block.attributes, concepts: block.concepts, ...(block.commentTarget && { commentTarget: block.commentTarget.kind }), ...(block.badge && { badge: block.badge }), ...(block.slot && { slot: block.slot }), ...(block.callTree && { callTree: block.callTree }), } if ('properties' in block) return { ...card, properties: block.properties.map(row => shiftRow(row, base)) } return { ...card, translation: shiftText(block.translation, base), ...(block.items && { items: block.items.map(item => shiftItem(item, base)) }), ...(block.table && { table: shiftTable(block.table, base) }), ...(block.doc && { doc: block.doc }), ...(block.rows && { rows: block.rows.map(row => shiftRow(row, base)) }), } } /** 文件里的一处:行号、下标 */ interface Point { line: number, offset: number } /** * 一串兄弟块发给页面的样子:每块的开头是它第一行的行首、结尾是最后一行的行尾,离前一块的结尾(第一块从 from 算)隔多远、自己多长记成 span, * 子块从这块的开头算起。 */ function pageList(blocks: readonly BlockView[], lines: SourceLines, from: Point): PageBlock[] { let cursor = from return blocks.map((block) => { const start = { line: block.startLine, offset: lines.start(block.startLine) } const end = { line: block.endLine, offset: lines.end(block.endLine) } const span: BlockSpan = { lineGap: start.line - cursor.line, gap: start.offset - cursor.offset, lines: end.line - start.line, length: end.offset - start.offset } cursor = end return { ...pageCard(block, start.offset), span, children: pageList(block.children, lines, start) } }) } /** 一份源码的块树发给页面的样子(见 PageBlock):顶层第一块从文件开头算。 */ export function pageBlocks(blocks: readonly BlockView[], source: string): PageBlock[] { return pageList(blocks, sourceLines(source), { line: 0, offset: 0 }) } function shiftTextDiff(diff: TextDiff, base: number): TextDiff { return 'markdown' in diff ? diff : { inline: diff.inline.map(part => ({ ...part, segments: shiftInline(part.segments, base) })) } } function shiftCells(cells: Record, base: number): Record { return Object.fromEntries(Object.entries(cells).map(([column, cell]) => [column, shiftTextDiff(cell, base)])) } function shiftItemDiff(diff: ItemDiff, base: number): ItemDiff { return { ...diff, ...(diff.cells && { cells: shiftCells(diff.cells, base) }), ...(diff.items && { items: shiftItemsDiff(diff.items, base) }), ...(diff.table && { table: shiftItemsDiff(diff.table, base) }), } } function shiftItemsDiff(diff: ItemsDiff, base: number): ItemsDiff { return { ...diff, changed: Object.fromEntries(Object.entries(diff.changed).map(([index, item]) => [index, shiftItemDiff(item, base)])) } } function shiftRowDiff(diff: RowDiff, base: number): RowDiff { return { ...diff, ...(diff.value && { value: shiftTextDiff(diff.value, base) }), ...(diff.note && { note: shiftTextDiff(diff.note, base) }), ...(diff.items && { items: shiftItemsDiff(diff.items, base) }), } } function shiftRowsDiff(diff: RowsDiff, base: number): RowsDiff { return { ...diff, changed: Object.fromEntries(Object.entries(diff.changed).map(([index, row]) => [index, shiftRowDiff(row, base)])) } } /** 卡片里要标的改动(BlockMarks)发给页面的样子:新加、没变的词带的下标同卡片的内容,从块的开头(base)算;删掉的词本来就不带下标。 */ export function pageMarks(marks: BlockMarks, base: number): BlockMarks { return { ...(marks.rows && { rows: shiftRowsDiff(marks.rows, base) }), ...(marks.translation && { translation: shiftTextDiff(marks.translation, base) }), ...(marks.items && { items: shiftItemsDiff(marks.items, base) }), ...(marks.table && { table: shiftItemsDiff(marks.table, base) }), ...(marks.doc && { doc: shiftTextDiff(marks.doc, base) }), } }