diff --git a/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarChat.tsx b/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarChat.tsx index 97add942891..363db8f2a43 100644 --- a/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarChat.tsx +++ b/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarChat.tsx @@ -35,6 +35,7 @@ import { ToolApprovalTypeSwitch } from '../void-settings-tsx/Settings.js'; import { persistentTerminalNameOfId } from '../../../terminalToolService.js'; import { removeMCPToolNamePrefix } from '../../../../common/mcpServiceTypes.js'; +import { CodeDataTransfers } from '../../../../../../../platform/dnd/browser/dnd.js' @@ -285,6 +286,87 @@ const ChatModeDropdown = ({ className }: { className: string }) => { } +const DragAndDropWrapper: React.FC<{ + children: React.ReactNode; + }> = ({ children}) => { + const accessor = useAccessor(); + const chatThreadService = accessor.get('IChatThreadService'); + const modelReferenceService = accessor.get('IVoidModelService'); + const fileService = accessor.get('IFileService'); + const [isDragOver, setIsDragOver] = useState(false); + + const handleDrop = async (event: React.DragEvent) => { + event.preventDefault(); + setIsDragOver(false); + // Extract file paths from CodeDataTransfers.FILES (file explorer drags) + const filesData = event.dataTransfer.getData(CodeDataTransfers.FILES); + const filePathsFromFiles = filesData ? JSON.parse(filesData) : []; + + // Extract file paths from CodeDataTransfers.EDITORS (open editor drags) + const editorData = event.dataTransfer.getData(CodeDataTransfers.EDITORS); + const filePathsFromEditors = editorData + ? JSON.parse(editorData).map((editor:any) => editor.resource.fsPath) + : []; + const allFilePaths = [...new Set([...filePathsFromFiles, ...filePathsFromEditors])]; + if (allFilePaths.length > 0) { + try { + if (Array.isArray(allFilePaths)) { + for (const path of allFilePaths) { + if(!path) continue; + const uri = URI.file(path); + try { + const stat = await fileService.stat(uri); + if (stat.isDirectory) { + // Folder case: no language or state + chatThreadService.addNewStagingSelection({ + type: 'Folder', + uri, + }); + } else { + // File case: include language and state + const model = await modelReferenceService.getModelSafe(uri); + const language = model.model?.getLanguageId() || 'plaintext'; + chatThreadService.addNewStagingSelection({ + type: 'File', + uri, + language, + state: { wasAddedAsCurrentFile: false }, + }); + } + } catch (error) { + console.error(`Failed to process dropped item: ${path}`, error); + } + } + } + } catch (error) { + console.error('Failed to parse dropped files data', error); + } + } + }; + + return ( +
{ + event.stopPropagation(); + event.preventDefault(); + if(event.dataTransfer.effectAllowed === 'copyMove') { + setIsDragOver(true); + } + }} + onDragLeave={() => setIsDragOver(false)} + onDrop={handleDrop} + className={`pointer-events-auto relative`} + > + {isDragOver && ( +
+ )} + {children} +
+ ); + }; + @@ -337,7 +419,8 @@ export const VoidChatArea: React.FC = ({ featureName, loadingIcon, }) => { - return ( + + const content = (
= ({
); + return setSelections && showSelections ? ( + + {content} + + ) : ( + content + ); + };