diff --git a/CHANGELOG.md b/CHANGELOG.md index ced88213..973eba9a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,11 @@ ## Features + +- Add a new `--diff` option that can be used to only show lines surrounding + Git changes, i.e. added, removed or modified lines. The amount of additional + context can be controlled with `--diff-context=N`. See #23 and #940 + ## Bugfixes ## Other ## `bat` as a library diff --git a/src/bin/bat/app.rs b/src/bin/bat/app.rs index e5221455..f0f519ea 100644 --- a/src/bin/bat/app.rs +++ b/src/bin/bat/app.rs @@ -15,7 +15,7 @@ use console::Term; use bat::{ assets::HighlightingAssets, - config::Config, + config::{Config, VisibleLines}, error::*, input::Input, line_range::{HighlightedLineRanges, LineRange, LineRanges}, @@ -196,13 +196,23 @@ impl App { } }) .unwrap_or_else(|| String::from(HighlightingAssets::default_theme())), - line_ranges: self - .matches - .values_of("line-range") - .map(|vs| vs.map(LineRange::from).collect()) - .transpose()? - .map(LineRanges::from) - .unwrap_or_default(), + visible_lines: if self.matches.is_present("diff") { + VisibleLines::DiffContext( + self.matches + .value_of("diff-context") + .and_then(|t| t.parse().ok()) + .unwrap_or(2), + ) + } else { + VisibleLines::Ranges( + self.matches + .values_of("line-range") + .map(|vs| vs.map(LineRange::from).collect()) + .transpose()? + .map(LineRanges::from) + .unwrap_or_default(), + ) + }, style_components, syntax_mapping, pager: self.matches.value_of("pager"), diff --git a/src/bin/bat/clap_app.rs b/src/bin/bat/clap_app.rs index c7344991..85edefde 100644 --- a/src/bin/bat/clap_app.rs +++ b/src/bin/bat/clap_app.rs @@ -105,6 +105,34 @@ pub fn build_app(interactive_output: bool) -> ClapApp<'static, 'static> { data to bat from STDIN when bat does not otherwise know \ the filename."), ) + .arg( + Arg::with_name("diff") + .long("diff") + .help("Only show lines that have been added/removed/modified.") + .long_help( + "Only show lines that have been added/removed/modified with respect \ + to the Git index. Use --diff-context=N to control how much context you want to see.", + ), + ) + .arg( + Arg::with_name("diff-context") + .long("diff-context") + .overrides_with("diff-context") + .takes_value(true) + .value_name("N") + .validator( + |n| { + n.parse::() + .map_err(|_| "must be a number") + .map(|_| ()) // Convert to Result<(), &str> + .map_err(|e| e.to_string()) + }, // Convert to Result<(), String> + ) + .hidden_short_help(true) + .long_help( + "Include N lines of context around added/removed/modified lines when using '--diff'.", + ), + ) .arg( Arg::with_name("tabs") .long("tabs") @@ -339,6 +367,7 @@ pub fn build_app(interactive_output: bool) -> ClapApp<'static, 'static> { .takes_value(true) .number_of_values(1) .value_name("N:M") + .conflicts_with("diff") .help("Only print the lines from N to M.") .long_help( "Only print the specified range of lines for each file. \ diff --git a/src/config.rs b/src/config.rs index d5df9910..3c24b77f 100644 --- a/src/config.rs +++ b/src/config.rs @@ -5,6 +5,32 @@ use crate::style::StyleComponents; use crate::syntax_mapping::SyntaxMapping; use crate::wrapping::WrappingMode; +#[derive(Debug, Clone)] +pub enum VisibleLines { + /// Show all lines which are included in the line ranges + Ranges(LineRanges), + + #[cfg(feature = "git")] + /// Only show lines surrounding added/deleted/modified lines + DiffContext(usize), +} + +impl VisibleLines { + pub fn diff_context(&self) -> bool { + match self { + Self::Ranges(_) => false, + #[cfg(feature = "git")] + Self::DiffContext(_) => true, + } + } +} + +impl Default for VisibleLines { + fn default() -> Self { + VisibleLines::Ranges(LineRanges::default()) + } +} + #[derive(Debug, Clone, Default)] pub struct Config<'a> { /// The explicitly configured language, if any @@ -39,8 +65,8 @@ pub struct Config<'a> { #[cfg(feature = "paging")] pub paging_mode: PagingMode, - /// Specifies the lines that should be printed - pub line_ranges: LineRanges, + /// Specifies which lines should be printed + pub visible_lines: VisibleLines, /// The syntax highlighting theme pub theme: String, @@ -62,10 +88,7 @@ pub struct Config<'a> { fn default_config_should_include_all_lines() { use crate::line_range::RangeCheckResult; - assert_eq!( - Config::default().line_ranges.check(17), - RangeCheckResult::InRange - ); + assert_eq!(LineRanges::default().check(17), RangeCheckResult::InRange); } #[test] diff --git a/src/controller.rs b/src/controller.rs index 09c6ec2a..f636d5fd 100644 --- a/src/controller.rs +++ b/src/controller.rs @@ -1,9 +1,13 @@ use std::io::{self, Write}; use crate::assets::HighlightingAssets; -use crate::config::Config; +use crate::config::{Config, VisibleLines}; +#[cfg(feature = "git")] +use crate::diff::{get_git_diff, LineChanges}; use crate::error::*; use crate::input::{Input, InputReader, OpenedInput}; +#[cfg(feature = "git")] +use crate::line_range::LineRange; use crate::line_range::{LineRanges, RangeCheckResult}; use crate::output::OutputType; #[cfg(feature = "paging")] @@ -68,6 +72,32 @@ impl<'b> Controller<'b> { no_errors = false; } Ok(mut opened_input) => { + #[cfg(feature = "git")] + let line_changes = if self.config.visible_lines.diff_context() + || (!self.config.loop_through && self.config.style_components.changes()) + { + if let crate::input::OpenedInputKind::OrdinaryFile(ref path) = + opened_input.kind + { + let diff = get_git_diff(path); + + if self.config.visible_lines.diff_context() + && diff + .as_ref() + .map(|changes| changes.is_empty()) + .unwrap_or(false) + { + continue; + } + + diff + } else { + None + } + } else { + None + }; + let mut printer: Box = if self.config.loop_through { Box::new(SimplePrinter::new()) } else { @@ -75,10 +105,18 @@ impl<'b> Controller<'b> { &self.config, &self.assets, &mut opened_input, + #[cfg(feature = "git")] + &line_changes, )) }; - let result = self.print_file(&mut *printer, writer, &mut opened_input); + let result = self.print_file( + &mut *printer, + writer, + &mut opened_input, + #[cfg(feature = "git")] + &line_changes, + ); if let Err(error) = result { handle_error(&error); @@ -96,13 +134,31 @@ impl<'b> Controller<'b> { printer: &mut dyn Printer, writer: &mut dyn Write, input: &mut OpenedInput, + #[cfg(feature = "git")] line_changes: &Option, ) -> Result<()> { if !input.reader.first_line.is_empty() || self.config.style_components.header() { printer.print_header(writer, input)?; } if !input.reader.first_line.is_empty() { - self.print_file_ranges(printer, writer, &mut input.reader, &self.config.line_ranges)?; + let line_ranges = match self.config.visible_lines { + VisibleLines::Ranges(ref line_ranges) => line_ranges.clone(), + #[cfg(feature = "git")] + VisibleLines::DiffContext(context) => { + let mut line_ranges: Vec = vec![]; + + if let Some(line_changes) = line_changes { + for line in line_changes.keys() { + let line = *line as usize; + line_ranges.push(LineRange::new(line - context, line + context)); + } + } + + LineRanges::from(line_ranges) + } + }; + + self.print_file_ranges(printer, writer, &mut input.reader, &line_ranges)?; } printer.print_footer(writer, input)?; diff --git a/src/pretty_printer.rs b/src/pretty_printer.rs index 0c78ea90..13bd5dbc 100644 --- a/src/pretty_printer.rs +++ b/src/pretty_printer.rs @@ -6,7 +6,7 @@ use syntect::parsing::SyntaxReference; use crate::{ assets::HighlightingAssets, - config::Config, + config::{Config, VisibleLines}, controller::Controller, error::Result, input::Input, @@ -205,7 +205,7 @@ impl<'a> PrettyPrinter<'a> { /// Specify the lines that should be printed (default: all) pub fn line_ranges(&mut self, ranges: LineRanges) -> &mut Self { - self.config.line_ranges = ranges; + self.config.visible_lines = VisibleLines::Ranges(ranges); self } diff --git a/src/printer.rs b/src/printer.rs index 5eed437e..2b245cfd 100644 --- a/src/printer.rs +++ b/src/printer.rs @@ -24,7 +24,7 @@ use crate::config::Config; use crate::decorations::LineChangesDecoration; use crate::decorations::{Decoration, GridBorderDecoration, LineNumberDecoration}; #[cfg(feature = "git")] -use crate::diff::{get_git_diff, LineChanges}; +use crate::diff::LineChanges; use crate::error::*; use crate::input::OpenedInput; use crate::line_range::RangeCheckResult; @@ -90,7 +90,7 @@ pub(crate) struct InteractivePrinter<'a> { ansi_prefix_sgr: String, content_type: Option, #[cfg(feature = "git")] - pub line_changes: Option, + pub line_changes: &'a Option, highlighter: Option>, syntax_set: &'a SyntaxSet, background_color_highlight: Option, @@ -101,6 +101,7 @@ impl<'a> InteractivePrinter<'a> { config: &'a Config, assets: &'a HighlightingAssets, input: &mut OpenedInput, + #[cfg(feature = "git")] line_changes: &'a Option, ) -> Self { let theme = assets.get_theme(&config.theme); @@ -145,9 +146,6 @@ impl<'a> InteractivePrinter<'a> { panel_width = 0; } - #[cfg(feature = "git")] - let mut line_changes = None; - let highlighter = if input .reader .content_type @@ -155,18 +153,6 @@ impl<'a> InteractivePrinter<'a> { { None } else { - // Get the Git modifications - #[cfg(feature = "git")] - { - use crate::input::OpenedInputKind; - - if config.style_components.changes() { - if let OpenedInputKind::OrdinaryFile(ref path) = input.kind { - line_changes = get_git_diff(path); - } - } - } - // Determine the type of syntax for highlighting let syntax = assets.get_syntax(config.language, input, &config.syntax_mapping); Some(HighlightLines::new(syntax, theme))