From 4e4110bf5015009d584ed0ed5d09d19eb5393430 Mon Sep 17 00:00:00 2001 From: eth-p <32112321+eth-p@users.noreply.github.com> Date: Fri, 11 May 2018 21:59:26 -0700 Subject: [PATCH 01/15] Added line wrapping. --- src/app.rs | 4 +- src/printer.rs | 240 ++++++++++++++++++++++++++++++++++-------------- src/style.rs | 5 + src/terminal.rs | 50 +++++++--- 4 files changed, 214 insertions(+), 85 deletions(-) diff --git a/src/app.rs b/src/app.rs index 7cd78f48..fd2b5bde 100644 --- a/src/app.rs +++ b/src/app.rs @@ -4,7 +4,7 @@ use console::Term; use errors::*; use std::collections::HashSet; use std::env; -use style::{OutputComponent, OutputComponents}; +use style::{OutputComponent, OutputComponents, OutputWrap}; pub struct App { pub matches: ArgMatches<'static>, @@ -135,6 +135,7 @@ impl App { true_color: is_truecolor_terminal(), output_components: self.output_components()?, language: self.matches.value_of("language"), + output_wrap: OutputWrap::Character, colored_output: match self.matches.value_of("color") { Some("always") => true, Some("never") => false, @@ -191,6 +192,7 @@ impl App { pub struct Config<'a> { pub true_color: bool, + pub output_wrap: OutputWrap, pub output_components: OutputComponents, pub language: Option<&'a str>, pub colored_output: bool, diff --git a/src/printer.rs b/src/printer.rs index ed36ddac..65906a70 100644 --- a/src/printer.rs +++ b/src/printer.rs @@ -5,14 +5,21 @@ use errors::*; use std::io::Write; use syntect::highlighting; use terminal::as_terminal_escaped; +use style::OutputWrap; use Colors; -const PANEL_WIDTH: usize = 7; +const LINE_NUMBER_WIDTH: usize = 4; + +struct PrintSegment { + size: usize, + text: String +} pub struct Printer<'a> { handle: &'a mut Write, colors: Colors, config: &'a Config<'a>, + panel_width: usize, pub line_changes: Option, } @@ -24,12 +31,27 @@ impl<'a> Printer<'a> { Colors::plain() }; - Printer { + // Create the instance. + let mut instance = Printer { handle, colors, config, + panel_width: 0, line_changes: None, - } + }; + + // Generate the panel (gutter) width. + let decorations = instance.gen_decorations(0); + instance.panel_width = decorations.len() + + decorations.iter().fold(0, |a, x| a + x.size) + + if config.output_components.grid() { + 0 + } else { + 0 + }; + + // Return the instance. + return instance; } pub fn print_header(&mut self, filename: Option<&str>) -> Result<()> { @@ -43,8 +65,12 @@ impl<'a> Printer<'a> { write!( self.handle, "{}{} ", - " ".repeat(PANEL_WIDTH), - self.colors.grid.paint("│"), + " ".repeat(self.panel_width), + self.colors.grid.paint(if self.panel_width > 0 { + "│" + } else { + "" + }), )?; } @@ -75,86 +101,162 @@ impl<'a> Printer<'a> { line_number: usize, regions: &[(highlighting::Style, &str)], ) -> Result<()> { - let decorations = vec![ - self.print_line_number(line_number), - self.print_git_marker(line_number), - self.print_line_border(), - Some(as_terminal_escaped( - ®ions, - self.config.true_color, - self.config.colored_output, - )), - ]; + let mut cursor:usize = 0; + let mut cursor_max:usize = self.config.term_width - 2; - let grid_requested = self.config.output_components.grid(); - write!( - self.handle, - "{}", - decorations - .into_iter() - .filter_map(|dec| if grid_requested { - Some(dec.unwrap_or_else(|| " ".to_owned())) - } else { - dec - }) - .collect::>() - .join(" ") - )?; + // Line decoration. + let decorations = self.gen_decorations(line_number); + let gutter_width = decorations.len() + decorations.iter().fold(0, |a, x| a + x.size); - Ok(()) + if gutter_width > 0 { + cursor_max -= gutter_width; + write!(self.handle, "{} ", decorations + .iter() + .map(|seg| seg.text.to_owned()) + .collect::>() + .join(" "))?; + } + + // Grid border. + let border = if gutter_width > 0 && self.config.output_components.grid() { + self.gen_border() + } else { + PrintSegment { + size: 0, + text: "".to_owned() + } + }; + + cursor_max -= border.size; + write!(self.handle, "{} ", border.text)?; + + // Line contents. + for &(style, text) in regions.iter() { + let mut chars = text.chars().filter(|c| *c != '\n'); + let mut remaining = chars.clone().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!(self.handle, "{}", as_terminal_escaped( + style, + &*text, + self.config.true_color, + self.config.colored_output + ))?; + break; + } + + // It wraps. + if self.config.output_wrap == OutputWrap::Character { + let text = chars.by_ref().take(available).collect::(); + cursor = 0; + remaining -= available; + + write!(self.handle, "{}\n{}{} ", as_terminal_escaped( + style, + &*text, + self.config.true_color, + self.config.colored_output + ), " ".repeat(gutter_width), border.text.to_owned())?; + + continue; + } + } + } + + // Finished. + write!(self.handle, "\n")?; + return Ok(()); } - fn print_line_number(&self, line_number: usize) -> Option { + + + #[doc = " + Generates all the line decorations. + + # Arguments + * `line_number` - The line number. + "] + fn gen_decorations(&self, line_number: usize) -> Vec { + let mut decorations = Vec::new(); + if self.config.output_components.numbers() { - Some( - self.colors - .line_number - .paint(format!("{:4}", line_number)) - .to_string(), - ) - } else if self.config.output_components.grid() { - Some(" ".to_owned()) - } else { - None + decorations.push(self.gen_deco_line_number(line_number)); } - } - fn print_git_marker(&self, line_number: usize) -> Option { if self.config.output_components.changes() { - Some( - if let Some(ref changes) = self.line_changes { - match changes.get(&(line_number as u32)) { - Some(&LineChange::Added) => self.colors.git_added.paint("+"), - Some(&LineChange::RemovedAbove) => self.colors.git_removed.paint("‾"), - Some(&LineChange::RemovedBelow) => self.colors.git_removed.paint("_"), - Some(&LineChange::Modified) => self.colors.git_modified.paint("~"), - _ => Style::default().paint(" "), - } - } else { - Style::default().paint(" ") - }.to_string(), - ) - } else if self.config.output_components.grid() { - Some(" ".to_owned()) - } else { - None + decorations.push(self.gen_deco_line_changes(line_number)); + } + + return decorations; + } + + #[doc = " + Generates the decoration for displaying the line number. + + # Arguments + * `line_number` - The line number. + "] + fn gen_deco_line_number(&self, line_number: usize) -> PrintSegment { + let plain:String = format!("{:width$}", line_number, width = LINE_NUMBER_WIDTH); + let color = self.colors.line_number.paint(plain.to_owned()); + + return PrintSegment { + text: color.to_string(), + size: plain.len() } } - fn print_line_border(&self) -> Option { - if self.config.output_components.grid() { - Some(self.colors.grid.paint("│").to_string()) + #[doc = " + Generates the decoration for displaying the git changes. + + # Arguments + * `line_number` - The line number. + "] + fn gen_deco_line_changes(&self, line_number: usize) -> PrintSegment { + let color = if let Some(ref changes) = self.line_changes { + match changes.get(&(line_number as u32)) { + Some(&LineChange::Added) => self.colors.git_added.paint("+"), + Some(&LineChange::RemovedAbove) => self.colors.git_removed.paint("‾"), + Some(&LineChange::RemovedBelow) => self.colors.git_removed.paint("_"), + Some(&LineChange::Modified) => self.colors.git_modified.paint("~"), + _ => Style::default().paint(" "), + } } else { - None + Style::default().paint(" ") + }; + + return PrintSegment { + text: color.to_string(), + size: 1 + } + } + + #[doc = " + Generates the vertical grid border. + "] + fn gen_border(&self) -> PrintSegment { + return PrintSegment { + text: self.colors.grid.paint("│").to_string(), + size: 2 } } fn print_horizontal_line(&mut self, grid_char: char) -> Result<()> { - let hline = "─".repeat(self.config.term_width - (PANEL_WIDTH + 1)); - let hline = format!("{}{}{}", "─".repeat(PANEL_WIDTH), grid_char, hline); + if self.panel_width == 0 { + writeln!(self.handle, "{}", "─".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!(self.handle, "{}", self.colors.grid.paint(hline))?; + } - writeln!(self.handle, "{}", self.colors.grid.paint(hline))?; - - Ok(()) + return Ok(()); } } diff --git a/src/style.rs b/src/style.rs index ce55c7d7..c993eb34 100644 --- a/src/style.rs +++ b/src/style.rs @@ -13,6 +13,11 @@ pub enum OutputComponent { Plain, } +#[derive(Debug, Eq, PartialEq, Copy, Clone, Hash)] +pub enum OutputWrap { + Character +} + impl OutputComponent { pub fn components(&self, interactive_terminal: bool) -> &'static [OutputComponent] { match *self { diff --git a/src/terminal.rs b/src/terminal.rs index 01c2459b..d1057add 100644 --- a/src/terminal.rs +++ b/src/terminal.rs @@ -26,26 +26,46 @@ fn rgb2ansi(r: u8, g: u8, b: u8) -> u8 { } } +//pub fn as_terminal_escaped( +// v: &[(highlighting::Style, &str)], +// true_color: bool, +// colored: bool, +//) -> String { +// let mut s: String = String::new(); +// for &(ref style, text) in v.iter() { +// let style = if !colored { +// Style::default() +// } else if true_color { +// RGB(style.foreground.r, style.foreground.g, style.foreground.b).normal() +// } else { +// let ansi = rgb2ansi(style.foreground.r, style.foreground.g, style.foreground.b); +// Fixed(ansi).normal() +// }; +// +// write!(s, "{}", style.paint(text)).unwrap(); +// } +// +// s +//} + pub fn as_terminal_escaped( - v: &[(highlighting::Style, &str)], + color:highlighting::Style, + text: &str, true_color: bool, colored: bool, ) -> String { + let style = if !colored { + Style::default() + } else if true_color { + RGB(color.foreground.r, color.foreground.g, color.foreground.b).normal() + } else { + let ansi = rgb2ansi(color.foreground.r, color.foreground.g, color.foreground.b); + Fixed(ansi).normal() + }; + let mut s: String = String::new(); - for &(ref style, text) in v.iter() { - let style = if !colored { - Style::default() - } else if true_color { - RGB(style.foreground.r, style.foreground.g, style.foreground.b).normal() - } else { - let ansi = rgb2ansi(style.foreground.r, style.foreground.g, style.foreground.b); - Fixed(ansi).normal() - }; - - write!(s, "{}", style.paint(text)).unwrap(); - } - - s + write!(s, "{}", style.paint(text)).unwrap(); + return s; } #[test] From fcc36b1f79c56d7d5c5d17de4216189c39b3d0da Mon Sep 17 00:00:00 2001 From: eth-p <32112321+eth-p@users.noreply.github.com> Date: Fri, 11 May 2018 22:49:26 -0700 Subject: [PATCH 02/15] Forgot to add styling to the horizontal line. --- src/printer.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/printer.rs b/src/printer.rs index 65906a70..f19efe73 100644 --- a/src/printer.rs +++ b/src/printer.rs @@ -250,7 +250,7 @@ impl<'a> Printer<'a> { fn print_horizontal_line(&mut self, grid_char: char) -> Result<()> { if self.panel_width == 0 { - writeln!(self.handle, "{}", "─".repeat(self.config.term_width))?; + writeln!(self.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); From fc160b0dcdcd47708cd152b3a7c6848de61c1bef Mon Sep 17 00:00:00 2001 From: eth-p <32112321+eth-p@users.noreply.github.com> Date: Sat, 12 May 2018 06:32:23 -0700 Subject: [PATCH 03/15] PR #102 Review Changes - Reformatted code. - Removed leftover code. - Removed leftover comments. - Fixed compiling on Rust 1.24.0 --- src/app.rs | 2 +- src/printer.rs | 125 ++++++++++++++++++++++-------------------------- src/style.rs | 2 +- src/terminal.rs | 24 +--------- 4 files changed, 61 insertions(+), 92 deletions(-) diff --git a/src/app.rs b/src/app.rs index fd2b5bde..85dc542b 100644 --- a/src/app.rs +++ b/src/app.rs @@ -59,7 +59,7 @@ impl App { .use_delimiter(true) .takes_value(true) .possible_values(&[ - "auto", "full", "plain", "changes", "header", "grid", "numbers", + "auto", "full", "plain", "changes", "header", "grid", "numbers" ]) .default_value("auto") .help("Additional info to display along with content"), diff --git a/src/printer.rs b/src/printer.rs index f19efe73..e1b6b651 100644 --- a/src/printer.rs +++ b/src/printer.rs @@ -12,7 +12,7 @@ const LINE_NUMBER_WIDTH: usize = 4; struct PrintSegment { size: usize, - text: String + text: String, } pub struct Printer<'a> { @@ -42,13 +42,7 @@ impl<'a> Printer<'a> { // Generate the panel (gutter) width. let decorations = instance.gen_decorations(0); - instance.panel_width = decorations.len() - + decorations.iter().fold(0, |a, x| a + x.size) - + if config.output_components.grid() { - 0 - } else { - 0 - }; + instance.panel_width = decorations.len() + decorations.iter().fold(0, |a, x| a + x.size); // Return the instance. return instance; @@ -66,11 +60,9 @@ impl<'a> Printer<'a> { self.handle, "{}{} ", " ".repeat(self.panel_width), - self.colors.grid.paint(if self.panel_width > 0 { - "│" - } else { - "" - }), + self.colors + .grid + .paint(if self.panel_width > 0 { "│" } else { "" }), )?; } @@ -101,8 +93,8 @@ impl<'a> Printer<'a> { line_number: usize, regions: &[(highlighting::Style, &str)], ) -> Result<()> { - let mut cursor:usize = 0; - let mut cursor_max:usize = self.config.term_width - 2; + let mut cursor: usize = 0; + let mut cursor_max: usize = self.config.term_width - 2; // Line decoration. let decorations = self.gen_decorations(line_number); @@ -110,11 +102,15 @@ impl<'a> Printer<'a> { if gutter_width > 0 { cursor_max -= gutter_width; - write!(self.handle, "{} ", decorations - .iter() - .map(|seg| seg.text.to_owned()) - .collect::>() - .join(" "))?; + write!( + self.handle, + "{} ", + decorations + .iter() + .map(|seg| seg.text.to_owned()) + .collect::>() + .join(" ") + )?; } // Grid border. @@ -123,7 +119,7 @@ impl<'a> Printer<'a> { } else { PrintSegment { size: 0, - text: "".to_owned() + text: "".to_owned(), } }; @@ -133,7 +129,7 @@ impl<'a> Printer<'a> { // Line contents. for &(style, text) in regions.iter() { let mut chars = text.chars().filter(|c| *c != '\n'); - let mut remaining = chars.clone().count(); + let mut remaining = text.chars().filter(|c| *c != '\n').count(); while remaining > 0 { let available = cursor_max - cursor; @@ -143,12 +139,16 @@ impl<'a> Printer<'a> { let text = chars.by_ref().take(remaining).collect::(); cursor += remaining; - write!(self.handle, "{}", as_terminal_escaped( - style, - &*text, - self.config.true_color, - self.config.colored_output - ))?; + write!( + self.handle, + "{}", + as_terminal_escaped( + style, + &*text, + self.config.true_color, + self.config.colored_output + ) + )?; break; } @@ -158,12 +158,18 @@ impl<'a> Printer<'a> { cursor = 0; remaining -= available; - write!(self.handle, "{}\n{}{} ", as_terminal_escaped( - style, - &*text, - self.config.true_color, - self.config.colored_output - ), " ".repeat(gutter_width), border.text.to_owned())?; + write!( + self.handle, + "{}\n{}{} ", + as_terminal_escaped( + style, + &*text, + self.config.true_color, + self.config.colored_output + ), + " ".repeat(gutter_width), + border.text.to_owned() + )?; continue; } @@ -172,17 +178,10 @@ impl<'a> Printer<'a> { // Finished. write!(self.handle, "\n")?; - return Ok(()); + Ok(()) } - - - #[doc = " - Generates all the line decorations. - - # Arguments - * `line_number` - The line number. - "] + /// Generates all the line decorations. fn gen_decorations(&self, line_number: usize) -> Vec { let mut decorations = Vec::new(); @@ -197,28 +196,18 @@ impl<'a> Printer<'a> { return decorations; } - #[doc = " - Generates the decoration for displaying the line number. - - # Arguments - * `line_number` - The line number. - "] + /// Generates the decoration for displaying the line number. fn gen_deco_line_number(&self, line_number: usize) -> PrintSegment { - let plain:String = format!("{:width$}", line_number, width = LINE_NUMBER_WIDTH); + let plain: String = format!("{:width$}", line_number, width = LINE_NUMBER_WIDTH); let color = self.colors.line_number.paint(plain.to_owned()); return PrintSegment { text: color.to_string(), - size: plain.len() - } + size: plain.len(), + }; } - #[doc = " - Generates the decoration for displaying the git changes. - - # Arguments - * `line_number` - The line number. - "] + /// Generates the decoration for displaying the git changes. fn gen_deco_line_changes(&self, line_number: usize) -> PrintSegment { let color = if let Some(ref changes) = self.line_changes { match changes.get(&(line_number as u32)) { @@ -234,29 +223,31 @@ impl<'a> Printer<'a> { return PrintSegment { text: color.to_string(), - size: 1 - } + size: 1, + }; } - #[doc = " - Generates the vertical grid border. - "] + /// Generates the vertical grid border. fn gen_border(&self) -> PrintSegment { return PrintSegment { text: self.colors.grid.paint("│").to_string(), - size: 2 - } + size: 2, + }; } fn print_horizontal_line(&mut self, grid_char: char) -> Result<()> { if self.panel_width == 0 { - writeln!(self.handle, "{}", self.colors.grid.paint("─".repeat(self.config.term_width)))?; + writeln!( + self.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!(self.handle, "{}", self.colors.grid.paint(hline))?; } - return Ok(()); + Ok(()) } } diff --git a/src/style.rs b/src/style.rs index c993eb34..491d0fec 100644 --- a/src/style.rs +++ b/src/style.rs @@ -15,7 +15,7 @@ pub enum OutputComponent { #[derive(Debug, Eq, PartialEq, Copy, Clone, Hash)] pub enum OutputWrap { - Character + Character, } impl OutputComponent { diff --git a/src/terminal.rs b/src/terminal.rs index d1057add..b8a22aa4 100644 --- a/src/terminal.rs +++ b/src/terminal.rs @@ -26,30 +26,8 @@ fn rgb2ansi(r: u8, g: u8, b: u8) -> u8 { } } -//pub fn as_terminal_escaped( -// v: &[(highlighting::Style, &str)], -// true_color: bool, -// colored: bool, -//) -> String { -// let mut s: String = String::new(); -// for &(ref style, text) in v.iter() { -// let style = if !colored { -// Style::default() -// } else if true_color { -// RGB(style.foreground.r, style.foreground.g, style.foreground.b).normal() -// } else { -// let ansi = rgb2ansi(style.foreground.r, style.foreground.g, style.foreground.b); -// Fixed(ansi).normal() -// }; -// -// write!(s, "{}", style.paint(text)).unwrap(); -// } -// -// s -//} - pub fn as_terminal_escaped( - color:highlighting::Style, + color: highlighting::Style, text: &str, true_color: bool, colored: bool, From f95a23f9482804148923861cc5decf83ca7c3311 Mon Sep 17 00:00:00 2001 From: eth-p <32112321+eth-p@users.noreply.github.com> Date: Sat, 12 May 2018 12:07:41 -0700 Subject: [PATCH 04/15] Fix off-by-one error with text wrapping and --style grid --- src/printer.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/printer.rs b/src/printer.rs index e1b6b651..94b6ed7b 100644 --- a/src/printer.rs +++ b/src/printer.rs @@ -231,7 +231,7 @@ impl<'a> Printer<'a> { fn gen_border(&self) -> PrintSegment { return PrintSegment { text: self.colors.grid.paint("│").to_string(), - size: 2, + size: 1, }; } From cd26d403a3ba327b81030cb8ba5c2b2c6470028c Mon Sep 17 00:00:00 2001 From: eth-p <32112321+eth-p@users.noreply.github.com> Date: Sat, 12 May 2018 13:23:33 -0700 Subject: [PATCH 05/15] Fix padding, add --wrap argument, disable wrap for non-tty. Now bat(1) can be used like cat(1) again! --- src/printer.rs | 96 +++++++++++++++++++++++--------------------------- 1 file changed, 44 insertions(+), 52 deletions(-) diff --git a/src/printer.rs b/src/printer.rs index 94b6ed7b..87975100 100644 --- a/src/printer.rs +++ b/src/printer.rs @@ -117,67 +117,59 @@ impl<'a> Printer<'a> { let border = if gutter_width > 0 && self.config.output_components.grid() { self.gen_border() } else { - PrintSegment { - size: 0, - text: "".to_owned(), - } - }; + for &(style, text) in regions.iter() { + let mut chars = text.chars().filter(|c| *c != '\n'); + let mut remaining = text.chars().filter(|c| *c != '\n').count(); - cursor_max -= border.size; - write!(self.handle, "{} ", border.text)?; + while remaining > 0 { + let available = cursor_max - cursor; - // Line contents. - for &(style, text) in regions.iter() { - let mut chars = text.chars().filter(|c| *c != '\n'); - let mut remaining = text.chars().filter(|c| *c != '\n').count(); + // It fits. + if remaining <= available { + let text = chars.by_ref().take(remaining).collect::(); + cursor += remaining; - while remaining > 0 { - let available = cursor_max - cursor; + write!( + self.handle, + "{}", + as_terminal_escaped( + style, + &*text, + self.config.true_color, + self.config.colored_output + ) + )?; + break; + } - // It fits. - if remaining <= available { - let text = chars.by_ref().take(remaining).collect::(); - cursor += remaining; + // It wraps. + if self.config.output_wrap == OutputWrap::Character { + let text = chars.by_ref().take(available).collect::(); + cursor = 0; + remaining -= available; - write!( - self.handle, - "{}", - as_terminal_escaped( - style, - &*text, - self.config.true_color, - self.config.colored_output - ) - )?; - break; - } + write!( + self.handle, + "{}\n{}{}", + as_terminal_escaped( + style, + &*text, + self.config.true_color, + self.config.colored_output + ), + " ".repeat(gutter_width), + border.text.to_owned() + )?; - // It wraps. - if self.config.output_wrap == OutputWrap::Character { - let text = chars.by_ref().take(available).collect::(); - cursor = 0; - remaining -= available; - - write!( - self.handle, - "{}\n{}{} ", - as_terminal_escaped( - style, - &*text, - self.config.true_color, - self.config.colored_output - ), - " ".repeat(gutter_width), - border.text.to_owned() - )?; - - continue; + continue; + } } } + + write!(self.handle, "\n")?; } // Finished. - write!(self.handle, "\n")?; Ok(()) } @@ -230,8 +222,8 @@ impl<'a> Printer<'a> { /// Generates the vertical grid border. fn gen_border(&self) -> PrintSegment { return PrintSegment { - text: self.colors.grid.paint("│").to_string(), - size: 1, + text: self.colors.grid.paint("│ ").to_string(), + size: 2, }; } From d4b438b9d3211b6382f130309bdd54589908e7bd Mon Sep 17 00:00:00 2001 From: eth-p <32112321+eth-p@users.noreply.github.com> Date: Sat, 12 May 2018 13:44:10 -0700 Subject: [PATCH 06/15] Fix padding, add --wrap argument, disable wrap for non-tty. (Fixed) I'm not quite sure what was up with git on that last commit, but it's all properly committed now. --- src/app.rs | 19 ++++++++++++++++++- src/printer.rs | 27 ++++++++++++++++++++++++--- src/style.rs | 1 + 3 files changed, 43 insertions(+), 4 deletions(-) diff --git a/src/app.rs b/src/app.rs index 85dc542b..803cc0cd 100644 --- a/src/app.rs +++ b/src/app.rs @@ -80,6 +80,14 @@ impl App { .default_value("auto") .help("When to use the pager"), ) + .arg( + Arg::with_name("wrap") + .long("wrap") + .takes_value(true) + .possible_values(&["character", "never"]) + .default_value("character") + .help("When to wrap text"), + ) .arg( Arg::with_name("list-languages") .long("list-languages") @@ -135,7 +143,16 @@ impl App { true_color: is_truecolor_terminal(), output_components: self.output_components()?, language: self.matches.value_of("language"), - output_wrap: OutputWrap::Character, + output_wrap: if ! self.interactive_output { + // We don't have the tty width when piping to another program. + // There's no point in wrapping when this is the case. + OutputWrap::None + } else { + match self.matches.value_of("wrap") { + Some("character") => OutputWrap::Character, + Some("never") | _ => OutputWrap::None, + } + }, colored_output: match self.matches.value_of("color") { Some("always") => true, Some("never") => false, diff --git a/src/printer.rs b/src/printer.rs index 87975100..6867da96 100644 --- a/src/printer.rs +++ b/src/printer.rs @@ -94,7 +94,7 @@ impl<'a> Printer<'a> { regions: &[(highlighting::Style, &str)], ) -> Result<()> { let mut cursor: usize = 0; - let mut cursor_max: usize = self.config.term_width - 2; + let mut cursor_max: usize = self.config.term_width; // Line decoration. let decorations = self.gen_decorations(line_number); @@ -116,6 +116,27 @@ impl<'a> Printer<'a> { // Grid border. let border = if gutter_width > 0 && self.config.output_components.grid() { self.gen_border() + } else { + PrintSegment { + size: 0, + text: "".to_owned(), + } + }; + + cursor_max -= border.size; + write!(self.handle, "{}", border.text)?; + + // Line contents. + if self.config.output_wrap == OutputWrap::None { + let true_color = self.config.true_color; + let colored_output = self.config.colored_output; + + write!(self.handle, "{}", + regions.iter() + .map(|&(style, text)| as_terminal_escaped(style, text, true_color, colored_output)) + .collect::>() + .join(" ") + )?; } else { for &(style, text) in regions.iter() { let mut chars = text.chars().filter(|c| *c != '\n'); @@ -136,7 +157,7 @@ impl<'a> Printer<'a> { style, &*text, self.config.true_color, - self.config.colored_output + self.config.colored_output, ) )?; break; @@ -155,7 +176,7 @@ impl<'a> Printer<'a> { style, &*text, self.config.true_color, - self.config.colored_output + self.config.colored_output, ), " ".repeat(gutter_width), border.text.to_owned() diff --git a/src/style.rs b/src/style.rs index 491d0fec..c788f661 100644 --- a/src/style.rs +++ b/src/style.rs @@ -16,6 +16,7 @@ pub enum OutputComponent { #[derive(Debug, Eq, PartialEq, Copy, Clone, Hash)] pub enum OutputWrap { Character, + None } impl OutputComponent { From b4cfc9633c30cbf6d98fcce670caf4bf22dbc4bb Mon Sep 17 00:00:00 2001 From: eth-p <32112321+eth-p@users.noreply.github.com> Date: Sat, 12 May 2018 14:02:47 -0700 Subject: [PATCH 07/15] Fix double spaces when outputting without wrapping. --- src/printer.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/printer.rs b/src/printer.rs index 6867da96..91aaa36a 100644 --- a/src/printer.rs +++ b/src/printer.rs @@ -135,7 +135,7 @@ impl<'a> Printer<'a> { regions.iter() .map(|&(style, text)| as_terminal_escaped(style, text, true_color, colored_output)) .collect::>() - .join(" ") + .join("") )?; } else { for &(style, text) in regions.iter() { From 9f005d115d8d1fc3de4bb6ef09f0ce79ad142426 Mon Sep 17 00:00:00 2001 From: eth-p <32112321+eth-p@users.noreply.github.com> Date: Sat, 12 May 2018 14:04:54 -0700 Subject: [PATCH 08/15] Update snapshot test files. --- .../output/changes,grid,header.snapshot.txt | 50 +++++++++---------- .../output/changes,grid.snapshot.txt | 44 ++++++++-------- .../output/grid,header,numbers.snapshot.txt | 50 +++++++++---------- .../snapshots/output/grid,header.snapshot.txt | 50 +++++++++---------- .../output/grid,numbers.snapshot.txt | 44 ++++++++-------- tests/snapshots/output/grid.snapshot.txt | 44 ++++++++-------- 6 files changed, 141 insertions(+), 141 deletions(-) diff --git a/tests/snapshots/output/changes,grid,header.snapshot.txt b/tests/snapshots/output/changes,grid,header.snapshot.txt index cb53d7a8..cf739b4a 100644 --- a/tests/snapshots/output/changes,grid,header.snapshot.txt +++ b/tests/snapshots/output/changes,grid,header.snapshot.txt @@ -1,25 +1,25 @@ -───────┬──────────────────────────────────────────────────────────────────────── - │ File: sample.rs -───────┼──────────────────────────────────────────────────────────────────────── - │ struct Rectangle { - │ width: u32, - │ height: u32, - │ } - │ - _ │ fn main() { - │ let rect1 = Rectangle { width: 30, height: 50 }; - │ - │ println!( - ~ │ "The perimeter of the rectangle is {} pixels.", - ~ │ perimeter(&rect1) - │ ); - │ } - │ - │ fn area(rectangle: &Rectangle) -> u32 { - │ rectangle.width * rectangle.height - │ } - + │ - + │ fn perimeter(rectangle: &Rectangle) -> u32 { - + │ (rectangle.width + rectangle.height) * 2 - + │ } -───────┴──────────────────────────────────────────────────────────────────────── +──┬───────────────────────────────────────────────────────────────────────────── + │ File: sample.rs +──┼───────────────────────────────────────────────────────────────────────────── + │ struct Rectangle { + │ width: u32, + │ height: u32, + │ } + │ +_ │ fn main() { + │ let rect1 = Rectangle { width: 30, height: 50 }; + │ + │ println!( +~ │ "The perimeter of the rectangle is {} pixels.", +~ │ perimeter(&rect1) + │ ); + │ } + │ + │ fn area(rectangle: &Rectangle) -> u32 { + │ rectangle.width * rectangle.height + │ } ++ │ ++ │ fn perimeter(rectangle: &Rectangle) -> u32 { ++ │ (rectangle.width + rectangle.height) * 2 ++ │ } +──┴───────────────────────────────────────────────────────────────────────────── diff --git a/tests/snapshots/output/changes,grid.snapshot.txt b/tests/snapshots/output/changes,grid.snapshot.txt index ff7ea09d..8f20a69e 100644 --- a/tests/snapshots/output/changes,grid.snapshot.txt +++ b/tests/snapshots/output/changes,grid.snapshot.txt @@ -1,22 +1,22 @@ - │ struct Rectangle { - │ width: u32, - │ height: u32, - │ } - │ - _ │ fn main() { - │ let rect1 = Rectangle { width: 30, height: 50 }; - │ - │ println!( - ~ │ "The perimeter of the rectangle is {} pixels.", - ~ │ perimeter(&rect1) - │ ); - │ } - │ - │ fn area(rectangle: &Rectangle) -> u32 { - │ rectangle.width * rectangle.height - │ } - + │ - + │ fn perimeter(rectangle: &Rectangle) -> u32 { - + │ (rectangle.width + rectangle.height) * 2 - + │ } -───────┴──────────────────────────────────────────────────────────────────────── + │ struct Rectangle { + │ width: u32, + │ height: u32, + │ } + │ +_ │ fn main() { + │ let rect1 = Rectangle { width: 30, height: 50 }; + │ + │ println!( +~ │ "The perimeter of the rectangle is {} pixels.", +~ │ perimeter(&rect1) + │ ); + │ } + │ + │ fn area(rectangle: &Rectangle) -> u32 { + │ rectangle.width * rectangle.height + │ } ++ │ ++ │ fn perimeter(rectangle: &Rectangle) -> u32 { ++ │ (rectangle.width + rectangle.height) * 2 ++ │ } +──┴───────────────────────────────────────────────────────────────────────────── diff --git a/tests/snapshots/output/grid,header,numbers.snapshot.txt b/tests/snapshots/output/grid,header,numbers.snapshot.txt index e842b5cb..56696b17 100644 --- a/tests/snapshots/output/grid,header,numbers.snapshot.txt +++ b/tests/snapshots/output/grid,header,numbers.snapshot.txt @@ -1,25 +1,25 @@ -───────┬──────────────────────────────────────────────────────────────────────── - │ File: sample.rs -───────┼──────────────────────────────────────────────────────────────────────── - 1 │ struct Rectangle { - 2 │ width: u32, - 3 │ height: u32, - 4 │ } - 5 │ - 6 │ fn main() { - 7 │ let rect1 = Rectangle { width: 30, height: 50 }; - 8 │ - 9 │ println!( - 10 │ "The perimeter of the rectangle is {} pixels.", - 11 │ perimeter(&rect1) - 12 │ ); - 13 │ } - 14 │ - 15 │ fn area(rectangle: &Rectangle) -> u32 { - 16 │ rectangle.width * rectangle.height - 17 │ } - 18 │ - 19 │ fn perimeter(rectangle: &Rectangle) -> u32 { - 20 │ (rectangle.width + rectangle.height) * 2 - 21 │ } -───────┴──────────────────────────────────────────────────────────────────────── +─────┬────────────────────────────────────────────────────────────────────────── + │ File: sample.rs +─────┼────────────────────────────────────────────────────────────────────────── + 1 │ struct Rectangle { + 2 │ width: u32, + 3 │ height: u32, + 4 │ } + 5 │ + 6 │ fn main() { + 7 │ let rect1 = Rectangle { width: 30, height: 50 }; + 8 │ + 9 │ println!( + 10 │ "The perimeter of the rectangle is {} pixels.", + 11 │ perimeter(&rect1) + 12 │ ); + 13 │ } + 14 │ + 15 │ fn area(rectangle: &Rectangle) -> u32 { + 16 │ rectangle.width * rectangle.height + 17 │ } + 18 │ + 19 │ fn perimeter(rectangle: &Rectangle) -> u32 { + 20 │ (rectangle.width + rectangle.height) * 2 + 21 │ } +─────┴────────────────────────────────────────────────────────────────────────── diff --git a/tests/snapshots/output/grid,header.snapshot.txt b/tests/snapshots/output/grid,header.snapshot.txt index a6fbca2d..781efb81 100644 --- a/tests/snapshots/output/grid,header.snapshot.txt +++ b/tests/snapshots/output/grid,header.snapshot.txt @@ -1,25 +1,25 @@ -───────┬──────────────────────────────────────────────────────────────────────── - │ File: sample.rs -───────┼──────────────────────────────────────────────────────────────────────── - │ struct Rectangle { - │ width: u32, - │ height: u32, - │ } - │ - │ fn main() { - │ let rect1 = Rectangle { width: 30, height: 50 }; - │ - │ println!( - │ "The perimeter of the rectangle is {} pixels.", - │ perimeter(&rect1) - │ ); - │ } - │ - │ fn area(rectangle: &Rectangle) -> u32 { - │ rectangle.width * rectangle.height - │ } - │ - │ fn perimeter(rectangle: &Rectangle) -> u32 { - │ (rectangle.width + rectangle.height) * 2 - │ } -───────┴──────────────────────────────────────────────────────────────────────── +──────────────────────────────────────────────────────────────────────────────── + File: sample.rs +──────────────────────────────────────────────────────────────────────────────── +struct Rectangle { + width: u32, + height: u32, +} + +fn main() { + let rect1 = Rectangle { width: 30, height: 50 }; + + println!( + "The perimeter of the rectangle is {} pixels.", + perimeter(&rect1) + ); +} + +fn area(rectangle: &Rectangle) -> u32 { + rectangle.width * rectangle.height +} + +fn perimeter(rectangle: &Rectangle) -> u32 { + (rectangle.width + rectangle.height) * 2 +} +──────────────────────────────────────────────────────────────────────────────── diff --git a/tests/snapshots/output/grid,numbers.snapshot.txt b/tests/snapshots/output/grid,numbers.snapshot.txt index 0186dac3..2a76f8d3 100644 --- a/tests/snapshots/output/grid,numbers.snapshot.txt +++ b/tests/snapshots/output/grid,numbers.snapshot.txt @@ -1,22 +1,22 @@ - 1 │ struct Rectangle { - 2 │ width: u32, - 3 │ height: u32, - 4 │ } - 5 │ - 6 │ fn main() { - 7 │ let rect1 = Rectangle { width: 30, height: 50 }; - 8 │ - 9 │ println!( - 10 │ "The perimeter of the rectangle is {} pixels.", - 11 │ perimeter(&rect1) - 12 │ ); - 13 │ } - 14 │ - 15 │ fn area(rectangle: &Rectangle) -> u32 { - 16 │ rectangle.width * rectangle.height - 17 │ } - 18 │ - 19 │ fn perimeter(rectangle: &Rectangle) -> u32 { - 20 │ (rectangle.width + rectangle.height) * 2 - 21 │ } -───────┴──────────────────────────────────────────────────────────────────────── + 1 │ struct Rectangle { + 2 │ width: u32, + 3 │ height: u32, + 4 │ } + 5 │ + 6 │ fn main() { + 7 │ let rect1 = Rectangle { width: 30, height: 50 }; + 8 │ + 9 │ println!( + 10 │ "The perimeter of the rectangle is {} pixels.", + 11 │ perimeter(&rect1) + 12 │ ); + 13 │ } + 14 │ + 15 │ fn area(rectangle: &Rectangle) -> u32 { + 16 │ rectangle.width * rectangle.height + 17 │ } + 18 │ + 19 │ fn perimeter(rectangle: &Rectangle) -> u32 { + 20 │ (rectangle.width + rectangle.height) * 2 + 21 │ } +─────┴────────────────────────────────────────────────────────────────────────── diff --git a/tests/snapshots/output/grid.snapshot.txt b/tests/snapshots/output/grid.snapshot.txt index 2adebd8f..b5bd13c2 100644 --- a/tests/snapshots/output/grid.snapshot.txt +++ b/tests/snapshots/output/grid.snapshot.txt @@ -1,22 +1,22 @@ - │ struct Rectangle { - │ width: u32, - │ height: u32, - │ } - │ - │ fn main() { - │ let rect1 = Rectangle { width: 30, height: 50 }; - │ - │ println!( - │ "The perimeter of the rectangle is {} pixels.", - │ perimeter(&rect1) - │ ); - │ } - │ - │ fn area(rectangle: &Rectangle) -> u32 { - │ rectangle.width * rectangle.height - │ } - │ - │ fn perimeter(rectangle: &Rectangle) -> u32 { - │ (rectangle.width + rectangle.height) * 2 - │ } -───────┴──────────────────────────────────────────────────────────────────────── +struct Rectangle { + width: u32, + height: u32, +} + +fn main() { + let rect1 = Rectangle { width: 30, height: 50 }; + + println!( + "The perimeter of the rectangle is {} pixels.", + perimeter(&rect1) + ); +} + +fn area(rectangle: &Rectangle) -> u32 { + rectangle.width * rectangle.height +} + +fn perimeter(rectangle: &Rectangle) -> u32 { + (rectangle.width + rectangle.height) * 2 +} +──────────────────────────────────────────────────────────────────────────────── From 870b3c0daf4d7d5467c51479fbea821f1d882e83 Mon Sep 17 00:00:00 2001 From: eth-p <32112321+eth-p@users.noreply.github.com> Date: Sun, 13 May 2018 03:26:23 -0700 Subject: [PATCH 09/15] PR #102 Followed @BrainMaestro's suggestions Also ran cargo fmt. --- src/app.rs | 2 +- src/printer.rs | 76 +++++++++++++++++++++++++------------------------ src/style.rs | 2 +- src/terminal.rs | 7 +---- 4 files changed, 42 insertions(+), 45 deletions(-) diff --git a/src/app.rs b/src/app.rs index 803cc0cd..7f7b6b70 100644 --- a/src/app.rs +++ b/src/app.rs @@ -143,7 +143,7 @@ impl App { true_color: is_truecolor_terminal(), output_components: self.output_components()?, language: self.matches.value_of("language"), - output_wrap: if ! self.interactive_output { + output_wrap: if !self.interactive_output { // We don't have the tty width when piping to another program. // There's no point in wrapping when this is the case. OutputWrap::None diff --git a/src/printer.rs b/src/printer.rs index 91aaa36a..3bc4a388 100644 --- a/src/printer.rs +++ b/src/printer.rs @@ -41,7 +41,7 @@ impl<'a> Printer<'a> { }; // Generate the panel (gutter) width. - let decorations = instance.gen_decorations(0); + let decorations = instance.line_decorations(0); instance.panel_width = decorations.len() + decorations.iter().fold(0, |a, x| a + x.size); // Return the instance. @@ -97,7 +97,7 @@ impl<'a> Printer<'a> { let mut cursor_max: usize = self.config.term_width; // Line decoration. - let decorations = self.gen_decorations(line_number); + let decorations = self.line_decorations(line_number); let gutter_width = decorations.len() + decorations.iter().fold(0, |a, x| a + x.size); if gutter_width > 0 { @@ -115,7 +115,7 @@ impl<'a> Printer<'a> { // Grid border. let border = if gutter_width > 0 && self.config.output_components.grid() { - self.gen_border() + self.line_border() } else { PrintSegment { size: 0, @@ -131,11 +131,19 @@ impl<'a> Printer<'a> { let true_color = self.config.true_color; let colored_output = self.config.colored_output; - write!(self.handle, "{}", - regions.iter() - .map(|&(style, text)| as_terminal_escaped(style, text, true_color, colored_output)) - .collect::>() - .join("") + write!( + self.handle, + "{}", + regions + .iter() + .map(|&(style, text)| as_terminal_escaped( + style, + text, + true_color, + colored_output + )) + .collect::>() + .join("") )?; } else { for &(style, text) in regions.iter() { @@ -164,26 +172,24 @@ impl<'a> Printer<'a> { } // It wraps. - if self.config.output_wrap == OutputWrap::Character { - let text = chars.by_ref().take(available).collect::(); - cursor = 0; - remaining -= available; + let text = chars.by_ref().take(available).collect::(); + cursor = 0; + remaining -= available; - write!( - self.handle, - "{}\n{}{}", - as_terminal_escaped( - style, - &*text, - self.config.true_color, - self.config.colored_output, - ), - " ".repeat(gutter_width), - border.text.to_owned() - )?; + write!( + self.handle, + "{}\n{}{}", + as_terminal_escaped( + style, + &*text, + self.config.true_color, + self.config.colored_output, + ), + " ".repeat(gutter_width), + border.text.to_owned() + )?; - continue; - } + continue; } } @@ -194,25 +200,23 @@ impl<'a> Printer<'a> { Ok(()) } - /// Generates all the line decorations. - fn gen_decorations(&self, line_number: usize) -> Vec { + fn line_decorations(&self, line_number: usize) -> Vec { let mut decorations = Vec::new(); if self.config.output_components.numbers() { - decorations.push(self.gen_deco_line_number(line_number)); + decorations.push(self.line_number(line_number)); } if self.config.output_components.changes() { - decorations.push(self.gen_deco_line_changes(line_number)); + decorations.push(self.line_changes(line_number)); } return decorations; } - /// Generates the decoration for displaying the line number. - fn gen_deco_line_number(&self, line_number: usize) -> PrintSegment { + fn line_number(&self, line_number: usize) -> PrintSegment { let plain: String = format!("{:width$}", line_number, width = LINE_NUMBER_WIDTH); - let color = self.colors.line_number.paint(plain.to_owned()); + let color = self.colors.line_number.paint(plain.clone()); return PrintSegment { text: color.to_string(), @@ -220,8 +224,7 @@ impl<'a> Printer<'a> { }; } - /// Generates the decoration for displaying the git changes. - fn gen_deco_line_changes(&self, line_number: usize) -> PrintSegment { + fn line_changes(&self, line_number: usize) -> PrintSegment { let color = if let Some(ref changes) = self.line_changes { match changes.get(&(line_number as u32)) { Some(&LineChange::Added) => self.colors.git_added.paint("+"), @@ -240,8 +243,7 @@ impl<'a> Printer<'a> { }; } - /// Generates the vertical grid border. - fn gen_border(&self) -> PrintSegment { + fn line_border(&self) -> PrintSegment { return PrintSegment { text: self.colors.grid.paint("│ ").to_string(), size: 2, diff --git a/src/style.rs b/src/style.rs index c788f661..82ba2ea1 100644 --- a/src/style.rs +++ b/src/style.rs @@ -16,7 +16,7 @@ pub enum OutputComponent { #[derive(Debug, Eq, PartialEq, Copy, Clone, Hash)] pub enum OutputWrap { Character, - None + None, } impl OutputComponent { diff --git a/src/terminal.rs b/src/terminal.rs index af12c404..4ebf9478 100644 --- a/src/terminal.rs +++ b/src/terminal.rs @@ -1,5 +1,3 @@ -use std::fmt::Write; - use ansi_term::Colour::{Fixed, RGB}; use ansi_term::Style; use syntect::highlighting::{self, FontStyle}; @@ -32,7 +30,6 @@ pub fn as_terminal_escaped( true_color: bool, colored: bool, ) -> String { - let style = if !colored { Style::default() } else { @@ -54,9 +51,7 @@ pub fn as_terminal_escaped( } }; - let mut s: String = String::new(); - write!(s, "{}", style.paint(text)).unwrap(); - return s; + style.paint(text).to_string() } #[test] From 9214a4a4f08a1614e40e01235b24c4f175546e77 Mon Sep 17 00:00:00 2001 From: eth-p <32112321+eth-p@users.noreply.github.com> Date: Sun, 13 May 2018 12:45:16 -0700 Subject: [PATCH 10/15] PR #102 Followed @sharkdp's suggestions --- src/printer.rs | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/printer.rs b/src/printer.rs index 3bc4a388..d62b443d 100644 --- a/src/printer.rs +++ b/src/printer.rs @@ -188,15 +188,12 @@ impl<'a> Printer<'a> { " ".repeat(gutter_width), border.text.to_owned() )?; - - continue; } } write!(self.handle, "\n")?; } - // Finished. Ok(()) } From b327127f373b104b46d062caa5b7016bc02c3a77 Mon Sep 17 00:00:00 2001 From: eth-p <32112321+eth-p@users.noreply.github.com> Date: Sun, 13 May 2018 18:44:07 -0700 Subject: [PATCH 11/15] Split decorations into a separate file and optimized them a bit. --- src/decorations.rs | 146 ++++++++++++++++++++++++++++++++++++++++ src/main.rs | 1 + src/printer.rs | 162 +++++++++++++++++---------------------------- 3 files changed, 208 insertions(+), 101 deletions(-) create mode 100644 src/decorations.rs diff --git a/src/decorations.rs b/src/decorations.rs new file mode 100644 index 00000000..acc701bc --- /dev/null +++ b/src/decorations.rs @@ -0,0 +1,146 @@ +use ansi_term::Style; +use diff::LineChange; +use printer::Printer; +use Colors; + +#[derive(Clone)] +pub struct DecorationText { + pub width: usize, + pub text: String, +} + +pub trait Decoration { + fn for_line(&self, line_number: usize, printer: &Printer) -> DecorationText; + fn for_wrap(&self, line_number: usize, printer: &Printer) -> DecorationText; + fn width(&self) -> usize; +} + +// Line number decoration. +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 for_line(&self, line_number: usize, _printer: &Printer) -> DecorationText { + let plain: String = format!("{:4}", line_number); + DecorationText { + width: plain.len(), + text: self.color.paint(plain).to_string(), + } + } + + fn for_wrap(&self, line_number: usize, _printer: &Printer) -> DecorationText { + 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() + } + + fn width(&self) -> usize { + 4 + } +} + +// Line changes decoration. +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 for_line(&self, line_number: usize, printer: &Printer) -> DecorationText { + if let Some(ref changes) = printer.line_changes { + 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(), + } + } else { + self.cached_none.clone() + } + // let status = printer.line_changes.and_then(|ref changes| changes.get(&(line_number as u32))); + } + + fn for_wrap(&self, _line_number: usize, _printer: &Printer) -> DecorationText { + self.cached_none.clone() + } + + fn width(&self) -> usize { + self.cached_none.width + } +} + +// Grid border decoration. +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 for_line(&self, _line_number: usize, _printer: &Printer) -> DecorationText { + self.cached.clone() + } + + fn for_wrap(&self, _line_number: usize, _printer: &Printer) -> DecorationText { + self.cached.clone() + } + + fn width(&self) -> usize { + self.cached.width + } +} diff --git a/src/main.rs b/src/main.rs index bcac470c..d5a907f0 100644 --- a/src/main.rs +++ b/src/main.rs @@ -23,6 +23,7 @@ mod diff; mod printer; mod style; mod terminal; +mod decorations; use std::fs::{self, File}; use std::io::{self, BufRead, BufReader, Write}; diff --git a/src/printer.rs b/src/printer.rs index d62b443d..fbf6658b 100644 --- a/src/printer.rs +++ b/src/printer.rs @@ -1,24 +1,20 @@ -use ansi_term::Style; use app::Config; -use diff::{LineChange, LineChanges}; +use diff::LineChanges; use errors::*; use std::io::Write; +use std::vec::Vec; +use std::boxed::Box; use syntect::highlighting; use terminal::as_terminal_escaped; use style::OutputWrap; +use decorations::{Decoration, GridBorderDecoration, LineChangesDecoration, LineNumberDecoration}; use Colors; -const LINE_NUMBER_WIDTH: usize = 4; - -struct PrintSegment { - size: usize, - text: String, -} - pub struct Printer<'a> { handle: &'a mut Write, colors: Colors, config: &'a Config<'a>, + decorations: Vec>, panel_width: usize, pub line_changes: Option, } @@ -31,21 +27,36 @@ impl<'a> Printer<'a> { Colors::plain() }; - // Create the instance. - let mut instance = Printer { + // 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 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.len() > 0 { + decorations.push(Box::new(GridBorderDecoration::new(&colors))); + } + + // Create printer. + Printer { + panel_width, handle, colors, config, - panel_width: 0, + decorations, line_changes: None, - }; - - // Generate the panel (gutter) width. - let decorations = instance.line_decorations(0); - instance.panel_width = decorations.len() + decorations.iter().fold(0, |a, x| a + x.size); - - // Return the instance. - return instance; + } } pub fn print_header(&mut self, filename: Option<&str>) -> Result<()> { @@ -95,36 +106,20 @@ impl<'a> Printer<'a> { ) -> Result<()> { let mut cursor: usize = 0; let mut cursor_max: usize = self.config.term_width; + let mut panel_wrap: Option = None; - // Line decoration. - let decorations = self.line_decorations(line_number); - let gutter_width = decorations.len() + decorations.iter().fold(0, |a, x| a + x.size); + // Line decorations. + if self.panel_width > 0 { + let decorations = self.decorations + .iter() + .map(|ref d| d.for_line(line_number, self)) + .collect::>(); - if gutter_width > 0 { - cursor_max -= gutter_width; - write!( - self.handle, - "{} ", - decorations - .iter() - .map(|seg| seg.text.to_owned()) - .collect::>() - .join(" ") - )?; - } - - // Grid border. - let border = if gutter_width > 0 && self.config.output_components.grid() { - self.line_border() - } else { - PrintSegment { - size: 0, - text: "".to_owned(), + for deco in decorations { + write!(self.handle, "{} ", deco.text)?; + cursor_max -= deco.width + 1; } - }; - - cursor_max -= border.size; - write!(self.handle, "{}", border.text)?; + } // Line contents. if self.config.output_wrap == OutputWrap::None { @@ -140,7 +135,7 @@ impl<'a> Printer<'a> { style, text, true_color, - colored_output + colored_output, )) .collect::>() .join("") @@ -171,6 +166,22 @@ impl<'a> Printer<'a> { 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.for_wrap(line_number, self).text) + .collect::>() + .join(" ") + )) + } else { + Some("".to_string()) + } + } + // It wraps. let text = chars.by_ref().take(available).collect::(); cursor = 0; @@ -178,15 +189,14 @@ impl<'a> Printer<'a> { write!( self.handle, - "{}\n{}{}", + "{}\n{}", as_terminal_escaped( style, &*text, self.config.true_color, self.config.colored_output, ), - " ".repeat(gutter_width), - border.text.to_owned() + panel_wrap.clone().unwrap() )?; } } @@ -197,56 +207,6 @@ impl<'a> Printer<'a> { Ok(()) } - fn line_decorations(&self, line_number: usize) -> Vec { - let mut decorations = Vec::new(); - - if self.config.output_components.numbers() { - decorations.push(self.line_number(line_number)); - } - - if self.config.output_components.changes() { - decorations.push(self.line_changes(line_number)); - } - - return decorations; - } - - fn line_number(&self, line_number: usize) -> PrintSegment { - let plain: String = format!("{:width$}", line_number, width = LINE_NUMBER_WIDTH); - let color = self.colors.line_number.paint(plain.clone()); - - return PrintSegment { - text: color.to_string(), - size: plain.len(), - }; - } - - fn line_changes(&self, line_number: usize) -> PrintSegment { - let color = if let Some(ref changes) = self.line_changes { - match changes.get(&(line_number as u32)) { - Some(&LineChange::Added) => self.colors.git_added.paint("+"), - Some(&LineChange::RemovedAbove) => self.colors.git_removed.paint("‾"), - Some(&LineChange::RemovedBelow) => self.colors.git_removed.paint("_"), - Some(&LineChange::Modified) => self.colors.git_modified.paint("~"), - _ => Style::default().paint(" "), - } - } else { - Style::default().paint(" ") - }; - - return PrintSegment { - text: color.to_string(), - size: 1, - }; - } - - fn line_border(&self) -> PrintSegment { - return PrintSegment { - text: self.colors.grid.paint("│ ").to_string(), - size: 2, - }; - } - fn print_horizontal_line(&mut self, grid_char: char) -> Result<()> { if self.panel_width == 0 { writeln!( From a5a7dc14e430ed3f097cbef60abaa3bb19161cdc Mon Sep 17 00:00:00 2001 From: eth-p <32112321+eth-p@users.noreply.github.com> Date: Mon, 14 May 2018 14:36:08 -0700 Subject: [PATCH 12/15] Fix \r character being printed with wrapping enabled. --- src/printer.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/printer.rs b/src/printer.rs index fbf6658b..0eda7df2 100644 --- a/src/printer.rs +++ b/src/printer.rs @@ -142,8 +142,9 @@ impl<'a> Printer<'a> { )?; } else { for &(style, text) in regions.iter() { - let mut chars = text.chars().filter(|c| *c != '\n'); - let mut remaining = text.chars().filter(|c| *c != '\n').count(); + let text = text.trim_right_matches(|c| c == '\r' || c == '\n'); + let mut chars = text.chars(); + let mut remaining = text.chars().count(); while remaining > 0 { let available = cursor_max - cursor; From d0ca5669794902a78eebb14b2ca61b01a67c9ee6 Mon Sep 17 00:00:00 2001 From: eth-p <32112321+eth-p@users.noreply.github.com> Date: Tue, 15 May 2018 13:55:38 -0700 Subject: [PATCH 13/15] Replace for_line and for_wrap with generate --- src/decorations.rs | 80 +++++++++++++++++++++++++--------------------- src/printer.rs | 4 +-- 2 files changed, 46 insertions(+), 38 deletions(-) diff --git a/src/decorations.rs b/src/decorations.rs index acc701bc..bd557064 100644 --- a/src/decorations.rs +++ b/src/decorations.rs @@ -10,8 +10,8 @@ pub struct DecorationText { } pub trait Decoration { - fn for_line(&self, line_number: usize, printer: &Printer) -> DecorationText; - fn for_wrap(&self, line_number: usize, printer: &Printer) -> DecorationText; + fn generate(&self, line_number: usize, continuation: bool, printer: &Printer) + -> DecorationText; fn width(&self) -> usize; } @@ -36,24 +36,29 @@ impl LineNumberDecoration { } impl Decoration for LineNumberDecoration { - fn for_line(&self, line_number: usize, _printer: &Printer) -> DecorationText { - let plain: String = format!("{:4}", line_number); - DecorationText { - width: plain.len(), - text: self.color.paint(plain).to_string(), - } - } + fn generate( + &self, + line_number: usize, + continuation: bool, + _printer: &Printer, + ) -> 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, + }; + } - fn for_wrap(&self, line_number: usize, _printer: &Printer) -> DecorationText { - 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(), + } } - - self.cached_wrap.clone() } fn width(&self) -> usize { @@ -91,22 +96,24 @@ impl LineChangesDecoration { } impl Decoration for LineChangesDecoration { - fn for_line(&self, line_number: usize, printer: &Printer) -> DecorationText { - if let Some(ref changes) = printer.line_changes { - 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(), + fn generate( + &self, + line_number: usize, + continuation: bool, + printer: &Printer, + ) -> 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(), + }; } - } else { - self.cached_none.clone() } - // let status = printer.line_changes.and_then(|ref changes| changes.get(&(line_number as u32))); - } - fn for_wrap(&self, _line_number: usize, _printer: &Printer) -> DecorationText { self.cached_none.clone() } @@ -132,11 +139,12 @@ impl GridBorderDecoration { } impl Decoration for GridBorderDecoration { - fn for_line(&self, _line_number: usize, _printer: &Printer) -> DecorationText { - self.cached.clone() - } - - fn for_wrap(&self, _line_number: usize, _printer: &Printer) -> DecorationText { + fn generate( + &self, + _line_number: usize, + _continuation: bool, + _printer: &Printer, + ) -> DecorationText { self.cached.clone() } diff --git a/src/printer.rs b/src/printer.rs index 0eda7df2..17cf20fa 100644 --- a/src/printer.rs +++ b/src/printer.rs @@ -112,7 +112,7 @@ impl<'a> Printer<'a> { if self.panel_width > 0 { let decorations = self.decorations .iter() - .map(|ref d| d.for_line(line_number, self)) + .map(|ref d| d.generate(line_number, false, self)) .collect::>(); for deco in decorations { @@ -174,7 +174,7 @@ impl<'a> Printer<'a> { "{} ", self.decorations .iter() - .map(|ref d| d.for_wrap(line_number, self).text) + .map(|ref d| d.generate(line_number, true, self).text) .collect::>() .join(" ") )) From 900f61032d36eb896a0fc3baf5bd630e38c04a0d Mon Sep 17 00:00:00 2001 From: eth-p <32112321+eth-p@users.noreply.github.com> Date: Tue, 15 May 2018 14:09:51 -0700 Subject: [PATCH 14/15] Fix #117 --- src/printer.rs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/printer.rs b/src/printer.rs index 17cf20fa..19a97aa7 100644 --- a/src/printer.rs +++ b/src/printer.rs @@ -38,7 +38,7 @@ impl<'a> Printer<'a> { decorations.push(Box::new(LineChangesDecoration::new(&colors))); } - let panel_width: usize = + 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 @@ -48,6 +48,13 @@ impl<'a> Printer<'a> { 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; + } + // Create printer. Printer { panel_width, From d569693dab51d95e5e49be25435df2c4ba64ef37 Mon Sep 17 00:00:00 2001 From: eth-p <32112321+eth-p@users.noreply.github.com> Date: Tue, 15 May 2018 17:45:58 -0700 Subject: [PATCH 15/15] Formatted with newer cargo fmt. It should pass all the checks now. --- src/app.rs | 2 +- src/diff.rs | 3 ++- src/main.rs | 2 +- src/printer.rs | 13 ++++++++----- tests/tester.rs | 5 +---- 5 files changed, 13 insertions(+), 12 deletions(-) diff --git a/src/app.rs b/src/app.rs index 7f7b6b70..def53f07 100644 --- a/src/app.rs +++ b/src/app.rs @@ -59,7 +59,7 @@ impl App { .use_delimiter(true) .takes_value(true) .possible_values(&[ - "auto", "full", "plain", "changes", "header", "grid", "numbers" + "auto", "full", "plain", "changes", "header", "grid", "numbers", ]) .default_value("auto") .help("Additional info to display along with content"), diff --git a/src/diff.rs b/src/diff.rs index e6719a4e..ab315c91 100644 --- a/src/diff.rs +++ b/src/diff.rs @@ -23,7 +23,8 @@ pub fn get_git_diff(filename: &str) -> Option { diff_options.pathspec(pathspec); diff_options.context_lines(0); - let diff = repo.diff_index_to_workdir(None, Some(&mut diff_options)) + let diff = repo + .diff_index_to_workdir(None, Some(&mut diff_options)) .ok()?; let mut line_changes: LineChanges = HashMap::new(); diff --git a/src/main.rs b/src/main.rs index d5a907f0..833fa5d7 100644 --- a/src/main.rs +++ b/src/main.rs @@ -19,11 +19,11 @@ extern crate syntect; mod app; mod assets; +mod decorations; mod diff; mod printer; mod style; mod terminal; -mod decorations; use std::fs::{self, File}; use std::io::{self, BufRead, BufReader, Write}; diff --git a/src/printer.rs b/src/printer.rs index 19a97aa7..a51cc4dc 100644 --- a/src/printer.rs +++ b/src/printer.rs @@ -1,13 +1,13 @@ use app::Config; +use decorations::{Decoration, GridBorderDecoration, LineChangesDecoration, LineNumberDecoration}; use diff::LineChanges; use errors::*; +use std::boxed::Box; use std::io::Write; use std::vec::Vec; -use std::boxed::Box; +use style::OutputWrap; use syntect::highlighting; use terminal::as_terminal_escaped; -use style::OutputWrap; -use decorations::{Decoration, GridBorderDecoration, LineChangesDecoration, LineNumberDecoration}; use Colors; pub struct Printer<'a> { @@ -50,7 +50,9 @@ impl<'a> Printer<'a> { // 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 { + if config.term_width + < (decorations.len() + decorations.iter().fold(0, |a, x| a + x.width())) + 5 + { decorations.clear(); panel_width = 0; } @@ -117,7 +119,8 @@ impl<'a> Printer<'a> { // Line decorations. if self.panel_width > 0 { - let decorations = self.decorations + let decorations = self + .decorations .iter() .map(|ref d| d.generate(line_number, false, self)) .collect::>(); diff --git a/tests/tester.rs b/tests/tester.rs index 21a7829d..9865cbc1 100644 --- a/tests/tester.rs +++ b/tests/tester.rs @@ -29,10 +29,7 @@ impl BatTester { pub fn test_snapshot(&self, style: &str) { let output = Command::new(&self.exe) - .args(&[ - "tests/snapshots/sample.rs", - &format!("--style={}", style), - ]) + .args(&["tests/snapshots/sample.rs", &format!("--style={}", style)]) .output() .expect("bat failed"); // have to do the replace because the filename in the header changes based on the current working directory