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