Skip to main content

ae_tree_sitter_bundle/
lib.rs

1//! # tree-sitter-bundle
2//!
3//! A collection of [tree-sitter](https://tree-sitter.github.io) parsers compiled
4//! into a single crate, with their highlight queries, ready for editor integration.
5//!
6//! Each grammar is gated behind a Cargo feature, so you only pay for the languages
7//! you enable. Enable everything with the `full` feature, or pick a subset:
8//!
9//! ```toml
10//! tree-sitter-bundle = { version = "0.1", features = ["rust", "python", "json"] }
11//! ```
12//!
13//! ## Parsing
14//!
15//! ```no_run
16//! let lang = tree_sitter_bundle::get("rust").unwrap();
17//! let mut parser = tree_sitter::Parser::new();
18//! parser.set_language(&lang.language()).unwrap();
19//! let tree = parser.parse("fn main() {}", None).unwrap();
20//! assert_eq!(tree.root_node().kind(), "source_file");
21//! ```
22//!
23//! ## Highlighting (requires the `highlight` feature, on by default)
24//!
25//! ```no_run
26//! # #[cfg(feature = "highlight")] {
27//! use tree_sitter_highlight::{Highlighter, HighlightEvent};
28//!
29//! let names = ["keyword", "function", "string", "type", "variable"]
30//!     .map(String::from);
31//! let lang = tree_sitter_bundle::get("rust").unwrap();
32//! let config = lang.highlight_config(&names).unwrap();
33//!
34//! let mut hl = Highlighter::new();
35//! let src = b"fn main() {}";
36//! for event in hl.highlight(&config, src, None, |_| None).unwrap() {
37//!     match event.unwrap() {
38//!         HighlightEvent::Source { start, end } => { /* emit text src[start..end] */ }
39//!         HighlightEvent::HighlightStart(h) => { /* open span names[h.0] */ }
40//!         HighlightEvent::HighlightEnd => { /* close span */ }
41//!     }
42//! }
43//! # }
44//! ```
45
46mod generated {
47    include!(concat!(env!("OUT_DIR"), "/generated.rs"));
48}
49
50use generated::{RawLanguage, RAW_LANGUAGES};
51
52pub use tree_sitter;
53#[cfg(feature = "highlight")]
54pub use tree_sitter_highlight;
55
56use tree_sitter::Language;
57
58/// A single bundled language: its grammar plus its bundled queries.
59#[derive(Clone, Copy)]
60pub struct LanguageEntry {
61    raw: &'static RawLanguage,
62}
63
64impl LanguageEntry {
65    /// The language identifier, e.g. `"rust"`, `"tsx"`, `"markdown_inline"`.
66    pub fn name(&self) -> &'static str {
67        self.raw.name
68    }
69
70    /// File extensions associated with this language (without the leading dot).
71    pub fn extensions(&self) -> &'static [&'static str] {
72        self.raw.extensions
73    }
74
75    /// The tree-sitter [`Language`] for this grammar.
76    pub fn language(&self) -> Language {
77        self.raw.language.into()
78    }
79
80    /// The bundled `highlights.scm` query source (may be empty).
81    pub fn highlights_query(&self) -> &'static str {
82        self.raw.highlights
83    }
84
85    /// The bundled `injections.scm` query source (may be empty).
86    pub fn injections_query(&self) -> &'static str {
87        self.raw.injections
88    }
89
90    /// The bundled `locals.scm` query source (may be empty).
91    pub fn locals_query(&self) -> &'static str {
92        self.raw.locals
93    }
94
95    /// Build a ready-to-use [`HighlightConfiguration`] from the bundled queries.
96    ///
97    /// `recognized_names` is the list of capture names your renderer understands
98    /// (e.g. `["keyword", "function", "string"]`); the configuration is wired so
99    /// that [`Highlight`] indices map into this slice.
100    ///
101    /// [`HighlightConfiguration`]: tree_sitter_highlight::HighlightConfiguration
102    /// [`Highlight`]: tree_sitter_highlight::Highlight
103    #[cfg(feature = "highlight")]
104    pub fn highlight_config(
105        &self,
106        recognized_names: &[String],
107    ) -> Result<tree_sitter_highlight::HighlightConfiguration, tree_sitter::QueryError> {
108        let mut config = tree_sitter_highlight::HighlightConfiguration::new(
109            self.language(),
110            self.name(),
111            self.highlights_query(),
112            self.injections_query(),
113            self.locals_query(),
114        )?;
115        config.configure(recognized_names);
116        Ok(config)
117    }
118}
119
120/// Iterate over every compiled language in the bundle.
121pub fn languages() -> impl Iterator<Item = LanguageEntry> {
122    RAW_LANGUAGES.iter().map(|raw| LanguageEntry { raw })
123}
124
125/// Number of compiled languages.
126pub fn count() -> usize {
127    RAW_LANGUAGES.len()
128}
129
130/// Look up a language by its identifier (case-sensitive), e.g. `get("rust")`.
131pub fn get(name: &str) -> Option<LanguageEntry> {
132    RAW_LANGUAGES
133        .iter()
134        .find(|raw| raw.name == name)
135        .map(|raw| LanguageEntry { raw })
136}
137
138/// Look up a language by file extension (with or without a leading dot).
139///
140/// Returns the first language that claims the extension. For ambiguous
141/// extensions you'll usually want your own filetype table on top of this.
142pub fn from_extension(ext: &str) -> Option<LanguageEntry> {
143    let ext = ext.trim_start_matches('.');
144    RAW_LANGUAGES
145        .iter()
146        .find(|raw| raw.extensions.iter().any(|e| e.eq_ignore_ascii_case(ext)))
147        .map(|raw| LanguageEntry { raw })
148}
149
150/// Look up a language by file path, using its extension.
151pub fn from_path(path: impl AsRef<std::path::Path>) -> Option<LanguageEntry> {
152    path.as_ref()
153        .extension()
154        .and_then(|e| e.to_str())
155        .and_then(from_extension)
156}