diff --git a/ui/src/components/PatchTool.tsx b/ui/src/components/PatchTool.tsx index 724b30fa346b4162575e439cbb4539930958879d..52e5682afa3c623ce1dbca657aa7e81d67bca8b1 100644 --- a/ui/src/components/PatchTool.tsx +++ b/ui/src/components/PatchTool.tsx @@ -85,6 +85,7 @@ function PatchTool({ const editorRef = useRef(null); const monacoRef = useRef(null); const commentInputRef = useRef(null); + const hoverDecorationsRef = useRef([]); // Track viewport size useEffect(() => { @@ -192,7 +193,7 @@ function PatchTool({ minimap: { enabled: false }, scrollBeyondLastLine: false, wordWrap: "on", - glyphMargin: false, + glyphMargin: !isMobile, // Enable glyph margin for comment indicator lineDecorationsWidth: isMobile ? 0 : 10, lineNumbersMinChars: isMobile ? 0 : 3, quickSuggestions: false, @@ -244,6 +245,47 @@ function PatchTool({ } } }); + + // Add hover highlighting with comment indicator + let lastHoveredLine = -1; + modifiedEditor.onMouseMove((e: Monaco.editor.IEditorMouseEvent) => { + const position = e.target.position; + const lineNumber = position?.lineNumber ?? -1; + + if (lineNumber === lastHoveredLine) return; + lastHoveredLine = lineNumber; + + if (lineNumber > 0) { + hoverDecorationsRef.current = modifiedEditor.deltaDecorations( + hoverDecorationsRef.current, + [ + { + range: new monaco.Range(lineNumber, 1, lineNumber, 1), + options: { + isWholeLine: true, + className: "patch-line-hover", + glyphMarginClassName: "patch-comment-glyph", + }, + }, + ], + ); + } else { + // Clear decorations when not hovering a line + hoverDecorationsRef.current = modifiedEditor.deltaDecorations( + hoverDecorationsRef.current, + [], + ); + } + }); + + // Clear decorations when mouse leaves editor + modifiedEditor.onMouseLeave(() => { + lastHoveredLine = -1; + hoverDecorationsRef.current = modifiedEditor.deltaDecorations( + hoverDecorationsRef.current, + [], + ); + }); } // Cleanup function diff --git a/ui/src/styles.css b/ui/src/styles.css index 102f0785dc084fc995e58eb5d923b89a17294a55..438a30a46a7e14f9f9b47ae27e482e06bf18932c 100644 --- a/ui/src/styles.css +++ b/ui/src/styles.css @@ -1238,6 +1238,38 @@ button { overflow: hidden; } +/* Monaco line hover highlighting (via decoration API) */ +.patch-tool-monaco-editor .monaco-editor .patch-line-hover { + background-color: rgba(37, 99, 235, 0.08) !important; +} + +.dark .patch-tool-monaco-editor .monaco-editor .patch-line-hover { + background-color: rgba(96, 165, 250, 0.12) !important; +} + +/* Comment indicator glyph in margin */ +.patch-tool-monaco-editor .monaco-editor .patch-comment-glyph { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='14' height='14' viewBox='0 0 24 24' fill='none' stroke='%236b7280' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z'%3E%3C/path%3E%3Cline x1='9' y1='10' x2='15' y2='10'%3E%3C/line%3E%3C/svg%3E"); + background-size: 14px 14px; + background-repeat: no-repeat; + background-position: center; + opacity: 0.6; + cursor: pointer; +} + +.patch-tool-monaco-editor .monaco-editor .patch-comment-glyph:hover { + opacity: 1; +} + +.dark .patch-tool-monaco-editor .monaco-editor .patch-comment-glyph { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='14' height='14' viewBox='0 0 24 24' fill='none' stroke='%239ca3af' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z'%3E%3C/path%3E%3Cline x1='9' y1='10' x2='15' y2='10'%3E%3C/line%3E%3C/svg%3E"); +} + +/* Make Monaco lines clickable with pointer cursor */ +.patch-tool-monaco-editor .monaco-editor .view-lines { + cursor: pointer; +} + /* Patch Tool Comment Dialog */ .patch-tool-comment-dialog { position: fixed;