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, go::GoContextProvider, 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    macro_rules! language {
 78        ($name:literal) => {
 79            let config = load_config($name);
 80            languages.register_language(
 81                config.name.clone(),
 82                config.grammar.clone(),
 83                config.matcher.clone(),
 84                config.hidden,
 85                Arc::new(move || {
 86                    Ok(LoadedLanguage {
 87                        config: config.clone(),
 88                        queries: load_queries($name),
 89                        context_provider: None,
 90                        toolchain_provider: None,
 91                    })
 92                }),
 93            );
 94        };
 95        ($name:literal, $adapters:expr) => {
 96            let config = load_config($name);
 97            // typeck helper
 98            let adapters: Vec<Arc<dyn LspAdapter>> = $adapters;
 99            for adapter in adapters {
100                languages.register_lsp_adapter(config.name.clone(), adapter);
101            }
102            languages.register_language(
103                config.name.clone(),
104                config.grammar.clone(),
105                config.matcher.clone(),
106                config.hidden,
107                Arc::new(move || {
108                    Ok(LoadedLanguage {
109                        config: config.clone(),
110                        queries: load_queries($name),
111                        context_provider: None,
112                        toolchain_provider: None,
113                    })
114                }),
115            );
116        };
117        ($name:literal, $adapters:expr, $context_provider:expr) => {
118            let config = load_config($name);
119            // typeck helper
120            let adapters: Vec<Arc<dyn LspAdapter>> = $adapters;
121            for adapter in adapters {
122                languages.register_lsp_adapter(config.name.clone(), adapter);
123            }
124            languages.register_language(
125                config.name.clone(),
126                config.grammar.clone(),
127                config.matcher.clone(),
128                config.hidden,
129                Arc::new(move || {
130                    Ok(LoadedLanguage {
131                        config: config.clone(),
132                        queries: load_queries($name),
133                        context_provider: Some(Arc::new($context_provider)),
134                        toolchain_provider: None,
135                    })
136                }),
137            );
138        };
139        ($name:literal, $adapters:expr, $context_provider:expr, $toolchain_provider:expr) => {
140            let config = load_config($name);
141            // typeck helper
142            let adapters: Vec<Arc<dyn LspAdapter>> = $adapters;
143            for adapter in adapters {
144                languages.register_lsp_adapter(config.name.clone(), adapter);
145            }
146            languages.register_language(
147                config.name.clone(),
148                config.grammar.clone(),
149                config.matcher.clone(),
150                config.hidden,
151                Arc::new(move || {
152                    Ok(LoadedLanguage {
153                        config: config.clone(),
154                        queries: load_queries($name),
155                        context_provider: Some(Arc::new($context_provider)),
156                        toolchain_provider: Some($toolchain_provider),
157                    })
158                }),
159            );
160        };
161    }
162    language!("bash", Vec::new(), bash_task_context());
163    language!("c", vec![Arc::new(c::CLspAdapter) as Arc<dyn LspAdapter>]);
164    language!("cpp", vec![Arc::new(c::CLspAdapter)]);
165    language!(
166        "css",
167        vec![Arc::new(css::CssLspAdapter::new(node_runtime.clone())),]
168    );
169    language!("diff");
170    language!("go", vec![Arc::new(go::GoLspAdapter)], GoContextProvider);
171    language!("gomod", vec![Arc::new(go::GoLspAdapter)], GoContextProvider);
172    language!(
173        "gowork",
174        vec![Arc::new(go::GoLspAdapter)],
175        GoContextProvider
176    );
177
178    language!(
179        "json",
180        vec![
181            Arc::new(json::JsonLspAdapter::new(
182                node_runtime.clone(),
183                languages.clone(),
184            )),
185            Arc::new(json::NodeVersionAdapter)
186        ],
187        json_task_context()
188    );
189    language!(
190        "jsonc",
191        vec![Arc::new(json::JsonLspAdapter::new(
192            node_runtime.clone(),
193            languages.clone(),
194        ))],
195        json_task_context()
196    );
197    language!("markdown");
198    language!("markdown-inline");
199    language!(
200        "python",
201        vec![
202            Arc::new(python::PythonLspAdapter::new(node_runtime.clone(),)),
203            Arc::new(python::PyLspAdapter::new())
204        ],
205        PythonContextProvider,
206        Arc::new(PythonToolchainProvider::default()) as Arc<dyn ToolchainLister>
207    );
208    language!(
209        "rust",
210        vec![Arc::new(rust::RustLspAdapter)],
211        RustContextProvider
212    );
213    language!(
214        "tsx",
215        vec![
216            Arc::new(typescript::TypeScriptLspAdapter::new(node_runtime.clone())),
217            Arc::new(vtsls::VtslsLspAdapter::new(node_runtime.clone()))
218        ],
219        typescript_task_context()
220    );
221    language!(
222        "typescript",
223        vec![
224            Arc::new(typescript::TypeScriptLspAdapter::new(node_runtime.clone())),
225            Arc::new(vtsls::VtslsLspAdapter::new(node_runtime.clone()))
226        ],
227        typescript_task_context()
228    );
229    language!(
230        "javascript",
231        vec![
232            Arc::new(typescript::TypeScriptLspAdapter::new(node_runtime.clone())),
233            Arc::new(vtsls::VtslsLspAdapter::new(node_runtime.clone()))
234        ],
235        typescript_task_context()
236    );
237    language!(
238        "jsdoc",
239        vec![
240            Arc::new(typescript::TypeScriptLspAdapter::new(node_runtime.clone(),)),
241            Arc::new(vtsls::VtslsLspAdapter::new(node_runtime.clone()))
242        ]
243    );
244    language!("regex");
245    language!(
246        "yaml",
247        vec![Arc::new(yaml::YamlLspAdapter::new(node_runtime.clone()))]
248    );
249
250    // Register globally available language servers.
251    //
252    // This will allow users to add support for a built-in language server (e.g., Tailwind)
253    // for a given language via the `language_servers` setting:
254    //
255    // ```json
256    // {
257    //   "languages": {
258    //     "My Language": {
259    //       "language_servers": ["tailwindcss-language-server", "..."]
260    //     }
261    //   }
262    // }
263    // ```
264    languages.register_available_lsp_adapter(
265        LanguageServerName("tailwindcss-language-server".into()),
266        {
267            let node_runtime = node_runtime.clone();
268            move || Arc::new(tailwind::TailwindLspAdapter::new(node_runtime.clone()))
269        },
270    );
271    languages.register_available_lsp_adapter(LanguageServerName("eslint".into()), {
272        let node_runtime = node_runtime.clone();
273        move || Arc::new(typescript::EsLintLspAdapter::new(node_runtime.clone()))
274    });
275
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        "HEEX",
285        "HTML",
286        "JavaScript",
287        "PHP",
288        "Svelte",
289        "TSX",
290        "Vue.js",
291    ];
292
293    for language in tailwind_languages {
294        languages.register_lsp_adapter(
295            language.into(),
296            Arc::new(tailwind::TailwindLspAdapter::new(node_runtime.clone())),
297        );
298    }
299
300    let eslint_languages = ["TSX", "TypeScript", "JavaScript", "Vue.js", "Svelte"];
301    for language in eslint_languages {
302        languages.register_lsp_adapter(
303            language.into(),
304            Arc::new(typescript::EsLintLspAdapter::new(node_runtime.clone())),
305        );
306    }
307
308    let mut subscription = languages.subscribe();
309    let mut prev_language_settings = languages.language_settings();
310
311    cx.spawn(|cx| async move {
312        while subscription.next().await.is_some() {
313            let language_settings = languages.language_settings();
314            if language_settings != prev_language_settings {
315                cx.update(|cx| {
316                    SettingsStore::update_global(cx, |settings, cx| {
317                        settings
318                            .set_extension_settings(language_settings.clone(), cx)
319                            .log_err();
320                    });
321                })?;
322                prev_language_settings = language_settings;
323            }
324        }
325        anyhow::Ok(())
326    })
327    .detach();
328}
329
330#[cfg(any(test, feature = "test-support"))]
331pub fn language(name: &str, grammar: tree_sitter::Language) -> Arc<Language> {
332    Arc::new(
333        Language::new(load_config(name), Some(grammar))
334            .with_queries(load_queries(name))
335            .unwrap(),
336    )
337}
338
339fn load_config(name: &str) -> LanguageConfig {
340    let config_toml = String::from_utf8(
341        LanguageDir::get(&format!("{}/config.toml", name))
342            .unwrap_or_else(|| panic!("missing config for language {:?}", name))
343            .data
344            .to_vec(),
345    )
346    .unwrap();
347
348    #[allow(unused_mut)]
349    let mut config: LanguageConfig = ::toml::from_str(&config_toml)
350        .with_context(|| format!("failed to load config.toml for language {name:?}"))
351        .unwrap();
352
353    #[cfg(not(any(feature = "load-grammars", test)))]
354    {
355        config = LanguageConfig {
356            name: config.name,
357            matcher: config.matcher,
358            ..Default::default()
359        }
360    }
361
362    config
363}
364
365fn load_queries(name: &str) -> LanguageQueries {
366    let mut result = LanguageQueries::default();
367    for path in LanguageDir::iter() {
368        if let Some(remainder) = path.strip_prefix(name).and_then(|p| p.strip_prefix('/')) {
369            if !remainder.ends_with(".scm") {
370                continue;
371            }
372            for (name, query) in QUERY_FILENAME_PREFIXES {
373                if remainder.starts_with(name) {
374                    let contents = asset_str::<LanguageDir>(path.as_ref());
375                    match query(&mut result) {
376                        None => *query(&mut result) = Some(contents),
377                        Some(r) => r.to_mut().push_str(contents.as_ref()),
378                    }
379                }
380            }
381        }
382    }
383    result
384}