diff --git a/package-lock.json b/package-lock.json index 91f5affa09..d4e0fa3b57 100644 --- a/package-lock.json +++ b/package-lock.json @@ -62,6 +62,7 @@ "tmp": "^0.2.3", "tree-sitter-wasms": "^0.1.11", "turndown": "^7.2.0", + "vscode-material-icons": "^0.1.1", "web-tree-sitter": "^0.22.6", "zod": "^3.23.8" }, @@ -21570,6 +21571,12 @@ "node": ">= 0.8" } }, + "node_modules/vscode-material-icons": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/vscode-material-icons/-/vscode-material-icons-0.1.1.tgz", + "integrity": "sha512-GsoEEF8Tbb0yUFQ6N6FPvh11kFkL9F95x0FkKlbbfRQN9eFms67h+L3t6b9cUv58dSn2gu8kEhNfoESVCrz4ag==", + "license": "MIT" + }, "node_modules/walker": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", diff --git a/package.json b/package.json index 4804150ffd..150c992528 100644 --- a/package.json +++ b/package.json @@ -454,6 +454,7 @@ "tmp": "^0.2.3", "tree-sitter-wasms": "^0.1.11", "turndown": "^7.2.0", + "vscode-material-icons": "^0.1.1", "web-tree-sitter": "^0.22.6", "zod": "^3.23.8" }, diff --git a/src/core/webview/ClineProvider.ts b/src/core/webview/ClineProvider.ts index f2b4391051..a04b3c47f7 100644 --- a/src/core/webview/ClineProvider.ts +++ b/src/core/webview/ClineProvider.ts @@ -621,6 +621,13 @@ export class ClineProvider extends EventEmitter implements "codicon.css", ]) + const materialIconsUri = getUri(webview, this.contextProxy.extensionUri, [ + "node_modules", + "vscode-material-icons", + "generated", + "icons", + ]) + const imagesUri = getUri(webview, this.contextProxy.extensionUri, ["assets", "images"]) const file = "src/index.tsx" @@ -655,7 +662,8 @@ export class ClineProvider extends EventEmitter implements Roo Code @@ -705,6 +713,14 @@ export class ClineProvider extends EventEmitter implements "codicon.css", ]) + // The material icons from the React build output + const materialIconsUri = getUri(webview, this.contextProxy.extensionUri, [ + "node_modules", + "vscode-material-icons", + "generated", + "icons", + ]) + const imagesUri = getUri(webview, this.contextProxy.extensionUri, ["assets", "images"]) // const scriptUri = webview.asWebviewUri(vscode.Uri.joinPath(this._extensionUri, "assets", "main.js")) @@ -741,6 +757,7 @@ export class ClineProvider extends EventEmitter implements Roo Code diff --git a/webview-ui/src/components/chat/ContextMenu.tsx b/webview-ui/src/components/chat/ContextMenu.tsx index a28eb10059..ef95b35fd7 100644 --- a/webview-ui/src/components/chat/ContextMenu.tsx +++ b/webview-ui/src/components/chat/ContextMenu.tsx @@ -1,4 +1,5 @@ -import React, { useEffect, useMemo, useRef } from "react" +import React, { useEffect, useMemo, useRef, useState } from "react" +import { getIconForFilePath, getIconUrlByName, getIconForDirectoryPath } from "vscode-material-icons" import { ContextMenuOptionType, ContextMenuQueryItem, @@ -35,6 +36,7 @@ const ContextMenu: React.FC = ({ loading = false, dynamicSearchResults = [], }) => { + const [materialIconsBaseUri, setMaterialIconsBaseUri] = useState("") const menuRef = useRef(null) const filteredOptions = useMemo(() => { @@ -57,6 +59,12 @@ const ContextMenu: React.FC = ({ } }, [selectedIndex]) + // get the icons base uri on mount + useEffect(() => { + const w = window as any + setMaterialIconsBaseUri(w.MATERIAL_ICONS_BASE_URI) + }, []) + const renderOptionContent = (option: ContextMenuQueryItem) => { switch (option.type) { case ContextMenuOptionType.Mode: @@ -175,6 +183,15 @@ const ContextMenu: React.FC = ({ } } + const getMaterialIconForOption = (option: ContextMenuQueryItem): string => { + // only take the last part of the path to handle both file and folder icons + // since material-icons have specific folder icons, we use them if available + const name = option.value?.split("/").filter(Boolean).at(-1) ?? "" + const iconName = + option.type === ContextMenuOptionType.Folder ? getIconForDirectoryPath(name) : getIconForFilePath(name) + return getIconUrlByName(iconName, materialIconsBaseUri) + } + const isOptionSelectable = (option: ContextMenuQueryItem): boolean => { return option.type !== ContextMenuOptionType.NoResults && option.type !== ContextMenuOptionType.URL } @@ -231,17 +248,35 @@ const ContextMenu: React.FC = ({ overflow: "hidden", paddingTop: 0, }}> - {option.type !== ContextMenuOptionType.Mode && getIconForOption(option) && ( - )} + {option.type !== ContextMenuOptionType.Mode && + option.type !== ContextMenuOptionType.File && + option.type !== ContextMenuOptionType.Folder && + option.type !== ContextMenuOptionType.OpenedFile && + getIconForOption(option) && ( + + )} {renderOptionContent(option)} {(option.type === ContextMenuOptionType.File || @@ -253,18 +288,6 @@ const ContextMenu: React.FC = ({ style={{ fontSize: "10px", flexShrink: 0, marginLeft: 8 }} /> )} - {(option.type === ContextMenuOptionType.Problems || - option.type === ContextMenuOptionType.Terminal || - ((option.type === ContextMenuOptionType.File || - option.type === ContextMenuOptionType.Folder || - option.type === ContextMenuOptionType.OpenedFile || - option.type === ContextMenuOptionType.Git) && - option.value)) && ( - - )} )) ) : (