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:
parent
dca5bc5986
commit
66affa969a
13 changed files with 108 additions and 62 deletions
|
@ -202,9 +202,7 @@ impl AssistantPanel {
|
||||||
|
|
||||||
let slash_command_registry = SlashCommandRegistry::global(cx);
|
let slash_command_registry = SlashCommandRegistry::global(cx);
|
||||||
|
|
||||||
slash_command_registry.register_command(file_command::FileSlashCommand::new(
|
slash_command_registry.register_command(file_command::FileSlashCommand);
|
||||||
workspace.project().clone(),
|
|
||||||
));
|
|
||||||
slash_command_registry.register_command(
|
slash_command_registry.register_command(
|
||||||
prompt_command::PromptSlashCommand::new(prompt_library.clone()),
|
prompt_command::PromptSlashCommand::new(prompt_library.clone()),
|
||||||
);
|
);
|
||||||
|
@ -4190,12 +4188,10 @@ mod tests {
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
let project = Project::test(fs.clone(), ["/test".as_ref()], cx).await;
|
|
||||||
let prompt_library = Arc::new(PromptLibrary::default());
|
let prompt_library = Arc::new(PromptLibrary::default());
|
||||||
let slash_command_registry = SlashCommandRegistry::new();
|
let slash_command_registry = SlashCommandRegistry::new();
|
||||||
|
|
||||||
slash_command_registry
|
slash_command_registry.register_command(file_command::FileSlashCommand);
|
||||||
.register_command(file_command::FileSlashCommand::new(project.clone()));
|
|
||||||
slash_command_registry.register_command(prompt_command::PromptSlashCommand::new(
|
slash_command_registry.register_command(prompt_command::PromptSlashCommand::new(
|
||||||
prompt_library.clone(),
|
prompt_library.clone(),
|
||||||
));
|
));
|
||||||
|
|
|
@ -102,6 +102,7 @@ impl SlashCommandCompletionProvider {
|
||||||
label: command.label(cx),
|
label: command.label(cx),
|
||||||
server_id: LanguageServerId(0),
|
server_id: LanguageServerId(0),
|
||||||
lsp_completion: Default::default(),
|
lsp_completion: Default::default(),
|
||||||
|
show_new_completions_on_confirm: requires_argument,
|
||||||
confirm: (!requires_argument).then(|| {
|
confirm: (!requires_argument).then(|| {
|
||||||
let command_name = mat.string.clone();
|
let command_name = mat.string.clone();
|
||||||
let command_range = command_range.clone();
|
let command_range = command_range.clone();
|
||||||
|
@ -142,7 +143,12 @@ impl SlashCommandCompletionProvider {
|
||||||
*flag = new_cancel_flag.clone();
|
*flag = new_cancel_flag.clone();
|
||||||
|
|
||||||
if let Some(command) = self.commands.command(command_name) {
|
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 command_name: Arc<str> = command_name.into();
|
||||||
let editor = self.editor.clone();
|
let editor = self.editor.clone();
|
||||||
let workspace = self.workspace.clone();
|
let workspace = self.workspace.clone();
|
||||||
|
@ -157,6 +163,7 @@ impl SlashCommandCompletionProvider {
|
||||||
documentation: None,
|
documentation: None,
|
||||||
server_id: LanguageServerId(0),
|
server_id: LanguageServerId(0),
|
||||||
lsp_completion: Default::default(),
|
lsp_completion: Default::default(),
|
||||||
|
show_new_completions_on_confirm: false,
|
||||||
confirm: Some(Arc::new({
|
confirm: Some(Arc::new({
|
||||||
let command_name = command_name.clone();
|
let command_name = command_name.clone();
|
||||||
let command_range = command_range.clone();
|
let command_range = command_range.clone();
|
||||||
|
|
|
@ -27,6 +27,7 @@ impl SlashCommand for ActiveSlashCommand {
|
||||||
&self,
|
&self,
|
||||||
_query: String,
|
_query: String,
|
||||||
_cancel: std::sync::Arc<std::sync::atomic::AtomicBool>,
|
_cancel: std::sync::Arc<std::sync::atomic::AtomicBool>,
|
||||||
|
_workspace: WeakView<Workspace>,
|
||||||
_cx: &mut AppContext,
|
_cx: &mut AppContext,
|
||||||
) -> Task<Result<Vec<String>>> {
|
) -> Task<Result<Vec<String>>> {
|
||||||
Task::ready(Err(anyhow!("this command does not require argument")))
|
Task::ready(Err(anyhow!("this command does not require argument")))
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
use super::{SlashCommand, SlashCommandOutput};
|
use super::{SlashCommand, SlashCommandOutput};
|
||||||
use anyhow::Result;
|
use anyhow::{anyhow, Result};
|
||||||
use assistant_slash_command::SlashCommandOutputSection;
|
use assistant_slash_command::SlashCommandOutputSection;
|
||||||
use fuzzy::PathMatch;
|
use fuzzy::PathMatch;
|
||||||
use gpui::{AppContext, Model, RenderOnce, SharedString, Task, WeakView};
|
use gpui::{AppContext, RenderOnce, SharedString, Task, View, WeakView};
|
||||||
use language::{LineEnding, LspAdapterDelegate};
|
use language::{LineEnding, LspAdapterDelegate};
|
||||||
use project::{PathMatchCandidateSet, Project};
|
use project::PathMatchCandidateSet;
|
||||||
use std::{
|
use std::{
|
||||||
ops::Range,
|
ops::Range,
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
|
@ -13,26 +13,41 @@ use std::{
|
||||||
use ui::{prelude::*, ButtonLike, ElevationIndex};
|
use ui::{prelude::*, ButtonLike, ElevationIndex};
|
||||||
use workspace::Workspace;
|
use workspace::Workspace;
|
||||||
|
|
||||||
pub(crate) struct FileSlashCommand {
|
pub(crate) struct FileSlashCommand;
|
||||||
project: Model<Project>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FileSlashCommand {
|
impl FileSlashCommand {
|
||||||
pub fn new(project: Model<Project>) -> Self {
|
|
||||||
Self { project }
|
|
||||||
}
|
|
||||||
|
|
||||||
fn search_paths(
|
fn search_paths(
|
||||||
&self,
|
&self,
|
||||||
query: String,
|
query: String,
|
||||||
cancellation_flag: Arc<AtomicBool>,
|
cancellation_flag: Arc<AtomicBool>,
|
||||||
|
workspace: &View<Workspace>,
|
||||||
cx: &mut AppContext,
|
cx: &mut AppContext,
|
||||||
) -> Task<Vec<PathMatch>> {
|
) -> Task<Vec<PathMatch>> {
|
||||||
let worktrees = self
|
if query.is_empty() {
|
||||||
.project
|
let workspace = workspace.read(cx);
|
||||||
.read(cx)
|
let project = workspace.project().read(cx);
|
||||||
.visible_worktrees(cx)
|
let entries = workspace.recent_navigation_history(Some(10), cx);
|
||||||
.collect::<Vec<_>>();
|
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(),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
let worktrees = workspace.read(cx).visible_worktrees(cx).collect::<Vec<_>>();
|
||||||
let candidate_sets = worktrees
|
let candidate_sets = worktrees
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|worktree| {
|
.map(|worktree| {
|
||||||
|
@ -63,6 +78,7 @@ impl FileSlashCommand {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl SlashCommand for FileSlashCommand {
|
impl SlashCommand for FileSlashCommand {
|
||||||
fn name(&self) -> String {
|
fn name(&self) -> String {
|
||||||
|
@ -85,9 +101,14 @@ impl SlashCommand for FileSlashCommand {
|
||||||
&self,
|
&self,
|
||||||
query: String,
|
query: String,
|
||||||
cancellation_flag: Arc<AtomicBool>,
|
cancellation_flag: Arc<AtomicBool>,
|
||||||
|
workspace: WeakView<Workspace>,
|
||||||
cx: &mut AppContext,
|
cx: &mut AppContext,
|
||||||
) -> gpui::Task<Result<Vec<String>>> {
|
) -> Task<Result<Vec<String>>> {
|
||||||
let paths = self.search_paths(query, cancellation_flag, cx);
|
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 {
|
cx.background_executor().spawn(async move {
|
||||||
Ok(paths
|
Ok(paths
|
||||||
.await
|
.await
|
||||||
|
@ -106,17 +127,23 @@ impl SlashCommand for FileSlashCommand {
|
||||||
fn run(
|
fn run(
|
||||||
self: Arc<Self>,
|
self: Arc<Self>,
|
||||||
argument: Option<&str>,
|
argument: Option<&str>,
|
||||||
_workspace: WeakView<Workspace>,
|
workspace: WeakView<Workspace>,
|
||||||
_delegate: Arc<dyn LspAdapterDelegate>,
|
_delegate: Arc<dyn LspAdapterDelegate>,
|
||||||
cx: &mut WindowContext,
|
cx: &mut WindowContext,
|
||||||
) -> Task<Result<SlashCommandOutput>> {
|
) -> 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 {
|
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 path = PathBuf::from(argument);
|
||||||
let abs_path = project.worktrees().find_map(|worktree| {
|
let abs_path = workspace
|
||||||
|
.read(cx)
|
||||||
|
.visible_worktrees(cx)
|
||||||
|
.find_map(|worktree| {
|
||||||
let worktree = worktree.read(cx);
|
let worktree = worktree.read(cx);
|
||||||
let worktree_root_path = Path::new(worktree.root_name());
|
let worktree_root_path = Path::new(worktree.root_name());
|
||||||
let relative_path = path.strip_prefix(worktree_root_path).ok()?;
|
let relative_path = path.strip_prefix(worktree_root_path).ok()?;
|
||||||
|
@ -124,10 +151,10 @@ impl SlashCommand for FileSlashCommand {
|
||||||
});
|
});
|
||||||
|
|
||||||
let Some(abs_path) = abs_path else {
|
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 argument = argument.to_string();
|
||||||
let text = cx.background_executor().spawn(async move {
|
let text = cx.background_executor().spawn(async move {
|
||||||
let mut content = fs.load(&abs_path).await?;
|
let mut content = fs.load(&abs_path).await?;
|
||||||
|
|
|
@ -105,6 +105,7 @@ impl SlashCommand for ProjectSlashCommand {
|
||||||
&self,
|
&self,
|
||||||
_query: String,
|
_query: String,
|
||||||
_cancel: Arc<AtomicBool>,
|
_cancel: Arc<AtomicBool>,
|
||||||
|
_workspace: WeakView<Workspace>,
|
||||||
_cx: &mut AppContext,
|
_cx: &mut AppContext,
|
||||||
) -> Task<Result<Vec<String>>> {
|
) -> Task<Result<Vec<String>>> {
|
||||||
Task::ready(Err(anyhow!("this command does not require argument")))
|
Task::ready(Err(anyhow!("this command does not require argument")))
|
||||||
|
|
|
@ -40,6 +40,7 @@ impl SlashCommand for PromptSlashCommand {
|
||||||
&self,
|
&self,
|
||||||
query: String,
|
query: String,
|
||||||
cancellation_flag: Arc<AtomicBool>,
|
cancellation_flag: Arc<AtomicBool>,
|
||||||
|
_workspace: WeakView<Workspace>,
|
||||||
cx: &mut AppContext,
|
cx: &mut AppContext,
|
||||||
) -> Task<Result<Vec<String>>> {
|
) -> Task<Result<Vec<String>>> {
|
||||||
let library = self.library.clone();
|
let library = self.library.clone();
|
||||||
|
|
|
@ -47,6 +47,7 @@ impl SlashCommand for SearchSlashCommand {
|
||||||
&self,
|
&self,
|
||||||
_query: String,
|
_query: String,
|
||||||
_cancel: Arc<AtomicBool>,
|
_cancel: Arc<AtomicBool>,
|
||||||
|
_workspace: WeakView<Workspace>,
|
||||||
_cx: &mut AppContext,
|
_cx: &mut AppContext,
|
||||||
) -> Task<Result<Vec<String>>> {
|
) -> Task<Result<Vec<String>>> {
|
||||||
Task::ready(Ok(Vec::new()))
|
Task::ready(Ok(Vec::new()))
|
||||||
|
|
|
@ -32,6 +32,7 @@ impl SlashCommand for TabsSlashCommand {
|
||||||
&self,
|
&self,
|
||||||
_query: String,
|
_query: String,
|
||||||
_cancel: Arc<std::sync::atomic::AtomicBool>,
|
_cancel: Arc<std::sync::atomic::AtomicBool>,
|
||||||
|
_workspace: WeakView<Workspace>,
|
||||||
_cx: &mut AppContext,
|
_cx: &mut AppContext,
|
||||||
) -> Task<Result<Vec<String>>> {
|
) -> Task<Result<Vec<String>>> {
|
||||||
Task::ready(Err(anyhow!("this command does not require argument")))
|
Task::ready(Err(anyhow!("this command does not require argument")))
|
||||||
|
|
|
@ -25,6 +25,7 @@ pub trait SlashCommand: 'static + Send + Sync {
|
||||||
&self,
|
&self,
|
||||||
query: String,
|
query: String,
|
||||||
cancel: Arc<AtomicBool>,
|
cancel: Arc<AtomicBool>,
|
||||||
|
workspace: WeakView<Workspace>,
|
||||||
cx: &mut AppContext,
|
cx: &mut AppContext,
|
||||||
) -> Task<Result<Vec<String>>>;
|
) -> Task<Result<Vec<String>>>;
|
||||||
fn requires_argument(&self) -> bool;
|
fn requires_argument(&self) -> bool;
|
||||||
|
|
|
@ -306,6 +306,7 @@ impl MessageEditor {
|
||||||
server_id: LanguageServerId(0), // TODO: Make this optional or something?
|
server_id: LanguageServerId(0), // TODO: Make this optional or something?
|
||||||
lsp_completion: Default::default(), // TODO: Make this optional or something?
|
lsp_completion: Default::default(), // TODO: Make this optional or something?
|
||||||
confirm: None,
|
confirm: None,
|
||||||
|
show_new_completions_on_confirm: false,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.collect()
|
.collect()
|
||||||
|
|
|
@ -4004,6 +4004,10 @@ impl Editor {
|
||||||
(confirm)(cx);
|
(confirm)(cx);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if completion.show_new_completions_on_confirm {
|
||||||
|
self.show_completions(&ShowCompletions, cx);
|
||||||
|
}
|
||||||
|
|
||||||
let provider = self.completion_provider.as_ref()?;
|
let provider = self.completion_provider.as_ref()?;
|
||||||
let apply_edits = provider.apply_additional_edits_for_completion(
|
let apply_edits = provider.apply_additional_edits_for_completion(
|
||||||
buffer_handle,
|
buffer_handle,
|
||||||
|
|
|
@ -39,6 +39,7 @@ impl SlashCommand for ExtensionSlashCommand {
|
||||||
&self,
|
&self,
|
||||||
_query: String,
|
_query: String,
|
||||||
_cancel: Arc<AtomicBool>,
|
_cancel: Arc<AtomicBool>,
|
||||||
|
_workspace: WeakView<Workspace>,
|
||||||
_cx: &mut AppContext,
|
_cx: &mut AppContext,
|
||||||
) -> Task<Result<Vec<String>>> {
|
) -> Task<Result<Vec<String>>> {
|
||||||
Task::ready(Ok(Vec::new()))
|
Task::ready(Ok(Vec::new()))
|
||||||
|
|
|
@ -423,6 +423,8 @@ pub struct Completion {
|
||||||
pub lsp_completion: lsp::CompletionItem,
|
pub lsp_completion: lsp::CompletionItem,
|
||||||
/// An optional callback to invoke when this completion is confirmed.
|
/// An optional callback to invoke when this completion is confirmed.
|
||||||
pub confirm: Option<Arc<dyn Send + Sync + Fn(&mut WindowContext)>>,
|
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 {
|
impl std::fmt::Debug for Completion {
|
||||||
|
@ -9252,6 +9254,7 @@ impl Project {
|
||||||
filter_range: Default::default(),
|
filter_range: Default::default(),
|
||||||
},
|
},
|
||||||
confirm: None,
|
confirm: None,
|
||||||
|
show_new_completions_on_confirm: false,
|
||||||
},
|
},
|
||||||
false,
|
false,
|
||||||
cx,
|
cx,
|
||||||
|
@ -10924,6 +10927,7 @@ async fn populate_labels_for_completions(
|
||||||
documentation,
|
documentation,
|
||||||
lsp_completion,
|
lsp_completion,
|
||||||
confirm: None,
|
confirm: None,
|
||||||
|
show_new_completions_on_confirm: false,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue