diff --git a/src/controller.rs b/src/controller.rs new file mode 100644 index 00000000..e2de210e --- /dev/null +++ b/src/controller.rs @@ -0,0 +1,92 @@ +use std::fs::File; +use std::io::{self, BufRead, BufReader, Write}; + +use app::Config; +use assets::HighlightingAssets; +use errors::*; +use line_range::LineRange; +use output::OutputType; +use printer::{InteractivePrinter, Printer}; + +pub struct Controller<'a> { + config: &'a Config<'a>, + assets: &'a HighlightingAssets, +} + +impl<'b> Controller<'b> { + pub fn new<'a>(config: &'a Config, assets: &'a HighlightingAssets) -> Controller<'a> { + Controller { config, assets } + } + + pub fn run(&self) -> Result { + let mut output_type = OutputType::from_mode(self.config.paging_mode); + let writer = output_type.handle()?; + let mut no_errors: bool = true; + + for file in &self.config.files { + let result = self.print_file(writer, *file); + + if let Err(error) = result { + handle_error(&error); + no_errors = false; + } + } + + Ok(no_errors) + } + + fn print_file(&self, writer: &mut Write, filename: Option<&str>) -> Result<()> { + let mut printer = InteractivePrinter::new(&self.config, &self.assets, filename); + + let stdin = io::stdin(); + { + let reader: Box = match filename { + None => Box::new(stdin.lock()), + Some(filename) => Box::new(BufReader::new(File::open(filename)?)), + }; + + printer.print_header(writer, filename)?; + self.print_file_ranges(&mut printer, writer, reader, &self.config.line_range)?; + printer.print_footer(writer)?; + } + Ok(()) + } + + fn print_file_ranges<'a, P: Printer>( + &self, + printer: &mut P, + writer: &mut Write, + mut reader: Box, + line_ranges: &Option, + ) -> Result<()> { + let mut buffer = Vec::new(); + + let mut line_number: usize = 1; + + while reader.read_until(b'\n', &mut buffer)? > 0 { + { + let line = String::from_utf8_lossy(&buffer); + + match line_ranges { + &Some(ref range) => { + if line_number < range.lower { + // skip line + } else if line_number > range.upper { + // no more lines in range + break; + } else { + printer.print_line(writer, line_number, &line)?; + } + } + &None => { + printer.print_line(writer, line_number, &line)?; + } + } + + line_number += 1; + } + buffer.clear(); + } + Ok(()) + } +} diff --git a/src/features.rs b/src/features.rs deleted file mode 100644 index 5d20c929..00000000 --- a/src/features.rs +++ /dev/null @@ -1,153 +0,0 @@ -use std::fs::File; -use std::io::{self, BufRead, BufReader}; - -use ansi_term::Colour::Green; - -use syntect::easy::HighlightLines; -use syntect::highlighting::Theme; -use syntect::parsing::SyntaxDefinition; - -use app::Config; -use assets::HighlightingAssets; -use diff::get_git_diff; -use errors::*; -use line_range::LineRange; -use output::OutputType; -use printer::{InteractivePrinter, Printer}; - -pub fn list_languages(assets: &HighlightingAssets, term_width: usize) { - let mut languages = assets - .syntax_set - .syntaxes() - .iter() - .filter(|syntax| !syntax.hidden && !syntax.file_extensions.is_empty()) - .collect::>(); - languages.sort_by_key(|lang| lang.name.to_uppercase()); - - let longest = languages - .iter() - .map(|syntax| syntax.name.len()) - .max() - .unwrap_or(32); // Fallback width if they have no language definitions. - - let comma_separator = ", "; - let separator = " "; - // Line-wrapping for the possible file extension overflow. - let desired_width = term_width - longest - separator.len(); - - for lang in languages { - print!("{:width$}{}", lang.name, separator, width = longest); - - // Number of characters on this line so far, wrap before `desired_width` - let mut num_chars = 0; - - let mut extension = lang.file_extensions.iter().peekable(); - while let Some(word) = extension.next() { - // If we can't fit this word in, then create a line break and align it in. - let new_chars = word.len() + comma_separator.len(); - if num_chars + new_chars >= desired_width { - num_chars = 0; - print!("\n{:width$}{}", "", separator, width = longest); - } - - num_chars += new_chars; - print!("{}", Green.paint(&word[..])); - if extension.peek().is_some() { - print!("{}", comma_separator); - } - } - println!(); - } -} - -pub fn list_themes(assets: &HighlightingAssets) { - let themes = &assets.theme_set.themes; - for (theme, _) in themes.iter() { - println!("{}", theme); - } -} - -pub fn print_files(assets: &HighlightingAssets, config: &Config) -> Result { - let theme = assets.get_theme(&config.theme); - - let mut output_type = OutputType::from_mode(config.paging_mode); - let handle = output_type.handle()?; - let mut printer = InteractivePrinter::new(handle, &config, &theme); - let mut no_errors: bool = true; - - for file in &config.files { - printer.ansi_prefix_sgr.clear(); - printer.line_changes = file.and_then(|filename| get_git_diff(filename)); - let syntax = assets.get_syntax(config.language, *file); - - let result = print_file(config, theme, &syntax, &mut printer, *file); - - if let Err(error) = result { - handle_error(&error); - no_errors = false; - } - } - - Ok(no_errors) -} - -fn print_file( - config: &Config, - theme: &Theme, - syntax: &SyntaxDefinition, - printer: &mut P, - filename: Option<&str>, -) -> Result<()> { - let stdin = io::stdin(); - { - let reader: Box = match filename { - None => Box::new(stdin.lock()), - Some(filename) => Box::new(BufReader::new(File::open(filename)?)), - }; - - let highlighter = HighlightLines::new(syntax, theme); - - printer.print_header(filename)?; - print_file_ranges(printer, reader, highlighter, &config.line_range)?; - printer.print_footer()?; - } - Ok(()) -} - -fn print_file_ranges<'a, P: Printer>( - printer: &mut P, - mut reader: Box, - mut highlighter: HighlightLines, - line_ranges: &Option, -) -> Result<()> { - let mut buffer = Vec::new(); - - let mut line_number: usize = 1; - - while reader.read_until(b'\n', &mut buffer)? > 0 { - { - let line = String::from_utf8_lossy(&buffer); - let regions = highlighter.highlight(line.as_ref()); - - match line_ranges { - &Some(ref range) => { - if line_number < range.lower { - // skip line - } else if line_number > range.upper { - // no more lines in range - break; - } else { - printer.print_line(line_number, ®ions)?; - } - } - &None => { - printer.print_line(line_number, ®ions)?; - } - } - - line_number += 1; - } - buffer.clear(); - } - Ok(()) -} diff --git a/src/main.rs b/src/main.rs index 098f597e..59941512 100644 --- a/src/main.rs +++ b/src/main.rs @@ -19,9 +19,9 @@ extern crate syntect; mod app; mod assets; +mod controller; mod decorations; mod diff; -mod features; mod line_range; mod output; mod printer; @@ -32,9 +32,11 @@ use std::io; use std::path::Path; use std::process; +use ansi_term::Colour::Green; + use app::App; use assets::{clear_assets, config_dir, HighlightingAssets}; -use features::{list_languages, list_themes, print_files}; +use controller::Controller; mod errors { error_chain! { @@ -81,6 +83,58 @@ fn run_cache_subcommand(matches: &clap::ArgMatches) -> Result<()> { Ok(()) } +pub fn list_languages(assets: &HighlightingAssets, term_width: usize) { + let mut languages = assets + .syntax_set + .syntaxes() + .iter() + .filter(|syntax| !syntax.hidden && !syntax.file_extensions.is_empty()) + .collect::>(); + languages.sort_by_key(|lang| lang.name.to_uppercase()); + + let longest = languages + .iter() + .map(|syntax| syntax.name.len()) + .max() + .unwrap_or(32); // Fallback width if they have no language definitions. + + let comma_separator = ", "; + let separator = " "; + // Line-wrapping for the possible file extension overflow. + let desired_width = term_width - longest - separator.len(); + + for lang in languages { + print!("{:width$}{}", lang.name, separator, width = longest); + + // Number of characters on this line so far, wrap before `desired_width` + let mut num_chars = 0; + + let mut extension = lang.file_extensions.iter().peekable(); + while let Some(word) = extension.next() { + // If we can't fit this word in, then create a line break and align it in. + let new_chars = word.len() + comma_separator.len(); + if num_chars + new_chars >= desired_width { + num_chars = 0; + print!("\n{:width$}{}", "", separator, width = longest); + } + + num_chars += new_chars; + print!("{}", Green.paint(&word[..])); + if extension.peek().is_some() { + print!("{}", comma_separator); + } + } + println!(); + } +} + +pub fn list_themes(assets: &HighlightingAssets) { + let themes = &assets.theme_set.themes; + for (theme, _) in themes.iter() { + println!("{}", theme); + } +} + /// Returns `Err(..)` upon fatal errors. Otherwise, returns `Some(true)` on full success and /// `Some(false)` if any intermediate errors occurred (were printed). fn run() -> Result { @@ -104,12 +158,8 @@ fn run() -> Result { Ok(true) } else { - if config.loop_through { - // TODO - print_files(&assets, &config) - } else { - print_files(&assets, &config) - } + let controller = Controller::new(&config, &assets); + controller.run() } } } diff --git a/src/printer.rs b/src/printer.rs index 3ab75089..c3cc38a6 100644 --- a/src/printer.rs +++ b/src/printer.rs @@ -7,37 +7,38 @@ use ansi_term::Style; use console::AnsiCodeIterator; -use syntect::highlighting::{self, Theme}; +use syntect::easy::HighlightLines; +use syntect::highlighting::Theme; use app::Config; +use assets::HighlightingAssets; use decorations::{Decoration, GridBorderDecoration, LineChangesDecoration, LineNumberDecoration}; +use diff::get_git_diff; use diff::LineChanges; use errors::*; use style::OutputWrap; use terminal::{as_terminal_escaped, to_ansi_color}; pub trait Printer { - fn print_header(&mut self, filename: Option<&str>) -> Result<()>; - fn print_footer(&mut self) -> Result<()>; - fn print_line( - &mut self, - line_number: usize, - regions: &[(highlighting::Style, &str)], - ) -> Result<()>; + fn print_header(&mut self, handle: &mut Write, filename: Option<&str>) -> Result<()>; + fn print_footer(&mut self, handle: &mut Write) -> Result<()>; + fn print_line(&mut self, handle: &mut Write, line_number: usize, line: &str) -> Result<()>; } pub struct InteractivePrinter<'a> { - handle: &'a mut Write, colors: Colors, - pub config: &'a Config<'a>, + config: &'a Config<'a>, decorations: Vec>, panel_width: usize, - pub ansi_prefix_sgr: String, + ansi_prefix_sgr: String, pub line_changes: Option, + highlighter: HighlightLines<'a>, } impl<'a> InteractivePrinter<'a> { - pub fn new(handle: &'a mut Write, config: &'a Config, theme: &Theme) -> Self { + pub fn new(config: &'a Config, assets: &'a HighlightingAssets, filename: Option<&str>) -> Self { + let theme = assets.get_theme(&config.theme); + let colors = if config.colored_output { Colors::colored(theme, config.true_color) } else { @@ -74,29 +75,35 @@ impl<'a> InteractivePrinter<'a> { panel_width = 0; } - // Create printer. + // Get the Git modifications + let line_changes = filename.and_then(|file| get_git_diff(file)); + + // Determine the type of syntax for highlighting + let syntax = assets.get_syntax(config.language, filename); + let highlighter = HighlightLines::new(syntax, theme); + InteractivePrinter { panel_width, - handle, colors, config, decorations, ansi_prefix_sgr: String::new(), - line_changes: None, + line_changes, + highlighter, } } - fn print_horizontal_line(&mut self, grid_char: char) -> Result<()> { + fn print_horizontal_line(&mut self, handle: &mut Write, grid_char: char) -> Result<()> { if self.panel_width == 0 { writeln!( - self.handle, + 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!(handle, "{}", self.colors.grid.paint(hline))?; } Ok(()) @@ -104,16 +111,16 @@ impl<'a> InteractivePrinter<'a> { } impl<'a> Printer for InteractivePrinter<'a> { - fn print_header(&mut self, filename: Option<&str>) -> Result<()> { + fn print_header(&mut self, handle: &mut Write, filename: Option<&str>) -> Result<()> { if !self.config.output_components.header() { return Ok(()); } if self.config.output_components.grid() { - self.print_horizontal_line('┬')?; + self.print_horizontal_line(handle, '┬')?; write!( - self.handle, + handle, "{}{}", " ".repeat(self.panel_width), self.colors @@ -121,36 +128,34 @@ impl<'a> Printer for InteractivePrinter<'a> { .paint(if self.panel_width > 0 { "│ " } else { "" }), )?; } else { - write!(self.handle, "{}", " ".repeat(self.panel_width))?; + write!(handle, "{}", " ".repeat(self.panel_width))?; } writeln!( - self.handle, + handle, "{}{}", filename.map_or("", |_| "File: "), self.colors.filename.paint(filename.unwrap_or("STDIN")) )?; if self.config.output_components.grid() { - self.print_horizontal_line('┼')?; + self.print_horizontal_line(handle, '┼')?; } Ok(()) } - fn print_footer(&mut self) -> Result<()> { + fn print_footer(&mut self, handle: &mut Write) -> Result<()> { if self.config.output_components.grid() { - self.print_horizontal_line('┴') + self.print_horizontal_line(handle, '┴') } else { Ok(()) } } - fn print_line( - &mut self, - line_number: usize, - regions: &[(highlighting::Style, &str)], - ) -> Result<()> { + fn print_line(&mut self, handle: &mut Write, line_number: usize, line: &str) -> Result<()> { + let regions = self.highlighter.highlight(line.as_ref()); + let mut cursor: usize = 0; let mut cursor_max: usize = self.config.term_width; let mut panel_wrap: Option = None; @@ -164,7 +169,7 @@ impl<'a> Printer for InteractivePrinter<'a> { .collect::>(); for deco in decorations { - write!(self.handle, "{} ", deco.text)?; + write!(handle, "{} ", deco.text)?; cursor_max -= deco.width + 1; } } @@ -175,7 +180,7 @@ impl<'a> Printer for InteractivePrinter<'a> { let colored_output = self.config.colored_output; write!( - self.handle, + handle, "{}", regions .iter() @@ -222,7 +227,7 @@ impl<'a> Printer for InteractivePrinter<'a> { cursor += remaining; write!( - self.handle, + handle, "{}", as_terminal_escaped( style, @@ -260,7 +265,7 @@ impl<'a> Printer for InteractivePrinter<'a> { remaining -= available; write!( - self.handle, + handle, "{}\n{}", as_terminal_escaped( style, @@ -282,7 +287,7 @@ impl<'a> Printer for InteractivePrinter<'a> { } } - write!(self.handle, "\n")?; + write!(handle, "\n")?; } Ok(())