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] 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]