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