Skip to content

Commit d6c6bbd

Browse files
authored
Merge pull request #258 from Muscraft/keep-lines
feat: Add a way to keep lines with no annotations
2 parents 5cd6f5d + 0cd0b1f commit d6c6bbd

File tree

6 files changed

+203
-2
lines changed

6 files changed

+203
-2
lines changed

examples/struct_name_as_context.rs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
use annotate_snippets::{AnnotationKind, Group, Level, Renderer, Snippet};
2+
fn main() {
3+
let source = r#"struct S {
4+
field1: usize,
5+
field2: usize,
6+
field3: usize,
7+
field4: usize,
8+
fn foo() {},
9+
field6: usize,
10+
}
11+
"#;
12+
let message =
13+
&[
14+
Group::with_title(
15+
Level::ERROR.title("functions are not allowed in struct definitions"),
16+
)
17+
.element(
18+
Snippet::source(source)
19+
.path("$DIR/struct_name_as_context.rs")
20+
.annotation(AnnotationKind::Primary.span(91..102))
21+
.annotation(AnnotationKind::Visible.span(0..8)),
22+
)
23+
.element(
24+
Level::HELP.message(
25+
"unlike in C++, Java, and C#, functions are declared in `impl` blocks",
26+
),
27+
),
28+
];
29+
30+
let renderer = Renderer::styled();
31+
anstream::println!("{}", renderer.render(message));
32+
}

examples/struct_name_as_context.svg

Lines changed: 44 additions & 0 deletions
Loading

src/renderer/source_map.rs

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,7 @@ impl<'a> SourceMap<'a> {
157157
line: info.line,
158158
line_index: info.line_index,
159159
annotations: vec![],
160+
keep: false,
160161
})
161162
.collect::<Vec<_>>();
162163
let mut multiline_annotations = vec![];
@@ -169,7 +170,12 @@ impl<'a> SourceMap<'a> {
169170
} in annotations
170171
{
171172
let (lo, mut hi) = self.span_to_locations(span.clone());
172-
173+
if kind == AnnotationKind::Visible {
174+
for line_idx in lo.line..=hi.line {
175+
self.keep_line(&mut annotated_line_infos, line_idx);
176+
}
177+
continue;
178+
}
173179
// Watch out for "empty spans". If we get a span like 6..6, we
174180
// want to just display a `^` at 6, so convert that to
175181
// 6..7. This is degenerate input, but it's best to degrade
@@ -306,7 +312,7 @@ impl<'a> SourceMap<'a> {
306312
}
307313

308314
if fold {
309-
annotated_line_infos.retain(|l| !l.annotations.is_empty());
315+
annotated_line_infos.retain(|l| !l.annotations.is_empty() || l.keep);
310316
}
311317

312318
(max_depth, annotated_line_infos)
@@ -333,6 +339,29 @@ impl<'a> SourceMap<'a> {
333339
line: info.line,
334340
line_index,
335341
annotations: vec![line_ann],
342+
keep: false,
343+
});
344+
annotated_line_infos.sort_by_key(|l| l.line_index);
345+
}
346+
}
347+
348+
fn keep_line(&self, annotated_line_infos: &mut Vec<AnnotatedLineInfo<'a>>, line_index: usize) {
349+
if let Some(line_info) = annotated_line_infos
350+
.iter_mut()
351+
.find(|line_info| line_info.line_index == line_index)
352+
{
353+
line_info.keep = true;
354+
} else {
355+
let info = self
356+
.lines
357+
.iter()
358+
.find(|l| l.line_index == line_index)
359+
.unwrap();
360+
annotated_line_infos.push(AnnotatedLineInfo {
361+
line: info.line,
362+
line_index,
363+
annotations: vec![],
364+
keep: true,
336365
});
337366
annotated_line_infos.sort_by_key(|l| l.line_index);
338367
}
@@ -595,6 +624,7 @@ pub(crate) struct AnnotatedLineInfo<'a> {
595624
pub(crate) line: &'a str,
596625
pub(crate) line_index: usize,
597626
pub(crate) annotations: Vec<LineAnnotation<'a>>,
627+
pub(crate) keep: bool,
598628
}
599629

600630
/// A source code location used for error reporting.

src/snippet.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -313,6 +313,24 @@ pub enum AnnotationKind {
313313
///
314314
/// [`Renderer::context`]: crate::renderer::Renderer
315315
Context,
316+
/// Prevents the annotated text from getting [folded][Snippet::fold]
317+
///
318+
/// By default, [`Snippet`]s will [fold][`Snippet::fold`] (remove) lines
319+
/// that do not contain any annotations. [`Visible`][Self::Visible] makes
320+
/// it possible to selectively prevent this behavior for specific text,
321+
/// allowing context to be preserved without adding any annotation
322+
/// characters.
323+
///
324+
/// # Example
325+
///
326+
/// ```rust
327+
/// # #[allow(clippy::needless_doctest_main)]
328+
#[doc = include_str!("../examples/struct_name_as_context.rs")]
329+
/// ```
330+
///
331+
#[doc = include_str!("../examples/struct_name_as_context.svg")]
332+
///
333+
Visible,
316334
}
317335

318336
impl AnnotationKind {

tests/examples.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,13 @@ fn multislice() {
7070
assert_example(target, expected);
7171
}
7272

73+
#[test]
74+
fn struct_name_as_context() {
75+
let target = "struct_name_as_context";
76+
let expected = snapbox::file!["../examples/struct_name_as_context.svg": TermSvg];
77+
assert_example(target, expected);
78+
}
79+
7380
#[track_caller]
7481
fn assert_example(target: &str, expected: snapbox::Data) {
7582
let bin_path = snapbox::cmd::compile_example(target, ["--features=testing-colors"]).unwrap();

tests/formatter.rs

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3005,3 +3005,73 @@ LL | .sum::<_>() //~ ERROR type annotations needed
30053005
let renderer = Renderer::plain().anonymized_line_numbers(true);
30063006
assert_data_eq!(renderer.render(input), expected);
30073007
}
3008+
3009+
#[test]
3010+
fn keep_lines1() {
3011+
let source = r#"
3012+
cargo
3013+
fuzzy
3014+
pizza
3015+
jumps
3016+
crazy
3017+
quack
3018+
zappy
3019+
"#;
3020+
3021+
let input_new = &[Group::with_title(
3022+
Level::ERROR
3023+
.title("the size for values of type `T` cannot be known at compilation time")
3024+
.id("E0277"),
3025+
)
3026+
.element(
3027+
Snippet::source(source)
3028+
.line_start(11)
3029+
.annotation(AnnotationKind::Primary.span(1..6))
3030+
.annotation(AnnotationKind::Visible.span(37..41)),
3031+
)];
3032+
let expected = str![[r#"
3033+
error[E0277]: the size for values of type `T` cannot be known at compilation time
3034+
|
3035+
12 | cargo
3036+
| ^^^^^
3037+
...
3038+
18 | zappy
3039+
"#]];
3040+
let renderer = Renderer::plain();
3041+
assert_data_eq!(renderer.render(input_new), expected);
3042+
}
3043+
3044+
#[test]
3045+
fn keep_lines2() {
3046+
let source = r#"
3047+
cargo
3048+
fuzzy
3049+
pizza
3050+
jumps
3051+
crazy
3052+
quack
3053+
zappy
3054+
"#;
3055+
3056+
let input_new = &[Group::with_title(
3057+
Level::ERROR
3058+
.title("the size for values of type `T` cannot be known at compilation time")
3059+
.id("E0277"),
3060+
)
3061+
.element(
3062+
Snippet::source(source)
3063+
.line_start(11)
3064+
.annotation(AnnotationKind::Primary.span(1..6))
3065+
.annotation(AnnotationKind::Visible.span(16..18)),
3066+
)];
3067+
let expected = str![[r#"
3068+
error[E0277]: the size for values of type `T` cannot be known at compilation time
3069+
|
3070+
12 | cargo
3071+
| ^^^^^
3072+
13 | fuzzy
3073+
14 | pizza
3074+
"#]];
3075+
let renderer = Renderer::plain();
3076+
assert_data_eq!(renderer.render(input_new), expected);
3077+
}

0 commit comments

Comments
 (0)