⚡ perf(vscode): 代码阅读预览注释里的引用解析过就记着:标坏链接、悬停、点击同一个引用只问一次 Pylance,同时问的等同一个结果,文档版本或块树、导入、用到的名字换了才作废,指到的别的文件改过再问,找不到和语言自带的不留;悬停看别的文件里的定义时读成的文档各预览共用,同一版不再重新解析,留最近 8 份,注册的插件变了全部重读

This commit is contained in:
cruldra committed 2026-10-14 19:57:20 +11:00
1 parent 3983032356
commit cfde0fedf5
9 files changed
+595 -70

No files matched your search

+38 -38
View File
@@ -10,7 +10,7 @@ import type { CodeQa, TopRequest } from '../qa/codeQa'
import type { CodeReaderRegistry } from '../registry'
import type { LookupSources } from '../ruleLookups'
import type { TreeSitterRuntime } from '../runtime'
import type { ImportedName, LinkTarget, ReferenceTarget, SymbolNode } from '../symbolLinks'
import type { ImportedName, LinkTarget, ReferenceTarget } from '../symbolLinks'
import type { ThemeColors } from '../theme/codeColors'
import type { IndexFind } from '../workspaceIndex'
import type { BlockFilter } from './blockFilter'
@@ -37,18 +37,20 @@ import { NOT_REWRITABLE } from '../qa/codeQa'
import { PATH_ASK_ANCHOR } from '../qa/pathQa'
import { askPrompt, followUpPrompt } from '../qa/prompt'
import { DocumentLookups } from '../ruleLookups'
import { outsideUses, referenceScope, resolveReference } from '../symbolLinks'
import { outsideUses } from '../symbolLinks'
import { definitionAction } from './definitionAction'
import { definitionSiteNames, namesOnCard, pagePeekBlock, peekBlock } from './definitionPeek'
import { definitionKind, findDefinitions, languageDefinitions, pickDefinition, ruleDefinitions } from './definitions'
import { DefinitionSources } from './definitionSources'
import { documentMessage, pageDocument } from './documentMessage'
import { DocumentReads } from './documentReads'
import { documentSymbolNodes } from './documentSymbols'
import { FIND_CONTEXT } from './findCommands'
import { NavigationContext, OCCURRENCES_CONTEXT } from './occurrenceNavigation'
import { occurrencesOf } from './occurrences'
import { languageOf, linesSpot, openingFor, revealOf } from './openTarget'
import { PATH_PREVIEW_SCHEME, topSubjectOf } from './pathPreview'
import { ReferenceTargets } from './referenceTargets'
import { findRepoSymbol, isRepoImagePath, repoRootOf, resolveRepoEntry } from './repoPath'
export const PREVIEW_VIEW_TYPE = 'superpowers.codeReader.preview'
@@ -85,6 +87,8 @@ export class PreviewEditorProvider implements CustomTextEditorProvider {
readonly finding = new NavigationContext<Preview>(value => void commands.executeCommand('setContext', FIND_CONTEXT, value))
/** 调用的名字的定义是哪种块:各预览共用,定义所在的文件同一版只解析一次 */
readonly definitionKinds: DefinitionKinds
/** 悬停看别的文件里的定义时那份文件读成的文档:各预览共用,同一版不再重新解析 */
readonly documentReads: DocumentReads
constructor(
private readonly extensionUri: Uri,
@@ -111,6 +115,7 @@ export class PreviewEditorProvider implements CustomTextEditorProvider {
readonly askFeature: (subject: Uri, request: TopRequest) => Promise<string | undefined>,
) {
this.definitionKinds = new DefinitionKinds(languageId => registry.languageFor(languageId), runtime)
this.documentReads = new DocumentReads(registry, runtime)
}
/**
@@ -241,6 +246,11 @@ class Preview {
offset => this.requests.distance(this.document.positionAt(offset).line),
)
/**
* 注释里不带路径的引用解析过的指到哪(见 [ReferenceTargets])。文档符号问这种语言的扩展(同仓库链接找符号),导入的、代码里用到的名字问语言扩展它的定义
* (同跳到定义,几处取第一处),哪些名字是语言自带的问语言插件([LanguageAdapter.builtins](vscode/src/codeReader/contract.ts#LanguageAdapter.builtins))
*/
private readonly references: ReferenceTargets
/** 最近一次发出去的文档里语言插件自己认得出的名字(LanguageAdapter.symbols):语言扩展查不到定义、各处出现时用 */
private symbols: { version: number, list: LocalSymbol[] } | undefined
/** 最近一次发出去的文档里各条导入引进来的名字:注释里不带路径的引用按它找导入的定义 */
@@ -283,6 +293,19 @@ class Preview {
return found.length > 0 ? found : this.localDefinitions(offset)
},
})
this.references = new ReferenceTargets({
uri: document.uri.toString(),
symbols: documentSymbolNodes,
definition: async (offset) => {
const [found] = await languageDefinitions(document.uri, document.positionAt(offset))
if (!found)
return undefined
const doc = await workspace.openTextDocument(found.uri)
return { uri: found.uri.toString(), selection: { startIndex: doc.offsetAt(found.range.start), endIndex: doc.offsetAt(found.range.end) } }
},
builtin: name => registry.languageFor(document.languageId)?.builtins?.has(name) ?? false,
versionOf: uri => workspace.textDocuments.find(doc => doc.uri.toString() === uri)?.version,
})
this.names = new DocumentNames((version, names) => {
if (version === document.version)
this.post({ type: 'displayNames', version, names })
@@ -870,8 +893,8 @@ class Preview {
}
/**
* uri 里 offset 处的定义的卡片:盖住它的那块(peekBlock)。同一份文件用最近发出去的那份文档的块和中文名;
* 别的文件打开它、按它的语言排视图数据(同它自己的预览:翻译插件、库规则、项目入口;规则查名字的定义、调用的种类不等),
* `uri` 里 `offset` 处的定义的卡片:盖住它的那块([peekBlock])。同一份文件用最近发出去的那份文档的块和中文名;
* 别的文件按它的语言读成文档(翻译插件、库规则、项目入口同它自己的预览,规则查名字的定义、调用的种类不等),读过的各预览共用(见 [DocumentReads]),
* 中文名只认起了中文名的定义本身。语言没有代码阅读、解析失败、没有块盖住定义处时没有。
*/
private async peekAt(uri: Uri, offset: number): Promise<DefinitionPeek | undefined> {
@@ -886,12 +909,9 @@ class Preview {
const block = peekBlock(sent.blocks, offset, line)
return block && { path, line, block: pagePeekBlock(block, sent.source), names: namesOnCard(block, this.names.known(target.version)) }
}
const { message, displayNames } = await documentMessage(
const { message, displayNames } = await this.owner.documentReads.read(
uri.toString(),
{ languageId: target.languageId, version: target.version, getText: () => target.getText(), fileName: target.fileName, entryNames: this.owner.entries.namesOf(uri) },
this.registry,
this.runtime,
// 那个文件里规则出的错由它自己的预览记
() => {},
)
const block = message.error === undefined ? peekBlock(message.blocks, offset, line) : undefined
return block && { path, line, block: pagePeekBlock(block, message.source), names: namesOnCard(block, definitionSiteNames(displayNames)) }
@@ -933,19 +953,13 @@ class Preview {
/** 预览里这批仓库路径,哪些文件或目录不在;这批不带路径的引用,各按写在哪块找,哪些找不到、哪些是语言自带的名字(页面当普通文字)。 */
private async checkRepoLinks(id: number, paths: string[], references: SymbolReference[]): Promise<void> {
const root = await repoRootOf(this.document.uri.fsPath)
const asked = new Map<string, Promise<SymbolNode[]>>()
const symbolsOf = (uri: string): Promise<SymbolNode[]> => {
const known = asked.get(uri) ?? documentSymbolNodes(uri)
asked.set(uri, known)
return known
}
const [missing, resolved] = await Promise.all([
Promise.all(paths.map(async (rel) => {
if (root === undefined || await resolveRepoEntry(root, rel) === undefined)
return rel
return undefined
})),
Promise.all(references.map(async reference => ({ reference, target: await this.resolveSymbolLink(reference, symbolsOf) }))),
Promise.all(references.map(async reference => ({ reference, target: await this.resolveSymbolLink(reference) }))),
])
this.post({
type: 'repoLinksChecked',
@@ -1062,32 +1076,18 @@ class Preview {
}
/**
* 注释里不带路径的引用(`[Name]`、`[Class.member]`)指到哪:从它写在的那块(`block`,在眼下这份块树里找)的声明起一层层往外找本文件定义的,
* 再是导入进来的、代码里用到的语言自带的名字,语言自带又问不到定义的是 `'builtin'`(见 [resolveReference])。检查坏链接、点击、悬停看定义卡片都走这里。
* 文档符号同仓库链接找符号那一套(`symbols`,一批引用一起问时共用一份),导入的、代码里用到的名字问语言扩展它的定义(同跳到定义),定义几处取第一处;
* 哪些名字是语言自带的问这门语言的语言插件([LanguageAdapter.builtins](vscode/src/codeReader/contract.ts#LanguageAdapter.builtins))。
* 注释里不带路径的引用(`[Name]`、`[Class.member]`)指到哪,检查坏链接、点击、悬停看定义卡片都走这里;解析过的记在 [ReferenceTargets] 里,不再问语言扩展。
*
* 从它写在的那块(在最近发出去的块树里找)的声明起一层层往外找本文件定义的,再找导入的、代码里用到的语言自带的名字,
* 语言自带又问不到定义的是 `'builtin'`(见 [解析引用](vscode/src/codeReader/symbolLinks.ts#resolveReference))。出错时记一笔,当找不到。
*/
private async resolveSymbolLink({ symbol, block }: SymbolReference, symbols: (uri: string) => Promise<SymbolNode[]> = documentSymbolNodes): Promise<ReferenceTarget> {
const { document, sent, uses } = this
const at = block === undefined || !sent ? undefined : referenceScope(sent.blocks, block)
const builtins = this.registry.languageFor(document.languageId)?.builtins
private async resolveSymbolLink(reference: SymbolReference): Promise<ReferenceTarget> {
const { document, sent, imports, uses } = this
try {
return await resolveReference(symbol, at, this.imports, {
uri: document.uri.toString(),
symbols,
definition: async (offset) => {
const [found] = await languageDefinitions(document.uri, document.positionAt(offset))
if (!found)
return undefined
const doc = await workspace.openTextDocument(found.uri)
return { uri: found.uri.toString(), selection: { startIndex: doc.offsetAt(found.range.start), endIndex: doc.offsetAt(found.range.end) } }
},
uses: name => uses.get(name) ?? [],
builtin: name => builtins?.has(name) ?? false,
})
return await this.references.resolve({ version: document.version, sent, imports, uses }, reference)
}
catch (err) {
logger.add({ level: 'warn', source: 'codeReader', message: `没能找注释里的引用 [${symbol}]:${err instanceof Error ? err.message : String(err)}` })
logger.add({ level: 'warn', source: 'codeReader', message: `没能找注释里的引用 [${reference.symbol}]:${err instanceof Error ? err.message : String(err)}` })
return undefined
}
}