extension_slash_command.rs

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