From 4e70f5649ed6fd623c00252e1b3023d5d7b14aac Mon Sep 17 00:00:00 2001 From: Scott Schafer Date: Mon, 30 Jun 2025 18:10:46 -0600 Subject: [PATCH 1/4] docs: More accuratly describe AnnotationKind variants --- src/snippet.rs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/snippet.rs b/src/snippet.rs index c2ed5c77..8c213260 100644 --- a/src/snippet.rs +++ b/src/snippet.rs @@ -262,10 +262,14 @@ impl<'a> Annotation<'a> { #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] #[non_exhaustive] pub enum AnnotationKind { - /// Color to the [`Level`] the first [`Title`] in [`Group`]. If no [`Title`] - /// is present, it will default to `error`. + /// Match the primary [`Level`] of the group. Primary, - /// "secondary"; fixed color + /// Additional context to explain the [`Primary`][Self::Primary] + /// [`Annotation`] + /// + /// See also [`Renderer::context`]. + /// + /// [`Renderer::context`]: crate::renderer::Renderer Context, } From 52fd5df0e0c3f3faf5ddc1ea3fdf3c88a500ddeb Mon Sep 17 00:00:00 2001 From: Scott Schafer Date: Tue, 24 Jun 2025 03:30:20 -0600 Subject: [PATCH 2/4] test: Add a test for a group with no Title --- examples/elide_header.rs | 21 +++++++++++++++++++ examples/elide_header.svg | 44 +++++++++++++++++++++++++++++++++++++++ tests/examples.rs | 7 +++++++ 3 files changed, 72 insertions(+) create mode 100644 examples/elide_header.rs create mode 100644 examples/elide_header.svg diff --git a/examples/elide_header.rs b/examples/elide_header.rs new file mode 100644 index 00000000..716f8e56 --- /dev/null +++ b/examples/elide_header.rs @@ -0,0 +1,21 @@ +use annotate_snippets::{AnnotationKind, Group, Level, Renderer, Snippet}; + +fn main() { + let source = r#"# Docstring followed by a newline + +def foobar(door, bar={}): + """ + """ +"#; + + let message = &[Group::new() + .element( + Snippet::source(source) + .fold(false) + .annotation(AnnotationKind::Primary.span(56..58).label("B006")), + ) + .element(Level::HELP.title("Replace with `None`; initialize within function"))]; + + let renderer = Renderer::styled(); + anstream::println!("{}", renderer.render(message)); +} diff --git a/examples/elide_header.svg b/examples/elide_header.svg new file mode 100644 index 00000000..cd4eead7 --- /dev/null +++ b/examples/elide_header.svg @@ -0,0 +1,44 @@ + + + + + + + | + + 1 | # Docstring followed by a newline + + 2 | + + 3 | def foobar(door, bar={}): + + | ^^ B006 + + 4 | """ + + 5 | """ + + | + + = help: Replace with `None`; initialize within function + + + + + + diff --git a/tests/examples.rs b/tests/examples.rs index ec2643e9..db00bc1f 100644 --- a/tests/examples.rs +++ b/tests/examples.rs @@ -14,6 +14,13 @@ fn custom_level() { assert_example(target, expected); } +#[test] +fn elide_header() { + let target = "elide_header"; + let expected = snapbox::file!["../examples/elide_header.svg": TermSvg]; + assert_example(target, expected); +} + #[test] fn expected_type() { let target = "expected_type"; From 74cc62b1e7f2dd93aba78057707714598671486c Mon Sep 17 00:00:00 2001 From: Scott Schafer Date: Mon, 30 Jun 2025 17:56:38 -0600 Subject: [PATCH 3/4] fix: Only the first element in a Group can set Level --- examples/elide_header.svg | 4 ++-- src/renderer/mod.rs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/examples/elide_header.svg b/examples/elide_header.svg index cd4eead7..080837d6 100644 --- a/examples/elide_header.svg +++ b/examples/elide_header.svg @@ -3,7 +3,7 @@ .fg { fill: #AAAAAA } .bg { background: #000000 } .fg-bright-blue { fill: #5555FF } - .fg-bright-cyan { fill: #55FFFF } + .fg-bright-red { fill: #FF5555 } .container { padding: 0 10px; line-height: 18px; @@ -27,7 +27,7 @@ 3 | def foobar(door, bar={}): - | ^^ B006 + | ^^ B006 4 | """ diff --git a/src/renderer/mod.rs b/src/renderer/mod.rs index 85bdf628..9912d52c 100644 --- a/src/renderer/mod.rs +++ b/src/renderer/mod.rs @@ -277,8 +277,8 @@ impl Renderer { } let level = group .elements - .iter() - .find_map(|s| match &s { + .first() + .and_then(|s| match &s { Element::Title(title) => Some(title.level.clone()), _ => None, }) From 5f0b4252f744ba2e02e5dadadc2c2837074a7eaf Mon Sep 17 00:00:00 2001 From: Scott Schafer Date: Tue, 24 Jun 2025 03:30:20 -0600 Subject: [PATCH 4/4] feat: Allow setting the primary level for a group --- examples/elide_header.rs | 1 + examples/elide_header.svg | 4 ++-- src/renderer/mod.rs | 18 ++++++++++-------- src/snippet.rs | 17 ++++++++++++++++- 4 files changed, 29 insertions(+), 11 deletions(-) diff --git a/examples/elide_header.rs b/examples/elide_header.rs index 716f8e56..a280616c 100644 --- a/examples/elide_header.rs +++ b/examples/elide_header.rs @@ -9,6 +9,7 @@ def foobar(door, bar={}): "#; let message = &[Group::new() + .primary_level(Level::NOTE) .element( Snippet::source(source) .fold(false) diff --git a/examples/elide_header.svg b/examples/elide_header.svg index 080837d6..ccec3e10 100644 --- a/examples/elide_header.svg +++ b/examples/elide_header.svg @@ -3,7 +3,7 @@ .fg { fill: #AAAAAA } .bg { background: #000000 } .fg-bright-blue { fill: #5555FF } - .fg-bright-red { fill: #FF5555 } + .fg-bright-green { fill: #55FF55 } .container { padding: 0 10px; line-height: 18px; @@ -27,7 +27,7 @@ 3 | def foobar(door, bar={}): - | ^^ B006 + | ^^ B006 4 | """ diff --git a/src/renderer/mod.rs b/src/renderer/mod.rs index 9912d52c..9d89026c 100644 --- a/src/renderer/mod.rs +++ b/src/renderer/mod.rs @@ -275,14 +275,16 @@ impl Renderer { if og_primary_path.is_none() && primary_path.is_some() { og_primary_path = primary_path; } - let level = group - .elements - .first() - .and_then(|s| match &s { - Element::Title(title) => Some(title.level.clone()), - _ => None, - }) - .unwrap_or(Level::ERROR); + let level = group.primary_level.clone().unwrap_or_else(|| { + group + .elements + .first() + .and_then(|s| match &s { + Element::Title(title) => Some(title.level.clone()), + _ => None, + }) + .unwrap_or(Level::ERROR) + }); let mut source_map_annotated_lines = VecDeque::new(); let mut max_depth = 0; for e in &group.elements { diff --git a/src/snippet.rs b/src/snippet.rs index 8c213260..8206f600 100644 --- a/src/snippet.rs +++ b/src/snippet.rs @@ -20,6 +20,7 @@ pub(crate) struct Id<'a> { /// An [`Element`] container #[derive(Clone, Debug)] pub struct Group<'a> { + pub(crate) primary_level: Option>, pub(crate) elements: Vec>, } @@ -31,7 +32,10 @@ impl Default for Group<'_> { impl<'a> Group<'a> { pub fn new() -> Self { - Self { elements: vec![] } + Self { + primary_level: None, + elements: vec![], + } } pub fn element(mut self, section: impl Into>) -> Self { @@ -44,6 +48,15 @@ impl<'a> Group<'a> { self } + /// Set the primary [`Level`] for this [`Group`]. + /// + /// If not specified, use the [`Level`] of the first element in a [`Group`] + /// if it is a [`Title`]. If not it will default to [`Level::ERROR`]. + pub fn primary_level(mut self, level: Level<'a>) -> Self { + self.primary_level = Some(level); + self + } + pub fn is_empty(&self) -> bool { self.elements.is_empty() } @@ -263,6 +276,8 @@ impl<'a> Annotation<'a> { #[non_exhaustive] pub enum AnnotationKind { /// Match the primary [`Level`] of the group. + /// + /// See [`Group::primary_level`] for details about how this is determined Primary, /// Additional context to explain the [`Primary`][Self::Primary] /// [`Annotation`]