Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 106f70c

Browse files
authoredJul 10, 2024··
Merge pull request #133 from Muscraft/merge-lines
Match Rust's multiline annotation output
2 parents 10e6e40 + b25bd3e commit 106f70c

File tree

11 files changed

+1725
-339
lines changed

11 files changed

+1725
-339
lines changed
 

‎examples/expected_type.svg

Lines changed: 4 additions & 4 deletions
Loading

‎examples/footer.svg

Lines changed: 4 additions & 4 deletions
Loading

‎examples/format.svg

Lines changed: 27 additions & 29 deletions
Loading

‎examples/multislice.svg

Lines changed: 4 additions & 4 deletions
Loading

‎src/renderer/display_list.rs

Lines changed: 622 additions & 290 deletions
Large diffs are not rendered by default.

‎src/renderer/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
1313
mod display_list;
1414
mod margin;
15+
mod styled_buffer;
1516
pub(crate) mod stylesheet;
1617

1718
use crate::snippet::Message;

‎src/renderer/styled_buffer.rs

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
//! Adapted from [styled_buffer]
2+
//!
3+
//! [styled_buffer]: https://github.com/rust-lang/rust/blob/894f7a4ba6554d3797404bbf550d9919df060b97/compiler/rustc_errors/src/styled_buffer.rs
4+
5+
use crate::renderer::stylesheet::Stylesheet;
6+
use anstyle::Style;
7+
use std::fmt;
8+
use std::fmt::Write;
9+
10+
#[derive(Debug)]
11+
pub(crate) struct StyledBuffer {
12+
lines: Vec<Vec<StyledChar>>,
13+
}
14+
15+
#[derive(Clone, Copy, Debug, PartialEq)]
16+
pub(crate) struct StyledChar {
17+
ch: char,
18+
style: Style,
19+
}
20+
21+
impl StyledChar {
22+
pub(crate) const SPACE: Self = StyledChar::new(' ', Style::new());
23+
24+
pub(crate) const fn new(ch: char, style: Style) -> StyledChar {
25+
StyledChar { ch, style }
26+
}
27+
}
28+
29+
impl StyledBuffer {
30+
pub(crate) fn new() -> StyledBuffer {
31+
StyledBuffer { lines: vec![] }
32+
}
33+
34+
fn ensure_lines(&mut self, line: usize) {
35+
if line >= self.lines.len() {
36+
self.lines.resize(line + 1, Vec::new());
37+
}
38+
}
39+
40+
pub(crate) fn render(&self, stylesheet: &Stylesheet) -> Result<String, fmt::Error> {
41+
let mut str = String::new();
42+
for (i, line) in self.lines.iter().enumerate() {
43+
let mut current_style = stylesheet.none;
44+
for ch in line {
45+
if ch.style != current_style {
46+
if !line.is_empty() {
47+
write!(str, "{}", current_style.render_reset())?;
48+
}
49+
current_style = ch.style;
50+
write!(str, "{}", current_style.render())?;
51+
}
52+
write!(str, "{}", ch.ch)?;
53+
}
54+
write!(str, "{}", current_style.render_reset())?;
55+
if i != self.lines.len() - 1 {
56+
writeln!(str)?;
57+
}
58+
}
59+
Ok(str)
60+
}
61+
62+
/// Sets `chr` with `style` for given `line`, `col`.
63+
/// If `line` does not exist in our buffer, adds empty lines up to the given
64+
/// and fills the last line with unstyled whitespace.
65+
pub(crate) fn putc(&mut self, line: usize, col: usize, chr: char, style: Style) {
66+
self.ensure_lines(line);
67+
if col >= self.lines[line].len() {
68+
self.lines[line].resize(col + 1, StyledChar::SPACE);
69+
}
70+
self.lines[line][col] = StyledChar::new(chr, style);
71+
}
72+
73+
/// Sets `string` with `style` for given `line`, starting from `col`.
74+
/// If `line` does not exist in our buffer, adds empty lines up to the given
75+
/// and fills the last line with unstyled whitespace.
76+
pub(crate) fn puts(&mut self, line: usize, col: usize, string: &str, style: Style) {
77+
let mut n = col;
78+
for c in string.chars() {
79+
self.putc(line, n, c, style);
80+
n += 1;
81+
}
82+
}
83+
/// For given `line` inserts `string` with `style` after old content of that line,
84+
/// adding lines if needed
85+
pub(crate) fn append(&mut self, line: usize, string: &str, style: Style) {
86+
if line >= self.lines.len() {
87+
self.puts(line, 0, string, style);
88+
} else {
89+
let col = self.lines[line].len();
90+
self.puts(line, col, string, style);
91+
}
92+
}
93+
94+
pub(crate) fn num_lines(&self) -> usize {
95+
self.lines.len()
96+
}
97+
}

‎tests/fixtures/no-color/simple.svg

Lines changed: 1 addition & 1 deletion
Loading

‎tests/fixtures/no-color/strip_line_non_ws.svg

Lines changed: 6 additions & 4 deletions
Loading

‎tests/formatter.rs

Lines changed: 176 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -262,8 +262,10 @@ fn test_source_annotation_standalone_multiline() {
262262
error
263263
|
264264
1 | tests
265-
| ----- help: Example string
266-
| ----- help: Second line
265+
| -----
266+
| |
267+
| help: Example string
268+
| help: Second line
267269
|
268270
"#]];
269271
let renderer = Renderer::plain();
@@ -296,7 +298,7 @@ error
296298
|
297299
LL | This is an example
298300
LL | of content lines
299-
LL |
301+
LL |
300302
LL | abc
301303
|
302304
"#]];
@@ -732,3 +734,174 @@ error
732734
let renderer = Renderer::plain().anonymized_line_numbers(false);
733735
assert_data_eq!(renderer.render(input).to_string(), expected);
734736
}
737+
738+
#[test]
739+
fn two_single_line_same_line() {
740+
let source = r#"bar = { version = "0.1.0", optional = true }"#;
741+
let input = Level::Error.title("unused optional dependency").snippet(
742+
Snippet::source(source)
743+
.origin("Cargo.toml")
744+
.line_start(4)
745+
.annotation(
746+
Level::Error
747+
.span(0..3)
748+
.label("I need this to be really long so I can test overlaps"),
749+
)
750+
.annotation(
751+
Level::Info
752+
.span(27..42)
753+
.label("This should also be long but not too long"),
754+
),
755+
);
756+
let expected = str![[r#"
757+
error: unused optional dependency
758+
--> Cargo.toml:4:1
759+
|
760+
4 | bar = { version = "0.1.0", optional = true }
761+
| ^^^ --------------- info: This should also be long but not too long
762+
| |
763+
| I need this to be really long so I can test overlaps
764+
|
765+
"#]];
766+
let renderer = Renderer::plain().anonymized_line_numbers(false);
767+
assert_data_eq!(renderer.render(input).to_string(), expected);
768+
}
769+
770+
#[test]
771+
fn multi_and_single() {
772+
let source = r#"bar = { version = "0.1.0", optional = true }
773+
this is another line
774+
so is this
775+
bar = { version = "0.1.0", optional = true }
776+
"#;
777+
let input = Level::Error.title("unused optional dependency").snippet(
778+
Snippet::source(source)
779+
.line_start(4)
780+
.annotation(
781+
Level::Error
782+
.span(41..119)
783+
.label("I need this to be really long so I can test overlaps"),
784+
)
785+
.annotation(
786+
Level::Info
787+
.span(27..42)
788+
.label("This should also be long but not too long"),
789+
),
790+
);
791+
let expected = str![[r#"
792+
error: unused optional dependency
793+
|
794+
4 | bar = { version = "0.1.0", optional = true }
795+
| ____________________________--------------^
796+
| | |
797+
| | info: This should also be long but not too long
798+
5 | | this is another line
799+
6 | | so is this
800+
7 | | bar = { version = "0.1.0", optional = true }
801+
| |__________________________________________^ I need this to be really long so I can test overlaps
802+
|
803+
"#]];
804+
let renderer = Renderer::plain();
805+
assert_data_eq!(renderer.render(input).to_string(), expected);
806+
}
807+
808+
#[test]
809+
fn two_multi_and_single() {
810+
let source = r#"bar = { version = "0.1.0", optional = true }
811+
this is another line
812+
so is this
813+
bar = { version = "0.1.0", optional = true }
814+
"#;
815+
let input = Level::Error.title("unused optional dependency").snippet(
816+
Snippet::source(source)
817+
.line_start(4)
818+
.annotation(
819+
Level::Error
820+
.span(41..119)
821+
.label("I need this to be really long so I can test overlaps"),
822+
)
823+
.annotation(
824+
Level::Error
825+
.span(8..102)
826+
.label("I need this to be really long so I can test overlaps"),
827+
)
828+
.annotation(
829+
Level::Info
830+
.span(27..42)
831+
.label("This should also be long but not too long"),
832+
),
833+
);
834+
let expected = str![[r#"
835+
error: unused optional dependency
836+
|
837+
4 | bar = { version = "0.1.0", optional = true }
838+
| _________^__________________--------------^
839+
| | | |
840+
| |_________| info: This should also be long but not too long
841+
| ||
842+
5 | || this is another line
843+
6 | || so is this
844+
7 | || bar = { version = "0.1.0", optional = true }
845+
| ||_________________________^________________^ I need this to be really long so I can test overlaps
846+
| |__________________________|
847+
| I need this to be really long so I can test overlaps
848+
|
849+
"#]];
850+
let renderer = Renderer::plain();
851+
assert_data_eq!(renderer.render(input).to_string(), expected);
852+
}
853+
854+
#[test]
855+
fn three_multi_and_single() {
856+
let source = r#"bar = { version = "0.1.0", optional = true }
857+
this is another line
858+
so is this
859+
bar = { version = "0.1.0", optional = true }
860+
this is another line
861+
"#;
862+
let input = Level::Error.title("unused optional dependency").snippet(
863+
Snippet::source(source)
864+
.line_start(4)
865+
.annotation(
866+
Level::Error
867+
.span(41..119)
868+
.label("I need this to be really long so I can test overlaps"),
869+
)
870+
.annotation(
871+
Level::Error
872+
.span(8..102)
873+
.label("I need this to be really long so I can test overlaps"),
874+
)
875+
.annotation(
876+
Level::Error
877+
.span(48..126)
878+
.label("I need this to be really long so I can test overlaps"),
879+
)
880+
.annotation(
881+
Level::Info
882+
.span(27..42)
883+
.label("This should also be long but not too long"),
884+
),
885+
);
886+
let expected = str![[r#"
887+
error: unused optional dependency
888+
|
889+
4 | bar = { version = "0.1.0", optional = true }
890+
| __________^__________________--------------^
891+
| | | |
892+
| |__________| info: This should also be long but not too long
893+
| ||
894+
5 | || this is another line
895+
| || ____^
896+
6 | ||| so is this
897+
7 | ||| bar = { version = "0.1.0", optional = true }
898+
| |||_________________________^________________^ I need this to be really long so I can test overlaps
899+
| |_|_________________________|
900+
| | I need this to be really long so I can test overlaps
901+
8 | | this is another line
902+
| |____^ I need this to be really long so I can test overlaps
903+
|
904+
"#]];
905+
let renderer = Renderer::plain();
906+
assert_data_eq!(renderer.render(input).to_string(), expected);
907+
}

‎tests/rustc_tests.rs

Lines changed: 783 additions & 0 deletions
Large diffs are not rendered by default.

0 commit comments

Comments
 (0)
Please sign in to comment.