⚡ perf(vscode): 代码阅读预览的文档消息里源码和着色只带一份:块不再各带一份连子块在内的整段源码和上色,编辑框按块的编辑范围从整份源码和着色里取,5126 行的消息 4.3MB → 1.9MB
This commit is contained in:
1 parent
0371712176
commit
2e3602242e
43 files changed
+501
-239
No files matched your search
@@ -1,5 +1,5 @@
|
||||
import type { Tree } from '@vscode/tree-sitter-wasm'
|
||||
import type { BlockAttributes, BlockConcept, BlockKind, CodeBlock, DisplayName, EmbeddedCode, EmbeddedReading, HttpEndpoint, Inline, KindBadge, LanguageAdapter, LibraryRule, LocalSymbol, Markdown, NameBinding, Node, PreviewTable, PreviewText, PropertyItem, PropertyRow, RuleCard, RuleContext, Segment, SourceDocument, SourceRange, SourceSpan, StepBlocks, TextBlock, TokenRole, Translator } from './contract'
|
||||
import type { BlockAttributes, BlockConcept, BlockKind, CodeBlock, DisplayName, EmbeddedCode, EmbeddedReading, HttpEndpoint, Inline, KindBadge, LanguageAdapter, LibraryRule, LocalSymbol, Markdown, NameBinding, Node, PreviewTable, PreviewText, PropertyItem, PropertyRow, RuleCard, RuleContext, SourceDocument, SourceRange, SourceSpan, StepBlocks, TextBlock, TokenRole, Translator } from './contract'
|
||||
import type { TreeSitterRuntime } from './runtime'
|
||||
import type { ImportedName } from './symbolLinks'
|
||||
import { blockAttributes } from './attributes'
|
||||
@@ -25,10 +25,6 @@ interface BlockViewBase {
|
||||
endLine: number
|
||||
/** 块自己那部分(头部)的最后一行:复合语句到主体开始之前,库规则认领的块是整条语句。问 AI 时贴外层块的开头用。 */
|
||||
headerEndLine: number
|
||||
/** 编辑范围的整行源码,去掉共同缩进。 */
|
||||
code: string
|
||||
/** 源码着色(拼起来等于 code)。 */
|
||||
codeTokens: Segment[]
|
||||
/** 块自己的名字(见 ownName)在源码里的范围:跳到的定义就是它时闪整块,不闪属性表里的某一行。 */
|
||||
defines?: SourceRange
|
||||
/** 筛选属性(含 kind),见 blockAttributes;库规则认领的块叠上规则给的,kind 是规则的种类 id。 */
|
||||
@@ -77,17 +73,26 @@ export type CommentTarget
|
||||
*/
|
||||
export type BlockView = BlockViewBase & ({ translation: PreviewText, items?: PropertyItem[], table?: PreviewTable, doc?: Markdown, rows?: PropertyRow[] } | { properties: PropertyRow[] })
|
||||
|
||||
interface Line { start: number, end: number }
|
||||
/**
|
||||
* 整份源码的着色,发给页面:页面编辑框给块的源码上色时按行取,父块、子块共用这一份。
|
||||
* 按源码先后一段段排,相邻同词性的连成一段;runs 两个数一组:这段有几个字符(UTF-16,换行符也算),词性是 roles 里的第几个。
|
||||
*/
|
||||
export interface SourceColors {
|
||||
roles: TokenRole[]
|
||||
runs: number[]
|
||||
}
|
||||
|
||||
/** 每行在源码里的起止下标(end 不含换行符)。 */
|
||||
function lineRanges(source: string): Line[] {
|
||||
const lines: Line[] = []
|
||||
let start = 0
|
||||
for (const text of splitLines(source)) {
|
||||
lines.push({ start, end: start + text.length })
|
||||
start += text.length + (source[start + text.length] === '\r' ? 2 : 1)
|
||||
/**
|
||||
* 一份源码里各块的编辑范围(startLine..endLine 整行)去掉共同缩进的样子,同页面编辑框里显示的(写回见 editText.reindentBlock):
|
||||
* 源码按行切一次,各块按行取。
|
||||
*/
|
||||
export function blockCodes(source: string): (block: Pick<BlockView, 'startLine' | 'endLine'>) => string {
|
||||
const texts = splitLines(source)
|
||||
return ({ startLine, endLine }) => {
|
||||
const rows = texts.slice(startLine, endLine + 1)
|
||||
const indent = commonIndent(rows).length
|
||||
return rows.map(row => row.slice(Math.min(indent, row.length))).join('\n')
|
||||
}
|
||||
return lines
|
||||
}
|
||||
|
||||
function leavesIn(node: Node, from: number, to: number, out: Node[]): void {
|
||||
@@ -130,27 +135,33 @@ function coloring(layers: readonly ColorLayer[], source: string): Coloring {
|
||||
return { owner, roles }
|
||||
}
|
||||
|
||||
/** 按语法树叶子给 [startRow, endRow] 这几行着色:每个有词性的叶子一段,空白、标点等 plain 的连成一段。 */
|
||||
function colorLines({ owner, roles }: Coloring, source: string, lines: Line[], startRow: number, endRow: number, indent: number): Segment[] {
|
||||
const segments: Segment[] = []
|
||||
let current = -2
|
||||
const put = (char: string, who: number): void => {
|
||||
const prev = segments.at(-1)
|
||||
/** 各着色层铺好后整份源码的着色(见 SourceColors):每个字符取它所在叶子的词性,不归哪个叶子的空白、标点是 plain。 */
|
||||
function sourceColors({ owner, roles }: Coloring): SourceColors {
|
||||
const colors: SourceColors = { roles: [], runs: [] }
|
||||
const order = new Map<TokenRole, number>()
|
||||
let current: TokenRole | undefined
|
||||
let length = 0
|
||||
const close = (): void => {
|
||||
if (current === undefined)
|
||||
return
|
||||
let index = order.get(current)
|
||||
if (index === undefined) {
|
||||
index = colors.roles.push(current) - 1
|
||||
order.set(current, index)
|
||||
}
|
||||
colors.runs.push(length, index)
|
||||
}
|
||||
for (const who of owner) {
|
||||
const role = who < 0 ? 'plain' : roles[who]
|
||||
if (prev && (who === current || (role === 'plain' && prev.role === 'plain')))
|
||||
prev.text += char
|
||||
else
|
||||
segments.push({ text: char, role })
|
||||
current = who
|
||||
if (role !== current) {
|
||||
close()
|
||||
current = role
|
||||
length = 0
|
||||
}
|
||||
length++
|
||||
}
|
||||
for (let row = startRow; row <= endRow; row++) {
|
||||
const line = lines[row]
|
||||
if (row > startRow)
|
||||
put('\n', owner[line.start - 1])
|
||||
for (let at = line.start + Math.min(indent, line.end - line.start); at < line.end; at++)
|
||||
put(source[at], owner[at])
|
||||
}
|
||||
return segments
|
||||
close()
|
||||
return colors
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -291,7 +302,7 @@ function withCommentRow(rows: readonly PropertyRow[], row: PropertyRow): Propert
|
||||
}
|
||||
|
||||
/**
|
||||
* 代码块树 → 视图数据:块的完整范围整行源码去共同缩进、按语法树着色,调用翻译插件。
|
||||
* 代码块树 → 视图数据:各块的编辑范围、翻译插件给的译文;块的源码、着色不按块带,页面从整份源码里按编辑范围取(见 SourceColors)。
|
||||
* 每块先问启用的库规则,认领了就换成规则给的替身(见 RuleBlock):规则的卡片,或认成的属性表块(交给翻译插件排,同语言插件报的这种块),
|
||||
* 并进去的子块不再单独成块。讲块的注释(BlockShape.comments)排成卡片上的「注释」一行(见 withCommentRow)。
|
||||
*/
|
||||
@@ -303,24 +314,9 @@ export function toBlockViews(
|
||||
embedded: EmbeddedParts = new Map(),
|
||||
): BlockView[] {
|
||||
const { source } = document
|
||||
const lines = lineRanges(source)
|
||||
const texts = splitLines(source)
|
||||
const outer = blocks[0]?.node.tree.rootNode
|
||||
const colors = coloring([
|
||||
...(outer ? [{ root: outer, tokenRole: document.language.tokenRole }] : []),
|
||||
...[...embedded.values()].map(part => ({ root: part.root, tokenRole: part.document.language.tokenRole })),
|
||||
], source)
|
||||
/** 一段占的整行源码去共同缩进,和它的着色(嵌着的代码按它那种语言着色)。 */
|
||||
const sourceRows = ({ startRow, endRow }: SourceSpan): Pick<BlockView, 'startLine' | 'endLine' | 'code' | 'codeTokens'> => {
|
||||
const rows = texts.slice(startRow, endRow + 1)
|
||||
const indent = commonIndent(rows).length
|
||||
return {
|
||||
startLine: startRow,
|
||||
endLine: endRow,
|
||||
code: rows.map(row => row.slice(Math.min(indent, row.length))).join('\n'),
|
||||
codeTokens: colorLines(colors, source, lines, startRow, endRow, indent),
|
||||
}
|
||||
}
|
||||
/** 一段占的行:块的编辑范围 */
|
||||
const spanLines = ({ startRow, endRow }: SourceSpan): Pick<BlockView, 'startLine' | 'endLine'> => ({ startLine: startRow, endLine: endRow })
|
||||
/** 一段占的整行源码:没名字的块按它认(见 BlockAnchors.place)。 */
|
||||
const rowsText = ({ startRow, endRow }: SourceSpan): string => texts.slice(startRow, endRow + 1).join('\n')
|
||||
/** 按源码先后逐块给位置记号:先序遍历,块的记号在它的子块之前要 */
|
||||
@@ -437,7 +433,7 @@ export function toBlockViews(
|
||||
id,
|
||||
anchor,
|
||||
kind: block.kind,
|
||||
...sourceRows(span),
|
||||
...spanLines(span),
|
||||
headerEndLine: block.header.endRow,
|
||||
...(name && { defines: { startIndex: name.startIndex, endIndex: name.endIndex } }),
|
||||
...(name && (block.kind === 'function' || block.kind === 'class') && { callTree: {} }),
|
||||
@@ -474,7 +470,7 @@ export function toBlockViews(
|
||||
id,
|
||||
anchor,
|
||||
kind,
|
||||
...sourceRows(span),
|
||||
...spanLines(span),
|
||||
headerEndLine: statement.endRow,
|
||||
...(defines && { defines: { startIndex: defines.startIndex, endIndex: defines.endIndex } }),
|
||||
...(defines && endpoint && { callTree: { endpoint } }),
|
||||
@@ -512,6 +508,8 @@ export function toBlockViews(
|
||||
*/
|
||||
export interface DocumentViews {
|
||||
blocks: BlockView[]
|
||||
/** 整份源码的着色(嵌着的代码按它那种语言着色) */
|
||||
colors: SourceColors
|
||||
displayNames: DisplayName[]
|
||||
symbols: LocalSymbol[]
|
||||
imports: ImportedName[]
|
||||
@@ -585,12 +583,16 @@ export async function readBlockViews(
|
||||
}
|
||||
const codeBlocks = adapter.toBlocks(tree.rootNode, source, languageId)
|
||||
const blocks = toBlockViews(codeBlocks, document, translator, extras.rules, parts)
|
||||
const colors = sourceColors(coloring([
|
||||
{ root: tree.rootNode, tokenRole: adapter.tokenRole },
|
||||
...[...parts.values()].map(part => ({ root: part.root, tokenRole: part.document.language.tokenRole })),
|
||||
], source))
|
||||
const symbols = [
|
||||
...adapter.symbols?.(tree.rootNode, source, languageId) ?? [],
|
||||
...[...parts.values()].flatMap(part => part.document.language.symbols?.(part.root, source, part.code.languageId) ?? []),
|
||||
]
|
||||
const bindings = adapter.bindings?.(tree.rootNode, source, languageId) ?? []
|
||||
return { blocks, displayNames: names, symbols, imports: importedNames(importsIn(codeBlocks)), kinds: namedKinds(codeBlocks), bindings }
|
||||
return { blocks, colors, displayNames: names, symbols, imports: importedNames(importsIn(codeBlocks)), kinds: namedKinds(codeBlocks), bindings }
|
||||
}
|
||||
finally {
|
||||
for (const parsed of trees)
|
||||
|
||||
Reference in new issue
Block a user