Show recently-opened files when autocompleting /file without arguments (#12434)

<img width="1588" alt="image"
src="https://github.com/zed-industries/zed/assets/482957/ea63b046-64d6-419e-8135-4863748b58fa">


Release Notes:

- N/A
This commit is contained in:
Antonio Scandurra 2024-05-29 17:46:18 +02:00 committed by GitHub
parent dca5bc5986
commit 66affa969a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 108 additions and 62 deletions

View File

@ -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(),
));

View File

@ -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();

View File

@ -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")))

View File

@ -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?;

View File

@ -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")))

View File

@ -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();

View File

@ -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()))

View File

@ -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")))

View File

@ -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;

View File

@ -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()

View File

@ -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,

View File

@ -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()))

View File

@ -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,
})
}
}