✨ feat(vscode): FastAPI 接口卡片的调用树带上前端调用它的地方

This commit is contained in:
cruldra committed 2026-10-12 12:22:51 +11:00
1 parent d4459e93b3
commit 29cedcb751
30 files changed
+1316 -130

No files matched your search

+7 -3
View File
@@ -5,6 +5,7 @@
import type { ClaudeImage } from '../../cc/spawnClaude'
import type { FileAttribute } from '../attributes'
import type { BlockView } from '../blockView'
import type { ContractMiss } from '../callTree/endpoint'
import type { CallTree } from '../callTree/model'
import type { DocumentDiff } from '../diff/previewDiff'
import type { ShownName } from '../nameResolution'
@@ -130,8 +131,9 @@ export interface StickyScrollSettings {
* repoLinkBroken:这个仓库链接打不开。没有 symbol 是文件或目录没有;有 symbol 是文件在、这个符号没有;没有 path 是不带路径的引用找不到
* repoLinkOpened:这个仓库链接打开了。路径在;带了 symbol 时这个符号也在;没有 path 是不带路径的引用找到了
* repoImagesChecked:id 那次 checkRepoImages 的回答。src 是预览里能直接用的图片地址;没有 src 是文件不在
* callTreeCounts:version 这份文档里,每个函数、类落在几棵调用树里(只包括大于 0 的)。不是页面上这个版本的不理
* callTree:anchor 那块的调用树。trees 空着是没有调用它的地方。不是页面上这个版本的不理
* callTreeCounts:version 这份文档里,每个能看调用树的块(BlockView.callTree)落在几棵调用树里(只包括大于 0 的)。不是页面上这个版本的不理
* callTree:anchor 那块的调用树。trees 空着、又没有 contractMiss 是没有调用它的地方;接口卡片在契约文件里没对上时带 contractMiss。
* 不是页面上这个版本的不理
*/
export type ExtensionToWebview
= | DocumentMessage
@@ -157,7 +159,7 @@ export type ExtensionToWebview
| { type: 'repoImagesChecked', id: number, images: { path: string, src?: string }[] }
| { type: 'focusPathAsk' }
| { type: 'callTreeCounts', version: number, counts: Record<string, number> }
| { type: 'callTree', version: number, anchor: string, name: string, where: string, trees: CallTree[] }
| { type: 'callTree', version: number, anchor: string, name: string, where: string, trees: CallTree[], contractMiss?: ContractMiss }
/**
* edit:把 startLine..endLine 这几行整行替换成 text(扩展端给每行补回原来的缩进)
@@ -184,6 +186,7 @@ export type ExtensionToWebview
* checkRepoLinks:预览里这批仓库路径问哪些文件或目录没有,这批不带路径的引用(symbols)问哪些找不到。id 标出是哪一次问
* checkRepoImages:预览里这批仓库图片,问哪些文件在,在的要预览里能直接用的地址。id 标出是哪一次问
* openCallTree:打开 anchor 那块的调用树。version 是这块所在的文档版本,对不上就不查
* goToCallNode:点了调用树上的一个节点,打开 uri 那个文件,跳到 line 行 character 列(都从 0 起,见 CallTreeNode.jump)
*/
export type WebviewToExtension
= | { type: 'ready' }
@@ -208,3 +211,4 @@ export type WebviewToExtension
| { type: 'checkRepoLinks', id: number, paths: string[], symbols: string[] }
| { type: 'checkRepoImages', id: number, paths: string[] }
| { type: 'openCallTree', version: number, anchor: string }
| { type: 'goToCallNode', uri: string, line: number, character: number }
+21 -7
View File
@@ -1,8 +1,9 @@
import type { CustomTextEditorProvider, Disposable, DocumentSymbol, Event, TextDocument, TextDocumentShowOptions, WebviewPanel } from 'vscode'
import type { ClaudeProfile } from '../../cc/profiles'
import type { ClaudeImage } from '../../cc/spawnClaude'
import type { IndexFind } from '../callTree/endpoint'
import type { CommentEdit, RewriteOutcome, WriteResult } from '../commentRewrite'
import type { BlockKind, LocalSymbol, RuleLocation, RuleReference, SourceRange, WorkspaceIndex } from '../contract'
import type { BlockKind, LocalSymbol, RuleLocation, RuleReference, SourceRange } from '../contract'
import type { DiffBase } from '../diff/diffBase'
import type { OldDocument, SentDocument } from '../diff/previewDiff'
import type { WorkspaceEntries } from '../entries/workspaceEntries'
@@ -12,7 +13,6 @@ import type { CodeReaderRegistry } from '../registry'
import type { LookupSources } from '../ruleLookups'
import type { TreeSitterRuntime } from '../runtime'
import type { ImportedName, LinkTarget, SymbolNode } from '../symbolLinks'
import type { IndexedEntry } from '../workspaceIndex'
import type { BlockFilter } from './blockFilter'
import type { FoundLocation } from './definitions'
import type { DefinitionPeek, ExtensionToWebview, Occurrence, Reveal, WebviewToExtension } from './messages'
@@ -23,7 +23,7 @@ import { commands, EndOfLine, Position, Range, Uri, window, workspace, Workspace
import { logger } from '../../logging/logger'
import { webviewHtml, webviewUiRoot } from '../../webview/html'
import { DocumentCallees } from '../calleeKinds'
import { loadCallEdges } from '../callTree/query'
import { loadBlockCalls } from '../callTree/query'
import { CallTreeSession } from '../callTree/session'
import { findBlock, rewriteBlockComment } from '../commentRewrite'
import { PreviewDiff } from '../diff/previewDiff'
@@ -82,8 +82,8 @@ export class PreviewEditorProvider implements CustomTextEditorProvider {
readonly qa: CodeQa,
/** 工作区里别的文件定义的、原名叫 name 的中文名(languageId 是查的这份文档的语言) */
readonly indexedNames: (languageId: string, name: string) => Promise<IndexedName[]>,
/** 库规则的工作区索引里键是 key 的各项(规则查名字的定义时用,见 ruleLookups.ts) */
readonly indexed: (index: WorkspaceIndex, key: string) => Promise<IndexedEntry[]>,
/** 工作区索引里键是 key 的各项(规则查名字的定义时用,见 ruleLookups.ts;接口的调用树查契约和前端请求时用) */
readonly indexed: IndexFind,
/** 预览标差异时对比什么 */
readonly diffBases: DiffBases,
/** 哪些文件是项目写明的入口、预览里哪些名字的块标成入口 */
@@ -189,7 +189,7 @@ class Preview {
private readonly rewriteStops = new Map<string, AbortController>()
/** 最近一次发出去的文档(版本、块树、源码):问 AI 时按块的记号在里面找那块代码,对比基准变了时拿它重新比 */
private sent: SentDocument | undefined
/** 函数、类落在几棵调用树里。文档发出去之后再数,不挡住第一次预览 */
/** 能看调用树的块(函数、类、接口卡片)落在几棵调用树里。文档发出去之后再数,不挡住第一次预览 */
private readonly callTrees = new CallTreeSession(
message => this.post(message),
() => {
@@ -198,7 +198,7 @@ class Preview {
return {
version: this.sent.version,
blocks: this.sent.blocks,
load: (offset, cancelled) => loadCallEdges(this.document, offset, cancelled),
load: (block, cancelled) => loadBlockCalls(this.document, block, this.owner.indexed, cancelled),
}
},
)
@@ -630,6 +630,9 @@ class Preview {
case 'openCallTree':
this.callTrees.open(msg.version, msg.anchor)
return
case 'goToCallNode':
await this.goToCallNode(Uri.parse(msg.uri), new Position(msg.line, msg.character))
return
case 'occurrenceNavigation':
this.owner.navigation.update(this, msg.available)
}
@@ -777,6 +780,17 @@ class Preview {
await open()
}
/** 点了调用树上的一个节点:打开它在的文件,滚到这一处(同 ⌘ + 点击跳到别的文件)。 */
private async goToCallNode(uri: Uri, position: Position): Promise<void> {
try {
const target = await workspace.openTextDocument(uri)
await this.openElsewhere(uri, { range: new Range(position, position), offset: target.offsetAt(position) })
}
catch (err) {
logger.add({ level: 'warn', source: 'codeReader', message: `调用树里的 ${uri.toString()} 打不开:${err instanceof Error ? err.message : String(err)}` })
}
}
/** 预览里这批仓库路径,哪些文件或目录不在;这批不带路径的引用,哪些找不到。 */
private async checkRepoLinks(id: number, paths: string[], symbols: string[]): Promise<void> {
const root = await repoRootOf(this.document.uri.fsPath)