✨ feat(vscode): 代码阅读的注释挂到它讲的代码块上成「注释」一行,重写注释改用技能一句话加结构化结果
- 普通注释不再单独成卡片:同一层挂到后面最近的块(隔空行也算),后面没有挂到前面那块,整层没代码挂到父块;只有文件开头隔着空行的注释、模块 docstring、//!、@file 单独成「文件开头的说明」 - 各语言(Python、TypeScript、Rust、Shell、Dockerfile)建好树后统一调 attachComments;块的编辑范围连着挂上的注释,差异、跳转、钉住卡片都按它 - 宿主统一给每张卡片(语言卡片、翻译插件的属性表、库规则卡片、一行译文的语句卡片)插「注释」一行,排在名称、说明后面 - TypeScript 的 JSDoc 也能当常量、变量的说明;sqlx、React 不再单独处理前面的注释 - 「重写注释」每块都有:提示只发 /ecc:writing-code-comments 路径:起-止,模型只读、按 --json-schema 交 body,没交结构化正文就失败不写;扩展换掉块前面的注释或在块上面新写一段
This commit is contained in:
1 parent
335cf9ca9f
commit
582ac4c987
49 files changed
+1730
-757
No files matched your search
@@ -1,5 +1,6 @@
|
||||
import type { CustomTextEditorProvider, Disposable, DocumentSymbol, Event, TextDocument, TextDocumentShowOptions, WebviewPanel } from 'vscode'
|
||||
import type { ClaudeImage } from '../../cc/spawnClaude'
|
||||
import type { CommentEdit, RewriteOutcome, WriteResult } from '../commentRewrite'
|
||||
import type { BlockKind, LocalSymbol, RuleLocation, RuleReference, SourceRange, WorkspaceIndex } from '../contract'
|
||||
import type { DiffBase } from '../diff/diffBase'
|
||||
import type { OldDocument, SentDocument } from '../diff/previewDiff'
|
||||
@@ -16,18 +17,18 @@ import type { FoundLocation } from './definitions'
|
||||
import type { DefinitionPeek, ExtensionToWebview, Occurrence, Reveal, WebviewToExtension } from './messages'
|
||||
import type { TargetSpot } from './openTarget'
|
||||
import { realpath } from 'node:fs/promises'
|
||||
import { dirname } from 'node:path'
|
||||
import { commands, Range, Uri, window, workspace, WorkspaceEdit } from 'vscode'
|
||||
import { dirname, relative, sep } from 'node:path'
|
||||
import { commands, EndOfLine, Position, Range, Uri, window, workspace, WorkspaceEdit } from 'vscode'
|
||||
import { logger } from '../../logging/logger'
|
||||
import { webviewHtml, webviewUiRoot } from '../../webview/html'
|
||||
import { DocumentCallees } from '../calleeKinds'
|
||||
import { commentRewritePrompt, commentSource, findPlaced, modelCommentBody, refillComment } from '../commentRewrite'
|
||||
import { findBlock, rewriteBlockComment } 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 { askCommentBody, askFailure } from '../qa/claudeAsk'
|
||||
import { PATH_ASK_ANCHOR } from '../qa/pathQa'
|
||||
import { askPrompt, followUpPrompt } from '../qa/prompt'
|
||||
import { DocumentLookups } from '../ruleLookups'
|
||||
@@ -164,12 +165,6 @@ 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
|
||||
@@ -962,113 +957,83 @@ class Preview {
|
||||
this.failRewrite(message)
|
||||
return { status: 'failed', error: message }
|
||||
}
|
||||
const found = this.sent && findPlaced(this.sent.blocks, anchor)
|
||||
if (!found)
|
||||
return failed('这块注释刚改过,找不到了。等预览刷新后再重写。')
|
||||
const { block, siblings, index } = found
|
||||
const startLine = block.commentStartLine
|
||||
const endLine = block.commentEndLine
|
||||
if (startLine === undefined || endLine === undefined)
|
||||
return failed('这块没有能重写的注释。')
|
||||
const block = this.sent && findBlock(this.sent.blocks, anchor)
|
||||
if (!block)
|
||||
return failed('这块代码刚改过,找不到了。等预览刷新后再重写。')
|
||||
const target = block.commentTarget
|
||||
if (!target)
|
||||
return failed('这块不能写注释。')
|
||||
const { document } = this
|
||||
if (startLine < 0 || endLine < startLine || endLine >= document.lineCount)
|
||||
return failed('这块注释的位置已经变了,没有写回。')
|
||||
const linesOf = (from: number, to: number): string => Array.from({ length: to - from + 1 }, (_, i) => document.lineAt(from + i).text).join('\n')
|
||||
const raw = linesOf(startLine, endLine).split('\n')
|
||||
const snapshot = raw.join('\n')
|
||||
const comment = commentSource(raw)
|
||||
const source = document.getText()
|
||||
const attached = startLine !== block.startLine || endLine !== block.endLine
|
||||
? [
|
||||
...(startLine > block.startLine ? [linesOf(block.startLine, startLine - 1)] : []),
|
||||
...(endLine < block.endLine ? [linesOf(endLine + 1, block.endLine)] : []),
|
||||
].filter(part => part !== '').join('\n')
|
||||
: undefined
|
||||
const next = siblings[index + 1]
|
||||
const following = attached === undefined && next && next.startLine >= 0 && next.endLine >= next.startLine && next.endLine < document.lineCount
|
||||
? linesOf(next.startLine, next.endLine)
|
||||
: undefined
|
||||
const prompt = commentRewritePrompt({
|
||||
comment,
|
||||
source,
|
||||
startLine,
|
||||
endLine,
|
||||
earlierKinds: siblings.slice(0, index).map(item => item.kind),
|
||||
...(following !== undefined && { following }),
|
||||
...(attached !== undefined && attached !== '' && { attached }),
|
||||
})
|
||||
if (!prompt)
|
||||
return failed('这段注释的定界符认不出来,没有改文件。')
|
||||
if (document.isDirty)
|
||||
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 cwd = at?.folder.uri.fsPath ?? dirname(document.uri.fsPath)
|
||||
const profile = await this.owner.qa.profiles.current()
|
||||
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,
|
||||
onEvent: (event) => {
|
||||
if (event.kind === 'text') {
|
||||
text = event.text
|
||||
activity = undefined
|
||||
}
|
||||
else if (event.kind === 'activity') {
|
||||
activity = event.label
|
||||
}
|
||||
else {
|
||||
return
|
||||
}
|
||||
publish()
|
||||
},
|
||||
})
|
||||
answerText = answer.text
|
||||
let question = ''
|
||||
let text = ''
|
||||
let activity: string | undefined
|
||||
const publish = (): void => {
|
||||
this.post({ type: 'rewriteProgress', anchor, question, text, ...(activity !== undefined && { activity }) })
|
||||
}
|
||||
catch (err) {
|
||||
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 (endLine >= document.lineCount)
|
||||
return failed('这段注释在重写期间变了,没有写回。')
|
||||
const current = linesOf(startLine, endLine)
|
||||
if (current !== snapshot)
|
||||
return failed('这段注释在重写期间变了,没有写回。')
|
||||
const filled = refillComment(comment, body)
|
||||
if (!filled)
|
||||
return failed('这段注释的定界符认不出来,没有改文件。')
|
||||
if (signal.aborted)
|
||||
return { status: 'stopped' }
|
||||
const wrote = await this.applyEdit(startLine, endLine, filled)
|
||||
if (wrote === 'written' || wrote === 'unchanged')
|
||||
return { status: 'done' }
|
||||
if (wrote === 'rejected')
|
||||
return { status: 'failed', error: `没能把修改写回 ${workspace.asRelativePath(document.uri)}` }
|
||||
return failed('这段注释在重写期间变了,没有写回。')
|
||||
const outcome = await rewriteBlockComment({
|
||||
target,
|
||||
startLine: block.startLine,
|
||||
endLine: block.endLine,
|
||||
path: relative(cwd, document.uri.fsPath).split(sep).join('/'),
|
||||
lineComment: this.registry.languageFor(this.languageId)?.lineComment,
|
||||
}, {
|
||||
lineAt: line => document.lineAt(line).text,
|
||||
lineCount: () => document.lineCount,
|
||||
ask: (prompt) => {
|
||||
question = prompt
|
||||
publish()
|
||||
return askCommentBody({
|
||||
prompt,
|
||||
...(profile && { profilePath: profile.path }),
|
||||
cwd,
|
||||
signal,
|
||||
onEvent: (event) => {
|
||||
if (event.kind === 'text') {
|
||||
text = event.text
|
||||
activity = undefined
|
||||
}
|
||||
else {
|
||||
activity = event.label
|
||||
}
|
||||
publish()
|
||||
},
|
||||
})
|
||||
},
|
||||
failure: askFailure,
|
||||
write: edit => this.applyCommentEdit(edit),
|
||||
signal,
|
||||
})
|
||||
if (outcome.status === 'failed')
|
||||
this.failRewrite(outcome.error)
|
||||
return outcome
|
||||
}
|
||||
|
||||
/** 把这几行换成 [text]。`written` 写进去了,`unchanged` 跟文件里一样不用写,`missing` 行号已经不在,`rejected` 编辑器没接受。 */
|
||||
private async applyEdit(startLine: number, endLine: number, text: string): Promise<'written' | 'unchanged' | 'missing' | 'rejected'> {
|
||||
/** 写回重写好的注释:换掉原来那几行(同块编辑),或在块上面插进新写的几行。 */
|
||||
private async applyCommentEdit(edit: CommentEdit): Promise<WriteResult> {
|
||||
if (edit.kind === 'replace')
|
||||
return this.applyEdit(edit.startLine, edit.endLine, edit.text)
|
||||
const { document } = this
|
||||
if (edit.line >= document.lineCount)
|
||||
return 'missing'
|
||||
const eol = document.eol === EndOfLine.CRLF ? '\r\n' : '\n'
|
||||
const change = new WorkspaceEdit()
|
||||
change.insert(document.uri, new Position(edit.line, 0), edit.text.split('\n').join(eol) + eol)
|
||||
if (!await workspace.applyEdit(change)) {
|
||||
void window.showErrorMessage(`代码阅读:没能把修改写回 ${workspace.asRelativePath(document.uri)}`)
|
||||
return 'rejected'
|
||||
}
|
||||
return 'written'
|
||||
}
|
||||
|
||||
/** 把这几行换成 [text](见 WriteResult)。 */
|
||||
private async applyEdit(startLine: number, endLine: number, text: string): Promise<WriteResult> {
|
||||
const { document } = this
|
||||
const replacement = blockReplacement(line => document.lineAt(line).text, document.lineCount, startLine, endLine, text)
|
||||
if (!replacement) {
|
||||
|
||||
Reference in new issue
Block a user