✨ feat(vscode): FastAPI 接口卡片的调用树带上前端调用它的地方
This commit is contained in:
1 parent
d4459e93b3
commit
29cedcb751
30 files changed
+1316
-130
No files matched your search
@@ -1,37 +1,60 @@
|
||||
/**
|
||||
* 问编辑器的调用关系命令,收成调用边。
|
||||
* 问编辑器的调用关系命令,收成调用边;接口卡片另外并上前端发这个请求的地方。
|
||||
* 语言扩展不提供、或者这一轮已经作废,就当没有。
|
||||
*/
|
||||
|
||||
import type { CallHierarchyIncomingCall, CallHierarchyItem, CallHierarchyOutgoingCall, TextDocument } from 'vscode'
|
||||
import type { RuleLocation } from '../contract'
|
||||
import type { IndexedEntry } from '../workspaceIndex'
|
||||
import type { IndexFind } from './endpoint'
|
||||
import type { CallEdge, CallRange, CallSymbol } from './model'
|
||||
import { commands, SymbolKind, workspace } from 'vscode'
|
||||
import type { RequestEntry } from './requests'
|
||||
import type { CallTreeBlock, LoadedCalls } from './session'
|
||||
import type { IncomingHit } from './walk'
|
||||
import * as path from 'node:path'
|
||||
import { commands, Position, SymbolKind, Uri, workspace } from 'vscode'
|
||||
import { endpointCalls } from './endpoint'
|
||||
import { symbolKey } from './model'
|
||||
import { collectCallEdges } from './walk'
|
||||
|
||||
/**
|
||||
* 问一块的调用关系。接口卡片(callTree.endpoint)先到契约里找前端发这个请求的地方(见 endpoint.ts),并进起点的调用者;
|
||||
* 契约里对不上时带上 contractMiss,Python 里的调用者照常问。
|
||||
*/
|
||||
export async function loadBlockCalls(document: TextDocument, block: CallTreeBlock, find: IndexFind, cancelled: () => boolean): Promise<LoadedCalls | undefined> {
|
||||
const offset = block.defines?.startIndex
|
||||
const endpoint = block.callTree?.endpoint
|
||||
if (offset === undefined)
|
||||
return undefined
|
||||
if (!endpoint)
|
||||
return loadCallEdges(document, offset, cancelled)
|
||||
const found = await endpointCalls(find, endpoint)
|
||||
if (cancelled())
|
||||
return undefined
|
||||
if ('requests' in found)
|
||||
return loadCallEdges(document, offset, cancelled, found.requests)
|
||||
const loaded = await loadCallEdges(document, offset, cancelled, [])
|
||||
const files = found.contracts.map(uri => workspace.asRelativePath(Uri.parse(uri)))
|
||||
return loaded && { ...loaded, contractMiss: { files, ...(found.ambiguous && { ambiguous: found.ambiguous }) } }
|
||||
}
|
||||
|
||||
/**
|
||||
* 在 offset 这个名字起点上问调用关系。
|
||||
* 先准备符号,再往上问谁调用了它;路径上的调用者再问它还调用了谁,用来收「另外 N 处」。
|
||||
* 给了 requests(接口卡片,可以是空的)时,它们并进起点的调用者,从它们往上照常问编辑器;
|
||||
* 这时编辑器认不出起点也照样收前端这一路,起点按名字起点自己写一个。
|
||||
*/
|
||||
export async function loadCallEdges(
|
||||
document: TextDocument,
|
||||
offset: number,
|
||||
cancelled: () => boolean,
|
||||
requests?: readonly IndexedEntry<RequestEntry>[],
|
||||
): Promise<{ symbol: CallSymbol, edges: CallEdge[] } | undefined> {
|
||||
if (cancelled())
|
||||
return undefined
|
||||
const position = document.positionAt(offset)
|
||||
let prepared: CallHierarchyItem | CallHierarchyItem[] | null | undefined
|
||||
try {
|
||||
prepared = await commands.executeCommand('vscode.prepareCallHierarchy', document.uri, position)
|
||||
}
|
||||
catch {
|
||||
return undefined
|
||||
}
|
||||
if (cancelled() || prepared == null)
|
||||
return undefined
|
||||
const items = (Array.isArray(prepared) ? prepared : [prepared]).filter(item => item)
|
||||
if (items.length === 0)
|
||||
const items = await prepare(document.uri, position)
|
||||
if (cancelled() || (items.length === 0 && !requests))
|
||||
return undefined
|
||||
|
||||
const vscodeItems = new Map<string, CallHierarchyItem>()
|
||||
@@ -50,15 +73,27 @@ export async function loadCallEdges(
|
||||
seen.add(key)
|
||||
starts.push(symbol)
|
||||
}
|
||||
const symbol = starts.find(item => item.line === position.line && item.character === position.character) ?? starts[0]!
|
||||
const edges: CallEdge[] = []
|
||||
for (const start of starts) {
|
||||
const symbol = starts.find(item => item.line === position.line && item.character === position.character) ?? starts[0] ?? symbolAt(document, position)
|
||||
const sent: IncomingHit[] = []
|
||||
for (const { location, entry } of requests ?? []) {
|
||||
if (cancelled())
|
||||
return undefined
|
||||
edges.push(...await collectCallEdges(start, incoming, outgoing, cancelled))
|
||||
sent.push({ from: await requestCaller(location, entry), ranges: [entry.at], request: `${entry.key} ${entry.path}` })
|
||||
}
|
||||
const edges: CallEdge[] = []
|
||||
for (const start of starts.length > 0 ? starts : [symbol]) {
|
||||
if (cancelled())
|
||||
return undefined
|
||||
edges.push(...await collectCallEdges(start, withRequests, outgoing, cancelled))
|
||||
}
|
||||
return cancelled() ? undefined : { symbol, edges }
|
||||
|
||||
/** 起点的调用者里并上前端发请求的地方;别的节点照常问编辑器。 */
|
||||
async function withRequests(node: CallSymbol): Promise<IncomingHit[]> {
|
||||
const calls = await incoming(node)
|
||||
return symbolKey(node) === symbolKey(symbol) ? [...calls, ...sent] : calls
|
||||
}
|
||||
|
||||
async function incoming(node: CallSymbol) {
|
||||
return hits(node, 'vscode.provideIncomingCalls', (call: CallHierarchyIncomingCall) => ({
|
||||
from: remember(call.from),
|
||||
@@ -73,6 +108,24 @@ export async function loadCallEdges(
|
||||
}))
|
||||
}
|
||||
|
||||
/** 发请求的地方所在的函数:编辑器认得它就用编辑器的(往上还能接着问),不在有名字的函数里、编辑器不认得时照索引写一个。 */
|
||||
async function requestCaller(location: RuleLocation, entry: RequestEntry): Promise<CallSymbol> {
|
||||
const uri = Uri.parse(location.uri)
|
||||
const at = new Position(location.start.line, location.start.character)
|
||||
const found = entry.caller ? await prepare(uri, at) : []
|
||||
const item = found.find(candidate => candidate.selectionRange.start.isEqual(at)) ?? found[0]
|
||||
if (item)
|
||||
return remember(item)
|
||||
return {
|
||||
uri: location.uri,
|
||||
line: at.line,
|
||||
character: at.character,
|
||||
name: entry.caller ?? path.posix.basename(uri.path),
|
||||
kind: entry.caller ? 'function' : 'module',
|
||||
path: workspace.asRelativePath(uri),
|
||||
}
|
||||
}
|
||||
|
||||
async function hits<T, R>(node: CallSymbol, command: string, map: (call: T) => R): Promise<R[]> {
|
||||
const item = vscodeItems.get(symbolKey(node))
|
||||
if (!item || cancelled())
|
||||
@@ -87,6 +140,30 @@ export async function loadCallEdges(
|
||||
}
|
||||
}
|
||||
|
||||
/** 编辑器在这个位置准备出的调用关系符号;语言扩展不提供时是空的。 */
|
||||
async function prepare(uri: Uri, position: Position): Promise<CallHierarchyItem[]> {
|
||||
try {
|
||||
const prepared = await commands.executeCommand<CallHierarchyItem | CallHierarchyItem[] | null | undefined>('vscode.prepareCallHierarchy', uri, position)
|
||||
return prepared == null ? [] : (Array.isArray(prepared) ? prepared : [prepared]).filter(item => item)
|
||||
}
|
||||
catch {
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
/** 编辑器认不出起点时按名字起点自己写的符号。 */
|
||||
function symbolAt(document: TextDocument, position: Position): CallSymbol {
|
||||
const word = document.getWordRangeAtPosition(position)
|
||||
return {
|
||||
uri: document.uri.toString(),
|
||||
line: position.line,
|
||||
character: position.character,
|
||||
name: word ? document.getText(word) : '',
|
||||
kind: 'function',
|
||||
path: workspace.asRelativePath(document.uri),
|
||||
}
|
||||
}
|
||||
|
||||
function toSymbol(item: CallHierarchyItem): CallSymbol {
|
||||
const start = item.selectionRange.start
|
||||
return {
|
||||
|
||||
Reference in new issue
Block a user