⚡ perf(vscode): 认调用分新建还是调用时,定义所在的文件按版本记着解析结果,同一版不再整份重解析;定义就在本文件时用发文档时读出的
This commit is contained in:
1 parent
80d9962856
commit
e9c1c67d6f
8 files changed
+194
-45
No files matched your search
@@ -1,7 +1,7 @@
|
|||||||
import type { BlockView } from './blockView'
|
import type { BlockView } from './blockView'
|
||||||
import type { LanguageAdapter, LibraryRule, Translator } from './contract'
|
import type { LanguageAdapter, LibraryRule, Translator } from './contract'
|
||||||
import { describe, expect, it } from 'vitest'
|
import { describe, expect, it } from 'vitest'
|
||||||
import { readBlockViews, readDefinitionKind, toBlockViews } from './blockView'
|
import { kindAt, readBlockViews, readNamedKinds, toBlockViews } from './blockView'
|
||||||
import { replaceLines } from './editText'
|
import { replaceLines } from './editText'
|
||||||
import { createBashAdapter } from './languages/bash'
|
import { createBashAdapter } from './languages/bash'
|
||||||
import { createDockerfileAdapter } from './languages/dockerfile'
|
import { createDockerfileAdapter } from './languages/dockerfile'
|
||||||
@@ -405,14 +405,16 @@ describe('blockView', () => {
|
|||||||
expect(view?.attributes.kind).toBe('demo.thing')
|
expect(view?.attributes.kind).toBe('demo.thing')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('readDefinitionKind:offset 落在哪块自己的名字上就是那块的种类,里层的块优先;不在名字上没有', async () => {
|
it('kindAt:offset 落在哪块自己的名字上就是那块的种类,里层的块优先;不在名字上没有。readBlockViews 顺带交出同样的一份', async () => {
|
||||||
const source = 'class UserService:\n def update_user(self):\n pass\n\nclass NotFound(Exception):\n pass\n\nlimit = 3\n'
|
const source = 'class UserService:\n def update_user(self):\n pass\n\nclass NotFound(Exception):\n pass\n\nlimit = 3\n'
|
||||||
const kind = (text: string): Promise<string | undefined> => readDefinitionKind(runtime, py, 'python', source, source.indexOf(text))
|
const kinds = await readNamedKinds(runtime, py, 'python', source)
|
||||||
expect(await kind('UserService')).toBe('class')
|
const kind = (text: string): string | undefined => kindAt(kinds, source.indexOf(text))
|
||||||
expect(await kind('update_user')).toBe('function')
|
expect(kind('UserService')).toBe('class')
|
||||||
expect(await kind('NotFound')).toBe('exception')
|
expect(kind('update_user')).toBe('function')
|
||||||
expect(await kind('limit')).toBe('variable')
|
expect(kind('NotFound')).toBe('exception')
|
||||||
expect(await kind('pass')).toBeUndefined()
|
expect(kind('limit')).toBe('variable')
|
||||||
|
expect(kind('pass')).toBeUndefined()
|
||||||
|
expect((await readBlockViews(runtime, py, pythonZhTranslator, 'python', source)).kinds).toEqual(kinds)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -507,13 +507,14 @@ export function toBlockViews(
|
|||||||
/**
|
/**
|
||||||
* 一份源码的视图数据,和它里面起了中文名的定义(预览找用到它们的各处要用,见 displayNames.ts)、
|
* 一份源码的视图数据,和它里面起了中文名的定义(预览找用到它们的各处要用,见 displayNames.ts)、
|
||||||
* 语言插件自己认得出的名字(LanguageAdapter.symbols,语言扩展查不到定义、各处出现时用)、
|
* 语言插件自己认得出的名字(LanguageAdapter.symbols,语言扩展查不到定义、各处出现时用)、
|
||||||
* 各条导入引进来的名字(注释里不带路径的引用按它找导入的定义,见 symbolLinks.ts)。
|
* 各条导入引进来的名字(注释里不带路径的引用按它找导入的定义,见 symbolLinks.ts)、各块自己的名字和种类(调用的定义就在这份源码里时,分新建还是调用用它,见 NamedKind)。
|
||||||
*/
|
*/
|
||||||
export interface DocumentViews {
|
export interface DocumentViews {
|
||||||
blocks: BlockView[]
|
blocks: BlockView[]
|
||||||
displayNames: DisplayName[]
|
displayNames: DisplayName[]
|
||||||
symbols: LocalSymbol[]
|
symbols: LocalSymbol[]
|
||||||
imports: ImportedName[]
|
imports: ImportedName[]
|
||||||
|
kinds: NamedKind[]
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -586,7 +587,7 @@ export async function readBlockViews(
|
|||||||
...adapter.symbols?.(tree.rootNode, source, languageId) ?? [],
|
...adapter.symbols?.(tree.rootNode, source, languageId) ?? [],
|
||||||
...[...parts.values()].flatMap(part => part.document.language.symbols?.(part.root, source, part.code.languageId) ?? []),
|
...[...parts.values()].flatMap(part => part.document.language.symbols?.(part.root, source, part.code.languageId) ?? []),
|
||||||
]
|
]
|
||||||
return { blocks, displayNames: names, symbols, imports: importedNames(importsIn(codeBlocks)) }
|
return { blocks, displayNames: names, symbols, imports: importedNames(importsIn(codeBlocks)), kinds: namedKinds(codeBlocks) }
|
||||||
}
|
}
|
||||||
finally {
|
finally {
|
||||||
for (const parsed of trees)
|
for (const parsed of trees)
|
||||||
@@ -594,30 +595,34 @@ export async function readBlockViews(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 块树里自己的名字(见 ownName)盖住 offset 的最里层那块的种类;offset 不在哪块的名字上时没有。 */
|
/** 块树里各块自己的名字(见 ownName)在源码里的范围和块的种类,子块排在父块前面(后序):盖住某个下标的第一个就是最里层那块。 */
|
||||||
export function blockKindAt(blocks: readonly CodeBlock[], offset: number): BlockKind | undefined {
|
export interface NamedKind extends SourceRange {
|
||||||
for (const block of blocks) {
|
kind: BlockKind
|
||||||
const inner = blockKindAt(block.children, offset)
|
|
||||||
if (inner)
|
|
||||||
return inner
|
|
||||||
const name = ownName(block)
|
|
||||||
if (name && name.startIndex <= offset && offset < name.endIndex)
|
|
||||||
return block.kind
|
|
||||||
}
|
|
||||||
return undefined
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 解析一份源码,offset 处的名字定义的是哪种块(见 blockKindAt);语法树用完即释放。 */
|
/** 块树里有自己名字的块,按后序排(见 NamedKind)。 */
|
||||||
export async function readDefinitionKind(
|
export function namedKinds(blocks: readonly CodeBlock[]): NamedKind[] {
|
||||||
|
return blocks.flatMap((block) => {
|
||||||
|
const name = ownName(block)
|
||||||
|
return [...namedKinds(block.children), ...(name ? [{ startIndex: name.startIndex, endIndex: name.endIndex, kind: block.kind }] : [])]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 自己的名字盖住 offset 的最里层那块的种类;offset 不在哪块的名字上时没有。 */
|
||||||
|
export function kindAt(kinds: readonly NamedKind[], offset: number): BlockKind | undefined {
|
||||||
|
return kinds.find(name => name.startIndex <= offset && offset < name.endIndex)?.kind
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 解析一份源码,读出各块自己的名字和种类(见 NamedKind);语法树用完即释放。 */
|
||||||
|
export async function readNamedKinds(
|
||||||
runtime: Pick<TreeSitterRuntime, 'parse'>,
|
runtime: Pick<TreeSitterRuntime, 'parse'>,
|
||||||
adapter: LanguageAdapter,
|
adapter: LanguageAdapter,
|
||||||
languageId: string,
|
languageId: string,
|
||||||
source: string,
|
source: string,
|
||||||
offset: number,
|
): Promise<NamedKind[]> {
|
||||||
): Promise<BlockKind | undefined> {
|
|
||||||
const tree = await runtime.parse(adapter, languageId, source)
|
const tree = await runtime.parse(adapter, languageId, source)
|
||||||
try {
|
try {
|
||||||
return blockKindAt(adapter.toBlocks(tree.rootNode, source, languageId), offset)
|
return namedKinds(adapter.toBlocks(tree.rootNode, source, languageId))
|
||||||
}
|
}
|
||||||
finally {
|
finally {
|
||||||
tree.delete()
|
tree.delete()
|
||||||
|
|||||||
@@ -0,0 +1,66 @@
|
|||||||
|
import type { KindSource } from './definitionKinds'
|
||||||
|
import type { TreeSitterRuntime } from './runtime'
|
||||||
|
import { describe, expect, it } from 'vitest'
|
||||||
|
import { readNamedKinds } from './blockView'
|
||||||
|
import { DefinitionKinds } from './definitionKinds'
|
||||||
|
import { createPythonAdapter } from './languages/python'
|
||||||
|
import { runtime, WASM_DIR } from './testSupport'
|
||||||
|
|
||||||
|
const py = createPythonAdapter(WASM_DIR)
|
||||||
|
const SOURCE = 'class UserService:\n def update_user(self):\n pass\n'
|
||||||
|
|
||||||
|
/** 数着解析了几次的解析运行时。 */
|
||||||
|
function counting(): { runtime: Pick<TreeSitterRuntime, 'parse'>, parses: () => number } {
|
||||||
|
let parses = 0
|
||||||
|
return {
|
||||||
|
runtime: {
|
||||||
|
parse: (...args) => {
|
||||||
|
parses++
|
||||||
|
return runtime.parse(...args)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
parses: () => parses,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function file(name: string, version: number, text = SOURCE): KindSource {
|
||||||
|
return { uri: `file:///${name}.py`, version, languageId: 'python', getText: () => text }
|
||||||
|
}
|
||||||
|
|
||||||
|
const at = (text: string): number => SOURCE.indexOf(text)
|
||||||
|
|
||||||
|
describe('定义处是哪种块(DefinitionKinds)', () => {
|
||||||
|
it('同一份文件同一版只解析一次,几处同时问也只解析一次;文件改了(版本变了)再解析', async () => {
|
||||||
|
const { runtime: parser, parses } = counting()
|
||||||
|
const kinds = new DefinitionKinds(languageId => languageId === 'python' ? py : undefined, parser)
|
||||||
|
expect(await Promise.all([kinds.kindAt(file('a', 1), at('UserService')), kinds.kindAt(file('a', 1), at('update_user'))])).toEqual(['class', 'function'])
|
||||||
|
expect(await kinds.kindAt(file('a', 1), at('pass'))).toBeUndefined()
|
||||||
|
expect(parses()).toBe(1)
|
||||||
|
expect(await kinds.kindAt(file('a', 2, 'def UserService():\n pass\n'), 4)).toBe('function')
|
||||||
|
expect(parses()).toBe(2)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('预览发文档时交进来的那份(remember)直接用,不再解析;没有语言插件的文件没有', async () => {
|
||||||
|
const { runtime: parser, parses } = counting()
|
||||||
|
const kinds = new DefinitionKinds(languageId => languageId === 'python' ? py : undefined, parser)
|
||||||
|
kinds.remember('file:///a.py', 3, await readNamedKinds(runtime, py, 'python', SOURCE))
|
||||||
|
expect(await kinds.kindAt(file('a', 3), at('update_user'))).toBe('function')
|
||||||
|
expect(parses()).toBe(0)
|
||||||
|
expect(await kinds.kindAt({ uri: 'file:///b.txt', version: 1, languageId: 'plaintext', getText: () => 'x' }, 0)).toBeUndefined()
|
||||||
|
expect(parses()).toBe(0)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('最多记 limit 份文件,最久没用过的先丢', async () => {
|
||||||
|
const { runtime: parser, parses } = counting()
|
||||||
|
const kinds = new DefinitionKinds(languageId => languageId === 'python' ? py : undefined, parser, 2)
|
||||||
|
await kinds.kindAt(file('a', 1), 0)
|
||||||
|
await kinds.kindAt(file('b', 1), 0)
|
||||||
|
await kinds.kindAt(file('a', 1), 0)
|
||||||
|
await kinds.kindAt(file('c', 1), 0)
|
||||||
|
expect(parses()).toBe(3)
|
||||||
|
await kinds.kindAt(file('a', 1), 0)
|
||||||
|
expect(parses()).toBe(3)
|
||||||
|
await kinds.kindAt(file('b', 1), 0)
|
||||||
|
expect(parses()).toBe(4)
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
import type { NamedKind } from './blockView'
|
||||||
|
import type { BlockKind, LanguageAdapter } from './contract'
|
||||||
|
import type { TreeSitterRuntime } from './runtime'
|
||||||
|
import { kindAt, readNamedKinds } from './blockView'
|
||||||
|
|
||||||
|
/** 定义所在的那份文件(TextDocument 的子集):uri、版本、语言、内容。 */
|
||||||
|
export interface KindSource {
|
||||||
|
uri: string
|
||||||
|
version: number
|
||||||
|
languageId: string
|
||||||
|
getText: () => string
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 默认记几份文件 */
|
||||||
|
const LIMIT = 64
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 调用的名字的定义是哪种块(分新建还是调用):定义所在的文件解析一遍,按 uri 记着这一版各块的名字和种类(见 NamedKind),
|
||||||
|
* 同一版再问不再解析,几处同时问同一版也只解析一次;预览发文档时把读出的那份交进来(remember),定义就在这份文档里时直接用。
|
||||||
|
* 最近用过的留 limit 份。这里不碰 VS Code。
|
||||||
|
*/
|
||||||
|
export class DefinitionKinds {
|
||||||
|
private readonly files = new Map<string, { version: number, kinds: Promise<NamedKind[] | undefined> }>()
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private readonly languageFor: (languageId: string) => LanguageAdapter | undefined,
|
||||||
|
private readonly runtime: Pick<TreeSitterRuntime, 'parse'>,
|
||||||
|
private readonly limit = LIMIT,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
/** uri 这份文件 version 这一版各块的名字和种类(预览发文档时读出的)。 */
|
||||||
|
remember(uri: string, version: number, kinds: NamedKind[]): void {
|
||||||
|
this.keep(uri, version, Promise.resolve(kinds))
|
||||||
|
}
|
||||||
|
|
||||||
|
/** file 里 offset 处的名字定义的是哪种块;没有语言插件、不在哪块的名字上时没有。 */
|
||||||
|
async kindAt(file: KindSource, offset: number): Promise<BlockKind | undefined> {
|
||||||
|
const kinds = await this.kindsOf(file)
|
||||||
|
return kinds && kindAt(kinds, offset)
|
||||||
|
}
|
||||||
|
|
||||||
|
private kindsOf(file: KindSource): Promise<NamedKind[] | undefined> {
|
||||||
|
const known = this.files.get(file.uri)
|
||||||
|
if (known?.version === file.version) {
|
||||||
|
this.keep(file.uri, known.version, known.kinds)
|
||||||
|
return known.kinds
|
||||||
|
}
|
||||||
|
const adapter = this.languageFor(file.languageId)
|
||||||
|
if (!adapter)
|
||||||
|
return Promise.resolve(undefined)
|
||||||
|
const kinds = readNamedKinds(this.runtime, adapter, file.languageId, file.getText())
|
||||||
|
this.keep(file.uri, file.version, kinds)
|
||||||
|
// 解析失败的不记,下次重来
|
||||||
|
kinds.catch(() => {
|
||||||
|
if (this.files.get(file.uri)?.kinds === kinds)
|
||||||
|
this.files.delete(file.uri)
|
||||||
|
})
|
||||||
|
return kinds
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 记下(或用到了)一份:挪到最近用过的那头,多了丢最久没用的。 */
|
||||||
|
private keep(uri: string, version: number, kinds: Promise<NamedKind[] | undefined>): void {
|
||||||
|
this.files.delete(uri)
|
||||||
|
this.files.set(uri, { version, kinds })
|
||||||
|
for (const oldest of this.files.keys()) {
|
||||||
|
if (this.files.size <= this.limit)
|
||||||
|
break
|
||||||
|
this.files.delete(oldest)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,10 +1,9 @@
|
|||||||
import type { Location, LocationLink, Position } from 'vscode'
|
import type { Location, LocationLink, Position } from 'vscode'
|
||||||
import type { BlockKind, LanguageAdapter, RuleLocation } from '../contract'
|
import type { BlockKind, RuleLocation } from '../contract'
|
||||||
import type { TreeSitterRuntime } from '../runtime'
|
import type { DefinitionKinds } from '../definitionKinds'
|
||||||
import type { DefinitionTarget } from './definitionAction'
|
import type { DefinitionTarget } from './definitionAction'
|
||||||
import { commands, Range, Uri, window, workspace } from 'vscode'
|
import { commands, Range, Uri, window, workspace } from 'vscode'
|
||||||
import { logger } from '../../logging/logger'
|
import { logger } from '../../logging/logger'
|
||||||
import { readDefinitionKind } from '../blockView'
|
|
||||||
|
|
||||||
/** 查到的一处定义:文件和定义处那个名字的范围。 */
|
/** 查到的一处定义:文件和定义处那个名字的范围。 */
|
||||||
export interface FoundLocation { uri: Uri, range: Range }
|
export interface FoundLocation { uri: Uri, range: Range }
|
||||||
@@ -131,16 +130,12 @@ export function ruleDefinitions(locations: readonly RuleLocation[]): FoundLocati
|
|||||||
return locations.map(({ uri, start, end }) => ({ uri: Uri.parse(uri), range: new Range(start.line, start.character, end.line, end.character) }))
|
return locations.map(({ uri, start, end }) => ({ uri: Uri.parse(uri), range: new Range(start.line, start.character, end.line, end.character) }))
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 定义处那个名字定义的是哪种块:打开目标文件,用它那种语言的语言插件分块认;目标打不开、语言不支持、不在哪块的名字上时没有。 */
|
/** 定义处那个名字定义的是哪种块:打开目标文件,按它这一版各块的名字认(见 DefinitionKinds);目标打不开、语言不支持、不在哪块的名字上时没有。 */
|
||||||
export async function definitionKind(
|
export async function definitionKind(location: FoundLocation, kinds: DefinitionKinds): Promise<BlockKind | undefined> {
|
||||||
location: FoundLocation,
|
|
||||||
languageFor: (languageId: string) => LanguageAdapter | undefined,
|
|
||||||
runtime: Pick<TreeSitterRuntime, 'parse'>,
|
|
||||||
): Promise<BlockKind | undefined> {
|
|
||||||
try {
|
try {
|
||||||
const document = await workspace.openTextDocument(location.uri)
|
const document = await workspace.openTextDocument(location.uri)
|
||||||
const adapter = languageFor(document.languageId)
|
const file = { uri: document.uri.toString(), version: document.version, languageId: document.languageId, getText: () => document.getText() }
|
||||||
return adapter && await readDefinitionKind(runtime, adapter, document.languageId, document.getText(), document.offsetAt(location.range.start))
|
return await kinds.kindAt(file, document.offsetAt(location.range.start))
|
||||||
}
|
}
|
||||||
catch (err) {
|
catch (err) {
|
||||||
logger.add({ level: 'warn', source: 'codeReader', message: `认不出 ${location.uri.toString()} 里定义的是哪种块:${err instanceof Error ? err.message : String(err)}` })
|
logger.add({ level: 'warn', source: 'codeReader', message: `认不出 ${location.uri.toString()} 里定义的是哪种块:${err instanceof Error ? err.message : String(err)}` })
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import type { NamedKind } from '../blockView'
|
||||||
import type { DisplayName, LocalSymbol, RuleContext, SourceDocument } from '../contract'
|
import type { DisplayName, LocalSymbol, RuleContext, SourceDocument } from '../contract'
|
||||||
import type { CodeReaderRegistry } from '../registry'
|
import type { CodeReaderRegistry } from '../registry'
|
||||||
import type { TreeSitterRuntime } from '../runtime'
|
import type { TreeSitterRuntime } from '../runtime'
|
||||||
@@ -22,13 +23,14 @@ export interface DocumentSnapshot {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 发给预览的文档,和这份源码里起了中文名的定义(扩展端接着找用到它们的各处)、语言插件自己认得出的名字(LocalSymbol)、
|
* 发给预览的文档,和这份源码里起了中文名的定义(扩展端接着找用到它们的各处)、语言插件自己认得出的名字(LocalSymbol)、
|
||||||
* 各条导入引进来的名字(注释里不带路径的引用按它找导入的定义)。
|
* 各条导入引进来的名字(注释里不带路径的引用按它找导入的定义)、各块自己的名字和种类(调用的定义就在这份文档里时认种类用,见 NamedKind)。
|
||||||
*/
|
*/
|
||||||
export interface DocumentRead {
|
export interface DocumentRead {
|
||||||
message: DocumentMessage
|
message: DocumentMessage
|
||||||
displayNames: DisplayName[]
|
displayNames: DisplayName[]
|
||||||
symbols: LocalSymbol[]
|
symbols: LocalSymbol[]
|
||||||
imports: ImportedName[]
|
imports: ImportedName[]
|
||||||
|
kinds: NamedKind[]
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -60,10 +62,10 @@ export async function documentMessage(
|
|||||||
}
|
}
|
||||||
const adapter = registry.languageFor(languageId)
|
const adapter = registry.languageFor(languageId)
|
||||||
if (!adapter)
|
if (!adapter)
|
||||||
return { message: { ...message, error: `代码阅读还不支持「${languageId}」这种语言` }, displayNames: [], symbols: [], imports: [] }
|
return { message: { ...message, error: `代码阅读还不支持「${languageId}」这种语言` }, displayNames: [], symbols: [], imports: [], kinds: [] }
|
||||||
try {
|
try {
|
||||||
const rules = registry.rulesFor(languageId)
|
const rules = registry.rulesFor(languageId)
|
||||||
const { blocks, displayNames, symbols, imports } = await readBlockViews(runtime, adapter, translator, languageId, source, {
|
const { blocks, displayNames, symbols, imports, kinds } = await readBlockViews(runtime, adapter, translator, languageId, source, {
|
||||||
...(document.fileName && { fileName: document.fileName }),
|
...(document.fileName && { fileName: document.fileName }),
|
||||||
rules: { rules, report, lookup },
|
rules: { rules, report, lookup },
|
||||||
calleeKind,
|
calleeKind,
|
||||||
@@ -78,9 +80,9 @@ export async function documentMessage(
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
const names = mergeAttributeNames([translator?.attributeNames, ...ruleAttributeNames(rules, translator?.displayLanguage, report)])
|
const names = mergeAttributeNames([translator?.attributeNames, ...ruleAttributeNames(rules, translator?.displayLanguage, report)])
|
||||||
return { message: { ...message, blocks: markEntryBlocks(blocks, source, document.entryNames ?? []), attributes: fileAttributes(blocks, names) }, displayNames, symbols, imports }
|
return { message: { ...message, blocks: markEntryBlocks(blocks, source, document.entryNames ?? []), attributes: fileAttributes(blocks, names) }, displayNames, symbols, imports, kinds }
|
||||||
}
|
}
|
||||||
catch (err) {
|
catch (err) {
|
||||||
return { message: { ...message, error: err instanceof Error ? err.message : String(err) }, displayNames: [], symbols: [], imports: [] }
|
return { message: { ...message, error: err instanceof Error ? err.message : String(err) }, displayNames: [], symbols: [], imports: [], kinds: [] }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -44,6 +44,7 @@ vi.mock('./documentMessage', () => ({
|
|||||||
displayNames: [],
|
displayNames: [],
|
||||||
symbols: [],
|
symbols: [],
|
||||||
imports: [],
|
imports: [],
|
||||||
|
kinds: [],
|
||||||
})),
|
})),
|
||||||
}))
|
}))
|
||||||
/** 文档符号由各个测试给 */
|
/** 文档符号由各个测试给 */
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ import { DocumentCallees } from '../calleeKinds'
|
|||||||
import { loadBlockCalls } from '../callTree/query'
|
import { loadBlockCalls } from '../callTree/query'
|
||||||
import { CallTreeSession } from '../callTree/session'
|
import { CallTreeSession } from '../callTree/session'
|
||||||
import { commentFollowUp, commentJob, commentRewritePrompt, findBlock, writeCommentBody } from '../commentRewrite'
|
import { commentFollowUp, commentJob, commentRewritePrompt, findBlock, writeCommentBody } from '../commentRewrite'
|
||||||
|
import { DefinitionKinds } from '../definitionKinds'
|
||||||
import { PreviewDiff } from '../diff/previewDiff'
|
import { PreviewDiff } from '../diff/previewDiff'
|
||||||
import { blockReplacement } from '../editText'
|
import { blockReplacement } from '../editText'
|
||||||
import { locate } from '../folderStore'
|
import { locate } from '../folderStore'
|
||||||
@@ -77,6 +78,8 @@ export class PreviewEditorProvider implements CustomTextEditorProvider {
|
|||||||
private readonly requests = new Map<string, OpenRequest>()
|
private readonly requests = new Map<string, OpenRequest>()
|
||||||
/** 哪个预览眼下能用键盘在高亮的各处出现之间跳,跟着它设快捷键 when 子句用的上下文键 */
|
/** 哪个预览眼下能用键盘在高亮的各处出现之间跳,跟着它设快捷键 when 子句用的上下文键 */
|
||||||
readonly navigation = new NavigationContext<Preview>(value => void commands.executeCommand('setContext', OCCURRENCES_CONTEXT, value))
|
readonly navigation = new NavigationContext<Preview>(value => void commands.executeCommand('setContext', OCCURRENCES_CONTEXT, value))
|
||||||
|
/** 调用的名字的定义是哪种块:各预览共用,定义所在的文件同一版只解析一次 */
|
||||||
|
readonly definitionKinds: DefinitionKinds
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private readonly extensionUri: Uri,
|
private readonly extensionUri: Uri,
|
||||||
@@ -101,7 +104,9 @@ export class PreviewEditorProvider implements CustomTextEditorProvider {
|
|||||||
readonly revealInTree: (uri: Uri) => Promise<boolean>,
|
readonly revealInTree: (uri: Uri) => Promise<boolean>,
|
||||||
/** 「了解一块代码」的对话页里接着说(subject 是那个功能,见 featureSubjectUri);说不了时返回给人看的原因 */
|
/** 「了解一块代码」的对话页里接着说(subject 是那个功能,见 featureSubjectUri);说不了时返回给人看的原因 */
|
||||||
readonly askFeature: (subject: Uri, request: TopRequest) => Promise<string | undefined>,
|
readonly askFeature: (subject: Uri, request: TopRequest) => Promise<string | undefined>,
|
||||||
) {}
|
) {
|
||||||
|
this.definitionKinds = new DefinitionKinds(languageId => registry.languageFor(languageId), runtime)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 注册成预览编辑器。标签页切走时页面留着(`retainContextWhenHidden`):切回来不用重新生成、重发文档(切走期间改过的才补一次),页面也不用整份重画,
|
* 注册成预览编辑器。标签页切走时页面留着(`retainContextWhenHidden`):切回来不用重新生成、重发文档(切走期间改过的才补一次),页面也不用整份重画,
|
||||||
@@ -404,12 +409,14 @@ class Preview {
|
|||||||
const source = this.document.getText()
|
const source = this.document.getText()
|
||||||
const session = this.lookups.session()
|
const session = this.lookups.session()
|
||||||
const callees = this.callees.session()
|
const callees = this.callees.session()
|
||||||
const { message, displayNames, symbols, imports } = await documentMessage({ languageId, version, getText: () => source, fileName: this.document.fileName, entryNames: this.owner.entries.namesOf(this.document.uri) }, this.registry, this.runtime, (text) => {
|
const { message, displayNames, symbols, imports, kinds } = await documentMessage({ languageId, version, getText: () => source, fileName: this.document.fileName, entryNames: this.owner.entries.namesOf(this.document.uri) }, this.registry, this.runtime, (text) => {
|
||||||
logger.add({ level: 'warn', source: 'codeReader', message: `${this.document.uri.fsPath}:${text}` })
|
logger.add({ level: 'warn', source: 'codeReader', message: `${this.document.uri.fsPath}:${text}` })
|
||||||
}, session.lookup, callees.calleeKind)
|
}, session.lookup, callees.calleeKind)
|
||||||
if (request !== this.latest)
|
if (request !== this.latest)
|
||||||
return
|
return
|
||||||
this.definitions.remember(message.version, message.blocks)
|
this.definitions.remember(message.version, message.blocks)
|
||||||
|
// 调用的定义就在这份文档里时,认种类直接用这一遍读出的,不再解析一遍
|
||||||
|
this.owner.definitionKinds.remember(this.document.uri.toString(), version, kinds)
|
||||||
// 解析失败的文档没有块:问 AI 找不到块,也不拿它比差异
|
// 解析失败的文档没有块:问 AI 找不到块,也不拿它比差异
|
||||||
this.sent = message.error === undefined ? { version: message.version, blocks: message.blocks, source } : undefined
|
this.sent = message.error === undefined ? { version: message.version, blocks: message.blocks, source } : undefined
|
||||||
this.symbols = { version, list: symbols }
|
this.symbols = { version, list: symbols }
|
||||||
@@ -452,7 +459,7 @@ class Preview {
|
|||||||
if (found.length === 0)
|
if (found.length === 0)
|
||||||
return undefined
|
return undefined
|
||||||
for (const location of found) {
|
for (const location of found) {
|
||||||
const kind = await definitionKind(location, languageId => this.registry.languageFor(languageId), this.runtime)
|
const kind = await definitionKind(location, this.owner.definitionKinds)
|
||||||
if (kind)
|
if (kind)
|
||||||
return kind
|
return kind
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in new issue
Block a user