1use std::path::PathBuf;
2use std::sync::Arc;
3
4use anyhow::Result;
5use fs::Fs;
6use gpui::{App, Global, ReadGlobal, SharedString, Task};
7use language::{LanguageMatcher, LanguageName, LanguageServerBinaryStatus, LoadedLanguage};
8use lsp::LanguageServerName;
9use parking_lot::RwLock;
10
11use crate::{Extension, SlashCommand};
12
13#[derive(Default)]
14struct GlobalExtensionHostProxy(Arc<ExtensionHostProxy>);
15
16impl Global for GlobalExtensionHostProxy {}
17
18/// A proxy for interacting with the extension host.
19///
20/// This object implements each of the individual proxy types so that their
21/// methods can be called directly on it.
22#[derive(Default)]
23pub struct ExtensionHostProxy {
24 theme_proxy: RwLock<Option<Arc<dyn ExtensionThemeProxy>>>,
25 grammar_proxy: RwLock<Option<Arc<dyn ExtensionGrammarProxy>>>,
26 language_proxy: RwLock<Option<Arc<dyn ExtensionLanguageProxy>>>,
27 language_server_proxy: RwLock<Option<Arc<dyn ExtensionLanguageServerProxy>>>,
28 snippet_proxy: RwLock<Option<Arc<dyn ExtensionSnippetProxy>>>,
29 slash_command_proxy: RwLock<Option<Arc<dyn ExtensionSlashCommandProxy>>>,
30 context_server_proxy: RwLock<Option<Arc<dyn ExtensionContextServerProxy>>>,
31 indexed_docs_provider_proxy: RwLock<Option<Arc<dyn ExtensionIndexedDocsProviderProxy>>>,
32}
33
34impl ExtensionHostProxy {
35 /// Returns the global [`ExtensionHostProxy`].
36 pub fn global(cx: &App) -> Arc<Self> {
37 GlobalExtensionHostProxy::global(cx).0.clone()
38 }
39
40 /// Returns the global [`ExtensionHostProxy`].
41 ///
42 /// Inserts a default [`ExtensionHostProxy`] if one does not yet exist.
43 pub fn default_global(cx: &mut App) -> Arc<Self> {
44 cx.default_global::<GlobalExtensionHostProxy>().0.clone()
45 }
46
47 pub fn new() -> Self {
48 Self {
49 theme_proxy: RwLock::default(),
50 grammar_proxy: RwLock::default(),
51 language_proxy: RwLock::default(),
52 language_server_proxy: RwLock::default(),
53 snippet_proxy: RwLock::default(),
54 slash_command_proxy: RwLock::default(),
55 context_server_proxy: RwLock::default(),
56 indexed_docs_provider_proxy: RwLock::default(),
57 }
58 }
59
60 pub fn register_theme_proxy(&self, proxy: impl ExtensionThemeProxy) {
61 self.theme_proxy.write().replace(Arc::new(proxy));
62 }
63
64 pub fn register_grammar_proxy(&self, proxy: impl ExtensionGrammarProxy) {
65 self.grammar_proxy.write().replace(Arc::new(proxy));
66 }
67
68 pub fn register_language_proxy(&self, proxy: impl ExtensionLanguageProxy) {
69 self.language_proxy.write().replace(Arc::new(proxy));
70 }
71
72 pub fn register_language_server_proxy(&self, proxy: impl ExtensionLanguageServerProxy) {
73 self.language_server_proxy.write().replace(Arc::new(proxy));
74 }
75
76 pub fn register_snippet_proxy(&self, proxy: impl ExtensionSnippetProxy) {
77 self.snippet_proxy.write().replace(Arc::new(proxy));
78 }
79
80 pub fn register_slash_command_proxy(&self, proxy: impl ExtensionSlashCommandProxy) {
81 self.slash_command_proxy.write().replace(Arc::new(proxy));
82 }
83
84 pub fn register_context_server_proxy(&self, proxy: impl ExtensionContextServerProxy) {
85 self.context_server_proxy.write().replace(Arc::new(proxy));
86 }
87
88 pub fn register_indexed_docs_provider_proxy(
89 &self,
90 proxy: impl ExtensionIndexedDocsProviderProxy,
91 ) {
92 self.indexed_docs_provider_proxy
93 .write()
94 .replace(Arc::new(proxy));
95 }
96}
97
98pub trait ExtensionThemeProxy: Send + Sync + 'static {
99 fn list_theme_names(&self, theme_path: PathBuf, fs: Arc<dyn Fs>) -> Task<Result<Vec<String>>>;
100
101 fn remove_user_themes(&self, themes: Vec<SharedString>);
102
103 fn load_user_theme(&self, theme_path: PathBuf, fs: Arc<dyn Fs>) -> Task<Result<()>>;
104
105 fn reload_current_theme(&self, cx: &mut App);
106
107 fn list_icon_theme_names(
108 &self,
109 icon_theme_path: PathBuf,
110 fs: Arc<dyn Fs>,
111 ) -> Task<Result<Vec<String>>>;
112
113 fn remove_icon_themes(&self, icon_themes: Vec<SharedString>);
114
115 fn load_icon_theme(
116 &self,
117 icon_theme_path: PathBuf,
118 icons_root_dir: PathBuf,
119 fs: Arc<dyn Fs>,
120 ) -> Task<Result<()>>;
121}
122
123impl ExtensionThemeProxy for ExtensionHostProxy {
124 fn list_theme_names(&self, theme_path: PathBuf, fs: Arc<dyn Fs>) -> Task<Result<Vec<String>>> {
125 let Some(proxy) = self.theme_proxy.read().clone() else {
126 return Task::ready(Ok(Vec::new()));
127 };
128
129 proxy.list_theme_names(theme_path, fs)
130 }
131
132 fn remove_user_themes(&self, themes: Vec<SharedString>) {
133 let Some(proxy) = self.theme_proxy.read().clone() else {
134 return;
135 };
136
137 proxy.remove_user_themes(themes)
138 }
139
140 fn load_user_theme(&self, theme_path: PathBuf, fs: Arc<dyn Fs>) -> Task<Result<()>> {
141 let Some(proxy) = self.theme_proxy.read().clone() else {
142 return Task::ready(Ok(()));
143 };
144
145 proxy.load_user_theme(theme_path, fs)
146 }
147
148 fn reload_current_theme(&self, cx: &mut App) {
149 let Some(proxy) = self.theme_proxy.read().clone() else {
150 return;
151 };
152
153 proxy.reload_current_theme(cx)
154 }
155
156 fn list_icon_theme_names(
157 &self,
158 icon_theme_path: PathBuf,
159 fs: Arc<dyn Fs>,
160 ) -> Task<Result<Vec<String>>> {
161 let Some(proxy) = self.theme_proxy.read().clone() else {
162 return Task::ready(Ok(Vec::new()));
163 };
164
165 proxy.list_icon_theme_names(icon_theme_path, fs)
166 }
167
168 fn remove_icon_themes(&self, icon_themes: Vec<SharedString>) {
169 let Some(proxy) = self.theme_proxy.read().clone() else {
170 return;
171 };
172
173 proxy.remove_icon_themes(icon_themes)
174 }
175
176 fn load_icon_theme(
177 &self,
178 icon_theme_path: PathBuf,
179 icons_root_dir: PathBuf,
180 fs: Arc<dyn Fs>,
181 ) -> Task<Result<()>> {
182 let Some(proxy) = self.theme_proxy.read().clone() else {
183 return Task::ready(Ok(()));
184 };
185
186 proxy.load_icon_theme(icon_theme_path, icons_root_dir, fs)
187 }
188}
189
190pub trait ExtensionGrammarProxy: Send + Sync + 'static {
191 fn register_grammars(&self, grammars: Vec<(Arc<str>, PathBuf)>);
192}
193
194impl ExtensionGrammarProxy for ExtensionHostProxy {
195 fn register_grammars(&self, grammars: Vec<(Arc<str>, PathBuf)>) {
196 let Some(proxy) = self.grammar_proxy.read().clone() else {
197 return;
198 };
199
200 proxy.register_grammars(grammars)
201 }
202}
203
204pub trait ExtensionLanguageProxy: Send + Sync + 'static {
205 fn register_language(
206 &self,
207 language: LanguageName,
208 grammar: Option<Arc<str>>,
209 matcher: LanguageMatcher,
210 hidden: bool,
211 load: Arc<dyn Fn() -> Result<LoadedLanguage> + Send + Sync + 'static>,
212 );
213
214 fn remove_languages(
215 &self,
216 languages_to_remove: &[LanguageName],
217 grammars_to_remove: &[Arc<str>],
218 );
219}
220
221impl ExtensionLanguageProxy for ExtensionHostProxy {
222 fn register_language(
223 &self,
224 language: LanguageName,
225 grammar: Option<Arc<str>>,
226 matcher: LanguageMatcher,
227 hidden: bool,
228 load: Arc<dyn Fn() -> Result<LoadedLanguage> + Send + Sync + 'static>,
229 ) {
230 let Some(proxy) = self.language_proxy.read().clone() else {
231 return;
232 };
233
234 proxy.register_language(language, grammar, matcher, hidden, load)
235 }
236
237 fn remove_languages(
238 &self,
239 languages_to_remove: &[LanguageName],
240 grammars_to_remove: &[Arc<str>],
241 ) {
242 let Some(proxy) = self.language_proxy.read().clone() else {
243 return;
244 };
245
246 proxy.remove_languages(languages_to_remove, grammars_to_remove)
247 }
248}
249
250pub trait ExtensionLanguageServerProxy: Send + Sync + 'static {
251 fn register_language_server(
252 &self,
253 extension: Arc<dyn Extension>,
254 language_server_id: LanguageServerName,
255 language: LanguageName,
256 );
257
258 fn remove_language_server(
259 &self,
260 language: &LanguageName,
261 language_server_id: &LanguageServerName,
262 );
263
264 fn update_language_server_status(
265 &self,
266 language_server_id: LanguageServerName,
267 status: LanguageServerBinaryStatus,
268 );
269}
270
271impl ExtensionLanguageServerProxy for ExtensionHostProxy {
272 fn register_language_server(
273 &self,
274 extension: Arc<dyn Extension>,
275 language_server_id: LanguageServerName,
276 language: LanguageName,
277 ) {
278 let Some(proxy) = self.language_server_proxy.read().clone() else {
279 return;
280 };
281
282 proxy.register_language_server(extension, language_server_id, language)
283 }
284
285 fn remove_language_server(
286 &self,
287 language: &LanguageName,
288 language_server_id: &LanguageServerName,
289 ) {
290 let Some(proxy) = self.language_server_proxy.read().clone() else {
291 return;
292 };
293
294 proxy.remove_language_server(language, language_server_id)
295 }
296
297 fn update_language_server_status(
298 &self,
299 language_server_id: LanguageServerName,
300 status: LanguageServerBinaryStatus,
301 ) {
302 let Some(proxy) = self.language_server_proxy.read().clone() else {
303 return;
304 };
305
306 proxy.update_language_server_status(language_server_id, status)
307 }
308}
309
310pub trait ExtensionSnippetProxy: Send + Sync + 'static {
311 fn register_snippet(&self, path: &PathBuf, snippet_contents: &str) -> Result<()>;
312}
313
314impl ExtensionSnippetProxy for ExtensionHostProxy {
315 fn register_snippet(&self, path: &PathBuf, snippet_contents: &str) -> Result<()> {
316 let Some(proxy) = self.snippet_proxy.read().clone() else {
317 return Ok(());
318 };
319
320 proxy.register_snippet(path, snippet_contents)
321 }
322}
323
324pub trait ExtensionSlashCommandProxy: Send + Sync + 'static {
325 fn register_slash_command(&self, extension: Arc<dyn Extension>, command: SlashCommand);
326}
327
328impl ExtensionSlashCommandProxy for ExtensionHostProxy {
329 fn register_slash_command(&self, extension: Arc<dyn Extension>, command: SlashCommand) {
330 let Some(proxy) = self.slash_command_proxy.read().clone() else {
331 return;
332 };
333
334 proxy.register_slash_command(extension, command)
335 }
336}
337
338pub trait ExtensionContextServerProxy: Send + Sync + 'static {
339 fn register_context_server(
340 &self,
341 extension: Arc<dyn Extension>,
342 server_id: Arc<str>,
343 cx: &mut App,
344 );
345}
346
347impl ExtensionContextServerProxy for ExtensionHostProxy {
348 fn register_context_server(
349 &self,
350 extension: Arc<dyn Extension>,
351 server_id: Arc<str>,
352 cx: &mut App,
353 ) {
354 let Some(proxy) = self.context_server_proxy.read().clone() else {
355 return;
356 };
357
358 proxy.register_context_server(extension, server_id, cx)
359 }
360}
361
362pub trait ExtensionIndexedDocsProviderProxy: Send + Sync + 'static {
363 fn register_indexed_docs_provider(&self, extension: Arc<dyn Extension>, provider_id: Arc<str>);
364}
365
366impl ExtensionIndexedDocsProviderProxy for ExtensionHostProxy {
367 fn register_indexed_docs_provider(&self, extension: Arc<dyn Extension>, provider_id: Arc<str>) {
368 let Some(proxy) = self.indexed_docs_provider_proxy.read().clone() else {
369 return;
370 };
371
372 proxy.register_indexed_docs_provider(extension, provider_id)
373 }
374}