diff --git a/src/renderer/render.rs b/src/renderer/render.rs index 843147a..03c7005 100644 --- a/src/renderer/render.rs +++ b/src/renderer/render.rs @@ -664,12 +664,12 @@ fn render_snippet_annotations( let mut label_right_margin = 0; let mut max_line_len = 0; for line_info in annotated_lines { - max_line_len = max(max_line_len, line_info.line.len()); + max_line_len = max(max_line_len, str_width(line_info.line)); for ann in &line_info.annotations { span_right_margin = max(span_right_margin, ann.start.display); span_right_margin = max(span_right_margin, ann.end.display); // FIXME: account for labels not in the same line - let label_right = ann.label.as_ref().map_or(0, |l| l.len() + 1); + let label_right = ann.label.as_ref().map_or(0, |l| str_width(l) + 1); label_right_margin = max(label_right_margin, ann.end.display + label_right); } } @@ -1995,15 +1995,23 @@ fn draw_line( // Create the source line we will highlight. let mut left = margin.left(line_len); let right = margin.right(line_len); - // FIXME: The following code looks fishy. See #132860. - // On long lines, we strip the source line, accounting for unicode. + let mut taken = 0; let mut skipped = 0; let code: String = source_string .chars() .skip_while(|ch| { - skipped += char_width(*ch); - skipped <= left + let w = char_width(*ch); + // If `skipped` is less than `left`, always skip the next `ch`, + // even if `ch` is a multi-width char that would make `skipped` + // exceed `left`. This ensures that we do not exceed term width on + // source lines. + if skipped < left { + skipped += w; + true + } else { + false + } }) .take_while(|ch| { // Make sure that the trimming on the right will fall within the terminal width. @@ -2011,7 +2019,10 @@ fn draw_line( taken <= (right - left) }) .collect(); - + // If we skipped more than `left`, adjust `left` to account for it. + if skipped > left { + left += skipped - left; + } let placeholder = renderer.decor_style.margin(); let padding = str_width(placeholder); let (width_taken, bytes_taken) = if margin.was_cut_left() { @@ -2027,10 +2038,6 @@ fn draw_line( } } - if width_taken > padding { - left -= width_taken - padding; - } - buffer.puts( line_offset, code_offset, diff --git a/tests/formatter.rs b/tests/formatter.rs index f7c0f28..99a0863 100644 --- a/tests/formatter.rs +++ b/tests/formatter.rs @@ -2952,111 +2952,212 @@ error: title } #[test] -fn unicode_cut_handling2() { +fn trim_unicode_annotate_ascii_end_with_label() { let source = "/*这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。*/?"; - let input = &[Level::ERROR - .primary_title("expected item, found `?`").element( - Snippet::source(source) - .fold(false) - .annotation(AnnotationKind::Primary.span(499..500).label("expected item")) - ).element( - Level::NOTE.message("for a full list of items that can appear in modules, see ") - - )]; + let input = &[Group::with_level(Level::ERROR).element( + Snippet::source(source).annotation( + AnnotationKind::Primary + .span(499..500) + .label("expected item"), + ), + )]; let expected_ascii = str![[r#" -error: expected item, found `?` | 1 | ... 的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。*/? - | ^ expected item - | - = note: for a full list of items that can appear in modules, see + | ^ expected item "#]]; let renderer = Renderer::plain(); assert_data_eq!(renderer.render(input), expected_ascii); let expected_unicode = str![[r#" -error: expected item, found `?` ╭▸ 1 │ … 宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。*/? - │ ━ expected item - │ - ╰ note: for a full list of items that can appear in modules, see + ╰╴ ━ expected item "#]]; let renderer = renderer.decor_style(DecorStyle::Unicode); assert_data_eq!(renderer.render(input), expected_unicode); } #[test] -fn unicode_cut_handling3() { +fn trim_unicode_annotate_ascii_end_no_label() { let source = "/*这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。*/?"; - let input = &[Level::ERROR - .primary_title("expected item, found `?`").element( - Snippet::source(source) - .fold(false) - .annotation(AnnotationKind::Primary.span(251..254).label("expected item")) - ).element( - Level::NOTE.message("for a full list of items that can appear in modules, see ") + let input = &[Group::with_level(Level::ERROR) + .element(Snippet::source(source).annotation(AnnotationKind::Primary.span(499..500)))]; + + let expected_ascii = str![[r#" + | +1 | ... 这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。*/? + | ^ +"#]]; - )]; + let renderer = Renderer::plain(); + assert_data_eq!(renderer.render(input), expected_ascii); + + let expected_unicode = str![[r#" + ╭▸ +1 │ … 。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。*/? + ╰╴ ━ +"#]]; + let renderer = renderer.decor_style(DecorStyle::Unicode); + assert_data_eq!(renderer.render(input), expected_unicode); +} + +#[test] +fn trim_unicode_annotate_unicode_end_with_label() { + let source = "/*这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。*/好"; + let input = &[Group::with_level(Level::ERROR).element( + Snippet::source(source).annotation( + AnnotationKind::Primary + .span(499..502) + .label("expected item"), + ), + )]; let expected_ascii = str![[r#" -error: expected item, found `?` | -1 | ... 。这是宽的。这是宽的。这是宽的... - | ^^ expected item +1 | ... 的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。*/好 + | ^^ expected item +"#]]; + + let renderer = Renderer::plain(); + assert_data_eq!(renderer.render(input), expected_ascii); + + let expected_unicode = str![[r#" + ╭▸ +1 │ … 宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。*/好 + ╰╴ ━━ expected item +"#]]; + let renderer = renderer.decor_style(DecorStyle::Unicode); + assert_data_eq!(renderer.render(input), expected_unicode); +} + +#[test] +fn trim_unicode_annotate_unicode_end_no_label() { + let source = "/*这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。*/好"; + let input = &[Group::with_level(Level::ERROR) + .element(Snippet::source(source).annotation(AnnotationKind::Primary.span(499..502)))]; + + let expected_ascii = str![[r#" | - = note: for a full list of items that can appear in modules, see +1 | ... 这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。*/好 + | ^^ +"#]]; + + let renderer = Renderer::plain(); + assert_data_eq!(renderer.render(input), expected_ascii); + + let expected_unicode = str![[r#" + ╭▸ +1 │ … 。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。*/好 + ╰╴ ━━ +"#]]; + let renderer = renderer.decor_style(DecorStyle::Unicode); + assert_data_eq!(renderer.render(input), expected_unicode); +} + +#[test] +fn trim_unicode_annotate_unicode_middle_with_label() { + let source = "/*这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。*/?"; + let input = &[Group::with_level(Level::ERROR).element( + Snippet::source(source).annotation( + AnnotationKind::Primary + .span(251..254) + .label("expected item"), + ), + )]; + + let expected_ascii = str![[r#" + | +1 | ... 这是宽的。这是宽的。这是宽的。... + | ^^ expected item "#]]; let renderer = Renderer::plain().term_width(43); assert_data_eq!(renderer.render(input), expected_ascii); let expected_unicode = str![[r#" -error: expected item, found `?` ╭▸ -1 │ … 的。这是宽的。这是宽的。这是宽的。… - │ ━━ expected item - │ - ╰ note: for a full list of items that can appear in modules, see +1 │ … 。这是宽的。这是宽的。这是宽的。这… + ╰╴ ━━ expected item "#]]; let renderer = renderer.decor_style(DecorStyle::Unicode); assert_data_eq!(renderer.render(input), expected_unicode); } #[test] -fn unicode_cut_handling4() { - let source = "/*aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa*/?"; - let input = &[Level::ERROR - .primary_title("expected item, found `?`").element( - Snippet::source(source) - .fold(false) - .annotation(AnnotationKind::Primary.span(334..335).label("expected item")) - ).element( - Level::NOTE.message("for a full list of items that can appear in modules, see ") +fn trim_unicode_annotate_unicode_middle_no_label() { + let source = "/*这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。*/?"; + let input = &[Group::with_level(Level::ERROR) + .element(Snippet::source(source).annotation(AnnotationKind::Primary.span(251..254)))]; + + let expected_ascii = str![[r#" + | +1 | ... 是宽的。这是宽的。这是宽的。这... + | ^^ +"#]]; + + let renderer = Renderer::plain().term_width(43); + assert_data_eq!(renderer.render(input), expected_ascii); + + let expected_unicode = str![[r#" + ╭▸ +1 │ … 这是宽的。这是宽的。这是宽的。这是… + ╰╴ ━━ +"#]]; + let renderer = renderer.decor_style(DecorStyle::Unicode); + assert_data_eq!(renderer.render(input), expected_unicode); +} - )]; +#[test] +fn trim_ascii_annotate_ascii_end_with_label() { + let source = "/*aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa*/?"; + let input = &[Group::with_level(Level::ERROR).element( + Snippet::source(source).annotation( + AnnotationKind::Primary + .span(334..335) + .label("expected item"), + ), + )]; let expected_ascii = str![[r#" -error: expected item, found `?` | 1 | ...aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa*/? | ^ expected item - | - = note: for a full list of items that can appear in modules, see "#]]; let renderer = Renderer::plain(); assert_data_eq!(renderer.render(input), expected_ascii); let expected_unicode = str![[r#" -error: expected item, found `?` ╭▸ 1 │ …aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa*/? - │ ━ expected item - │ - ╰ note: for a full list of items that can appear in modules, see + ╰╴ ━ expected item +"#]]; + let renderer = renderer.decor_style(DecorStyle::Unicode); + assert_data_eq!(renderer.render(input), expected_unicode); +} + +#[test] +fn trim_ascii_annotate_ascii_end_no_label() { + let source = "/*aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa*/?"; + let input = &[Group::with_level(Level::ERROR) + .element(Snippet::source(source).annotation(AnnotationKind::Primary.span(334..335)))]; + + let expected_ascii = str![[r#" + | +1 | ...aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa*/? + | ^ +"#]]; + + let renderer = Renderer::plain(); + assert_data_eq!(renderer.render(input), expected_ascii); + + let expected_unicode = str![[r#" + ╭▸ +1 │ …aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa*/? + ╰╴ ━ "#]]; let renderer = renderer.decor_style(DecorStyle::Unicode); assert_data_eq!(renderer.render(input), expected_unicode);