ratatui_base16/
lib.rs

1#![doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/README.md"))]
2
3use figment::{
4    providers::{Format, Toml, Yaml},
5    Figment,
6};
7use ratatui::style::Color;
8use serde::de;
9use serde::de::Deserializer;
10use serde::{Deserialize, Serialize};
11use serde_with::serde_as;
12use std::path::PathBuf;
13use std::str::FromStr;
14use thiserror::Error;
15
16/// The `Base16PaletteError` enum represents errors that can occur while working
17/// with the Base16 color palette configuration.
18#[derive(Error, Debug)]
19#[non_exhaustive]
20pub enum Base16PaletteError {
21    /// This error occurs when the extraction of data from a file or
22    /// configuration fails.
23    ///
24    /// This variant encapsulates a `figment::Error`, indicating that it
25    /// originated from the Figment configuration library, which might be
26    /// used to handle configuration data in various formats like JSON,
27    /// TOML, YAML, etc.
28    #[error("unable to extract data from file")]
29    ExtractionFailed(#[from] figment::Error),
30}
31
32/// A `Base16Palette` defines a color palette based on the Base16 styling
33/// guidelines. These color codes are typically used to create themes for syntax
34/// highlighting, terminal emulators, and other developer tools. Each field
35/// represents a different element of the user interface that can be customized.
36/// Base16 aims to group similar language constructs with a single colour. For
37/// example, floats, ints, and doubles would belong to the same colour group.
38/// The colours for the default theme were chosen to be easily separable, but
39/// scheme designers should pick whichever colours they desire, e.g. base0B
40/// (green by default) could be replaced with red. There are, however, some
41/// general guidelines below that stipulate which base0B should be used to
42/// highlight each construct when designing templates for editors.
43///
44/// Colours base00 to base07 are typically variations of a shade and run from
45/// darkest to lightest. These colours are used for foreground and background,
46/// status bars, line highlighting and such. colours base08 to base0F are
47/// typically individual colours used for types, operators, names and variables.
48/// In order to create a dark theme, colours base00 to base07 should span from
49/// dark to light. For a light theme, these colours should span from light to
50/// dark.
51#[serde_as]
52#[derive(Debug, Clone, Deserialize, Serialize)]
53#[serde(rename_all = "snake_case")]
54pub struct Base16Palette {
55    /// Name
56    #[serde(skip, alias = "scheme")]
57    pub name: &'static str,
58
59    /// Author
60    #[serde(skip)]
61    pub author: &'static str,
62
63    /// Default Background
64    #[serde(skip)]
65    pub slug: &'static str,
66
67    /// Default Background
68    #[serde(deserialize_with = "deserialize_from_str")]
69    pub base00: Color,
70
71    /// Lighter Background (Used for status bars, line number and folding marks)
72    #[serde(deserialize_with = "deserialize_from_str")]
73    pub base01: Color,
74
75    /// Selection Background (Settings where you need to highlight text, such as
76    /// find results)
77    #[serde(deserialize_with = "deserialize_from_str")]
78    pub base02: Color,
79
80    /// Comments, Invisibles, Line Highlighting
81    #[serde(deserialize_with = "deserialize_from_str")]
82    pub base03: Color,
83
84    /// Dark Foreground (Used for status bars)
85    #[serde(deserialize_with = "deserialize_from_str")]
86    pub base04: Color,
87
88    /// Default Foreground, Caret, Delimiters, Operators
89    #[serde(deserialize_with = "deserialize_from_str")]
90    pub base05: Color,
91
92    /// Light Foreground (Not often used, could be used for hover states or
93    /// dividers)
94    #[serde(deserialize_with = "deserialize_from_str")]
95    pub base06: Color,
96
97    /// Light Background (Probably at most for cursor line background color)
98    #[serde(deserialize_with = "deserialize_from_str")]
99    pub base07: Color,
100
101    /// Variables, XML Tags, Markup Link Text, Markup Lists, Diff Deleted
102    #[serde(deserialize_with = "deserialize_from_str")]
103    pub base08: Color,
104
105    /// Integers, Boolean, Constants, XML Attributes, Markup Link Url
106    #[serde(deserialize_with = "deserialize_from_str")]
107    pub base09: Color,
108
109    /// Classes, Markup Bold, Search Text Background
110    #[serde(deserialize_with = "deserialize_from_str")]
111    pub base0a: Color,
112
113    /// Strings, Inherited Class, Markup Code, Diff Inserted
114    #[serde(deserialize_with = "deserialize_from_str")]
115    pub base0b: Color,
116
117    /// Support, Regular Expressions, Escape Characters, Markup Quotes
118    #[serde(deserialize_with = "deserialize_from_str")]
119    pub base0c: Color,
120
121    /// Functions, Methods, Attribute IDs, Headings
122    #[serde(deserialize_with = "deserialize_from_str")]
123    pub base0d: Color,
124
125    /// Keywords, Storage, Selector, Markup Bold, Diff Changed
126    #[serde(deserialize_with = "deserialize_from_str")]
127    pub base0e: Color,
128
129    /// Deprecated, Opening/Closing Embedded Language Tags, e.g. `<?php ?>
130    #[serde(deserialize_with = "deserialize_from_str")]
131    pub base0f: Color,
132}
133
134impl Default for Base16Palette {
135    fn default() -> Self {
136        Self {
137            name: "Default",
138            author: "Dheepak Krishnamurthy",
139            slug: "ratatui-base16",
140            base00: Color::Indexed(0),
141            base01: Color::Indexed(1),
142            base02: Color::Indexed(2),
143            base03: Color::Indexed(3),
144            base04: Color::Indexed(4),
145            base05: Color::Indexed(5),
146            base06: Color::Indexed(6),
147            base07: Color::Indexed(7),
148            base08: Color::Indexed(8),
149            base09: Color::Indexed(9),
150            base0a: Color::Indexed(10),
151            base0b: Color::Indexed(11),
152            base0c: Color::Indexed(12),
153            base0d: Color::Indexed(13),
154            base0e: Color::Indexed(14),
155            base0f: Color::Indexed(15),
156        }
157    }
158}
159
160impl Base16Palette {
161    /// Loads a `Base16Palette` instance from a YAML file.
162    ///
163    /// Given a file path, this function uses Figment's `Yaml` provider to read
164    /// and parse the YAML content into a `Base16Palette` instance. This
165    /// allows for loading the color palette configuration from a
166    /// YAML-formatted file.
167    ///
168    /// # Arguments
169    ///
170    /// * `file`: The file path pointing to the YAML configuration file. The
171    ///   file path type is generic and can be any type that implements
172    ///   `Into<PathBuf>`.
173    ///
174    /// # Returns
175    ///
176    /// If the function is successful, it returns `Ok(Base16Palette)`, the
177    /// loaded palette instance. If an error occurs during reading or
178    /// parsing the file, it returns a `Base16PaletteError`.
179    ///
180    /// # Examples
181    ///
182    /// ```rust
183    /// # use ratatui_base16::Base16Palette;
184    /// let palette_result = Base16Palette::from_yaml("path_to_file.yaml");
185    /// ```
186    pub fn from_yaml(file: impl Into<PathBuf>) -> Result<Self, Base16PaletteError> {
187        Figment::new()
188            .merge(Yaml::file(file.into()))
189            .extract::<Base16Palette>()
190            .map_err(Base16PaletteError::ExtractionFailed)
191    }
192
193    /// Loads a `Base16Palette` instance from a TOML file.
194    ///
195    /// Given a file path, this function uses Figment's `Toml` provider to read
196    /// and parse the TOML content into a `Base16Palette` instance. This
197    /// allows for loading the color palette configuration from a
198    /// TOML-formatted file.
199    ///
200    /// # Arguments
201    ///
202    /// * `file`: The file path pointing to the TOML configuration file. The
203    ///   file path type is generic and can be any type that implements
204    ///   `Into<PathBuf>`.
205    ///
206    /// # Returns
207    ///
208    /// If the function is successful, it returns `Ok(Base16Palette)`, the
209    /// loaded palette instance. If an error occurs during reading or
210    /// parsing the file, it returns a `Base16PaletteError`.
211    ///
212    /// # Examples
213    ///
214    /// ```rust
215    /// # use ratatui_base16::Base16Palette;
216    /// let palette_result = Base16Palette::from_toml("path_to_file.toml");
217    /// ```
218    pub fn from_toml(file: impl Into<PathBuf>) -> Result<Self, Base16PaletteError> {
219        Figment::new()
220            .merge(Toml::file(file.into()))
221            .extract::<Base16Palette>()
222            .map_err(Base16PaletteError::ExtractionFailed)
223    }
224}
225
226fn deserialize_from_str<'de, D>(deserializer: D) -> Result<Color, D::Error>
227where
228    D: Deserializer<'de>,
229{
230    let s = String::deserialize(deserializer)?;
231    if s.starts_with('#') {
232        Color::from_str(&s).map_err(de::Error::custom)
233    } else {
234        Color::from_str(&format!("#{s}")).map_err(de::Error::custom)
235    }
236}
237
238macro_rules! palette {
239    (
240        $name:ident,
241        scheme : $scheme:literal,
242        author : $author:literal,
243        slug : $slug:literal,
244        base00 : $base00:literal,
245        base01 : $base01:literal,
246        base02 : $base02:literal,
247        base03 : $base03:literal,
248        base04 : $base04:literal,
249        base05 : $base05:literal,
250        base06 : $base06:literal,
251        base07 : $base07:literal,
252        base08 : $base08:literal,
253        base09 : $base09:literal,
254        base0a : $base0a:literal,
255        base0b : $base0b:literal,
256        base0c : $base0c:literal,
257        base0d : $base0d:literal,
258        base0e : $base0e:literal,
259        base0f : $base0f:literal,
260    ) => {
261        pub const $name: $crate::Base16Palette = $crate::Base16Palette {
262            name: $scheme,
263            author: $author,
264            slug: $slug,
265            base00: ratatui::style::Color::from_u32($base00),
266            base01: ratatui::style::Color::from_u32($base01),
267            base02: ratatui::style::Color::from_u32($base02),
268            base03: ratatui::style::Color::from_u32($base03),
269            base04: ratatui::style::Color::from_u32($base04),
270            base05: ratatui::style::Color::from_u32($base05),
271            base06: ratatui::style::Color::from_u32($base06),
272            base07: ratatui::style::Color::from_u32($base07),
273            base08: ratatui::style::Color::from_u32($base08),
274            base09: ratatui::style::Color::from_u32($base09),
275            base0a: ratatui::style::Color::from_u32($base0a),
276            base0b: ratatui::style::Color::from_u32($base0b),
277            base0c: ratatui::style::Color::from_u32($base0c),
278            base0d: ratatui::style::Color::from_u32($base0d),
279            base0e: ratatui::style::Color::from_u32($base0e),
280            base0f: ratatui::style::Color::from_u32($base0f),
281        };
282    };
283}
284
285palette! {
286    CUPCAKE,
287    scheme: "Cupcake",
288    author: "Chris Kempson (http://chriskempson.com)",
289    slug: "https://github.com/chriskempson/base16-default-schemes/blob/master/cupcake.yaml",
290    base00: 0x00fbf1f2,
291    base01: 0x00f2f1f4,
292    base02: 0x00d8d5dd,
293    base03: 0x00bfb9c6,
294    base04: 0x00a59daf,
295    base05: 0x008b8198,
296    base06: 0x0072677E,
297    base07: 0x00585062,
298    base08: 0x00D57E85,
299    base09: 0x00EBB790,
300    base0a: 0x00DCB16C,
301    base0b: 0x00A3B367,
302    base0c: 0x0069A9A7,
303    base0d: 0x007297B9,
304    base0e: 0x00BB99B4,
305    base0f: 0x00BAA58C,
306}
307
308palette! {
309    DEFAULT_DARK,
310    scheme: "Default Dark",
311    author: "Chris Kempson (http://chriskempson.com)",
312    slug: "https://github.com/chriskempson/base16-default-schemes/blob/master/default-dark.yaml",
313    base00: 0x00181818,
314    base01: 0x00282828,
315    base02: 0x00383838,
316    base03: 0x00585858,
317    base04: 0x00b8b8b8,
318    base05: 0x00d8d8d8,
319    base06: 0x00e8e8e8,
320    base07: 0x00f8f8f8,
321    base08: 0x00ab4642,
322    base09: 0x00dc9656,
323    base0a: 0x00f7ca88,
324    base0b: 0x00a1b56c,
325    base0c: 0x0086c1b9,
326    base0d: 0x007cafc2,
327    base0e: 0x00ba8baf,
328    base0f: 0x00a16946,
329}
330
331palette! {
332    DEFAULT_LIGHT,
333    scheme: "Default Light",
334    author: "Chris Kempson (http://chriskempson.com)",
335    slug: "https://github.com/chriskempson/base16-default-schemes/blob/master/default-light.yaml",
336    base00: 0x00f8f8f8,
337    base01: 0x00e8e8e8,
338    base02: 0x00d8d8d8,
339    base03: 0x00b8b8b8,
340    base04: 0x00585858,
341    base05: 0x00383838,
342    base06: 0x00282828,
343    base07: 0x00181818,
344    base08: 0x00ab4642,
345    base09: 0x00dc9656,
346    base0a: 0x00f7ca88,
347    base0b: 0x00a1b56c,
348    base0c: 0x0086c1b9,
349    base0d: 0x007cafc2,
350    base0e: 0x00ba8baf,
351    base0f: 0x00a16946,
352}
353
354palette! {
355    EIGHTIES,
356    scheme: "Eighties",
357    author: "Chris Kempson (http://chriskempson.com)",
358    slug: "https://github.com/chriskempson/base16-default-schemes/blob/master/eighties.yaml",
359    base00: 0x002d2d2d,
360    base01: 0x00393939,
361    base02: 0x00515151,
362    base03: 0x00747369,
363    base04: 0x00a09f93,
364    base05: 0x00d3d0c8,
365    base06: 0x00e8e6df,
366    base07: 0x00f2f0ec,
367    base08: 0x00f2777a,
368    base09: 0x00f99157,
369    base0a: 0x00ffcc66,
370    base0b: 0x0099cc99,
371    base0c: 0x0066cccc,
372    base0d: 0x006699cc,
373    base0e: 0x00cc99cc,
374    base0f: 0x00d27b53,
375}
376
377palette! {
378    MOCHA,
379    scheme: "Mocha",
380    author: "Chris Kempson (http://chriskempson.com)",
381    slug: "https://github.com/chriskempson/base16-default-schemes/blob/master/mocha.yaml",
382    base00: 0x003b3228,
383    base01: 0x00534636,
384    base02: 0x00645240,
385    base03: 0x007e705a,
386    base04: 0x00b8afad,
387    base05: 0x00d0c8c6,
388    base06: 0x00e9e1dd,
389    base07: 0x00f5eeeb,
390    base08: 0x00cb6077,
391    base09: 0x00d28b71,
392    base0a: 0x00f4bc87,
393    base0b: 0x00beb55b,
394    base0c: 0x007bbda4,
395    base0d: 0x008ab3b5,
396    base0e: 0x00a89bb9,
397    base0f: 0x00bb9584,
398}
399
400palette! {
401    OCEAN,
402    scheme: "Ocean",
403    author: "Chris Kempson (http://chriskempson.com)",
404    slug: "https://github.com/chriskempson/base16-default-schemes/blob/master/ocean.yaml",
405    base00: 0x002b303b,
406    base01: 0x00343d46,
407    base02: 0x004f5b66,
408    base03: 0x0065737e,
409    base04: 0x00a7adba,
410    base05: 0x00c0c5ce,
411    base06: 0x00dfe1e8,
412    base07: 0x00eff1f5,
413    base08: 0x00bf616a,
414    base09: 0x00d08770,
415    base0a: 0x00ebcb8b,
416    base0b: 0x00a3be8c,
417    base0c: 0x0096b5b4,
418    base0d: 0x008fa1b3,
419    base0e: 0x00b48ead,
420    base0f: 0x00ab7967,
421}
422
423palette! {
424    DRACULA,
425    scheme: "Dracula",
426    author: "Mike Barkmin (http://github.com/mikebarkmin) based on Dracula Theme (http://github.com/dracula)",
427    slug: "https://github.com/dracula/base16-dracula-scheme/blob/master/dracula.yaml",
428    base00: 0x00282936,
429    base01: 0x003a3c4e,
430    base02: 0x004d4f68,
431    base03: 0x00626483,
432    base04: 0x0062d6e8,
433    base05: 0x00e9e9f4,
434    base06: 0x00f1f2f8,
435    base07: 0x00f7f7fb,
436    base08: 0x00ea51b2,
437    base09: 0x00b45bcf,
438    base0a: 0x0000f769,
439    base0b: 0x00ebff87,
440    base0c: 0x00a1efe4,
441    base0d: 0x0062d6e8,
442    base0e: 0x00b45bcf,
443    base0f: 0x0000f769,
444}
445
446palette! {
447    GITHUB_LIGHT,
448    scheme: "Github",
449    author: "Defman21",
450    slug: "https://github.com/Defman21/base16-github-scheme/blob/master/github.yaml",
451    base00: 0x00ffffff,
452    base01: 0x00f5f5f5,
453    base02: 0x00c8c8fa,
454    base03: 0x00969896,
455    base04: 0x00e8e8e8,
456    base05: 0x00333333,
457    base06: 0x00ffffff,
458    base07: 0x00ffffff,
459    base08: 0x00ed6a43,
460    base09: 0x000086b3,
461    base0a: 0x00795da3,
462    base0b: 0x00183691,
463    base0c: 0x00183691,
464    base0d: 0x00795da3,
465    base0e: 0x00a71d5d,
466    base0f: 0x00333333,
467}
468
469palette! {
470    ROSE_PINE_DAWN,
471    scheme: "Rosé Pine Dawn",
472    author: "Emilia Dunfelt <edun@dunfelt.se>",
473    slug: "https://github.com/edunfelt/base16-rose-pine-scheme/blob/main/rose-pine-dawn.yaml",
474    base00: 0x00faf4ed,
475    base01: 0x00fffaf3,
476    base02: 0x00f2e9de,
477    base03: 0x009893a5,
478    base04: 0x00797593,
479    base05: 0x00575279,
480    base06: 0x00575279,
481    base07: 0x00cecacd,
482    base08: 0x00b4637a,
483    base09: 0x00ea9d34,
484    base0a: 0x00d7827e,
485    base0b: 0x00286983,
486    base0c: 0x0056949f,
487    base0d: 0x00907aa9,
488    base0e: 0x00ea9d34,
489    base0f: 0x00cecacd,
490}
491
492palette! {
493    ROSE_PINE_MOON,
494    scheme: "Rosé Pine Moon",
495    author: "Emilia Dunfelt <edun@dunfelt.se>",
496    slug: "https://github.com/edunfelt/base16-rose-pine-scheme/blob/main/rose-pine-moon.yaml",
497    base00: 0x00232136,
498    base01: 0x002a273f,
499    base02: 0x00393552,
500    base03: 0x006e6a86,
501    base04: 0x00908caa,
502    base05: 0x00e0def4,
503    base06: 0x00e0def4,
504    base07: 0x0056526e,
505    base08: 0x00eb6f92,
506    base09: 0x00f6c177,
507    base0a: 0x00ea9a97,
508    base0b: 0x003e8fb0,
509    base0c: 0x009ccfd8,
510    base0d: 0x00c4a7e7,
511    base0e: 0x00f6c177,
512    base0f: 0x0056526e,
513}
514
515palette! {
516    ROSE_PINE,
517    scheme: "Rosé Pine",
518    author: "Emilia Dunfelt <edun@dunfelt.se>",
519    slug: "https://github.com/edunfelt/base16-rose-pine-scheme/blob/main/rose-pine.yaml",
520    base00: 0x00191724,
521    base01: 0x001f1d2e,
522    base02: 0x0026233a,
523    base03: 0x006e6a86,
524    base04: 0x00908caa,
525    base05: 0x00e0def4,
526    base06: 0x00e0def4,
527    base07: 0x00524f67,
528    base08: 0x00eb6f92,
529    base09: 0x00f6c177,
530    base0a: 0x00ebbcba,
531    base0b: 0x0031748f,
532    base0c: 0x009ccfd8,
533    base0d: 0x00c4a7e7,
534    base0e: 0x00f6c177,
535    base0f: 0x00524f67,
536}
537
538#[cfg(test)]
539mod tests {
540    use super::*;
541
542    #[test]
543    fn read_from_yaml() {
544        let mut file = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
545        file.push("./.config/dracula.yaml");
546        let _ = Base16Palette::from_yaml(file).unwrap();
547
548        let mut file = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
549        file.push("./.config/github.yaml");
550        let _ = Base16Palette::from_yaml(file).unwrap();
551    }
552}