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    languages.register_available_lsp_adapter(LanguageServerName("vtsls".into()), {
276        let node_runtime = node_runtime.clone();
277        move || Arc::new(vtsls::VtslsLspAdapter::new(node_runtime.clone()))
278    });
279    languages.register_available_lsp_adapter(
280        LanguageServerName("typescript-language-server".into()),
281        {
282            let node_runtime = node_runtime.clone();
283            move || Arc::new(typescript::TypeScriptLspAdapter::new(node_runtime.clone()))
284        },
285    );
286
287    // Register Tailwind for the existing languages that should have it by default.
288    //
289    // This can be driven by the `language_servers` setting once we have a way for
290    // extensions to provide their own default value for that setting.
291    let tailwind_languages = [
292        "Astro",
293        "CSS",
294        "ERB",
295        "HEEX",
296        "HTML",
297        "JavaScript",
298        "PHP",
299        "Svelte",
300        "TSX",
301        "Vue.js",
302    ];
303
304    for language in tailwind_languages {
305        languages.register_lsp_adapter(
306            language.into(),
307            Arc::new(tailwind::TailwindLspAdapter::new(node_runtime.clone())),
308        );
309    }
310
311    let eslint_languages = ["TSX", "TypeScript", "JavaScript", "Vue.js", "Svelte"];
312    for language in eslint_languages {
313        languages.register_lsp_adapter(
314            language.into(),
315            Arc::new(typescript::EsLintLspAdapter::new(node_runtime.clone())),
316        );
317    }
318
319    let mut subscription = languages.subscribe();
320    let mut prev_language_settings = languages.language_settings();
321
322    cx.spawn(|cx| async move {
323        while subscription.next().await.is_some() {
324            let language_settings = languages.language_settings();
325            if language_settings != prev_language_settings {
326                cx.update(|cx| {
327                    SettingsStore::update_global(cx, |settings, cx| {
328                        settings
329                            .set_extension_settings(language_settings.clone(), cx)
330                            .log_err();
331                    });
332                })?;
333                prev_language_settings = language_settings;
334            }
335        }
336        anyhow::Ok(())
337    })
338    .detach();
339}
340
341#[cfg(any(test, feature = "test-support"))]
342pub fn language(name: &str, grammar: tree_sitter::Language) -> Arc<Language> {
343    Arc::new(
344        Language::new(load_config(name), Some(grammar))
345            .with_queries(load_queries(name))
346            .unwrap(),
347    )
348}
349
350fn load_config(name: &str) -> LanguageConfig {
351    let config_toml = String::from_utf8(
352        LanguageDir::get(&format!("{}/config.toml", name))
353            .unwrap_or_else(|| panic!("missing config for language {:?}", name))
354            .data
355            .to_vec(),
356    )
357    .unwrap();
358
359    #[allow(unused_mut)]
360    let mut config: LanguageConfig = ::toml::from_str(&config_toml)
361        .with_context(|| format!("failed to load config.toml for language {name:?}"))
362        .unwrap();
363
364    #[cfg(not(any(feature = "load-grammars", test)))]
365    {
366        config = LanguageConfig {
367            name: config.name,
368            matcher: config.matcher,
369            ..Default::default()
370        }
371    }
372
373    config
374}
375
376fn load_queries(name: &str) -> LanguageQueries {
377    let mut result = LanguageQueries::default();
378    for path in LanguageDir::iter() {
379        if let Some(remainder) = path.strip_prefix(name).and_then(|p| p.strip_prefix('/')) {
380            if !remainder.ends_with(".scm") {
381                continue;
382            }
383            for (name, query) in QUERY_FILENAME_PREFIXES {
384                if remainder.starts_with(name) {
385                    let contents = asset_str::<LanguageDir>(path.as_ref());
386                    match query(&mut result) {
387                        None => *query(&mut result) = Some(contents),
388                        Some(r) => r.to_mut().push_str(contents.as_ref()),
389                    }
390                }
391            }
392        }
393    }
394    result
395}