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}