✨ feat(vscode): 代码阅读预览按侧栏筛选算代码差异(未提交的更改对比上一次提交、审查对比 PR 目标分支;兄弟块按名字 / 相似度对齐,卡片内按行 / 值 / 词比对),算好后补发给预览
This commit is contained in:
1 parent
3db44f84ae
commit
2a5806c6b5
23 files changed
+1527
-31
No files matched your search
@@ -1,7 +1,8 @@
|
||||
import type { CustomTextEditorProvider, Disposable, TextDocument, TextDocumentShowOptions, Uri, WebviewPanel } from 'vscode'
|
||||
import type { CustomTextEditorProvider, Disposable, Event, TextDocument, TextDocumentShowOptions, Uri, WebviewPanel } from 'vscode'
|
||||
import type { ClaudeImage } from '../../cc/spawnClaude'
|
||||
import type { BlockView } from '../blockView'
|
||||
import type { BlockKind, LocalSymbol, RuleLocation, RuleReference, WorkspaceIndex } from '../contract'
|
||||
import type { DiffBase } from '../diff/diffBase'
|
||||
import type { OldDocument, SentDocument } from '../diff/previewDiff'
|
||||
import type { DefinitionStart, IndexedName, NameSources } from '../nameResolution'
|
||||
import type { CodeQa } from '../qa/codeQa'
|
||||
import type { CodeReaderRegistry } from '../registry'
|
||||
@@ -15,6 +16,7 @@ import { commands, Range, window, workspace, WorkspaceEdit } from 'vscode'
|
||||
import { logger } from '../../logging/logger'
|
||||
import { webviewHtml, webviewUiRoot } from '../../webview/html'
|
||||
import { DocumentCallees } from '../calleeKinds'
|
||||
import { PreviewDiff } from '../diff/previewDiff'
|
||||
import { blockReplacement } from '../editText'
|
||||
import { locate } from '../folderStore'
|
||||
import { definitionFor, symbolAt, symbolOccurrences } from '../localSymbols'
|
||||
@@ -33,6 +35,12 @@ export const PREVIEW_VIEW_TYPE = 'superpowers.codeReader.preview'
|
||||
/** 连续输入时合并成一次重新解析。 */
|
||||
const REFRESH_DELAY_MS = 150
|
||||
|
||||
/** 预览对比什么:侧栏开着的筛选定(TreeFilters),换了筛选、基准变了发 onDidChangeDiffBase。 */
|
||||
export interface DiffBases {
|
||||
diffBase: (uri: Uri) => DiffBase | undefined
|
||||
onDidChangeDiffBase: Event<void>
|
||||
}
|
||||
|
||||
/**
|
||||
* 「代码阅读预览」自定义编辑器。预览和普通编辑器共用同一个 TextDocument:
|
||||
* 块内修改走 WorkspaceEdit,未保存标记、⌘S、⌘Z 都是 VS Code 原生行为。
|
||||
@@ -57,6 +65,8 @@ export class PreviewEditorProvider implements CustomTextEditorProvider {
|
||||
readonly indexedNames: (languageId: string, name: string) => Promise<IndexedName[]>,
|
||||
/** 库规则的工作区索引里键是 key 的各项(规则查名字的定义时用,见 ruleLookups.ts) */
|
||||
readonly indexed: (index: WorkspaceIndex, key: string) => Promise<IndexedEntry[]>,
|
||||
/** 预览标差异时对比什么 */
|
||||
readonly diffBases: DiffBases,
|
||||
) {}
|
||||
|
||||
resolveCustomTextEditor(document: TextDocument, panel: WebviewPanel): void {
|
||||
@@ -127,8 +137,8 @@ class Preview {
|
||||
private readonly definitions: DefinitionSources<FoundLocation>
|
||||
/** webview 报来的视口顶部那块的起始行 */
|
||||
visibleLine: number | undefined
|
||||
/** 最近一次发出去的块树和它的源码:问 AI 时按块的记号在里面找那块代码 */
|
||||
private sent: { blocks: BlockView[], source: string } | undefined
|
||||
/** 最近一次发出去的文档(版本、块树、源码):问 AI 时按块的记号在里面找那块代码,对比基准变了时拿它重新比 */
|
||||
private sent: SentDocument | undefined
|
||||
/** 最近一次发出去的文档里语言插件自己认得出的名字(LanguageAdapter.symbols):语言扩展查不到定义、各处出现时用 */
|
||||
private symbols: { version: number, list: LocalSymbol[] } | undefined
|
||||
/** 这份文档各版本显示成中文名的名字 */
|
||||
@@ -137,6 +147,8 @@ class Preview {
|
||||
private readonly lookups: DocumentLookups
|
||||
/** 调用的名字定义成哪种块(分新建还是调用):查到的变了就重发文档 */
|
||||
private readonly callees: DocumentCallees
|
||||
/** 这份文件相比对比基准改了什么:发完文档再算、补发 */
|
||||
private readonly diff: PreviewDiff
|
||||
|
||||
constructor(
|
||||
private readonly document: TextDocument,
|
||||
@@ -164,7 +176,20 @@ class Preview {
|
||||
})
|
||||
this.lookups = new DocumentLookups(() => this.schedulePush())
|
||||
this.callees = new DocumentCallees(() => this.schedulePush())
|
||||
this.diff = new PreviewDiff(
|
||||
() => owner.diffBases.diffBase(document.uri),
|
||||
source => this.readOld(source),
|
||||
(diff) => {
|
||||
if (this.ready)
|
||||
this.post({ type: 'diff', diff })
|
||||
},
|
||||
text => logger.add({ level: 'warn', source: 'codeReader', message: `${document.uri.fsPath}:${text}` }),
|
||||
)
|
||||
this.disposables = [
|
||||
owner.diffBases.onDidChangeDiffBase(() => {
|
||||
if (this.ready && this.sent)
|
||||
void this.diff.update(this.sent)
|
||||
}),
|
||||
panel.webview.onDidReceiveMessage((msg: WebviewToExtension) => this.handleMessage(msg)),
|
||||
panel.onDidChangeViewState(() => {
|
||||
if (panel.visible)
|
||||
@@ -240,11 +265,14 @@ class Preview {
|
||||
if (request !== this.latest || !this.ready)
|
||||
return
|
||||
this.definitions.remember(message.version, message.blocks)
|
||||
this.sent = { blocks: message.blocks, source }
|
||||
// 解析失败的文档没有块:问 AI 找不到块,也不拿它比差异
|
||||
this.sent = message.error === undefined ? { version: message.version, blocks: message.blocks, source } : undefined
|
||||
this.symbols = { version, list: symbols }
|
||||
const reveal = this.pendingReveal
|
||||
this.pendingReveal = undefined
|
||||
this.post({ ...message, names: this.names.known(version), ...(reveal && { reveal }) })
|
||||
if (this.sent)
|
||||
void this.diff.update(this.sent)
|
||||
// 文档在解析期间又改了的,等下一次发文档再查
|
||||
if (version === this.document.version) {
|
||||
this.names.resolve(version, report => resolveNames(displayNames, message.blocks, this.nameSources(), report))
|
||||
@@ -253,6 +281,22 @@ class Preview {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 对比基准里的旧版本排成视图数据:和这份文档同一种语言、同一个翻译插件和库规则;规则查定义、调用的种类用这份文档眼下查到的答案,
|
||||
* 两边同样的写法翻得一样,不会因为答案晚到被当成改了。旧版本里规则出的错不记(记的是现在这份的)。
|
||||
*/
|
||||
private async readOld(source: string): Promise<OldDocument | undefined> {
|
||||
const { message } = await documentMessage(
|
||||
{ languageId: this.document.languageId, version: this.document.version, getText: () => source },
|
||||
this.registry,
|
||||
this.runtime,
|
||||
() => {},
|
||||
this.lookups.session().lookup,
|
||||
this.callees.session().calleeKind,
|
||||
)
|
||||
return message.error === undefined ? { blocks: message.blocks, attributes: message.attributes } : undefined
|
||||
}
|
||||
|
||||
/** 这份文档里 offset 处名字的定义是哪种块:语言扩展查到的几处定义里第一处认得出的;语言扩展没答是 undefined。 */
|
||||
private async calleeKind(offset: number): Promise<BlockKind | null | undefined> {
|
||||
const { document } = this
|
||||
@@ -360,7 +404,7 @@ class Preview {
|
||||
*/
|
||||
private async ask(askId: string, anchor: string, question: string, images: ClaudeImage[]): Promise<void> {
|
||||
const at = locate(this.document.uri)
|
||||
const subject = at && this.sent && { path: at.key, languageId: this.document.languageId, ...this.sent, anchor }
|
||||
const subject = at && this.sent && { path: at.key, languageId: this.document.languageId, blocks: this.sent.blocks, source: this.sent.source, anchor }
|
||||
const prompt = subject && askPrompt(subject, question)
|
||||
const followUp = subject && followUpPrompt(subject, question)
|
||||
const refused = prompt === undefined || followUp === undefined
|
||||
|
||||
Reference in new issue
Block a user