on v0.4.1 help sign in
bat/src/printer.rs
287 lines 9.8 kB history raw
1use ansi_term::Colour::{Fixed, Green, Red, White, Yellow};
2use ansi_term::Style;
3use app::Config;
4use console::AnsiCodeIterator;
5use decorations::{Decoration, GridBorderDecoration, LineChangesDecoration, LineNumberDecoration};
6use diff::LineChanges;
7use errors::*;
8use std::boxed::Box;
9use std::io::Write;
10use std::vec::Vec;
11use style::OutputWrap;
12use syntect::highlighting;
13use terminal::as_terminal_escaped;
15pub struct Printer<'a> {
16 handle: &'a mut Write,
17 colors: Colors,
18 config: &'a Config<'a>,
19 decorations: Vec<Box<Decoration>>,
20 panel_width: usize,
21 line_number: usize,
22 pub line_changes: Option<LineChanges>,
25impl<'a> Printer<'a> {
26 pub fn new(handle: &'a mut Write, config: &'a Config) -> Self {
27 let colors = if config.colored_output {
28 Colors::colored()
29 } else {
30 Colors::plain()
31 };
33 // Create decorations.
34 let mut decorations: Vec<Box<Decoration>> = Vec::new();
36 if config.output_components.numbers() {
37 decorations.push(Box::new(LineNumberDecoration::new(&colors)));
38 }
40 if config.output_components.changes() {
41 decorations.push(Box::new(LineChangesDecoration::new(&colors)));
42 }
44 let mut panel_width: usize =
45 decorations.len() + decorations.iter().fold(0, |a, x| a + x.width());
47 // The grid border decoration isn't added until after the panel_width calculation, since the
48 // print_horizontal_line, print_header, and print_footer functions all assume the panel
49 // width is without the grid border.
50 if config.output_components.grid() && !decorations.is_empty() {
51 decorations.push(Box::new(GridBorderDecoration::new(&colors)));
52 }
54 // Disable the panel if the terminal is too small (i.e. can't fit 5 characters with the
55 // panel showing).
56 if config.term_width
57 < (decorations.len() + decorations.iter().fold(0, |a, x| a + x.width())) + 5
58 {
59 decorations.clear();
60 panel_width = 0;
61 }
63 // Create printer.
64 Printer {
65 panel_width,
66 handle,
67 colors,
68 config,
69 decorations,
70 line_number: 0,
71 line_changes: None,
72 }
73 }
75 pub fn print_header(&mut self, filename: Option<&str>) -> Result<()> {
76 self.line_number = 0;
78 if !self.config.output_components.header() {
79 return Ok(());
80 }
82 if self.config.output_components.grid() {
83 self.print_horizontal_line('┬')?;
85 write!(
86 self.handle,
87 "{}{}",
88 " ".repeat(self.panel_width),
89 self.colors
90 .grid
91 .paint(if self.panel_width > 0 { "│ " } else { "" }),
92 )?;
93 } else {
94 write!(self.handle, "{}", " ".repeat(self.panel_width))?;
95 }
97 writeln!(
98 self.handle,
99 "{}{}",
100 filename.map_or("", |_| "File: "),
101 self.colors.filename.paint(filename.unwrap_or("STDIN"))
102 )?;
104 if self.config.output_components.grid() {
105 self.print_horizontal_line('┼')?;
106 }
108 Ok(())
109 }
111 pub fn print_footer(&mut self) -> Result<()> {
112 if self.config.output_components.grid() {
113 self.print_horizontal_line('┴')
114 } else {
115 Ok(())
116 }
117 }
119 pub fn print_line(&mut self, regions: &[(highlighting::Style, &str)]) -> Result<()> {
120 self.line_number += 1;
121 let mut cursor: usize = 0;
122 let mut cursor_max: usize = self.config.term_width;
123 let mut panel_wrap: Option<String> = None;
125 // Line decorations.
126 if self.panel_width > 0 {
127 let decorations = self
128 .decorations
129 .iter()
130 .map(|ref d| d.generate(self.line_number, false, self))
131 .collect::<Vec<_>>();
133 for deco in decorations {
134 write!(self.handle, "{} ", deco.text)?;
135 cursor_max -= deco.width + 1;
136 }
137 }
139 // Line contents.
140 if self.config.output_wrap == OutputWrap::None {
141 let true_color = self.config.true_color;
142 let colored_output = self.config.colored_output;
144 write!(
145 self.handle,
146 "{}",
147 regions
148 .iter()
149 .map(|&(style, text)| as_terminal_escaped(
150 style,
151 text,
152 true_color,
153 colored_output,
154 ))
155 .collect::<Vec<_>>()
156 .join("")
157 )?;
158 } else {
159 for &(style, region) in regions.iter() {
160 let mut ansi_iterator = AnsiCodeIterator::new(region);
161 let mut ansi_prefix = "";
162 for chunk in ansi_iterator {
163 match chunk {
164 // ANSI escape passthrough.
165 (text, true) => {
166 ansi_prefix = text;
167 }
169 // Regular text.
170 (text, false) => {
171 let text = text.trim_right_matches(|c| c == '\r' || c == '\n');
172 let mut chars = text.chars();
173 let mut remaining = text.chars().count();
175 while remaining > 0 {
176 let available = cursor_max - cursor;
178 // It fits.
179 if remaining <= available {
180 let text = chars.by_ref().take(remaining).collect::<String>();
181 cursor += remaining;
183 write!(
184 self.handle,
185 "{}",
186 as_terminal_escaped(
187 style,
188 &*format!("{}{}", ansi_prefix, text),
189 self.config.true_color,
190 self.config.colored_output,
191 )
192 )?;
193 break;
194 }
196 // Generate wrap padding if not already generated.
197 if panel_wrap.is_none() {
198 panel_wrap = if self.panel_width > 0 {
199 Some(format!(
200 "{} ",
201 self.decorations
202 .iter()
203 .map(|ref d| d
204 .generate(self.line_number, true, self)
205 .text)
206 .collect::<Vec<String>>()
207 .join(" ")
208 ))
209 } else {
210 Some("".to_string())
211 }
212 }
214 // It wraps.
215 let text = chars.by_ref().take(available).collect::<String>();
216 cursor = 0;
217 remaining -= available;
219 write!(
220 self.handle,
221 "{}\n{}",
222 as_terminal_escaped(
223 style,
224 &*format!("{}{}", ansi_prefix, text),
225 self.config.true_color,
226 self.config.colored_output,
227 ),
228 panel_wrap.clone().unwrap()
229 )?;
230 }
231 }
232 }
233 }
234 }
236 write!(self.handle, "\n")?;
237 }
239 Ok(())
240 }
242 fn print_horizontal_line(&mut self, grid_char: char) -> Result<()> {
243 if self.panel_width == 0 {
244 writeln!(
245 self.handle,
246 "{}",
247 self.colors.grid.paint("─".repeat(self.config.term_width))
248 )?;
249 } else {
250 let hline = "─".repeat(self.config.term_width - (self.panel_width + 1));
251 let hline = format!("{}{}{}", "─".repeat(self.panel_width), grid_char, hline);
252 writeln!(self.handle, "{}", self.colors.grid.paint(hline))?;
253 }
255 Ok(())
256 }
259const GRID_COLOR: u8 = 238;
260const LINE_NUMBER_COLOR: u8 = 244;
262#[derive(Default)]
263pub struct Colors {
264 pub grid: Style,
265 pub filename: Style,
266 pub git_added: Style,
267 pub git_removed: Style,
268 pub git_modified: Style,
269 pub line_number: Style,
272impl Colors {
273 fn plain() -> Self {
274 Colors::default()
275 }
277 fn colored() -> Self {
278 Colors {
279 grid: Fixed(GRID_COLOR).normal(),
280 filename: White.bold(),
281 git_added: Green.normal(),
282 git_removed: Red.normal(),
283 git_modified: Yellow.normal(),
284 line_number: Fixed(LINE_NUMBER_COLOR).normal(),
285 }
286 }