diff --git a/examples/struct_name_as_context.rs b/examples/struct_name_as_context.rs new file mode 100644 index 0000000..94a3d52 --- /dev/null +++ b/examples/struct_name_as_context.rs @@ -0,0 +1,32 @@ +use annotate_snippets::{AnnotationKind, Group, Level, Renderer, Snippet}; +fn main() { + let source = r#"struct S { + field1: usize, + field2: usize, + field3: usize, + field4: usize, + fn foo() {}, + field6: usize, +} +"#; + let message = + &[ + Group::with_title( + Level::ERROR.title("functions are not allowed in struct definitions"), + ) + .element( + Snippet::source(source) + .path("$DIR/struct_name_as_context.rs") + .annotation(AnnotationKind::Primary.span(91..102)) + .annotation(AnnotationKind::Visible.span(0..8)), + ) + .element( + Level::HELP.message( + "unlike in C++, Java, and C#, functions are declared in `impl` blocks", + ), + ), + ]; + + let renderer = Renderer::styled(); + anstream::println!("{}", renderer.render(message)); +} diff --git a/examples/struct_name_as_context.svg b/examples/struct_name_as_context.svg new file mode 100644 index 0000000..177fbba --- /dev/null +++ b/examples/struct_name_as_context.svg @@ -0,0 +1,44 @@ + + + + + + + error: functions are not allowed in struct definitions + + --> $DIR/struct_name_as_context.rs:6:5 + + | + + 1 | struct S { + + ... + + 6 | fn foo() {}, + + | ^^^^^^^^^^^ + + | + + = help: unlike in C++, Java, and C#, functions are declared in `impl` blocks + + + + + + diff --git a/src/renderer/source_map.rs b/src/renderer/source_map.rs index fdc4cd6..db19216 100644 --- a/src/renderer/source_map.rs +++ b/src/renderer/source_map.rs @@ -157,6 +157,7 @@ impl<'a> SourceMap<'a> { line: info.line, line_index: info.line_index, annotations: vec![], + keep: false, }) .collect::>(); let mut multiline_annotations = vec![]; @@ -169,7 +170,12 @@ impl<'a> SourceMap<'a> { } in annotations { let (lo, mut hi) = self.span_to_locations(span.clone()); - + if kind == AnnotationKind::Visible { + for line_idx in lo.line..=hi.line { + self.keep_line(&mut annotated_line_infos, line_idx); + } + continue; + } // Watch out for "empty spans". If we get a span like 6..6, we // want to just display a `^` at 6, so convert that to // 6..7. This is degenerate input, but it's best to degrade @@ -306,7 +312,7 @@ impl<'a> SourceMap<'a> { } if fold { - annotated_line_infos.retain(|l| !l.annotations.is_empty()); + annotated_line_infos.retain(|l| !l.annotations.is_empty() || l.keep); } (max_depth, annotated_line_infos) @@ -333,6 +339,29 @@ impl<'a> SourceMap<'a> { line: info.line, line_index, annotations: vec![line_ann], + keep: false, + }); + annotated_line_infos.sort_by_key(|l| l.line_index); + } + } + + fn keep_line(&self, annotated_line_infos: &mut Vec>, line_index: usize) { + if let Some(line_info) = annotated_line_infos + .iter_mut() + .find(|line_info| line_info.line_index == line_index) + { + line_info.keep = true; + } else { + let info = self + .lines + .iter() + .find(|l| l.line_index == line_index) + .unwrap(); + annotated_line_infos.push(AnnotatedLineInfo { + line: info.line, + line_index, + annotations: vec![], + keep: true, }); annotated_line_infos.sort_by_key(|l| l.line_index); } @@ -595,6 +624,7 @@ pub(crate) struct AnnotatedLineInfo<'a> { pub(crate) line: &'a str, pub(crate) line_index: usize, pub(crate) annotations: Vec>, + pub(crate) keep: bool, } /// A source code location used for error reporting. diff --git a/src/snippet.rs b/src/snippet.rs index b140a14..3ec5d02 100644 --- a/src/snippet.rs +++ b/src/snippet.rs @@ -313,6 +313,24 @@ pub enum AnnotationKind { /// /// [`Renderer::context`]: crate::renderer::Renderer Context, + /// Prevents the annotated text from getting [folded][Snippet::fold] + /// + /// By default, [`Snippet`]s will [fold][`Snippet::fold`] (remove) lines + /// that do not contain any annotations. [`Visible`][Self::Visible] makes + /// it possible to selectively prevent this behavior for specific text, + /// allowing context to be preserved without adding any annotation + /// characters. + /// + /// # Example + /// + /// ```rust + /// # #[allow(clippy::needless_doctest_main)] + #[doc = include_str!("../examples/struct_name_as_context.rs")] + /// ``` + /// + #[doc = include_str!("../examples/struct_name_as_context.svg")] + /// + Visible, } impl AnnotationKind { diff --git a/tests/examples.rs b/tests/examples.rs index 226c31f..6345559 100644 --- a/tests/examples.rs +++ b/tests/examples.rs @@ -70,6 +70,13 @@ fn multislice() { assert_example(target, expected); } +#[test] +fn struct_name_as_context() { + let target = "struct_name_as_context"; + let expected = snapbox::file!["../examples/struct_name_as_context.svg": TermSvg]; + assert_example(target, expected); +} + #[track_caller] fn assert_example(target: &str, expected: snapbox::Data) { let bin_path = snapbox::cmd::compile_example(target, ["--features=testing-colors"]).unwrap(); diff --git a/tests/formatter.rs b/tests/formatter.rs index a34517a..efb9793 100644 --- a/tests/formatter.rs +++ b/tests/formatter.rs @@ -3005,3 +3005,73 @@ LL | .sum::<_>() //~ ERROR type annotations needed let renderer = Renderer::plain().anonymized_line_numbers(true); assert_data_eq!(renderer.render(input), expected); } + +#[test] +fn keep_lines1() { + let source = r#" +cargo +fuzzy +pizza +jumps +crazy +quack +zappy +"#; + + let input_new = &[Group::with_title( + Level::ERROR + .title("the size for values of type `T` cannot be known at compilation time") + .id("E0277"), + ) + .element( + Snippet::source(source) + .line_start(11) + .annotation(AnnotationKind::Primary.span(1..6)) + .annotation(AnnotationKind::Visible.span(37..41)), + )]; + let expected = str![[r#" +error[E0277]: the size for values of type `T` cannot be known at compilation time + | +12 | cargo + | ^^^^^ +... +18 | zappy +"#]]; + let renderer = Renderer::plain(); + assert_data_eq!(renderer.render(input_new), expected); +} + +#[test] +fn keep_lines2() { + let source = r#" +cargo +fuzzy +pizza +jumps +crazy +quack +zappy +"#; + + let input_new = &[Group::with_title( + Level::ERROR + .title("the size for values of type `T` cannot be known at compilation time") + .id("E0277"), + ) + .element( + Snippet::source(source) + .line_start(11) + .annotation(AnnotationKind::Primary.span(1..6)) + .annotation(AnnotationKind::Visible.span(16..18)), + )]; + let expected = str![[r#" +error[E0277]: the size for values of type `T` cannot be known at compilation time + | +12 | cargo + | ^^^^^ +13 | fuzzy +14 | pizza +"#]]; + let renderer = Renderer::plain(); + assert_data_eq!(renderer.render(input_new), expected); +}