diff --git a/webview-ui/src/components/ChatTextArea.tsx b/webview-ui/src/components/ChatTextArea.tsx index 7d295eb..b27250d 100644 --- a/webview-ui/src/components/ChatTextArea.tsx +++ b/webview-ui/src/components/ChatTextArea.tsx @@ -87,6 +87,9 @@ const ChatTextArea = forwardRef( } else if (type === "file" || type === "folder") { // For files and folders, we insert the path insertValue = value + } else if (type === "problems") { + // For workspace problems, we insert @problems + insertValue = "problems" } const newValue = insertMention(textAreaRef.current.value, cursorPosition, insertValue) @@ -105,6 +108,12 @@ const ChatTextArea = forwardRef( const handleKeyDown = useCallback( (event: React.KeyboardEvent) => { if (showContextMenu) { + if (event.key === "Escape") { + // event.preventDefault() + setShowContextMenu(false) + return + } + if (event.key === "ArrowUp" || event.key === "ArrowDown") { event.preventDefault() setSelectedMenuIndex((prevIndex) => { @@ -159,7 +168,7 @@ const ChatTextArea = forwardRef( charAfterCursor === " " || charAfterCursor === "\n" || charAfterCursor === "\r\n" if ( charBeforeIsWhitespace && - inputValue.slice(0, cursorPosition - 1).match(/@(\/|\w+:\/\/)[^\s]+$/) + inputValue.slice(0, cursorPosition - 1).match(/@((?:\/|\w+:\/\/)[^\s]+|problems)$/) ) { const newCursorPosition = cursorPosition - 1 if (!charAfterIsWhitespace) { @@ -220,7 +229,7 @@ const ChatTextArea = forwardRef( if (query.length > 0) { setSelectedMenuIndex(0) } else { - setSelectedMenuIndex(2) // Set to "File" option by default + setSelectedMenuIndex(3) // Set to "File" option by default } } else { setSearchQuery("") @@ -230,6 +239,12 @@ const ChatTextArea = forwardRef( [setInputValue] ) + useEffect(() => { + if (!showContextMenu) { + setSelectedType(null) + } + }, [showContextMenu]) + const handleBlur = useCallback(() => { // Only hide the context menu if the user didn't click on it if (!isMouseDownOnMenu) { @@ -299,7 +314,7 @@ const ChatTextArea = forwardRef( if (!textAreaRef.current || !highlightLayerRef.current) return const text = textAreaRef.current.value - const mentionRegex = /@(\/|\w+:\/\/)[^\s]+/g + const mentionRegex = /@((?:\/|\w+:\/\/)[^\s]+|problems\b)/g highlightLayerRef.current.innerHTML = text .replace(/\n$/, "\n\n") diff --git a/webview-ui/src/components/ContextMenu.tsx b/webview-ui/src/components/ContextMenu.tsx index 9a3c5d1..e2f7766 100644 --- a/webview-ui/src/components/ContextMenu.tsx +++ b/webview-ui/src/components/ContextMenu.tsx @@ -88,6 +88,8 @@ const ContextMenu: React.FC = ({ ? "Add file" : option.value === "Folder" ? "Add folder" + : option.value === "Problems" + ? "Workspace Problems" : option.value === "URL" ? "Paste URL to scrape" : option.value} @@ -95,11 +97,12 @@ const ContextMenu: React.FC = ({ {(option.value === "File" || option.value === "Folder") && ( )} - {(option.type === "file" || option.type === "folder") && - option.value !== "File" && - option.value !== "Folder" && ( - - )} + {(option.type === "problems" || + ((option.type === "file" || option.type === "folder") && + option.value !== "File" && + option.value !== "Folder")) && ( + + )} ))} diff --git a/webview-ui/src/utils/mention-context.ts b/webview-ui/src/utils/mention-context.ts index b882303..00eee2b 100644 --- a/webview-ui/src/utils/mention-context.ts +++ b/webview-ui/src/utils/mention-context.ts @@ -1,4 +1,5 @@ export const mockPaths = [ + { type: "problems", path: "Problems" }, { type: "file", path: "/src/components/Header.tsx" }, { type: "file", path: "/src/components/Footer.tsx" }, { type: "file", path: "/src/utils/helpers.ts" }, @@ -29,7 +30,7 @@ export function insertMention(text: string, position: number, value: string): st } export function removeMention(text: string, position: number): { newText: string; newPosition: number } { - const mentionRegex = /@(\/|\w+:\/\/)[^\s]+/ + const mentionRegex = /@((?:\/|\w+:\/\/)[^\s]+|problems\b)/ const beforeCursor = text.slice(0, position) const afterCursor = text.slice(position) @@ -73,6 +74,11 @@ export function getContextMenuOptions( if (query === "") { return [ { type: "url", value: "URL", icon: "link" }, + { + type: "problems", + value: "Problems", + icon: "warning", + }, { type: "folder", value: "Folder", icon: "folder" }, { type: "file", value: "File", icon: "file" }, ] @@ -93,12 +99,17 @@ export function getContextMenuOptions( return matchingPaths.map((item) => ({ type: item.type, value: item.path, - icon: item.type === "file" ? "file" : "folder", + icon: item.type === "file" ? "file" : item.type === "problems" ? "warning" : "folder", })) } else { // If no matches, show all options return [ { type: "url", value: "URL", icon: "link" }, + { + type: "problems", + value: "Problems", + icon: "warning", + }, { type: "folder", value: "Folder", icon: "folder" }, { type: "file", value: "File", icon: "file" }, ] @@ -120,6 +131,9 @@ export function shouldShowContextMenu(text: string, position: number): boolean { // Don't show the menu if it's a URL if (textAfterAt.toLowerCase().startsWith("http")) return false + // Don't show the menu if it's a problems + if (textAfterAt.toLowerCase().startsWith("problems")) return false + // Show the menu if there's just '@' or '@' followed by some text (but not a URL) return true }