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
276 // Register Tailwind for the existing languages that should have it by default.
277 //
278 // This can be driven by the `language_servers` setting once we have a way for
279 // extensions to provide their own default value for that setting.
280 let tailwind_languages = [
281 "Astro",
282 "CSS",
283 "ERB",
284 "HEEX",
285 "HTML",
286 "JavaScript",
287 "PHP",
288 "Svelte",
289 "TSX",
290 "Vue.js",
291 ];
292
293 for language in tailwind_languages {
294 languages.register_lsp_adapter(
295 language.into(),
296 Arc::new(tailwind::TailwindLspAdapter::new(node_runtime.clone())),
297 );
298 }
299
300 let eslint_languages = ["TSX", "TypeScript", "JavaScript", "Vue.js", "Svelte"];
301 for language in eslint_languages {
302 languages.register_lsp_adapter(
303 language.into(),
304 Arc::new(typescript::EsLintLspAdapter::new(node_runtime.clone())),
305 );
306 }
307
308 let mut subscription = languages.subscribe();
309 let mut prev_language_settings = languages.language_settings();
310
311 cx.spawn(|cx| async move {
312 while subscription.next().await.is_some() {
313 let language_settings = languages.language_settings();
314 if language_settings != prev_language_settings {
315 cx.update(|cx| {
316 SettingsStore::update_global(cx, |settings, cx| {
317 settings
318 .set_extension_settings(language_settings.clone(), cx)
319 .log_err();
320 });
321 })?;
322 prev_language_settings = language_settings;
323 }
324 }
325 anyhow::Ok(())
326 })
327 .detach();
328}
329
330#[cfg(any(test, feature = "test-support"))]
331pub fn language(name: &str, grammar: tree_sitter::Language) -> Arc<Language> {
332 Arc::new(
333 Language::new(load_config(name), Some(grammar))
334 .with_queries(load_queries(name))
335 .unwrap(),
336 )
337}
338
339fn load_config(name: &str) -> LanguageConfig {
340 let config_toml = String::from_utf8(
341 LanguageDir::get(&format!("{}/config.toml", name))
342 .unwrap_or_else(|| panic!("missing config for language {:?}", name))
343 .data
344 .to_vec(),
345 )
346 .unwrap();
347
348 #[allow(unused_mut)]
349 let mut config: LanguageConfig = ::toml::from_str(&config_toml)
350 .with_context(|| format!("failed to load config.toml for language {name:?}"))
351 .unwrap();
352
353 #[cfg(not(any(feature = "load-grammars", test)))]
354 {
355 config = LanguageConfig {
356 name: config.name,
357 matcher: config.matcher,
358 ..Default::default()
359 }
360 }
361
362 config
363}
364
365fn load_queries(name: &str) -> LanguageQueries {
366 let mut result = LanguageQueries::default();
367 for path in LanguageDir::iter() {
368 if let Some(remainder) = path.strip_prefix(name).and_then(|p| p.strip_prefix('/')) {
369 if !remainder.ends_with(".scm") {
370 continue;
371 }
372 for (name, query) in QUERY_FILENAME_PREFIXES {
373 if remainder.starts_with(name) {
374 let contents = asset_str::<LanguageDir>(path.as_ref());
375 match query(&mut result) {
376 None => *query(&mut result) = Some(contents),
377 Some(r) => r.to_mut().push_str(contents.as_ref()),
378 }
379 }
380 }
381 }
382 }
383 result
384}