on f140f6d help sign in
bat/src/assets.rs
234 lines 7.2 kB history raw
1use directories::ProjectDirs;
2use errors::*;
3use std::borrow::Cow;
4use std::collections::BTreeMap;
5use std::fs::{self, File};
6use std::path::{Path, PathBuf};
7use syntect::dumps::{dump_to_file, from_binary, from_reader};
8use syntect::highlighting::{Theme, ThemeSet};
9use syntect::parsing::{SyntaxDefinition, SyntaxSet};
11#[cfg(unix)]
12use std::os::unix::fs::FileTypeExt;
14use app::InputFile;
16lazy_static! {
17 static ref PROJECT_DIRS: ProjectDirs =
18 ProjectDirs::from("", "", crate_name!()).expect("Could not get home directory");
21pub const BAT_THEME_DEFAULT: &str = "Monokai Extended";
23pub struct HighlightingAssets {
24 pub syntax_set: SyntaxSet,
25 pub theme_set: ThemeSet,
28impl HighlightingAssets {
29 pub fn new() -> Self {
30 Self::from_cache().unwrap_or_else(|_| Self::from_binary())
31 }
33 fn empty() -> Self {
34 let mut syntax_set = SyntaxSet::new();
35 syntax_set.load_plain_text_syntax();
37 let theme_set = ThemeSet {
38 themes: BTreeMap::new(),
39 };
40 HighlightingAssets {
41 syntax_set,
42 theme_set,
43 }
44 }
46 pub fn from_files(dir: Option<&Path>, start_empty: bool) -> Result<Self> {
47 let source_dir = dir.unwrap_or_else(|| PROJECT_DIRS.config_dir());
49 let mut assets = if start_empty {
50 Self::empty()
51 } else {
52 Self::from_binary_unlinked()
53 };
55 let theme_dir = source_dir.join("themes");
57 let res = extend_theme_set(&mut assets.theme_set, &theme_dir);
58 if !res.is_ok() {
59 println!(
60 "No themes were found in '{}', using the default set",
61 theme_dir.to_string_lossy()
62 );
63 }
65 let syntax_dir = source_dir.join("syntaxes");
66 if syntax_dir.exists() {
67 assets.syntax_set.load_syntaxes(syntax_dir, true)?;
68 } else {
69 println!(
70 "No syntaxes were found in '{}', using the default set.",
71 syntax_dir.to_string_lossy()
72 );
73 }
75 Ok(assets)
76 }
78 fn from_cache() -> Result<Self> {
79 let theme_set_path = theme_set_path();
80 let syntax_set_file = File::open(&syntax_set_path()).chain_err(|| {
81 format!(
82 "Could not load cached syntax set '{}'",
83 syntax_set_path().to_string_lossy()
84 )
85 })?;
86 let mut syntax_set: SyntaxSet =
87 from_reader(syntax_set_file).chain_err(|| "Could not parse cached syntax set")?;
88 syntax_set.link_syntaxes();
90 let theme_set_file = File::open(&theme_set_path).chain_err(|| {
91 format!(
92 "Could not load cached theme set '{}'",
93 theme_set_path.to_string_lossy()
94 )
95 })?;
96 let theme_set: ThemeSet =
97 from_reader(theme_set_file).chain_err(|| "Could not parse cached theme set")?;
99 Ok(HighlightingAssets {
100 syntax_set,
101 theme_set,
102 })
103 }
105 fn from_binary_unlinked() -> Self {
106 let syntax_set: SyntaxSet = from_binary(include_bytes!("../assets/syntaxes.bin"));
107 let theme_set: ThemeSet = from_binary(include_bytes!("../assets/themes.bin"));
109 HighlightingAssets {
110 syntax_set,
111 theme_set,
112 }
113 }
115 fn from_binary() -> Self {
116 let mut assets = Self::from_binary_unlinked();
117 assets.syntax_set.link_syntaxes();
118 assets
119 }
121 pub fn save(&self, dir: Option<&Path>) -> Result<()> {
122 let target_dir = dir.unwrap_or_else(|| PROJECT_DIRS.cache_dir());
123 let _ = fs::create_dir(target_dir);
124 let theme_set_path = target_dir.join("themes.bin");
125 let syntax_set_path = target_dir.join("syntaxes.bin");
127 print!(
128 "Writing theme set to {} ... ",
129 theme_set_path.to_string_lossy()
130 );
131 dump_to_file(&self.theme_set, &theme_set_path).chain_err(|| {
132 format!(
133 "Could not save theme set to {}",
134 theme_set_path.to_string_lossy()
135 )
136 })?;
137 println!("okay");
139 print!(
140 "Writing syntax set to {} ... ",
141 syntax_set_path.to_string_lossy()
142 );
143 dump_to_file(&self.syntax_set, &syntax_set_path).chain_err(|| {
144 format!(
145 "Could not save syntax set to {}",
146 syntax_set_path.to_string_lossy()
147 )
148 })?;
149 println!("okay");
151 Ok(())
152 }
154 pub fn get_theme(&self, theme: &str) -> &Theme {
155 match self.theme_set.themes.get(theme) {
156 Some(theme) => theme,
157 None => {
158 use ansi_term::Colour::Yellow;
159 eprintln!(
160 "{}: Unknown theme '{}', using default.",
161 Yellow.paint("[bat warning]"),
162 theme
163 );
164 &self.theme_set.themes[BAT_THEME_DEFAULT]
165 }
166 }
167 }
169 pub fn get_syntax(&self, language: Option<&str>, filename: InputFile) -> &SyntaxDefinition {
170 let syntax = match (language, filename) {
171 (Some(language), _) => self.syntax_set.find_syntax_by_token(language),
172 (None, InputFile::Ordinary(filename)) => {
173 #[cfg(not(unix))]
174 let may_read_from_file = true;
176 // Do not peek at the file (to determine the syntax) if it is a FIFO because they can
177 // only be read once.
178 #[cfg(unix)]
179 let may_read_from_file = !fs::metadata(filename)
180 .map(|m| m.file_type().is_fifo())
181 .unwrap_or(false);
183 if may_read_from_file {
184 self.syntax_set
185 .find_syntax_for_file(filename)
186 .unwrap_or(None)
187 } else {
188 None
189 }
190 }
191 (None, InputFile::StdIn) => None,
192 (_, InputFile::ThemePreviewFile) => self.syntax_set.find_syntax_by_name("Rust"),
193 };
195 syntax.unwrap_or_else(|| self.syntax_set.find_syntax_plain_text())
196 }
199// TODO: this function will soon be part of syntect's `ThemeSet`.
200fn extend_theme_set<P: AsRef<Path>>(theme_set: &mut ThemeSet, folder: P) -> Result<()> {
201 let paths = ThemeSet::discover_theme_paths(folder)?;
202 for p in &paths {
203 let theme = ThemeSet::get_theme(p)?;
204 let basename = p
205 .file_stem()
206 .and_then(|x| x.to_str())
207 .ok_or("Could not get theme basename")?;
208 theme_set.themes.insert(basename.to_owned(), theme);
209 }
211 Ok(())
214fn theme_set_path() -> PathBuf {
215 PROJECT_DIRS.cache_dir().join("themes.bin")
218fn syntax_set_path() -> PathBuf {
219 PROJECT_DIRS.cache_dir().join("syntaxes.bin")
222pub fn config_dir() -> Cow<'static, str> {
223 PROJECT_DIRS.config_dir().to_string_lossy()
226pub fn clear_assets() {
227 print!("Clearing theme set cache ... ");
228 fs::remove_file(theme_set_path()).ok();
229 println!("okay");
231 print!("Clearing syntax set cache ... ");
232 fs::remove_file(syntax_set_path()).ok();
233 println!("okay");