1use std::path::PathBuf;
2use std::sync::Arc;
3
4use anyhow::Result;
5use fs::Fs;
6use gpui::{AppContext, 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: &AppContext) -> 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 AppContext) -> 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 AppContext);
106}
107
108impl ExtensionThemeProxy for ExtensionHostProxy {
109 fn list_theme_names(&self, theme_path: PathBuf, fs: Arc<dyn Fs>) -> Task<Result<Vec<String>>> {
110 let Some(proxy) = self.theme_proxy.read().clone() else {
111 return Task::ready(Ok(Vec::new()));
112 };
113
114 proxy.list_theme_names(theme_path, fs)
115 }
116
117 fn remove_user_themes(&self, themes: Vec<SharedString>) {
118 let Some(proxy) = self.theme_proxy.read().clone() else {
119 return;
120 };
121
122 proxy.remove_user_themes(themes)
123 }
124
125 fn load_user_theme(&self, theme_path: PathBuf, fs: Arc<dyn Fs>) -> Task<Result<()>> {
126 let Some(proxy) = self.theme_proxy.read().clone() else {
127 return Task::ready(Ok(()));
128 };
129
130 proxy.load_user_theme(theme_path, fs)
131 }
132
133 fn reload_current_theme(&self, cx: &mut AppContext) {
134 let Some(proxy) = self.theme_proxy.read().clone() else {
135 return;
136 };
137
138 proxy.reload_current_theme(cx)
139 }
140}
141
142pub trait ExtensionGrammarProxy: Send + Sync + 'static {
143 fn register_grammars(&self, grammars: Vec<(Arc<str>, PathBuf)>);
144}
145
146impl ExtensionGrammarProxy for ExtensionHostProxy {
147 fn register_grammars(&self, grammars: Vec<(Arc<str>, PathBuf)>) {
148 let Some(proxy) = self.grammar_proxy.read().clone() else {
149 return;
150 };
151
152 proxy.register_grammars(grammars)
153 }
154}
155
156pub trait ExtensionLanguageProxy: Send + Sync + 'static {
157 fn register_language(
158 &self,
159 language: LanguageName,
160 grammar: Option<Arc<str>>,
161 matcher: LanguageMatcher,
162 hidden: bool,
163 load: Arc<dyn Fn() -> Result<LoadedLanguage> + Send + Sync + 'static>,
164 );
165
166 fn remove_languages(
167 &self,
168 languages_to_remove: &[LanguageName],
169 grammars_to_remove: &[Arc<str>],
170 );
171}
172
173impl ExtensionLanguageProxy for ExtensionHostProxy {
174 fn register_language(
175 &self,
176 language: LanguageName,
177 grammar: Option<Arc<str>>,
178 matcher: LanguageMatcher,
179 hidden: bool,
180 load: Arc<dyn Fn() -> Result<LoadedLanguage> + Send + Sync + 'static>,
181 ) {
182 let Some(proxy) = self.language_proxy.read().clone() else {
183 return;
184 };
185
186 proxy.register_language(language, grammar, matcher, hidden, load)
187 }
188
189 fn remove_languages(
190 &self,
191 languages_to_remove: &[LanguageName],
192 grammars_to_remove: &[Arc<str>],
193 ) {
194 let Some(proxy) = self.language_proxy.read().clone() else {
195 return;
196 };
197
198 proxy.remove_languages(languages_to_remove, grammars_to_remove)
199 }
200}
201
202pub trait ExtensionLanguageServerProxy: Send + Sync + 'static {
203 fn register_language_server(
204 &self,
205 extension: Arc<dyn Extension>,
206 language_server_id: LanguageServerName,
207 language: LanguageName,
208 );
209
210 fn remove_language_server(
211 &self,
212 language: &LanguageName,
213 language_server_id: &LanguageServerName,
214 );
215
216 fn update_language_server_status(
217 &self,
218 language_server_id: LanguageServerName,
219 status: LanguageServerBinaryStatus,
220 );
221}
222
223impl ExtensionLanguageServerProxy for ExtensionHostProxy {
224 fn register_language_server(
225 &self,
226 extension: Arc<dyn Extension>,
227 language_server_id: LanguageServerName,
228 language: LanguageName,
229 ) {
230 let Some(proxy) = self.language_server_proxy.read().clone() else {
231 return;
232 };
233
234 proxy.register_language_server(extension, language_server_id, language)
235 }
236
237 fn remove_language_server(
238 &self,
239 language: &LanguageName,
240 language_server_id: &LanguageServerName,
241 ) {
242 let Some(proxy) = self.language_server_proxy.read().clone() else {
243 return;
244 };
245
246 proxy.remove_language_server(language, language_server_id)
247 }
248
249 fn update_language_server_status(
250 &self,
251 language_server_id: LanguageServerName,
252 status: LanguageServerBinaryStatus,
253 ) {
254 let Some(proxy) = self.language_server_proxy.read().clone() else {
255 return;
256 };
257
258 proxy.update_language_server_status(language_server_id, status)
259 }
260}
261
262pub trait ExtensionSnippetProxy: Send + Sync + 'static {
263 fn register_snippet(&self, path: &PathBuf, snippet_contents: &str) -> Result<()>;
264}
265
266impl ExtensionSnippetProxy for ExtensionHostProxy {
267 fn register_snippet(&self, path: &PathBuf, snippet_contents: &str) -> Result<()> {
268 let Some(proxy) = self.snippet_proxy.read().clone() else {
269 return Ok(());
270 };
271
272 proxy.register_snippet(path, snippet_contents)
273 }
274}
275
276pub trait ExtensionSlashCommandProxy: Send + Sync + 'static {
277 fn register_slash_command(&self, extension: Arc<dyn Extension>, command: SlashCommand);
278}
279
280impl ExtensionSlashCommandProxy for ExtensionHostProxy {
281 fn register_slash_command(&self, extension: Arc<dyn Extension>, command: SlashCommand) {
282 let Some(proxy) = self.slash_command_proxy.read().clone() else {
283 return;
284 };
285
286 proxy.register_slash_command(extension, command)
287 }
288}
289
290pub trait ExtensionContextServerProxy: Send + Sync + 'static {
291 fn register_context_server(
292 &self,
293 extension: Arc<dyn Extension>,
294 server_id: Arc<str>,
295 cx: &mut AppContext,
296 );
297}
298
299impl ExtensionContextServerProxy for ExtensionHostProxy {
300 fn register_context_server(
301 &self,
302 extension: Arc<dyn Extension>,
303 server_id: Arc<str>,
304 cx: &mut AppContext,
305 ) {
306 let Some(proxy) = self.context_server_proxy.read().clone() else {
307 return;
308 };
309
310 proxy.register_context_server(extension, server_id, cx)
311 }
312}
313
314pub trait ExtensionIndexedDocsProviderProxy: Send + Sync + 'static {
315 fn register_indexed_docs_provider(&self, extension: Arc<dyn Extension>, provider_id: Arc<str>);
316}
317
318impl ExtensionIndexedDocsProviderProxy for ExtensionHostProxy {
319 fn register_indexed_docs_provider(&self, extension: Arc<dyn Extension>, provider_id: Arc<str>) {
320 let Some(proxy) = self.indexed_docs_provider_proxy.read().clone() else {
321 return;
322 };
323
324 proxy.register_indexed_docs_provider(extension, provider_id)
325 }
326}