1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
//! Methods for dumping serializable structs to a compressed binary format
//! These are used to load and store the dumps used for fast startup times.
//!
//! Currently syntect serializes `SyntaxSet` structs with `dump_to_file`
//! into `.packdump` files and likewise `ThemeSet` structs to `.themedump` files.
//!
//! You can use these methods to manage your own caching of compiled syntaxes and
//! themes. And even your own `rustc_serialize::Encodable` structures if you want to
//! be consistent with your format.
use bincode::SizeLimit;
use bincode::rustc_serialize::*;
use std::fs::File;
use std::io::{BufReader, BufWriter};
use parsing::SyntaxSet;
use highlighting::ThemeSet;
use std::path::Path;
use flate2::write::ZlibEncoder;
use flate2::read::ZlibDecoder;
use flate2::Compression;
use rustc_serialize::{Encodable, Decodable};

/// Dumps an object to a binary array in the same format as `dump_to_file`
pub fn dump_binary<T: Encodable>(o: &T) -> Vec<u8> {
    let mut v = Vec::new();
    {
        let mut encoder = ZlibEncoder::new(&mut v, Compression::Best);
        encode_into(o, &mut encoder, SizeLimit::Infinite).unwrap();
    }
    v
}

/// Dumps an encodable object to a file at a given path. If a file already exists at that path
/// it will be overwritten. The files created are encoded with the `bincode` crate and then
/// compressed with the `flate2` crate.
pub fn dump_to_file<T: Encodable, P: AsRef<Path>>(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 syntax set from
/// a binary dump. Panics if the dump is invalid.
pub fn from_binary<T: Decodable>(v: &[u8]) -> T {
    let mut decoder = ZlibDecoder::new(v);
    decode_from(&mut decoder, SizeLimit::Infinite).unwrap()
}

/// Returns a fully loaded and linked syntax set from
/// a binary dump file.
pub fn from_dump_file<T: Decodable, P: AsRef<Path>>(path: P) -> DecodingResult<T> {
    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 SyntaxSet {
    /// Instantiates a new syntax set from a binary dump of
    /// Sublime Text's default open source syntax definitions and then links it.
    /// These dumps are included in this library's binary for convenience.
    ///
    /// This method loads the version for parsing line strings with no `\n` characters at the end.
    /// If you're able to efficiently include newlines at the end of strings, use `load_defaults_newlines`
    /// since it works better. See `SyntaxSet#load_syntaxes` for more info on this issue.
    ///
    /// This is the recommended way of creating a syntax set for
    /// non-advanced use cases. It is also significantly faster than loading the YAML files.
    ///
    /// Note that you can load additional syntaxes after doing this,
    /// you'll just have to link again. If you want you can even
    /// use the fact that SyntaxDefinitions are serializable with
    /// the bincode crate to cache dumps of additional syntaxes yourself.
    pub fn load_defaults_nonewlines() -> SyntaxSet {
        let mut ps: SyntaxSet = from_binary(include_bytes!("../assets/default_nonewlines.\
                                                             packdump"));
        ps.link_syntaxes();
        ps
    }

    /// Same as `load_defaults_nonewlines` but for parsing line strings with newlines at the end.
    /// 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() -> SyntaxSet {
        let mut ps: SyntaxSet = from_binary(include_bytes!("../assets/default_newlines.packdump"));
        ps.link_syntaxes();
        ps
    }
}

impl ThemeSet {
    /// Loads the set of default themes
    /// Currently includes (these are the keys for the map):
    ///
    /// - `base16-ocean.dark`,`base16-eighties.dark`,`base16-mocha.dark`,`base16-ocean.light`
    /// - `InspiredGitHub` from [here](https://github.com/sethlopezme/InspiredGitHub.tmtheme)
    /// - `Solarized (dark)` and `Solarized (light)`
    pub fn load_defaults() -> ThemeSet {
        from_binary(include_bytes!("../assets/default.themedump"))
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use parsing::SyntaxSet;
    use highlighting::ThemeSet;
    #[test]
    fn can_dump_and_load() {
        let mut ps = SyntaxSet::new();
        ps.load_syntaxes("testdata/Packages", false).unwrap();

        let bin = dump_binary(&ps);
        let ps2: SyntaxSet = from_binary(&bin[..]);
        assert_eq!(ps.syntaxes().len(), ps2.syntaxes().len());

        let themes = ThemeSet::load_defaults();
        assert!(themes.themes.len() > 4);
    }
}