diff --git a/src/browser/components/shared/DiffRenderer.tsx b/src/browser/components/shared/DiffRenderer.tsx index 3b02316c1..9fb7d0d44 100644 --- a/src/browser/components/shared/DiffRenderer.tsx +++ b/src/browser/components/shared/DiffRenderer.tsx @@ -175,10 +175,31 @@ interface DiffIndicatorProps { type: DiffLineType; /** Render review button overlay on hover */ reviewButton?: React.ReactNode; + /** When provided, enables drag-to-select behavior in SelectableDiffRenderer */ + onMouseDown?: React.MouseEventHandler; + onMouseEnter?: React.MouseEventHandler; + isInteractive?: boolean; + lineIndex?: number; } -const DiffIndicator: React.FC = ({ type, reviewButton }) => ( - +const DiffIndicator: React.FC = ({ + type, + reviewButton, + onMouseDown, + onMouseEnter, + isInteractive, + lineIndex, +}) => ( + ( searchConfig, enableHighlighting = true, }) => { + const dragAnchorRef = React.useRef(null); + const [isDragging, setIsDragging] = React.useState(false); + + React.useEffect(() => { + const stopDragging = () => { + setIsDragging(false); + dragAnchorRef.current = null; + }; + + window.addEventListener("mouseup", stopDragging); + window.addEventListener("blur", stopDragging); + + return () => { + window.removeEventListener("mouseup", stopDragging); + window.removeEventListener("blur", stopDragging); + }; + }, []); const { theme } = useTheme(); const [selection, setSelection] = React.useState(null); @@ -742,6 +780,34 @@ export const SelectableDiffRenderer = React.memo( }; }, [lineData, showLineNumbers]); + const startDragSelection = React.useCallback( + (lineIndex: number, shiftKey: boolean) => { + if (!onReviewNote) { + return; + } + + // Notify parent that this hunk should become active + onLineClick?.(); + + const anchor = shiftKey && selection ? selection.startIndex : lineIndex; + dragAnchorRef.current = anchor; + setIsDragging(true); + setSelection({ startIndex: anchor, endIndex: lineIndex }); + }, + [onLineClick, onReviewNote, selection] + ); + + const updateDragSelection = React.useCallback( + (lineIndex: number) => { + if (!isDragging || dragAnchorRef.current === null) { + return; + } + + setSelection({ startIndex: dragAnchorRef.current, endIndex: lineIndex }); + }, + [isDragging] + ); + const handleCommentButtonClick = (lineIndex: number, shiftKey: boolean) => { // Notify parent that this hunk should become active onLineClick?.(); @@ -797,6 +863,7 @@ export const SelectableDiffRenderer = React.memo(
( > { + if (!onReviewNote) return; + if (e.button !== 0) return; + e.preventDefault(); + e.stopPropagation(); + startDragSelection(displayIndex, e.shiftKey); + }} + onMouseEnter={() => { + if (!onReviewNote) return; + updateDragSelection(displayIndex); + }} reviewButton={ onReviewNote && ( - +