From b2db55afc867c6f6701cedf928a41799e4e48678 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=A8=8B=E5=BA=8F=E5=91=98=E9=98=BF=E6=B1=9F=28Relakkes?= =?UTF-8?q?=29?= Date: Thu, 16 Jul 2026 18:29:16 +0800 Subject: [PATCH] fix(desktop): support Shift range comments in files #1004 --- .../workspace/WorkspacePanel.test.tsx | 76 ++++++++++++++ .../components/workspace/WorkspacePanel.tsx | 98 +++++++++++++------ desktop/src/i18n/locales/en.ts | 1 + desktop/src/i18n/locales/jp.ts | 1 + desktop/src/i18n/locales/kr.ts | 1 + desktop/src/i18n/locales/zh-TW.ts | 1 + desktop/src/i18n/locales/zh.ts | 1 + 7 files changed, 150 insertions(+), 29 deletions(-) diff --git a/desktop/src/components/workspace/WorkspacePanel.test.tsx b/desktop/src/components/workspace/WorkspacePanel.test.tsx index 9e67b626..17385fd5 100644 --- a/desktop/src/components/workspace/WorkspacePanel.test.tsx +++ b/desktop/src/components/workspace/WorkspacePanel.test.tsx @@ -2247,6 +2247,82 @@ describe('WorkspacePanel', () => { ]) }) + it('adds a Shift-selected line range comment from a code preview to the chat context', async () => { + await setWorkspaceState((state) => ({ + ...state, + panelBySession: { + ...state.panelBySession, + 'session-line-range-comment': { + isOpen: true, + activeView: 'all', + }, + }, + statusBySession: { + ...state.statusBySession, + 'session-line-range-comment': { + state: 'ok', + workDir: '/repo', + repoName: 'repo', + branch: 'main', + isGitRepo: true, + changedFiles: [], + }, + }, + previewTabsBySession: { + ...state.previewTabsBySession, + 'session-line-range-comment': [{ + id: 'file:src/App.tsx', + path: 'src/App.tsx', + kind: 'file', + title: 'App.tsx', + language: 'tsx', + content: 'const title = "Todo"\nconst count = 1\nexport default title', + state: 'ok', + size: 64, + }], + }, + activePreviewTabIdBySession: { + ...state.activePreviewTabIdBySession, + 'session-line-range-comment': 'file:src/App.tsx', + }, + })) + + const view = await renderPanel('session-line-range-comment') + const firstLine = view.getByRole('button', { name: 'Comment line 1' }) + const secondLine = view.getByRole('button', { name: 'Comment line 2' }) + const thirdLine = view.getByRole('button', { name: 'Comment line 3' }) + + await clickElement(firstLine) + await act(async () => { + fireEvent.click(thirdLine, { shiftKey: true }) + await Promise.resolve() + }) + + expect(firstLine.getAttribute('aria-pressed')).toBe('true') + expect(secondLine.getAttribute('aria-pressed')).toBe('true') + expect(thirdLine.getAttribute('aria-pressed')).toBe('true') + expect(view.getByText('Lines 1–3')).toBeTruthy() + + const textarea = view.getByPlaceholderText('Describe what should change here...') + await act(() => { + fireEvent.change(textarea, { target: { value: 'Extract this setup' } }) + }) + await clickElement(view.getByRole('button', { name: 'Add comment' })) + + expect(useWorkspaceChatContextStore.getState().referencesBySession['session-line-range-comment']).toMatchObject([ + { + kind: 'code-comment', + path: 'src/App.tsx', + absolutePath: '/repo/src/App.tsx', + name: 'App.tsx', + lineStart: 1, + lineEnd: 3, + note: 'Extract this setup', + quote: 'const title = "Todo"\nconst count = 1\nexport default title', + }, + ]) + }) + it('adds a side-aware diff comment, keeps the diff open, and focuses the composer', async () => { await setWorkspaceState((state) => ({ ...state, diff --git a/desktop/src/components/workspace/WorkspacePanel.tsx b/desktop/src/components/workspace/WorkspacePanel.tsx index d514fee8..86e7b03b 100644 --- a/desktop/src/components/workspace/WorkspacePanel.tsx +++ b/desktop/src/components/workspace/WorkspacePanel.tsx @@ -532,26 +532,30 @@ function CodeSurface({ }: { value: string language: string - onAddLineComment: (line: number, note: string, quote: string) => void + onAddLineComment: (lineStart: number, lineEnd: number, note: string, quote: string) => void onAddSelection: (selection: WorkspaceTextSelection) => void }) { const t = useTranslation() const surfaceRef = useRef(null) const selectionMenuRef = useRef(null) - const [commentLine, setCommentLine] = useState(null) + const [commentRange, setCommentRange] = useState<{ anchorLine: number; focusLine: number } | null>(null) const [commentDraft, setCommentDraft] = useState('') const [showAllLines, setShowAllLines] = useState(false) const [selectionMenu, setSelectionMenu] = useState(null) const [shikiTokensByLine, setShikiTokensByLine] = useState(null) const lines = value.split('\n') const visibleLines = showAllLines ? lines : lines.slice(0, WORKSPACE_PREVIEW_LINE_LIMIT) - const activeQuote = commentLine ? visibleLines[commentLine - 1] ?? '' : '' + const commentLineStart = commentRange ? Math.min(commentRange.anchorLine, commentRange.focusLine) : null + const commentLineEnd = commentRange ? Math.max(commentRange.anchorLine, commentRange.focusLine) : null + const activeQuote = commentLineStart && commentLineEnd + ? visibleLines.slice(commentLineStart - 1, commentLineEnd).join('\n') + : '' const usePlainLargePreview = showAllLines && lines.length > WORKSPACE_PREVIEW_LINE_LIMIT const visibleCode = usePlainLargePreview ? '' : visibleLines.join('\n') useEffect(() => { setShowAllLines(false) - setCommentLine(null) + setCommentRange(null) setCommentDraft('') setSelectionMenu(null) }, [language, value]) @@ -588,9 +592,9 @@ function CodeSurface({ }) const submitLineComment = () => { - if (!commentLine || !commentDraft.trim()) return - onAddLineComment(commentLine, commentDraft.trim(), activeQuote) - setCommentLine(null) + if (!commentLineStart || !commentLineEnd || !commentDraft.trim()) return + onAddLineComment(commentLineStart, commentLineEnd, commentDraft.trim(), activeQuote) + setCommentRange(null) setCommentDraft('') } @@ -619,7 +623,7 @@ function CodeSurface({ } const renderLineCommentEditor = (lineNumber: number) => { - if (commentLine !== lineNumber) return null + if (!commentLineStart || commentLineEnd !== lineNumber) return null return (
@@ -629,7 +633,9 @@ function CodeSurface({ chat_bubble {t('workspace.localComment')} - {t('workspace.commentLineTarget', { line: lineNumber })} + {commentLineStart === commentLineEnd + ? t('workspace.commentLineTarget', { line: commentLineStart }) + : t('workspace.commentLineRangeTarget', { start: commentLineStart, end: commentLineEnd })}