1 use super::theme::Theme; 2 use super::settings::*; 3 use super::super::LoadingError; 4 use std::collections::BTreeMap; 5 use std::path::{Path, PathBuf}; 6 use std::io::{BufReader, BufRead, Seek}; 7 use walkdir::WalkDir; 8 use std::fs::File; 9 10 #[derive(Debug, Default, Serialize, Deserialize)] 11 pub struct ThemeSet { 12 // This is a `BTreeMap` because they're faster than hashmaps on small sets 13 pub themes: BTreeMap<String, Theme>, 14 } 15 16 /// A set of themes, includes convenient methods for loading and discovering themes. 17 impl ThemeSet { 18 /// Creates an empty set new() -> ThemeSet19 pub fn new() -> ThemeSet { 20 ThemeSet::default() 21 } 22 23 /// Returns all the themes found in a folder 24 /// 25 /// This is god for enumerating before loading one with [`get_theme`](#method.get_theme) discover_theme_paths<P: AsRef<Path>>(folder: P) -> Result<Vec<PathBuf>, LoadingError>26 pub fn discover_theme_paths<P: AsRef<Path>>(folder: P) -> Result<Vec<PathBuf>, LoadingError> { 27 let mut themes = Vec::new(); 28 for entry in WalkDir::new(folder) { 29 let entry = entry.map_err(LoadingError::WalkDir)?; 30 if entry.path().extension().map_or(false, |e| e == "tmTheme") { 31 themes.push(entry.path().to_owned()); 32 } 33 } 34 Ok(themes) 35 } 36 37 /// Loads a theme given a path to a .tmTheme file get_theme<P: AsRef<Path>>(path: P) -> Result<Theme, LoadingError>38 pub fn get_theme<P: AsRef<Path>>(path: P) -> Result<Theme, LoadingError> { 39 let file = File::open(path)?; 40 let mut file = BufReader::new(file); 41 Self::load_from_reader(&mut file) 42 } 43 44 /// Loads a theme given a readable stream load_from_reader<R: BufRead + Seek>(r: &mut R) -> Result<Theme, LoadingError>45 pub fn load_from_reader<R: BufRead + Seek>(r: &mut R) -> Result<Theme, LoadingError> { 46 Ok(Theme::parse_settings(read_plist(r)?)?) 47 } 48 49 /// Generate a `ThemeSet` from all themes in a folder load_from_folder<P: AsRef<Path>>(folder: P) -> Result<ThemeSet, LoadingError>50 pub fn load_from_folder<P: AsRef<Path>>(folder: P) -> Result<ThemeSet, LoadingError> { 51 let mut theme_set = Self::new(); 52 theme_set.add_from_folder(folder)?; 53 Ok(theme_set) 54 } 55 56 /// Load all the themes in the folder into this `ThemeSet` add_from_folder<P: AsRef<Path>>(&mut self, folder: P) -> Result<(), LoadingError>57 pub fn add_from_folder<P: AsRef<Path>>(&mut self, folder: P) -> Result<(), LoadingError> { 58 let paths = Self::discover_theme_paths(folder)?; 59 for p in &paths { 60 let theme = Self::get_theme(p)?; 61 let basename = 62 p.file_stem().and_then(|x| x.to_str()).ok_or(LoadingError::BadPath)?; 63 self.themes.insert(basename.to_owned(), theme); 64 } 65 66 Ok(()) 67 } 68 } 69 70 71 #[cfg(test)] 72 mod tests { 73 use crate::highlighting::{ThemeSet, Color}; 74 #[test] can_parse_common_themes()75 fn can_parse_common_themes() { 76 let themes = ThemeSet::load_from_folder("testdata").unwrap(); 77 let all_themes: Vec<&str> = themes.themes.keys().map(|x| &**x).collect(); 78 assert!(all_themes.contains(&"base16-ocean.dark")); 79 80 println!("{:?}", all_themes); 81 82 let theme = ThemeSet::get_theme("testdata/spacegray/base16-ocean.dark.tmTheme").unwrap(); 83 assert_eq!(theme.name.unwrap(), "Base16 Ocean Dark"); 84 assert_eq!(theme.settings.selection.unwrap(), 85 Color { 86 r: 0x4f, 87 g: 0x5b, 88 b: 0x66, 89 a: 0xff, 90 }); 91 assert_eq!(theme.scopes[0].style.foreground.unwrap(), 92 Color { 93 r: 0xc0, 94 g: 0xc5, 95 b: 0xce, 96 a: 0xFF, 97 }); 98 // assert!(false); 99 } 100 } 101