Add --side-by-side-fill-empty to fill up unmatched lines

This changes

│ 10 │ deleted line   │    │                │
│    │                │ 11 │ added line     │

to the following. The fill char and style can be configured.

│ 10 │ deleted line   │    │ ╱╱╱╱╱╱╱╱╱╱╱╱╱╱ │
│    │ ╱╱╱╱╱╱╱╱╱╱╱╱╱╱ │ 11 │ added line     │

Thanks to @guanghechen for the idea.
This commit is contained in:
Thomas Otto
2025-12-09 22:09:08 +01:00
parent f471e2c5be
commit 73a727b115
4 changed files with 87 additions and 6 deletions
+8
View File
@@ -860,6 +860,14 @@ pub struct Opt {
/// Display diffs in side-by-side layout.
pub side_by_side: bool,
#[arg(long = "side-by-side-fill-empty", value_name = "CHAR_AND_STYLE")]
/// Fill empty side. Can be "true", or a fill character, or a character plus a style.
///
/// On "true" the unmatched side will be filled with "/" in the style of
/// --line-numbers-zero-style, if a single character is given this is used instead.
/// If the character is followed by a style, that style is used.
pub side_by_side_fill_empty: Option<String>,
#[arg(long = "syntax-theme", value_name = "SYNTAX_THEME")]
/// The syntax-highlighting theme to use.
///
+9
View File
@@ -128,6 +128,7 @@ pub struct Config {
pub relative_paths: bool,
pub show_themes: bool,
pub side_by_side_data: side_by_side::SideBySideData,
pub side_by_side_fill_empty: Option<side_by_side::SideBySideFillEmpty>,
pub side_by_side: bool,
pub syntax_set: SyntaxSet,
pub syntax_theme: Option<SyntaxTheme>,
@@ -248,6 +249,13 @@ impl From<cli::Opt> for Config {
side_by_side_data,
);
let side_by_side_fill_empty = side_by_side::SideBySideFillEmpty::from_str(
opt.side_by_side_fill_empty,
&opt.line_numbers_zero_style,
opt.computed.true_color,
opt.git_config.as_ref(),
);
let navigate_regex = if (opt.navigate || opt.show_themes)
&& (opt.navigate_regex.is_none() || opt.navigate_regex == Some("".to_string()))
{
@@ -427,6 +435,7 @@ impl From<cli::Opt> for Config {
show_themes: opt.show_themes,
side_by_side: opt.side_by_side && !handlers::hunk::is_word_diff(),
side_by_side_data,
side_by_side_fill_empty,
styles_map,
syntax_set: opt.computed.syntax_set,
syntax_theme: opt.computed.syntax_theme,
+69 -6
View File
@@ -4,6 +4,7 @@ use unicode_width::UnicodeWidthStr;
use crate::ansi;
use crate::cli;
use crate::config::ensure_display_width_1;
use crate::config::{self, delta_unreachable, Config};
use crate::delta::DiffType;
use crate::delta::State;
@@ -56,6 +57,59 @@ impl SideBySideData {
}
}
#[derive(Clone, Debug)]
pub struct SideBySideFillEmpty {
pub filler: smol_str::SmolStr,
pub style: Style,
}
impl SideBySideFillEmpty {
fn new(filler: smol_str::SmolStr, style: Style) -> Self {
ensure_display_width_1(
"first argument of side-by-side-fill-empty",
filler.to_string(),
);
Self { filler, style }
}
fn default_filler(style: Style) -> Self {
Self {
filler: "/".into(),
style,
}
}
pub fn from_str(
s: Option<String>,
default_style: &str,
true_color: bool,
git_config: Option<&crate::git_config::GitConfig>,
) -> Option<Self> {
match s?.split(char::is_whitespace).collect::<Vec<_>>().as_slice() {
[] | [""] => None,
[single_arg] => match single_arg.to_lowercase().as_str() {
"false" => None,
"true" => Some(SideBySideFillEmpty::default_filler(Style::from_str(
default_style,
None,
None,
true_color,
git_config,
))),
_ => Some(SideBySideFillEmpty::new(
single_arg.into(),
Style::from_str(default_style, None, None, true_color, git_config),
)),
},
args => {
let style =
Style::from_str(&args[1..].join(" "), None, None, true_color, git_config);
Some(SideBySideFillEmpty::new(args[0].into(), style))
}
}
}
}
pub fn available_line_width(
config: &Config,
data: &line_numbers::LineNumbersData,
@@ -518,18 +572,27 @@ fn pad_panel_line_to_width(
config,
);
match bg_fill_mode {
Some(BgFillMethod::TryAnsiSequence) => {
match (
bg_fill_mode,
config.side_by_side_fill_empty.as_ref(),
panel_line_is_empty,
) {
(_, Some(fill), true) => panel_line.push_str(
&fill
.style
.paint(fill.filler.repeat(panel_width - text_width))
.to_string(),
),
(Some(BgFillMethod::TryAnsiSequence), _, _) => {
Painter::right_fill_background_color(panel_line, fill_style)
}
Some(BgFillMethod::Spaces) if text_width >= panel_width => (),
Some(BgFillMethod::Spaces) => panel_line.push_str(
#[allow(clippy::unnecessary_to_owned)]
(Some(BgFillMethod::Spaces), _, _) if text_width >= panel_width => (),
(Some(BgFillMethod::Spaces), _, _) => panel_line.push_str(
&fill_style
.paint(" ".repeat(panel_width - text_width))
.to_string(),
),
None => (),
(None, _, _) => (),
}
}
+1
View File
@@ -218,6 +218,7 @@ pub fn set_options(
show_colors,
show_themes,
side_by_side,
side_by_side_fill_empty,
wrap_max_lines,
wrap_right_prefix_symbol,
wrap_right_percent,