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