use std::io::Read; use std::path::Path; use console::Term; use crate::{ assets::HighlightingAssets, config::{Config, VisibleLines}, controller::Controller, error::Result, input, line_range::{HighlightedLineRanges, LineRange, LineRanges}, style::StyleComponent, SyntaxMapping, WrappingMode, }; #[cfg(feature = "paging")] use crate::paging::PagingMode; #[derive(Default)] struct ActiveStyleComponents { header_filename: bool, #[cfg(feature = "git")] vcs_modification_markers: bool, grid: bool, rule: bool, line_numbers: bool, snip: bool, } #[non_exhaustive] pub struct Syntax { pub name: String, pub file_extensions: Vec, } pub struct PrettyPrinter<'a> { inputs: Vec>, config: Config<'a>, assets: HighlightingAssets, highlighted_lines: Vec, term_width: Option, active_style_components: ActiveStyleComponents, } impl<'a> PrettyPrinter<'a> { pub fn new() -> Self { let config = Config { colored_output: true, true_color: true, ..Default::default() }; PrettyPrinter { inputs: vec![], config, assets: HighlightingAssets::from_binary(), highlighted_lines: vec![], term_width: None, active_style_components: ActiveStyleComponents::default(), } } /// Add an input which should be pretty-printed pub fn input(&mut self, input: Input<'a>) -> &mut Self { self.inputs.push(input); self } /// Adds multiple inputs which should be pretty-printed pub fn inputs(&mut self, inputs: impl IntoIterator>) -> &mut Self { for input in inputs { self.inputs.push(input); } self } /// Add a file which should be pretty-printed pub fn input_file(&mut self, path: impl AsRef) -> &mut Self { self.input(Input::from_file(path).kind("File")) } /// Add multiple files which should be pretty-printed pub fn input_files(&mut self, paths: I) -> &mut Self where I: IntoIterator, P: AsRef, { self.inputs(paths.into_iter().map(Input::from_file)) } /// Add STDIN as an input pub fn input_stdin(&mut self) -> &mut Self { self.inputs.push(Input::from_stdin()); self } /// Add a byte string as an input pub fn input_from_bytes(&mut self, content: &'a [u8]) -> &mut Self { self.input_from_reader(content) } /// Add a custom reader as an input pub fn input_from_reader(&mut self, reader: R) -> &mut Self { self.inputs.push(Input::from_reader(reader)); self } /// Specify the syntax file which should be used (default: auto-detect) pub fn language(&mut self, language: &'a str) -> &mut Self { self.config.language = Some(language); self } /// The character width of the terminal (default: autodetect) pub fn term_width(&mut self, width: usize) -> &mut Self { self.term_width = Some(width); self } /// The width of tab characters (default: None - do not turn tabs to spaces) pub fn tab_width(&mut self, tab_width: Option) -> &mut Self { self.config.tab_width = tab_width.unwrap_or(0); self } /// Whether or not the output should be colorized (default: true) pub fn colored_output(&mut self, yes: bool) -> &mut Self { self.config.colored_output = yes; self } /// Whether or not to output 24bit colors (default: true) pub fn true_color(&mut self, yes: bool) -> &mut Self { self.config.true_color = yes; self } /// Whether to show a header with the file name pub fn header(&mut self, yes: bool) -> &mut Self { self.active_style_components.header_filename = yes; self } /// Whether to show line numbers pub fn line_numbers(&mut self, yes: bool) -> &mut Self { self.active_style_components.line_numbers = yes; self } /// Whether to paint a grid, separating line numbers, git changes and the code pub fn grid(&mut self, yes: bool) -> &mut Self { self.active_style_components.grid = yes; self } /// Whether to paint a horizontal rule to delimit files pub fn rule(&mut self, yes: bool) -> &mut Self { self.active_style_components.rule = yes; self } /// Whether to show modification markers for VCS changes. This has no effect if /// the `git` feature is not activated. #[cfg(feature = "git")] pub fn vcs_modification_markers(&mut self, yes: bool) -> &mut Self { self.active_style_components.vcs_modification_markers = yes; self } /// Whether to print binary content or nonprintable characters (default: no) pub fn show_nonprintable(&mut self, yes: bool) -> &mut Self { self.config.show_nonprintable = yes; self } /// Whether to show "snip" markers between visible line ranges (default: no) pub fn snip(&mut self, yes: bool) -> &mut Self { self.active_style_components.snip = yes; self } /// Text wrapping mode (default: do not wrap) pub fn wrapping_mode(&mut self, mode: WrappingMode) -> &mut Self { self.config.wrapping_mode = mode; self } /// Whether or not to use ANSI italics (default: off) pub fn use_italics(&mut self, yes: bool) -> &mut Self { self.config.use_italic_text = yes; self } /// If and how to use a pager (default: no paging) #[cfg(feature = "paging")] pub fn paging_mode(&mut self, mode: PagingMode) -> &mut Self { self.config.paging_mode = mode; self } /// Specify the command to start the pager (default: use "less") #[cfg(feature = "paging")] pub fn pager(&mut self, cmd: &'a str) -> &mut Self { self.config.pager = Some(cmd); self } /// Specify the lines that should be printed (default: all) pub fn line_ranges(&mut self, ranges: LineRanges) -> &mut Self { self.config.visible_lines = VisibleLines::Ranges(ranges); self } /// Specify a line that should be highlighted (default: none). /// This can be called multiple times to highlight more than one /// line. See also: highlight_range. pub fn highlight(&mut self, line: usize) -> &mut Self { self.highlighted_lines.push(LineRange::new(line, line)); self } /// Specify a range of lines that should be highlighted (default: none). /// This can be called multiple times to highlight more than one range /// of lines. pub fn highlight_range(&mut self, from: usize, to: usize) -> &mut Self { self.highlighted_lines.push(LineRange::new(from, to)); self } /// Specify the maximum number of consecutive empty lines to print. pub fn squeeze_empty_lines(&mut self, maximum: Option) -> &mut Self { self.config.squeeze_lines = maximum; self } /// Specify the highlighting theme pub fn theme(&mut self, theme: impl AsRef) -> &mut Self { self.config.theme = theme.as_ref().to_owned(); self } /// Specify custom file extension / file name to syntax mappings pub fn syntax_mapping(&mut self, mapping: SyntaxMapping<'a>) -> &mut Self { self.config.syntax_mapping = mapping; self } pub fn themes(&self) -> impl Iterator { self.assets.themes() } pub fn syntaxes(&self) -> impl Iterator + '_ { // We always use assets from the binary, which are guaranteed to always // be valid, so get_syntaxes() can never fail here self.assets .get_syntaxes() .unwrap() .iter() .filter(|s| !s.hidden) .map(|s| Syntax { name: s.name.clone(), file_extensions: s.file_extensions.clone(), }) } /// Pretty-print all specified inputs. This method will "use" all stored inputs. /// If you want to call 'print' multiple times, you have to call the appropriate /// input_* methods again. pub fn print(&mut self) -> Result { let highlight_lines = std::mem::take(&mut self.highlighted_lines); self.config.highlighted_lines = HighlightedLineRanges(LineRanges::from(highlight_lines)); self.config.term_width = self .term_width .unwrap_or_else(|| Term::stdout().size().1 as usize); self.config.style_components.clear(); if self.active_style_components.grid { self.config.style_components.insert(StyleComponent::Grid); } if self.active_style_components.rule { self.config.style_components.insert(StyleComponent::Rule); } if self.active_style_components.header_filename { self.config .style_components .insert(StyleComponent::HeaderFilename); } if self.active_style_components.line_numbers { self.config .style_components .insert(StyleComponent::LineNumbers); } if self.active_style_components.snip { self.config.style_components.insert(StyleComponent::Snip); } #[cfg(feature = "git")] if self.active_style_components.vcs_modification_markers { self.config.style_components.insert(StyleComponent::Changes); } // Collect the inputs to print let inputs = std::mem::take(&mut self.inputs); // Run the controller let controller = Controller::new(&self.config, &self.assets); controller.run(inputs.into_iter().map(|i| i.into()).collect(), None) } } impl Default for PrettyPrinter<'_> { fn default() -> Self { Self::new() } } /// An input source for the pretty printer. pub struct Input<'a> { input: input::Input<'a>, } impl<'a> Input<'a> { /// A new input from a reader. pub fn from_reader(reader: R) -> Self { input::Input::from_reader(Box::new(reader)).into() } /// A new input from a file. pub fn from_file(path: impl AsRef) -> Self { input::Input::ordinary_file(path).into() } /// A new input from bytes. pub fn from_bytes(bytes: &'a [u8]) -> Self { Input::from_reader(bytes) } /// A new input from STDIN. pub fn from_stdin() -> Self { input::Input::stdin().into() } /// The filename of the input. /// This affects syntax detection and changes the default header title. pub fn name(mut self, name: impl AsRef) -> Self { self.input = self.input.with_name(Some(name)); self } /// The description for the type of input (e.g. "File") pub fn kind(mut self, kind: impl Into) -> Self { let kind = kind.into(); self.input .description_mut() .set_kind(if kind.is_empty() { None } else { Some(kind) }); self } /// The title for the input (e.g. "Descriptive title") /// This defaults to the file name. pub fn title(mut self, title: impl Into) -> Self { self.input.description_mut().set_title(Some(title.into())); self } } impl<'a> From> for Input<'a> { fn from(input: input::Input<'a>) -> Self { Self { input } } } impl<'a> From> for input::Input<'a> { fn from(Input { input }: Input<'a>) -> Self { input } }