From a66e4410e7ec98949a564f6fa276eea7259ffa17 Mon Sep 17 00:00:00 2001 From: cruldra Date: Fri, 2 Oct 2026 03:20:57 +0800 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20feat(vscode):=20=E6=9C=89=E8=AF=B4?= =?UTF-8?q?=E6=98=8E=E7=9A=84=E5=87=BD=E6=95=B0=E3=80=81=E7=B1=BB=E5=8D=A1?= =?UTF-8?q?=E7=89=87=E4=B9=9F=E8=83=BD=E9=87=8D=E5=86=99=E6=B3=A8=E9=87=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 重写按钮不再只挂在单独的注释卡片上。只替换说明那几行,上下文用这块代码,不当成文件顶上的说明。 --- vscode/src/codeReader/blockView.ts | 11 +++++++++++ vscode/src/codeReader/commentRewrite.test.ts | 14 ++++++++++++++ vscode/src/codeReader/commentRewrite.ts | 16 ++++++++++------ vscode/src/codeReader/languages/shared.ts | 10 ++++++++++ vscode/webview-ui/src/codeReader/BlockTree.tsx | 2 +- 5 files changed, 46 insertions(+), 7 deletions(-) diff --git a/vscode/src/codeReader/blockView.ts b/vscode/src/codeReader/blockView.ts index 9513150..96d99d7 100644 --- a/vscode/src/codeReader/blockView.ts +++ b/vscode/src/codeReader/blockView.ts @@ -4,6 +4,7 @@ import type { TreeSitterRuntime } from './runtime' import { blockAttributes } from './attributes' import { KIND_ICONS } from './contract' import { commonIndent, splitLines } from './editText' +import { commentSpan } from './languages/shared' import { activeRules, claimBlock, importsIn } from './libraryRules' import { BlockAnchors } from './qa/anchor' @@ -35,6 +36,9 @@ interface BlockViewBase { * 同一个 id 只留第一个。webview 按这个画图标,不自己用种类查。 */ concepts: BlockConcept[] + /** 能重写的那段注释的起止行,0 起。注释块是整块;函数、类等是它的说明。没有说明就没有,不显示重写按钮。 */ + commentStartLine?: number + commentEndLine?: number /** 库规则的卡片才有:筛选栏里这个种类的名字,卡片边框的颜色(RuleCard.badge)。图标在 concepts 里。预览同类块一样默认收起它的子块。 */ badge?: KindBadge /** 库规则放进父卡片里的子块才有(见 RuleSlot):画在父卡片属性表第 row 行下面,不排在父卡片的子块里。 */ @@ -330,6 +334,11 @@ export function toBlockViews( } return concepts } + function commentLines(block: CodeBlock): { commentStartLine: number, commentEndLine: number } | undefined { + const span = commentSpan(block) + return span && { commentStartLine: span.startLine, commentEndLine: span.endLine } + } + /** 块照它自己的样子(语言插件报的,或库规则认成的属性表块):编辑范围是 span,头部是块自己那部分,子块照常交给各规则看。scope 见 BlockAnchors.place */ function plain(block: CodeBlock, span: SourceSpan, id: string, scope: string): BlockView { const name = ownName(block) @@ -343,6 +352,7 @@ export function toBlockViews( ...(name && { defines: { startIndex: name.startIndex, endIndex: name.endIndex } }), attributes: blockAttributes(block), concepts: blockConcepts(block), + ...commentLines(block), children: [ ...list(block.children, `${id}.`, inner), ...embeddedChildren(block, id, inner, block.children.length), @@ -378,6 +388,7 @@ export function toBlockViews( ...(defines && { defines: { startIndex: defines.startIndex, endIndex: defines.endIndex } }), attributes: { ...blockAttributes(block), ...attributes, kind }, concepts: blockConcepts(block, replacement), + ...commentLines(block), badge, children: list(kept, `${id}.`, inner, 0, slots), properties: rows, diff --git a/vscode/src/codeReader/commentRewrite.test.ts b/vscode/src/codeReader/commentRewrite.test.ts index 0aac85a..97d683e 100644 --- a/vscode/src/codeReader/commentRewrite.test.ts +++ b/vscode/src/codeReader/commentRewrite.test.ts @@ -147,6 +147,20 @@ describe('重写注释的提示', () => { expect(isModuleComment('"""旧"""', ['function'])).toBe(false) }) + it('函数上的说明用这块代码当上下文,不当成文件顶上的说明', () => { + const prompt = commentRewritePrompt({ + comment: '"""旧说明"""', + source: 'def run():\n """旧说明"""\n return 1\n\ndef other():\n return 2', + startLine: 1, + endLine: 1, + earlierKinds: [], + attached: 'def run():\n return 1', + }) + expect(prompt).toContain('def run():') + expect(prompt).toContain('return 1') + expect(prompt).not.toContain('def other()') + }) + it('模型套了代码围栏时只留里面的正文', () => { expect(modelCommentBody('```\n新说明\n```')).toBe('新说明') expect(modelCommentBody(' ')).toBeUndefined() diff --git a/vscode/src/codeReader/commentRewrite.ts b/vscode/src/codeReader/commentRewrite.ts index 27380e6..8577b6d 100644 --- a/vscode/src/codeReader/commentRewrite.ts +++ b/vscode/src/codeReader/commentRewrite.ts @@ -285,6 +285,8 @@ export interface CommentRewriteInput { earlierKinds: readonly string[] /** 普通注释后面紧挨着的那一块的原文;没有就不填 */ following?: string + /** 说明挂在这块代码上(函数、类的 docstring)。有它就不当成文件顶上的说明,上下文用这块。 */ + attached?: string } /** 发给模型的整段话。开头是技能命令,Claude Code 自己加载技能。注释认不出就不组。 */ @@ -292,12 +294,14 @@ export function commentRewritePrompt(input: CommentRewriteInput): string | undef const body = commentProse(input.comment) if (body === undefined) return undefined - const fileComment = isModuleComment(input.comment, input.earlierKinds) - const context = fileComment - ? clip(splitLines(input.source).filter((_, index) => index < input.startLine || index > input.endLine).join('\n').trim(), '文件其余部分') - : input.following - ? clip(input.following, '这块代码') - : { text: '(后面没有紧挨着的代码)', note: '' } + const fileComment = input.attached === undefined && isModuleComment(input.comment, input.earlierKinds) + const context = input.attached !== undefined + ? clip(input.attached, '这块代码') + : fileComment + ? clip(splitLines(input.source).filter((_, index) => index < input.startLine || index > input.endLine).join('\n').trim(), '文件其余部分') + : input.following + ? clip(input.following, '这块代码') + : { text: '(后面没有紧挨着的代码)', note: '' } return [ COMMENT_SKILL, '只按这个技能重写这一段注释。用中文。不要改代码,不要加技能没要求的中文名记号。只输出新注释的正文,不要定界符,不要解释。', diff --git a/vscode/src/codeReader/languages/shared.ts b/vscode/src/codeReader/languages/shared.ts index 8bf8a47..0fd8d1a 100644 --- a/vscode/src/codeReader/languages/shared.ts +++ b/vscode/src/codeReader/languages/shared.ts @@ -219,6 +219,16 @@ function ownDoc(block: CodeBlock): Node | undefined { } } +/** 能重写的那段注释:注释块是整块;函数、类、枚举、异常、常量、变量是它的说明。没有说明就没有。 */ +export function commentSpan(block: CodeBlock): { startLine: number, endLine: number } | undefined { + if (block.kind === 'comment') + return { startLine: block.header.startRow, endLine: block.header.endRow } + const doc = ownDoc(block) + if (!doc) + return undefined + return { startLine: doc.startPosition.row, endLine: doc.endPosition.row } +} + /** 类属性的说明,照源码顺序:紧挨在它上面的注释块、它这一行行尾的注释、Python 紧跟在后面的属性 docstring。 */ function fieldDocs(children: readonly CodeBlock[], block: CodeBlock): Node[] { const trailing = block.node.nextNamedSibling diff --git a/vscode/webview-ui/src/codeReader/BlockTree.tsx b/vscode/webview-ui/src/codeReader/BlockTree.tsx index 617e35c..545b14e 100644 --- a/vscode/webview-ui/src/codeReader/BlockTree.tsx +++ b/vscode/webview-ui/src/codeReader/BlockTree.tsx @@ -291,7 +291,7 @@ function BlockCard({ block, tree }: { block: BlockView, tree: BlockTreeState }): asking={tree.qa?.state.open.includes(block.anchor)} onToggleAsk={tree.qa && (() => tree.qa?.onToggle(block.anchor))} rewriting={tree.rewrites.get(block.anchor)?.status === 'running'} - onRewrite={block.kind === 'comment' ? () => tree.onRewriteComment(block.anchor) : undefined} + onRewrite={block.commentStartLine !== undefined ? () => tree.onRewriteComment(block.anchor) : undefined} /> )