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}