1use anyhow::Result;
  2use async_trait::async_trait;
  3use extension::{Extension, ExtensionHostProxy, ExtensionSlashCommandProxy, WorktreeDelegate};
  4use gpui::{App, Task, WeakEntity, Window};
  5use language::{BufferSnapshot, LspAdapterDelegate};
  6use std::sync::{Arc, atomic::AtomicBool};
  7use ui::prelude::*;
  8use util::rel_path::RelPath;
  9use workspace::Workspace;
 10
 11use crate::{
 12    ArgumentCompletion, SlashCommand, SlashCommandOutput, SlashCommandOutputSection,
 13    SlashCommandRegistry, SlashCommandResult,
 14};
 15
 16pub fn init(cx: &mut App) {
 17    let proxy = ExtensionHostProxy::default_global(cx);
 18    proxy.register_slash_command_proxy(SlashCommandRegistryProxy {
 19        slash_command_registry: SlashCommandRegistry::global(cx),
 20    });
 21}
 22
 23struct SlashCommandRegistryProxy {
 24    slash_command_registry: Arc<SlashCommandRegistry>,
 25}
 26
 27impl ExtensionSlashCommandProxy for SlashCommandRegistryProxy {
 28    fn register_slash_command(
 29        &self,
 30        extension: Arc<dyn Extension>,
 31        command: extension::SlashCommand,
 32    ) {
 33        self.slash_command_registry
 34            .register_command(ExtensionSlashCommand::new(extension, command), false)
 35    }
 36
 37    fn unregister_slash_command(&self, command_name: Arc<str>) {
 38        self.slash_command_registry
 39            .unregister_command_by_name(&command_name)
 40    }
 41}
 42
 43/// An adapter that allows an [`LspAdapterDelegate`] to be used as a [`WorktreeDelegate`].
 44struct WorktreeDelegateAdapter(Arc<dyn LspAdapterDelegate>);
 45
 46#[async_trait]
 47impl WorktreeDelegate for WorktreeDelegateAdapter {
 48    fn id(&self) -> u64 {
 49        self.0.worktree_id().to_proto()
 50    }
 51
 52    fn root_path(&self) -> String {
 53        self.0.worktree_root_path().to_string_lossy().into_owned()
 54    }
 55
 56    async fn read_text_file(&self, path: &RelPath) -> Result<String> {
 57        self.0.read_text_file(path).await
 58    }
 59
 60    async fn which(&self, binary_name: String) -> Option<String> {
 61        self.0
 62            .which(binary_name.as_ref())
 63            .await
 64            .map(|path| path.to_string_lossy().into_owned())
 65    }
 66
 67    async fn shell_env(&self) -> Vec<(String, String)> {
 68        self.0.shell_env().await.into_iter().collect()
 69    }
 70}
 71
 72pub struct ExtensionSlashCommand {
 73    extension: Arc<dyn Extension>,
 74    command: extension::SlashCommand,
 75}
 76
 77impl ExtensionSlashCommand {
 78    pub fn new(extension: Arc<dyn Extension>, command: extension::SlashCommand) -> Self {
 79        Self { extension, command }
 80    }
 81}
 82
 83impl SlashCommand for ExtensionSlashCommand {
 84    fn name(&self) -> String {
 85        self.command.name.clone()
 86    }
 87
 88    fn description(&self) -> String {
 89        self.command.description.clone()
 90    }
 91
 92    fn menu_text(&self) -> String {
 93        self.command.tooltip_text.clone()
 94    }
 95
 96    fn requires_argument(&self) -> bool {
 97        self.command.requires_argument
 98    }
 99
100    fn complete_argument(
101        self: Arc<Self>,
102        arguments: &[String],
103        _cancel: Arc<AtomicBool>,
104        _workspace: Option<WeakEntity<Workspace>>,
105        _window: &mut Window,
106        cx: &mut App,
107    ) -> Task<Result<Vec<ArgumentCompletion>>> {
108        let command = self.command.clone();
109        let arguments = arguments.to_owned();
110        cx.background_spawn(async move {
111            let completions = self
112                .extension
113                .complete_slash_command_argument(command, arguments)
114                .await?;
115
116            anyhow::Ok(
117                completions
118                    .into_iter()
119                    .map(|completion| ArgumentCompletion {
120                        label: completion.label.into(),
121                        new_text: completion.new_text,
122                        replace_previous_arguments: false,
123                        after_completion: completion.run_command.into(),
124                    })
125                    .collect(),
126            )
127        })
128    }
129
130    fn run(
131        self: Arc<Self>,
132        arguments: &[String],
133        _context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>],
134        _context_buffer: BufferSnapshot,
135        _workspace: WeakEntity<Workspace>,
136        delegate: Option<Arc<dyn LspAdapterDelegate>>,
137        _window: &mut Window,
138        cx: &mut App,
139    ) -> Task<SlashCommandResult> {
140        let command = self.command.clone();
141        let arguments = arguments.to_owned();
142        let output = cx.background_spawn(async move {
143            let delegate =
144                delegate.map(|delegate| Arc::new(WorktreeDelegateAdapter(delegate.clone())) as _);
145            let output = self
146                .extension
147                .run_slash_command(command, arguments, delegate)
148                .await?;
149
150            anyhow::Ok(output)
151        });
152        cx.foreground_executor().spawn(async move {
153            let output = output.await?;
154            Ok(SlashCommandOutput {
155                text: output.text,
156                sections: output
157                    .sections
158                    .into_iter()
159                    .map(|section| SlashCommandOutputSection {
160                        range: section.range,
161                        icon: IconName::Code,
162                        label: section.label.into(),
163                        metadata: None,
164                    })
165                    .collect(),
166                run_commands_in_text: false,
167            }
168            .into_event_stream())
169        })
170    }
171}