From b23ff24ebcca480449fbcf6286d81e7e4429b2a8 Mon Sep 17 00:00:00 2001 From: eth-p <32112321+eth-p@users.noreply.github.com> Date: Mon, 10 Sep 2018 18:11:59 -0700 Subject: [PATCH] Added tab expansion preprocessing step. --- src/app.rs | 18 ++++++++++++++++++ src/main.rs | 1 + src/preprocessor.rs | 35 +++++++++++++++++++++++++++++++++++ src/printer.rs | 11 ++++++++++- 4 files changed, 64 insertions(+), 1 deletion(-) create mode 100644 src/preprocessor.rs diff --git a/src/app.rs b/src/app.rs index a9ac691e..f2e2a84c 100644 --- a/src/app.rs +++ b/src/app.rs @@ -41,6 +41,9 @@ pub struct Config<'a> { /// The character width of the terminal pub term_width: usize, + /// The width of tab characters. + pub tab_width: usize, + /// Whether or not to simply loop through all input (`cat` mode) pub loop_through: bool, @@ -276,6 +279,15 @@ impl App { 'unbuffered'). The output is always unbuffered - this option \ is simply ignored.", ), + ).arg( + Arg::with_name("tabs") + .long("tabs") + .short("t") + .takes_value(true) + .value_name("width") + .help("Sets the tab width.") + .long_help("Sets the tab width. Use a width of 0 to pass tabs through \ + directly"), ).arg( Arg::with_name("terminal-width") .long("terminal-width") @@ -393,6 +405,12 @@ impl App { || self.matches.value_of("color") == Some("always") || self.matches.value_of("decorations") == Some("always")), files, + tab_width: self + .matches + .value_of("tabs") + .and_then(|w| w.parse().ok()) + .or_else(|| env::var("BAT_TABS").ok().and_then(|w| w.parse().ok())) + .unwrap_or(8), theme: self .matches .value_of("theme") diff --git a/src/main.rs b/src/main.rs index ff87f9b8..bc3d11aa 100644 --- a/src/main.rs +++ b/src/main.rs @@ -24,6 +24,7 @@ mod decorations; mod diff; mod line_range; mod output; +mod preprocessor; mod printer; mod style; mod terminal; diff --git a/src/preprocessor.rs b/src/preprocessor.rs new file mode 100644 index 00000000..5d266abf --- /dev/null +++ b/src/preprocessor.rs @@ -0,0 +1,35 @@ +use console::AnsiCodeIterator; + +/// Expand tabs like an ANSI-enabled expand(1). +pub fn expand(line: &str, width: usize) -> String { + let mut buffer = String::with_capacity(line.len() * 2); + let mut cursor = 0; + + for chunk in AnsiCodeIterator::new(line) { + match chunk { + (text, true) => buffer.push_str(text), + (mut text, false) => { + while let Some(index) = text.find('\t') { + // Add previous text. + if index > 0 { + cursor += index; + buffer.push_str(&text[0..index]); + } + + // Add tab. + let spaces = width - (cursor % width); + cursor += spaces; + buffer.push_str(&*" ".repeat(spaces)); + + // Next. + text = &text[index + 1..text.len()]; + } + + cursor += text.len(); + buffer.push_str(text); + } + } + } + + buffer +} diff --git a/src/printer.rs b/src/printer.rs index afa2ac8b..dc0137da 100644 --- a/src/printer.rs +++ b/src/printer.rs @@ -16,6 +16,7 @@ use decorations::{Decoration, GridBorderDecoration, LineChangesDecoration, LineN use diff::get_git_diff; use diff::LineChanges; use errors::*; +use preprocessor::expand; use style::OutputWrap; use terminal::{as_terminal_escaped, to_ansi_color}; @@ -200,9 +201,17 @@ impl<'a> Printer for InteractivePrinter<'a> { line_number: usize, line_buffer: &[u8], ) -> Result<()> { - let line = String::from_utf8_lossy(&line_buffer); + let mut line = String::from_utf8_lossy(&line_buffer).to_string(); + + // Preprocess. + if self.config.tab_width > 0 { + line = expand(&line, self.config.tab_width); + } + + // Highlight. let regions = self.highlighter.highlight(line.as_ref()); + // Print. if out_of_range { return Ok(()); }