Merge pull request #102 from eth-p/master

Added text wrapping. (Fixes #54)
pull/121/head
David Peter 6 years ago committed by GitHub
commit 2eee68599d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -4,7 +4,7 @@ use console::Term;
use errors::*; use errors::*;
use std::collections::HashSet; use std::collections::HashSet;
use std::env; use std::env;
use style::{OutputComponent, OutputComponents}; use style::{OutputComponent, OutputComponents, OutputWrap};
#[cfg(windows)] #[cfg(windows)]
use ansi_term; use ansi_term;
@ -86,6 +86,14 @@ impl App {
.default_value("auto") .default_value("auto")
.help("When to use the pager"), .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(
Arg::with_name("list-languages") Arg::with_name("list-languages")
.long("list-languages") .long("list-languages")
@ -141,6 +149,16 @@ impl App {
true_color: is_truecolor_terminal(), true_color: is_truecolor_terminal(),
output_components: self.output_components()?, output_components: self.output_components()?,
language: self.matches.value_of("language"), language: self.matches.value_of("language"),
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") { colored_output: match self.matches.value_of("color") {
Some("always") => true, Some("always") => true,
Some("never") => false, Some("never") => false,
@ -197,6 +215,7 @@ impl App {
pub struct Config<'a> { pub struct Config<'a> {
pub true_color: bool, pub true_color: bool,
pub output_wrap: OutputWrap,
pub output_components: OutputComponents, pub output_components: OutputComponents,
pub language: Option<&'a str>, pub language: Option<&'a str>,
pub colored_output: bool, pub colored_output: bool,

@ -0,0 +1,154 @@
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 generate(&self, line_number: usize, continuation: bool, 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 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,
};
}
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
}
}
// 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 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(),
};
}
}
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 generate(
&self,
_line_number: usize,
_continuation: bool,
_printer: &Printer,
) -> DecorationText {
self.cached.clone()
}
fn width(&self) -> usize {
self.cached.width
}
}

@ -19,6 +19,7 @@ extern crate syntect;
mod app; mod app;
mod assets; mod assets;
mod decorations;
mod diff; mod diff;
mod printer; mod printer;
mod style; mod style;

@ -1,18 +1,21 @@
use ansi_term::Style;
use app::Config; use app::Config;
use diff::{LineChange, LineChanges}; use decorations::{Decoration, GridBorderDecoration, LineChangesDecoration, LineNumberDecoration};
use diff::LineChanges;
use errors::*; use errors::*;
use std::boxed::Box;
use std::io::Write; use std::io::Write;
use std::vec::Vec;
use style::OutputWrap;
use syntect::highlighting; use syntect::highlighting;
use terminal::as_terminal_escaped; use terminal::as_terminal_escaped;
use Colors; use Colors;
const PANEL_WIDTH: usize = 7;
pub struct Printer<'a> { pub struct Printer<'a> {
handle: &'a mut Write, handle: &'a mut Write,
colors: Colors, colors: Colors,
config: &'a Config<'a>, config: &'a Config<'a>,
decorations: Vec<Box<Decoration>>,
panel_width: usize,
pub line_changes: Option<LineChanges>, pub line_changes: Option<LineChanges>,
} }
@ -24,10 +27,43 @@ impl<'a> Printer<'a> {
Colors::plain() Colors::plain()
}; };
// Create decorations.
let mut decorations: Vec<Box<Decoration>> = 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.len() > 0 {
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 { Printer {
panel_width,
handle, handle,
colors, colors,
config, config,
decorations,
line_changes: None, line_changes: None,
} }
} }
@ -43,8 +79,10 @@ impl<'a> Printer<'a> {
write!( write!(
self.handle, self.handle,
"{}{} ", "{}{} ",
" ".repeat(PANEL_WIDTH), " ".repeat(self.panel_width),
self.colors.grid.paint("│"), self.colors
.grid
.paint(if self.panel_width > 0 { "│" } else { "" }),
)?; )?;
} }
@ -75,85 +113,123 @@ impl<'a> Printer<'a> {
line_number: usize, line_number: usize,
regions: &[(highlighting::Style, &str)], regions: &[(highlighting::Style, &str)],
) -> Result<()> { ) -> Result<()> {
let decorations = vec![ let mut cursor: usize = 0;
self.print_line_number(line_number), let mut cursor_max: usize = self.config.term_width;
self.print_git_marker(line_number), let mut panel_wrap: Option<String> = None;
self.print_line_border(),
Some(as_terminal_escaped( // Line decorations.
&regions, if self.panel_width > 0 {
self.config.true_color, let decorations = self
self.config.colored_output, .decorations
)), .iter()
]; .map(|ref d| d.generate(line_number, false, self))
.collect::<Vec<_>>();
for deco in decorations {
write!(self.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 grid_requested = self.config.output_components.grid();
write!( write!(
self.handle, self.handle,
"{}", "{}",
decorations regions
.into_iter() .iter()
.filter_map(|dec| if grid_requested { .map(|&(style, text)| as_terminal_escaped(
Some(dec.unwrap_or_else(|| " ".to_owned())) style,
} else { text,
dec true_color,
}) colored_output,
))
.collect::<Vec<_>>() .collect::<Vec<_>>()
.join(" ") .join("")
)?; )?;
} else {
for &(style, text) in regions.iter() {
let text = text.trim_right_matches(|c| c == '\r' || c == '\n');
let mut chars = text.chars();
let mut remaining = text.chars().count();
Ok(()) while remaining > 0 {
} let available = cursor_max - cursor;
fn print_line_number(&self, line_number: usize) -> Option<String> { // It fits.
if self.config.output_components.numbers() { if remaining <= available {
Some( let text = chars.by_ref().take(remaining).collect::<String>();
self.colors cursor += remaining;
.line_number
.paint(format!("{:4}", line_number)) write!(
.to_string(), self.handle,
"{}",
as_terminal_escaped(
style,
&*text,
self.config.true_color,
self.config.colored_output,
) )
} else if self.config.output_components.grid() { )?;
Some(" ".to_owned()) 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::<Vec<String>>()
.join(" ")
))
} else { } else {
None Some("".to_string())
} }
} }
fn print_git_marker(&self, line_number: usize) -> Option<String> { // It wraps.
if self.config.output_components.changes() { let text = chars.by_ref().take(available).collect::<String>();
Some( cursor = 0;
if let Some(ref changes) = self.line_changes { remaining -= available;
match changes.get(&(line_number as u32)) {
Some(&LineChange::Added) => self.colors.git_added.paint("+"), write!(
Some(&LineChange::RemovedAbove) => self.colors.git_removed.paint("‾"), self.handle,
Some(&LineChange::RemovedBelow) => self.colors.git_removed.paint("_"), "{}\n{}",
Some(&LineChange::Modified) => self.colors.git_modified.paint("~"), as_terminal_escaped(
_ => Style::default().paint(" "), style,
} &*text,
} else { self.config.true_color,
Style::default().paint(" ") self.config.colored_output,
}.to_string(), ),
) panel_wrap.clone().unwrap()
} else if self.config.output_components.grid() { )?;
Some(" ".to_owned())
} else {
None
} }
} }
fn print_line_border(&self) -> Option<String> { write!(self.handle, "\n")?;
if self.config.output_components.grid() {
Some(self.colors.grid.paint("│").to_string())
} else {
None
} }
Ok(())
} }
fn print_horizontal_line(&mut self, grid_char: char) -> Result<()> { fn print_horizontal_line(&mut self, grid_char: char) -> Result<()> {
let hline = "─".repeat(self.config.term_width - (PANEL_WIDTH + 1)); if self.panel_width == 0 {
let hline = format!("{}{}{}", "─".repeat(PANEL_WIDTH), grid_char, hline); 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))?; writeln!(self.handle, "{}", self.colors.grid.paint(hline))?;
}
Ok(()) Ok(())
} }

@ -13,6 +13,12 @@ pub enum OutputComponent {
Plain, Plain,
} }
#[derive(Debug, Eq, PartialEq, Copy, Clone, Hash)]
pub enum OutputWrap {
Character,
None,
}
impl OutputComponent { impl OutputComponent {
pub fn components(&self, interactive_terminal: bool) -> &'static [OutputComponent] { pub fn components(&self, interactive_terminal: bool) -> &'static [OutputComponent] {
match *self { match *self {

@ -1,5 +1,3 @@
use std::fmt::Write;
use ansi_term::Colour::{Fixed, RGB}; use ansi_term::Colour::{Fixed, RGB};
use ansi_term::Style; use ansi_term::Style;
use syntect::highlighting::{self, FontStyle}; use syntect::highlighting::{self, FontStyle};
@ -27,12 +25,11 @@ fn rgb2ansi(r: u8, g: u8, b: u8) -> u8 {
} }
pub fn as_terminal_escaped( pub fn as_terminal_escaped(
v: &[(highlighting::Style, &str)], style: highlighting::Style,
text: &str,
true_color: bool, true_color: bool,
colored: bool, colored: bool,
) -> String { ) -> String {
let mut s: String = String::new();
for &(ref style, text) in v.iter() {
let style = if !colored { let style = if !colored {
Style::default() Style::default()
} else { } else {
@ -54,10 +51,7 @@ pub fn as_terminal_escaped(
} }
}; };
write!(s, "{}", style.paint(text)).unwrap(); style.paint(text).to_string()
}
s
} }
#[test] #[test]

@ -1,25 +1,25 @@
─────────────────────────────────────────────────────────────────────────────── ───────────────────────────────────────────────────────────────────────────────
│ File: sample.rs │ File: sample.rs
─────────────────────────────────────────────────────────────────────────────── ───────────────────────────────────────────────────────────────────────────────
│ struct Rectangle { │ struct Rectangle {
│ width: u32, │ width: u32,
│ height: u32, │ height: u32,
│ } │ }
_ │ fn main() { _ │ fn main() {
│ let rect1 = Rectangle { width: 30, height: 50 }; │ let rect1 = Rectangle { width: 30, height: 50 };
│ println!( │ println!(
~ │ "The perimeter of the rectangle is {} pixels.", ~ │ "The perimeter of the rectangle is {} pixels.",
~ │ perimeter(&rect1) ~ │ perimeter(&rect1)
│ ); │ );
│ } │ }
│ fn area(rectangle: &Rectangle) -> u32 { │ fn area(rectangle: &Rectangle) -> u32 {
│ rectangle.width * rectangle.height │ rectangle.width * rectangle.height
│ } │ }
+ │ + │
+ │ fn perimeter(rectangle: &Rectangle) -> u32 { + │ fn perimeter(rectangle: &Rectangle) -> u32 {
+ │ (rectangle.width + rectangle.height) * 2 + │ (rectangle.width + rectangle.height) * 2
+ │ } + │ }
─────────────────────────────────────────────────────────────────────────────── ───────────────────────────────────────────────────────────────────────────────

@ -3,20 +3,20 @@
│ height: u32, │ height: u32,
│ } │ }
_ │ fn main() { _ │ fn main() {
│ let rect1 = Rectangle { width: 30, height: 50 }; │ let rect1 = Rectangle { width: 30, height: 50 };
│ println!( │ println!(
~ │ "The perimeter of the rectangle is {} pixels.", ~ │ "The perimeter of the rectangle is {} pixels.",
~ │ perimeter(&rect1) ~ │ perimeter(&rect1)
│ ); │ );
│ } │ }
│ fn area(rectangle: &Rectangle) -> u32 { │ fn area(rectangle: &Rectangle) -> u32 {
│ rectangle.width * rectangle.height │ rectangle.width * rectangle.height
│ } │ }
+ │ + │
+ │ fn perimeter(rectangle: &Rectangle) -> u32 { + │ fn perimeter(rectangle: &Rectangle) -> u32 {
+ │ (rectangle.width + rectangle.height) * 2 + │ (rectangle.width + rectangle.height) * 2
+ │ } + │ }
─────────────────────────────────────────────────────────────────────────────── ───────────────────────────────────────────────────────────────────────────────

@ -1,6 +1,6 @@
─────────────────────────────────────────────────────────────────────────────── ───────────────────────────────────────────────────────────────────────────────
│ File: sample.rs │ File: sample.rs
─────────────────────────────────────────────────────────────────────────────── ───────────────────────────────────────────────────────────────────────────────
1 │ struct Rectangle { 1 │ struct Rectangle {
2 │ width: u32, 2 │ width: u32,
3 │ height: u32, 3 │ height: u32,
@ -22,4 +22,4 @@
19 │ fn perimeter(rectangle: &Rectangle) -> u32 { 19 │ fn perimeter(rectangle: &Rectangle) -> u32 {
20 │ (rectangle.width + rectangle.height) * 2 20 │ (rectangle.width + rectangle.height) * 2
21 │ } 21 │ }
─────────────────────────────────────────────────────────────────────────────── ───────────────────────────────────────────────────────────────────────────────

@ -1,25 +1,25 @@
─────────────────────────────────────────────────────────────────────────────── ───────────────────────────────────────────────────────────────────────────────
File: sample.rs File: sample.rs
─────────────────────────────────────────────────────────────────────────────── ───────────────────────────────────────────────────────────────────────────────
struct Rectangle { struct Rectangle {
width: u32, width: u32,
height: u32, height: u32,
} }
fn main() { fn main() {
let rect1 = Rectangle { width: 30, height: 50 }; let rect1 = Rectangle { width: 30, height: 50 };
println!( println!(
"The perimeter of the rectangle is {} pixels.", "The perimeter of the rectangle is {} pixels.",
perimeter(&rect1) perimeter(&rect1)
); );
} }
fn area(rectangle: &Rectangle) -> u32 { fn area(rectangle: &Rectangle) -> u32 {
rectangle.width * rectangle.height rectangle.width * rectangle.height
} }
fn perimeter(rectangle: &Rectangle) -> u32 { fn perimeter(rectangle: &Rectangle) -> u32 {
(rectangle.width + rectangle.height) * 2 (rectangle.width + rectangle.height) * 2
} }
─────────────────────────────────────────────────────────────────────────────── ───────────────────────────────────────────────────────────────────────────────

@ -19,4 +19,4 @@
19 │ fn perimeter(rectangle: &Rectangle) -> u32 { 19 │ fn perimeter(rectangle: &Rectangle) -> u32 {
20 │ (rectangle.width + rectangle.height) * 2 20 │ (rectangle.width + rectangle.height) * 2
21 │ } 21 │ }
─────────────────────────────────────────────────────────────────────────────── ───────────────────────────────────────────────────────────────────────────────

@ -1,22 +1,22 @@
struct Rectangle { struct Rectangle {
width: u32, width: u32,
height: u32, height: u32,
} }
fn main() { fn main() {
let rect1 = Rectangle { width: 30, height: 50 }; let rect1 = Rectangle { width: 30, height: 50 };
println!( println!(
"The perimeter of the rectangle is {} pixels.", "The perimeter of the rectangle is {} pixels.",
perimeter(&rect1) perimeter(&rect1)
); );
} }
fn area(rectangle: &Rectangle) -> u32 { fn area(rectangle: &Rectangle) -> u32 {
rectangle.width * rectangle.height rectangle.width * rectangle.height
} }
fn perimeter(rectangle: &Rectangle) -> u32 { fn perimeter(rectangle: &Rectangle) -> u32 {
(rectangle.width + rectangle.height) * 2 (rectangle.width + rectangle.height) * 2
} }
─────────────────────────────────────────────────────────────────────────────── ───────────────────────────────────────────────────────────────────────────────

Loading…
Cancel
Save