assistant: Don't require a worktree to run slash commands (#15655)

Marshall Bowers created

This PR fixes an issue where slash commands were not able to run when
Zed did not have any worktrees opened.

This requirement was only necessary for slash commands originating from
extensions, and we can enforce the presence of a worktree just for
those:

<img width="378" alt="Screenshot 2024-08-01 at 5 01 58 PM"
src="https://github.com/user-attachments/assets/38bea947-e33b-4c64-853c-c1f36c63d779">

Release Notes:

- N/A

Change summary

crates/assistant/src/assistant_panel.rs                       | 22 ++--
crates/assistant/src/context.rs                               |  2 
crates/assistant/src/slash_command/active_command.rs          |  2 
crates/assistant/src/slash_command/default_command.rs         |  2 
crates/assistant/src/slash_command/diagnostics_command.rs     |  2 
crates/assistant/src/slash_command/docs_command.rs            |  2 
crates/assistant/src/slash_command/fetch_command.rs           |  2 
crates/assistant/src/slash_command/file_command.rs            |  2 
crates/assistant/src/slash_command/now_command.rs             |  2 
crates/assistant/src/slash_command/project_command.rs         |  2 
crates/assistant/src/slash_command/prompt_command.rs          |  2 
crates/assistant/src/slash_command/search_command.rs          |  2 
crates/assistant/src/slash_command/symbols_command.rs         |  2 
crates/assistant/src/slash_command/tabs_command.rs            |  2 
crates/assistant/src/slash_command/term_command.rs            |  2 
crates/assistant_slash_command/src/assistant_slash_command.rs |  2 
crates/extension/src/extension_slash_command.rs               |  5 
17 files changed, 29 insertions(+), 28 deletions(-)

Detailed changes

crates/assistant/src/assistant_panel.rs 🔗

@@ -1596,18 +1596,16 @@ impl ContextEditor {
         cx: &mut ViewContext<Self>,
     ) {
         if let Some(command) = SlashCommandRegistry::global(cx).command(name) {
-            if let Some(lsp_adapter_delegate) = self.lsp_adapter_delegate.clone() {
-                let argument = argument.map(ToString::to_string);
-                let output = command.run(argument.as_deref(), workspace, lsp_adapter_delegate, cx);
-                self.context.update(cx, |context, cx| {
-                    context.insert_command_output(
-                        command_range,
-                        output,
-                        insert_trailing_newline,
-                        cx,
-                    )
-                });
-            }
+            let argument = argument.map(ToString::to_string);
+            let output = command.run(
+                argument.as_deref(),
+                workspace,
+                self.lsp_adapter_delegate.clone(),
+                cx,
+            );
+            self.context.update(cx, |context, cx| {
+                context.insert_command_output(command_range, output, insert_trailing_newline, cx)
+            });
         }
     }
 

crates/assistant/src/context.rs 🔗

@@ -3414,7 +3414,7 @@ mod tests {
             self: Arc<Self>,
             _argument: Option<&str>,
             _workspace: WeakView<Workspace>,
-            _delegate: Arc<dyn LspAdapterDelegate>,
+            _delegate: Option<Arc<dyn LspAdapterDelegate>>,
             _cx: &mut WindowContext,
         ) -> Task<Result<SlashCommandOutput>> {
             Task::ready(Ok(SlashCommandOutput {

crates/assistant/src/slash_command/active_command.rs 🔗

@@ -46,7 +46,7 @@ impl SlashCommand for ActiveSlashCommand {
         self: Arc<Self>,
         _argument: Option<&str>,
         workspace: WeakView<Workspace>,
-        _delegate: Arc<dyn LspAdapterDelegate>,
+        _delegate: Option<Arc<dyn LspAdapterDelegate>>,
         cx: &mut WindowContext,
     ) -> Task<Result<SlashCommandOutput>> {
         let output = workspace.update(cx, |workspace, cx| {

crates/assistant/src/slash_command/default_command.rs 🔗

@@ -44,7 +44,7 @@ impl SlashCommand for DefaultSlashCommand {
         self: Arc<Self>,
         _argument: Option<&str>,
         _workspace: WeakView<Workspace>,
-        _delegate: Arc<dyn LspAdapterDelegate>,
+        _delegate: Option<Arc<dyn LspAdapterDelegate>>,
         cx: &mut WindowContext,
     ) -> Task<Result<SlashCommandOutput>> {
         let store = PromptStore::global(cx);

crates/assistant/src/slash_command/diagnostics_command.rs 🔗

@@ -158,7 +158,7 @@ impl SlashCommand for DiagnosticsSlashCommand {
         self: Arc<Self>,
         argument: Option<&str>,
         workspace: WeakView<Workspace>,
-        _delegate: Arc<dyn LspAdapterDelegate>,
+        _delegate: Option<Arc<dyn LspAdapterDelegate>>,
         cx: &mut WindowContext,
     ) -> Task<Result<SlashCommandOutput>> {
         let Some(workspace) = workspace.upgrade() else {

crates/assistant/src/slash_command/docs_command.rs 🔗

@@ -242,7 +242,7 @@ impl SlashCommand for DocsSlashCommand {
         self: Arc<Self>,
         argument: Option<&str>,
         _workspace: WeakView<Workspace>,
-        _delegate: Arc<dyn LspAdapterDelegate>,
+        _delegate: Option<Arc<dyn LspAdapterDelegate>>,
         cx: &mut WindowContext,
     ) -> Task<Result<SlashCommandOutput>> {
         let Some(argument) = argument else {

crates/assistant/src/slash_command/fetch_command.rs 🔗

@@ -129,7 +129,7 @@ impl SlashCommand for FetchSlashCommand {
         self: Arc<Self>,
         argument: Option<&str>,
         workspace: WeakView<Workspace>,
-        _delegate: Arc<dyn LspAdapterDelegate>,
+        _delegate: Option<Arc<dyn LspAdapterDelegate>>,
         cx: &mut WindowContext,
     ) -> Task<Result<SlashCommandOutput>> {
         let Some(argument) = argument else {

crates/assistant/src/slash_command/file_command.rs 🔗

@@ -136,7 +136,7 @@ impl SlashCommand for FileSlashCommand {
         self: Arc<Self>,
         argument: Option<&str>,
         workspace: WeakView<Workspace>,
-        _delegate: Arc<dyn LspAdapterDelegate>,
+        _delegate: Option<Arc<dyn LspAdapterDelegate>>,
         cx: &mut WindowContext,
     ) -> Task<Result<SlashCommandOutput>> {
         let Some(workspace) = workspace.upgrade() else {

crates/assistant/src/slash_command/now_command.rs 🔗

@@ -44,7 +44,7 @@ impl SlashCommand for NowSlashCommand {
         self: Arc<Self>,
         _argument: Option<&str>,
         _workspace: WeakView<Workspace>,
-        _delegate: Arc<dyn LspAdapterDelegate>,
+        _delegate: Option<Arc<dyn LspAdapterDelegate>>,
         _cx: &mut WindowContext,
     ) -> Task<Result<SlashCommandOutput>> {
         let now = Local::now();

crates/assistant/src/slash_command/project_command.rs 🔗

@@ -119,7 +119,7 @@ impl SlashCommand for ProjectSlashCommand {
         self: Arc<Self>,
         _argument: Option<&str>,
         workspace: WeakView<Workspace>,
-        _delegate: Arc<dyn LspAdapterDelegate>,
+        _delegate: Option<Arc<dyn LspAdapterDelegate>>,
         cx: &mut WindowContext,
     ) -> Task<Result<SlashCommandOutput>> {
         let output = workspace.update(cx, |workspace, cx| {

crates/assistant/src/slash_command/prompt_command.rs 🔗

@@ -55,7 +55,7 @@ impl SlashCommand for PromptSlashCommand {
         self: Arc<Self>,
         title: Option<&str>,
         _workspace: WeakView<Workspace>,
-        _delegate: Arc<dyn LspAdapterDelegate>,
+        _delegate: Option<Arc<dyn LspAdapterDelegate>>,
         cx: &mut WindowContext,
     ) -> Task<Result<SlashCommandOutput>> {
         let Some(title) = title else {

crates/assistant/src/slash_command/search_command.rs 🔗

@@ -54,7 +54,7 @@ impl SlashCommand for SearchSlashCommand {
         self: Arc<Self>,
         argument: Option<&str>,
         workspace: WeakView<Workspace>,
-        _delegate: Arc<dyn LspAdapterDelegate>,
+        _delegate: Option<Arc<dyn LspAdapterDelegate>>,
         cx: &mut WindowContext,
     ) -> Task<Result<SlashCommandOutput>> {
         let Some(workspace) = workspace.upgrade() else {

crates/assistant/src/slash_command/symbols_command.rs 🔗

@@ -42,7 +42,7 @@ impl SlashCommand for OutlineSlashCommand {
         self: Arc<Self>,
         _argument: Option<&str>,
         workspace: WeakView<Workspace>,
-        _delegate: Arc<dyn LspAdapterDelegate>,
+        _delegate: Option<Arc<dyn LspAdapterDelegate>>,
         cx: &mut WindowContext,
     ) -> Task<Result<SlashCommandOutput>> {
         let output = workspace.update(cx, |workspace, cx| {

crates/assistant/src/slash_command/tabs_command.rs 🔗

@@ -46,7 +46,7 @@ impl SlashCommand for TabsSlashCommand {
         self: Arc<Self>,
         _argument: Option<&str>,
         workspace: WeakView<Workspace>,
-        _delegate: Arc<dyn LspAdapterDelegate>,
+        _delegate: Option<Arc<dyn LspAdapterDelegate>>,
         cx: &mut WindowContext,
     ) -> Task<Result<SlashCommandOutput>> {
         let open_buffers = workspace.update(cx, |workspace, cx| {

crates/assistant/src/slash_command/term_command.rs 🔗

@@ -58,7 +58,7 @@ impl SlashCommand for TermSlashCommand {
         self: Arc<Self>,
         argument: Option<&str>,
         workspace: WeakView<Workspace>,
-        _delegate: Arc<dyn LspAdapterDelegate>,
+        _delegate: Option<Arc<dyn LspAdapterDelegate>>,
         cx: &mut WindowContext,
     ) -> Task<Result<SlashCommandOutput>> {
         let Some(workspace) = workspace.upgrade() else {

crates/assistant_slash_command/src/assistant_slash_command.rs 🔗

@@ -49,7 +49,7 @@ pub trait SlashCommand: 'static + Send + Sync {
         //
         // It may be that `LspAdapterDelegate` needs a more general name, or
         // perhaps another kind of delegate is needed here.
-        delegate: Arc<dyn LspAdapterDelegate>,
+        delegate: Option<Arc<dyn LspAdapterDelegate>>,
         cx: &mut WindowContext,
     ) -> Task<Result<SlashCommandOutput>>;
 }

crates/extension/src/extension_slash_command.rs 🔗

@@ -81,7 +81,7 @@ impl SlashCommand for ExtensionSlashCommand {
         self: Arc<Self>,
         argument: Option<&str>,
         _workspace: WeakView<Workspace>,
-        delegate: Arc<dyn LspAdapterDelegate>,
+        delegate: Option<Arc<dyn LspAdapterDelegate>>,
         cx: &mut WindowContext,
     ) -> Task<Result<SlashCommandOutput>> {
         let argument = argument.map(|arg| arg.to_string());
@@ -91,6 +91,9 @@ impl SlashCommand for ExtensionSlashCommand {
                     let this = self.clone();
                     move |extension, store| {
                         async move {
+                            let delegate = delegate.ok_or_else(|| {
+                                anyhow!("no worktree for extension slash command")
+                            })?;
                             let resource = store.data_mut().table().push(delegate)?;
                             let output = extension
                                 .call_run_slash_command(