From 54406e5bcbd6c5c6af4c31b24abae1d7e410d843 Mon Sep 17 00:00:00 2001 From: Meriel Luna Mittelbach Date: Mon, 25 Aug 2025 17:00:14 +0200 Subject: [PATCH] feat: add `newlines` option to handle when blank lines are added Takes any combination of `file`, `hunk-header`, and `diff` to insert a blank line after the filename line, hunk header line, or after each diff hunk respectively. Handles edge cases like omitting both file and hunk-header but requesting newlines after diffs. Ensures output never starts with a blank line (unless git somehow causes it to). Commit boundaries are not handled due to the fact that git will insert a blank line between commits by itself unless --oneline is requested. Closes #1944, #1517 --- src/cli.rs | 8 ++++++++ src/config.rs | 6 ++++++ src/delta.rs | 2 ++ src/handlers/commit_meta.rs | 1 + src/handlers/diff_header.rs | 15 +++++++++++---- src/handlers/hunk.rs | 1 + src/handlers/hunk_header.rs | 15 +++++++++++---- src/handlers/mod.rs | 1 + src/options/set.rs | 1 + 9 files changed, 42 insertions(+), 8 deletions(-) diff --git a/src/cli.rs b/src/cli.rs index 544b25ceb..e8ff7922a 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -733,6 +733,14 @@ pub struct Opt { /// Regular expression defining navigation stop points. pub navigate_regex: Option, + #[arg( + long = "newlines", + default_value = "file diff", + value_name = "WHERE" + )] + /// Insert newlines between elements. + pub newlines: String, + #[arg(long = "no-gitconfig")] /// Do not read any settings from git config. /// diff --git a/src/config.rs b/src/config.rs index a536d7e42..05655a8ce 100644 --- a/src/config.rs +++ b/src/config.rs @@ -115,6 +115,9 @@ pub struct Config { pub minus_style: Style, pub navigate_regex: Option, pub navigate: bool, + pub newline_after_diff: bool, + pub newline_after_file: bool, + pub newline_after_hunk_header: bool, pub null_style: Style, pub null_syntect_style: SyntectStyle, pub pager: Option, @@ -410,6 +413,9 @@ impl From for Config { minus_style: styles["minus-style"], navigate: opt.navigate, navigate_regex, + newline_after_diff: opt.newlines.split_whitespace().any(|s| s == "diff"), + newline_after_file: opt.newlines.split_whitespace().any(|s| s == "file"), + newline_after_hunk_header: opt.newlines.split_whitespace().any(|s| s == "hunk-header"), null_style: Style::new(), null_syntect_style: SyntectStyle::default(), pager: opt.pager, diff --git a/src/delta.rs b/src/delta.rs index 80c5d7a85..a1a26eac9 100644 --- a/src/delta.rs +++ b/src/delta.rs @@ -112,6 +112,7 @@ pub struct StateMachine<'a> { pub handled_diff_header_header_line_file_pair: Option<(String, String)>, pub blame_key_colors: HashMap, pub minus_line_counter: AmbiguousDiffMinusCounter, + pub in_hunk: bool, } pub fn delta(lines: ByteLines, writer: &mut dyn Write, config: &Config) -> std::io::Result<()> @@ -140,6 +141,7 @@ impl<'a> StateMachine<'a> { config, blame_key_colors: HashMap::new(), minus_line_counter: AmbiguousDiffMinusCounter::not_needed(), + in_hunk: false, } } diff --git a/src/handlers/commit_meta.rs b/src/handlers/commit_meta.rs index aee18c91b..ae91e94e5 100644 --- a/src/handlers/commit_meta.rs +++ b/src/handlers/commit_meta.rs @@ -14,6 +14,7 @@ impl StateMachine<'_> { if !self.test_commit_meta_header_line() { return Ok(false); } + self.in_hunk &= self.config.commit_style.is_omitted; let mut handled_line = false; self.painter.paint_buffered_minus_and_plus_lines(); self.handle_pending_line_with_diff_name()?; diff --git a/src/handlers/diff_header.rs b/src/handlers/diff_header.rs index b5fe114c1..217755b23 100644 --- a/src/handlers/diff_header.rs +++ b/src/handlers/diff_header.rs @@ -63,6 +63,7 @@ impl StateMachine<'_> { &mut self.painter, &mut self.mode_info, self.config, + &mut self.in_hunk, )?; Ok(true) } else { @@ -206,6 +207,7 @@ impl StateMachine<'_> { &mut self.painter, &mut self.mode_info, self.config, + &mut self.in_hunk, ) } @@ -248,6 +250,7 @@ impl StateMachine<'_> { &mut self.painter, &mut self.mode_info, self.config, + &mut self.in_hunk, ) } else if !self.config.color_only && self.should_handle() @@ -270,6 +273,7 @@ pub fn write_generic_diff_header_header_line( painter: &mut Painter, mode_info: &mut String, config: &Config, + in_hunk: &mut bool ) -> std::io::Result<()> { // If file_style is "omit", we'll skip the process and print nothing. // However in the case of color_only mode, @@ -279,9 +283,9 @@ pub fn write_generic_diff_header_header_line( } let (mut draw_fn, pad, decoration_ansi_term_style) = draw::get_draw_function(config.file_style.decoration_style); - if !config.color_only { - // Maintain 1-1 correspondence between input and output lines. + if !config.color_only && *in_hunk && config.newline_after_diff { writeln!(painter.writer)?; + *in_hunk = false; } draw_fn( painter.writer, @@ -292,6 +296,9 @@ pub fn write_generic_diff_header_header_line( config.file_style, decoration_ansi_term_style, )?; + if !config.color_only && config.newline_after_file { + writeln!(painter.writer)?; + } if !mode_info.is_empty() { mode_info.truncate(0); } @@ -757,7 +764,7 @@ index 0000000..323fae0 +++ b.lua @@ -1,5 +1,4 @@ #!/usr/bin/env lua - + print("Hello") --- World? print("..") @@ -766,7 +773,7 @@ index 0000000..323fae0 +++ d.lua @@ -1,4 +1,3 @@ #!/usr/bin/env lua - + print("Hello") --- World? "#; diff --git a/src/handlers/hunk.rs b/src/handlers/hunk.rs index 073ce9089..60ae30bc7 100644 --- a/src/handlers/hunk.rs +++ b/src/handlers/hunk.rs @@ -78,6 +78,7 @@ impl StateMachine<'_> { if let State::HunkHeader(_, parsed_hunk_header, line, raw_line) = &self.state.clone() { self.emit_hunk_header_line(parsed_hunk_header, line, raw_line)?; } + self.in_hunk = true; self.state = match new_line_state(&self.line, &self.raw_line, &self.state, self.config) { Some(HunkMinus(diff_type, raw_line)) => { if let HunkPlus(_, _) = self.state { diff --git a/src/handlers/hunk_header.rs b/src/handlers/hunk_header.rs index 9ad2cb3dd..c02a9a381 100644 --- a/src/handlers/hunk_header.rs +++ b/src/handlers/hunk_header.rs @@ -174,11 +174,12 @@ impl StateMachine<'_> { if self.config.hunk_header_style.is_raw { write_hunk_header_raw(&mut self.painter, line, raw_line, self.config)?; } else if self.config.hunk_header_style.is_omitted { - writeln!(self.painter.writer)?; + // TODO: I don't know how to avoid the stuttering here... + if !self.config.color_only && self.in_hunk && self.config.newline_after_diff { + writeln!(self.painter.writer)?; + } } else { - // Add a blank line below the hunk-header-line for readability, unless - // color_only mode is active. - if !self.config.color_only { + if !self.config.color_only && self.in_hunk && self.config.newline_after_diff { writeln!(self.painter.writer)?; } @@ -203,6 +204,12 @@ impl StateMachine<'_> { ":", self.config, )?; + + // Add a blank line below the hunk-header-line for readability, unless + // color_only mode is active. + if !self.config.color_only && self.config.newline_after_hunk_header { + writeln!(self.painter.writer)?; + } }; self.painter.set_highlighter(); Ok(true) diff --git a/src/handlers/mod.rs b/src/handlers/mod.rs index 40bdf68ee..e3ba3d6b2 100644 --- a/src/handlers/mod.rs +++ b/src/handlers/mod.rs @@ -46,6 +46,7 @@ impl StateMachine<'_> { &mut self.painter, &mut self.mode_info, self.config, + &mut self.in_hunk, )?; handled_line = true; } diff --git a/src/options/set.rs b/src/options/set.rs index 6d26d15df..df52390f4 100644 --- a/src/options/set.rs +++ b/src/options/set.rs @@ -191,6 +191,7 @@ pub fn set_options( minus_non_emph_style, navigate, navigate_regex, + newlines, line_fill_method, line_numbers, line_numbers_left_format,