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