1use std::path::PathBuf;
2use std::sync::Arc;
3
4use anyhow::Result;
5use fs::Fs;
6use gpui::{App, Global, ReadGlobal, SharedString, Task};
7use language::{BinaryStatus, LanguageMatcher, LanguageName, 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 set_extensions_loaded(&self);
100
101 fn list_theme_names(&self, theme_path: PathBuf, fs: Arc<dyn Fs>) -> Task<Result<Vec<String>>>;
102
103 fn remove_user_themes(&self, themes: Vec<SharedString>);
104
105 fn load_user_theme(&self, theme_path: PathBuf, fs: Arc<dyn Fs>) -> Task<Result<()>>;
106
107 fn reload_current_theme(&self, cx: &mut App);
108
109 fn list_icon_theme_names(
110 &self,
111 icon_theme_path: PathBuf,
112 fs: Arc<dyn Fs>,
113 ) -> Task<Result<Vec<String>>>;
114
115 fn remove_icon_themes(&self, icon_themes: Vec<SharedString>);
116
117 fn load_icon_theme(
118 &self,
119 icon_theme_path: PathBuf,
120 icons_root_dir: PathBuf,
121 fs: Arc<dyn Fs>,
122 ) -> Task<Result<()>>;
123
124 fn reload_current_icon_theme(&self, cx: &mut App);
125}
126
127impl ExtensionThemeProxy for ExtensionHostProxy {
128 fn set_extensions_loaded(&self) {
129 let Some(proxy) = self.theme_proxy.read().clone() else {
130 return;
131 };
132
133 proxy.set_extensions_loaded()
134 }
135
136 fn list_theme_names(&self, theme_path: PathBuf, fs: Arc<dyn Fs>) -> Task<Result<Vec<String>>> {
137 let Some(proxy) = self.theme_proxy.read().clone() else {
138 return Task::ready(Ok(Vec::new()));
139 };
140
141 proxy.list_theme_names(theme_path, fs)
142 }
143
144 fn remove_user_themes(&self, themes: Vec<SharedString>) {
145 let Some(proxy) = self.theme_proxy.read().clone() else {
146 return;
147 };
148
149 proxy.remove_user_themes(themes)
150 }
151
152 fn load_user_theme(&self, theme_path: PathBuf, fs: Arc<dyn Fs>) -> Task<Result<()>> {
153 let Some(proxy) = self.theme_proxy.read().clone() else {
154 return Task::ready(Ok(()));
155 };
156
157 proxy.load_user_theme(theme_path, fs)
158 }
159
160 fn reload_current_theme(&self, cx: &mut App) {
161 let Some(proxy) = self.theme_proxy.read().clone() else {
162 return;
163 };
164
165 proxy.reload_current_theme(cx)
166 }
167
168 fn list_icon_theme_names(
169 &self,
170 icon_theme_path: PathBuf,
171 fs: Arc<dyn Fs>,
172 ) -> Task<Result<Vec<String>>> {
173 let Some(proxy) = self.theme_proxy.read().clone() else {
174 return Task::ready(Ok(Vec::new()));
175 };
176
177 proxy.list_icon_theme_names(icon_theme_path, fs)
178 }
179
180 fn remove_icon_themes(&self, icon_themes: Vec<SharedString>) {
181 let Some(proxy) = self.theme_proxy.read().clone() else {
182 return;
183 };
184
185 proxy.remove_icon_themes(icon_themes)
186 }
187
188 fn load_icon_theme(
189 &self,
190 icon_theme_path: PathBuf,
191 icons_root_dir: PathBuf,
192 fs: Arc<dyn Fs>,
193 ) -> Task<Result<()>> {
194 let Some(proxy) = self.theme_proxy.read().clone() else {
195 return Task::ready(Ok(()));
196 };
197
198 proxy.load_icon_theme(icon_theme_path, icons_root_dir, fs)
199 }
200
201 fn reload_current_icon_theme(&self, cx: &mut App) {
202 let Some(proxy) = self.theme_proxy.read().clone() else {
203 return;
204 };
205
206 proxy.reload_current_icon_theme(cx)
207 }
208}
209
210pub trait ExtensionGrammarProxy: Send + Sync + 'static {
211 fn register_grammars(&self, grammars: Vec<(Arc<str>, PathBuf)>);
212}
213
214impl ExtensionGrammarProxy for ExtensionHostProxy {
215 fn register_grammars(&self, grammars: Vec<(Arc<str>, PathBuf)>) {
216 let Some(proxy) = self.grammar_proxy.read().clone() else {
217 return;
218 };
219
220 proxy.register_grammars(grammars)
221 }
222}
223
224pub trait ExtensionLanguageProxy: Send + Sync + 'static {
225 fn register_language(
226 &self,
227 language: LanguageName,
228 grammar: Option<Arc<str>>,
229 matcher: LanguageMatcher,
230 hidden: bool,
231 load: Arc<dyn Fn() -> Result<LoadedLanguage> + Send + Sync + 'static>,
232 );
233
234 fn remove_languages(
235 &self,
236 languages_to_remove: &[LanguageName],
237 grammars_to_remove: &[Arc<str>],
238 );
239}
240
241impl ExtensionLanguageProxy for ExtensionHostProxy {
242 fn register_language(
243 &self,
244 language: LanguageName,
245 grammar: Option<Arc<str>>,
246 matcher: LanguageMatcher,
247 hidden: bool,
248 load: Arc<dyn Fn() -> Result<LoadedLanguage> + Send + Sync + 'static>,
249 ) {
250 let Some(proxy) = self.language_proxy.read().clone() else {
251 return;
252 };
253
254 proxy.register_language(language, grammar, matcher, hidden, load)
255 }
256
257 fn remove_languages(
258 &self,
259 languages_to_remove: &[LanguageName],
260 grammars_to_remove: &[Arc<str>],
261 ) {
262 let Some(proxy) = self.language_proxy.read().clone() else {
263 return;
264 };
265
266 proxy.remove_languages(languages_to_remove, grammars_to_remove)
267 }
268}
269
270pub trait ExtensionLanguageServerProxy: Send + Sync + 'static {
271 fn register_language_server(
272 &self,
273 extension: Arc<dyn Extension>,
274 language_server_id: LanguageServerName,
275 language: LanguageName,
276 );
277
278 fn remove_language_server(
279 &self,
280 language: &LanguageName,
281 language_server_id: &LanguageServerName,
282 );
283
284 fn update_language_server_status(
285 &self,
286 language_server_id: LanguageServerName,
287 status: BinaryStatus,
288 );
289}
290
291impl ExtensionLanguageServerProxy for ExtensionHostProxy {
292 fn register_language_server(
293 &self,
294 extension: Arc<dyn Extension>,
295 language_server_id: LanguageServerName,
296 language: LanguageName,
297 ) {
298 let Some(proxy) = self.language_server_proxy.read().clone() else {
299 return;
300 };
301
302 proxy.register_language_server(extension, language_server_id, language)
303 }
304
305 fn remove_language_server(
306 &self,
307 language: &LanguageName,
308 language_server_id: &LanguageServerName,
309 ) {
310 let Some(proxy) = self.language_server_proxy.read().clone() else {
311 return;
312 };
313
314 proxy.remove_language_server(language, language_server_id)
315 }
316
317 fn update_language_server_status(
318 &self,
319 language_server_id: LanguageServerName,
320 status: BinaryStatus,
321 ) {
322 let Some(proxy) = self.language_server_proxy.read().clone() else {
323 return;
324 };
325
326 proxy.update_language_server_status(language_server_id, status)
327 }
328}
329
330pub trait ExtensionSnippetProxy: Send + Sync + 'static {
331 fn register_snippet(&self, path: &PathBuf, snippet_contents: &str) -> Result<()>;
332}
333
334impl ExtensionSnippetProxy for ExtensionHostProxy {
335 fn register_snippet(&self, path: &PathBuf, snippet_contents: &str) -> Result<()> {
336 let Some(proxy) = self.snippet_proxy.read().clone() else {
337 return Ok(());
338 };
339
340 proxy.register_snippet(path, snippet_contents)
341 }
342}
343
344pub trait ExtensionSlashCommandProxy: Send + Sync + 'static {
345 fn register_slash_command(&self, extension: Arc<dyn Extension>, command: SlashCommand);
346}
347
348impl ExtensionSlashCommandProxy for ExtensionHostProxy {
349 fn register_slash_command(&self, extension: Arc<dyn Extension>, command: SlashCommand) {
350 let Some(proxy) = self.slash_command_proxy.read().clone() else {
351 return;
352 };
353
354 proxy.register_slash_command(extension, command)
355 }
356}
357
358pub trait ExtensionContextServerProxy: Send + Sync + 'static {
359 fn register_context_server(
360 &self,
361 extension: Arc<dyn Extension>,
362 server_id: Arc<str>,
363 cx: &mut App,
364 );
365
366 fn unregister_context_server(&self, server_id: Arc<str>, cx: &mut App);
367}
368
369impl ExtensionContextServerProxy for ExtensionHostProxy {
370 fn register_context_server(
371 &self,
372 extension: Arc<dyn Extension>,
373 server_id: Arc<str>,
374 cx: &mut App,
375 ) {
376 let Some(proxy) = self.context_server_proxy.read().clone() else {
377 return;
378 };
379
380 proxy.register_context_server(extension, server_id, cx)
381 }
382
383 fn unregister_context_server(&self, server_id: Arc<str>, cx: &mut App) {
384 let Some(proxy) = self.context_server_proxy.read().clone() else {
385 return;
386 };
387
388 proxy.unregister_context_server(server_id, cx)
389 }
390}
391
392pub trait ExtensionIndexedDocsProviderProxy: Send + Sync + 'static {
393 fn register_indexed_docs_provider(&self, extension: Arc<dyn Extension>, provider_id: Arc<str>);
394}
395
396impl ExtensionIndexedDocsProviderProxy for ExtensionHostProxy {
397 fn register_indexed_docs_provider(&self, extension: Arc<dyn Extension>, provider_id: Arc<str>) {
398 let Some(proxy) = self.indexed_docs_provider_proxy.read().clone() else {
399 return;
400 };
401
402 proxy.register_indexed_docs_provider(extension, provider_id)
403 }
404}