Skip to content

Commit 7e85cbd

Browse files
authored
Merge pull request #331 from Muscraft/fix-unicode-trimming
fix: Use correct offset when trimming unicode char
2 parents fd61af9 + b5eae2a commit 7e85cbd

File tree

2 files changed

+172
-64
lines changed

2 files changed

+172
-64
lines changed

src/renderer/render.rs

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -664,12 +664,12 @@ fn render_snippet_annotations(
664664
let mut label_right_margin = 0;
665665
let mut max_line_len = 0;
666666
for line_info in annotated_lines {
667-
max_line_len = max(max_line_len, line_info.line.len());
667+
max_line_len = max(max_line_len, str_width(line_info.line));
668668
for ann in &line_info.annotations {
669669
span_right_margin = max(span_right_margin, ann.start.display);
670670
span_right_margin = max(span_right_margin, ann.end.display);
671671
// FIXME: account for labels not in the same line
672-
let label_right = ann.label.as_ref().map_or(0, |l| l.len() + 1);
672+
let label_right = ann.label.as_ref().map_or(0, |l| str_width(l) + 1);
673673
label_right_margin = max(label_right_margin, ann.end.display + label_right);
674674
}
675675
}
@@ -1995,23 +1995,34 @@ fn draw_line(
19951995
// Create the source line we will highlight.
19961996
let mut left = margin.left(line_len);
19971997
let right = margin.right(line_len);
1998-
// FIXME: The following code looks fishy. See #132860.
1999-
// On long lines, we strip the source line, accounting for unicode.
1998+
20001999
let mut taken = 0;
20012000
let mut skipped = 0;
20022001
let code: String = source_string
20032002
.chars()
20042003
.skip_while(|ch| {
2005-
skipped += char_width(*ch);
2006-
skipped <= left
2004+
let w = char_width(*ch);
2005+
// If `skipped` is less than `left`, always skip the next `ch`,
2006+
// even if `ch` is a multi-width char that would make `skipped`
2007+
// exceed `left`. This ensures that we do not exceed term width on
2008+
// source lines.
2009+
if skipped < left {
2010+
skipped += w;
2011+
true
2012+
} else {
2013+
false
2014+
}
20072015
})
20082016
.take_while(|ch| {
20092017
// Make sure that the trimming on the right will fall within the terminal width.
20102018
taken += char_width(*ch);
20112019
taken <= (right - left)
20122020
})
20132021
.collect();
2014-
2022+
// If we skipped more than `left`, adjust `left` to account for it.
2023+
if skipped > left {
2024+
left += skipped - left;
2025+
}
20152026
let placeholder = renderer.decor_style.margin();
20162027
let padding = str_width(placeholder);
20172028
let (width_taken, bytes_taken) = if margin.was_cut_left() {
@@ -2027,10 +2038,6 @@ fn draw_line(
20272038
}
20282039
}
20292040

2030-
if width_taken > padding {
2031-
left -= width_taken - padding;
2032-
}
2033-
20342041
buffer.puts(
20352042
line_offset,
20362043
code_offset,

tests/formatter.rs

Lines changed: 154 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -2952,111 +2952,212 @@ error: title
29522952
}
29532953

29542954
#[test]
2955-
fn unicode_cut_handling2() {
2955+
fn trim_unicode_annotate_ascii_end_with_label() {
29562956
let source = "/*这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。*/?";
2957-
let input = &[Level::ERROR
2958-
.primary_title("expected item, found `?`").element(
2959-
Snippet::source(source)
2960-
.fold(false)
2961-
.annotation(AnnotationKind::Primary.span(499..500).label("expected item"))
2962-
).element(
2963-
Level::NOTE.message("for a full list of items that can appear in modules, see <https://doc.rust-lang.org/reference/items.html>")
2964-
2965-
)];
2957+
let input = &[Group::with_level(Level::ERROR).element(
2958+
Snippet::source(source).annotation(
2959+
AnnotationKind::Primary
2960+
.span(499..500)
2961+
.label("expected item"),
2962+
),
2963+
)];
29662964

29672965
let expected_ascii = str![[r#"
2968-
error: expected item, found `?`
29692966
|
29702967
1 | ... 的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。*/?
2971-
| ^ expected item
2972-
|
2973-
= note: for a full list of items that can appear in modules, see <https://doc.rust-lang.org/reference/items.html>
2968+
| ^ expected item
29742969
"#]];
29752970

29762971
let renderer = Renderer::plain();
29772972
assert_data_eq!(renderer.render(input), expected_ascii);
29782973

29792974
let expected_unicode = str![[r#"
2980-
error: expected item, found `?`
29812975
╭▸
29822976
1 │ … 宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。*/?
2983-
│ ━ expected item
2984-
2985-
╰ note: for a full list of items that can appear in modules, see <https://doc.rust-lang.org/reference/items.html>
2977+
╰╴ ━ expected item
29862978
"#]];
29872979
let renderer = renderer.decor_style(DecorStyle::Unicode);
29882980
assert_data_eq!(renderer.render(input), expected_unicode);
29892981
}
29902982

29912983
#[test]
2992-
fn unicode_cut_handling3() {
2984+
fn trim_unicode_annotate_ascii_end_no_label() {
29932985
let source = "/*这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。*/?";
2994-
let input = &[Level::ERROR
2995-
.primary_title("expected item, found `?`").element(
2996-
Snippet::source(source)
2997-
.fold(false)
2998-
.annotation(AnnotationKind::Primary.span(251..254).label("expected item"))
2999-
).element(
3000-
Level::NOTE.message("for a full list of items that can appear in modules, see <https://doc.rust-lang.org/reference/items.html>")
2986+
let input = &[Group::with_level(Level::ERROR)
2987+
.element(Snippet::source(source).annotation(AnnotationKind::Primary.span(499..500)))];
2988+
2989+
let expected_ascii = str![[r#"
2990+
|
2991+
1 | ... 这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。*/?
2992+
| ^
2993+
"#]];
30012994

3002-
)];
2995+
let renderer = Renderer::plain();
2996+
assert_data_eq!(renderer.render(input), expected_ascii);
2997+
2998+
let expected_unicode = str![[r#"
2999+
╭▸
3000+
1 │ … 。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。*/?
3001+
╰╴ ━
3002+
"#]];
3003+
let renderer = renderer.decor_style(DecorStyle::Unicode);
3004+
assert_data_eq!(renderer.render(input), expected_unicode);
3005+
}
3006+
3007+
#[test]
3008+
fn trim_unicode_annotate_unicode_end_with_label() {
3009+
let source = "/*这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。*/好";
3010+
let input = &[Group::with_level(Level::ERROR).element(
3011+
Snippet::source(source).annotation(
3012+
AnnotationKind::Primary
3013+
.span(499..502)
3014+
.label("expected item"),
3015+
),
3016+
)];
30033017

30043018
let expected_ascii = str![[r#"
3005-
error: expected item, found `?`
30063019
|
3007-
1 | ... 。这是宽的。这是宽的。这是宽的...
3008-
| ^^ expected item
3020+
1 | ... 的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。*/好
3021+
| ^^ expected item
3022+
"#]];
3023+
3024+
let renderer = Renderer::plain();
3025+
assert_data_eq!(renderer.render(input), expected_ascii);
3026+
3027+
let expected_unicode = str![[r#"
3028+
╭▸
3029+
1 │ … 宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。*/好
3030+
╰╴ ━━ expected item
3031+
"#]];
3032+
let renderer = renderer.decor_style(DecorStyle::Unicode);
3033+
assert_data_eq!(renderer.render(input), expected_unicode);
3034+
}
3035+
3036+
#[test]
3037+
fn trim_unicode_annotate_unicode_end_no_label() {
3038+
let source = "/*这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。*/好";
3039+
let input = &[Group::with_level(Level::ERROR)
3040+
.element(Snippet::source(source).annotation(AnnotationKind::Primary.span(499..502)))];
3041+
3042+
let expected_ascii = str![[r#"
30093043
|
3010-
= note: for a full list of items that can appear in modules, see <https://doc.rust-lang.org/reference/items.html>
3044+
1 | ... 这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。*/好
3045+
| ^^
3046+
"#]];
3047+
3048+
let renderer = Renderer::plain();
3049+
assert_data_eq!(renderer.render(input), expected_ascii);
3050+
3051+
let expected_unicode = str![[r#"
3052+
╭▸
3053+
1 │ … 。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。*/好
3054+
╰╴ ━━
3055+
"#]];
3056+
let renderer = renderer.decor_style(DecorStyle::Unicode);
3057+
assert_data_eq!(renderer.render(input), expected_unicode);
3058+
}
3059+
3060+
#[test]
3061+
fn trim_unicode_annotate_unicode_middle_with_label() {
3062+
let source = "/*这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。*/?";
3063+
let input = &[Group::with_level(Level::ERROR).element(
3064+
Snippet::source(source).annotation(
3065+
AnnotationKind::Primary
3066+
.span(251..254)
3067+
.label("expected item"),
3068+
),
3069+
)];
3070+
3071+
let expected_ascii = str![[r#"
3072+
|
3073+
1 | ... 这是宽的。这是宽的。这是宽的。...
3074+
| ^^ expected item
30113075
"#]];
30123076

30133077
let renderer = Renderer::plain().term_width(43);
30143078
assert_data_eq!(renderer.render(input), expected_ascii);
30153079

30163080
let expected_unicode = str![[r#"
3017-
error: expected item, found `?`
30183081
╭▸
3019-
1 │ … 的。这是宽的。这是宽的。这是宽的。…
3020-
│ ━━ expected item
3021-
3022-
╰ note: for a full list of items that can appear in modules, see <https://doc.rust-lang.org/reference/items.html>
3082+
1 │ … 。这是宽的。这是宽的。这是宽的。这…
3083+
╰╴ ━━ expected item
30233084
"#]];
30243085
let renderer = renderer.decor_style(DecorStyle::Unicode);
30253086
assert_data_eq!(renderer.render(input), expected_unicode);
30263087
}
30273088

30283089
#[test]
3029-
fn unicode_cut_handling4() {
3030-
let source = "/*aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa*/?";
3031-
let input = &[Level::ERROR
3032-
.primary_title("expected item, found `?`").element(
3033-
Snippet::source(source)
3034-
.fold(false)
3035-
.annotation(AnnotationKind::Primary.span(334..335).label("expected item"))
3036-
).element(
3037-
Level::NOTE.message("for a full list of items that can appear in modules, see <https://doc.rust-lang.org/reference/items.html>")
3090+
fn trim_unicode_annotate_unicode_middle_no_label() {
3091+
let source = "/*这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。*/?";
3092+
let input = &[Group::with_level(Level::ERROR)
3093+
.element(Snippet::source(source).annotation(AnnotationKind::Primary.span(251..254)))];
3094+
3095+
let expected_ascii = str![[r#"
3096+
|
3097+
1 | ... 是宽的。这是宽的。这是宽的。这...
3098+
| ^^
3099+
"#]];
3100+
3101+
let renderer = Renderer::plain().term_width(43);
3102+
assert_data_eq!(renderer.render(input), expected_ascii);
3103+
3104+
let expected_unicode = str![[r#"
3105+
╭▸
3106+
1 │ … 这是宽的。这是宽的。这是宽的。这是…
3107+
╰╴ ━━
3108+
"#]];
3109+
let renderer = renderer.decor_style(DecorStyle::Unicode);
3110+
assert_data_eq!(renderer.render(input), expected_unicode);
3111+
}
30383112

3039-
)];
3113+
#[test]
3114+
fn trim_ascii_annotate_ascii_end_with_label() {
3115+
let source = "/*aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa*/?";
3116+
let input = &[Group::with_level(Level::ERROR).element(
3117+
Snippet::source(source).annotation(
3118+
AnnotationKind::Primary
3119+
.span(334..335)
3120+
.label("expected item"),
3121+
),
3122+
)];
30403123

30413124
let expected_ascii = str![[r#"
3042-
error: expected item, found `?`
30433125
|
30443126
1 | ...aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa*/?
30453127
| ^ expected item
3046-
|
3047-
= note: for a full list of items that can appear in modules, see <https://doc.rust-lang.org/reference/items.html>
30483128
"#]];
30493129

30503130
let renderer = Renderer::plain();
30513131
assert_data_eq!(renderer.render(input), expected_ascii);
30523132

30533133
let expected_unicode = str![[r#"
3054-
error: expected item, found `?`
30553134
╭▸
30563135
1 │ …aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa*/?
3057-
│ ━ expected item
3058-
3059-
╰ note: for a full list of items that can appear in modules, see <https://doc.rust-lang.org/reference/items.html>
3136+
╰╴ ━ expected item
3137+
"#]];
3138+
let renderer = renderer.decor_style(DecorStyle::Unicode);
3139+
assert_data_eq!(renderer.render(input), expected_unicode);
3140+
}
3141+
3142+
#[test]
3143+
fn trim_ascii_annotate_ascii_end_no_label() {
3144+
let source = "/*aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa*/?";
3145+
let input = &[Group::with_level(Level::ERROR)
3146+
.element(Snippet::source(source).annotation(AnnotationKind::Primary.span(334..335)))];
3147+
3148+
let expected_ascii = str![[r#"
3149+
|
3150+
1 | ...aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa*/?
3151+
| ^
3152+
"#]];
3153+
3154+
let renderer = Renderer::plain();
3155+
assert_data_eq!(renderer.render(input), expected_ascii);
3156+
3157+
let expected_unicode = str![[r#"
3158+
╭▸
3159+
1 │ …aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa*/?
3160+
╰╴ ━
30603161
"#]];
30613162
let renderer = renderer.decor_style(DecorStyle::Unicode);
30623163
assert_data_eq!(renderer.render(input), expected_unicode);

0 commit comments

Comments
 (0)