✨ feat(vscode): 代码阅读的调用与构造:跨多行写的调用排成「新建 / 调用 名字 · N 个参数」一行,实参摊成参数 / 值表;Python 查被调用名字的定义分新建和调用,查到后刷新
This commit is contained in:
1 parent
ebf228cdc7
commit
ecc415d763
30 files changed
+1033
-157
No files matched your search
@@ -1,8 +1,10 @@
|
||||
import type { Location, LocationLink, Position } from 'vscode'
|
||||
import type { RuleLocation } from '../contract'
|
||||
import type { BlockKind, LanguageAdapter, RuleLocation } from '../contract'
|
||||
import type { TreeSitterRuntime } from '../runtime'
|
||||
import type { DefinitionTarget } from './definitionAction'
|
||||
import { commands, Range, Uri, window, workspace } from 'vscode'
|
||||
import { logger } from '../../logging/logger'
|
||||
import { readDefinitionKind } from '../blockView'
|
||||
|
||||
/** 查到的一处定义:文件和定义处那个名字的范围。 */
|
||||
export interface FoundLocation { uri: Uri, range: Range }
|
||||
@@ -36,6 +38,23 @@ 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) }))
|
||||
}
|
||||
|
||||
/** 定义处那个名字定义的是哪种块:打开目标文件,用它那种语言的语言插件分块认;目标打不开、语言不支持、不在哪块的名字上时没有。 */
|
||||
export async function definitionKind(
|
||||
location: FoundLocation,
|
||||
languageFor: (languageId: string) => LanguageAdapter | undefined,
|
||||
runtime: Pick<TreeSitterRuntime, 'parse'>,
|
||||
): Promise<BlockKind | undefined> {
|
||||
try {
|
||||
const document = await workspace.openTextDocument(location.uri)
|
||||
const adapter = languageFor(document.languageId)
|
||||
return adapter && await readDefinitionKind(runtime, adapter, document.languageId, document.getText(), document.offsetAt(location.range.start))
|
||||
}
|
||||
catch (err) {
|
||||
logger.add({ level: 'warn', source: 'codeReader', message: `认不出 ${location.uri.toString()} 里定义的是哪种块:${err instanceof Error ? err.message : String(err)}` })
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
|
||||
/** 查到的各处定义打开目标文件补上行文字、下标;目标打不开成文本的(例如 Python 命名空间包指向的目录)略过。 */
|
||||
export async function findDefinitions(locations: readonly FoundLocation[], supports: (languageId: string) => boolean): Promise<Definition[]> {
|
||||
const found = await Promise.all(locations.map(async (location): Promise<Definition[]> => {
|
||||
|
||||
@@ -118,6 +118,21 @@ describe('预览的 document 消息', () => {
|
||||
expect(reports).toEqual(['库规则 fastapi 给筛选属性起名时出错,筛选栏照写 id:词表坏了'])
|
||||
})
|
||||
|
||||
it('调用的名字定义成哪种块交给 calleeKind 问:跨几行新建对象的一行译文下面带实参表', async () => {
|
||||
const source = 'def f(user):\n return Detail(\n id=user.id,\n name=user.name,\n )\n'
|
||||
const asked: string[] = []
|
||||
const { message } = await documentMessage(doc('python', source), registryWithPython(), runtime, ignore, undefined, (call) => {
|
||||
asked.push(call.callee.text)
|
||||
return 'class'
|
||||
})
|
||||
expect(asked).toEqual(['Detail'])
|
||||
expect(message.blocks[0].children[0]).toMatchObject({
|
||||
kind: 'return',
|
||||
translation: [{ text: '返回' }, { text: ' ' }, { text: '新建' }, { text: ' ' }, { text: 'Detail', role: 'type' }, { text: ' ' }, { text: '· 2 个参数', role: 'hint' }],
|
||||
table: { columns: ['参数', '值'], items: [{ cells: [[{ text: 'id' }], expect.anything()] }, { cells: [[{ text: 'name' }], expect.anything()] }] },
|
||||
})
|
||||
})
|
||||
|
||||
it('库规则抛错:不算解析失败,交给 report 记下,这块照常显示', async () => {
|
||||
const broken: LibraryRule = {
|
||||
id: 'broken',
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { DisplayName, RuleContext } from '../contract'
|
||||
import type { DisplayName, RuleContext, SourceDocument } from '../contract'
|
||||
import type { CodeReaderRegistry } from '../registry'
|
||||
import type { TreeSitterRuntime } from '../runtime'
|
||||
import type { DocumentMessage } from './messages'
|
||||
@@ -21,7 +21,8 @@ export interface DocumentRead {
|
||||
|
||||
/**
|
||||
* 当前文档 → 发给预览的 document 消息;语言不支持或解析失败时带 error,blocks 为空。库规则出错不算失败,交给 report 记下;
|
||||
* 规则查名字的定义时问 lookup(RuleContext.lookup)。筛选属性和值的名字先取翻译插件的,没给的再取库规则的(LibraryRule.attributeNames)。
|
||||
* 规则查名字的定义时问 lookup(RuleContext.lookup),翻译插件问调用的名字定义成哪种块时问 calleeKind(SourceDocument.calleeKind)。
|
||||
* 筛选属性和值的名字先取翻译插件的,没给的再取库规则的(LibraryRule.attributeNames)。
|
||||
*/
|
||||
export async function documentMessage(
|
||||
document: DocumentSnapshot,
|
||||
@@ -29,6 +30,7 @@ export async function documentMessage(
|
||||
runtime: Pick<TreeSitterRuntime, 'parse'>,
|
||||
report: (message: string) => void,
|
||||
lookup?: RuleContext['lookup'],
|
||||
calleeKind?: SourceDocument['calleeKind'],
|
||||
): Promise<DocumentRead> {
|
||||
const { languageId, version } = document
|
||||
const source = document.getText()
|
||||
@@ -48,7 +50,7 @@ export async function documentMessage(
|
||||
return { message: { ...message, error: `代码阅读还不支持「${languageId}」这种语言` }, displayNames: [] }
|
||||
try {
|
||||
const rules = registry.rulesFor(languageId)
|
||||
const { blocks, displayNames } = await readBlockViews(runtime, adapter, translator, languageId, source, { rules, report, lookup })
|
||||
const { blocks, displayNames } = await readBlockViews(runtime, adapter, translator, languageId, source, { rules, report, lookup }, calleeKind)
|
||||
const names = mergeAttributeNames([translator?.attributeNames, ...ruleAttributeNames(rules, translator?.displayLanguage, report)])
|
||||
return { message: { ...message, blocks, attributes: fileAttributes(blocks, names) }, displayNames }
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { CustomTextEditorProvider, Disposable, TextDocument, TextDocumentShowOptions, Uri, WebviewPanel } from 'vscode'
|
||||
import type { ClaudeImage } from '../../cc/spawnClaude'
|
||||
import type { BlockView } from '../blockView'
|
||||
import type { RuleLocation, RuleReference, WorkspaceIndex } from '../contract'
|
||||
import type { BlockKind, RuleLocation, RuleReference, WorkspaceIndex } from '../contract'
|
||||
import type { DefinitionStart, IndexedName, NameSources } from '../nameResolution'
|
||||
import type { CodeQa } from '../qa/codeQa'
|
||||
import type { CodeReaderRegistry } from '../registry'
|
||||
@@ -14,13 +14,14 @@ import type { ExtensionToWebview, Reveal, WebviewToExtension } from './messages'
|
||||
import { commands, Range, window, workspace, WorkspaceEdit } from 'vscode'
|
||||
import { logger } from '../../logging/logger'
|
||||
import { webviewHtml, webviewUiRoot } from '../../webview/html'
|
||||
import { DocumentCallees } from '../calleeKinds'
|
||||
import { blockReplacement } from '../editText'
|
||||
import { locate } from '../folderStore'
|
||||
import { DocumentNames, resolveNames } from '../nameResolution'
|
||||
import { askPrompt, followUpPrompt } from '../qa/prompt'
|
||||
import { DocumentLookups } from '../ruleLookups'
|
||||
import { definitionAction } from './definitionAction'
|
||||
import { findDefinitions, languageDefinitions, pickDefinition, ruleDefinitions } from './definitions'
|
||||
import { definitionKind, findDefinitions, languageDefinitions, pickDefinition, ruleDefinitions } from './definitions'
|
||||
import { DefinitionSources } from './definitionSources'
|
||||
import { documentMessage } from './documentMessage'
|
||||
import { NavigationContext, OCCURRENCES_CONTEXT } from './occurrenceNavigation'
|
||||
@@ -131,6 +132,8 @@ class Preview {
|
||||
private readonly names: DocumentNames
|
||||
/** 库规则问的名字的定义查到了什么:查到的变了就重发文档 */
|
||||
private readonly lookups: DocumentLookups
|
||||
/** 调用的名字定义成哪种块(分新建还是调用):查到的变了就重发文档 */
|
||||
private readonly callees: DocumentCallees
|
||||
|
||||
constructor(
|
||||
private readonly document: TextDocument,
|
||||
@@ -154,6 +157,7 @@ class Preview {
|
||||
this.post({ type: 'displayNames', version, names })
|
||||
})
|
||||
this.lookups = new DocumentLookups(() => this.schedulePush())
|
||||
this.callees = new DocumentCallees(() => this.schedulePush())
|
||||
this.disposables = [
|
||||
panel.webview.onDidReceiveMessage((msg: WebviewToExtension) => this.handleMessage(msg)),
|
||||
panel.onDidChangeViewState(() => {
|
||||
@@ -223,9 +227,10 @@ class Preview {
|
||||
const { languageId, version } = this.document
|
||||
const source = this.document.getText()
|
||||
const session = this.lookups.session()
|
||||
const callees = this.callees.session()
|
||||
const { message, displayNames } = await documentMessage({ languageId, version, getText: () => source }, this.registry, this.runtime, (text) => {
|
||||
logger.add({ level: 'warn', source: 'codeReader', message: `${this.document.uri.fsPath}:${text}` })
|
||||
}, session.lookup)
|
||||
}, session.lookup, callees.calleeKind)
|
||||
if (request !== this.latest || !this.ready)
|
||||
return
|
||||
this.definitions.remember(message.version, message.blocks)
|
||||
@@ -237,9 +242,24 @@ class Preview {
|
||||
if (version === this.document.version) {
|
||||
this.names.resolve(version, report => resolveNames(displayNames, message.blocks, this.nameSources(), report))
|
||||
this.lookups.resolve(version, session.requests(), this.lookupSources())
|
||||
this.callees.resolve(version, callees.requests(), offset => this.calleeKind(offset))
|
||||
}
|
||||
}
|
||||
|
||||
/** 这份文档里 offset 处名字的定义是哪种块:语言扩展查到的几处定义里第一处认得出的;语言扩展没答是 undefined。 */
|
||||
private async calleeKind(offset: number): Promise<BlockKind | null | undefined> {
|
||||
const { document } = this
|
||||
const found = await languageDefinitions(document.uri, document.positionAt(offset))
|
||||
if (found.length === 0)
|
||||
return undefined
|
||||
for (const location of found) {
|
||||
const kind = await definitionKind(location, languageId => this.registry.languageFor(languageId), this.runtime)
|
||||
if (kind)
|
||||
return kind
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
/** 这份文档里 offset 处名字的定义(语言扩展查的)落在哪儿。 */
|
||||
private async definitionStarts(offset: number): Promise<DefinitionStart[]> {
|
||||
const { document } = this
|
||||
@@ -284,6 +304,7 @@ class Preview {
|
||||
clearTimeout(this.timer)
|
||||
this.names.dispose()
|
||||
this.lookups.dispose()
|
||||
this.callees.dispose()
|
||||
for (const disposable of this.disposables)
|
||||
disposable.dispose()
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user