diff --git a/.gitmodules b/.gitmodules index c161c7e0..e42197e5 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,9 +1,6 @@ [submodule "testdata/Packages"] path = testdata/Packages url = https://github.com/sublimehq/Packages -[submodule "testdata/themes.tmbundle"] - path = testdata/themes.tmbundle - url = https://github.com/textmate/themes.tmbundle.git [submodule "testdata/InspiredGitHub.tmtheme"] path = testdata/InspiredGitHub.tmtheme url = https://github.com/sethlopezme/InspiredGitHub.tmtheme.git diff --git a/assets/default.themedump b/assets/default.themedump new file mode 100644 index 00000000..b817f8bd Binary files /dev/null and b/assets/default.themedump differ diff --git a/benches/highlighting.rs b/benches/highlighting.rs index 7c81707b..f4f0c3a0 100644 --- a/benches/highlighting.rs +++ b/benches/highlighting.rs @@ -5,6 +5,7 @@ extern crate syntect; use test::Bencher; use syntect::package_set::PackageSet; +use syntect::theme_set::ThemeSet; use syntect::scope::ScopeStack; use syntect::parser::*; use syntect::theme::highlighter::*; @@ -15,7 +16,7 @@ use std::io::Read; fn highlight_file(b: &mut Bencher, path_s: &str) { let ps = PackageSet::load_from_folder("testdata/Packages").unwrap(); - let highlighter = Highlighter::new(PackageSet::get_theme("testdata/spacegray/base16-ocean.\ + let highlighter = Highlighter::new(ThemeSet::get_theme("testdata/spacegray/base16-ocean.\ dark.tmTheme") .unwrap()); let path = Path::new(path_s); diff --git a/benches/loading.rs b/benches/loading.rs index 9f58eed1..a2489fce 100644 --- a/benches/loading.rs +++ b/benches/loading.rs @@ -5,20 +5,29 @@ extern crate syntect; use test::Bencher; use syntect::package_set::PackageSet; +use syntect::theme_set::ThemeSet; #[bench] -fn bench_load_syntax_dump(b: &mut Bencher) { +fn bench_load_internal_dump(b: &mut Bencher) { b.iter(|| { - let ps = PackageSet::from_dump_file("assets/default_newlines.packdump"); + let ps = PackageSet::load_defaults_newlines(); test::black_box(&ps); }); } #[bench] -fn bench_load_internal_dump(b: &mut Bencher) { +fn bench_load_internal_themes(b: &mut Bencher) { b.iter(|| { - let ps = PackageSet::load_defaults_newlines(); - test::black_box(&ps); + let ts = ThemeSet::load_defaults(); + test::black_box(&ts); + }); +} + +#[bench] +fn bench_load_theme(b: &mut Bencher) { + b.iter(|| { + let theme = ThemeSet::get_theme("testdata/spacegray/base16-ocean.dark.tmTheme"); + test::black_box(&theme); }); } diff --git a/examples/gendata.rs b/examples/gendata.rs index 17235075..3bfc37ee 100644 --- a/examples/gendata.rs +++ b/examples/gendata.rs @@ -1,12 +1,17 @@ extern crate syntect; use syntect::package_set::PackageSet; +use syntect::theme_set::ThemeSet; +use syntect::dumps::*; fn main() { let mut ps = PackageSet::new(); ps.load_syntaxes("testdata/Packages", true).unwrap(); - ps.dump_to_file("assets/default_newlines.packdump").unwrap(); + dump_to_file(&ps, "assets/default_newlines.packdump").unwrap(); let mut ps2 = PackageSet::new(); ps2.load_syntaxes("testdata/Packages", false).unwrap(); - ps2.dump_to_file("assets/default_nonewlines.packdump").unwrap(); + dump_to_file(&ps2, "assets/default_nonewlines.packdump").unwrap(); + + let ts = ThemeSet::load_from_folder("testdata").unwrap(); + dump_to_file(&ts, "assets/default.themedump").unwrap(); } diff --git a/examples/syncat.rs b/examples/syncat.rs index 43ddc4e2..d9447e85 100644 --- a/examples/syncat.rs +++ b/examples/syncat.rs @@ -1,6 +1,7 @@ extern crate syntect; use syntect::scope::ScopeStack; use syntect::package_set::PackageSet; +use syntect::theme_set::ThemeSet; use syntect::parser::*; use syntect::theme::highlighter::*; use syntect::theme::style::*; @@ -13,9 +14,8 @@ use std::fs::File; fn main() { let ps = PackageSet::load_defaults_nonewlines(); - let highlighter = Highlighter::new(PackageSet::get_theme("testdata/spacegray/base16-ocean.\ - dark.tmTheme") - .unwrap()); + let ts = ThemeSet::load_defaults(); + let highlighter = Highlighter::new(&ts.themes["base16-ocean.dark"]); let args: Vec = std::env::args().collect(); if args.len() < 2 { diff --git a/src/dumps.rs b/src/dumps.rs index 1539310b..e38880eb 100644 --- a/src/dumps.rs +++ b/src/dumps.rs @@ -3,10 +3,42 @@ use bincode::rustc_serialize::*; use std::fs::File; use std::io::{BufReader, BufWriter}; use package_set::PackageSet; +use theme_set::ThemeSet; use std::path::Path; use flate2::write::ZlibEncoder; use flate2::read::ZlibDecoder; use flate2::Compression; +use rustc_serialize::{Encodable, Decodable}; + +pub fn dump_binary(o: &T) -> Vec { + let mut v = Vec::new(); + { + let mut encoder = ZlibEncoder::new(&mut v, Compression::Best); + encode_into(o, &mut encoder, SizeLimit::Infinite).unwrap(); + } + v +} + +pub fn dump_to_file>(o: &T, path: P) -> EncodingResult<()> { + let f = BufWriter::new(try!(File::create(path).map_err(EncodingError::IoError))); + let mut encoder = ZlibEncoder::new(f, Compression::Best); + encode_into(o, &mut encoder, SizeLimit::Infinite) +} + +/// Returns a fully loaded and linked package set from +/// a binary dump. Panics if the dump is invalid. +pub fn from_binary(v: &[u8]) -> T { + let mut decoder = ZlibDecoder::new(v); + decode_from(&mut decoder, SizeLimit::Infinite).unwrap() +} + +/// Returns a fully loaded and linked package set from +/// a binary dump file. +pub fn from_dump_file>(path: P) -> DecodingResult { + let f = try!(File::open(path).map_err(DecodingError::IoError)); + let mut decoder = ZlibDecoder::new(BufReader::new(f)); + decode_from(&mut decoder, SizeLimit::Infinite) +} impl PackageSet { /// Instantiates a new package set from a binary dump of @@ -22,7 +54,8 @@ impl PackageSet { /// use the fact that SyntaxDefinitions are serializable with /// the bincode crate to cache dumps of additional syntaxes yourself. pub fn load_defaults_nonewlines() -> PackageSet { - let mut ps = Self::from_binary(include_bytes!("../assets/default_nonewlines.packdump")); + let mut ps: PackageSet = from_binary(include_bytes!("../assets/default_nonewlines.\ + packdump")); ps.link_syntaxes(); ps } @@ -31,55 +64,35 @@ impl PackageSet { /// These are separate methods because thanks to linker garbage collection, only the serialized /// dumps for the method(s) you call will be included in the binary (each is ~200kb for now). pub fn load_defaults_newlines() -> PackageSet { - let mut ps = Self::from_binary(include_bytes!("../assets/default_newlines.packdump")); - ps.link_syntaxes(); - ps - } - - pub fn dump_binary(&self) -> Vec { - assert!(!self.is_linked); - let mut v = Vec::new(); - { - let mut encoder = ZlibEncoder::new(&mut v, Compression::Best); - encode_into(self, &mut encoder, SizeLimit::Infinite).unwrap(); - } - v - } - - pub fn dump_to_file>(&self, path: P) -> EncodingResult<()> { - let f = BufWriter::new(try!(File::create(path).map_err(EncodingError::IoError))); - let mut encoder = ZlibEncoder::new(f, Compression::Best); - encode_into(self, &mut encoder, SizeLimit::Infinite) - } - - /// Returns a fully loaded and linked package set from - /// a binary dump. Panics if the dump is invalid. - pub fn from_binary(v: &[u8]) -> PackageSet { - let mut decoder = ZlibDecoder::new(v); - let mut ps: PackageSet = decode_from(&mut decoder, SizeLimit::Infinite).unwrap(); + let mut ps: PackageSet = from_binary(include_bytes!("../assets/default_newlines.packdump")); ps.link_syntaxes(); ps } +} - /// Returns a fully loaded and linked package set from - /// a binary dump file. - pub fn from_dump_file>(path: P) -> DecodingResult { - let f = try!(File::open(path).map_err(DecodingError::IoError)); - let mut decoder = ZlibDecoder::new(BufReader::new(f)); - decode_from(&mut decoder, SizeLimit::Infinite) +impl ThemeSet { + /// Loads the set of default themes + /// Currently includes Solarized light/dark, Base16 ocean/mocha/eighties and InspiredGithub + pub fn load_defaults() -> ThemeSet { + from_binary(include_bytes!("../assets/default.themedump")) } } #[cfg(test)] mod tests { use package_set::PackageSet; + use theme_set::ThemeSet; + use dumps::*; #[test] fn can_dump_and_load() { let mut ps = PackageSet::new(); ps.load_syntaxes("testdata/Packages", false).unwrap(); - let bin = ps.dump_binary(); - let ps2 = PackageSet::from_binary(&bin[..]); + let bin = dump_binary(&ps); + let ps2: PackageSet = from_binary(&bin[..]); assert_eq!(ps.syntaxes.len(), ps2.syntaxes.len()); + + let themes = ThemeSet::load_defaults(); + assert!(themes.themes.len() > 4); } } diff --git a/src/lib.rs b/src/lib.rs index da331455..40144a97 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -13,6 +13,7 @@ extern crate flate2; pub mod syntax_definition; pub mod yaml_load; pub mod package_set; +pub mod theme_set; pub mod scope; pub mod parser; pub mod theme; diff --git a/src/package_set.rs b/src/package_set.rs index 8b59be3e..b80a2b99 100644 --- a/src/package_set.rs +++ b/src/package_set.rs @@ -1,11 +1,9 @@ use syntax_definition::*; use scope::*; use yaml_load::*; -use theme::theme::{Theme, ParseThemeError}; -use theme::settings::*; -use std::path::{Path, PathBuf}; -use std::io::{Error as IoError, BufReader}; +use std::path::Path; +use std::io::Error as IoError; use walkdir::WalkDir; use std::io::{self, Read}; use std::fs::File; @@ -33,14 +31,6 @@ pub enum PackageError { WalkDir(walkdir::Error), Io(io::Error), ParseSyntax(ParseSyntaxError), - ParseTheme(ParseThemeError), - ReadSettings(SettingsError), -} - -impl From for PackageError { - fn from(error: SettingsError) -> PackageError { - PackageError::ReadSettings(error) - } } impl From for PackageError { @@ -49,12 +39,6 @@ impl From for PackageError { } } -impl From for PackageError { - fn from(error: ParseThemeError) -> PackageError { - PackageError::ParseTheme(error) - } -} - impl From for PackageError { fn from(error: ParseSyntaxError) -> PackageError { PackageError::ParseSyntax(error) @@ -90,18 +74,6 @@ impl PackageSet { Ok(ps) } - /// Returns all the themes found in a folder, good for enumerating before loading one with get_theme - pub fn discover_themes>(folder: P) -> Result, PackageError> { - let mut themes = Vec::new(); - for entry in WalkDir::new(folder) { - let entry = try!(entry.map_err(|e| PackageError::WalkDir(e))); - if entry.path().extension().map(|e| e == "tmTheme").unwrap_or(false) { - themes.push(entry.path().to_owned()); - } - } - Ok(themes) - } - /// Loads all the .sublime-syntax files in a folder into this package set. /// It does not link the syntaxes, in case you want to serialize this package set. /// @@ -223,20 +195,6 @@ impl PackageSet { self.link_context(syntax, mut_ref.deref_mut()); } } - - fn read_file(path: &Path) -> Result, PackageError> { - let reader = try!(File::open(path)); - Ok(BufReader::new(reader)) - } - - fn read_plist(path: &Path) -> Result { - Ok(try!(read_plist(try!(Self::read_file(path))))) - } - - /// Loads a theme given a path to a .tmTheme file - pub fn get_theme>(path: P) -> Result { - Ok(try!(Theme::parse_settings(try!(Self::read_plist(path.as_ref()))))) - } } #[cfg(test)] @@ -258,32 +216,4 @@ mod tests { let count = context_iter(main_context.clone()).count(); assert_eq!(count, 91); } - #[test] - fn can_parse_common_themes() { - use package_set::PackageSet; - use theme::style::Color; - let theme_paths = PackageSet::discover_themes("testdata/themes.tmbundle").unwrap(); - for theme_path in theme_paths.iter() { - println!("{:?}", theme_path); - PackageSet::get_theme(theme_path).unwrap(); - } - - let theme = PackageSet::get_theme("testdata/themes.tmbundle/Themes/Amy.tmTheme").unwrap(); - assert_eq!(theme.name.unwrap(), "Amy"); - assert_eq!(theme.settings.selection.unwrap(), - Color { - r: 0x80, - g: 0x00, - b: 0x00, - a: 0x80, - }); - assert_eq!(theme.scopes[0].style.foreground.unwrap(), - Color { - r: 0x40, - g: 0x40, - b: 0x80, - a: 0xFF, - }); - // assert!(false); - } } diff --git a/src/scope.rs b/src/scope.rs index c6223b5b..8c1e5e8a 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -34,7 +34,7 @@ pub struct ScopeRepository { atom_index_map: HashMap, } -#[derive(Debug, Clone, PartialEq, Eq, Default)] +#[derive(Debug, Clone, PartialEq, Eq, Default, RustcEncodable, RustcDecodable)] pub struct ScopeStack { scopes: Vec, } diff --git a/src/theme/highlighter.rs b/src/theme/highlighter.rs index 87aba832..232eda89 100644 --- a/src/theme/highlighter.rs +++ b/src/theme/highlighter.rs @@ -8,8 +8,8 @@ use theme::theme::Theme; use theme::style::{Style, StyleModifier, FontStyle, BLACK, WHITE}; #[derive(Debug)] -pub struct Highlighter { - theme: Theme, // TODO add caching or accelerator structure +pub struct Highlighter<'a> { + theme: &'a Theme, // TODO add caching or accelerator structure } #[derive(Debug, Clone)] @@ -24,7 +24,7 @@ pub struct HighlightIterator<'a> { pos: usize, changes: &'a [(usize, ScopeStackOp)], text: &'a str, - highlighter: &'a Highlighter, + highlighter: &'a Highlighter<'a>, state: &'a mut HighlightState, } @@ -101,8 +101,8 @@ impl<'a> Iterator for HighlightIterator<'a> { } } -impl Highlighter { - pub fn new(theme: Theme) -> Highlighter { +impl<'a> Highlighter<'a> { + pub fn new(theme: &'a Theme) -> Highlighter<'a> { Highlighter { theme: theme } } @@ -136,6 +136,7 @@ impl Highlighter { #[cfg(test)] mod tests { use package_set::PackageSet; + use theme_set::ThemeSet; use scope::ScopeStack; use parser::*; use theme::highlighter::*; @@ -148,28 +149,27 @@ mod tests { let syntax = ps.find_syntax_by_name("Ruby on Rails").unwrap(); ParseState::new(syntax) }; - let highlighter = Highlighter::new(PackageSet::get_theme("testdata/themes.\ - tmbundle/Themes/Amy.tmTheme") - .unwrap()); + let ts = ThemeSet::load_defaults(); + let highlighter = Highlighter::new(&ts.themes["base16-ocean.dark"]); let mut highlight_state = HighlightState::new(&highlighter, ScopeStack::new()); let line = "module Bob::Wow::Troll::Five; 5; end"; let ops = state.parse_line(line); let iter = HighlightIterator::new(&mut highlight_state, &ops[..], line, &highlighter); let regions: Vec<(Style, &str)> = iter.collect(); - println!("{:#?}", regions); + // println!("{:#?}", regions); assert_eq!(regions[11], (Style { foreground: Color { - r: 0x70, - g: 0x90, - b: 0xB0, + r: 208, + g: 135, + b: 112, a: 0xFF, }, background: Color { - r: 0x20, - g: 0x00, - b: 0x20, + r: 43, + g: 48, + b: 59, a: 0xFF, }, font_style: FontStyle::empty(), diff --git a/src/theme/selector.rs b/src/theme/selector.rs index e34c0648..fd599e8b 100644 --- a/src/theme/selector.rs +++ b/src/theme/selector.rs @@ -4,13 +4,13 @@ use scope::*; use std::str::FromStr; -#[derive(Debug, Clone, PartialEq, Eq, Default)] +#[derive(Debug, Clone, PartialEq, Eq, Default, RustcEncodable, RustcDecodable)] pub struct ScopeSelector { path: ScopeStack, exclude: Option, } -#[derive(Debug, Clone, PartialEq, Eq, Default)] +#[derive(Debug, Clone, PartialEq, Eq, Default, RustcEncodable, RustcDecodable)] pub struct ScopeSelectors { pub selectors: Vec, } @@ -96,7 +96,7 @@ mod tests { } #[test] fn matching_works() { - use scope::{ScopeStack,MatchPower}; + use scope::{ScopeStack, MatchPower}; use theme::selector::*; use std::str::FromStr; assert_eq!(ScopeSelectors::from_str("a.b, a e, e.f") diff --git a/src/theme/style.rs b/src/theme/style.rs index dd81cb0e..5334e8bc 100644 --- a/src/theme/style.rs +++ b/src/theme/style.rs @@ -1,6 +1,6 @@ /// Code based on https://github.com/defuz/sublimate/blob/master/src/core/syntax/style.rs /// released under the MIT license by @defuz -#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, RustcEncodable, RustcDecodable)] pub struct Style { /// Foreground color. pub foreground: Color, @@ -10,7 +10,7 @@ pub struct Style { pub font_style: FontStyle, } -#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)] +#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, RustcEncodable, RustcDecodable)] pub struct StyleModifier { /// Foreground color. pub foreground: Option, @@ -33,7 +33,7 @@ pub const WHITE: Color = Color { a: 0xFF, }; -#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, RustcEncodable, RustcDecodable)] pub struct Color { pub r: u8, pub g: u8, @@ -42,6 +42,7 @@ pub struct Color { } bitflags! { + #[derive(RustcEncodable, RustcDecodable)] flags FontStyle: u8 { const FONT_STYLE_BOLD = 1, const FONT_STYLE_UNDERLINE = 2, diff --git a/src/theme/theme.rs b/src/theme/theme.rs index cb3b45af..7128d6cd 100644 --- a/src/theme/theme.rs +++ b/src/theme/theme.rs @@ -10,7 +10,7 @@ use scope::ParseScopeError; use self::ParseThemeError::*; -#[derive(Debug, Default)] +#[derive(Debug, Default, RustcEncodable, RustcDecodable)] pub struct Theme { pub name: Option, pub author: Option, @@ -18,7 +18,7 @@ pub struct Theme { pub scopes: Vec, } -#[derive(Debug, Default)] +#[derive(Debug, Default, RustcEncodable, RustcDecodable)] pub struct ThemeSettings { /// Foreground color for the view. pub foreground: Option, @@ -89,14 +89,14 @@ pub struct ThemeSettings { pub highlight_foreground: Option, } -#[derive(Debug, Default)] +#[derive(Debug, Default, RustcEncodable, RustcDecodable)] pub struct ThemeItem { /// Target scope name. pub scope: ScopeSelectors, pub style: StyleModifier, } -#[derive(Debug)] +#[derive(Debug, RustcEncodable, RustcDecodable)] pub enum UnderlineOption { None, Underline, diff --git a/src/theme_set.rs b/src/theme_set.rs new file mode 100644 index 00000000..cb404688 --- /dev/null +++ b/src/theme_set.rs @@ -0,0 +1,113 @@ +use theme::theme::{Theme, ParseThemeError}; +use theme::settings::*; +use std::collections::BTreeMap; +use std::path::{Path, PathBuf}; +use std::io::{Error as IoError, BufReader}; +use walkdir::WalkDir; +use std::io; +use std::fs::File; +use walkdir; + +#[derive(Debug, RustcEncodable, RustcDecodable)] +pub struct ThemeSet { + pub themes: BTreeMap, +} + +#[derive(Debug)] +pub enum ThemeSetError { + WalkDir(walkdir::Error), + Io(io::Error), + ParseTheme(ParseThemeError), + ReadSettings(SettingsError), + BadPath, +} + +impl From for ThemeSetError { + fn from(error: SettingsError) -> ThemeSetError { + ThemeSetError::ReadSettings(error) + } +} + +impl From for ThemeSetError { + fn from(error: IoError) -> ThemeSetError { + ThemeSetError::Io(error) + } +} + +impl From for ThemeSetError { + fn from(error: ParseThemeError) -> ThemeSetError { + ThemeSetError::ParseTheme(error) + } +} + +impl ThemeSet { + /// Returns all the themes found in a folder, good for enumerating before loading one with get_theme + pub fn discover_theme_paths>(folder: P) -> Result, ThemeSetError> { + let mut themes = Vec::new(); + for entry in WalkDir::new(folder) { + let entry = try!(entry.map_err(|e| ThemeSetError::WalkDir(e))); + if entry.path().extension().map(|e| e == "tmTheme").unwrap_or(false) { + themes.push(entry.path().to_owned()); + } + } + Ok(themes) + } + + fn read_file(path: &Path) -> Result, ThemeSetError> { + let reader = try!(File::open(path)); + Ok(BufReader::new(reader)) + } + + fn read_plist(path: &Path) -> Result { + Ok(try!(read_plist(try!(Self::read_file(path))))) + } + + /// Loads a theme given a path to a .tmTheme file + pub fn get_theme>(path: P) -> Result { + Ok(try!(Theme::parse_settings(try!(Self::read_plist(path.as_ref()))))) + } + + /// Loads all the themes in a folder + pub fn load_from_folder>(folder: P) -> Result { + let paths = try!(Self::discover_theme_paths(folder)); + let mut map = BTreeMap::new(); + for p in paths.iter() { + let theme = try!(Self::get_theme(p)); + let basename = + try!(p.file_stem().and_then(|x| x.to_str()).ok_or(ThemeSetError::BadPath)); + map.insert(basename.to_owned(), theme); + } + Ok(ThemeSet { themes: map }) + } +} + + +#[cfg(test)] +mod tests { + use theme_set::ThemeSet; + #[test] + fn can_parse_common_themes() { + use theme::style::Color; + let themes = ThemeSet::load_from_folder("testdata").unwrap(); + let all_themes: Vec<&str> = themes.themes.keys().map(|x| &**x).collect(); + println!("{:?}", all_themes); + + let theme = ThemeSet::get_theme("testdata/spacegray/base16-ocean.dark.tmTheme").unwrap(); + assert_eq!(theme.name.unwrap(), "Base16 Ocean Dark"); + assert_eq!(theme.settings.selection.unwrap(), + Color { + r: 0x4f, + g: 0x5b, + b: 0x66, + a: 0xff, + }); + assert_eq!(theme.scopes[0].style.foreground.unwrap(), + Color { + r: 0xc0, + g: 0xc5, + b: 0xce, + a: 0xFF, + }); + // assert!(false); + } +} diff --git a/testdata/themes.tmbundle b/testdata/themes.tmbundle deleted file mode 160000 index c3b78309..00000000 --- a/testdata/themes.tmbundle +++ /dev/null @@ -1 +0,0 @@ -Subproject commit c3b783095ea29375cd85376fbbd084daf916baa7