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, go::GoContextProvider, 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 macro_rules! language {
78 ($name:literal) => {
79 let config = load_config($name);
80 languages.register_language(
81 config.name.clone(),
82 config.grammar.clone(),
83 config.matcher.clone(),
84 config.hidden,
85 Arc::new(move || {
86 Ok(LoadedLanguage {
87 config: config.clone(),
88 queries: load_queries($name),
89 context_provider: None,
90 toolchain_provider: None,
91 })
92 }),
93 );
94 };
95 ($name:literal, $adapters:expr) => {
96 let config = load_config($name);
97 // typeck helper
98 let adapters: Vec<Arc<dyn LspAdapter>> = $adapters;
99 for adapter in adapters {
100 languages.register_lsp_adapter(config.name.clone(), adapter);
101 }
102 languages.register_language(
103 config.name.clone(),
104 config.grammar.clone(),
105 config.matcher.clone(),
106 config.hidden,
107 Arc::new(move || {
108 Ok(LoadedLanguage {
109 config: config.clone(),
110 queries: load_queries($name),
111 context_provider: None,
112 toolchain_provider: None,
113 })
114 }),
115 );
116 };
117 ($name:literal, $adapters:expr, $context_provider:expr) => {
118 let config = load_config($name);
119 // typeck helper
120 let adapters: Vec<Arc<dyn LspAdapter>> = $adapters;
121 for adapter in adapters {
122 languages.register_lsp_adapter(config.name.clone(), adapter);
123 }
124 languages.register_language(
125 config.name.clone(),
126 config.grammar.clone(),
127 config.matcher.clone(),
128 config.hidden,
129 Arc::new(move || {
130 Ok(LoadedLanguage {
131 config: config.clone(),
132 queries: load_queries($name),
133 context_provider: Some(Arc::new($context_provider)),
134 toolchain_provider: None,
135 })
136 }),
137 );
138 };
139 ($name:literal, $adapters:expr, $context_provider:expr, $toolchain_provider:expr) => {
140 let config = load_config($name);
141 // typeck helper
142 let adapters: Vec<Arc<dyn LspAdapter>> = $adapters;
143 for adapter in adapters {
144 languages.register_lsp_adapter(config.name.clone(), adapter);
145 }
146 languages.register_language(
147 config.name.clone(),
148 config.grammar.clone(),
149 config.matcher.clone(),
150 config.hidden,
151 Arc::new(move || {
152 Ok(LoadedLanguage {
153 config: config.clone(),
154 queries: load_queries($name),
155 context_provider: Some(Arc::new($context_provider)),
156 toolchain_provider: Some($toolchain_provider),
157 })
158 }),
159 );
160 };
161 }
162 language!("bash", Vec::new(), bash_task_context());
163 language!("c", vec![Arc::new(c::CLspAdapter) as Arc<dyn LspAdapter>]);
164 language!("cpp", vec![Arc::new(c::CLspAdapter)]);
165 language!(
166 "css",
167 vec![Arc::new(css::CssLspAdapter::new(node_runtime.clone())),]
168 );
169 language!("diff");
170 language!("go", vec![Arc::new(go::GoLspAdapter)], GoContextProvider);
171 language!("gomod", vec![Arc::new(go::GoLspAdapter)], GoContextProvider);
172 language!(
173 "gowork",
174 vec![Arc::new(go::GoLspAdapter)],
175 GoContextProvider
176 );
177
178 language!(
179 "json",
180 vec![
181 Arc::new(json::JsonLspAdapter::new(
182 node_runtime.clone(),
183 languages.clone(),
184 )),
185 Arc::new(json::NodeVersionAdapter)
186 ],
187 json_task_context()
188 );
189 language!(
190 "jsonc",
191 vec![Arc::new(json::JsonLspAdapter::new(
192 node_runtime.clone(),
193 languages.clone(),
194 ))],
195 json_task_context()
196 );
197 language!("markdown");
198 language!("markdown-inline");
199 language!(
200 "python",
201 vec![
202 Arc::new(python::PythonLspAdapter::new(node_runtime.clone(),)),
203 Arc::new(python::PyLspAdapter::new())
204 ],
205 PythonContextProvider,
206 Arc::new(PythonToolchainProvider::default()) as Arc<dyn ToolchainLister>
207 );
208 language!(
209 "rust",
210 vec![Arc::new(rust::RustLspAdapter)],
211 RustContextProvider
212 );
213 language!(
214 "tsx",
215 vec![
216 Arc::new(typescript::TypeScriptLspAdapter::new(node_runtime.clone())),
217 Arc::new(vtsls::VtslsLspAdapter::new(node_runtime.clone()))
218 ],
219 typescript_task_context()
220 );
221 language!(
222 "typescript",
223 vec![
224 Arc::new(typescript::TypeScriptLspAdapter::new(node_runtime.clone())),
225 Arc::new(vtsls::VtslsLspAdapter::new(node_runtime.clone()))
226 ],
227 typescript_task_context()
228 );
229 language!(
230 "javascript",
231 vec![
232 Arc::new(typescript::TypeScriptLspAdapter::new(node_runtime.clone())),
233 Arc::new(vtsls::VtslsLspAdapter::new(node_runtime.clone()))
234 ],
235 typescript_task_context()
236 );
237 language!(
238 "jsdoc",
239 vec![
240 Arc::new(typescript::TypeScriptLspAdapter::new(node_runtime.clone(),)),
241 Arc::new(vtsls::VtslsLspAdapter::new(node_runtime.clone()))
242 ]
243 );
244 language!("regex");
245 language!(
246 "yaml",
247 vec![Arc::new(yaml::YamlLspAdapter::new(node_runtime.clone()))]
248 );
249
250 // Register globally available language servers.
251 //
252 // This will allow users to add support for a built-in language server (e.g., Tailwind)
253 // for a given language via the `language_servers` setting:
254 //
255 // ```json
256 // {
257 // "languages": {
258 // "My Language": {
259 // "language_servers": ["tailwindcss-language-server", "..."]
260 // }
261 // }
262 // }
263 // ```
264 languages.register_available_lsp_adapter(
265 LanguageServerName("tailwindcss-language-server".into()),
266 {
267 let node_runtime = node_runtime.clone();
268 move || Arc::new(tailwind::TailwindLspAdapter::new(node_runtime.clone()))
269 },
270 );
271 languages.register_available_lsp_adapter(LanguageServerName("eslint".into()), {
272 let node_runtime = node_runtime.clone();
273 move || Arc::new(typescript::EsLintLspAdapter::new(node_runtime.clone()))
274 });
275 languages.register_available_lsp_adapter(LanguageServerName("vtsls".into()), {
276 let node_runtime = node_runtime.clone();
277 move || Arc::new(vtsls::VtslsLspAdapter::new(node_runtime.clone()))
278 });
279 languages.register_available_lsp_adapter(
280 LanguageServerName("typescript-language-server".into()),
281 {
282 let node_runtime = node_runtime.clone();
283 move || Arc::new(typescript::TypeScriptLspAdapter::new(node_runtime.clone()))
284 },
285 );
286
287 // Register Tailwind for the existing languages that should have it by default.
288 //
289 // This can be driven by the `language_servers` setting once we have a way for
290 // extensions to provide their own default value for that setting.
291 let tailwind_languages = [
292 "Astro",
293 "CSS",
294 "ERB",
295 "HEEX",
296 "HTML",
297 "JavaScript",
298 "PHP",
299 "Svelte",
300 "TSX",
301 "Vue.js",
302 ];
303
304 for language in tailwind_languages {
305 languages.register_lsp_adapter(
306 language.into(),
307 Arc::new(tailwind::TailwindLspAdapter::new(node_runtime.clone())),
308 );
309 }
310
311 let eslint_languages = ["TSX", "TypeScript", "JavaScript", "Vue.js", "Svelte"];
312 for language in eslint_languages {
313 languages.register_lsp_adapter(
314 language.into(),
315 Arc::new(typescript::EsLintLspAdapter::new(node_runtime.clone())),
316 );
317 }
318
319 let mut subscription = languages.subscribe();
320 let mut prev_language_settings = languages.language_settings();
321
322 cx.spawn(|cx| async move {
323 while subscription.next().await.is_some() {
324 let language_settings = languages.language_settings();
325 if language_settings != prev_language_settings {
326 cx.update(|cx| {
327 SettingsStore::update_global(cx, |settings, cx| {
328 settings
329 .set_extension_settings(language_settings.clone(), cx)
330 .log_err();
331 });
332 })?;
333 prev_language_settings = language_settings;
334 }
335 }
336 anyhow::Ok(())
337 })
338 .detach();
339}
340
341#[cfg(any(test, feature = "test-support"))]
342pub fn language(name: &str, grammar: tree_sitter::Language) -> Arc<Language> {
343 Arc::new(
344 Language::new(load_config(name), Some(grammar))
345 .with_queries(load_queries(name))
346 .unwrap(),
347 )
348}
349
350fn load_config(name: &str) -> LanguageConfig {
351 let config_toml = String::from_utf8(
352 LanguageDir::get(&format!("{}/config.toml", name))
353 .unwrap_or_else(|| panic!("missing config for language {:?}", name))
354 .data
355 .to_vec(),
356 )
357 .unwrap();
358
359 #[allow(unused_mut)]
360 let mut config: LanguageConfig = ::toml::from_str(&config_toml)
361 .with_context(|| format!("failed to load config.toml for language {name:?}"))
362 .unwrap();
363
364 #[cfg(not(any(feature = "load-grammars", test)))]
365 {
366 config = LanguageConfig {
367 name: config.name,
368 matcher: config.matcher,
369 ..Default::default()
370 }
371 }
372
373 config
374}
375
376fn load_queries(name: &str) -> LanguageQueries {
377 let mut result = LanguageQueries::default();
378 for path in LanguageDir::iter() {
379 if let Some(remainder) = path.strip_prefix(name).and_then(|p| p.strip_prefix('/')) {
380 if !remainder.ends_with(".scm") {
381 continue;
382 }
383 for (name, query) in QUERY_FILENAME_PREFIXES {
384 if remainder.starts_with(name) {
385 let contents = asset_str::<LanguageDir>(path.as_ref());
386 match query(&mut result) {
387 None => *query(&mut result) = Some(contents),
388 Some(r) => r.to_mut().push_str(contents.as_ref()),
389 }
390 }
391 }
392 }
393 }
394 result
395}