Split into Controller and Printer

This commit is contained in:
sharkdp 2018-08-23 22:37:27 +02:00
parent ea955c734d
commit 246cf79dbd
4 changed files with 191 additions and 197 deletions

92
src/controller.rs Normal file
View File

@ -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<bool> {
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<BufRead> = 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<BufRead + 'a>,
line_ranges: &Option<LineRange>,
) -> 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(())
}
}

View File

@ -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::<Vec<_>>();
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<bool> {
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<P: Printer>(
config: &Config,
theme: &Theme,
syntax: &SyntaxDefinition,
printer: &mut P,
filename: Option<&str>,
) -> Result<()> {
let stdin = io::stdin();
{
let reader: Box<BufRead> = 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<BufRead + 'a>,
mut highlighter: HighlightLines,
line_ranges: &Option<LineRange>,
) -> 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, &regions)?;
}
}
&None => {
printer.print_line(line_number, &regions)?;
}
}
line_number += 1;
}
buffer.clear();
}
Ok(())
}

View File

@ -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::<Vec<_>>();
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<bool> {
@ -104,12 +158,8 @@ fn run() -> Result<bool> {
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()
}
}
}

View File

@ -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<Box<Decoration>>,
panel_width: usize,
pub ansi_prefix_sgr: String,
ansi_prefix_sgr: String,
pub line_changes: Option<LineChanges>,
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<String> = None;
@ -164,7 +169,7 @@ impl<'a> Printer for InteractivePrinter<'a> {
.collect::<Vec<_>>();
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(())