From 66affa969a76a86a48a63f24e29e4ac85dcb6561 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 29 May 2024 17:46:18 +0200 Subject: [PATCH] Show recently-opened files when autocompleting /file without arguments (#12434) image Release Notes: - N/A --- crates/assistant/src/assistant_panel.rs | 8 +- crates/assistant/src/slash_command.rs | 9 +- .../src/slash_command/active_command.rs | 1 + .../src/slash_command/file_command.rs | 137 +++++++++++------- .../src/slash_command/project_command.rs | 1 + .../src/slash_command/prompt_command.rs | 1 + .../src/slash_command/search_command.rs | 1 + .../src/slash_command/tabs_command.rs | 1 + .../src/assistant_slash_command.rs | 1 + .../src/chat_panel/message_editor.rs | 1 + crates/editor/src/editor.rs | 4 + .../extension/src/extension_slash_command.rs | 1 + crates/project/src/project.rs | 4 + 13 files changed, 108 insertions(+), 62 deletions(-) diff --git a/crates/assistant/src/assistant_panel.rs b/crates/assistant/src/assistant_panel.rs index 9a2b336a068ae4f53d2219a838cc90df8a907bb1..0cb202ecc6e48f143f533118355bb1003312c9a7 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 a4f040ff9dc1e4ce8d5f5cf10645e47568b848b4..5f975a8964089d1127c14990d904e5be145c6cb3 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 76365fe10fd813b6a7c2855a47d906a0e4ddf4ce..779b39b60fe4184d5c3174e86016b42e4f4657e8 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 f55177eb1b8cabcdfd54877e612c393bc08f1a62..48d0ff71ee6715078a276eee29f0c247da905472 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 0eba1e7793f4ba8dcd385e668b7dfbecd1f28a08..a9a29c122733b5a5a4dfb46afcdc3b9bff659e29 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 eaabf793c857bffcc9ffa1a7e03af5f79a1323d3..559fd796d66f5742f09095addf201dd47ecc3d54 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 9a4dae66e915ae8e17c93e845334df16e645f589..6e2a9f511669448b38a7cbb1df9518cef310fcbc 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 af6ef3dc9b90a2282bb8e94348a3b247a9f6385b..8c480761c28f885c00d8e383d27f48c994969a6f 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 1692acaf63309eed12c5c564be4f94426298d654..5765c9eb37343eda1e01aa50db7678ace5ac8a4d 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 8b616880dba23a6f5ed7eb82e5214373757fe5d6..01074fd90faea0e7c89d5b315a019f3cd819d417 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 1e7a3fa2814ddc5374e1f29b03c5ea77336a1633..67f0d0908ad65e5300b265939e191a7dd28da875 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 e5de0fe852db1e1efa91760409588f182cbed31c..ffc81903ae686e24588086bdb6542d4172b48337 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 32e7b758de449055a0e937a60ce338e8f4f014b6..72abe6c6356260966a27a928511d198ae54d0c34 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, }) } }