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