use std::collections::{BTreeMap, HashSet};
use std::env;
use std::io::Write;
use console::Term;
use syntect::{dumps::from_binary, highlighting::Theme};
use crate::assets::{HighlightingAssets, PRETTYPRINT_THEME_DEFAULT};
use crate::errors::*;
use crate::inputfile::{InputFile, InputFileReader};
use crate::line_range::RangeCheckResult;
use crate::output::OutputType;
use crate::printer::{InteractivePrinter, Printer};
#[cfg(windows)]
use ansi_term;
use crate::line_range::LineRanges;
use crate::style::{OutputComponent, OutputComponents, OutputWrap};
use crate::syntax_mapping::SyntaxMapping;
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum PagingMode {
Always,
QuitIfOneScreen,
Never,
Error,
}
impl Default for PagingMode {
fn default() -> Self {
PagingMode::Always
}
}
#[derive(Default, Builder, Debug)]
#[builder(name = "PrettyPrinter", setter(into))]
pub struct PrettyPrint {
#[builder(default = "\"unknown\".to_string()")]
language: String,
#[builder(default = "false")]
show_nonprintable: bool,
#[builder(default = "Term::stdout().size().1 as usize")]
term_width: usize,
#[builder(default = "0")]
tab_width: usize,
#[builder(default = "false")]
loop_through: bool,
#[builder(default = "true")]
colored_output: bool,
#[builder(default = "is_truecolor_terminal()")]
true_color: bool,
#[builder(default = "true")]
grid: bool,
#[builder(default = "true")]
header: bool,
#[builder(default = "true")]
line_numbers: bool,
#[builder(default = "OutputWrap::None")]
output_wrap: OutputWrap,
#[builder(default = "PagingMode::QuitIfOneScreen")]
paging_mode: PagingMode,
#[builder(default)]
line_ranges: LineRanges,
#[builder(default = "String::from(PRETTYPRINT_THEME_DEFAULT)")]
theme: String,
#[builder(default)]
syntax_mapping: SyntaxMapping,
#[builder(default = "None")]
load_syntax: Option<Vec<u8>>,
#[builder(default = "None")]
load_theme: Option<Vec<u8>>,
#[builder(default = "None")]
pager: Option<String>,
#[builder(default = "false")]
use_italic_text: bool,
}
impl From<&PrettyPrint> for PrettyPrinter {
fn from(printer: &PrettyPrint) -> Self {
PrettyPrinter::default()
.language(printer.language.clone())
.show_nonprintable(printer.show_nonprintable)
.term_width(printer.term_width)
.tab_width(printer.tab_width)
.loop_through(printer.loop_through)
.colored_output(printer.colored_output)
.true_color(printer.true_color)
.grid(printer.grid)
.header(printer.header)
.line_numbers(printer.line_numbers)
.output_wrap(printer.output_wrap)
.paging_mode(printer.paging_mode)
.line_ranges(printer.line_ranges.clone())
.theme(printer.theme.clone())
.syntax_mapping(printer.syntax_mapping.clone())
.pager(printer.pager.clone())
.use_italic_text(printer.use_italic_text)
.clone() }
}
impl PrettyPrint {
pub fn configure(&self) -> PrettyPrinter {
self.into()
}
pub fn file<T: Into<String>>(&self, filename: T) -> Result<()> {
let file_string = filename.into();
let input = if file_string == "-" {
InputFile::StdIn
} else {
InputFile::Ordinary(file_string)
};
self.run_controller(input, None)
}
pub fn string<T: Into<String>>(&self, input: T) -> Result<()> {
self.run_controller(InputFile::String(input.into()), None)
}
pub fn string_with_header<T: Into<String>>(&self, input: T, header: T) -> Result<()> {
self.run_controller(InputFile::String(input.into()), Some(header.into()))
}
pub fn get_themes(&self) -> BTreeMap<String, Theme> {
let assets = self.get_assets();
assets.theme_set.themes
}
fn run_controller(
&self,
input_file: InputFile,
header_overwrite: Option<String>,
) -> Result<()> {
#[cfg(windows)]
let _ = ansi_term::enable_ansi_support();
let assets = self.get_assets();
let mut reader = input_file.get_reader()?;
let lang_opt = match self.language.as_ref() {
"unknown" => None,
s => Some(s.to_string()),
};
let mut printer = InteractivePrinter::new(
&assets,
&input_file,
&mut reader,
self.get_output_components(),
self.theme.clone(),
self.colored_output,
self.true_color,
self.term_width,
lang_opt,
self.syntax_mapping.clone(),
self.tab_width,
self.show_nonprintable,
self.output_wrap,
self.use_italic_text,
);
let mut output_type = OutputType::from_mode(self.paging_mode, self.pager.clone())?;
let writer = output_type.handle()?;
self.print_file(reader, &mut printer, writer, &input_file, header_overwrite)?;
Ok(())
}
fn get_assets(&self) -> HighlightingAssets {
let syntax_set = self.load_syntax.as_ref().map(|b| from_binary(b.as_slice()));
let theme_set = self.load_theme.as_ref().map(|b| from_binary(b.as_slice()));
match (syntax_set, theme_set) {
(Some(syntax_set), Some(theme_set)) => HighlightingAssets {
syntax_set,
theme_set,
},
(Some(syntax_set), None) => HighlightingAssets {
syntax_set,
theme_set: HighlightingAssets::get_integrated_themeset(),
},
(None, Some(theme_set)) => HighlightingAssets {
syntax_set: HighlightingAssets::get_integrated_syntaxset(),
theme_set,
},
(None, None) => HighlightingAssets::new(),
}
}
fn get_output_components(&self) -> OutputComponents {
let mut components = HashSet::new();
if self.grid {
components.insert(OutputComponent::Grid);
}
if self.header {
components.insert(OutputComponent::Header);
}
if self.line_numbers {
components.insert(OutputComponent::Numbers);
}
OutputComponents(components)
}
fn print_file<'a, P: Printer>(
&self,
reader: InputFileReader,
printer: &mut P,
writer: &mut dyn Write,
input_file: &InputFile,
header_overwrite: Option<String>,
) -> Result<()> {
printer.print_header(writer, &input_file, header_overwrite)?;
self.print_file_ranges(printer, writer, reader, &self.line_ranges)?;
printer.print_footer(writer)?;
Ok(())
}
fn print_file_ranges<'a, P: Printer>(
&self,
printer: &mut P,
writer: &mut dyn Write,
mut reader: InputFileReader,
line_ranges: &LineRanges,
) -> Result<()> {
let mut line_buffer = Vec::new();
let mut line_number: usize = 1;
while reader.read_line(&mut line_buffer)? {
match line_ranges.check(line_number) {
RangeCheckResult::OutsideRange => {
printer.print_line(true, writer, line_number, &line_buffer)?;
}
RangeCheckResult::InRange => {
printer.print_line(false, writer, line_number, &line_buffer)?;
}
RangeCheckResult::AfterLastRange => {
break;
}
}
line_number += 1;
line_buffer.clear();
}
Ok(())
}
}
fn is_truecolor_terminal() -> bool {
env::var("COLORTERM")
.map(|colorterm| colorterm == "truecolor" || colorterm == "24bit")
.unwrap_or(false)
}