Detailed changes
@@ -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(),
));
@@ -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<str> = 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();
@@ -27,6 +27,7 @@ impl SlashCommand for ActiveSlashCommand {
&self,
_query: String,
_cancel: std::sync::Arc<std::sync::atomic::AtomicBool>,
+ _workspace: WeakView<Workspace>,
_cx: &mut AppContext,
) -> Task<Result<Vec<String>>> {
Task::ready(Err(anyhow!("this command does not require argument")))
@@ -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<Project>,
-}
+pub(crate) struct FileSlashCommand;
impl FileSlashCommand {
- pub fn new(project: Model<Project>) -> Self {
- Self { project }
- }
-
fn search_paths(
&self,
query: String,
cancellation_flag: Arc<AtomicBool>,
+ workspace: &View<Workspace>,
cx: &mut AppContext,
) -> Task<Vec<PathMatch>> {
- let worktrees = self
- .project
- .read(cx)
- .visible_worktrees(cx)
- .collect::<Vec<_>>();
- 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::<Vec<_>>();
-
- 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<str> = "".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::<Vec<_>>();
+ 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::<Vec<_>>();
+
+ 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<AtomicBool>,
+ workspace: WeakView<Workspace>,
cx: &mut AppContext,
- ) -> gpui::Task<Result<Vec<String>>> {
- let paths = self.search_paths(query, cancellation_flag, cx);
+ ) -> Task<Result<Vec<String>>> {
+ 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<Self>,
argument: Option<&str>,
- _workspace: WeakView<Workspace>,
+ workspace: WeakView<Workspace>,
_delegate: Arc<dyn LspAdapterDelegate>,
cx: &mut WindowContext,
) -> Task<Result<SlashCommandOutput>> {
- 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?;
@@ -105,6 +105,7 @@ impl SlashCommand for ProjectSlashCommand {
&self,
_query: String,
_cancel: Arc<AtomicBool>,
+ _workspace: WeakView<Workspace>,
_cx: &mut AppContext,
) -> Task<Result<Vec<String>>> {
Task::ready(Err(anyhow!("this command does not require argument")))
@@ -40,6 +40,7 @@ impl SlashCommand for PromptSlashCommand {
&self,
query: String,
cancellation_flag: Arc<AtomicBool>,
+ _workspace: WeakView<Workspace>,
cx: &mut AppContext,
) -> Task<Result<Vec<String>>> {
let library = self.library.clone();
@@ -47,6 +47,7 @@ impl SlashCommand for SearchSlashCommand {
&self,
_query: String,
_cancel: Arc<AtomicBool>,
+ _workspace: WeakView<Workspace>,
_cx: &mut AppContext,
) -> Task<Result<Vec<String>>> {
Task::ready(Ok(Vec::new()))
@@ -32,6 +32,7 @@ impl SlashCommand for TabsSlashCommand {
&self,
_query: String,
_cancel: Arc<std::sync::atomic::AtomicBool>,
+ _workspace: WeakView<Workspace>,
_cx: &mut AppContext,
) -> Task<Result<Vec<String>>> {
Task::ready(Err(anyhow!("this command does not require argument")))
@@ -25,6 +25,7 @@ pub trait SlashCommand: 'static + Send + Sync {
&self,
query: String,
cancel: Arc<AtomicBool>,
+ workspace: WeakView<Workspace>,
cx: &mut AppContext,
) -> Task<Result<Vec<String>>>;
fn requires_argument(&self) -> bool;
@@ -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()
@@ -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,
@@ -39,6 +39,7 @@ impl SlashCommand for ExtensionSlashCommand {
&self,
_query: String,
_cancel: Arc<AtomicBool>,
+ _workspace: WeakView<Workspace>,
_cx: &mut AppContext,
) -> Task<Result<Vec<String>>> {
Task::ready(Ok(Vec::new()))
@@ -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<Arc<dyn Send + Sync + Fn(&mut WindowContext)>>,
+ /// 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,
})
}
}