lib.rs

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