Skip to content

Commit 694d299

Browse files
committed
fix: Remove false positive line trimming
Previously, it was possible for a `...` to be inserted when no trimming was actually done. For example: ``` | 1 | version = "0.1.0" 2 | # Ensure that the spans from toml handle utf-8 correctly 3 | authors = [ | ___________^ 4 | | { name = "Z...ALGO", email = 1 } 5 | | ] | |_^ RUF200 | ``` After this fix, the `...` is no longer inserted: ``` | 1 | version = "0.1.0" 2 | # Ensure that the spans from toml handle utf-8 correctly 3 | authors = [ | ___________^ 4 | | { name = "ZALGO", email = 1 } 5 | | ] | |_^ RUF200 | ``` This is another low confidence fix where I'm not sure that it's right. But the implementation was previously seeming to conflate the line length (in bytes) versus the actual rendered length. So instead of trying to do some math to figure out whether an ellipsis should be inserted, we just keep track of whether the code line we write was truncated or not.
1 parent 7132bf3 commit 694d299

File tree

3 files changed

+47
-29
lines changed

3 files changed

+47
-29
lines changed

Diff for: src/renderer/display_list.rs

+17-17
Original file line numberDiff line numberDiff line change
@@ -331,28 +331,28 @@ impl DisplaySet<'_> {
331331

332332
// On long lines, we strip the source line, accounting for unicode.
333333
let mut taken = 0;
334-
let code: String = text
335-
.chars()
336-
.skip(left)
337-
.take_while(|ch| {
338-
// Make sure that the trimming on the right will fall within the terminal width.
339-
// FIXME: `unicode_width` sometimes disagrees with terminals on how wide a `char`
340-
// is. For now, just accept that sometimes the code line will be longer than
341-
// desired.
342-
let next = unicode_width::UnicodeWidthChar::width(*ch).unwrap_or(1);
343-
if taken + next > right - left {
344-
return false;
345-
}
346-
taken += next;
347-
true
348-
})
349-
.collect();
334+
let mut was_cut_right = false;
335+
let mut code = String::new();
336+
for ch in text.chars().skip(left) {
337+
// Make sure that the trimming on the right will fall within the terminal width.
338+
// FIXME: `unicode_width` sometimes disagrees with terminals on how wide a `char`
339+
// is. For now, just accept that sometimes the code line will be longer than
340+
// desired.
341+
let next = unicode_width::UnicodeWidthChar::width(ch).unwrap_or(1);
342+
if taken + next > right - left {
343+
was_cut_right = true;
344+
break;
345+
}
346+
taken += next;
347+
code.push(ch);
348+
}
349+
350350
buffer.puts(line_offset, code_offset, &code, Style::new());
351351
if self.margin.was_cut_left() {
352352
// We have stripped some code/whitespace from the beginning, make it clear.
353353
buffer.puts(line_offset, code_offset, "...", *lineno_color);
354354
}
355-
if self.margin.was_cut_right(line_len) {
355+
if was_cut_right {
356356
buffer.puts(line_offset, code_offset + taken - 3, "...", *lineno_color);
357357
}
358358

Diff for: src/renderer/margin.rs

-12
Original file line numberDiff line numberDiff line change
@@ -58,18 +58,6 @@ impl Margin {
5858
self.computed_left > 0
5959
}
6060

61-
pub(crate) fn was_cut_right(&self, line_len: usize) -> bool {
62-
let right =
63-
if self.computed_right == self.span_right || self.computed_right == self.label_right {
64-
// Account for the "..." padding given above. Otherwise we end up with code lines that
65-
// do fit but end in "..." as if they were trimmed.
66-
self.computed_right - ELLIPSIS_PASSING
67-
} else {
68-
self.computed_right
69-
};
70-
right < line_len && self.computed_left + self.term_width < line_len
71-
}
72-
7361
fn compute(&mut self, max_line_len: usize) {
7462
// When there's a lot of whitespace (>20), we want to trim it as it is useless.
7563
self.computed_left = if self.whitespace_left > LONG_WHITESPACE {

Diff for: tests/formatter.rs

+30
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,36 @@ use annotate_snippets::{Level, Renderer, Snippet};
22

33
use snapbox::{assert_data_eq, str};
44

5+
// This tests that an ellipsis is not inserted into Unicode text when a line
6+
// wasn't actually trimmed.
7+
//
8+
// This is a regression test where `...` was inserted because the code wasn't
9+
// properly accounting for the *rendered* length versus the length in bytes in
10+
// all cases.
11+
#[test]
12+
fn unicode_cut_handling() {
13+
let source = "version = \"0.1.0\"\n# Ensure that the spans from toml handle utf-8 correctly\nauthors = [\n { name = \"Z\u{351}\u{36b}\u{343}\u{36a}\u{302}\u{36b}\u{33d}\u{34f}\u{334}\u{319}\u{324}\u{31e}\u{349}\u{35a}\u{32f}\u{31e}\u{320}\u{34d}A\u{36b}\u{357}\u{334}\u{362}\u{335}\u{31c}\u{330}\u{354}L\u{368}\u{367}\u{369}\u{358}\u{320}G\u{311}\u{357}\u{30e}\u{305}\u{35b}\u{341}\u{334}\u{33b}\u{348}\u{34d}\u{354}\u{339}O\u{342}\u{30c}\u{30c}\u{358}\u{328}\u{335}\u{339}\u{33b}\u{31d}\u{333}\", email = 1 }\n]\n";
14+
let input = Level::Error.title("title").snippet(
15+
Snippet::source(source)
16+
.fold(false)
17+
.annotation(Level::Error.span(85..228).label("annotation")),
18+
);
19+
let expected = "\
20+
error: title
21+
|
22+
1 | version = \"0.1.0\"
23+
2 | # Ensure that the spans from toml handle utf-8 correctly
24+
3 | authors = [
25+
| ___________^
26+
4 | | { name = \"Z\u{351}\u{36b}\u{343}\u{36a}\u{302}\u{36b}\u{33d}\u{34f}\u{334}\u{319}\u{324}\u{31e}\u{349}\u{35a}\u{32f}\u{31e}\u{320}\u{34d}A\u{36b}\u{357}\u{334}\u{362}\u{335}\u{31c}\u{330}\u{354}L\u{368}\u{367}\u{369}\u{358}\u{320}G\u{311}\u{357}\u{30e}\u{305}\u{35b}\u{341}\u{334}\u{33b}\u{348}\u{34d}\u{354}\u{339}O\u{342}\u{30c}\u{30c}\u{358}\u{328}\u{335}\u{339}\u{33b}\u{31d}\u{333}\", email = 1 }
27+
5 | | ]
28+
| |_^ annotation
29+
|\
30+
";
31+
let renderer = Renderer::plain();
32+
assert_data_eq!(renderer.render(input).to_string(), expected);
33+
}
34+
535
#[test]
636
fn test_i_29() {
737
let snippets = Level::Error.title("oops").snippet(

0 commit comments

Comments
 (0)