1use anyhow::Context as _;
2use gpui::{App, UpdateGlobal};
3use json::json_task_context;
4pub use language::*;
5use node_runtime::NodeRuntime;
6use python::{PythonContextProvider, PythonToolchainProvider};
7use rust_embed::RustEmbed;
8use settings::SettingsStore;
9use smol::stream::StreamExt;
10use std::{str, sync::Arc};
11use typescript::typescript_task_context;
12use util::{asset_str, ResultExt};
13
14use crate::{bash::bash_task_context, rust::RustContextProvider};
15
16mod bash;
17mod c;
18mod css;
19mod go;
20mod json;
21mod python;
22mod rust;
23mod tailwind;
24mod typescript;
25mod vtsls;
26mod yaml;
27
28#[derive(RustEmbed)]
29#[folder = "src/"]
30#[exclude = "*.rs"]
31struct LanguageDir;
32
33/// A shared grammar for plain text, exposed for reuse by downstream crates.
34#[cfg(feature = "tree-sitter-gitcommit")]
35pub static LANGUAGE_GIT_COMMIT: std::sync::LazyLock<Arc<Language>> =
36 std::sync::LazyLock::new(|| {
37 Arc::new(Language::new(
38 LanguageConfig {
39 name: "Git Commit".into(),
40 soft_wrap: Some(language::language_settings::SoftWrap::EditorWidth),
41 matcher: LanguageMatcher {
42 path_suffixes: vec!["COMMIT_EDITMSG".to_owned()],
43 first_line_pattern: None,
44 },
45 line_comments: vec![Arc::from("#")],
46 ..LanguageConfig::default()
47 },
48 Some(tree_sitter_gitcommit::LANGUAGE.into()),
49 ))
50 });
51
52pub fn init(languages: Arc<LanguageRegistry>, node_runtime: NodeRuntime, cx: &mut App) {
53 #[cfg(feature = "load-grammars")]
54 languages.register_native_grammars([
55 ("bash", tree_sitter_bash::LANGUAGE),
56 ("c", tree_sitter_c::LANGUAGE),
57 ("cpp", tree_sitter_cpp::LANGUAGE),
58 ("css", tree_sitter_css::LANGUAGE),
59 ("diff", tree_sitter_diff::LANGUAGE),
60 ("go", tree_sitter_go::LANGUAGE),
61 ("gomod", tree_sitter_go_mod::LANGUAGE),
62 ("gowork", tree_sitter_gowork::LANGUAGE),
63 ("jsdoc", tree_sitter_jsdoc::LANGUAGE),
64 ("json", tree_sitter_json::LANGUAGE),
65 ("jsonc", tree_sitter_json::LANGUAGE),
66 ("markdown", tree_sitter_md::LANGUAGE),
67 ("markdown-inline", tree_sitter_md::INLINE_LANGUAGE),
68 ("python", tree_sitter_python::LANGUAGE),
69 ("regex", tree_sitter_regex::LANGUAGE),
70 ("rust", tree_sitter_rust::LANGUAGE),
71 ("tsx", tree_sitter_typescript::LANGUAGE_TSX),
72 ("typescript", tree_sitter_typescript::LANGUAGE_TYPESCRIPT),
73 ("yaml", tree_sitter_yaml::LANGUAGE),
74 ("gitcommit", tree_sitter_gitcommit::LANGUAGE),
75 ]);
76
77 // Following are a series of helper macros for registering languages.
78 // Macros are used instead of a function or for loop in order to avoid
79 // code duplication and improve readability as the types get quite verbose
80 // to type out in some cases.
81 // Additionally, the `provider` fields in LoadedLanguage
82 // would have be `Copy` if we were to use a function or for-loop to register the languages
83 // due to the fact that we pass an `Arc<Fn>` to `languages.register_language`
84 // that loads and initializes the language lazily.
85 // We avoid this entirely by using a Macro
86
87 macro_rules! context_provider {
88 ($name:expr) => {
89 Some(Arc::new($name) as Arc<dyn ContextProvider>)
90 };
91 () => {
92 None
93 };
94 }
95
96 macro_rules! toolchain_provider {
97 ($name:expr) => {
98 Some(Arc::new($name) as Arc<dyn ToolchainLister>)
99 };
100 () => {
101 None
102 };
103 }
104
105 macro_rules! adapters {
106 ($($item:expr),+ $(,)?) => {
107 vec![
108 $(Arc::new($item) as Arc<dyn LspAdapter>,)*
109 ]
110 };
111 () => {
112 vec![]
113 };
114 }
115
116 macro_rules! register_language {
117 ($name:expr, adapters => $adapters:expr, context => $context:expr, toolchain => $toolchain:expr) => {
118 let config = load_config($name);
119 for adapter in $adapters {
120 languages.register_lsp_adapter(config.name.clone(), adapter);
121 }
122 languages.register_language(
123 config.name.clone(),
124 config.grammar.clone(),
125 config.matcher.clone(),
126 config.hidden,
127 Arc::new(move || {
128 Ok(LoadedLanguage {
129 config: config.clone(),
130 queries: load_queries($name),
131 context_provider: $context,
132 toolchain_provider: $toolchain,
133 })
134 }),
135 );
136 };
137 ($name:expr) => {
138 register_language!($name, adapters => adapters![], context => context_provider!(), toolchain => toolchain_provider!())
139 };
140 ($name:expr, adapters => $adapters:expr, context => $context:expr, toolchain => $toolchain:expr) => {
141 register_language!($name, adapters => $adapters, context => $context, toolchain => $toolchain)
142 };
143 ($name:expr, adapters => $adapters:expr, context => $context:expr) => {
144 register_language!($name, adapters => $adapters, context => $context, toolchain => toolchain_provider!())
145 };
146 ($name:expr, adapters => $adapters:expr) => {
147 register_language!($name, adapters => $adapters, context => context_provider!(), toolchain => toolchain_provider!())
148 };
149 }
150
151 register_language!(
152 "bash",
153 adapters => adapters![],
154 context => context_provider!(bash_task_context()),
155 toolchain => toolchain_provider!()
156 );
157
158 register_language!(
159 "c",
160 adapters => adapters![c::CLspAdapter]
161 );
162 register_language!(
163 "cpp",
164 adapters => adapters![c::CLspAdapter]
165 );
166
167 register_language!(
168 "css",
169 adapters => adapters![css::CssLspAdapter::new(node_runtime.clone())]
170 );
171
172 register_language!("diff");
173
174 register_language!(
175 "go",
176 adapters => adapters![go::GoLspAdapter],
177 context => context_provider!(go::GoContextProvider)
178 );
179 register_language!(
180 "gomod",
181 adapters => adapters![go::GoLspAdapter],
182 context => context_provider!(go::GoContextProvider)
183 );
184 register_language!(
185 "gowork",
186 adapters => adapters![go::GoLspAdapter],
187 context => context_provider!(go::GoContextProvider)
188 );
189
190 register_language!(
191 "json",
192 adapters => adapters![
193 json::JsonLspAdapter::new(node_runtime.clone(), languages.clone(),),
194 json::NodeVersionAdapter,
195 ],
196 context => context_provider!(json_task_context())
197 );
198 register_language!(
199 "jsonc",
200 adapters => adapters![
201 json::JsonLspAdapter::new(node_runtime.clone(), languages.clone(),),
202 ],
203 context => context_provider!(json_task_context())
204 );
205
206 register_language!("markdown");
207 register_language!("markdown-inline");
208
209 register_language!(
210 "python",
211 adapters => adapters![
212 python::PythonLspAdapter::new(node_runtime.clone()),
213 python::PyLspAdapter::new()
214 ],
215 context => context_provider!(PythonContextProvider),
216 toolchain => toolchain_provider!(PythonToolchainProvider::default())
217 );
218 register_language!(
219 "rust",
220 adapters => adapters![rust::RustLspAdapter],
221 context => context_provider!(RustContextProvider)
222 );
223 register_language!(
224 "tsx",
225 adapters => adapters![
226 typescript::TypeScriptLspAdapter::new(node_runtime.clone()),
227 vtsls::VtslsLspAdapter::new(node_runtime.clone()),
228 ],
229 context => context_provider!(typescript_task_context()),
230 toolchain => toolchain_provider!()
231 );
232 register_language!(
233 "typescript",
234 adapters => adapters![
235 typescript::TypeScriptLspAdapter::new(node_runtime.clone()),
236 vtsls::VtslsLspAdapter::new(node_runtime.clone()),
237 ],
238 context => context_provider!(typescript_task_context())
239 );
240 register_language!(
241 "javascript",
242 adapters => adapters![
243 typescript::TypeScriptLspAdapter::new(node_runtime.clone()),
244 vtsls::VtslsLspAdapter::new(node_runtime.clone()),
245 ],
246 context => context_provider!(typescript_task_context())
247 );
248 register_language!(
249 "jsdoc",
250 adapters => adapters![
251 typescript::TypeScriptLspAdapter::new(node_runtime.clone()),
252 vtsls::VtslsLspAdapter::new(node_runtime.clone()),
253 ]
254 );
255
256 register_language!("regex");
257
258 register_language!("yaml",
259 adapters => adapters![
260 yaml::YamlLspAdapter::new(node_runtime.clone()),
261 ]
262 );
263
264 // Register globally available language servers.
265 //
266 // This will allow users to add support for a built-in language server (e.g., Tailwind)
267 // for a given language via the `language_servers` setting:
268 //
269 // ```json
270 // {
271 // "languages": {
272 // "My Language": {
273 // "language_servers": ["tailwindcss-language-server", "..."]
274 // }
275 // }
276 // }
277 // ```
278 languages.register_available_lsp_adapter(
279 LanguageServerName("tailwindcss-language-server".into()),
280 {
281 let node_runtime = node_runtime.clone();
282 move || Arc::new(tailwind::TailwindLspAdapter::new(node_runtime.clone()))
283 },
284 );
285 languages.register_available_lsp_adapter(LanguageServerName("eslint".into()), {
286 let node_runtime = node_runtime.clone();
287 move || Arc::new(typescript::EsLintLspAdapter::new(node_runtime.clone()))
288 });
289 languages.register_available_lsp_adapter(LanguageServerName("vtsls".into()), {
290 let node_runtime = node_runtime.clone();
291 move || Arc::new(vtsls::VtslsLspAdapter::new(node_runtime.clone()))
292 });
293 languages.register_available_lsp_adapter(
294 LanguageServerName("typescript-language-server".into()),
295 {
296 let node_runtime = node_runtime.clone();
297 move || Arc::new(typescript::TypeScriptLspAdapter::new(node_runtime.clone()))
298 },
299 );
300
301 // Register Tailwind for the existing languages that should have it by default.
302 //
303 // This can be driven by the `language_servers` setting once we have a way for
304 // extensions to provide their own default value for that setting.
305 let tailwind_languages = [
306 "Astro",
307 "CSS",
308 "ERB",
309 "HEEX",
310 "HTML",
311 "JavaScript",
312 "PHP",
313 "Svelte",
314 "TSX",
315 "Vue.js",
316 ];
317
318 for language in tailwind_languages {
319 languages.register_lsp_adapter(
320 language.into(),
321 Arc::new(tailwind::TailwindLspAdapter::new(node_runtime.clone())),
322 );
323 }
324
325 let eslint_languages = ["TSX", "TypeScript", "JavaScript", "Vue.js", "Svelte"];
326 for language in eslint_languages {
327 languages.register_lsp_adapter(
328 language.into(),
329 Arc::new(typescript::EsLintLspAdapter::new(node_runtime.clone())),
330 );
331 }
332
333 let mut subscription = languages.subscribe();
334 let mut prev_language_settings = languages.language_settings();
335
336 cx.spawn(|cx| async move {
337 while subscription.next().await.is_some() {
338 let language_settings = languages.language_settings();
339 if language_settings != prev_language_settings {
340 cx.update(|cx| {
341 SettingsStore::update_global(cx, |settings, cx| {
342 settings
343 .set_extension_settings(language_settings.clone(), cx)
344 .log_err();
345 });
346 })?;
347 prev_language_settings = language_settings;
348 }
349 }
350 anyhow::Ok(())
351 })
352 .detach();
353}
354
355#[cfg(any(test, feature = "test-support"))]
356pub fn language(name: &str, grammar: tree_sitter::Language) -> Arc<Language> {
357 Arc::new(
358 Language::new(load_config(name), Some(grammar))
359 .with_queries(load_queries(name))
360 .unwrap(),
361 )
362}
363
364fn load_config(name: &str) -> LanguageConfig {
365 let config_toml = String::from_utf8(
366 LanguageDir::get(&format!("{}/config.toml", name))
367 .unwrap_or_else(|| panic!("missing config for language {:?}", name))
368 .data
369 .to_vec(),
370 )
371 .unwrap();
372
373 #[allow(unused_mut)]
374 let mut config: LanguageConfig = ::toml::from_str(&config_toml)
375 .with_context(|| format!("failed to load config.toml for language {name:?}"))
376 .unwrap();
377
378 #[cfg(not(any(feature = "load-grammars", test)))]
379 {
380 config = LanguageConfig {
381 name: config.name,
382 matcher: config.matcher,
383 jsx_tag_auto_close: config.jsx_tag_auto_close,
384 ..Default::default()
385 }
386 }
387
388 config
389}
390
391fn load_queries(name: &str) -> LanguageQueries {
392 let mut result = LanguageQueries::default();
393 for path in LanguageDir::iter() {
394 if let Some(remainder) = path.strip_prefix(name).and_then(|p| p.strip_prefix('/')) {
395 if !remainder.ends_with(".scm") {
396 continue;
397 }
398 for (name, query) in QUERY_FILENAME_PREFIXES {
399 if remainder.starts_with(name) {
400 let contents = asset_str::<LanguageDir>(path.as_ref());
401 match query(&mut result) {
402 None => *query(&mut result) = Some(contents),
403 Some(r) => r.to_mut().push_str(contents.as_ref()),
404 }
405 }
406 }
407 }
408 }
409 result
410}