✨ feat(vscode): 重写注释时在卡片下展示模型过程

This commit is contained in:
cruldra committed 2026-10-03 03:22:20 +08:00
1 parent 08fb04e3fd
commit 5e8e7b65ee
15 files changed
+369 -83

No files matched your search

+91 -46
View File
@@ -129,6 +129,12 @@ export class PreviewEditorProvider implements CustomTextEditorProvider {
}
}
/** 一段注释重写的结果:`done` 已经写进文件,`failed` 没写并带给人看的原因,`stopped` 是被叫停、没写。 */
type RewriteOutcome
= | { status: 'done' }
| { status: 'stopped' }
| { status: 'failed', error: string }
class Preview {
/** webview 发过 ready 之后才发消息;标签页切走时页面被销毁,切回来重新加载、再发 ready,这期间也不发 */
private ready = false
@@ -144,6 +150,8 @@ class Preview {
private readonly rewriting = new Set<string>()
/** 预览关掉时停掉还在跑的重写 */
private readonly rewriteAbort = new AbortController()
/** 每段正在重写的注释各自的停止开关;预览关掉时 [rewriteAbort] 会把它们都停掉 */
private readonly rewriteStops = new Map<string, AbortController>()
/** 最近一次发出去的文档(版本、块树、源码):问 AI 时按块的记号在里面找那块代码,对比基准变了时拿它重新比 */
private sent: SentDocument | undefined
/** 最近一次发出去的文档里语言插件自己认得出的名字(LanguageAdapter.symbols):语言扩展查不到定义、各处出现时用 */
@@ -464,6 +472,9 @@ class Preview {
case 'rewriteComment':
await this.rewriteComment(msg.anchor)
return
case 'stopRewrite':
this.rewriteStops.get(msg.anchor)?.abort()
return
case 'selectTranslator':
await this.registry.selectTranslator(this.languageId, msg.id)
this.owner.refresh(this.languageId)
@@ -555,12 +566,19 @@ class Preview {
if (this.rewriting.has(anchor) || this.rewriteAbort.signal.aborted)
return
this.rewriting.add(anchor)
const stop = new AbortController()
const onDispose = (): void => stop.abort()
this.rewriteAbort.signal.addEventListener('abort', onDispose)
this.rewriteStops.set(anchor, stop)
try {
await this.rewriteCommentNow(anchor)
const outcome = await this.rewriteCommentNow(anchor, stop.signal)
if (!this.rewriteAbort.signal.aborted)
this.post({ type: 'rewriteEnd', anchor, status: outcome.status, ...(outcome.status === 'failed' && { error: outcome.error }) })
}
finally {
this.rewriteAbort.signal.removeEventListener('abort', onDispose)
this.rewriteStops.delete(anchor)
this.rewriting.delete(anchor)
this.post({ type: 'commentRewritten', anchor })
}
}
@@ -568,18 +586,18 @@ class Preview {
void window.showErrorMessage(`代码阅读:${message}`)
}
private async rewriteCommentNow(anchor: string): Promise<void> {
const found = this.sent && findPlaced(this.sent.blocks, anchor)
if (!found || found.block.kind !== 'comment') {
this.failRewrite('这块注释刚改过,找不到了。等预览刷新后再重写。')
return
private async rewriteCommentNow(anchor: string, signal: AbortSignal): Promise<RewriteOutcome> {
const failed = (message: string): RewriteOutcome => {
this.failRewrite(message)
return { status: 'failed', error: message }
}
const found = this.sent && findPlaced(this.sent.blocks, anchor)
if (!found || found.block.kind !== 'comment')
return failed('这块注释刚改过,找不到了。等预览刷新后再重写。')
const { block, siblings, index } = found
const { document } = this
if (block.startLine < 0 || block.endLine < block.startLine || block.endLine >= document.lineCount) {
this.failRewrite('这块注释的位置已经变了,没有写回。')
return
}
if (block.startLine < 0 || block.endLine < block.startLine || block.endLine >= document.lineCount)
return failed('这块注释的位置已经变了,没有写回。')
const raw = Array.from({ length: block.endLine - block.startLine + 1 }, (_, i) => document.lineAt(block.startLine + i).text)
const snapshot = raw.join('\n')
const comment = commentSource(raw)
@@ -596,65 +614,92 @@ class Preview {
earlierKinds: siblings.slice(0, index).map(item => item.kind),
...(following !== undefined && { following }),
})
if (!prompt) {
this.failRewrite('这段注释的定界符认不出来,没有改文件。')
return
}
if (!prompt)
return failed('这段注释的定界符认不出来,没有改文件。')
const at = locate(document.uri)
let text = ''
let activity: string | undefined
const publish = (): void => {
this.post({
type: 'rewriteProgress',
anchor,
question: prompt,
text,
...(activity !== undefined && { activity }),
})
}
publish()
const profile = await this.owner.qa.profiles.current()
let text: string
if (signal.aborted)
return { status: 'stopped' }
let answerText: string
try {
const answer = await askClaude({
prompt,
images: [],
...(profile && { profilePath: profile.path }),
cwd: at?.folder.uri.fsPath ?? dirname(document.uri.fsPath),
signal: this.rewriteAbort.signal,
onEvent: () => {},
signal,
onEvent: (event) => {
if (event.kind === 'text') {
text = event.text
activity = undefined
}
else if (event.kind === 'activity') {
activity = event.label
}
else {
return
}
publish()
},
})
text = answer.text
answerText = answer.text
}
catch (err) {
if (!this.rewriteAbort.signal.aborted)
this.failRewrite(askFailure(err))
return
}
if (this.rewriteAbort.signal.aborted)
return
const body = modelCommentBody(text)
if (!body) {
this.failRewrite('模型没有给出注释正文,没有改文件。')
return
}
if (block.endLine >= document.lineCount) {
this.failRewrite('这段注释在重写期间变了,没有写回。')
return
if (signal.aborted)
return { status: 'stopped' }
return failed(askFailure(err))
}
if (signal.aborted)
return { status: 'stopped' }
const body = modelCommentBody(answerText)
if (!body)
return failed('模型没有给出注释正文,没有改文件。')
if (block.endLine >= document.lineCount)
return failed('这段注释在重写期间变了,没有写回。')
const current = Array.from({ length: block.endLine - block.startLine + 1 }, (_, i) => document.lineAt(block.startLine + i).text).join('\n')
if (current !== snapshot) {
this.failRewrite('这段注释在重写期间变了,没有写回。')
return
}
if (current !== snapshot)
return failed('这段注释在重写期间变了,没有写回。')
const filled = refillComment(comment, body)
if (!filled) {
this.failRewrite('这段注释的定界符认不出来,没有改文件。')
return
}
await this.applyEdit(block.startLine, block.endLine, filled)
if (!filled)
return failed('这段注释的定界符认不出来,没有改文件。')
if (signal.aborted)
return { status: 'stopped' }
const wrote = await this.applyEdit(block.startLine, block.endLine, filled)
if (wrote === 'written' || wrote === 'unchanged')
return { status: 'done' }
if (wrote === 'rejected')
return { status: 'failed', error: `没能把修改写回 ${workspace.asRelativePath(document.uri)}` }
return failed('这段注释在重写期间变了,没有写回。')
}
private async applyEdit(startLine: number, endLine: number, text: string): Promise<void> {
/** 把这几行换成 [text]。`written` 写进去了,`unchanged` 跟文件里一样不用写,`missing` 行号已经不在,`rejected` 编辑器没接受。 */
private async applyEdit(startLine: number, endLine: number, text: string): Promise<'written' | 'unchanged' | 'missing' | 'rejected'> {
const { document } = this
const replacement = blockReplacement(line => document.lineAt(line).text, document.lineCount, startLine, endLine, text)
if (!replacement) {
logger.add({ level: 'warn', source: 'codeReader', message: `块内修改的行号 ${startLine}-${endLine} 已不在 ${document.uri.fsPath} 里,没有写回` })
return
return 'missing'
}
if (replacement.unchanged)
return
return 'unchanged'
const edit = new WorkspaceEdit()
edit.replace(document.uri, new Range(replacement.startLine, 0, replacement.endLine, replacement.endCharacter), replacement.text)
if (!await workspace.applyEdit(edit))
if (!await workspace.applyEdit(edit)) {
void window.showErrorMessage(`代码阅读:没能把修改写回 ${workspace.asRelativePath(document.uri)}`)
return 'rejected'
}
return 'written'
}
}