168 lines
7.3 KiB
TypeScript
168 lines
7.3 KiB
TypeScript
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<number, TextDiff>, base: number): Record<number, TextDiff> {
|
|
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) }),
|
|
}
|
|
}
|