Skip to content

Commit e98ff09

Browse files
authored
Merge pull request #380 from Muscraft/fix-patch-tabs-last-line
fix: correctly count tabs for last patch line
2 parents 972df9c + 8985bd1 commit e98ff09

5 files changed

Lines changed: 126 additions & 56 deletions

File tree

src/renderer/render.rs

Lines changed: 33 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -1723,64 +1723,42 @@ fn emit_suggestion_default(
17231723
// logic to show the whole prior snippet, but the current output is not
17241724
// too bad to begin with, so we side-step that issue here.
17251725
for (i, line) in snippet.lines().enumerate() {
1726-
let full_line = if i == 0 {
1727-
// `snippet` starts at the first character that must be edited,
1728-
// which often does not contain the indent of the line. Retrieve
1729-
// the full line in order to avoid missing leading tabs.
1730-
sm.get_line(span_start.line).unwrap_or_default()
1731-
} else {
1732-
line
1733-
};
1734-
1735-
let tabs: usize = full_line
1736-
.chars()
1737-
.take(span_start.char)
1738-
.map(|ch| match ch {
1739-
'\t' => 3,
1740-
_ => 0,
1741-
})
1742-
.sum();
1743-
let line = normalize_whitespace(line);
1726+
let norm_line = normalize_whitespace(line);
17441727
// Going lower than buffer_offset (+ 1) would mean
17451728
// overwriting existing content in the buffer
17461729
let min_row = buffer_offset + usize::from(!matches_previous_suggestion);
17471730
let row = (row_num - 2 - (offset - i - 1)).max(min_row);
1748-
// On the first line, we highlight between the start of the part
1749-
// span, and the end of that line.
1750-
// On the last line, we highlight between the start of the line, and
1751-
// the column of the part span end.
1752-
// On all others, we highlight the whole line.
1753-
let start = if i == 0 {
1754-
(padding as isize + (span_start.char + tabs) as isize) as usize
1755-
} else {
1756-
padding
1757-
};
1758-
let end = if i == 0 {
1759-
(padding as isize
1760-
+ (span_start.char + tabs) as isize
1761-
+ line.chars().count() as isize)
1762-
as usize
1731+
let (start, end) = if i == 0 {
1732+
// On the first line, we highlight between the start of the part
1733+
// span, and the end of that line.
1734+
let full_line = sm.get_line(span_start.line).unwrap_or_default();
1735+
let extra_width = extra_width_from_tabs(full_line, span_start.char);
1736+
let start = span_start.char + extra_width;
1737+
(start, start + norm_line.chars().count())
17631738
} else if i == newlines - 1 {
1764-
(padding as isize + (span_end.char + tabs) as isize) as usize
1739+
// On the last line, we highlight between the start of the line, and
1740+
// the column of the part span end.
1741+
let extra_width = extra_width_from_tabs(line, span_end.char);
1742+
(0, span_end.char + extra_width)
17651743
} else {
1766-
(padding as isize + line.chars().count() as isize) as usize
1744+
// On all others, we highlight the whole line.
1745+
(0, norm_line.chars().count())
17671746
};
1768-
buffer.set_style_range(row, start, end, ElementStyle::Removal, true);
1747+
buffer.set_style_range(
1748+
row,
1749+
padding + start,
1750+
padding + end,
1751+
ElementStyle::Removal,
1752+
true,
1753+
);
17691754
}
17701755
} else {
1771-
let tabs: usize = snippet
1772-
.chars()
1773-
.take(span_start.char)
1774-
.map(|ch| match ch {
1775-
'\t' => 3,
1776-
_ => 0,
1777-
})
1778-
.sum();
1756+
let extra_width: usize = extra_width_from_tabs(snippet, span_start.char);
17791757
// The removed code fits all in one line.
17801758
buffer.set_style_range(
17811759
row_num - 2,
1782-
(padding as isize + (span_start.char + tabs) as isize) as usize,
1783-
(padding as isize + (span_end.char + tabs) as isize) as usize,
1760+
span_start.char + extra_width,
1761+
span_end.char + extra_width,
17841762
ElementStyle::Removal,
17851763
true,
17861764
);
@@ -1992,18 +1970,11 @@ fn draw_code_line(
19921970
// This is a no-op for empty ranges
19931971
if start != end {
19941972
// Account for tabs when highlighting (#87972).
1995-
let tabs: usize = line_to_add
1996-
.chars()
1997-
.take(start)
1998-
.map(|ch| match ch {
1999-
'\t' => 3,
2000-
_ => 0,
2001-
})
2002-
.sum();
1973+
let extra_width: usize = extra_width_from_tabs(line_to_add, start);
20031974
buffer.set_style_range(
20041975
*row_num,
2005-
max_line_num_len + 3 + start + tabs,
2006-
max_line_num_len + 3 + end + tabs,
1976+
max_line_num_len + 3 + start + extra_width,
1977+
max_line_num_len + 3 + end + extra_width,
20071978
ElementStyle::Addition,
20081979
true,
20091980
);
@@ -2311,6 +2282,12 @@ impl MessageOrTitle for Message<'_> {
23112282
}
23122283
}
23132284

2285+
/// Count extra display columns from tabs in the first `n` chars of `s`.
2286+
/// Each tab is displayed as 4 spaces, so the extra width per tab is 3.
2287+
fn extra_width_from_tabs(s: &str, n: usize) -> usize {
2288+
s.chars().take(n).filter(|&ch| ch == '\t').count() * 3
2289+
}
2290+
23142291
// instead of taking the String length or dividing by 10 while > 0, we multiply a limit by 10 until
23152292
// we're higher. If the loop isn't exited by the `return`, the last multiplication will wrap, which
23162293
// is OK, because while we cannot fit a higher power of 10 in a usize, the loop will end anyway.

tests/color/main.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ mod highlight_duplicated_diff_lines;
1414
mod highlight_first_line_tab_371;
1515
mod highlight_source;
1616
mod issue_9;
17+
mod multiline_removal_last_line_tabs;
1718
mod multiline_removal_suggestion;
1819
mod multiple_annotations;
1920
mod multiple_highlight_duplicated;
Lines changed: 37 additions & 0 deletions
Loading
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
use annotate_snippets::{renderer::DecorStyle, Group, Level, Patch, Renderer, Snippet};
2+
3+
use snapbox::{assert_data_eq, file};
4+
5+
#[test]
6+
fn test() {
7+
let report = &[Group::with_level(Level::ERROR).element(
8+
Snippet::source("a.foo(|t| {\n\t\t\tb\n\t\t\t}).bar()\n").patch(Patch::new(1..22, "")),
9+
)];
10+
11+
let expected_ascii = file!["multiline_removal_last_line_tabs.ascii.term.svg": TermSvg];
12+
let renderer = Renderer::styled();
13+
assert_data_eq!(renderer.render(report), expected_ascii);
14+
15+
let expected_unicode = file!["multiline_removal_last_line_tabs.unicode.term.svg": TermSvg];
16+
let renderer = renderer.decor_style(DecorStyle::Unicode);
17+
assert_data_eq!(renderer.render(report), expected_unicode);
18+
}
Lines changed: 37 additions & 0 deletions
Loading

0 commit comments

Comments
 (0)