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