grammars.rs

  1use anyhow::Context as _;
  2use language_core::{LanguageConfig, LanguageQueries, QUERY_FILENAME_PREFIXES};
  3use rust_embed::RustEmbed;
  4use util::asset_str;
  5
  6#[derive(RustEmbed)]
  7#[folder = "src/"]
  8#[exclude = "*.rs"]
  9struct GrammarDir;
 10
 11/// Register all built-in native tree-sitter grammars with the provided registration function.
 12///
 13/// Each grammar is registered as a `(&str, tree_sitter_language::LanguageFn)` pair.
 14/// This must be called before loading language configs/queries.
 15#[cfg(feature = "load-grammars")]
 16pub fn native_grammars() -> Vec<(&'static str, tree_sitter::Language)> {
 17    vec![
 18        ("bash", tree_sitter_bash::LANGUAGE.into()),
 19        ("c", tree_sitter_c::LANGUAGE.into()),
 20        ("cpp", tree_sitter_cpp::LANGUAGE.into()),
 21        ("css", tree_sitter_css::LANGUAGE.into()),
 22        ("diff", tree_sitter_diff::LANGUAGE.into()),
 23        ("go", tree_sitter_go::LANGUAGE.into()),
 24        ("gomod", tree_sitter_go_mod::LANGUAGE.into()),
 25        ("gowork", tree_sitter_gowork::LANGUAGE.into()),
 26        ("jsdoc", tree_sitter_jsdoc::LANGUAGE.into()),
 27        ("json", tree_sitter_json::LANGUAGE.into()),
 28        ("jsonc", tree_sitter_json::LANGUAGE.into()),
 29        ("markdown", tree_sitter_md::LANGUAGE.into()),
 30        ("markdown-inline", tree_sitter_md::INLINE_LANGUAGE.into()),
 31        ("python", tree_sitter_python::LANGUAGE.into()),
 32        ("regex", tree_sitter_regex::LANGUAGE.into()),
 33        ("rust", tree_sitter_rust::LANGUAGE.into()),
 34        ("tsx", tree_sitter_typescript::LANGUAGE_TSX.into()),
 35        (
 36            "typescript",
 37            tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into(),
 38        ),
 39        ("yaml", tree_sitter_yaml::LANGUAGE.into()),
 40        ("gitcommit", tree_sitter_gitcommit::LANGUAGE.into()),
 41    ]
 42}
 43
 44/// Load and parse the `config.toml` for a given language name.
 45pub fn load_config(name: &str) -> LanguageConfig {
 46    let config_toml = String::from_utf8(
 47        GrammarDir::get(&format!("{}/config.toml", name))
 48            .unwrap_or_else(|| panic!("missing config for language {:?}", name))
 49            .data
 50            .to_vec(),
 51    )
 52    .unwrap();
 53
 54    let config: LanguageConfig = ::toml::from_str(&config_toml)
 55        .with_context(|| format!("failed to load config.toml for language {name:?}"))
 56        .unwrap();
 57
 58    config
 59}
 60
 61/// Load and parse the `config.toml` for a given language name, stripping fields
 62/// that require grammar support when grammars are not loaded.
 63pub fn load_config_for_feature(name: &str, grammars_loaded: bool) -> LanguageConfig {
 64    let config = load_config(name);
 65
 66    if grammars_loaded {
 67        config
 68    } else {
 69        LanguageConfig {
 70            name: config.name,
 71            matcher: config.matcher,
 72            jsx_tag_auto_close: config.jsx_tag_auto_close,
 73            ..Default::default()
 74        }
 75    }
 76}
 77
 78/// Get a raw embedded file by path (relative to `src/`).
 79///
 80/// Returns the file data as bytes, or `None` if the file does not exist.
 81pub fn get_file(path: &str) -> Option<rust_embed::EmbeddedFile> {
 82    GrammarDir::get(path)
 83}
 84
 85/// Load all `.scm` query files for a given language name into a `LanguageQueries`.
 86///
 87/// Multiple `.scm` files with the same prefix (e.g. `highlights.scm` and
 88/// `highlights_extra.scm`) are concatenated together with their contents appended.
 89pub fn load_queries(name: &str) -> LanguageQueries {
 90    let mut result = LanguageQueries::default();
 91    for path in GrammarDir::iter() {
 92        if let Some(remainder) = path.strip_prefix(name).and_then(|p| p.strip_prefix('/')) {
 93            if !remainder.ends_with(".scm") {
 94                continue;
 95            }
 96            for (prefix, query) in QUERY_FILENAME_PREFIXES {
 97                if remainder.starts_with(prefix) {
 98                    let contents = asset_str::<GrammarDir>(path.as_ref());
 99                    match query(&mut result) {
100                        None => *query(&mut result) = Some(contents),
101                        Some(existing) => existing.to_mut().push_str(contents.as_ref()),
102                    }
103                }
104            }
105        }
106    }
107    result
108}