1use bat::assets::HighlightingAssets;
2use color_eyre::Result;
3use gag::Gag;
4use std::path::{Path, PathBuf};
5use syntect::easy::HighlightLines;
6use syntect::highlighting::{
7 HighlightIterator, HighlightState, Highlighter, Style, Theme,
8};
9use syntect::parsing::{ParseState, ScopeStack, SyntaxReference, SyntaxSet};
10use tracing::warn;
11
12#[allow(dead_code)]
13#[derive(Debug, Clone)]
14pub struct HighlightingState {
15 parse_state: ParseState,
16 highlight_state: HighlightState,
17}
18
19impl HighlightingState {
20 pub fn new(
21 parse_state: ParseState,
22 highlight_state: HighlightState,
23 ) -> Self {
24 Self {
25 parse_state,
26 highlight_state,
27 }
28 }
29}
30
31struct LineHighlighter<'a> {
32 highlighter: Highlighter<'a>,
33 pub parse_state: ParseState,
34 pub highlight_state: HighlightState,
35}
36
37impl<'a> LineHighlighter<'a> {
38 pub fn new(
39 syntax: &SyntaxReference,
40 theme: &'a Theme,
41 ) -> LineHighlighter<'a> {
42 let highlighter = Highlighter::new(theme);
43 let highlight_state =
44 HighlightState::new(&highlighter, ScopeStack::new());
45 Self {
46 highlighter,
47 parse_state: ParseState::new(syntax),
48 highlight_state,
49 }
50 }
51
52 #[allow(dead_code)]
53 pub fn from_state(
54 state: HighlightingState,
55 theme: &'a Theme,
56 ) -> LineHighlighter<'a> {
57 Self {
58 highlighter: Highlighter::new(theme),
59 parse_state: state.parse_state,
60 highlight_state: state.highlight_state,
61 }
62 }
63
64 pub fn highlight_line<'b>(
66 &mut self,
67 line: &'b str,
68 syntax_set: &SyntaxSet,
69 ) -> Result<Vec<(Style, &'b str)>, syntect::Error> {
70 let ops = self.parse_state.parse_line(line, syntax_set)?;
71 let iter = HighlightIterator::new(
72 &mut self.highlight_state,
73 &ops[..],
74 line,
75 &self.highlighter,
76 );
77 Ok(iter.collect())
78 }
79}
80
81#[deprecated(
82 note = "Use `compute_highlights_incremental` instead, which also returns the state"
83)]
84pub fn compute_highlights_for_path(
85 file_path: &Path,
86 lines: &[String],
87 syntax_set: &SyntaxSet,
88 syntax_theme: &Theme,
89) -> Result<Vec<Vec<(Style, String)>>> {
90 let syntax = set_syntax_set(syntax_set, file_path);
91 let mut highlighter = HighlightLines::new(syntax, syntax_theme);
92 let mut highlighted_lines = Vec::new();
93 for line in lines {
94 let hl_regions = highlighter.highlight_line(line, syntax_set)?;
95 highlighted_lines.push(
96 hl_regions
97 .iter()
98 .map(|(style, text)| (*style, (*text).to_string()))
99 .collect(),
100 );
101 }
102 Ok(highlighted_lines)
103}
104
105fn set_syntax_set<'a>(
106 syntax_set: &'a SyntaxSet,
107 file_path: &Path,
108) -> &'a SyntaxReference {
109 syntax_set
110 .find_syntax_for_file(file_path)
111 .unwrap_or(None)
112 .unwrap_or_else(|| {
113 warn!(
114 "No syntax found for {:?}, defaulting to plain text",
115 file_path
116 );
117 syntax_set.find_syntax_plain_text()
118 })
119}
120
121#[derive(Debug, Clone)]
122pub struct HighlightedLines {
123 pub lines: Vec<Vec<(Style, String)>>,
124 }
126
127impl HighlightedLines {
128 pub fn new(
129 lines: Vec<Vec<(Style, String)>>,
130 _state: &Option<HighlightingState>,
131 ) -> Self {
132 Self { lines, }
133 }
134}
135
136pub fn compute_highlights_incremental(
137 file_path: &Path,
138 lines: &[String],
139 syntax_set: &SyntaxSet,
140 syntax_theme: &Theme,
141 _cached_lines: &Option<HighlightedLines>,
142) -> Result<HighlightedLines> {
143 let mut highlighted_lines: Vec<_>;
144 let mut highlighter: LineHighlighter;
145
146 let syntax = set_syntax_set(syntax_set, file_path);
159 highlighter = LineHighlighter::new(syntax, syntax_theme);
160 highlighted_lines = Vec::with_capacity(lines.len());
161
162 for line in lines {
163 let hl_regions = highlighter.highlight_line(line, syntax_set)?;
164 highlighted_lines.push(
165 hl_regions
166 .iter()
167 .map(|(style, text)| (*style, (*text).to_string()))
168 .collect(),
169 );
170 }
171
172 Ok(HighlightedLines::new(
173 highlighted_lines,
174 &Some(HighlightingState::new(
175 highlighter.parse_state.clone(),
176 highlighter.highlight_state.clone(),
177 )),
178 ))
179}
180
181#[allow(dead_code)]
182pub fn compute_highlights_for_line<'a>(
183 line: &'a str,
184 syntax_set: &SyntaxSet,
185 syntax_theme: &Theme,
186 file_path: &str,
187) -> Result<Vec<(Style, &'a str)>> {
188 let syntax = syntax_set.find_syntax_for_file(file_path)?;
189 match syntax {
190 None => {
191 warn!(
192 "No syntax found for path {:?}, defaulting to plain text",
193 file_path
194 );
195 Ok(vec![(Style::default(), line)])
196 }
197 Some(syntax) => {
198 let mut highlighter = HighlightLines::new(syntax, syntax_theme);
199 Ok(highlighter.highlight_line(line, syntax_set)?)
200 }
201 }
202}
203
204use directories::BaseDirs;
227use lazy_static::lazy_static;
228
229#[cfg(target_os = "macos")]
230use std::env;
231
232pub struct BatProjectDirs {
237 cache_dir: PathBuf,
238}
239
240impl BatProjectDirs {
241 fn new() -> Option<BatProjectDirs> {
242 #[cfg(target_os = "macos")]
243 let cache_dir_op = env::var_os("XDG_CACHE_HOME")
244 .map(PathBuf::from)
245 .filter(|p| p.is_absolute())
246 .or_else(|| BaseDirs::new().map(|d| d.home_dir().join(".cache")));
247
248 #[cfg(not(target_os = "macos"))]
249 let cache_dir_op = BaseDirs::new().map(|d| d.cache_dir().to_owned());
250
251 let cache_dir = cache_dir_op.map(|d| d.join("bat"))?;
252
253 Some(BatProjectDirs { cache_dir })
254 }
255
256 pub fn cache_dir(&self) -> &Path {
257 &self.cache_dir
258 }
259}
260
261lazy_static! {
262 pub static ref PROJECT_DIRS: BatProjectDirs = BatProjectDirs::new()
263 .unwrap_or_else(|| panic!("Could not get home directory"));
264}
265
266pub fn load_highlighting_assets() -> HighlightingAssets {
267 HighlightingAssets::from_cache(PROJECT_DIRS.cache_dir())
268 .unwrap_or_else(|_| HighlightingAssets::from_binary())
269}
270
271pub trait HighlightingAssetsExt {
272 fn get_theme_no_output(&self, theme_name: &str) -> &Theme;
273}
274
275impl HighlightingAssetsExt for HighlightingAssets {
276 fn get_theme_no_output(&self, theme_name: &str) -> &Theme {
282 let _e = Gag::stderr().unwrap();
283 let _o = Gag::stdout().unwrap();
284 let theme = self.get_theme(theme_name);
285 theme
286 }
287}