From d17b037e317dffa6fb08585a2c6127a82cb1ddf8 Mon Sep 17 00:00:00 2001 From: Scott Schafer Date: Tue, 20 Jan 2026 16:54:40 -0700 Subject: [PATCH 1/2] test: Unicode highlight alignment in patches Co-authored-by: Teru Shigure --- ...f_line_with_wide_characters.ascii.term.svg | 54 +++++++++++++++++++ ...ighlight_diff_line_with_wide_characters.rs | 44 +++++++++++++++ ...line_with_wide_characters.unicode.term.svg | 54 +++++++++++++++++++ tests/color/main.rs | 1 + 4 files changed, 153 insertions(+) create mode 100644 tests/color/highlight_diff_line_with_wide_characters.ascii.term.svg create mode 100644 tests/color/highlight_diff_line_with_wide_characters.rs create mode 100644 tests/color/highlight_diff_line_with_wide_characters.unicode.term.svg diff --git a/tests/color/highlight_diff_line_with_wide_characters.ascii.term.svg b/tests/color/highlight_diff_line_with_wide_characters.ascii.term.svg new file mode 100644 index 0000000..a998ea9 --- /dev/null +++ b/tests/color/highlight_diff_line_with_wide_characters.ascii.term.svg @@ -0,0 +1,54 @@ + + + + + + + error[E0422]: cannot find struct, variant or union type `哈哈哈哈` in this scope + + --> $DIR/highlight_diff_line_with_wide_characters.rs:3:18 + + | + + 1 | struct 啊啊啊啊 {} + + | ------------------ similarly named struct `啊啊啊啊` defined here + + 2 | + + 3 | const 哦哦: 啊啊啊啊 = 哈哈哈哈 {}; // some comment + + | ^^^^^^^^ here + + | + + help: a struct with a similar name exists: `啊啊啊啊` + + | + + 3 - const 哦哦: 啊啊啊啊 = 哈哈哈哈 {}; // some comment + + 3 + const 哦哦: 啊啊啊啊 = 啊啊啊啊 {}; // some comment + + | + + + + diff --git a/tests/color/highlight_diff_line_with_wide_characters.rs b/tests/color/highlight_diff_line_with_wide_characters.rs new file mode 100644 index 0000000..ec29de7 --- /dev/null +++ b/tests/color/highlight_diff_line_with_wide_characters.rs @@ -0,0 +1,44 @@ +use annotate_snippets::{renderer::DecorStyle, AnnotationKind, Level, Patch, Renderer, Snippet}; + +use snapbox::{assert_data_eq, file}; + +#[test] +fn case() { + let source = r#"struct 啊啊啊啊 {} + +const 哦哦: 啊啊啊啊 = 哈哈哈哈 {}; // some comment"#; + + let path = "$DIR/highlight_diff_line_with_wide_characters.rs"; + + let report = &[ + Level::ERROR + .primary_title("cannot find struct, variant or union type `哈哈哈哈` in this scope") + .id("E0422") + .element( + Snippet::source(source) + .path(path) + .annotation(AnnotationKind::Primary.span(53..65).label("here")) + .annotation( + AnnotationKind::Context + .span(0..22) + .label("similarly named struct `啊啊啊啊` defined here"), + ), + ), + Level::HELP + .secondary_title("a struct with a similar name exists: `啊啊啊啊`") + .element( + Snippet::source(source) + .path(path) + .patch(Patch::new(53..65, "啊啊啊啊")), + ), + ]; + + let expected_ascii = file!["highlight_diff_line_with_wide_characters.ascii.term.svg": TermSvg]; + let renderer = Renderer::styled(); + assert_data_eq!(renderer.render(report), expected_ascii); + + let expected_unicode = + file!["highlight_diff_line_with_wide_characters.unicode.term.svg": TermSvg]; + let renderer = renderer.decor_style(DecorStyle::Unicode); + assert_data_eq!(renderer.render(report), expected_unicode); +} diff --git a/tests/color/highlight_diff_line_with_wide_characters.unicode.term.svg b/tests/color/highlight_diff_line_with_wide_characters.unicode.term.svg new file mode 100644 index 0000000..e4a7019 --- /dev/null +++ b/tests/color/highlight_diff_line_with_wide_characters.unicode.term.svg @@ -0,0 +1,54 @@ + + + + + + + error[E0422]: cannot find struct, variant or union type `哈哈哈哈` in this scope + + ╭▸ $DIR/highlight_diff_line_with_wide_characters.rs:3:18 + + + + 1 struct 啊啊啊啊 {} + + ────────────────── similarly named struct `啊啊啊啊` defined here + + 2 + + 3 const 哦哦: 啊啊啊啊 = 哈哈哈哈 {}; // some comment + + ━━━━━━━━ here + + ╰╴ + + help: a struct with a similar name exists: `啊啊啊啊` + + ╭╴ + + 3 - const 哦哦: 啊啊啊啊 = 哈哈哈哈 {}; // some comment + + 3 + const 哦哦: 啊啊啊啊 = 啊啊啊啊 {}; // some comment + + ╰╴ + + + + diff --git a/tests/color/main.rs b/tests/color/main.rs index 7d78f4b..77ec70f 100644 --- a/tests/color/main.rs +++ b/tests/color/main.rs @@ -9,6 +9,7 @@ mod fold_ann_multiline; mod fold_bad_origin_line; mod fold_leading; mod fold_trailing; +mod highlight_diff_line_with_wide_characters; mod highlight_duplicated_diff_lines; mod highlight_source; mod issue_9; From 3d365cc7a3238b847af335e4471a2187a4094a66 Mon Sep 17 00:00:00 2001 From: Scott Schafer Date: Tue, 20 Jan 2026 16:54:40 -0700 Subject: [PATCH 2/2] fix: Fix Unicode highlight alignment in patches --- src/renderer/render.rs | 30 +++++++++++++++---- ...f_line_with_wide_characters.ascii.term.svg | 2 +- ...line_with_wide_characters.unicode.term.svg | 2 +- 3 files changed, 26 insertions(+), 8 deletions(-) diff --git a/src/renderer/render.rs b/src/renderer/render.rs index 283fe77..8cf7ffa 100644 --- a/src/renderer/render.rs +++ b/src/renderer/render.rs @@ -1721,6 +1721,14 @@ fn emit_suggestion_default( // logic to show the whole prior snippet, but the current output is not // too bad to begin with, so we side-step that issue here. for (i, line) in snippet.lines().enumerate() { + let tabs: usize = line + .chars() + .take(span_start.char) + .map(|ch| match ch { + '\t' => 3, + _ => 0, + }) + .sum(); let line = normalize_whitespace(line); // Going lower than buffer_offset (+ 1) would mean // overwriting existing content in the buffer @@ -1732,26 +1740,36 @@ fn emit_suggestion_default( // the column of the part span end. // On all others, we highlight the whole line. let start = if i == 0 { - (padding as isize + span_start_pos as isize) as usize + (padding as isize + (span_start.char + tabs) as isize) as usize } else { padding }; let end = if i == 0 { - (padding as isize + span_start_pos as isize + line.len() as isize) + (padding as isize + + (span_start.char + tabs) as isize + + line.chars().count() as isize) as usize } else if i == newlines - 1 { - (padding as isize + span_end_pos as isize) as usize + (padding as isize + (span_end.char + tabs) as isize) as usize } else { - (padding as isize + line.len() as isize) as usize + (padding as isize + line.chars().count() as isize) as usize }; buffer.set_style_range(row, start, end, ElementStyle::Removal, true); } } else { + let tabs: usize = snippet + .chars() + .take(span_start.char) + .map(|ch| match ch { + '\t' => 3, + _ => 0, + }) + .sum(); // The removed code fits all in one line. buffer.set_style_range( row_num - 2, - (padding as isize + span_start_pos as isize) as usize, - (padding as isize + span_end_pos as isize) as usize, + (padding as isize + (span_start.char + tabs) as isize) as usize, + (padding as isize + (span_end.char + tabs) as isize) as usize, ElementStyle::Removal, true, ); diff --git a/tests/color/highlight_diff_line_with_wide_characters.ascii.term.svg b/tests/color/highlight_diff_line_with_wide_characters.ascii.term.svg index a998ea9..de62e3c 100644 --- a/tests/color/highlight_diff_line_with_wide_characters.ascii.term.svg +++ b/tests/color/highlight_diff_line_with_wide_characters.ascii.term.svg @@ -43,7 +43,7 @@ | - 3 - const 哦哦: 啊啊啊啊 = 哈哈哈哈 {}; // some comment + 3 - const 哦哦: 啊啊啊啊 = 哈哈哈哈 {}; // some comment 3 + const 哦哦: 啊啊啊啊 = 啊啊啊啊 {}; // some comment diff --git a/tests/color/highlight_diff_line_with_wide_characters.unicode.term.svg b/tests/color/highlight_diff_line_with_wide_characters.unicode.term.svg index e4a7019..fe3154e 100644 --- a/tests/color/highlight_diff_line_with_wide_characters.unicode.term.svg +++ b/tests/color/highlight_diff_line_with_wide_characters.unicode.term.svg @@ -43,7 +43,7 @@ ╭╴ - 3 - const 哦哦: 啊啊啊啊 = 哈哈哈哈 {}; // some comment + 3 - const 哦哦: 啊啊啊啊 = 哈哈哈哈 {}; // some comment 3 + const 哦哦: 啊啊啊啊 = 啊啊啊啊 {}; // some comment