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