vim: Add vim command filename autocomplete (#36332)

Release Notes:

- Adds filename autocomplete for vim commands:
  - write
  - edit
  - split
  - vsplit
  - tabedit
  - tabnew
- Makes command palette interceptor async
<img width="1382" height="634" alt="image"
src="https://github.com/user-attachments/assets/e7bf01c5-e9cd-4a7d-b38c-12fc3df5069f"
/>

---------

Co-authored-by: Conrad Irwin <conrad.irwin@gmail.com>
This commit is contained in:
AidanV
2025-10-17 04:19:01 +00:00
committed by GitHub
co-authored by Conrad Irwin
parent 908ae95cf8
commit 0cbab311a1
7 changed files with 426 additions and 184 deletions
+61 -28
View File
@@ -9,7 +9,8 @@ use std::{
use client::parse_zed_link;
use command_palette_hooks::{
CommandInterceptResult, CommandPaletteFilter, CommandPaletteInterceptor,
CommandInterceptItem, CommandInterceptResult, CommandPaletteFilter,
GlobalCommandPaletteInterceptor,
};
use fuzzy::{StringMatch, StringMatchCandidate};
@@ -81,14 +82,17 @@ impl CommandPalette {
let Some(previous_focus_handle) = window.focused(cx) else {
return;
};
let entity = cx.weak_entity();
workspace.toggle_modal(window, cx, move |window, cx| {
CommandPalette::new(previous_focus_handle, query, window, cx)
CommandPalette::new(previous_focus_handle, query, entity, window, cx)
});
}
fn new(
previous_focus_handle: FocusHandle,
query: &str,
entity: WeakEntity<Workspace>,
window: &mut Window,
cx: &mut Context<Self>,
) -> Self {
@@ -109,8 +113,12 @@ impl CommandPalette {
})
.collect();
let delegate =
CommandPaletteDelegate::new(cx.entity().downgrade(), commands, previous_focus_handle);
let delegate = CommandPaletteDelegate::new(
cx.entity().downgrade(),
entity,
commands,
previous_focus_handle,
);
let picker = cx.new(|cx| {
let picker = Picker::uniform_list(delegate, window, cx);
@@ -146,6 +154,7 @@ impl Render for CommandPalette {
pub struct CommandPaletteDelegate {
latest_query: String,
command_palette: WeakEntity<CommandPalette>,
workspace: WeakEntity<Workspace>,
all_commands: Vec<Command>,
commands: Vec<Command>,
matches: Vec<StringMatch>,
@@ -153,7 +162,7 @@ pub struct CommandPaletteDelegate {
previous_focus_handle: FocusHandle,
updating_matches: Option<(
Task<()>,
postage::dispatch::Receiver<(Vec<Command>, Vec<StringMatch>)>,
postage::dispatch::Receiver<(Vec<Command>, Vec<StringMatch>, CommandInterceptResult)>,
)>,
}
@@ -174,11 +183,13 @@ impl Clone for Command {
impl CommandPaletteDelegate {
fn new(
command_palette: WeakEntity<CommandPalette>,
workspace: WeakEntity<Workspace>,
commands: Vec<Command>,
previous_focus_handle: FocusHandle,
) -> Self {
Self {
command_palette,
workspace,
all_commands: commands.clone(),
matches: vec![],
commands,
@@ -194,30 +205,19 @@ impl CommandPaletteDelegate {
query: String,
mut commands: Vec<Command>,
mut matches: Vec<StringMatch>,
cx: &mut Context<Picker<Self>>,
intercept_result: CommandInterceptResult,
_: &mut Context<Picker<Self>>,
) {
self.updating_matches.take();
self.latest_query = query.clone();
let mut intercept_results = CommandPaletteInterceptor::try_global(cx)
.map(|interceptor| interceptor.intercept(&query, cx))
.unwrap_or_default();
if parse_zed_link(&query, cx).is_some() {
intercept_results = vec![CommandInterceptResult {
action: OpenZedUrl { url: query.clone() }.boxed_clone(),
string: query,
positions: vec![],
}]
}
self.latest_query = query;
let mut new_matches = Vec::new();
for CommandInterceptResult {
for CommandInterceptItem {
action,
string,
positions,
} in intercept_results
} in intercept_result.results
{
if let Some(idx) = matches
.iter()
@@ -236,7 +236,9 @@ impl CommandPaletteDelegate {
score: 0.0,
})
}
new_matches.append(&mut matches);
if !intercept_result.exclusive {
new_matches.append(&mut matches);
}
self.commands = commands;
self.matches = new_matches;
if self.matches.is_empty() {
@@ -295,12 +297,22 @@ impl PickerDelegate for CommandPaletteDelegate {
if let Some(alias) = settings.command_aliases.get(&query) {
query = alias.to_string();
}
let workspace = self.workspace.clone();
let intercept_task = GlobalCommandPaletteInterceptor::intercept(&query, workspace, cx);
let (mut tx, mut rx) = postage::dispatch::channel(1);
let query_str = query.as_str();
let is_zed_link = parse_zed_link(query_str, cx).is_some();
let task = cx.background_spawn({
let mut commands = self.all_commands.clone();
let hit_counts = self.hit_counts();
let executor = cx.background_executor().clone();
let query = normalize_action_query(query.as_str());
let query = normalize_action_query(query_str);
let query_for_link = query_str.to_string();
async move {
commands.sort_by_key(|action| {
(
@@ -326,13 +338,34 @@ impl PickerDelegate for CommandPaletteDelegate {
)
.await;
tx.send((commands, matches)).await.log_err();
let intercept_result = if is_zed_link {
CommandInterceptResult {
results: vec![CommandInterceptItem {
action: OpenZedUrl {
url: query_for_link.clone(),
}
.boxed_clone(),
string: query_for_link,
positions: vec![],
}],
exclusive: false,
}
} else if let Some(task) = intercept_task {
task.await
} else {
CommandInterceptResult::default()
};
tx.send((commands, matches, intercept_result))
.await
.log_err();
}
});
self.updating_matches = Some((task, rx.clone()));
cx.spawn_in(window, async move |picker, cx| {
let Some((commands, matches)) = rx.recv().await else {
let Some((commands, matches, intercept_result)) = rx.recv().await else {
return;
};
@@ -340,7 +373,7 @@ impl PickerDelegate for CommandPaletteDelegate {
.update(cx, |picker, cx| {
picker
.delegate
.matches_updated(query, commands, matches, cx)
.matches_updated(query, commands, matches, intercept_result, cx)
})
.log_err();
})
@@ -361,8 +394,8 @@ impl PickerDelegate for CommandPaletteDelegate {
.background_executor()
.block_with_timeout(duration, rx.clone().recv())
{
Ok(Some((commands, matches))) => {
self.matches_updated(query, commands, matches, cx);
Ok(Some((commands, matches, interceptor_result))) => {
self.matches_updated(query, commands, matches, interceptor_result, cx);
true
}
_ => {