diff --git a/src/side_diff.rs b/src/side_diff.rs index 56953d2..1128e2e 100644 --- a/src/side_diff.rs +++ b/src/side_diff.rs @@ -285,6 +285,8 @@ fn push_output( // the diff always want to put all tabs possible in the usable are, // even in the middle space between the gutters if possible. + // Pad to the middle of the gutter so the marker doesn't drift on wider gutters (#269). + format_tabs_and_spaces(half_width + 1, separator_pos, config, output)?; output.write_all(&[symbol])?; if !right_ln.is_empty() { format_tabs_and_spaces(separator_pos + 1, column_two_offset, config, output)?; @@ -1003,6 +1005,26 @@ mod tests { assert_eq!(contains_string(&output, "equal"), 2) } + // Regression for #269: the `-y` marker must sit at the gutter middle at any width. + #[test] + fn test_gutter_marker_column_wide_gutter() { + let params = Params { + tabsize: 8, + expand_tabs: true, + width: 40, + ..Default::default() + }; + let mut output = vec![]; + diff(b"aa\n", b"", &mut output, ¶ms); + let text = String::from_utf8(output).unwrap(); + let line = text.lines().next().unwrap(); + assert_eq!( + line.find('<'), + Some(19), + "gutter marker '<' should be at column 19 for --width=40, got: {line:?}" + ); + } + #[test] fn test_different_lines() { let params = generate_params(); diff --git a/tests/integration.rs b/tests/integration.rs index 12aabb8..99cce27 100644 --- a/tests/integration.rs +++ b/tests/integration.rs @@ -343,6 +343,54 @@ mod diff { Ok(()) } + + // Visual (tab-aware) column of the `-y` gutter marker on the first output line. + fn side_marker_visual_column(output: &[u8], tabsize: usize) -> Option { + let text = String::from_utf8_lossy(output); + let line = text.lines().next()?; + let mut col = 0; + for c in line.chars() { + match c { + '<' | '|' | '>' => return Some(col), + '\t' => col += tabsize - (col % tabsize), + _ => col += 1, + } + } + None + } + + // Regression for #269: the `-y` gutter marker must sit at the middle of the gutter. + #[test] + fn sdiff_gutter_marker_column() -> Result<(), Box> { + let mut file1 = NamedTempFile::new()?; + file1.write_all("aa\n".as_bytes())?; + let mut file2 = NamedTempFile::new()?; + file2.write_all("bb\n".as_bytes())?; + + // --expand-tabs: padding is spaces, marker lands at visual column 19. + let mut cmd = cargo_bin_cmd!("diffutils"); + cmd.arg("diff") + .arg("-y") + .arg("--width=40") + .arg("--expand-tabs") + .arg(file1.path()) + .arg(file2.path()); + let output = cmd.output().unwrap().stdout; + assert_eq!(side_marker_visual_column(&output, 8), Some(19)); + + // Default (expand_tabs: false): padding goes through the tab path, but the + // marker must still land at the same visual column. + let mut cmd = cargo_bin_cmd!("diffutils"); + cmd.arg("diff") + .arg("-y") + .arg("--width=40") + .arg(file1.path()) + .arg(file2.path()); + let output = cmd.output().unwrap().stdout; + assert_eq!(side_marker_visual_column(&output, 8), Some(19)); + + Ok(()) + } } mod cmp {