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