Detailed changes
@@ -2580,9 +2580,9 @@ impl ConversationEditor {
let slash_command_registry = conversation.read(cx).slash_command_registry.clone();
let completion_provider = SlashCommandCompletionProvider::new(
- cx.view().downgrade(),
slash_command_registry.clone(),
- workspace.downgrade(),
+ Some(cx.view().downgrade()),
+ Some(workspace.downgrade()),
);
let editor = cx.new_view(|cx| {
@@ -1,5 +1,5 @@
use crate::{
- slash_command::SlashCommandLine, CompletionProvider, LanguageModelRequest,
+ slash_command::SlashCommandCompletionProvider, CompletionProvider, LanguageModelRequest,
LanguageModelRequestMessage, Role,
};
use anyhow::{anyhow, Result};
@@ -11,17 +11,14 @@ use futures::{
future::{self, BoxFuture, Shared},
FutureExt,
};
-use fuzzy::{match_strings, StringMatchCandidate};
+use fuzzy::StringMatchCandidate;
use gpui::{
actions, point, size, AnyElement, AppContext, BackgroundExecutor, Bounds, DevicePixels,
- EventEmitter, Global, Model, PromptLevel, ReadGlobal, Subscription, Task, TitlebarOptions,
- View, WindowBounds, WindowHandle, WindowOptions,
+ EventEmitter, Global, PromptLevel, ReadGlobal, Subscription, Task, TitlebarOptions, View,
+ WindowBounds, WindowHandle, WindowOptions,
};
use heed::{types::SerdeBincode, Database, RoTxn};
-use language::{
- language_settings::SoftWrap, Buffer, Documentation, LanguageRegistry, LanguageServerId, Point,
- ToPoint as _,
-};
+use language::{language_settings::SoftWrap, Buffer, LanguageRegistry};
use parking_lot::RwLock;
use picker::{Picker, PickerDelegate};
use rope::Rope;
@@ -482,6 +479,7 @@ impl PromptLibrary {
self.set_active_prompt(Some(prompt_id), cx);
} else {
let language_registry = self.language_registry.clone();
+ let commands = SlashCommandRegistry::global(cx);
let prompt = self.store.load(prompt_id);
self.pending_load = cx.spawn(|this, mut cx| async move {
let prompt = prompt.await;
@@ -500,8 +498,9 @@ impl PromptLibrary {
editor.set_show_gutter(false, cx);
editor.set_show_wrap_guides(false, cx);
editor.set_show_indent_guides(false, cx);
- editor
- .set_completion_provider(Box::new(SlashCommandCompletionProvider));
+ editor.set_completion_provider(Box::new(
+ SlashCommandCompletionProvider::new(commands, None, None),
+ ));
if focus {
editor.focus(cx);
}
@@ -1092,123 +1091,3 @@ fn title_from_body(body: impl IntoIterator<Item = char>) -> Option<SharedString>
None
}
}
-
-struct SlashCommandCompletionProvider;
-
-impl editor::CompletionProvider for SlashCommandCompletionProvider {
- fn completions(
- &self,
- buffer: &Model<Buffer>,
- buffer_position: language::Anchor,
- cx: &mut ViewContext<Editor>,
- ) -> Task<Result<Vec<project::Completion>>> {
- let Some((command_name, name_range)) = buffer.update(cx, |buffer, _cx| {
- let position = buffer_position.to_point(buffer);
- let line_start = Point::new(position.row, 0);
- let mut lines = buffer.text_for_range(line_start..position).lines();
- let line = lines.next()?;
- let call = SlashCommandLine::parse(line)?;
-
- if call.argument.is_some() {
- // Don't autocomplete arguments.
- None
- } else {
- let name = line[call.name.clone()].to_string();
- let name_range_start = Point::new(position.row, call.name.start as u32);
- let name_range_end = Point::new(position.row, call.name.end as u32);
- let name_range =
- buffer.anchor_after(name_range_start)..buffer.anchor_after(name_range_end);
- Some((name, name_range))
- }
- }) else {
- return Task::ready(Ok(Vec::new()));
- };
-
- let commands = SlashCommandRegistry::global(cx);
- let candidates = commands
- .command_names()
- .into_iter()
- .enumerate()
- .map(|(ix, def)| StringMatchCandidate {
- id: ix,
- string: def.to_string(),
- char_bag: def.as_ref().into(),
- })
- .collect::<Vec<_>>();
- let command_name = command_name.to_string();
- cx.spawn(|_, mut cx| async move {
- let matches = match_strings(
- &candidates,
- &command_name,
- true,
- usize::MAX,
- &Default::default(),
- cx.background_executor().clone(),
- )
- .await;
- cx.update(|cx| {
- matches
- .into_iter()
- .filter_map(|mat| {
- let command = commands.command(&mat.string)?;
- let mut new_text = mat.string.clone();
- let requires_argument = command.requires_argument();
- if requires_argument {
- new_text.push(' ');
- }
-
- Some(project::Completion {
- old_range: name_range.clone(),
- documentation: Some(Documentation::SingleLine(command.description())),
- new_text,
- label: command.label(cx),
- server_id: LanguageServerId(0),
- lsp_completion: Default::default(),
- show_new_completions_on_confirm: false,
- confirm: None,
- })
- })
- .collect()
- })
- })
- }
-
- fn resolve_completions(
- &self,
- _: Model<Buffer>,
- _: Vec<usize>,
- _: Arc<RwLock<Box<[project::Completion]>>>,
- _: &mut ViewContext<Editor>,
- ) -> Task<Result<bool>> {
- Task::ready(Ok(true))
- }
-
- fn apply_additional_edits_for_completion(
- &self,
- _: Model<Buffer>,
- _: project::Completion,
- _: bool,
- _: &mut ViewContext<Editor>,
- ) -> Task<Result<Option<language::Transaction>>> {
- Task::ready(Ok(None))
- }
-
- fn is_completion_trigger(
- &self,
- buffer: &Model<Buffer>,
- position: language::Anchor,
- _text: &str,
- _trigger_in_words: bool,
- cx: &mut ViewContext<Editor>,
- ) -> bool {
- let buffer = buffer.read(cx);
- let position = position.to_point(buffer);
- let line_start = Point::new(position.row, 0);
- let mut lines = buffer.text_for_range(line_start..position).lines();
- if let Some(line) = lines.next() {
- SlashCommandLine::parse(line).is_some()
- } else {
- false
- }
- }
-}
@@ -27,10 +27,10 @@ pub mod search_command;
pub mod tabs_command;
pub(crate) struct SlashCommandCompletionProvider {
- editor: WeakView<ConversationEditor>,
commands: Arc<SlashCommandRegistry>,
cancel_flag: Mutex<Arc<AtomicBool>>,
- workspace: WeakView<Workspace>,
+ editor: Option<WeakView<ConversationEditor>>,
+ workspace: Option<WeakView<Workspace>>,
}
pub(crate) struct SlashCommandLine {
@@ -42,9 +42,9 @@ pub(crate) struct SlashCommandLine {
impl SlashCommandCompletionProvider {
pub fn new(
- editor: WeakView<ConversationEditor>,
commands: Arc<SlashCommandRegistry>,
- workspace: WeakView<Workspace>,
+ editor: Option<WeakView<ConversationEditor>>,
+ workspace: Option<WeakView<Workspace>>,
) -> Self {
Self {
cancel_flag: Mutex::new(Arc::new(AtomicBool::new(false))),
@@ -98,6 +98,30 @@ impl SlashCommandCompletionProvider {
new_text.push(' ');
}
+ let confirm = editor.clone().zip(workspace.clone()).and_then(
+ |(editor, workspace)| {
+ (!requires_argument).then(|| {
+ let command_name = mat.string.clone();
+ let command_range = command_range.clone();
+ let editor = editor.clone();
+ let workspace = workspace.clone();
+ Arc::new(move |cx: &mut WindowContext| {
+ editor
+ .update(cx, |editor, cx| {
+ editor.run_command(
+ command_range.clone(),
+ &command_name,
+ None,
+ true,
+ workspace.clone(),
+ cx,
+ );
+ })
+ .ok();
+ }) as Arc<_>
+ })
+ },
+ );
Some(project::Completion {
old_range: name_range.clone(),
documentation: Some(Documentation::SingleLine(command.description())),
@@ -106,26 +130,7 @@ impl SlashCommandCompletionProvider {
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();
- let editor = editor.clone();
- let workspace = workspace.clone();
- Arc::new(move |cx: &mut WindowContext| {
- editor
- .update(cx, |editor, cx| {
- editor.run_command(
- command_range.clone(),
- &command_name,
- None,
- true,
- workspace.clone(),
- cx,
- );
- })
- .ok();
- }) as Arc<_>
- }),
+ confirm,
})
})
.collect()
@@ -160,34 +165,42 @@ impl SlashCommandCompletionProvider {
Ok(completions
.await?
.into_iter()
- .map(|arg| project::Completion {
- old_range: argument_range.clone(),
- label: CodeLabel::plain(arg.clone(), None),
- new_text: arg.clone(),
- 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();
- let editor = editor.clone();
- let workspace = workspace.clone();
- move |cx| {
- editor
- .update(cx, |editor, cx| {
- editor.run_command(
- command_range.clone(),
- &command_name,
- Some(&arg),
- true,
- workspace.clone(),
- cx,
- );
- })
- .ok();
- }
- })),
+ .map(|command_argument| {
+ let confirm =
+ editor
+ .clone()
+ .zip(workspace.clone())
+ .map(|(editor, workspace)| {
+ Arc::new({
+ let command_range = command_range.clone();
+ let command_name = command_name.clone();
+ let command_argument = command_argument.clone();
+ move |cx: &mut WindowContext| {
+ editor
+ .update(cx, |editor, cx| {
+ editor.run_command(
+ command_range.clone(),
+ &command_name,
+ Some(&command_argument),
+ true,
+ workspace.clone(),
+ cx,
+ );
+ })
+ .ok();
+ }
+ }) as Arc<_>
+ });
+ project::Completion {
+ old_range: argument_range.clone(),
+ label: CodeLabel::plain(command_argument.clone(), None),
+ new_text: command_argument.clone(),
+ documentation: None,
+ server_id: LanguageServerId(0),
+ lsp_completion: Default::default(),
+ show_new_completions_on_confirm: false,
+ confirm,
+ }
})
.collect())
})
@@ -27,7 +27,7 @@ impl SlashCommand for ActiveSlashCommand {
&self,
_query: String,
_cancel: std::sync::Arc<std::sync::atomic::AtomicBool>,
- _workspace: WeakView<Workspace>,
+ _workspace: Option<WeakView<Workspace>>,
_cx: &mut AppContext,
) -> Task<Result<Vec<String>>> {
Task::ready(Err(anyhow!("this command does not require argument")))
@@ -34,7 +34,7 @@ impl SlashCommand for DefaultSlashCommand {
&self,
_query: String,
_cancellation_flag: Arc<AtomicBool>,
- _workspace: WeakView<Workspace>,
+ _workspace: Option<WeakView<Workspace>>,
_cx: &mut AppContext,
) -> Task<Result<Vec<String>>> {
Task::ready(Err(anyhow!("this command does not require argument")))
@@ -62,7 +62,7 @@ impl SlashCommand for FetchSlashCommand {
&self,
_query: String,
_cancel: Arc<AtomicBool>,
- _workspace: WeakView<Workspace>,
+ _workspace: Option<WeakView<Workspace>>,
_cx: &mut AppContext,
) -> Task<Result<Vec<String>>> {
Task::ready(Ok(Vec::new()))
@@ -101,10 +101,10 @@ impl SlashCommand for FileSlashCommand {
&self,
query: String,
cancellation_flag: Arc<AtomicBool>,
- workspace: WeakView<Workspace>,
+ workspace: Option<WeakView<Workspace>>,
cx: &mut AppContext,
) -> Task<Result<Vec<String>>> {
- let Some(workspace) = workspace.upgrade() else {
+ let Some(workspace) = workspace.and_then(|workspace| workspace.upgrade()) else {
return Task::ready(Err(anyhow!("workspace was dropped")));
};
@@ -105,7 +105,7 @@ impl SlashCommand for ProjectSlashCommand {
&self,
_query: String,
_cancel: Arc<AtomicBool>,
- _workspace: WeakView<Workspace>,
+ _workspace: Option<WeakView<Workspace>>,
_cx: &mut AppContext,
) -> Task<Result<Vec<String>>> {
Task::ready(Err(anyhow!("this command does not require argument")))
@@ -31,7 +31,7 @@ impl SlashCommand for PromptSlashCommand {
&self,
query: String,
_cancellation_flag: Arc<AtomicBool>,
- _workspace: WeakView<Workspace>,
+ _workspace: Option<WeakView<Workspace>>,
cx: &mut AppContext,
) -> Task<Result<Vec<String>>> {
let store = PromptStore::global(cx);
@@ -119,7 +119,7 @@ impl SlashCommand for RustdocSlashCommand {
&self,
_query: String,
_cancel: Arc<AtomicBool>,
- _workspace: WeakView<Workspace>,
+ _workspace: Option<WeakView<Workspace>>,
_cx: &mut AppContext,
) -> Task<Result<Vec<String>>> {
Task::ready(Ok(Vec::new()))
@@ -47,7 +47,7 @@ impl SlashCommand for SearchSlashCommand {
&self,
_query: String,
_cancel: Arc<AtomicBool>,
- _workspace: WeakView<Workspace>,
+ _workspace: Option<WeakView<Workspace>>,
_cx: &mut AppContext,
) -> Task<Result<Vec<String>>> {
Task::ready(Ok(Vec::new()))
@@ -32,7 +32,7 @@ impl SlashCommand for TabsSlashCommand {
&self,
_query: String,
_cancel: Arc<std::sync::atomic::AtomicBool>,
- _workspace: WeakView<Workspace>,
+ _workspace: Option<WeakView<Workspace>>,
_cx: &mut AppContext,
) -> Task<Result<Vec<String>>> {
Task::ready(Err(anyhow!("this command does not require argument")))
@@ -25,7 +25,7 @@ pub trait SlashCommand: 'static + Send + Sync {
&self,
query: String,
cancel: Arc<AtomicBool>,
- workspace: WeakView<Workspace>,
+ workspace: Option<WeakView<Workspace>>,
cx: &mut AppContext,
) -> Task<Result<Vec<String>>>;
fn requires_argument(&self) -> bool;
@@ -39,7 +39,7 @@ impl SlashCommand for ExtensionSlashCommand {
&self,
_query: String,
_cancel: Arc<AtomicBool>,
- _workspace: WeakView<Workspace>,
+ _workspace: Option<WeakView<Workspace>>,
_cx: &mut AppContext,
) -> Task<Result<Vec<String>>> {
Task::ready(Ok(Vec::new()))