extension_slash_command.rs

  1use std::path::PathBuf;
  2use std::sync::{atomic::AtomicBool, Arc};
  3
  4use anyhow::Result;
  5use async_trait::async_trait;
  6use extension::{Extension, ExtensionHostProxy, ExtensionSlashCommandProxy, WorktreeDelegate};
  7use gpui::{AppContext, Task, WeakView, WindowContext};
  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 AppContext) {
 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<WeakView<Workspace>>,
101        cx: &mut WindowContext,
102    ) -> Task<Result<Vec<ArgumentCompletion>>> {
103        let command = self.command.clone();
104        let arguments = arguments.to_owned();
105        cx.background_executor().spawn(async move {
106            let completions = self
107                .extension
108                .complete_slash_command_argument(command, arguments)
109                .await?;
110
111            anyhow::Ok(
112                completions
113                    .into_iter()
114                    .map(|completion| ArgumentCompletion {
115                        label: completion.label.into(),
116                        new_text: completion.new_text,
117                        replace_previous_arguments: false,
118                        after_completion: completion.run_command.into(),
119                    })
120                    .collect(),
121            )
122        })
123    }
124
125    fn run(
126        self: Arc<Self>,
127        arguments: &[String],
128        _context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>],
129        _context_buffer: BufferSnapshot,
130        _workspace: WeakView<Workspace>,
131        delegate: Option<Arc<dyn LspAdapterDelegate>>,
132        cx: &mut WindowContext,
133    ) -> Task<SlashCommandResult> {
134        let command = self.command.clone();
135        let arguments = arguments.to_owned();
136        let output = cx.background_executor().spawn(async move {
137            let delegate =
138                delegate.map(|delegate| Arc::new(WorktreeDelegateAdapter(delegate.clone())) as _);
139            let output = self
140                .extension
141                .run_slash_command(command, arguments, delegate)
142                .await?;
143
144            anyhow::Ok(output)
145        });
146        cx.foreground_executor().spawn(async move {
147            let output = output.await?;
148            Ok(SlashCommandOutput {
149                text: output.text,
150                sections: output
151                    .sections
152                    .into_iter()
153                    .map(|section| SlashCommandOutputSection {
154                        range: section.range,
155                        icon: IconName::Code,
156                        label: section.label.into(),
157                        metadata: None,
158                    })
159                    .collect(),
160                run_commands_in_text: false,
161            }
162            .to_event_stream())
163        })
164    }
165}