✨ feat(vscode): 注释卡片可按规范重写注释
This commit is contained in:
1 parent
724c81188c
commit
299acb979f
11 files changed
+717
-5
No files matched your search
@@ -37,6 +37,9 @@ export function App(): ReactElement | null {
|
||||
// 先于下面发 ready 的那个 effect 挂上收消息:ready 后扩展端马上发问答
|
||||
const qa = useQa()
|
||||
const [doc, setDoc] = useState<DocumentMessage | null>(null)
|
||||
/** 正在按注释规范重写的块(记号)。点下去立刻记上,扩展端写完或失败再发 commentRewritten */
|
||||
const rewritingRef = useRef(new Set<string>())
|
||||
const [rewriting, setRewriting] = useState<ReadonlySet<string>>(NONE)
|
||||
const [view, setView] = useState<BlockModes>(savedBlockModes)
|
||||
const [focusId, setFocusId] = useState<string | null>(null)
|
||||
/** 等着滚过去的位置:新预览第一份文档带的,或已开着的预览收到的 reveal */
|
||||
@@ -114,6 +117,10 @@ export function App(): ReactElement | null {
|
||||
return
|
||||
case 'diff':
|
||||
setDiff(msg.diff)
|
||||
return
|
||||
case 'commentRewritten':
|
||||
rewritingRef.current.delete(msg.anchor)
|
||||
setRewriting(new Set(rewritingRef.current))
|
||||
}
|
||||
})
|
||||
postMessage({ type: 'ready' })
|
||||
@@ -126,6 +133,13 @@ export function App(): ReactElement | null {
|
||||
}, [view])
|
||||
|
||||
const onFocused = useCallback(() => setFocusId(null), [])
|
||||
const onRewriteComment = useCallback((anchor: string) => {
|
||||
if (rewritingRef.current.has(anchor))
|
||||
return
|
||||
rewritingRef.current.add(anchor)
|
||||
setRewriting(new Set(rewritingRef.current))
|
||||
postMessage({ type: 'rewriteComment', anchor })
|
||||
}, [])
|
||||
const kindNames = useMemo(() => new Map(doc?.attributes.find(attribute => attribute.id === 'kind')?.values.map(value => [value.id, value.name])), [doc])
|
||||
const kindName = useCallback((kind: string) => kindNames.get(kind) ?? kind, [kindNames])
|
||||
const tree = useMemo((): BlockTreeState | null => doc && {
|
||||
@@ -147,11 +161,13 @@ export function App(): ReactElement | null {
|
||||
},
|
||||
onToggleFold: id => setFolded([id], !collapsed.has(id)),
|
||||
onSetFolds: folds => setView(prev => ({ ...prev, folds: { ...prev.folds, ...folds } })),
|
||||
rewriting,
|
||||
onRewriteComment,
|
||||
...(qa.state.available && { qa }),
|
||||
...(changes && { diff: changes }),
|
||||
...(folded && { folded }),
|
||||
onOpenRun: id => setOpenedRuns(prev => new Set([...prev, id])),
|
||||
}, [doc, view, collapsed, filter, covered, kindName, focusId, onFocused, setFolded, qa, changes, folded])
|
||||
}, [doc, view, collapsed, filter, covered, kindName, focusId, onFocused, setFolded, rewriting, onRewriteComment, qa, changes, folded])
|
||||
|
||||
if (!doc || !tree)
|
||||
return null
|
||||
|
||||
@@ -2,8 +2,15 @@ import { renderToStaticMarkup } from 'react-dom/server'
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { BlockTools } from './BlockTools'
|
||||
|
||||
function render(editing: boolean, ask?: { asking: boolean }): string {
|
||||
return renderToStaticMarkup(<BlockTools editing={editing} onToggleEdit={() => {}} {...(ask && { asking: ask.asking, onToggleAsk: () => {} })} />)
|
||||
function render(editing: boolean, ask?: { asking: boolean }, rewrite?: { rewriting: boolean }): string {
|
||||
return renderToStaticMarkup(
|
||||
<BlockTools
|
||||
editing={editing}
|
||||
onToggleEdit={() => {}}
|
||||
{...(ask && { asking: ask.asking, onToggleAsk: () => {} })}
|
||||
{...(rewrite && { rewriting: rewrite.rewriting, onRewrite: () => {} })}
|
||||
/>,
|
||||
)
|
||||
}
|
||||
|
||||
/** 标签之间的字:图标(svg)没有字,按钮上有字就会出现在这里 */
|
||||
@@ -31,4 +38,19 @@ describe('块右上角的图标按钮', () => {
|
||||
expect(buttons[0].includes('data-active')).toBe(asking)
|
||||
expect(buttons[1]).toContain('data-tooltip="编辑"')
|
||||
})
|
||||
|
||||
it('「按注释规范重写」排在「问 AI」左边;正在重写时禁用并转圈,不用原生 title', () => {
|
||||
const html = render(false, { asking: false }, { rewriting: false })
|
||||
expect(visibleText(html)).toBe('')
|
||||
expect(html).not.toContain(' title=')
|
||||
const buttons = [...html.matchAll(/<button[^>]*>/g)].map(match => match[0])
|
||||
expect(buttons).toHaveLength(3)
|
||||
expect(buttons[0]).toContain('data-tooltip="按注释规范重写" aria-label="按注释规范重写"')
|
||||
expect(buttons[0]).not.toContain('disabled')
|
||||
expect(buttons[1]).toContain('data-tooltip="问 AI"')
|
||||
expect(buttons[2]).toContain('data-tooltip="编辑"')
|
||||
const busy = render(false, { asking: false }, { rewriting: true })
|
||||
expect(busy).toContain('disabled=""')
|
||||
expect(busy).toContain('cr-qa-spin')
|
||||
})
|
||||
})
|
||||
@@ -1,23 +1,43 @@
|
||||
import type { ReactElement } from 'react'
|
||||
import { Eye, Pencil, Sparkles } from 'lucide-react'
|
||||
import { Eye, LoaderCircle, Pencil, RefreshCw, Sparkles } from 'lucide-react'
|
||||
|
||||
/**
|
||||
* 块右上角的一排图标按钮(普通块、属性表、库规则的卡片共用):只放图标,名字写在悬停提示(data-tooltip)和读屏用的 aria-label 里。
|
||||
* 「按注释规范重写」:只给注释块。点了用写注释的技能重写这一段并写回文件;正在重写时转圈,不能再点。
|
||||
* 「问 AI」:点开 / 收起块下面的问答区,展开时带按下的底色。
|
||||
* 「编辑 / 预览」切换:预览时是铅笔「编辑」,编辑时是眼睛「预览」、带按下的底色。
|
||||
*/
|
||||
export function BlockTools({ editing, onToggleEdit, asking, onToggleAsk }: {
|
||||
export function BlockTools({ editing, onToggleEdit, asking, onToggleAsk, rewriting, onRewrite }: {
|
||||
editing: boolean
|
||||
onToggleEdit: () => void
|
||||
/** 块下面的问答区展开着 */
|
||||
asking?: boolean
|
||||
/** 没给就不显示「问 AI」(文件不在工作区文件夹里,问答没地方存) */
|
||||
onToggleAsk?: () => void
|
||||
/** 这段注释正在重写 */
|
||||
rewriting?: boolean
|
||||
/** 没给就不显示「按注释规范重写」(不是注释块) */
|
||||
onRewrite?: () => void
|
||||
}): ReactElement {
|
||||
const label = editing ? '预览' : '编辑'
|
||||
const Icon = editing ? Eye : Pencil
|
||||
return (
|
||||
<div className="flex h-5 shrink-0 items-center gap-0.5" role="group" aria-label="块操作">
|
||||
{onRewrite && (
|
||||
<button
|
||||
type="button"
|
||||
className="cr-tool flex h-5 w-6 items-center justify-center rounded"
|
||||
data-tooltip="按注释规范重写"
|
||||
aria-label="按注释规范重写"
|
||||
aria-busy={rewriting === true}
|
||||
disabled={rewriting === true}
|
||||
onClick={onRewrite}
|
||||
>
|
||||
{rewriting
|
||||
? <LoaderCircle size={14} strokeWidth={1.8} className="cr-qa-spin" aria-hidden />
|
||||
: <RefreshCw size={14} strokeWidth={1.8} aria-hidden />}
|
||||
</button>
|
||||
)}
|
||||
{onToggleAsk && (
|
||||
<button
|
||||
type="button"
|
||||
|
||||
@@ -67,6 +67,8 @@ function render(folds: Record<string, boolean>): string {
|
||||
onToggleEdit: () => {},
|
||||
onToggleFold: () => {},
|
||||
onSetFolds: () => {},
|
||||
rewriting: new Set(),
|
||||
onRewriteComment: () => {},
|
||||
onOpenRun: () => {},
|
||||
}
|
||||
return renderToStaticMarkup(<BlockLevel blocks={[CARD]} tree={tree} />)
|
||||
|
||||
@@ -37,6 +37,9 @@ export interface BlockTreeState {
|
||||
onToggleFold: (id: string) => void
|
||||
/** 一次记下几块、几行折叠还是展开(点子行展开一块、同一行里别的收起) */
|
||||
onSetFolds: (folds: Record<string, boolean>) => void
|
||||
/** 正在按注释规范重写的注释块(记号) */
|
||||
rewriting: ReadonlySet<string>
|
||||
onRewriteComment: (anchor: string) => void
|
||||
/** 问 AI;文件不在工作区文件夹里(问答没地方存)时没有 */
|
||||
qa?: QaTree
|
||||
/** 代码差异:新增、修改的块和删掉的块;侧栏开着「未提交的更改」或审查工单、基准里有这个文件时才有 */
|
||||
@@ -276,6 +279,8 @@ function BlockCard({ block, tree }: { block: BlockView, tree: BlockTreeState }):
|
||||
onToggleEdit={() => tree.onToggleEdit(block.id)}
|
||||
asking={tree.qa?.state.open.includes(block.anchor)}
|
||||
onToggleAsk={tree.qa && (() => tree.qa?.onToggle(block.anchor))}
|
||||
rewriting={tree.rewriting.has(block.anchor)}
|
||||
onRewrite={block.kind === 'comment' ? () => tree.onRewriteComment(block.anchor) : undefined}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -119,6 +119,9 @@ body:is(.vscode-light, .vscode-high-contrast-light) [data-badge] { --kind: var(-
|
||||
outline: 1px solid var(--vscode-focusBorder);
|
||||
outline-offset: -1px;
|
||||
}
|
||||
.cr-tool:disabled {
|
||||
opacity: 0.4;
|
||||
}
|
||||
|
||||
/* 层级竖线:悬停一个块时,它子层的竖线换成它的种类色,标出这个块管到哪里为止(块和子层中间隔着问答区也算) */
|
||||
.cr-guide {
|
||||
|
||||
@@ -406,6 +406,7 @@ export type ExtensionToWebview
|
||||
| ({ type: 'askProfiles' } & ProfileChoice)
|
||||
| { type: 'answer', askId: string, text: string, activity?: string }
|
||||
| AnswerEnd
|
||||
| { type: 'commentRewritten', anchor: string }
|
||||
| { type: 'diff', diff: DocumentDiff | null }
|
||||
|
||||
/**
|
||||
@@ -429,6 +430,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 }
|
||||
|
||||
Reference in new issue
Block a user