From cfd33168af2a64f5ae9de22abf3994420ee222ea Mon Sep 17 00:00:00 2001 From: Fahmi Akbar Wildana Date: Sun, 6 Oct 2019 09:10:03 +0700 Subject: =?UTF-8?q?Fix=20all=20compile=20errors=20in=20lib.rs=20?= =?UTF-8?q?=F0=9F=9A=9A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Move {controller,output,printer,decorations}.rs into src/bin/ * Add `mod errors` from main.rs --- src/bin/controller.rs | 136 +++++++++++ src/bin/decorations.rs | 154 +++++++++++++ src/bin/output.rs | 118 ++++++++++ src/bin/printer.rs | 601 +++++++++++++++++++++++++++++++++++++++++++++++++ src/controller.rs | 136 ----------- src/decorations.rs | 154 ------------- src/lib.rs | 31 ++- src/output.rs | 118 ---------- src/printer.rs | 601 ------------------------------------------------- 9 files changed, 1035 insertions(+), 1014 deletions(-) create mode 100644 src/bin/controller.rs create mode 100644 src/bin/decorations.rs create mode 100644 src/bin/output.rs create mode 100644 src/bin/printer.rs delete mode 100644 src/controller.rs delete mode 100644 src/decorations.rs delete mode 100644 src/output.rs delete mode 100644 src/printer.rs diff --git a/src/bin/controller.rs b/src/bin/controller.rs new file mode 100644 index 00000000..b67d03c5 --- /dev/null +++ b/src/bin/controller.rs @@ -0,0 +1,136 @@ +use std::io::{self, Write}; +use std::path::Path; + +use crate::app::{Config, PagingMode}; +use crate::assets::HighlightingAssets; +use crate::errors::*; +use crate::inputfile::{InputFile, InputFileReader}; +use crate::line_range::{LineRanges, RangeCheckResult}; +use crate::output::OutputType; +use crate::printer::{InteractivePrinter, Printer, SimplePrinter}; + +pub struct Controller<'a> { + config: &'a Config<'a>, + assets: &'a HighlightingAssets, +} + +impl<'b> Controller<'b> { + pub fn new<'a>(config: &'a Config, assets: &'a HighlightingAssets) -> Controller<'a> { + Controller { config, assets } + } + + pub fn run(&self) -> Result { + // Do not launch the pager if NONE of the input files exist + let mut paging_mode = self.config.paging_mode; + if self.config.paging_mode != PagingMode::Never { + let call_pager = self.config.files.iter().any(|file| { + if let InputFile::Ordinary(path) = file { + return Path::new(path).exists(); + } else { + return true; + } + }); + if !call_pager { + paging_mode = PagingMode::Never; + } + } + + let mut output_type = OutputType::from_mode(paging_mode, self.config.pager)?; + let writer = output_type.handle()?; + let mut no_errors: bool = true; + + let stdin = io::stdin(); + + for input_file in &self.config.files { + match input_file.get_reader(&stdin) { + Err(error) => { + handle_error(&error); + no_errors = false; + } + Ok(mut reader) => { + let result = if self.config.loop_through { + let mut printer = SimplePrinter::new(); + self.print_file(reader, &mut printer, writer, *input_file) + } else { + let mut printer = InteractivePrinter::new( + &self.config, + &self.assets, + *input_file, + &mut reader, + ); + self.print_file(reader, &mut printer, writer, *input_file) + }; + + if let Err(error) = result { + handle_error(&error); + no_errors = false; + } + } + } + } + + Ok(no_errors) + } + + fn print_file<'a, P: Printer>( + &self, + reader: InputFileReader, + printer: &mut P, + writer: &mut dyn Write, + input_file: InputFile<'a>, + ) -> Result<()> { + printer.print_header(writer, input_file)?; + if !reader.first_line.is_empty() { + self.print_file_ranges(printer, writer, reader, &self.config.line_ranges)?; + } + printer.print_footer(writer)?; + + Ok(()) + } + + fn print_file_ranges( + &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; + + let mut first_range: bool = true; + let mut mid_range: bool = false; + + while reader.read_line(&mut line_buffer)? { + match line_ranges.check(line_number) { + RangeCheckResult::OutsideRange => { + // Call the printer in case we need to call the syntax highlighter + // for this line. However, set `out_of_range` to `true`. + printer.print_line(true, writer, line_number, &line_buffer)?; + mid_range = false; + } + + RangeCheckResult::InRange => { + if self.config.output_components.snip() { + if first_range { + first_range = false; + mid_range = true; + } else if !mid_range { + mid_range = true; + printer.print_snip(writer)?; + } + } + + printer.print_line(false, writer, line_number, &line_buffer)?; + } + RangeCheckResult::AfterLastRange => { + break; + } + } + + line_number += 1; + line_buffer.clear(); + } + Ok(()) + } +} diff --git a/src/bin/decorations.rs b/src/bin/decorations.rs new file mode 100644 index 00000000..7654c617 --- /dev/null +++ b/src/bin/decorations.rs @@ -0,0 +1,154 @@ +use crate::diff::LineChange; +use crate::printer::{Colors, InteractivePrinter}; +use ansi_term::Style; + +#[derive(Clone)] +pub struct DecorationText { + pub width: usize, + pub text: String, +} + +pub trait Decoration { + fn generate( + &self, + line_number: usize, + continuation: bool, + printer: &InteractivePrinter, + ) -> DecorationText; + fn width(&self) -> usize; +} + +pub struct LineNumberDecoration { + color: Style, + cached_wrap: DecorationText, + cached_wrap_invalid_at: usize, +} + +impl LineNumberDecoration { + pub fn new(colors: &Colors) -> Self { + LineNumberDecoration { + color: colors.line_number, + cached_wrap_invalid_at: 10000, + cached_wrap: DecorationText { + text: colors.line_number.paint(" ".repeat(4)).to_string(), + width: 4, + }, + } + } +} + +impl Decoration for LineNumberDecoration { + fn generate( + &self, + line_number: usize, + continuation: bool, + _printer: &InteractivePrinter, + ) -> DecorationText { + if continuation { + if line_number > self.cached_wrap_invalid_at { + let new_width = self.cached_wrap.width + 1; + return DecorationText { + text: self.color.paint(" ".repeat(new_width)).to_string(), + width: new_width, + }; + } + + self.cached_wrap.clone() + } else { + let plain: String = format!("{:4}", line_number); + DecorationText { + width: plain.len(), + text: self.color.paint(plain).to_string(), + } + } + } + + fn width(&self) -> usize { + 4 + } +} + +pub struct LineChangesDecoration { + cached_none: DecorationText, + cached_added: DecorationText, + cached_removed_above: DecorationText, + cached_removed_below: DecorationText, + cached_modified: DecorationText, +} + +impl LineChangesDecoration { + #[inline] + fn generate_cached(style: Style, text: &str) -> DecorationText { + DecorationText { + text: style.paint(text).to_string(), + width: text.chars().count(), + } + } + + pub fn new(colors: &Colors) -> Self { + LineChangesDecoration { + cached_none: Self::generate_cached(Style::default(), " "), + cached_added: Self::generate_cached(colors.git_added, "+"), + cached_removed_above: Self::generate_cached(colors.git_removed, "‾"), + cached_removed_below: Self::generate_cached(colors.git_removed, "_"), + cached_modified: Self::generate_cached(colors.git_modified, "~"), + } + } +} + +impl Decoration for LineChangesDecoration { + fn generate( + &self, + line_number: usize, + continuation: bool, + printer: &InteractivePrinter, + ) -> DecorationText { + if !continuation { + if let Some(ref changes) = printer.line_changes { + return match changes.get(&(line_number as u32)) { + Some(&LineChange::Added) => self.cached_added.clone(), + Some(&LineChange::RemovedAbove) => self.cached_removed_above.clone(), + Some(&LineChange::RemovedBelow) => self.cached_removed_below.clone(), + Some(&LineChange::Modified) => self.cached_modified.clone(), + _ => self.cached_none.clone(), + }; + } + } + + self.cached_none.clone() + } + + fn width(&self) -> usize { + self.cached_none.width + } +} + +pub struct GridBorderDecoration { + cached: DecorationText, +} + +impl GridBorderDecoration { + pub fn new(colors: &Colors) -> Self { + GridBorderDecoration { + cached: DecorationText { + text: colors.grid.paint("│").to_string(), + width: 1, + }, + } + } +} + +impl Decoration for GridBorderDecoration { + fn generate( + &self, + _line_number: usize, + _continuation: bool, + _printer: &InteractivePrinter, + ) -> DecorationText { + self.cached.clone() + } + + fn width(&self) -> usize { + self.cached.width + } +} diff --git a/src/bin/output.rs b/src/bin/output.rs new file mode 100644 index 00000000..e0b567a6 --- /dev/null +++ b/src/bin/output.rs @@ -0,0 +1,118 @@ +use std::env; +use std::ffi::OsString; +use std::io::{self, Write}; +use std::path::PathBuf; +use std::process::{Child, Command, Stdio}; + +use shell_words; + +use crate::app::PagingMode; +use crate::errors::*; + +pub enum OutputType { + Pager(Child), + Stdout(io::Stdout), +} + +impl OutputType { + pub fn from_mode(mode: PagingMode, pager: Option<&str>) -> Result { + use self::PagingMode::*; + Ok(match mode { + Always => OutputType::try_pager(false, pager)?, + QuitIfOneScreen => OutputType::try_pager(true, pager)?, + _ => OutputType::stdout(), + }) + } + + /// Try to launch the pager. Fall back to stdout in case of errors. + fn try_pager(quit_if_one_screen: bool, pager_from_config: Option<&str>) -> Result { + let mut replace_arguments_to_less = false; + + let pager_from_env = match (env::var("BAT_PAGER"), env::var("PAGER")) { + (Ok(bat_pager), _) => Some(bat_pager), + (_, Ok(pager)) => { + // less needs to be called with the '-R' option in order to properly interpret the + // ANSI color sequences printed by bat. If someone has set PAGER="less -F", we + // therefore need to overwrite the arguments and add '-R'. + // + // We only do this for PAGER (as it is not specific to 'bat'), not for BAT_PAGER + // or bats '--pager' command line option. + replace_arguments_to_less = true; + Some(pager) + } + _ => None, + }; + + let pager_from_config = pager_from_config.map(|p| p.to_string()); + + if pager_from_config.is_some() { + replace_arguments_to_less = false; + } + + let pager = pager_from_config + .or(pager_from_env) + .unwrap_or_else(|| String::from("less")); + + let pagerflags = + shell_words::split(&pager).chain_err(|| "Could not parse pager command.")?; + + match pagerflags.split_first() { + Some((pager_name, args)) => { + let mut pager_path = PathBuf::from(pager_name); + + if pager_path.file_stem() == Some(&OsString::from("bat")) { + pager_path = PathBuf::from("less"); + } + + let is_less = pager_path.file_stem() == Some(&OsString::from("less")); + + let mut process = if is_less { + let mut p = Command::new(&pager_path); + if args.is_empty() || replace_arguments_to_less { + p.args(vec!["--RAW-CONTROL-CHARS", "--no-init"]); + if quit_if_one_screen { + p.arg("--quit-if-one-screen"); + } + } else { + p.args(args); + } + p.env("LESSCHARSET", "UTF-8"); + p + } else { + let mut p = Command::new(&pager_path); + p.args(args); + p + }; + + Ok(process + .stdin(Stdio::piped()) + .spawn() + .map(OutputType::Pager) + .unwrap_or_else(|_| OutputType::stdout())) + } + None => Ok(OutputType::stdout()), + } + } + + fn stdout() -> Self { + OutputType::Stdout(io::stdout()) + } + + pub fn handle(&mut self) -> Result<&mut dyn Write> { + Ok(match *self { + OutputType::Pager(ref mut command) => command + .stdin + .as_mut() + .chain_err(|| "Could not open stdin for pager")?, + OutputType::Stdout(ref mut handle) => handle, + }) + } +} + +impl Drop for OutputType { + fn drop(&mut self) { + if let OutputType::Pager(ref mut command) = *self { + let _ = command.wait(); + } + } +} diff --git a/src/bin/printer.rs b/src/bin/printer.rs new file mode 100644 index 00000000..8da7af93 --- /dev/null +++ b/src/bin/printer.rs @@ -0,0 +1,601 @@ +use std::io::Write; +use std::vec::Vec; + +use ansi_term::Colour::{Fixed, Green, Red, Yellow}; +use ansi_term::Style; + +use console::AnsiCodeIterator; + +use syntect::easy::HighlightLines; +use syntect::highlighting::Color; +use syntect::highlighting::Theme; +use syntect::parsing::SyntaxSet; + +use content_inspector::ContentType; + +use encoding::all::{UTF_16BE, UTF_16LE}; +use encoding::{DecoderTrap, Encoding}; + +use crate::app::Config; +use crate::assets::HighlightingAssets; +use crate::decorations::{ + Decoration, GridBorderDecoration, LineChangesDecoration, LineNumberDecoration, +}; +use crate::diff::get_git_diff; +use crate::diff::LineChanges; +use crate::errors::*; +use crate::inputfile::{InputFile, InputFileReader}; +use crate::preprocessor::{expand_tabs, replace_nonprintable}; +use crate::style::OutputWrap; +use crate::terminal::{as_terminal_escaped, to_ansi_color}; + +pub trait Printer { + fn print_header(&mut self, handle: &mut dyn Write, file: InputFile) -> Result<()>; + fn print_footer(&mut self, handle: &mut dyn Write) -> Result<()>; + + fn print_snip(&mut self, handle: &mut dyn Write) -> Result<()>; + + fn print_line( + &mut self, + out_of_range: bool, + handle: &mut dyn Write, + line_number: usize, + line_buffer: &[u8], + ) -> Result<()>; +} + +pub struct SimplePrinter; + +impl SimplePrinter { + pub fn new() -> Self { + SimplePrinter {} + } +} + +impl Printer for SimplePrinter { + fn print_header(&mut self, _handle: &mut dyn Write, _file: InputFile) -> Result<()> { + Ok(()) + } + + fn print_footer(&mut self, _handle: &mut dyn Write) -> Result<()> { + Ok(()) + } + + fn print_snip(&mut self, _handle: &mut dyn Write) -> Result<()> { + Ok(()) + } + + fn print_line( + &mut self, + out_of_range: bool, + handle: &mut dyn Write, + _line_number: usize, + line_buffer: &[u8], + ) -> Result<()> { + if !out_of_range { + handle.write_all(line_buffer)?; + } + Ok(()) + } +} + +pub struct InteractivePrinter<'a> { + colors: Colors, + config: &'a Config<'a>, + decorations: Vec>, + panel_width: usize, + ansi_prefix_sgr: String, + content_type: Option, + pub line_changes: Option, + highlighter: Option>, + syntax_set: &'a SyntaxSet, + background_color_highlight: Option, +} + +impl<'a> InteractivePrinter<'a> { + pub fn new( + config: &'a Config, + assets: &'a HighlightingAssets, + file: InputFile, + reader: &mut InputFileReader, + ) -> Self { + let theme = assets.get_theme(&config.theme); + + let background_color_highlight = theme.settings.line_highlight; + + let colors = if config.colored_output { + Colors::colored(theme, config.true_color) + } else { + Colors::plain() + }; + + // Create decorations. + let mut decorations: Vec> = Vec::new(); + + if config.output_components.numbers() { + decorations.push(Box::new(LineNumberDecoration::new(&colors))); + } + + if config.output_components.changes() { + decorations.push(Box::new(LineChangesDecoration::new(&colors))); + } + + let mut panel_width: usize = + decorations.len() + decorations.iter().fold(0, |a, x| a + x.width()); + + // The grid border decoration isn't added until after the panel_width calculation, since the + // print_horizontal_line, print_header, and print_footer functions all assume the panel + // width is without the grid border. + if config.output_components.grid() && !decorations.is_empty() { + decorations.push(Box::new(GridBorderDecoration::new(&colors))); + } + + // Disable the panel if the terminal is too small (i.e. can't fit 5 characters with the + // panel showing). + if config.term_width + < (decorations.len() + decorations.iter().fold(0, |a, x| a + x.width())) + 5 + { + decorations.clear(); + panel_width = 0; + } + + let mut line_changes = None; + + let highlighter = if reader + .content_type + .map_or(false, |c| c.is_binary() && !config.show_nonprintable) + { + None + } else { + // Get the Git modifications + line_changes = if config.output_components.changes() { + match file { + InputFile::Ordinary(filename) => get_git_diff(filename), + _ => None, + } + } else { + None + }; + + // Determine the type of syntax for highlighting + let syntax = assets.get_syntax(config.language, file, reader, &config.syntax_mapping); + Some(HighlightLines::new(syntax, theme)) + }; + + InteractivePrinter { + panel_width, + colors, + config, + decorations, + content_type: reader.content_type, + ansi_prefix_sgr: String::new(), + line_changes, + highlighter, + syntax_set: &assets.syntax_set, + background_color_highlight, + } + } + + fn print_horizontal_line(&mut self, handle: &mut dyn Write, grid_char: char) -> Result<()> { + if self.panel_width == 0 { + writeln!( + handle, + "{}", + self.colors.grid.paint("─".repeat(self.config.term_width)) + )?; + } else { + let hline = "─".repeat(self.config.term_width - (self.panel_width + 1)); + let hline = format!("{}{}{}", "─".repeat(self.panel_width), grid_char, hline); + writeln!(handle, "{}", self.colors.grid.paint(hline))?; + } + + Ok(()) + } + + fn create_fake_panel(&self, text: &str) -> String { + if self.panel_width == 0 { + "".to_string() + } else { + let text_truncated: String = text.chars().take(self.panel_width - 1).collect(); + let text_filled: String = format!( + "{}{}", + text_truncated, + " ".repeat(self.panel_width - 1 - text_truncated.len()) + ); + if self.config.output_components.grid() { + format!("{} │ ", text_filled) + } else { + format!("{}", text_filled) + } + } + } + + fn preprocess(&self, text: &str, cursor: &mut usize) -> String { + if self.config.tab_width > 0 { + expand_tabs(text, self.config.tab_width, cursor) + } else { + text.to_string() + } + } +} + +impl<'a> Printer for InteractivePrinter<'a> { + fn print_header(&mut self, handle: &mut dyn Write, file: InputFile) -> Result<()> { + if !self.config.output_components.header() { + if Some(ContentType::BINARY) == self.content_type && !self.config.show_nonprintable { + let input = match file { + InputFile::Ordinary(filename) => format!("file '{}'", filename), + _ => "STDIN".into(), + }; + + writeln!( + handle, + "{}: Binary content from {} will not be printed to the terminal \ + (but will be present if the output of 'bat' is piped). You can use 'bat -A' \ + to show the binary file contents.", + Yellow.paint("[bat warning]"), + input + )?; + } else { + if self.config.output_components.grid() { + self.print_horizontal_line(handle, '┬')?; + } + } + return Ok(()); + } + + if self.config.output_components.grid() { + self.print_horizontal_line(handle, '┬')?; + + write!( + handle, + "{}{}", + " ".repeat(self.panel_width), + self.colors + .grid + .paint(if self.panel_width > 0 { "│ " } else { "" }), + )?; + } else { + write!(handle, "{}", " ".repeat(self.panel_width))?; + } + + let (prefix, name) = match file { + InputFile::Ordinary(filename) => ("File: ", filename), + _ => ("", "STDIN"), + }; + + let mode = match self.content_type { + Some(ContentType::BINARY) => " ", + Some(ContentType::UTF_16LE) => " ", + Some(ContentType::UTF_16BE) => " ", + None => " ", + _ => "", + }; + + writeln!( + handle, + "{}{}{}", + prefix, + self.colors.filename.paint(name), + mode + )?; + + if self.config.output_components.grid() { + if self.content_type.map_or(false, |c| c.is_text()) || self.config.show_nonprintable { + self.print_horizontal_line(handle, '┼')?; + } else { + self.print_horizontal_line(handle, '┴')?; + } + } + + Ok(()) + } + + fn print_footer(&mut self, handle: &mut dyn Write) -> Result<()> { + if self.config.output_components.grid() + && (self.content_type.map_or(false, |c| c.is_text()) || self.config.show_nonprintable) + { + self.print_horizontal_line(handle, '┴') + } else { + Ok(()) + } + } + + fn print_snip(&mut self, handle: &mut dyn Write) -> Result<()> { + let panel = self.create_fake_panel(" ..."); + let panel_count = panel.chars().count(); + + let title = "8<"; + let title_count = title.chars().count(); + + let snip_left = "─ ".repeat((self.config.term_width - panel_count - (title_count / 2)) / 4); + let snip_left_count = snip_left.chars().count(); // Can't use .len() with Unicode. + + let snip_right = + " ─".repeat((self.config.term_width - panel_count - snip_left_count - title_count) / 2); + + write!( + handle, + "{}\n", + self.colors + .grid + .paint(format!("{}{}{}{}", panel, snip_left, title, snip_right)) + )?; + + Ok(()) + } + + fn print_line( + &mut self, + out_of_range: bool, + handle: &mut dyn Write, + line_number: usize, + line_buffer: &[u8], + ) -> Result<()> { + let line = if self.config.show_nonprintable { + replace_nonprintable(&line_buffer, self.config.tab_width) + } else { + match self.content_type { + Some(ContentType::BINARY) | None => { + return Ok(()); + } + Some(ContentType::UTF_16LE) => UTF_16LE + .decode(&line_buffer, DecoderTrap::Replace) + .map_err(|_| "Invalid UTF-16LE")?, + Some(ContentType::UTF_16BE) => UTF_16BE + .decode(&line_buffer, DecoderTrap::Replace) + .map_err(|_| "Invalid UTF-16BE")?, + _ => String::from_utf8_lossy(&line_buffer).to_string(), + } + }; + + let regions = { + let highlighter = match self.highlighter { + Some(ref mut highlighter) => highlighter, + _ => { + return Ok(()); + } + }; + highlighter.highlight(line.as_ref(), self.syntax_set) + }; + + if out_of_range { + return Ok(()); + } + + let mut cursor: usize = 0; + let mut cursor_max: usize = self.config.term_width; + let mut cursor_total: usize = 0; + let mut panel_wrap: Option = None; + + // Line highlighting + let highlight_this_line = self + .config + .highlight_lines + .iter() + .any(|&l| l == line_number); + + let background_color = self + .background_color_highlight + .filter(|_| highlight_this_line); + + // Line decorations. + if self.panel_width > 0 { + let decorations = self + .decorations + .iter() + .map(|ref d| d.generate(line_number, false, self)) + .collect::>(); + + for deco in decorations { + write!(handle, "{} ", deco.text)?; + cursor_max -= deco.width + 1; + } + } + + // Line contents. + if self.config.output_wrap == OutputWrap::None { + let true_color = self.config.true_color; + let colored_output = self.config.colored_output; + let italics = self.config.use_italic_text; + + for &(style, region) in regions.iter() { + let text = &*self.preprocess(region, &mut cursor_total); + let text_trimmed = text.trim_end_matches(|c| c == '\r' || c == '\n'); + write!( + handle, + "{}", + as_terminal_escaped( + style, + text_trimmed, + true_color, + colored_output, + italics, + background_color + ) + )?; + + if text.len() != text_trimmed.len() { + if let Some(background_color) = background_color { + let mut ansi_style = Style::default(); + ansi_style.background = Some(to_ansi_color(background_color, true_color)); + let width = if cursor_total <= cursor_max { + cursor_max - cursor_total + 1 + } else { + 0 + }; + write!(handle, "{}", ansi_style.paint(" ".repeat(width)))?; + } + write!(handle, "{}", &text[text_trimmed.len()..])?; + } + } + + if line.bytes().next_back() != Some(b'\n') { + writeln!(handle)?; + } + } else { + for &(style, region) in regions.iter() { + let ansi_iterator = AnsiCodeIterator::new(region); + let mut ansi_prefix: String = String::new(); + for chunk in ansi_iterator { + match chunk { + // ANSI escape passthrough. + (text, true) => { + let is_ansi_csi = text.starts_with("\x1B["); + + if is_ansi_csi && text.ends_with('m') { + // It's an ANSI SGR sequence. + // We should be mostly safe to just append these together. + ansi_prefix.push_str(text); + if text == "\x1B[0m" { + self.ansi_prefix_sgr = "\x1B[0m".to_owned(); + } else { + self.ansi_prefix_sgr.push_str(text); + } + } else if is_ansi_csi { + // It's a regular CSI sequence. + // We should be mostly safe to just append these together. + ansi_prefix.push_str(text); + } else { + // It's probably a VT100 code. + // Passing it through is the safest bet. + write!(handle, "{}", text)?; + } + } + + // Regular text. + (text, false) => { + let text = self.preprocess( + text.trim_end_matches(|c| c == '\r' || c == '\n'), + &mut cursor_total, + ); + + let mut chars = text.chars(); + let mut remaining = text.chars().count(); + + while remaining > 0 { + let available = cursor_max - cursor; + + // It fits. + if remaining <= available { + let text = chars.by_ref().take(remaining).collect::(); + cursor += remaining; + + write!( + handle, + "{}", + as_terminal_escaped( + style, + &*format!( + "{}{}{}", + self.ansi_prefix_sgr, ansi_prefix, text + ), + self.config.true_color, + self.config.colored_output, + self.config.use_italic_text, + background_color + ) + )?; + break; + } + + // Generate wrap padding if not already generated. + if panel_wrap.is_none() { + panel_wrap = if self.panel_width > 0 { + Some(format!( + "{} ", + self.decorations + .iter() + .map(|ref d| d + .generate(line_number, true, self) + .text) + .collect::>() + .join(" ") + )) + } else { + Some("".to_string()) + } + } + + // It wraps. + let text = chars.by_ref().take(available).collect::(); + cursor = 0; + remaining -= available; + + write!( + handle, + "{}\n{}", + as_terminal_escaped( + style, + &*format!( + "{}{}{}", + self.ansi_prefix_sgr, ansi_prefix, text + ), + self.config.true_color, + self.config.colored_output, + self.config.use_italic_text, + background_color + ), + panel_wrap.clone().unwrap() + )?; + } + + // Clear the ANSI prefix buffer. + ansi_prefix.clear(); + } + } + } + } + + if let Some(background_color) = background_color { + let mut ansi_style = Style::default(); + ansi_style.background = + Some(to_ansi_color(background_color, self.config.true_color)); + + write!( + handle, + "{}", + ansi_style.paint(" ".repeat(cursor_max - cursor)) + )?; + } + writeln!(handle)?; + } + + Ok(()) + } +} + +const DEFAULT_GUTTER_COLOR: u8 = 238; + +#[derive(Default)] +pub struct Colors { + pub grid: Style, + pub filename: Style, + pub git_added: Style, + pub git_removed: Style, + pub git_modified: Style, + pub line_number: Style, +} + +impl Colors { + fn plain() -> Self { + Colors::default() + } + + fn colored(theme: &Theme, true_color: bool) -> Self { + let gutter_color = theme + .settings + .gutter_foreground + .map(|c| to_ansi_color(c, true_color)) + .unwrap_or(Fixed(DEFAULT_GUTTER_COLOR)); + + Colors { + grid: gutter_color.normal(), + filename: Style::new().bold(), + git_added: Green.normal(), + git_removed: Red.normal(), + git_modified: Yellow.normal(), + line_number: gutter_color.normal(), + } + } +} diff --git a/src/controller.rs b/src/controller.rs deleted file mode 100644 index b67d03c5..00000000 --- a/src/controller.rs +++ /dev/null @@ -1,136 +0,0 @@ -use std::io::{self, Write}; -use std::path::Path; - -use crate::app::{Config, PagingMode}; -use crate::assets::HighlightingAssets; -use crate::errors::*; -use crate::inputfile::{InputFile, InputFileReader}; -use crate::line_range::{LineRanges, RangeCheckResult}; -use crate::output::OutputType; -use crate::printer::{InteractivePrinter, Printer, SimplePrinter}; - -pub struct Controller<'a> { - config: &'a Config<'a>, - assets: &'a HighlightingAssets, -} - -impl<'b> Controller<'b> { - pub fn new<'a>(config: &'a Config, assets: &'a HighlightingAssets) -> Controller<'a> { - Controller { config, assets } - } - - pub fn run(&self) -> Result { - // Do not launch the pager if NONE of the input files exist - let mut paging_mode = self.config.paging_mode; - if self.config.paging_mode != PagingMode::Never { - let call_pager = self.config.files.iter().any(|file| { - if let InputFile::Ordinary(path) = file { - return Path::new(path).exists(); - } else { - return true; - } - }); - if !call_pager { - paging_mode = PagingMode::Never; - } - } - - let mut output_type = OutputType::from_mode(paging_mode, self.config.pager)?; - let writer = output_type.handle()?; - let mut no_errors: bool = true; - - let stdin = io::stdin(); - - for input_file in &self.config.files { - match input_file.get_reader(&stdin) { - Err(error) => { - handle_error(&error); - no_errors = false; - } - Ok(mut reader) => { - let result = if self.config.loop_through { - let mut printer = SimplePrinter::new(); - self.print_file(reader, &mut printer, writer, *input_file) - } else { - let mut printer = InteractivePrinter::new( - &self.config, - &self.assets, - *input_file, - &mut reader, - ); - self.print_file(reader, &mut printer, writer, *input_file) - }; - - if let Err(error) = result { - handle_error(&error); - no_errors = false; - } - } - } - } - - Ok(no_errors) - } - - fn print_file<'a, P: Printer>( - &self, - reader: InputFileReader, - printer: &mut P, - writer: &mut dyn Write, - input_file: InputFile<'a>, - ) -> Result<()> { - printer.print_header(writer, input_file)?; - if !reader.first_line.is_empty() { - self.print_file_ranges(printer, writer, reader, &self.config.line_ranges)?; - } - printer.print_footer(writer)?; - - Ok(()) - } - - fn print_file_ranges( - &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; - - let mut first_range: bool = true; - let mut mid_range: bool = false; - - while reader.read_line(&mut line_buffer)? { - match line_ranges.check(line_number) { - RangeCheckResult::OutsideRange => { - // Call the printer in case we need to call the syntax highlighter - // for this line. However, set `out_of_range` to `true`. - printer.print_line(true, writer, line_number, &line_buffer)?; - mid_range = false; - } - - RangeCheckResult::InRange => { - if self.config.output_components.snip() { - if first_range { - first_range = false; - mid_range = true; - } else if !mid_range { - mid_range = true; - printer.print_snip(writer)?; - } - } - - printer.print_line(false, writer, line_number, &line_buffer)?; - } - RangeCheckResult::AfterLastRange => { - break; - } - } - - line_number += 1; - line_buffer.clear(); - } - Ok(()) - } -} diff --git a/src/decorations.rs b/src/decorations.rs deleted file mode 100644 index 7654c617..00000000 --- a/src/decorations.rs +++ /dev/null @@ -1,154 +0,0 @@ -use crate::diff::LineChange; -use crate::printer::{Colors, InteractivePrinter}; -use ansi_term::Style; - -#[derive(Clone)] -pub struct DecorationText { - pub width: usize, - pub text: String, -} - -pub trait Decoration { - fn generate( - &self, - line_number: usize, - continuation: bool, - printer: &InteractivePrinter, - ) -> DecorationText; - fn width(&self) -> usize; -} - -pub struct LineNumberDecoration { - color: Style, - cached_wrap: DecorationText, - cached_wrap_invalid_at: usize, -} - -impl LineNumberDecoration { - pub fn new(colors: &Colors) -> Self { - LineNumberDecoration { - color: colors.line_number, - cached_wrap_invalid_at: 10000, - cached_wrap: DecorationText { - text: colors.line_number.paint(" ".repeat(4)).to_string(), - width: 4, - }, - } - } -} - -impl Decoration for LineNumberDecoration { - fn generate( - &self, - line_number: usize, - continuation: bool, - _printer: &InteractivePrinter, - ) -> DecorationText { - if continuation { - if line_number > self.cached_wrap_invalid_at { - let new_width = self.cached_wrap.width + 1; - return DecorationText { - text: self.color.paint(" ".repeat(new_width)).to_string(), - width: new_width, - }; - } - - self.cached_wrap.clone() - } else { - let plain: String = format!("{:4}", line_number); - DecorationText { - width: plain.len(), - text: self.color.paint(plain).to_string(), - } - } - } - - fn width(&self) -> usize { - 4 - } -} - -pub struct LineChangesDecoration { - cached_none: DecorationText, - cached_added: DecorationText, - cached_removed_above: DecorationText, - cached_removed_below: DecorationText, - cached_modified: DecorationText, -} - -impl LineChangesDecoration { - #[inline] - fn generate_cached(style: Style, text: &str) -> DecorationText { - DecorationText { - text: style.paint(text).to_string(), - width: text.chars().count(), - } - } - - pub fn new(colors: &Colors) -> Self { - LineChangesDecoration { - cached_none: Self::generate_cached(Style::default(), " "), - cached_added: Self::generate_cached(colors.git_added, "+"), - cached_removed_above: Self::generate_cached(colors.git_removed, "‾"), - cached_removed_below: Self::generate_cached(colors.git_removed, "_"), - cached_modified: Self::generate_cached(colors.git_modified, "~"), - } - } -} - -impl Decoration for LineChangesDecoration { - fn generate( - &self, - line_number: usize, - continuation: bool, - printer: &InteractivePrinter, - ) -> DecorationText { - if !continuation { - if let Some(ref changes) = printer.line_changes { - return match changes.get(&(line_number as u32)) { - Some(&LineChange::Added) => self.cached_added.clone(), - Some(&LineChange::RemovedAbove) => self.cached_removed_above.clone(), - Some(&LineChange::RemovedBelow) => self.cached_removed_below.clone(), - Some(&LineChange::Modified) => self.cached_modified.clone(), - _ => self.cached_none.clone(), - }; - } - } - - self.cached_none.clone() - } - - fn width(&self) -> usize { - self.cached_none.width - } -} - -pub struct GridBorderDecoration { - cached: DecorationText, -} - -impl GridBorderDecoration { - pub fn new(colors: &Colors) -> Self { - GridBorderDecoration { - cached: DecorationText { - text: colors.grid.paint("│").to_string(), - width: 1, - }, - } - } -} - -impl Decoration for GridBorderDecoration { - fn generate( - &self, - _line_number: usize, - _continuation: bool, - _printer: &InteractivePrinter, - ) -> DecorationText { - self.cached.clone() - } - - fn width(&self) -> usize { - self.cached.width - } -} diff --git a/src/lib.rs b/src/lib.rs index f8359055..9ee612fe 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -23,16 +23,37 @@ extern crate wild; mod assets; mod config; -mod controller; -mod decorations; mod diff; mod dirs; mod inputfile; mod line_range; -mod output; mod preprocessor; -mod printer; mod style; mod syntax_mapping; mod terminal; -mod util; \ No newline at end of file +mod util; + +mod errors { + error_chain! { + foreign_links { + Clap(::clap::Error); + Io(::std::io::Error); + SyntectError(::syntect::LoadingError); + ParseIntError(::std::num::ParseIntError); + } + } + + pub fn handle_error(error: &Error) { + match error { + Error(ErrorKind::Io(ref io_error), _) + if io_error.kind() == ::std::io::ErrorKind::BrokenPipe => + { + ::std::process::exit(0); + } + _ => { + use ansi_term::Colour::Red; + eprintln!("{}: {}", Red.paint("[bat error]"), error); + } + }; + } +} \ No newline at end of file diff --git a/src/output.rs b/src/output.rs deleted file mode 100644 index e0b567a6..00000000 --- a/src/output.rs +++ /dev/null @@ -1,118 +0,0 @@ -use std::env; -use std::ffi::OsString; -use std::io::{self, Write}; -use std::path::PathBuf; -use std::process::{Child, Command, Stdio}; - -use shell_words; - -use crate::app::PagingMode; -use crate::errors::*; - -pub enum OutputType { - Pager(Child), - Stdout(io::Stdout), -} - -impl OutputType { - pub fn from_mode(mode: PagingMode, pager: Option<&str>) -> Result { - use self::PagingMode::*; - Ok(match mode { - Always => OutputType::try_pager(false, pager)?, - QuitIfOneScreen => OutputType::try_pager(true, pager)?, - _ => OutputType::stdout(), - }) - } - - /// Try to launch the pager. Fall back to stdout in case of errors. - fn try_pager(quit_if_one_screen: bool, pager_from_config: Option<&str>) -> Result { - let mut replace_arguments_to_less = false; - - let pager_from_env = match (env::var("BAT_PAGER"), env::var("PAGER")) { - (Ok(bat_pager), _) => Some(bat_pager), - (_, Ok(pager)) => { - // less needs to be called with the '-R' option in order to properly interpret the - // ANSI color sequences printed by bat. If someone has set PAGER="less -F", we - // therefore need to overwrite the arguments and add '-R'. - // - // We only do this for PAGER (as it is not specific to 'bat'), not for BAT_PAGER - // or bats '--pager' command line option. - replace_arguments_to_less = true; - Some(pager) - } - _ => None, - }; - - let pager_from_config = pager_from_config.map(|p| p.to_string()); - - if pager_from_config.is_some() { - replace_arguments_to_less = false; - } - - let pager = pager_from_config - .or(pager_from_env) - .unwrap_or_else(|| String::from("less")); - - let pagerflags = - shell_words::split(&pager).chain_err(|| "Could not parse pager command.")?; - - match pagerflags.split_first() { - Some((pager_name, args)) => { - let mut pager_path = PathBuf::from(pager_name); - - if pager_path.file_stem() == Some(&OsString::from("bat")) { - pager_path = PathBuf::from("less"); - } - - let is_less = pager_path.file_stem() == Some(&OsString::from("less")); - - let mut process = if is_less { - let mut p = Command::new(&pager_path); - if args.is_empty() || replace_arguments_to_less { - p.args(vec!["--RAW-CONTROL-CHARS", "--no-init"]); - if quit_if_one_screen { - p.arg("--quit-if-one-screen"); - } - } else { - p.args(args); - } - p.env("LESSCHARSET", "UTF-8"); - p - } else { - let mut p = Command::new(&pager_path); - p.args(args); - p - }; - - Ok(process - .stdin(Stdio::piped()) - .spawn() - .map(OutputType::Pager) - .unwrap_or_else(|_| OutputType::stdout())) - } - None => Ok(OutputType::stdout()), - } - } - - fn stdout() -> Self { - OutputType::Stdout(io::stdout()) - } - - pub fn handle(&mut self) -> Result<&mut dyn Write> { - Ok(match *self { - OutputType::Pager(ref mut command) => command - .stdin - .as_mut() - .chain_err(|| "Could not open stdin for pager")?, - OutputType::Stdout(ref mut handle) => handle, - }) - } -} - -impl Drop for OutputType { - fn drop(&mut self) { - if let OutputType::Pager(ref mut command) = *self { - let _ = command.wait(); - } - } -} diff --git a/src/printer.rs b/src/printer.rs deleted file mode 100644 index 8da7af93..00000000 --- a/src/printer.rs +++ /dev/null @@ -1,601 +0,0 @@ -use std::io::Write; -use std::vec::Vec; - -use ansi_term::Colour::{Fixed, Green, Red, Yellow}; -use ansi_term::Style; - -use console::AnsiCodeIterator; - -use syntect::easy::HighlightLines; -use syntect::highlighting::Color; -use syntect::highlighting::Theme; -use syntect::parsing::SyntaxSet; - -use content_inspector::ContentType; - -use encoding::all::{UTF_16BE, UTF_16LE}; -use encoding::{DecoderTrap, Encoding}; - -use crate::app::Config; -use crate::assets::HighlightingAssets; -use crate::decorations::{ - Decoration, GridBorderDecoration, LineChangesDecoration, LineNumberDecoration, -}; -use crate::diff::get_git_diff; -use crate::diff::LineChanges; -use crate::errors::*; -use crate::inputfile::{InputFile, InputFileReader}; -use crate::preprocessor::{expand_tabs, replace_nonprintable}; -use crate::style::OutputWrap; -use crate::terminal::{as_terminal_escaped, to_ansi_color}; - -pub trait Printer { - fn print_header(&mut self, handle: &mut dyn Write, file: InputFile) -> Result<()>; - fn print_footer(&mut self, handle: &mut dyn Write) -> Result<()>; - - fn print_snip(&mut self, handle: &mut dyn Write) -> Result<()>; - - fn print_line( - &mut self, - out_of_range: bool, - handle: &mut dyn Write, - line_number: usize, - line_buffer: &[u8], - ) -> Result<()>; -} - -pub struct SimplePrinter; - -impl SimplePrinter { - pub fn new() -> Self { - SimplePrinter {} - } -} - -impl Printer for SimplePrinter { - fn print_header(&mut self, _handle: &mut dyn Write, _file: InputFile) -> Result<()> { - Ok(()) - } - - fn print_footer(&mut self, _handle: &mut dyn Write) -> Result<()> { - Ok(()) - } - - fn print_snip(&mut self, _handle: &mut dyn Write) -> Result<()> { - Ok(()) - } - - fn print_line( - &mut self, - out_of_range: bool, - handle: &mut dyn Write, - _line_number: usize, - line_buffer: &[u8], - ) -> Result<()> { - if !out_of_range { - handle.write_all(line_buffer)?; - } - Ok(()) - } -} - -pub struct InteractivePrinter<'a> { - colors: Colors, - config: &'a Config<'a>, - decorations: Vec>, - panel_width: usize, - ansi_prefix_sgr: String, - content_type: Option, - pub line_changes: Option, - highlighter: Option>, - syntax_set: &'a SyntaxSet, - background_color_highlight: Option, -} - -impl<'a> InteractivePrinter<'a> { - pub fn new( - config: &'a Config, - assets: &'a HighlightingAssets, - file: InputFile, - reader: &mut InputFileReader, - ) -> Self { - let theme = assets.get_theme(&config.theme); - - let background_color_highlight = theme.settings.line_highlight; - - let colors = if config.colored_output { - Colors::colored(theme, config.true_color) - } else { - Colors::plain() - }; - - // Create decorations. - let mut decorations: Vec> = Vec::new(); - - if config.output_components.numbers() { - decorations.push(Box::new(LineNumberDecoration::new(&colors))); - } - - if config.output_components.changes() { - decorations.push(Box::new(LineChangesDecoration::new(&colors))); - } - - let mut panel_width: usize = - decorations.len() + decorations.iter().fold(0, |a, x| a + x.width()); - - // The grid border decoration isn't added until after the panel_width calculation, since the - // print_horizontal_line, print_header, and print_footer functions all assume the panel - // width is without the grid border. - if config.output_components.grid() && !decorations.is_empty() { - decorations.push(Box::new(GridBorderDecoration::new(&colors))); - } - - // Disable the panel if the terminal is too small (i.e. can't fit 5 characters with the - // panel showing). - if config.term_width - < (decorations.len() + decorations.iter().fold(0, |a, x| a + x.width())) + 5 - { - decorations.clear(); - panel_width = 0; - } - - let mut line_changes = None; - - let highlighter = if reader - .content_type - .map_or(false, |c| c.is_binary() && !config.show_nonprintable) - { - None - } else { - // Get the Git modifications - line_changes = if config.output_components.changes() { - match file { - InputFile::Ordinary(filename) => get_git_diff(filename), - _ => None, - } - } else { - None - }; - - // Determine the type of syntax for highlighting - let syntax = assets.get_syntax(config.language, file, reader, &config.syntax_mapping); - Some(HighlightLines::new(syntax, theme)) - }; - - InteractivePrinter { - panel_width, - colors, - config, - decorations, - content_type: reader.content_type, - ansi_prefix_sgr: String::new(), - line_changes, - highlighter, - syntax_set: &assets.syntax_set, - background_color_highlight, - } - } - - fn print_horizontal_line(&mut self, handle: &mut dyn Write, grid_char: char) -> Result<()> { - if self.panel_width == 0 { - writeln!( - handle, - "{}", - self.colors.grid.paint("─".repeat(self.config.term_width)) - )?; - } else { - let hline = "─".repeat(self.config.term_width - (self.panel_width + 1)); - let hline = format!("{}{}{}", "─".repeat(self.panel_width), grid_char, hline); - writeln!(handle, "{}", self.colors.grid.paint(hline))?; - } - - Ok(()) - } - - fn create_fake_panel(&self, text: &str) -> String { - if self.panel_width == 0 { - "".to_string() - } else { - let text_truncated: String = text.chars().take(self.panel_width - 1).collect(); - let text_filled: String = format!( - "{}{}", - text_truncated, - " ".repeat(self.panel_width - 1 - text_truncated.len()) - ); - if self.config.output_components.grid() { - format!("{} │ ", text_filled) - } else { - format!("{}", text_filled) - } - } - } - - fn preprocess(&self, text: &str, cursor: &mut usize) -> String { - if self.config.tab_width > 0 { - expand_tabs(text, self.config.tab_width, cursor) - } else { - text.to_string() - } - } -} - -impl<'a> Printer for InteractivePrinter<'a> { - fn print_header(&mut self, handle: &mut dyn Write, file: InputFile) -> Result<()> { - if !self.config.output_components.header() { - if Some(ContentType::BINARY) == self.content_type && !self.config.show_nonprintable { - let input = match file { - InputFile::Ordinary(filename) => format!("file '{}'", filename), - _ => "STDIN".into(), - }; - - writeln!( - handle, - "{}: Binary content from {} will not be printed to the terminal \ - (but will be present if the output of 'bat' is piped). You can use 'bat -A' \ - to show the binary file contents.", - Yellow.paint("[bat warning]"), - input - )?; - } else { - if self.config.output_components.grid() { - self.print_horizontal_line(handle, '┬')?; - } - } - return Ok(()); - } - - if self.config.output_components.grid() { - self.print_horizontal_line(handle, '┬')?; - - write!( - handle, - "{}{}", - " ".repeat(self.panel_width), - self.colors - .grid - .paint(if self.panel_width > 0 { "│ " } else { "" }), - )?; - } else { - write!(handle, "{}", " ".repeat(self.panel_width))?; - } - - let (prefix, name) = match file { - InputFile::Ordinary(filename) => ("File: ", filename), - _ => ("", "STDIN"), - }; - - let mode = match self.content_type { - Some(ContentType::BINARY) => " ", - Some(ContentType::UTF_16LE) => " ", - Some(ContentType::UTF_16BE) => " ", - None => " ", - _ => "", - }; - - writeln!( - handle, - "{}{}{}", - prefix, - self.colors.filename.paint(name), - mode - )?; - - if self.config.output_components.grid() { - if self.content_type.map_or(false, |c| c.is_text()) || self.config.show_nonprintable { - self.print_horizontal_line(handle, '┼')?; - } else { - self.print_horizontal_line(handle, '┴')?; - } - } - - Ok(()) - } - - fn print_footer(&mut self, handle: &mut dyn Write) -> Result<()> { - if self.config.output_components.grid() - && (self.content_type.map_or(false, |c| c.is_text()) || self.config.show_nonprintable) - { - self.print_horizontal_line(handle, '┴') - } else { - Ok(()) - } - } - - fn print_snip(&mut self, handle: &mut dyn Write) -> Result<()> { - let panel = self.create_fake_panel(" ..."); - let panel_count = panel.chars().count(); - - let title = "8<"; - let title_count = title.chars().count(); - - let snip_left = "─ ".repeat((self.config.term_width - panel_count - (title_count / 2)) / 4); - let snip_left_count = snip_left.chars().count(); // Can't use .len() with Unicode. - - let snip_right = - " ─".repeat((self.config.term_width - panel_count - snip_left_count - title_count) / 2); - - write!( - handle, - "{}\n", - self.colors - .grid - .paint(format!("{}{}{}{}", panel, snip_left, title, snip_right)) - )?; - - Ok(()) - } - - fn print_line( - &mut self, - out_of_range: bool, - handle: &mut dyn Write, - line_number: usize, - line_buffer: &[u8], - ) -> Result<()> { - let line = if self.config.show_nonprintable { - replace_nonprintable(&line_buffer, self.config.tab_width) - } else { - match self.content_type { - Some(ContentType::BINARY) | None => { - return Ok(()); - } - Some(ContentType::UTF_16LE) => UTF_16LE - .decode(&line_buffer, DecoderTrap::Replace) - .map_err(|_| "Invalid UTF-16LE")?, - Some(ContentType::UTF_16BE) => UTF_16BE - .decode(&line_buffer, DecoderTrap::Replace) - .map_err(|_| "Invalid UTF-16BE")?, - _ => String::from_utf8_lossy(&line_buffer).to_string(), - } - }; - - let regions = { - let highlighter = match self.highlighter { - Some(ref mut highlighter) => highlighter, - _ => { - return Ok(()); - } - }; - highlighter.highlight(line.as_ref(), self.syntax_set) - }; - - if out_of_range { - return Ok(()); - } - - let mut cursor: usize = 0; - let mut cursor_max: usize = self.config.term_width; - let mut cursor_total: usize = 0; - let mut panel_wrap: Option = None; - - // Line highlighting - let highlight_this_line = self - .config - .highlight_lines - .iter() - .any(|&l| l == line_number); - - let background_color = self - .background_color_highlight - .filter(|_| highlight_this_line); - - // Line decorations. - if self.panel_width > 0 { - let decorations = self - .decorations - .iter() - .map(|ref d| d.generate(line_number, false, self)) - .collect::>(); - - for deco in decorations { - write!(handle, "{} ", deco.text)?; - cursor_max -= deco.width + 1; - } - } - - // Line contents. - if self.config.output_wrap == OutputWrap::None { - let true_color = self.config.true_color; - let colored_output = self.config.colored_output; - let italics = self.config.use_italic_text; - - for &(style, region) in regions.iter() { - let text = &*self.preprocess(region, &mut cursor_total); - let text_trimmed = text.trim_end_matches(|c| c == '\r' || c == '\n'); - write!( - handle, - "{}", - as_terminal_escaped( - style, - text_trimmed, - true_color, - colored_output, - italics, - background_color - ) - )?; - - if text.len() != tex