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