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