✨ feat(vscode): 定义卡片底部带上那块问过 AI 的问答,点问题展开回答

This commit is contained in:
cruldra committed 2026-10-15 19:59:51 +11:00
1 parent fdf7a35b18
commit b99659ea68
10 files changed
+336 -46

No files matched your search

+24 -12
View File
@@ -1,11 +1,12 @@
import type { CustomTextEditorProvider, Disposable, TextDocument, TextDocumentShowOptions, WebviewPanel } from 'vscode'
import type { ClaudeImage } from '../../cc/spawnClaude'
import type { BlockView } from '../blockView'
import type { CommentEdit, CommentJob, WriteResult } from '../commentRewrite'
import type { BlockKind, LocalSymbol, RuleLocation, RuleReference, SourceRange } from '../contract'
import type { OpenedFrom, PreviewDiffBases } from '../diff/diffBase'
import type { OldDocument, SentDocument } from '../diff/previewDiff'
import type { WorkspaceEntries } from '../entries/workspaceEntries'
import type { DefinitionStart, IndexedName, NameSources } from '../nameResolution'
import type { DefinitionStart, IndexedName, NameSources, ShownName } from '../nameResolution'
import type { CodeQa, TopRequest } from '../qa/codeQa'
import type { CodeReaderRegistry } from '../registry'
import type { LookupSources } from '../ruleLookups'
@@ -196,14 +197,13 @@ export class PreviewEditorProvider implements CustomTextEditorProvider {
}
/**
* 预览页面能读的本地目录:页面自己的脚本和样式;问答带的图片所在的目录(工作区文件夹的 .spx/code-qa/),历史里才画得出缩略图;
* 注释、回答里的仓库图片所在的各个根(见 [linkBaseOf]),根的真实位置和这个路径不一样时两个都放,读不到真实位置就只放这个路径。
* 预览页面能读的本地目录:页面自己的脚本和样式;问答带的图片所在的目录(各工作区文件夹的 `.spx/code-qa/`,见 [CodeQa.imageRoots]),
* 历史和定义卡片里才画得出缩略图;注释、回答里的仓库图片所在的各个根(见 [linkBaseOf]),根的真实位置和这个路径不一样时两个都放,读不到真实位置就只放这个路径。
*/
private async readableRoots(uri: Uri): Promise<Uri[]> {
const imageRoot = this.qa.imageRoot(topSubjectOf(uri).uri)
const { roots } = await linkBaseOf(uri)
const repoRoots = await Promise.all(roots.map(async root => [root, await realpath(root).catch(() => root)]))
return [webviewUiRoot(this.extensionUri), ...(imageRoot ? [imageRoot] : []), ...[...new Set(repoRoots.flat())].map(root => Uri.file(root))]
return [webviewUiRoot(this.extensionUri), ...this.qa.imageRoots(), ...[...new Set(repoRoots.flat())].map(root => Uri.file(root))]
}
/** 「跳到下一处 / 上一处高亮」:发给能跳的那个预览,从命令面板运行时发给活动的预览。 */
@@ -659,7 +659,12 @@ class Preview {
if (!this.ready)
return
const subject = this.subject()
this.post(this.owner.qa.view(subject.uri, file => this.panel.webview.asWebviewUri(file).toString(), subject.kind))
this.post(this.owner.qa.view(subject.uri, file => this.webviewUrl(file), subject.kind))
}
/** 本地文件 `file` 在这个预览页面里能直接用的地址(`webview.asWebviewUri`)。 */
private webviewUrl(file: Uri): string {
return this.panel.webview.asWebviewUri(file).toString()
}
/** 这一页顶部问的是什么(见 topSubjectOf)。 */
@@ -926,18 +931,16 @@ class Preview {
try {
const target = await workspace.openTextDocument(uri)
const line = target.positionAt(offset).line
const path = workspace.asRelativePath(uri)
const file = uri.toString()
if (file === this.document.uri.toString()) {
if (uri.toString() === this.document.uri.toString()) {
const { sent } = this
if (sent?.version !== target.version)
return undefined
const block = peekBlock(sent.blocks, offset, line)
return block && { path, uri: file, line, block: pagePeekBlock(block, sent.source), names: namesOnCard(block, this.names.known(target.version)) }
return block && this.peekCard(uri, line, block, sent.source, namesOnCard(block, this.names.known(target.version)))
}
const { message, displayNames } = await this.owner.readElsewhere(target)
const block = message.error === undefined ? peekBlock(message.blocks, offset, line) : undefined
return block && { path, uri: file, line, block: pagePeekBlock(block, message.source), names: namesOnCard(block, definitionSiteNames(displayNames)) }
return block && this.peekCard(uri, line, block, message.source, namesOnCard(block, definitionSiteNames(displayNames)))
}
catch (err) {
logger.add({ level: 'warn', source: 'codeReader', message: `没能读出 ${uri.toString()} 里定义的那块:${err instanceof Error ? err.message : String(err)}` })
@@ -945,6 +948,15 @@ class Preview {
}
}
/**
* `uri` 第 `line` 行的定义所在那块 `block`(`source` 是那份文件的源码)发给页面的卡片,带上 `names` 和这块自己下面问过 AI 的各次。
* 问答按那份文件和这块在那份文件块树里的记号(`block.anchor`,同那个文件的预览挂对话用的)找,子块上问的记号不同,不带。
*/
private peekCard(uri: Uri, line: number, block: BlockView, source: string, names: ShownName[]): DefinitionPeek {
const qa = this.owner.qa.blockThreads(uri, file => this.webviewUrl(file)).find(thread => thread.anchor === block.anchor)?.entries
return { path: workspace.asRelativePath(uri), uri: uri.toString(), line, block: pagePeekBlock(block, source), names, ...(qa && { qa }) }
}
/**
* 在预览所在的编辑器组打开别的文件,和普通编辑器里 ⌘ + 点击一样:语言有代码阅读的用预览打开(新开的不比,已经开着的照旧),给了 spot 就滚过去、闪这处;
* 其余用 VS Code 默认的打开方式,给了 spot 就选中它(见 openingFor)。目标是否按「预览标签」打开听 VS Code 的设置,
@@ -1002,7 +1014,7 @@ class Preview {
const entry = await resolveRepoEntry(place.root, place.rel)
if (!entry || entry.isDirectory)
return { path: rel }
const src = this.panel.webview.asWebviewUri(Uri.file(entry.fsPath)).toString()
const src = this.webviewUrl(Uri.file(entry.fsPath))
return { path: rel, src }
}))
this.post({ type: 'repoImagesChecked', id, images })