lib.rs

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