diff --git a/crates/agent/src/active_thread.rs b/crates/agent/src/active_thread.rs index cda2e44cae..6760fb6e5d 100644 --- a/crates/agent/src/active_thread.rs +++ b/crates/agent/src/active_thread.rs @@ -858,6 +858,10 @@ impl ActiveThread { .map(|(id, state)| (*id, state.last_estimated_token_count.unwrap_or(0))) } + pub fn context_store(&self) -> &Entity { + &self.context_store + } + pub fn thread_store(&self) -> &Entity { &self.thread_store } diff --git a/crates/agent/src/assistant_panel.rs b/crates/agent/src/assistant_panel.rs index 76d75ccad4..4fa351ef42 100644 --- a/crates/agent/src/assistant_panel.rs +++ b/crates/agent/src/assistant_panel.rs @@ -22,18 +22,18 @@ use editor::{Anchor, AnchorRangeExt as _, Editor, EditorEvent, MultiBuffer}; use fs::Fs; use gpui::{ Action, Animation, AnimationExt as _, AnyElement, App, AsyncWindowContext, ClipboardItem, - Corner, DismissEvent, Entity, EventEmitter, FocusHandle, Focusable, FontWeight, KeyContext, - Pixels, Subscription, Task, UpdateGlobal, WeakEntity, linear_color_stop, linear_gradient, - prelude::*, pulsating_between, + Corner, DismissEvent, Entity, EventEmitter, ExternalPaths, FocusHandle, Focusable, FontWeight, + KeyContext, Pixels, Subscription, Task, UpdateGlobal, WeakEntity, linear_color_stop, + linear_gradient, prelude::*, pulsating_between, }; use language::LanguageRegistry; use language_model::{LanguageModelProviderTosView, LanguageModelRegistry, RequestUsage}; use language_model_selector::ToggleModelSelector; -use project::Project; +use project::{Project, ProjectPath, Worktree}; use prompt_store::{PromptBuilder, PromptStore, UserPromptId}; use proto::Plan; use rules_library::{RulesLibrary, open_rules_library}; -use search::{BufferSearchBar, buffer_search::DivRegistrar}; +use search::{BufferSearchBar, buffer_search}; use settings::{Settings, update_settings_file}; use theme::ThemeSettings; use time::UtcOffset; @@ -43,7 +43,7 @@ use ui::{ }; use util::{ResultExt as _, maybe}; use workspace::dock::{DockPosition, Panel, PanelEvent}; -use workspace::{CollaboratorId, ToolbarItemView, Workspace}; +use workspace::{CollaboratorId, DraggedSelection, DraggedTab, ToolbarItemView, Workspace}; use zed_actions::agent::OpenConfiguration; use zed_actions::assistant::{OpenRulesLibrary, ToggleFocus}; use zed_actions::{DecreaseBufferFontSize, IncreaseBufferFontSize, ResetBufferFontSize}; @@ -2570,6 +2570,108 @@ impl AssistantPanel { .into_any() } + fn render_drag_target(&self, cx: &Context) -> Div { + let is_local = self.project.read(cx).is_local(); + div() + .invisible() + .absolute() + .top_0() + .right_0() + .bottom_0() + .left_0() + .bg(cx.theme().colors().drop_target_background) + .drag_over::(|this, _, _, _| this.visible()) + .drag_over::(|this, _, _, _| this.visible()) + .when(is_local, |this| { + this.drag_over::(|this, _, _, _| this.visible()) + }) + .on_drop(cx.listener(move |this, tab: &DraggedTab, window, cx| { + let item = tab.pane.read(cx).item_for_index(tab.ix); + let project_paths = item + .and_then(|item| item.project_path(cx)) + .into_iter() + .collect::>(); + this.handle_drop(project_paths, vec![], window, cx); + })) + .on_drop( + cx.listener(move |this, selection: &DraggedSelection, window, cx| { + let project_paths = selection + .items() + .filter_map(|item| this.project.read(cx).path_for_entry(item.entry_id, cx)) + .collect::>(); + this.handle_drop(project_paths, vec![], window, cx); + }), + ) + .on_drop(cx.listener(move |this, paths: &ExternalPaths, window, cx| { + let tasks = paths + .paths() + .into_iter() + .map(|path| { + Workspace::project_path_for_path(this.project.clone(), &path, false, cx) + }) + .collect::>(); + cx.spawn_in(window, async move |this, cx| { + let mut paths = vec![]; + let mut added_worktrees = vec![]; + let opened_paths = futures::future::join_all(tasks).await; + for entry in opened_paths { + if let Some((worktree, project_path)) = entry.log_err() { + added_worktrees.push(worktree); + paths.push(project_path); + } + } + this.update_in(cx, |this, window, cx| { + this.handle_drop(paths, added_worktrees, window, cx); + }) + .ok(); + }) + .detach(); + })) + } + + fn handle_drop( + &mut self, + paths: Vec, + added_worktrees: Vec>, + window: &mut Window, + cx: &mut Context, + ) { + match &self.active_view { + ActiveView::Thread { .. } => { + let context_store = self.thread.read(cx).context_store().clone(); + context_store.update(cx, move |context_store, cx| { + let mut tasks = Vec::new(); + for project_path in &paths { + tasks.push(context_store.add_file_from_path( + project_path.clone(), + false, + cx, + )); + } + cx.background_spawn(async move { + futures::future::join_all(tasks).await; + // Need to hold onto the worktrees until they have already been used when + // opening the buffers. + drop(added_worktrees); + }) + .detach(); + }); + } + ActiveView::PromptEditor { context_editor, .. } => { + context_editor.update(cx, |context_editor, cx| { + ContextEditor::insert_dragged_files( + context_editor, + paths, + added_worktrees, + window, + cx, + ); + }); + } + ActiveView::History | ActiveView::Configuration => {} + } + } + fn create_copy_button(&self, message: impl Into) -> impl IntoElement { let message = message.into(); IconButton::new("copy", IconName::Copy) @@ -2617,18 +2719,24 @@ impl Render for AssistantPanel { .child(self.render_toolbar(window, cx)) .children(self.render_trial_upsell(window, cx)) .map(|parent| match &self.active_view { - ActiveView::Thread { .. } => parent - .child(self.render_active_thread_or_empty_state(window, cx)) - .children(self.render_tool_use_limit_reached(cx)) - .child(h_flex().child(self.message_editor.clone())) - .children(self.render_last_error(cx)), + ActiveView::Thread { .. } => parent.child( + v_flex() + .relative() + .justify_between() + .size_full() + .child(self.render_active_thread_or_empty_state(window, cx)) + .children(self.render_tool_use_limit_reached(cx)) + .child(h_flex().child(self.message_editor.clone())) + .children(self.render_last_error(cx)) + .child(self.render_drag_target(cx)), + ), ActiveView::History => parent.child(self.history.clone()), ActiveView::PromptEditor { context_editor, buffer_search_bar, .. } => { - let mut registrar = DivRegistrar::new( + let mut registrar = buffer_search::DivRegistrar::new( |this, _, _cx| match &this.active_view { ActiveView::PromptEditor { buffer_search_bar, .. @@ -2642,6 +2750,7 @@ impl Render for AssistantPanel { registrar .into_div() .size_full() + .relative() .map(|parent| { buffer_search_bar.update(cx, |buffer_search_bar, cx| { if buffer_search_bar.is_dismissed() { @@ -2657,7 +2766,8 @@ impl Render for AssistantPanel { ) }) }) - .child(context_editor.clone()), + .child(context_editor.clone()) + .child(self.render_drag_target(cx)), ) } ActiveView::Configuration => parent.children(self.configuration.clone()), diff --git a/crates/assistant/src/assistant_panel.rs b/crates/assistant/src/assistant_panel.rs index 6ed5edc902..f205832f97 100644 --- a/crates/assistant/src/assistant_panel.rs +++ b/crates/assistant/src/assistant_panel.rs @@ -34,7 +34,7 @@ use smol::stream::StreamExt; use std::ops::Range; use std::path::Path; -use std::{ops::ControlFlow, path::PathBuf, sync::Arc}; +use std::{ops::ControlFlow, sync::Arc}; use terminal_view::{TerminalView, terminal_panel::TerminalPanel}; use ui::{ContextMenu, PopoverMenu, Tooltip, prelude::*}; use util::{ResultExt, maybe}; @@ -54,7 +54,7 @@ pub fn init(cx: &mut App) { .register_action(ContextEditor::quote_selection) .register_action(ContextEditor::insert_selection) .register_action(ContextEditor::copy_code) - .register_action(ContextEditor::insert_dragged_files) + .register_action(ContextEditor::handle_insert_dragged_files) .register_action(AssistantPanel::show_configuration) .register_action(AssistantPanel::create_new_context) .register_action(AssistantPanel::restart_context_servers) @@ -182,20 +182,7 @@ impl AssistantPanel { None }?; - let paths = project_paths - .into_iter() - .filter_map(|project_path| { - let worktree = project - .read(cx) - .worktree_for_id(project_path.worktree_id, cx)?; - - let mut full_path = PathBuf::from(worktree.read(cx).root_name()); - full_path.push(&project_path.path); - Some(full_path) - }) - .collect::>(); - - Some(InsertDraggedFiles::ProjectPaths(paths)) + Some(InsertDraggedFiles::ProjectPaths(project_paths)) }); if let Some(action) = action { diff --git a/crates/assistant_context_editor/src/context_editor.rs b/crates/assistant_context_editor/src/context_editor.rs index 894da1b00b..7772d2cc49 100644 --- a/crates/assistant_context_editor/src/context_editor.rs +++ b/crates/assistant_context_editor/src/context_editor.rs @@ -43,8 +43,8 @@ use language_model_selector::{ }; use multi_buffer::MultiBufferRow; use picker::Picker; -use project::lsp_store::LocalLspAdapterDelegate; use project::{Project, Worktree}; +use project::{ProjectPath, lsp_store::LocalLspAdapterDelegate}; use rope::Point; use serde::{Deserialize, Serialize}; use settings::{Settings, SettingsStore, update_settings_file}; @@ -100,7 +100,7 @@ actions!( #[derive(PartialEq, Clone)] pub enum InsertDraggedFiles { - ProjectPaths(Vec), + ProjectPaths(Vec), ExternalFiles(Vec), } @@ -1725,7 +1725,7 @@ impl ContextEditor { ); } - pub fn insert_dragged_files( + pub fn handle_insert_dragged_files( workspace: &mut Workspace, action: &InsertDraggedFiles, window: &mut Window, @@ -1740,7 +1740,7 @@ impl ContextEditor { return; }; - let project = workspace.project().clone(); + let project = context_editor_view.read(cx).project.clone(); let paths = match action { InsertDraggedFiles::ProjectPaths(paths) => Task::ready((paths.clone(), vec![])), @@ -1751,22 +1751,17 @@ impl ContextEditor { .map(|path| Workspace::project_path_for_path(project.clone(), &path, false, cx)) .collect::>(); - cx.spawn(async move |_, cx| { + cx.background_spawn(async move { let mut paths = vec![]; let mut worktrees = vec![]; let opened_paths = futures::future::join_all(tasks).await; - for (worktree, project_path) in opened_paths.into_iter().flatten() { - let Ok(worktree_root_name) = - worktree.read_with(cx, |worktree, _| worktree.root_name().to_string()) - else { - continue; - }; - let mut full_path = PathBuf::from(worktree_root_name.clone()); - full_path.push(&project_path.path); - paths.push(full_path); - worktrees.push(worktree); + for entry in opened_paths { + if let Some((worktree, project_path)) = entry.log_err() { + worktrees.push(worktree); + paths.push(project_path); + } } (paths, worktrees) @@ -1774,33 +1769,50 @@ impl ContextEditor { } }; - window - .spawn(cx, async move |cx| { + context_editor_view.update(cx, |_, cx| { + cx.spawn_in(window, async move |this, cx| { let (paths, dragged_file_worktrees) = paths.await; - let cmd_name = FileSlashCommand.name(); - - context_editor_view - .update_in(cx, |context_editor, window, cx| { - let file_argument = paths - .into_iter() - .map(|path| path.to_string_lossy().to_string()) - .collect::>() - .join(" "); - - context_editor.editor.update(cx, |editor, cx| { - editor.insert("\n", window, cx); - editor.insert(&format!("/{} {}", cmd_name, file_argument), window, cx); - }); - - context_editor.confirm_command(&ConfirmCommand, window, cx); - - context_editor - .dragged_file_worktrees - .extend(dragged_file_worktrees); - }) - .log_err(); + this.update_in(cx, |this, window, cx| { + this.insert_dragged_files(paths, dragged_file_worktrees, window, cx); + }) + .ok(); }) .detach(); + }) + } + + pub fn insert_dragged_files( + &mut self, + opened_paths: Vec, + added_worktrees: Vec>, + window: &mut Window, + cx: &mut Context, + ) { + let mut file_slash_command_args = vec![]; + for project_path in opened_paths.into_iter() { + let Some(worktree) = self + .project + .read(cx) + .worktree_for_id(project_path.worktree_id, cx) + else { + continue; + }; + let worktree_root_name = worktree.read(cx).root_name().to_string(); + let mut full_path = PathBuf::from(worktree_root_name.clone()); + full_path.push(&project_path.path); + file_slash_command_args.push(full_path.to_string_lossy().to_string()); + } + + let cmd_name = FileSlashCommand.name(); + + let file_argument = file_slash_command_args.join(" "); + + self.editor.update(cx, |editor, cx| { + editor.insert("\n", window, cx); + editor.insert(&format!("/{} {}", cmd_name, file_argument), window, cx); + }); + self.confirm_command(&ConfirmCommand, window, cx); + self.dragged_file_worktrees.extend(added_worktrees); } pub fn quote_selection(