on 302707f help sign in
bat/src/pretty_printer.rs
295 lines 9.3 kB history raw
1use std::ffi::OsStr;
2use std::io::Read;
4use console::Term;
5use syntect::parsing::SyntaxReference;
7use crate::{
8 assets::HighlightingAssets,
9 config::{Config, VisibleLines},
10 controller::Controller,
11 error::Result,
12 input::Input,
13 line_range::{HighlightedLineRanges, LineRange, LineRanges},
14 style::{StyleComponent, StyleComponents},
15 SyntaxMapping, WrappingMode,
16};
18#[cfg(feature = "paging")]
19use crate::paging::PagingMode;
21#[derive(Default)]
22struct ActiveStyleComponents {
23 header: bool,
24 vcs_modification_markers: bool,
25 grid: bool,
26 line_numbers: bool,
27 snip: bool,
30pub struct PrettyPrinter<'a> {
31 inputs: Vec<Input<'a>>,
32 config: Config<'a>,
33 assets: HighlightingAssets,
35 highlighted_lines: Vec<LineRange>,
36 term_width: Option<usize>,
37 active_style_components: ActiveStyleComponents,
40impl<'a> PrettyPrinter<'a> {
41 pub fn new() -> Self {
42 let mut config = Config::default();
44 config.colored_output = true;
45 config.true_color = true;
47 PrettyPrinter {
48 inputs: vec![],
49 config,
50 assets: HighlightingAssets::from_binary(),
52 highlighted_lines: vec![],
53 term_width: None,
54 active_style_components: ActiveStyleComponents::default(),
55 }
56 }
58 /// Add a file which should be pretty-printed
59 pub fn input_file(&mut self, path: impl AsRef<OsStr>) -> &mut Self {
60 self.inputs.push(Input::ordinary_file(path.as_ref()));
61 self
62 }
64 /// Add multiple files which should be pretty-printed
65 pub fn input_files<I, P>(&mut self, paths: I) -> &mut Self
66 where
67 I: IntoIterator<Item = P>,
68 P: AsRef<OsStr>,
69 {
70 for path in paths {
71 self.inputs.push(Input::ordinary_file(path.as_ref()));
72 }
73 self
74 }
76 /// Add STDIN as an input
77 pub fn input_stdin(&mut self) -> &mut Self {
78 self.inputs.push(Input::stdin());
79 self
80 }
82 /// Add STDIN as an input (with customized name)
83 pub fn input_stdin_with_name(&mut self, name: impl AsRef<OsStr>) -> &mut Self {
84 self.inputs
85 .push(Input::stdin().with_name(Some(name.as_ref())));
86 self
87 }
89 /// Add a byte string as an input
90 pub fn input_from_bytes(&mut self, content: &'a [u8]) -> &mut Self {
91 self.input_from_reader(content)
92 }
94 /// Add a byte string as an input (with customized name)
95 pub fn input_from_bytes_with_name(
96 &mut self,
97 content: &'a [u8],
98 name: impl AsRef<OsStr>,
99 ) -> &mut Self {
100 self.input_from_reader_with_name(content, name)
101 }
103 /// Add a custom reader as an input
104 pub fn input_from_reader<R: Read + 'a>(&mut self, reader: R) -> &mut Self {
105 self.inputs.push(Input::from_reader(Box::new(reader)));
106 self
107 }
109 /// Add a custom reader as an input (with customized name)
110 pub fn input_from_reader_with_name<R: Read + 'a>(
111 &mut self,
112 reader: R,
113 name: impl AsRef<OsStr>,
114 ) -> &mut Self {
115 self.inputs
116 .push(Input::from_reader(Box::new(reader)).with_name(Some(name.as_ref())));
117 self
118 }
120 /// Specify the syntax file which should be used (default: auto-detect)
121 pub fn language(&mut self, language: &'a str) -> &mut Self {
122 self.config.language = Some(language);
123 self
124 }
126 /// The character width of the terminal (default: autodetect)
127 pub fn term_width(&mut self, width: usize) -> &mut Self {
128 self.term_width = Some(width);
129 self
130 }
132 /// The width of tab characters (default: None - do not turn tabs to spaces)
133 pub fn tab_width(&mut self, tab_width: Option<usize>) -> &mut Self {
134 self.config.tab_width = tab_width.unwrap_or(0);
135 self
136 }
138 /// Whether or not the output should be colorized (default: true)
139 pub fn colored_output(&mut self, yes: bool) -> &mut Self {
140 self.config.colored_output = yes;
141 self
142 }
144 /// Whether or not to output 24bit colors (default: true)
145 pub fn true_color(&mut self, yes: bool) -> &mut Self {
146 self.config.true_color = yes;
147 self
148 }
150 /// Whether to show a header with the file name
151 pub fn header(&mut self, yes: bool) -> &mut Self {
152 self.active_style_components.header = yes;
153 self
154 }
156 /// Whether to show line numbers
157 pub fn line_numbers(&mut self, yes: bool) -> &mut Self {
158 self.active_style_components.line_numbers = yes;
159 self
160 }
162 /// Whether to paint a grid, separating line numbers, git changes and the code
163 pub fn grid(&mut self, yes: bool) -> &mut Self {
164 self.active_style_components.grid = yes;
165 self
166 }
168 /// Whether to show modification markers for VCS changes. This has no effect if
169 /// the `git` feature is not activated.
170 #[cfg_attr(
171 not(feature = "git"),
172 deprecated(
173 note = "Using vcs_modification_markers without the 'git' feature has no effect. \
174 The function will be removed (for non-'git' use cases) in the future."
175 )
176 )]
177 #[allow(unused_variables)]
178 pub fn vcs_modification_markers(&mut self, yes: bool) -> &mut Self {
179 #[cfg(feature = "git")]
180 {
181 self.active_style_components.vcs_modification_markers = yes;
182 }
183 self
184 }
186 /// Whether to show "snip" markers between visible line ranges (default: no)
187 pub fn snip(&mut self, yes: bool) -> &mut Self {
188 self.active_style_components.snip = yes;
189 self
190 }
192 /// Text wrapping mode (default: do not wrap)
193 pub fn wrapping_mode(&mut self, mode: WrappingMode) -> &mut Self {
194 self.config.wrapping_mode = mode;
195 self
196 }
198 /// Whether or not to use ANSI italics (default: off)
199 pub fn use_italics(&mut self, yes: bool) -> &mut Self {
200 self.config.use_italic_text = yes;
201 self
202 }
204 /// If and how to use a pager (default: no paging)
205 #[cfg(feature = "paging")]
206 pub fn paging_mode(&mut self, mode: PagingMode) -> &mut Self {
207 self.config.paging_mode = mode;
208 self
209 }
211 /// Specify the command to start the pager (default: use "less")
212 #[cfg(feature = "paging")]
213 pub fn pager(&mut self, cmd: &'a str) -> &mut Self {
214 self.config.pager = Some(cmd);
215 self
216 }
218 /// Specify the lines that should be printed (default: all)
219 pub fn line_ranges(&mut self, ranges: LineRanges) -> &mut Self {
220 self.config.visible_lines = VisibleLines::Ranges(ranges);
221 self
222 }
224 /// Specify a line that should be highlighted (default: none).
225 /// This can be called multiple times to highlight more than one
226 /// line. See also: highlight_range.
227 pub fn highlight(&mut self, line: usize) -> &mut Self {
228 self.highlighted_lines.push(LineRange::new(line, line));
229 self
230 }
232 /// Specify a range of lines that should be highlighted (default: none).
233 /// This can be called multiple times to highlight more than one range
234 /// of lines.
235 pub fn highlight_range(&mut self, from: usize, to: usize) -> &mut Self {
236 self.highlighted_lines.push(LineRange::new(from, to));
237 self
238 }
240 /// Specify the highlighting theme
241 pub fn theme(&mut self, theme: impl AsRef<str>) -> &mut Self {
242 self.config.theme = theme.as_ref().to_owned();
243 self
244 }
246 /// Specify custom file extension / file name to syntax mappings
247 pub fn syntax_mapping(&mut self, mapping: SyntaxMapping<'a>) -> &mut Self {
248 self.config.syntax_mapping = mapping;
249 self
250 }
252 pub fn themes(&self) -> impl Iterator<Item = &str> {
253 self.assets.themes()
254 }
256 pub fn syntaxes(&self) -> impl Iterator<Item = &SyntaxReference> {
257 self.assets.syntaxes().iter()
258 }
260 /// Pretty-print all specified inputs. This method will "use" all stored inputs.
261 /// If you want to call 'print' multiple times, you have to call the appropriate
262 /// input_* methods again.
263 pub fn print(&mut self) -> Result<bool> {
264 self.config.highlighted_lines =
265 HighlightedLineRanges(LineRanges::from(self.highlighted_lines.clone()));
266 self.config.term_width = self
267 .term_width
268 .unwrap_or_else(|| Term::stdout().size().1 as usize);
270 let mut style_components = vec![];
271 if self.active_style_components.grid {
272 style_components.push(StyleComponent::Grid);
273 }
274 if self.active_style_components.header {
275 style_components.push(StyleComponent::Header);
276 }
277 if self.active_style_components.line_numbers {
278 style_components.push(StyleComponent::LineNumbers);
279 }
280 if self.active_style_components.snip {
281 style_components.push(StyleComponent::Snip);
282 }
283 if self.active_style_components.vcs_modification_markers {
284 #[cfg(feature = "git")]
285 style_components.push(StyleComponent::Changes);
286 }
287 self.config.style_components = StyleComponents::new(&style_components);
289 let mut inputs: Vec<Input> = vec![];
290 std::mem::swap(&mut inputs, &mut self.inputs);
292 let controller = Controller::new(&self.config, &self.assets);
293 controller.run(inputs)
294 }