tree_painter/
lib.rs

1//! tree-painter – a source code syntax highlighting library based on tree-sitter and Helix editor
2//! themes and rendering to HTML and CSS.
3//!
4//! Unlike [syntect] which uses a regex-based parser and Sublime-2-based theme definitions,
5//! tree-painter employs [tree-sitter] to parse source code quickly and correctly as well as [Helix
6//! TOML themes] to match colors and text styles with recognized language items.
7//!
8//! [syntect]: https://github.com/trishume/syntect
9//! [tree-sitter]: https://tree-sitter.github.io/tree-sitter
10//! [Helix TOML themes]: https://docs.helix-editor.com/themes.html
11//!
12//! # Usage
13//!
14//! First, you need to define which kind of language you want to highlight:
15//!
16//! ```
17//! // If you know what you are going to highlight ...
18//! let cpp_lang = tree_painter::Lang::Cpp;
19//!
20//! // ... if you don't, you can guess from the file extension.
21//! let rust_lang = tree_painter::Lang::from("file.rs").unwrap();
22//! ```
23//!
24//! Then load a Helix theme:
25//!
26//! ```no_run
27//! let data = std::fs::read_to_string("custom.toml").unwrap();
28//! let custom = tree_painter::Theme::from_helix(&data).unwrap();
29//! // or use a bundled theme
30//! let theme = tree_painter::Theme::from_helix(&tree_painter::themes::CATPPUCCIN_MOCHA).unwrap();
31//! ```
32//!
33//! Finally render the code:
34//!
35//! ```no_run
36//! # let rust_lang = tree_painter::Lang::from("file.rs").unwrap();
37//! # let data = std::fs::read_to_string("catppuccin.toml").unwrap();
38//! # let theme = tree_painter::Theme::from_helix(&data).unwrap();
39//! let mut renderer = tree_painter::Renderer::new(theme);
40//! let source =  std::fs::read_to_string("file.rs").unwrap();
41//!
42//! for line in renderer.render(&rust_lang, source.as_bytes()).unwrap() {
43//!     println!("{line}");
44//! }
45//! ```
46//!
47//! Note that each line is formatted using `<span>`s and CSS classes. In order to map the CSS
48//! classes to the theme's color include the output of [`Renderer::css()`] appropriately.
49//!
50//! # Feature flags
51//!
52//! The default feature flag enables support for all tree-sitter grammars supporting tree-sitter
53//! 0.20 as well as a couple of bundled [`themes`]. You can opt out and enable
54//! specific grammars to reduce bloat with
55//!
56//! ```toml
57//! [dependencies]
58//! tree-painter = { version = "0", default-features = false, features = ["tree-sitter-c"] }
59//! ```
60
61use std::path::Path;
62use tree_sitter_highlight::HighlightConfiguration;
63
64mod error;
65mod renderer;
66mod theme;
67
68pub use error::Error;
69pub use renderer::Renderer;
70pub use theme::Theme;
71
72#[cfg(feature = "themes")]
73/// Bundled themes for use with [`Theme::from_helix()`].
74pub mod themes {
75    /// Ayu Dark.
76    pub const AYU_DARK: &str = include_str!("themes/ayu_dark.toml");
77    /// Ayu Light.
78    pub const AYU_LIGHT: &str = include_str!("themes/ayu_light.toml");
79    /// Ayu Mirage.
80    pub const AYU_MIRAGE: &str = include_str!("themes/ayu_mirage.toml");
81    /// Catppuccin Frappe.
82    pub const CATPPUCCIN_FRAPPE: &str = include_str!("themes/catppuccin_frappe.toml");
83    /// Catppuccin Latte.
84    pub const CATPPUCCIN_LATTE: &str = include_str!("themes/catppuccin_latte.toml");
85    /// Catppuccin Macchiato.
86    pub const CATPPUCCIN_MACCHIATO: &str = include_str!("themes/catppuccin_macchiato.toml");
87    /// Catppuccin Mocha.
88    pub const CATPPUCCIN_MOCHA: &str = include_str!("themes/catppuccin_mocha.toml");
89}
90
91/// Languages supported for syntax highlighting.
92#[derive(Eq, Hash, PartialEq, Clone, Debug)]
93pub enum Lang {
94    #[cfg(feature = "tree-sitter-c")]
95    C,
96    #[cfg(feature = "tree-sitter-commonlisp")]
97    CommonLisp,
98    #[cfg(feature = "tree-sitter-cpp")]
99    Cpp,
100    // #[cfg(feature = "tree-sitter-c-sharp")]
101    // CSharp,
102    #[cfg(feature = "tree-sitter-cuda")]
103    Cuda,
104    // #[cfg(feature = "tree-sitter-go")]
105    // Go,
106    #[cfg(feature = "tree-sitter-javascript")]
107    Js,
108    // #[cfg(feature = "tree-sitter-json")]
109    // Json,
110    #[cfg(feature = "tree-sitter-python")]
111    Python,
112    #[cfg(feature = "tree-sitter-rust")]
113    Rust,
114    // #[cfg(feature = "tree-sitter-typescript")]
115    // Ts,
116}
117
118impl Lang {
119    /// Try to guess [`Lang`] from a file extension or return [`None`].
120    ///
121    /// # Examples
122    ///
123    /// ```
124    /// let lang = tree_painter::Lang::from("file.rs");
125    /// assert!(matches!(Some(tree_painter::Lang::Rust), lang));
126    ///
127    /// let lang = tree_painter::Lang::from("file.bin");
128    /// assert_eq!(lang, None);
129    /// ```
130    pub fn from<T: AsRef<Path>>(path: T) -> Option<Self> {
131        path.as_ref()
132            .extension()
133            .and_then(|e| e.to_str())
134            .and_then(|e| match e {
135                #[cfg(feature = "tree-sitter-c")]
136                "c" => Some(Lang::C),
137                // #[cfg(feature = "tree-sitter-c-sharp")]
138                // "cs" => Some(Lang::CSharp),
139                #[cfg(feature = "tree-sitter-commonlisp")]
140                "lisp" | "lsp" | "l" | "cl" => Some(Lang::CommonLisp),
141                #[cfg(feature = "tree-sitter-cpp")]
142                "cpp" | "cc" | "cxx" => Some(Lang::Cpp),
143                #[cfg(feature = "tree-sitter-cuda")]
144                "cu" => Some(Lang::Cuda),
145                // #[cfg(feature = "tree-sitter-go")]
146                // "go" => Some(Lang::Go),
147                #[cfg(feature = "tree-sitter-javascript")]
148                "js" => Some(Lang::Js),
149                // #[cfg(feature = "tree-sitter-json")]
150                // "json" => Some(Lang::Json),
151                #[cfg(feature = "tree-sitter-python")]
152                "py" => Some(Lang::Python),
153                #[cfg(feature = "tree-sitter-rust")]
154                "rs" => Some(Lang::Rust),
155                // #[cfg(feature = "tree-sitter-typescript")]
156                // "ts" => Some(Lang::Ts),
157                &_ => None,
158            })
159    }
160
161    fn config(&self) -> HighlightConfiguration {
162        match self {
163            #[cfg(feature = "tree-sitter-c")]
164            Lang::C => HighlightConfiguration::new(
165                tree_sitter_c::language(),
166                tree_sitter_c::HIGHLIGHT_QUERY,
167                "",
168                "",
169            )
170            .expect("loading tree-sitter-c"),
171            // #[cfg(feature = "tree-sitter-c-sharp")]
172            // Lang::CSharp => {
173            //     HighlightConfiguration::new(tree_sitter_c_sharp::language(), "", "", "")
174            //         .expect("loading tree-sitter-c-sharp")
175            // }
176            #[cfg(feature = "tree-sitter-commonlisp")]
177            Lang::CommonLisp => {
178                HighlightConfiguration::new(tree_sitter_commonlisp::language(), "", "", "")
179                    .expect("loading tree-sitter-commonlisp")
180            }
181            #[cfg(feature = "tree-sitter-cpp")]
182            Lang::Cpp => HighlightConfiguration::new(
183                tree_sitter_cpp::language(),
184                tree_sitter_cpp::HIGHLIGHT_QUERY,
185                "",
186                "",
187            )
188            .expect("loading tree-sitter-cpp"),
189            #[cfg(feature = "tree-sitter-cuda")]
190            Lang::Cuda => HighlightConfiguration::new(tree_sitter_cuda::language(), "", "", "")
191                .expect("loading tree-sitter-cuda"),
192            // #[cfg(feature = "tree-sitter-go")]
193            // Lang::Go => HighlightConfiguration::new(
194            //     tree_sitter_go::language(),
195            //     tree_sitter_go::HIGHLIGHT_QUERY,
196            //     "",
197            //     "",
198            // )
199            // .expect("loading tree-sitter-cpp"),
200            #[cfg(feature = "tree-sitter-javascript")]
201            Lang::Js => HighlightConfiguration::new(
202                tree_sitter_javascript::language(),
203                tree_sitter_javascript::HIGHLIGHT_QUERY,
204                tree_sitter_javascript::INJECTION_QUERY,
205                tree_sitter_javascript::LOCALS_QUERY,
206            )
207            .expect("loading tree-sitter-javascript"),
208            // #[cfg(feature = "tree-sitter-json")]
209            // Lang::Json => HighlightConfiguration::new(
210            //     tree_sitter_json::language(),
211            //     tree_sitter_json::HIGHLIGHT_QUERY,
212            //     "",
213            //     "",
214            // )
215            // .expect("loading tree-sitter-json"),
216            #[cfg(feature = "tree-sitter-python")]
217            Lang::Python => HighlightConfiguration::new(
218                tree_sitter_python::language(),
219                tree_sitter_python::HIGHLIGHT_QUERY,
220                "",
221                "",
222            )
223            .expect("loading tree-sitter-cpp"),
224            #[cfg(feature = "tree-sitter-rust")]
225            Lang::Rust => HighlightConfiguration::new(
226                tree_sitter_rust::language(),
227                tree_sitter_rust::HIGHLIGHT_QUERY,
228                "",
229                "",
230            )
231            .expect("loading tree-sitter-rust"),
232            // Lang::Ts => HighlightConfiguration::new(
233            //     tree_sitter_typescript::language_typescript(),
234            //     tree_sitter_typescript::HIGHLIGHT_QUERY,
235            //     tree_sitter_typescript::LOCALS_QUERY,
236            //     "",
237            // )
238            // .expect("loading tree-sitter-typescript"),
239        }
240    }
241}