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