on 565a803 help sign in
bat/src/pretty_printer.rs
373 lines 12 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,
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 an input which should be pretty-printed
59 pub fn input(&mut self, input: Input<'a>) -> &mut Self {
60 self.inputs.push(input);
61 self
62 }
64 /// Adds multiple inputs which should be pretty-printed
65 pub fn inputs(&mut self, inputs: impl IntoIterator<Item = Input<'a>>) -> &mut Self {
66 for input in inputs {
67 self.inputs.push(input);
68 }
69 self
70 }
72 /// Add a file which should be pretty-printed
73 pub fn input_file(&mut self, path: impl AsRef<OsStr>) -> &mut Self {
74 self.input(Input::from_file(path).kind("File"))
75 }
77 /// Add multiple files which should be pretty-printed
78 pub fn input_files<I, P>(&mut self, paths: I) -> &mut Self
79 where
80 I: IntoIterator<Item = P>,
81 P: AsRef<OsStr>,
82 {
83 self.inputs(paths.into_iter().map(Input::from_file))
84 }
86 /// Add STDIN as an input
87 pub fn input_stdin(&mut self) -> &mut Self {
88 self.inputs.push(Input::from_stdin());
89 self
90 }
92 /// Add STDIN as an input (with customized name)
93 #[deprecated]
94 pub fn input_stdin_with_name(&mut self, name: impl AsRef<OsStr>) -> &mut Self {
95 self.inputs
96 .push(Input::from_stdin().name(name).kind("File"));
97 self
98 }
100 /// Add a byte string as an input
101 pub fn input_from_bytes(&mut self, content: &'a [u8]) -> &mut Self {
102 self.input_from_reader(content)
103 }
105 /// Add a byte string as an input (with customized name)
106 #[deprecated]
107 #[allow(deprecated)]
108 pub fn input_from_bytes_with_name(
109 &mut self,
110 content: &'a [u8],
111 name: impl AsRef<OsStr>,
112 ) -> &mut Self {
113 self.input_from_reader_with_name(content, name)
114 }
116 /// Add a custom reader as an input
117 pub fn input_from_reader<R: Read + 'a>(&mut self, reader: R) -> &mut Self {
118 self.inputs.push(Input::from_reader(reader));
119 self
120 }
122 /// Add a custom reader as an input (with customized name)
123 #[deprecated]
124 pub fn input_from_reader_with_name<R: Read + 'a>(
125 &mut self,
126 reader: R,
127 name: impl AsRef<OsStr>,
128 ) -> &mut Self {
129 self.inputs
130 .push(Input::from_reader(reader).name(name).kind("File"));
131 self
132 }
134 /// Specify the syntax file which should be used (default: auto-detect)
135 pub fn language(&mut self, language: &'a str) -> &mut Self {
136 self.config.language = Some(language);
137 self
138 }
140 /// The character width of the terminal (default: autodetect)
141 pub fn term_width(&mut self, width: usize) -> &mut Self {
142 self.term_width = Some(width);
143 self
144 }
146 /// The width of tab characters (default: None - do not turn tabs to spaces)
147 pub fn tab_width(&mut self, tab_width: Option<usize>) -> &mut Self {
148 self.config.tab_width = tab_width.unwrap_or(0);
149 self
150 }
152 /// Whether or not the output should be colorized (default: true)
153 pub fn colored_output(&mut self, yes: bool) -> &mut Self {
154 self.config.colored_output = yes;
155 self
156 }
158 /// Whether or not to output 24bit colors (default: true)
159 pub fn true_color(&mut self, yes: bool) -> &mut Self {
160 self.config.true_color = yes;
161 self
162 }
164 /// Whether to show a header with the file name
165 pub fn header(&mut self, yes: bool) -> &mut Self {
166 self.active_style_components.header = yes;
167 self
168 }
170 /// Whether to show line numbers
171 pub fn line_numbers(&mut self, yes: bool) -> &mut Self {
172 self.active_style_components.line_numbers = yes;
173 self
174 }
176 /// Whether to paint a grid, separating line numbers, git changes and the code
177 pub fn grid(&mut self, yes: bool) -> &mut Self {
178 self.active_style_components.grid = yes;
179 self
180 }
182 /// Whether to show modification markers for VCS changes. This has no effect if
183 /// the `git` feature is not activated.
184 #[cfg_attr(
185 not(feature = "git"),
186 deprecated(
187 note = "Using vcs_modification_markers without the 'git' feature has no effect. \
188 The function will be removed (for non-'git' use cases) in the future."
189 )
190 )]
191 #[allow(unused_variables)]
192 pub fn vcs_modification_markers(&mut self, yes: bool) -> &mut Self {
193 #[cfg(feature = "git")]
194 {
195 self.active_style_components.vcs_modification_markers = yes;
196 }
197 self
198 }
200 /// Whether to show "snip" markers between visible line ranges (default: no)
201 pub fn snip(&mut self, yes: bool) -> &mut Self {
202 self.active_style_components.snip = yes;
203 self
204 }
206 /// Text wrapping mode (default: do not wrap)
207 pub fn wrapping_mode(&mut self, mode: WrappingMode) -> &mut Self {
208 self.config.wrapping_mode = mode;
209 self
210 }
212 /// Whether or not to use ANSI italics (default: off)
213 pub fn use_italics(&mut self, yes: bool) -> &mut Self {
214 self.config.use_italic_text = yes;
215 self
216 }
218 /// If and how to use a pager (default: no paging)
219 #[cfg(feature = "paging")]
220 pub fn paging_mode(&mut self, mode: PagingMode) -> &mut Self {
221 self.config.paging_mode = mode;
222 self
223 }
225 /// Specify the command to start the pager (default: use "less")
226 #[cfg(feature = "paging")]
227 pub fn pager(&mut self, cmd: &'a str) -> &mut Self {
228 self.config.pager = Some(cmd);
229 self
230 }
232 /// Specify the lines that should be printed (default: all)
233 pub fn line_ranges(&mut self, ranges: LineRanges) -> &mut Self {
234 self.config.visible_lines = VisibleLines::Ranges(ranges);
235 self
236 }
238 /// Specify a line that should be highlighted (default: none).
239 /// This can be called multiple times to highlight more than one
240 /// line. See also: highlight_range.
241 pub fn highlight(&mut self, line: usize) -> &mut Self {
242 self.highlighted_lines.push(LineRange::new(line, line));
243 self
244 }
246 /// Specify a range of lines that should be highlighted (default: none).
247 /// This can be called multiple times to highlight more than one range
248 /// of lines.
249 pub fn highlight_range(&mut self, from: usize, to: usize) -> &mut Self {
250 self.highlighted_lines.push(LineRange::new(from, to));
251 self
252 }
254 /// Specify the highlighting theme
255 pub fn theme(&mut self, theme: impl AsRef<str>) -> &mut Self {
256 self.config.theme = theme.as_ref().to_owned();
257 self
258 }
260 /// Specify custom file extension / file name to syntax mappings
261 pub fn syntax_mapping(&mut self, mapping: SyntaxMapping<'a>) -> &mut Self {
262 self.config.syntax_mapping = mapping;
263 self
264 }
266 pub fn themes(&self) -> impl Iterator<Item = &str> {
267 self.assets.themes()
268 }
270 pub fn syntaxes(&self) -> impl Iterator<Item = &SyntaxReference> {
271 self.assets.syntaxes().iter()
272 }
274 /// Pretty-print all specified inputs. This method will "use" all stored inputs.
275 /// If you want to call 'print' multiple times, you have to call the appropriate
276 /// input_* methods again.
277 pub fn print(&mut self) -> Result<bool> {
278 self.config.highlighted_lines =
279 HighlightedLineRanges(LineRanges::from(self.highlighted_lines.clone()));
280 self.config.term_width = self
281 .term_width
282 .unwrap_or_else(|| Term::stdout().size().1 as usize);
284 let mut style_components = vec![];
285 if self.active_style_components.grid {
286 style_components.push(StyleComponent::Grid);
287 }
288 if self.active_style_components.header {
289 style_components.push(StyleComponent::Header);
290 }
291 if self.active_style_components.line_numbers {
292 style_components.push(StyleComponent::LineNumbers);
293 }
294 if self.active_style_components.snip {
295 style_components.push(StyleComponent::Snip);
296 }
297 if self.active_style_components.vcs_modification_markers {
298 #[cfg(feature = "git")]
299 style_components.push(StyleComponent::Changes);
300 }
301 self.config.style_components = StyleComponents::new(&style_components);
303 // Collect the inputs to print
304 let mut inputs: Vec<Input> = vec![];
305 std::mem::swap(&mut inputs, &mut self.inputs);
307 // Run the controller
308 let controller = Controller::new(&self.config, &self.assets);
309 controller.run(inputs.into_iter().map(|i| i.into()).collect())
310 }
313/// An input source for the pretty printer.
314pub struct Input<'a> {
315 input: input::Input<'a>,
318impl<'a> Input<'a> {
319 /// A new input from a reader.
320 pub fn from_reader<R: Read + 'a>(reader: R) -> Self {
321 input::Input::from_reader(Box::new(reader)).into()
322 }
324 /// A new input from a file.
325 pub fn from_file(path: impl AsRef<OsStr>) -> Self {
326 input::Input::ordinary_file(path.as_ref()).into()
327 }
329 /// A new input from bytes.
330 pub fn from_bytes(bytes: &'a [u8]) -> Self {
331 Input::from_reader(bytes).into()
332 }
334 /// A new input from STDIN.
335 pub fn from_stdin() -> Self {
336 input::Input::stdin().into()
337 }
339 /// The filename of the input.
340 /// This affects syntax detection and changes the default header title.
341 pub fn name(mut self, name: impl AsRef<OsStr>) -> Self {
342 self.input = self.input.with_name(Some(name.as_ref()));
343 self
344 }
346 /// The description for the type of input (e.g. "File")
347 pub fn kind(mut self, kind: impl Into<String>) -> Self {
348 let kind = kind.into();
349 self.input
350 .description_mut()
351 .set_kind(if kind.is_empty() { None } else { Some(kind) });
352 self
353 }
355 /// The title for the input (e.g. "http://example.com/example.txt")
356 /// This defaults to the file name.
357 pub fn title(mut self, title: impl Into<String>) -> Self {
358 self.input.description_mut().set_title(Some(title.into()));
359 self
360 }
363impl<'a> Into<Input<'a>> for input::Input<'a> {
364 fn into(self) -> Input<'a> {
365 Input { input: self }
366 }
369impl<'a> Into<input::Input<'a>> for Input<'a> {
370 fn into(self) -> input::Input<'a> {
371 self.input
372 }