on master help sign in
Split into Controller and Printer
246cf79sharkdp
src/controller.rsnew file+92
@@ -0,0 +1,92 @@
1+use std::fs::File;
2+use std::io::{self, BufRead, BufReader, Write};
3+
4+use app::Config;
5+use assets::HighlightingAssets;
6+use errors::*;
7+use line_range::LineRange;
8+use output::OutputType;
9+use printer::{InteractivePrinter, Printer};
10+
11+pub struct Controller<'a> {
12+ config: &'a Config<'a>,
13+ assets: &'a HighlightingAssets,
14+}
15+
16+impl<'b> Controller<'b> {
17+ pub fn new<'a>(config: &'a Config, assets: &'a HighlightingAssets) -> Controller<'a> {
18+ Controller { config, assets }
19+ }
20+
21+ pub fn run(&self) -> Result<bool> {
22+ let mut output_type = OutputType::from_mode(self.config.paging_mode);
23+ let writer = output_type.handle()?;
24+ let mut no_errors: bool = true;
25+
26+ for file in &self.config.files {
27+ let result = self.print_file(writer, *file);
28+
29+ if let Err(error) = result {
30+ handle_error(&error);
31+ no_errors = false;
32+ }
33+ }
34+
35+ Ok(no_errors)
36+ }
37+
38+ fn print_file(&self, writer: &mut Write, filename: Option<&str>) -> Result<()> {
39+ let mut printer = InteractivePrinter::new(&self.config, &self.assets, filename);
40+
41+ let stdin = io::stdin();
42+ {
43+ let reader: Box<BufRead> = match filename {
44+ None => Box::new(stdin.lock()),
45+ Some(filename) => Box::new(BufReader::new(File::open(filename)?)),
46+ };
47+
48+ printer.print_header(writer, filename)?;
49+ self.print_file_ranges(&mut printer, writer, reader, &self.config.line_range)?;
50+ printer.print_footer(writer)?;
51+ }
52+ Ok(())
53+ }
54+
55+ fn print_file_ranges<'a, P: Printer>(
56+ &self,
57+ printer: &mut P,
58+ writer: &mut Write,
59+ mut reader: Box<BufRead + 'a>,
60+ line_ranges: &Option<LineRange>,
61+ ) -> Result<()> {
62+ let mut buffer = Vec::new();
63+
64+ let mut line_number: usize = 1;
65+
66+ while reader.read_until(b'\n', &mut buffer)? > 0 {
67+ {
68+ let line = String::from_utf8_lossy(&buffer);
69+
70+ match line_ranges {
71+ &Some(ref range) => {
72+ if line_number < range.lower {
73+ // skip line
74+ } else if line_number > range.upper {
75+ // no more lines in range
76+ break;
77+ } else {
78+ printer.print_line(writer, line_number, &line)?;
79+ }
80+ }
81+ &None => {
82+ printer.print_line(writer, line_number, &line)?;
83+ }
84+ }
85+
86+ line_number += 1;
87+ }
88+ buffer.clear();
89+ }
90+ Ok(())
91+ }
92+}
src/features.rsdeleted−153
@@ -1,153 +0,0 @@
1−use std::fs::File;
2−use std::io::{self, BufRead, BufReader};
3−
4−use ansi_term::Colour::Green;
5−
6−use syntect::easy::HighlightLines;
7−use syntect::highlighting::Theme;
8−use syntect::parsing::SyntaxDefinition;
9−
10−use app::Config;
11−use assets::HighlightingAssets;
12−use diff::get_git_diff;
13−use errors::*;
14−use line_range::LineRange;
15−use output::OutputType;
16−use printer::{InteractivePrinter, Printer};
17−
18−pub fn list_languages(assets: &HighlightingAssets, term_width: usize) {
19− let mut languages = assets
20− .syntax_set
21− .syntaxes()
22− .iter()
23− .filter(|syntax| !syntax.hidden && !syntax.file_extensions.is_empty())
24− .collect::<Vec<_>>();
25− languages.sort_by_key(|lang| lang.name.to_uppercase());
26−
27− let longest = languages
28− .iter()
29− .map(|syntax| syntax.name.len())
30− .max()
31− .unwrap_or(32); // Fallback width if they have no language definitions.
32−
33− let comma_separator = ", ";
34− let separator = " ";
35− // Line-wrapping for the possible file extension overflow.
36− let desired_width = term_width - longest - separator.len();
37−
38− for lang in languages {
39− print!("{:width$}{}", lang.name, separator, width = longest);
40−
41− // Number of characters on this line so far, wrap before `desired_width`
42− let mut num_chars = 0;
43−
44− let mut extension = lang.file_extensions.iter().peekable();
45− while let Some(word) = extension.next() {
46− // If we can't fit this word in, then create a line break and align it in.
47− let new_chars = word.len() + comma_separator.len();
48− if num_chars + new_chars >= desired_width {
49− num_chars = 0;
50− print!("\n{:width$}{}", "", separator, width = longest);
51− }
52−
53− num_chars += new_chars;
54− print!("{}", Green.paint(&word[..]));
55− if extension.peek().is_some() {
56− print!("{}", comma_separator);
57− }
58− }
59− println!();
60− }
61−}
62−
63−pub fn list_themes(assets: &HighlightingAssets) {
64− let themes = &assets.theme_set.themes;
65− for (theme, _) in themes.iter() {
66− println!("{}", theme);
67− }
68−}
69−
70−pub fn print_files(assets: &HighlightingAssets, config: &Config) -> Result<bool> {
71− let theme = assets.get_theme(&config.theme);
72−
73− let mut output_type = OutputType::from_mode(config.paging_mode);
74− let handle = output_type.handle()?;
75− let mut printer = InteractivePrinter::new(handle, &config, &theme);
76− let mut no_errors: bool = true;
77−
78− for file in &config.files {
79− printer.ansi_prefix_sgr.clear();
80− printer.line_changes = file.and_then(|filename| get_git_diff(filename));
81− let syntax = assets.get_syntax(config.language, *file);
82−
83− let result = print_file(config, theme, &syntax, &mut printer, *file);
84−
85− if let Err(error) = result {
86− handle_error(&error);
87− no_errors = false;
88− }
89− }
90−
91− Ok(no_errors)
92−}
93−
94−fn print_file<P: Printer>(
95− config: &Config,
96− theme: &Theme,
97− syntax: &SyntaxDefinition,
98− printer: &mut P,
99− filename: Option<&str>,
100−) -> Result<()> {
101− let stdin = io::stdin();
102− {
103− let reader: Box<BufRead> = match filename {
104− None => Box::new(stdin.lock()),
105− Some(filename) => Box::new(BufReader::new(File::open(filename)?)),
106− };
107−
108− let highlighter = HighlightLines::new(syntax, theme);
109−
110− printer.print_header(filename)?;
111− print_file_ranges(printer, reader, highlighter, &config.line_range)?;
112− printer.print_footer()?;
113− }
114− Ok(())
115−}
116−
117−fn print_file_ranges<'a, P: Printer>(
118− printer: &mut P,
119− mut reader: Box<BufRead + 'a>,
120− mut highlighter: HighlightLines,
121− line_ranges: &Option<LineRange>,
122−) -> Result<()> {
123− let mut buffer = Vec::new();
124−
125− let mut line_number: usize = 1;
126−
127− while reader.read_until(b'\n', &mut buffer)? > 0 {
128− {
129− let line = String::from_utf8_lossy(&buffer);
130− let regions = highlighter.highlight(line.as_ref());
131−
132− match line_ranges {
133− &Some(ref range) => {
134− if line_number < range.lower {
135− // skip line
136− } else if line_number > range.upper {
137− // no more lines in range
138− break;
139− } else {
140− printer.print_line(line_number, &regions)?;
141− }
142− }
143− &None => {
144− printer.print_line(line_number, &regions)?;
145− }
146− }
147−
148− line_number += 1;
149− }
150− buffer.clear();
151− }
152− Ok(())
153−}
src/main.rs+58−8
@@ -19,9 +19,9 @@ extern crate syntect;
19
19
20mod app;
20mod app;
21mod assets;
21mod assets;
22+mod controller;
22mod decorations;
23mod decorations;
23mod diff;
24mod diff;
24−mod features;
25mod line_range;
25mod line_range;
26mod output;
26mod output;
27mod printer;
27mod printer;
@@ -32,9 +32,11 @@ use std::io;
32use std::path::Path;
32use std::path::Path;
33use std::process;
33use std::process;
34
34
35+use ansi_term::Colour::Green;
36+
35use app::App;
37use app::App;
36use assets::{clear_assets, config_dir, HighlightingAssets};
38use assets::{clear_assets, config_dir, HighlightingAssets};
37−use features::{list_languages, list_themes, print_files};
39+use controller::Controller;
38
40
39mod errors {
41mod errors {
40 error_chain! {
42 error_chain! {
@@ -81,6 +83,58 @@ fn run_cache_subcommand(matches: &clap::ArgMatches) -> Result<()> {
81 Ok(())
83 Ok(())
82}
84}
83
85
86+pub fn list_languages(assets: &HighlightingAssets, term_width: usize) {
87+ let mut languages = assets
88+ .syntax_set
89+ .syntaxes()
90+ .iter()
91+ .filter(|syntax| !syntax.hidden && !syntax.file_extensions.is_empty())
92+ .collect::<Vec<_>>();
93+ languages.sort_by_key(|lang| lang.name.to_uppercase());
94+
95+ let longest = languages
96+ .iter()
97+ .map(|syntax| syntax.name.len())
98+ .max()
99+ .unwrap_or(32); // Fallback width if they have no language definitions.
100+
101+ let comma_separator = ", ";
102+ let separator = " ";
103+ // Line-wrapping for the possible file extension overflow.
104+ let desired_width = term_width - longest - separator.len();
105+
106+ for lang in languages {
107+ print!("{:width$}{}", lang.name, separator, width = longest);
108+
109+ // Number of characters on this line so far, wrap before `desired_width`
110+ let mut num_chars = 0;
111+
112+ let mut extension = lang.file_extensions.iter().peekable();
113+ while let Some(word) = extension.next() {
114+ // If we can't fit this word in, then create a line break and align it in.
115+ let new_chars = word.len() + comma_separator.len();
116+ if num_chars + new_chars >= desired_width {
117+ num_chars = 0;
118+ print!("\n{:width$}{}", "", separator, width = longest);
119+ }
120+
121+ num_chars += new_chars;
122+ print!("{}", Green.paint(&word[..]));
123+ if extension.peek().is_some() {
124+ print!("{}", comma_separator);
125+ }
126+ }
127+ println!();
128+ }
129+}
130+
131+pub fn list_themes(assets: &HighlightingAssets) {
132+ let themes = &assets.theme_set.themes;
133+ for (theme, _) in themes.iter() {
134+ println!("{}", theme);
135+ }
136+}
137+
84/// Returns `Err(..)` upon fatal errors. Otherwise, returns `Some(true)` on full success and
138/// Returns `Err(..)` upon fatal errors. Otherwise, returns `Some(true)` on full success and
85/// `Some(false)` if any intermediate errors occurred (were printed).
139/// `Some(false)` if any intermediate errors occurred (were printed).
86fn run() -> Result<bool> {
140fn run() -> Result<bool> {
@@ -104,12 +158,8 @@ fn run() -> Result<bool> {
104
158
105 Ok(true)
159 Ok(true)
106 } else {
160 } else {
107− if config.loop_through {
161+ let controller = Controller::new(&config, &assets);
108− // TODO
162+ controller.run()
109− print_files(&assets, &config)
110− } else {
111− print_files(&assets, &config)
112− }
113 }
163 }
114 }
164 }
115 }
165 }
src/printer.rs+41−36
@@ -7,37 +7,38 @@ use ansi_term::Style;
7
7
8use console::AnsiCodeIterator;
8use console::AnsiCodeIterator;
9
9
10+use syntect::easy::HighlightLines;
10−use syntect::highlighting::{self, Theme};
11+use syntect::highlighting::Theme;
11
12
12use app::Config;
13use app::Config;
14+use assets::HighlightingAssets;
13use decorations::{Decoration, GridBorderDecoration, LineChangesDecoration, LineNumberDecoration};
15use decorations::{Decoration, GridBorderDecoration, LineChangesDecoration, LineNumberDecoration};
16+use diff::get_git_diff;
14use diff::LineChanges;
17use diff::LineChanges;
15use errors::*;
18use errors::*;
16use style::OutputWrap;
19use style::OutputWrap;
17use terminal::{as_terminal_escaped, to_ansi_color};
20use terminal::{as_terminal_escaped, to_ansi_color};
18
21
19pub trait Printer {
22pub trait Printer {
20− fn print_header(&mut self, filename: Option<&str>) -> Result<()>;
23+ fn print_header(&mut self, handle: &mut Write, filename: Option<&str>) -> Result<()>;
21− fn print_footer(&mut self) -> Result<()>;
24+ fn print_footer(&mut self, handle: &mut Write) -> Result<()>;
22− fn print_line(
23− &mut self,
24− line_number: usize,
25+ fn print_line(&mut self, handle: &mut Write, line_number: usize, line: &str) -> Result<()>;
25− regions: &[(highlighting::Style, &str)],
26− ) -> Result<()>;
27}
26}
28
27
29pub struct InteractivePrinter<'a> {
28pub struct InteractivePrinter<'a> {
30− handle: &'a mut Write,
31 colors: Colors,
29 colors: Colors,
32− pub config: &'a Config<'a>,
30+ config: &'a Config<'a>,
33 decorations: Vec<Box<Decoration>>,
31 decorations: Vec<Box<Decoration>>,
34 panel_width: usize,
32 panel_width: usize,
35− pub ansi_prefix_sgr: String,
33+ ansi_prefix_sgr: String,
36 pub line_changes: Option<LineChanges>,
34 pub line_changes: Option<LineChanges>,
35+ highlighter: HighlightLines<'a>,
37}
36}
38
37
39impl<'a> InteractivePrinter<'a> {
38impl<'a> InteractivePrinter<'a> {
40− pub fn new(handle: &'a mut Write, config: &'a Config, theme: &Theme) -> Self {
39+ pub fn new(config: &'a Config, assets: &'a HighlightingAssets, filename: Option<&str>) -> Self {
40+ let theme = assets.get_theme(&config.theme);
41+
41 let colors = if config.colored_output {
42 let colors = if config.colored_output {
42 Colors::colored(theme, config.true_color)
43 Colors::colored(theme, config.true_color)
43 } else {
44 } else {
@@ -74,29 +75,35 @@ impl<'a> InteractivePrinter<'a> {
74 panel_width = 0;
75 panel_width = 0;
75 }
76 }
76
77
77− // Create printer.
78+ // Get the Git modifications
79+ let line_changes = filename.and_then(|file| get_git_diff(file));
80+
81+ // Determine the type of syntax for highlighting
82+ let syntax = assets.get_syntax(config.language, filename);
83+ let highlighter = HighlightLines::new(syntax, theme);
84+
78 InteractivePrinter {
85 InteractivePrinter {
79 panel_width,
86 panel_width,
80− handle,
81 colors,
87 colors,
82 config,
88 config,
83 decorations,
89 decorations,
84 ansi_prefix_sgr: String::new(),
90 ansi_prefix_sgr: String::new(),
85− line_changes: None,
91+ line_changes,
92+ highlighter,
86 }
93 }
87 }
94 }
88
95
89− fn print_horizontal_line(&mut self, grid_char: char) -> Result<()> {
96+ fn print_horizontal_line(&mut self, handle: &mut Write, grid_char: char) -> Result<()> {
90 if self.panel_width == 0 {
97 if self.panel_width == 0 {
91 writeln!(
98 writeln!(
92− self.handle,
99+ handle,
93 "{}",
100 "{}",
94 self.colors.grid.paint("─".repeat(self.config.term_width))
101 self.colors.grid.paint("─".repeat(self.config.term_width))
95 )?;
102 )?;
96 } else {
103 } else {
97 let hline = "─".repeat(self.config.term_width - (self.panel_width + 1));
104 let hline = "─".repeat(self.config.term_width - (self.panel_width + 1));
98 let hline = format!("{}{}{}", "─".repeat(self.panel_width), grid_char, hline);
105 let hline = format!("{}{}{}", "─".repeat(self.panel_width), grid_char, hline);
99− writeln!(self.handle, "{}", self.colors.grid.paint(hline))?;
106+ writeln!(handle, "{}", self.colors.grid.paint(hline))?;
100 }
107 }
101
108
102 Ok(())
109 Ok(())
@@ -104,16 +111,16 @@ impl<'a> InteractivePrinter<'a> {
104}
111}
105
112
106impl<'a> Printer for InteractivePrinter<'a> {
113impl<'a> Printer for InteractivePrinter<'a> {
107− fn print_header(&mut self, filename: Option<&str>) -> Result<()> {
114+ fn print_header(&mut self, handle: &mut Write, filename: Option<&str>) -> Result<()> {
108 if !self.config.output_components.header() {
115 if !self.config.output_components.header() {
109 return Ok(());
116 return Ok(());
110 }
117 }
111
118
112 if self.config.output_components.grid() {
119 if self.config.output_components.grid() {
113− self.print_horizontal_line('┬')?;
120+ self.print_horizontal_line(handle, '┬')?;
114
121
115 write!(
122 write!(
116− self.handle,
123+ handle,
117 "{}{}",
124 "{}{}",
118 " ".repeat(self.panel_width),
125 " ".repeat(self.panel_width),
119 self.colors
126 self.colors
@@ -121,36 +128,34 @@ impl<'a> Printer for InteractivePrinter<'a> {
121 .paint(if self.panel_width > 0 { "│ " } else { "" }),
128 .paint(if self.panel_width > 0 { "│ " } else { "" }),
122 )?;
129 )?;
123 } else {
130 } else {
124− write!(self.handle, "{}", " ".repeat(self.panel_width))?;
131+ write!(handle, "{}", " ".repeat(self.panel_width))?;
125 }
132 }
126
133
127 writeln!(
134 writeln!(
128− self.handle,
135+ handle,
129 "{}{}",
136 "{}{}",
130 filename.map_or("", |_| "File: "),
137 filename.map_or("", |_| "File: "),
131 self.colors.filename.paint(filename.unwrap_or("STDIN"))
138 self.colors.filename.paint(filename.unwrap_or("STDIN"))
132 )?;
139 )?;
133
140
134 if self.config.output_components.grid() {
141 if self.config.output_components.grid() {
135− self.print_horizontal_line('┼')?;
142+ self.print_horizontal_line(handle, '┼')?;
136 }
143 }
137
144
138 Ok(())
145 Ok(())
139 }
146 }
140
147
141− fn print_footer(&mut self) -> Result<()> {
148+ fn print_footer(&mut self, handle: &mut Write) -> Result<()> {
142 if self.config.output_components.grid() {
149 if self.config.output_components.grid() {
143− self.print_horizontal_line('┴')
150+ self.print_horizontal_line(handle, '┴')
144 } else {
151 } else {
145 Ok(())
152 Ok(())
146 }
153 }
147 }
154 }
148
155
149− fn print_line(
150− &mut self,
151− line_number: usize,
156+ fn print_line(&mut self, handle: &mut Write, line_number: usize, line: &str) -> Result<()> {
152− regions: &[(highlighting::Style, &str)],
157+ let regions = self.highlighter.highlight(line.as_ref());
153− ) -> Result<()> {
158+
154 let mut cursor: usize = 0;
159 let mut cursor: usize = 0;
155 let mut cursor_max: usize = self.config.term_width;
160 let mut cursor_max: usize = self.config.term_width;
156 let mut panel_wrap: Option<String> = None;
161 let mut panel_wrap: Option<String> = None;
@@ -164,7 +169,7 @@ impl<'a> Printer for InteractivePrinter<'a> {
164 .collect::<Vec<_>>();
169 .collect::<Vec<_>>();
165
170
166 for deco in decorations {
171 for deco in decorations {
167− write!(self.handle, "{} ", deco.text)?;
172+ write!(handle, "{} ", deco.text)?;
168 cursor_max -= deco.width + 1;
173 cursor_max -= deco.width + 1;
169 }
174 }
170 }
175 }
@@ -175,7 +180,7 @@ impl<'a> Printer for InteractivePrinter<'a> {
175 let colored_output = self.config.colored_output;
180 let colored_output = self.config.colored_output;
176
181
177 write!(
182 write!(
178− self.handle,
183+ handle,
179 "{}",
184 "{}",
180 regions
185 regions
181 .iter()
186 .iter()
@@ -222,7 +227,7 @@ impl<'a> Printer for InteractivePrinter<'a> {
222 cursor += remaining;
227 cursor += remaining;
223
228
224 write!(
229 write!(
225− self.handle,
230+ handle,
226 "{}",
231 "{}",
227 as_terminal_escaped(
232 as_terminal_escaped(
228 style,
233 style,
@@ -260,7 +265,7 @@ impl<'a> Printer for InteractivePrinter<'a> {
260 remaining -= available;
265 remaining -= available;
261
266
262 write!(
267 write!(
263− self.handle,
268+ handle,
264 "{}\n{}",
269 "{}\n{}",
265 as_terminal_escaped(
270 as_terminal_escaped(
266 style,
271 style,
@@ -282,7 +287,7 @@ impl<'a> Printer for InteractivePrinter<'a> {
282 }
287 }
283 }
288 }
284
289
285− write!(self.handle, "\n")?;
290+ write!(handle, "\n")?;
286 }
291 }
287
292
288 Ok(())
293 Ok(())