✨ feat(vscode): 注释卡片可按规范重写注释

This commit is contained in:
cruldra committed 2026-10-03 03:22:20 +08:00
1 parent 724c81188c
commit 299acb979f
11 files changed
+717 -5

No files matched your search

+2
View File
@@ -106,6 +106,7 @@ export type ExtensionToWebview
| ({ type: 'askProfiles' } & ProfileChoice)
| { type: 'answer', askId: string, text: string, activity?: string }
| { type: 'answerEnd', askId: string, status: 'done' | 'failed' | 'stopped', text?: string, error?: string, entryId?: string }
| { type: 'commentRewritten', anchor: string }
| { type: 'diff', diff: DocumentDiff | null }
/**
@@ -130,6 +131,7 @@ export type WebviewToExtension
| { type: 'setFilter', excluded: Excluded }
| { type: 'setOnlyChanges', on: boolean }
| { type: 'edit', startLine: number, endLine: number, text: string }
| { type: 'rewriteComment', anchor: string }
| { type: 'selectTranslator', id: string }
| { type: 'visibleLine', line: number }
| { type: 'probeDefinition', id: number, version: number, offset: number }
@@ -12,15 +12,18 @@ import type { IndexedEntry } from '../workspaceIndex'
import type { BlockFilter } from './blockFilter'
import type { Definition, FoundLocation } from './definitions'
import type { ExtensionToWebview, Occurrence, Reveal, WebviewToExtension } from './messages'
import { dirname } from 'node:path'
import { commands, Range, window, workspace, WorkspaceEdit } from 'vscode'
import { logger } from '../../logging/logger'
import { webviewHtml, webviewUiRoot } from '../../webview/html'
import { DocumentCallees } from '../calleeKinds'
import { commentRewritePrompt, commentSource, findPlaced, loadCommentSkill, modelCommentBody, refillComment } from '../commentRewrite'
import { PreviewDiff } from '../diff/previewDiff'
import { blockReplacement } from '../editText'
import { locate } from '../folderStore'
import { definitionFor, symbolAt, symbolOccurrences } from '../localSymbols'
import { DocumentNames, resolveNames } from '../nameResolution'
import { askClaude, askFailure } from '../qa/claudeAsk'
import { askPrompt, followUpPrompt } from '../qa/prompt'
import { DocumentLookups } from '../ruleLookups'
import { definitionAction } from './definitionAction'
@@ -137,6 +140,10 @@ class Preview {
private readonly definitions: DefinitionSources<FoundLocation>
/** webview 报来的视口顶部那块的起始行 */
visibleLine: number | undefined
/** 正在重写的注释(块的记号)。同一块没写完不再开一次 */
private readonly rewriting = new Set<string>()
/** 预览关掉时停掉还在跑的重写 */
private readonly rewriteAbort = new AbortController()
/** 最近一次发出去的文档(版本、块树、源码):问 AI 时按块的记号在里面找那块代码,对比基准变了时拿它重新比 */
private sent: SentDocument | undefined
/** 最近一次发出去的文档里语言插件自己认得出的名字(LanguageAdapter.symbols):语言扩展查不到定义、各处出现时用 */
@@ -374,6 +381,7 @@ class Preview {
}
dispose(): void {
this.rewriteAbort.abort()
clearTimeout(this.timer)
this.names.dispose()
this.lookups.dispose()
@@ -453,6 +461,9 @@ class Preview {
case 'edit':
await this.applyEdit(msg.startLine, msg.endLine, msg.text)
return
case 'rewriteComment':
await this.rewriteComment(msg.anchor)
return
case 'selectTranslator':
await this.registry.selectTranslator(this.languageId, msg.id)
this.owner.refresh(this.languageId)
@@ -539,6 +550,111 @@ class Preview {
await this.owner.openAt(uri, reveal, () => commands.executeCommand('vscode.openWith', uri, PREVIEW_VIEW_TYPE, options))
}
/** 用写注释的技能重写这一段,只把正文套回原来的定界符,再走块编辑那条写回。 */
private async rewriteComment(anchor: string): Promise<void> {
if (this.rewriting.has(anchor) || this.rewriteAbort.signal.aborted)
return
this.rewriting.add(anchor)
try {
await this.rewriteCommentNow(anchor)
}
finally {
this.rewriting.delete(anchor)
this.post({ type: 'commentRewritten', anchor })
}
}
private failRewrite(message: string): void {
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
}
const { block, siblings, index } = found
const { document } = this
if (block.startLine < 0 || block.endLine < block.startLine || block.endLine >= document.lineCount) {
this.failRewrite('这块注释的位置已经变了,没有写回。')
return
}
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)
const source = document.getText()
const next = siblings[index + 1]
const following = next && next.startLine >= 0 && next.endLine >= next.startLine && next.endLine < document.lineCount
? Array.from({ length: next.endLine - next.startLine + 1 }, (_, i) => document.lineAt(next.startLine + i).text).join('\n')
: undefined
let skill: string | undefined
try {
skill = await loadCommentSkill()
}
catch (err) {
if (!this.rewriteAbort.signal.aborted)
this.failRewrite(`读注释技能失败:${err instanceof Error ? err.message : String(err)}`)
return
}
if (this.rewriteAbort.signal.aborted)
return
const prompt = commentRewritePrompt({
skill,
comment,
source,
startLine: block.startLine,
endLine: block.endLine,
earlierKinds: siblings.slice(0, index).map(item => item.kind),
...(following !== undefined && { following }),
})
if (!prompt) {
this.failRewrite(skill === undefined ? '找不到写注释的技能' : '这段注释的定界符认不出来,没有改文件。')
return
}
const at = locate(document.uri)
const profile = await this.owner.qa.profiles.current()
let text: 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: () => {},
})
text = 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
}
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
}
const filled = refillComment(comment, body)
if (!filled) {
this.failRewrite('这段注释的定界符认不出来,没有改文件。')
return
}
await this.applyEdit(block.startLine, block.endLine, filled)
}
private async applyEdit(startLine: number, endLine: number, text: string): Promise<void> {
const { document } = this
const replacement = blockReplacement(line => document.lineAt(line).text, document.lineCount, startLine, endLine, text)