diff --git a/crates/assistant/src/assistant_panel.rs b/crates/assistant/src/assistant_panel.rs index 9a2b336a06..0cb202ecc6 100644 --- a/crates/assistant/src/assistant_panel.rs +++ b/crates/assistant/src/assistant_panel.rs @@ -202,9 +202,7 @@ impl AssistantPanel { let slash_command_registry = SlashCommandRegistry::global(cx); - slash_command_registry.register_command(file_command::FileSlashCommand::new( - workspace.project().clone(), - )); + slash_command_registry.register_command(file_command::FileSlashCommand); slash_command_registry.register_command( prompt_command::PromptSlashCommand::new(prompt_library.clone()), ); @@ -4190,12 +4188,10 @@ mod tests { ) .await; - let project = Project::test(fs.clone(), ["/test".as_ref()], cx).await; let prompt_library = Arc::new(PromptLibrary::default()); let slash_command_registry = SlashCommandRegistry::new(); - slash_command_registry - .register_command(file_command::FileSlashCommand::new(project.clone())); + slash_command_registry.register_command(file_command::FileSlashCommand); slash_command_registry.register_command(prompt_command::PromptSlashCommand::new( prompt_library.clone(), )); diff --git a/crates/assistant/src/slash_command.rs b/crates/assistant/src/slash_command.rs index a4f040ff9d..5f975a8964 100644 --- a/crates/assistant/src/slash_command.rs +++ b/crates/assistant/src/slash_command.rs @@ -102,6 +102,7 @@ impl SlashCommandCompletionProvider { label: command.label(cx), server_id: LanguageServerId(0), lsp_completion: Default::default(), + show_new_completions_on_confirm: requires_argument, confirm: (!requires_argument).then(|| { let command_name = mat.string.clone(); let command_range = command_range.clone(); @@ -142,7 +143,12 @@ impl SlashCommandCompletionProvider { *flag = new_cancel_flag.clone(); if let Some(command) = self.commands.command(command_name) { - let completions = command.complete_argument(argument, new_cancel_flag.clone(), cx); + let completions = command.complete_argument( + argument, + new_cancel_flag.clone(), + self.workspace.clone(), + cx, + ); let command_name: Arc = command_name.into(); let editor = self.editor.clone(); let workspace = self.workspace.clone(); @@ -157,6 +163,7 @@ impl SlashCommandCompletionProvider { documentation: None, server_id: LanguageServerId(0), lsp_completion: Default::default(), + show_new_completions_on_confirm: false, confirm: Some(Arc::new({ let command_name = command_name.clone(); let command_range = command_range.clone(); diff --git a/crates/assistant/src/slash_command/active_command.rs b/crates/assistant/src/slash_command/active_command.rs index 76365fe10f..779b39b60f 100644 --- a/crates/assistant/src/slash_command/active_command.rs +++ b/crates/assistant/src/slash_command/active_command.rs @@ -27,6 +27,7 @@ impl SlashCommand for ActiveSlashCommand { &self, _query: String, _cancel: std::sync::Arc, + _workspace: WeakView, _cx: &mut AppContext, ) -> Task>> { Task::ready(Err(anyhow!("this command does not require argument"))) diff --git a/crates/assistant/src/slash_command/file_command.rs b/crates/assistant/src/slash_command/file_command.rs index f55177eb1b..48d0ff71ee 100644 --- a/crates/assistant/src/slash_command/file_command.rs +++ b/crates/assistant/src/slash_command/file_command.rs @@ -1,10 +1,10 @@ use super::{SlashCommand, SlashCommandOutput}; -use anyhow::Result; +use anyhow::{anyhow, Result}; use assistant_slash_command::SlashCommandOutputSection; use fuzzy::PathMatch; -use gpui::{AppContext, Model, RenderOnce, SharedString, Task, WeakView}; +use gpui::{AppContext, RenderOnce, SharedString, Task, View, WeakView}; use language::{LineEnding, LspAdapterDelegate}; -use project::{PathMatchCandidateSet, Project}; +use project::PathMatchCandidateSet; use std::{ ops::Range, path::{Path, PathBuf}, @@ -13,54 +13,70 @@ use std::{ use ui::{prelude::*, ButtonLike, ElevationIndex}; use workspace::Workspace; -pub(crate) struct FileSlashCommand { - project: Model, -} +pub(crate) struct FileSlashCommand; impl FileSlashCommand { - pub fn new(project: Model) -> Self { - Self { project } - } - fn search_paths( &self, query: String, cancellation_flag: Arc, + workspace: &View, cx: &mut AppContext, ) -> Task> { - let worktrees = self - .project - .read(cx) - .visible_worktrees(cx) - .collect::>(); - let candidate_sets = worktrees - .into_iter() - .map(|worktree| { - let worktree = worktree.read(cx); - PathMatchCandidateSet { - snapshot: worktree.snapshot(), - include_ignored: worktree - .root_entry() - .map_or(false, |entry| entry.is_ignored), - include_root_name: true, - directories_only: false, - } - }) - .collect::>(); - - let executor = cx.background_executor().clone(); - cx.foreground_executor().spawn(async move { - fuzzy::match_path_sets( - candidate_sets.as_slice(), - query.as_str(), - None, - false, - 100, - &cancellation_flag, - executor, + if query.is_empty() { + let workspace = workspace.read(cx); + let project = workspace.project().read(cx); + let entries = workspace.recent_navigation_history(Some(10), cx); + let path_prefix: Arc = "".into(); + Task::ready( + entries + .into_iter() + .filter_map(|(entry, _)| { + let worktree = project.worktree_for_id(entry.worktree_id, cx)?; + let mut full_path = PathBuf::from(worktree.read(cx).root_name()); + full_path.push(&entry.path); + Some(PathMatch { + score: 0., + positions: Vec::new(), + worktree_id: entry.worktree_id.to_usize(), + path: full_path.into(), + path_prefix: path_prefix.clone(), + distance_to_relative_ancestor: 0, + }) + }) + .collect(), ) - .await - }) + } else { + let worktrees = workspace.read(cx).visible_worktrees(cx).collect::>(); + let candidate_sets = worktrees + .into_iter() + .map(|worktree| { + let worktree = worktree.read(cx); + PathMatchCandidateSet { + snapshot: worktree.snapshot(), + include_ignored: worktree + .root_entry() + .map_or(false, |entry| entry.is_ignored), + include_root_name: true, + directories_only: false, + } + }) + .collect::>(); + + let executor = cx.background_executor().clone(); + cx.foreground_executor().spawn(async move { + fuzzy::match_path_sets( + candidate_sets.as_slice(), + query.as_str(), + None, + false, + 100, + &cancellation_flag, + executor, + ) + .await + }) + } } } @@ -85,9 +101,14 @@ impl SlashCommand for FileSlashCommand { &self, query: String, cancellation_flag: Arc, + workspace: WeakView, cx: &mut AppContext, - ) -> gpui::Task>> { - let paths = self.search_paths(query, cancellation_flag, cx); + ) -> Task>> { + let Some(workspace) = workspace.upgrade() else { + return Task::ready(Err(anyhow!("workspace was dropped"))); + }; + + let paths = self.search_paths(query, cancellation_flag, &workspace, cx); cx.background_executor().spawn(async move { Ok(paths .await @@ -106,28 +127,34 @@ impl SlashCommand for FileSlashCommand { fn run( self: Arc, argument: Option<&str>, - _workspace: WeakView, + workspace: WeakView, _delegate: Arc, cx: &mut WindowContext, ) -> Task> { - let project = self.project.read(cx); + let Some(workspace) = workspace.upgrade() else { + return Task::ready(Err(anyhow!("workspace was dropped"))); + }; + let Some(argument) = argument else { - return Task::ready(Err(anyhow::anyhow!("missing path"))); + return Task::ready(Err(anyhow!("missing path"))); }; let path = PathBuf::from(argument); - let abs_path = project.worktrees().find_map(|worktree| { - let worktree = worktree.read(cx); - let worktree_root_path = Path::new(worktree.root_name()); - let relative_path = path.strip_prefix(worktree_root_path).ok()?; - worktree.absolutize(&relative_path).ok() - }); + let abs_path = workspace + .read(cx) + .visible_worktrees(cx) + .find_map(|worktree| { + let worktree = worktree.read(cx); + let worktree_root_path = Path::new(worktree.root_name()); + let relative_path = path.strip_prefix(worktree_root_path).ok()?; + worktree.absolutize(&relative_path).ok() + }); let Some(abs_path) = abs_path else { - return Task::ready(Err(anyhow::anyhow!("missing path"))); + return Task::ready(Err(anyhow!("missing path"))); }; - let fs = project.fs().clone(); + let fs = workspace.read(cx).app_state().fs.clone(); let argument = argument.to_string(); let text = cx.background_executor().spawn(async move { let mut content = fs.load(&abs_path).await?; diff --git a/crates/assistant/src/slash_command/project_command.rs b/crates/assistant/src/slash_command/project_command.rs index 0eba1e7793..a9a29c1227 100644 --- a/crates/assistant/src/slash_command/project_command.rs +++ b/crates/assistant/src/slash_command/project_command.rs @@ -105,6 +105,7 @@ impl SlashCommand for ProjectSlashCommand { &self, _query: String, _cancel: Arc, + _workspace: WeakView, _cx: &mut AppContext, ) -> Task>> { Task::ready(Err(anyhow!("this command does not require argument"))) diff --git a/crates/assistant/src/slash_command/prompt_command.rs b/crates/assistant/src/slash_command/prompt_command.rs index eaabf793c8..559fd796d6 100644 --- a/crates/assistant/src/slash_command/prompt_command.rs +++ b/crates/assistant/src/slash_command/prompt_command.rs @@ -40,6 +40,7 @@ impl SlashCommand for PromptSlashCommand { &self, query: String, cancellation_flag: Arc, + _workspace: WeakView, cx: &mut AppContext, ) -> Task>> { let library = self.library.clone(); diff --git a/crates/assistant/src/slash_command/search_command.rs b/crates/assistant/src/slash_command/search_command.rs index 9a4dae66e9..6e2a9f5116 100644 --- a/crates/assistant/src/slash_command/search_command.rs +++ b/crates/assistant/src/slash_command/search_command.rs @@ -47,6 +47,7 @@ impl SlashCommand for SearchSlashCommand { &self, _query: String, _cancel: Arc, + _workspace: WeakView, _cx: &mut AppContext, ) -> Task>> { Task::ready(Ok(Vec::new())) diff --git a/crates/assistant/src/slash_command/tabs_command.rs b/crates/assistant/src/slash_command/tabs_command.rs index af6ef3dc9b..8c480761c2 100644 --- a/crates/assistant/src/slash_command/tabs_command.rs +++ b/crates/assistant/src/slash_command/tabs_command.rs @@ -32,6 +32,7 @@ impl SlashCommand for TabsSlashCommand { &self, _query: String, _cancel: Arc, + _workspace: WeakView, _cx: &mut AppContext, ) -> Task>> { Task::ready(Err(anyhow!("this command does not require argument"))) diff --git a/crates/assistant_slash_command/src/assistant_slash_command.rs b/crates/assistant_slash_command/src/assistant_slash_command.rs index 1692acaf63..5765c9eb37 100644 --- a/crates/assistant_slash_command/src/assistant_slash_command.rs +++ b/crates/assistant_slash_command/src/assistant_slash_command.rs @@ -25,6 +25,7 @@ pub trait SlashCommand: 'static + Send + Sync { &self, query: String, cancel: Arc, + workspace: WeakView, cx: &mut AppContext, ) -> Task>>; fn requires_argument(&self) -> bool; diff --git a/crates/collab_ui/src/chat_panel/message_editor.rs b/crates/collab_ui/src/chat_panel/message_editor.rs index 8b616880db..01074fd90f 100644 --- a/crates/collab_ui/src/chat_panel/message_editor.rs +++ b/crates/collab_ui/src/chat_panel/message_editor.rs @@ -306,6 +306,7 @@ impl MessageEditor { server_id: LanguageServerId(0), // TODO: Make this optional or something? lsp_completion: Default::default(), // TODO: Make this optional or something? confirm: None, + show_new_completions_on_confirm: false, } }) .collect() diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 1e7a3fa281..67f0d0908a 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -4004,6 +4004,10 @@ impl Editor { (confirm)(cx); } + if completion.show_new_completions_on_confirm { + self.show_completions(&ShowCompletions, cx); + } + let provider = self.completion_provider.as_ref()?; let apply_edits = provider.apply_additional_edits_for_completion( buffer_handle, diff --git a/crates/extension/src/extension_slash_command.rs b/crates/extension/src/extension_slash_command.rs index e5de0fe852..ffc81903ae 100644 --- a/crates/extension/src/extension_slash_command.rs +++ b/crates/extension/src/extension_slash_command.rs @@ -39,6 +39,7 @@ impl SlashCommand for ExtensionSlashCommand { &self, _query: String, _cancel: Arc, + _workspace: WeakView, _cx: &mut AppContext, ) -> Task>> { Task::ready(Ok(Vec::new())) diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 32e7b758de..72abe6c635 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -423,6 +423,8 @@ pub struct Completion { pub lsp_completion: lsp::CompletionItem, /// An optional callback to invoke when this completion is confirmed. pub confirm: Option>, + /// If true, the editor will show a new completion menu after this completion is confirmed. + pub show_new_completions_on_confirm: bool, } impl std::fmt::Debug for Completion { @@ -9252,6 +9254,7 @@ impl Project { filter_range: Default::default(), }, confirm: None, + show_new_completions_on_confirm: false, }, false, cx, @@ -10924,6 +10927,7 @@ async fn populate_labels_for_completions( documentation, lsp_completion, confirm: None, + show_new_completions_on_confirm: false, }) } }