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}