From 3cf7ea156d0bd19ce6df2afa659ef51fb0d0d556 Mon Sep 17 00:00:00 2001 From: Scott Schafer Date: Wed, 27 Aug 2025 04:08:34 -0600 Subject: [PATCH 1/4] docs: Clarify meaning and use of API --- examples/expected_type.rs | 4 +- examples/multi_suggestion.rs | 74 ++++++++++++++++++++++ examples/multi_suggestion.svg | 82 +++++++++++++++++++++++++ src/level.rs | 53 ++++++++-------- src/lib.rs | 84 +++++++++++++++++++++++-- src/renderer/mod.rs | 112 ++++++++++++++++++++-------------- src/snippet.rs | 100 ++++++++++++++++++++++-------- tests/examples.rs | 7 +++ 8 files changed, 409 insertions(+), 107 deletions(-) create mode 100644 examples/multi_suggestion.rs create mode 100644 examples/multi_suggestion.svg diff --git a/examples/expected_type.rs b/examples/expected_type.rs index c8f1c8cd..d8ba1869 100644 --- a/examples/expected_type.rs +++ b/examples/expected_type.rs @@ -5,7 +5,7 @@ fn main() { label: "expected struct `annotate_snippets::snippet::Slice`, found reference" , range: <22, 25>,"#; - let message = + let report = &[ Group::with_title(Level::ERROR.primary_title("expected type, found `22`")).element( Snippet::source(source) @@ -23,5 +23,5 @@ fn main() { ]; let renderer = Renderer::styled(); - anstream::println!("{}", renderer.render(message)); + anstream::println!("{}", renderer.render(report)); } diff --git a/examples/multi_suggestion.rs b/examples/multi_suggestion.rs new file mode 100644 index 00000000..b2b8f50b --- /dev/null +++ b/examples/multi_suggestion.rs @@ -0,0 +1,74 @@ +use annotate_snippets::{ + renderer::DecorStyle, AnnotationKind, Group, Level, Patch, Renderer, Snippet, +}; + +fn main() { + let source = r#" +#![allow(dead_code)] +struct U { + wtf: Option>>, + x: T, +} +fn main() { + U { + wtf: Some(Box(U { + wtf: None, + x: (), + })), + x: () + }; + let _ = std::collections::HashMap(); + let _ = std::collections::HashMap {}; + let _ = Box {}; +} +"#; + + let message = &[ + Group::with_title(Level::ERROR.primary_title( + "cannot construct `Box<_, _>` with struct literal syntax due to private fields", + )) + .element( + Snippet::source(source) + .path("$DIR/multi-suggestion.rs") + .annotation(AnnotationKind::Primary.span(295..298)), + ) + .element(Level::NOTE.message("private fields `0` and `1` that were not provided")), + Group::with_title(Level::HELP.secondary_title( + "you might have meant to use an associated function to build this type", + )) + .element( + Snippet::source(source) + .path("$DIR/multi-suggestion.rs") + .patch(Patch::new(298..301, "::new(_)")), + ) + .element( + Snippet::source(source) + .path("$DIR/multi-suggestion.rs") + .patch(Patch::new(298..301, "::new_uninit()")), + ) + .element( + Snippet::source(source) + .path("$DIR/multi-suggestion.rs") + .patch(Patch::new(298..301, "::new_zeroed()")), + ) + .element( + Snippet::source(source) + .path("$DIR/multi-suggestion.rs") + .patch(Patch::new(298..301, "::new_in(_, _)")), + ) + .element(Level::NOTE.no_name().message("and 12 other candidates")), + Group::with_title(Level::HELP.secondary_title("consider using the `Default` trait")) + .element( + Snippet::source(source) + .path("$DIR/multi-suggestion.rs") + .patch(Patch::new(295..295, "<")) + .patch(Patch::new( + 298..301, + " as std::default::Default>::default()", + )), + ), + ]; + + let renderer = Renderer::styled().decor_style(DecorStyle::Unicode); + anstream::println!("{}", renderer.render(message)); +} diff --git a/examples/multi_suggestion.svg b/examples/multi_suggestion.svg new file mode 100644 index 00000000..ecf07670 --- /dev/null +++ b/examples/multi_suggestion.svg @@ -0,0 +1,82 @@ + + + + + + + error: cannot construct `Box<_, _>` with struct literal syntax due to private fields + + ╭▸ $DIR/multi-suggestion.rs:17:13 + + + + 17 let _ = Box {}; + + ━━━ + + + + note: private fields `0` and `1` that were not provided + + help: you might have meant to use an associated function to build this type + + ╭╴ + + 17 - let _ = Box {}; + + 17 + let _ = Box::new(_); + + ├╴ + + 17 - let _ = Box {}; + + 17 + let _ = Box::new_uninit(); + + ├╴ + + 17 - let _ = Box {}; + + 17 + let _ = Box::new_zeroed(); + + ├╴ + + 17 - let _ = Box {}; + + 17 + let _ = Box::new_in(_, _); + + + + and 12 other candidates + + help: consider using the `Default` trait + + ╭╴ + + 17 - let _ = Box {}; + + 17 + let _ = <Box as std::default::Default>::default(); + + ╰╴ + + + + + + diff --git a/src/level.rs b/src/level.rs index bc15f24c..02cdeb29 100644 --- a/src/level.rs +++ b/src/level.rs @@ -37,6 +37,23 @@ pub const HELP: Level<'_> = Level { }; /// Severity level for [`Title`]s and [`Message`]s +/// +/// # Example +/// +/// ```rust +/// # use annotate_snippets::*; +/// let report = &[ +/// Group::with_title( +/// Level::ERROR.primary_title("mismatched types").id("E0308") +/// ) +/// .element( +/// Level::NOTE.message("expected reference"), +/// ), +/// Group::with_title( +/// Level::HELP.secondary_title("function defined here") +/// ), +/// ]; +/// ``` #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] pub struct Level<'a> { pub(crate) name: Option>>, @@ -53,9 +70,7 @@ impl<'a> Level<'a> { } impl<'a> Level<'a> { - /// Creates a [`Title`] that has bolded text, and is - /// intended to be used with the "primary" (first) - /// [`Group`][crate::Group] in a [`Report`][crate::Report]. + /// For the primary, or root cause, [`Group`][crate::Group] (the first) in a [`Report`][crate::Report] /// /// See [`Group::with_title`][crate::Group::with_title] /// @@ -66,15 +81,6 @@ impl<'a> Level<'a> { /// not allowed to be passed to this function. /// /// - /// - /// # Example - /// - /// ```rust - /// # use annotate_snippets::{Group, Snippet, AnnotationKind, Level}; - /// let input = &[ - /// Group::with_title(Level::ERROR.primary_title("mismatched types").id("E0308")) - /// ]; - /// ``` pub fn primary_title(self, text: impl Into>) -> Title<'a> { Title { level: self, @@ -84,8 +90,7 @@ impl<'a> Level<'a> { } } - /// Creates a [`Title`] that is allowed to be styled, for more info see the - /// warning below. + /// For any secondary, or context, [`Group`][crate::Group]s (subsequent) in a [`Report`][crate::Report] /// /// See [`Group::with_title`][crate::Group::with_title] /// @@ -116,20 +121,6 @@ impl<'a> Level<'a> { /// used to normalize untrusted text before it is passed to this function. /// /// - /// - /// # Example - /// - /// ```rust - /// # use annotate_snippets::{Group, Snippet, AnnotationKind, Level}; - /// let input = &[ - /// Group::with_title(Level::ERROR.primary_title("mismatched types").id("E0308")) - /// .element( - /// Level::NOTE - /// .no_name() - /// .message("expected reference `&str`\nfound reference `&'static [u8; 0]`"), - /// ), - /// ]; - /// ``` pub fn message(self, text: impl Into>) -> Message<'a> { Message { level: self, @@ -182,6 +173,10 @@ impl<'a> Level<'a> { /// Do not show the [`Level`]s name /// + /// Useful for: + /// - Another layer of the application will include the level (e.g. when rendering errors) + /// - [`Message`]s that are part of a previous [`Group`][crate::Group] [`Element`][crate::Element]s + /// /// # Example /// /// ```rust @@ -190,7 +185,7 @@ impl<'a> Level<'a> { /// let b: &[u8] = include_str!("file.txt"); //~ ERROR mismatched types /// let s: &str = include_bytes!("file.txt"); //~ ERROR mismatched types /// }"#; - /// let input = &[ + /// let report = &[ /// Group::with_title(Level::ERROR.primary_title("mismatched types").id("E0308")) /// .element( /// Snippet::source(source) diff --git a/src/lib.rs b/src/lib.rs index 74f116aa..e0bae5b9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,7 +1,4 @@ -//! A library for formatting of text or programming code snippets. -//! -//! It's primary purpose is to build an ASCII-graphical representation of the snippet -//! with annotations. +//! Format [diagnostic reports][Report], including highlighting snippets of text //! //! # Example //! @@ -12,7 +9,83 @@ //! #![doc = include_str!("../examples/expected_type.svg")] //! -//! # features +//! # Visual overview +//! +//! [`Report`] +//! +#![doc = include_str!("../examples/multi_suggestion.svg")] +//! +//! ### Primary group +//! +//! [`Title`] +//! ```text +//! error: cannot construct `Box<_, _>` with struct literal syntax due to private fields +//! ``` +//! +//! +//! [`Annotation`] on a [`Snippet`] +//! ```text +//! ╭▸ $DIR/multi-suggestion.rs:17:13 +//! │ +//! 17 │ let _ = Box {}; +//! │ ━━━ +//! │ +//! ``` +//! +//! [`Message`] +//! ```text +//! ╰ note: private fields `0` and `1` that were not provided +//! ``` +//! +//! +//! +//! ### Secondary group: suggested fix +//! +//! [`Title`] (proposed solution) +//! ```text +//! help: you might have meant to use an associated function to build this type +//! ``` +//! +//! [`Patch`] Option 1 on a [`Snippet`] +//! ```text +//! ╭╴ +//! 21 - let _ = Box {}; +//! 21 + let _ = Box::new(_); +//! ├╴ +//! ``` +//! +//! [`Patch`] Option 2 on a [`Snippet`] +//! ```text +//! ├╴ +//! 17 - let _ = Box {}; +//! 17 + let _ = Box::new_uninit(); +//! ├╴ +//! ``` +//! +//! *etc for Options 3 and 4* +//! +//! [`Message`] +//! ```text +//! ╰ and 12 other candidates +//! ``` +//! +//! ### Secondary group: alternative suggested fix +//! +//! [`Title`] (proposed solution) +//! ```text +//! help: consider using the `Default` trait +//! ``` +//! +//! Only [`Patch`] on a [`Snippet`] +//! ```text +//! ╭╴ +//! 17 - let _ = Box {}; +//! 17 + let _ = ::default(); +//! ╰╴ +//! ``` +//! +//! # Cargo `features` +//! //! - `testing-colors` - Makes [Renderer::styled] colors OS independent, which //! allows for easier testing when testing colored output. It should be added as //! a feature in `[dev-dependencies]`, which can be done with the following command: @@ -30,6 +103,7 @@ pub mod renderer; mod snippet; /// Normalize the string to avoid any unicode control characters. +/// /// This is important for untrusted input, as it can contain /// invalid unicode sequences. pub fn normalize_untrusted_str(s: &str) -> String { diff --git a/src/renderer/mod.rs b/src/renderer/mod.rs index a676715e..64ec89fd 100644 --- a/src/renderer/mod.rs +++ b/src/renderer/mod.rs @@ -1,40 +1,23 @@ // Most of this file is adapted from https://github.com/rust-lang/rust/blob/160905b6253f42967ed4aef4b98002944c7df24c/compiler/rustc_errors/src/emitter.rs -//! The renderer for [`Group`]s +//! The [Renderer] and its settings //! //! # Example -//! ``` -//! use annotate_snippets::*; -//! use annotate_snippets::Level; -//! -//! let source = r#" -//! use baz::zed::bar; -//! -//! mod baz {} -//! mod zed { -//! pub fn bar() { println!("bar3"); } -//! } -//! fn main() { -//! bar(); -//! } -//! "#; //! +//! ``` +//! # use annotate_snippets::*; +//! # use annotate_snippets::renderer::*; +//! # use annotate_snippets::Level; +//! let report = // ... +//! # &[Group::with_title( +//! # Level::ERROR +//! # .primary_title("unresolved import `baz::zed`") +//! # .id("E0432") +//! # )]; //! -//! Group::with_title( -//! Level::ERROR -//! .primary_title("unresolved import `baz::zed`") -//! .id("E0432") -//! ) -//! .element( -//! Snippet::source(source) -//! .path("temp.rs") -//! .line_start(1) -//! .annotation( -//! AnnotationKind::Primary -//! .span(10..13) -//! .label("could not find `zed` in `baz`"), -//! ) -//! ); +//! let renderer = Renderer::styled().decor_style(DecorStyle::Unicode); +//! let output = renderer.render(report); +//! anstream::println!("{output}"); //! ``` mod margin; @@ -60,9 +43,35 @@ use std::fmt; use stylesheet::Stylesheet; const ANONYMIZED_LINE_NUM: &str = "LL"; + +/// See [`Renderer::term_width`] pub const DEFAULT_TERM_WIDTH: usize = 140; -/// A renderer for [`Group`]s +/// The [Renderer] for a [`Report`] +/// +/// The caller is expected to detect any relevant terminal features and configure the renderer, +/// including +/// - ANSI Escape code support (always outputted with [`Renderer::styled`]) +/// - Terminal width ([`Renderer::term_width`]) +/// - Unicode support ([`Renderer::decor_style`]) +/// +/// # Example +/// +/// ``` +/// # use annotate_snippets::*; +/// # use annotate_snippets::renderer::*; +/// # use annotate_snippets::Level; +/// let report = // ... +/// # &[Group::with_title( +/// # Level::ERROR +/// # .primary_title("unresolved import `baz::zed`") +/// # .id("E0432") +/// # )]; +/// +/// let renderer = Renderer::styled(); +/// let output = renderer.render(report); +/// anstream::println!("{output}"); +/// ``` #[derive(Clone, Debug)] pub struct Renderer { anonymized_line_numbers: bool, @@ -86,7 +95,12 @@ impl Renderer { /// Default terminal styling /// + /// If ANSI escape codes are not supported, either + /// - Call [`Renderer::plain`] instead + /// - Strip them after the fact, like with [`anstream`](https://docs.rs/anstream/latest/anstream/) + /// /// # Note + /// /// When testing styled terminal output, see the [`testing-colors` feature](crate#features) pub const fn styled() -> Self { const USE_WINDOWS_COLORS: bool = cfg!(windows) && !cfg!(feature = "testing-colors"); @@ -125,8 +139,7 @@ impl Renderer { /// Anonymize line numbers /// - /// This enables (or disables) line number anonymization. When enabled, line numbers are replaced - /// with `LL`. + /// When enabled, line numbers are replaced with `LL` which is useful for tests. /// /// # Example /// @@ -142,83 +155,87 @@ impl Renderer { self } + /// Abbreviate the message pub const fn short_message(mut self, short_message: bool) -> Self { self.short_message = short_message; self } - // Set the terminal width + /// Set the width to render within + /// + /// Affects the rendering of [`Snippet`]s pub const fn term_width(mut self, term_width: usize) -> Self { self.term_width = term_width; self } + /// Set the character set used for rendering decor pub const fn decor_style(mut self, decor_style: DecorStyle) -> Self { self.decor_style = decor_style; self } - /// Set the output style for `error` + /// Override the output style for `error` pub const fn error(mut self, style: Style) -> Self { self.stylesheet.error = style; self } - /// Set the output style for `warning` + /// Override the output style for `warning` pub const fn warning(mut self, style: Style) -> Self { self.stylesheet.warning = style; self } - /// Set the output style for `info` + /// Override the output style for `info` pub const fn info(mut self, style: Style) -> Self { self.stylesheet.info = style; self } - /// Set the output style for `note` + /// Override the output style for `note` pub const fn note(mut self, style: Style) -> Self { self.stylesheet.note = style; self } - /// Set the output style for `help` + /// Override the output style for `help` pub const fn help(mut self, style: Style) -> Self { self.stylesheet.help = style; self } - /// Set the output style for line numbers + /// Override the output style for line numbers pub const fn line_num(mut self, style: Style) -> Self { self.stylesheet.line_num = style; self } - /// Set the output style for emphasis + /// Override the output style for emphasis pub const fn emphasis(mut self, style: Style) -> Self { self.stylesheet.emphasis = style; self } - /// Set the output style for none + /// Override the output style for none pub const fn none(mut self, style: Style) -> Self { self.stylesheet.none = style; self } - /// Set the output style for [`AnnotationKind::Context`] + /// Override the output style for [`AnnotationKind::Context`] pub const fn context(mut self, style: Style) -> Self { self.stylesheet.context = style; self } - /// Set the output style for additions + /// Override the output style for additions pub const fn addition(mut self, style: Style) -> Self { self.stylesheet.addition = style; self } - /// Set the output style for removals + /// Override the output style for removals pub const fn removal(mut self, style: Style) -> Self { self.stylesheet.removal = style; self @@ -226,7 +243,7 @@ impl Renderer { } impl Renderer { - /// Render a diagnostic, a series of [`Group`]s + /// Render a diagnostic [`Report`] pub fn render(&self, groups: Report<'_>) -> String { if self.short_message { self.render_short_message(groups).unwrap() @@ -2926,6 +2943,7 @@ struct UnderlineParts { multiline_bottom_right_with_text: char, } +/// The character set for rendering for decor #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum DecorStyle { Ascii, diff --git a/src/snippet.rs b/src/snippet.rs index 78ad264a..7d4adb6c 100644 --- a/src/snippet.rs +++ b/src/snippet.rs @@ -11,11 +11,14 @@ pub(crate) const INFO_TXT: &str = "info"; pub(crate) const NOTE_TXT: &str = "note"; pub(crate) const WARNING_TXT: &str = "warning"; -/// A [diagnostic message][Title] and any associated context to help users +/// A [diagnostic message][Title] and any associated [context][Element] to help users /// understand it /// -/// The first [`Group`] is the "primary" group, ie it contains the diagnostic -/// message. All subsequent [`Group`]s are for distinct pieces of context. +/// The first [`Group`] is the ["primary" group][Level::primary_title], ie it contains the diagnostic +/// message. +/// +/// All subsequent [`Group`]s are for distinct pieces of [context][Level::secondary_title]. +/// The primary group will be visually distinguished to help tell them apart. pub type Report<'a> = &'a [Group<'a>]; #[derive(Clone, Debug, Default)] @@ -24,11 +27,13 @@ pub(crate) struct Id<'a> { pub(crate) url: Option>, } -/// An [`Element`] container +/// A [`Title`] with supporting [context][Element] within a [`Report`] +/// +/// [Decor][crate::renderer::DecorStyle] is used to visually connect [`Element`]s of a `Group`. /// -/// A [diagnostic][crate::Renderer::render] is made of several `Group`s. -/// `Group`s are used to [annotate][AnnotationKind::Primary] [`Snippet`]s -/// with different [semantic reasons][Title]. +/// Generally, you will create separate group's for: +/// - New [`Snippet`]s, especially if they need their own [`AnnotationKind::Primary`] +/// - Each logically distinct set of [suggestions][Patch`] /// /// # Example /// @@ -45,7 +50,7 @@ pub struct Group<'a> { } impl<'a> Group<'a> { - /// Create group with a title, deriving the primary [`Level`] for [`Annotation`]s from it + /// Create group with a [`Title`], deriving [`AnnotationKind::Primary`] from its [`Level`] pub fn with_title(title: Title<'a>) -> Self { let level = title.level.clone(); let mut x = Self::with_level(level); @@ -53,7 +58,7 @@ impl<'a> Group<'a> { x } - /// Create a title-less group with a primary [`Level`] for [`Annotation`]s + /// Create a title-less group with a primary [`Level`] for [`AnnotationKind::Primary`] /// /// # Example /// @@ -70,11 +75,13 @@ impl<'a> Group<'a> { } } + /// Append an [`Element`] that adds context to the [`Title`] pub fn element(mut self, section: impl Into>) -> Self { self.elements.push(section.into()); self } + /// Append [`Element`]s that adds context to the [`Title`] pub fn elements(mut self, sections: impl IntoIterator>>) -> Self { self.elements.extend(sections.into_iter().map(Into::into)); self @@ -130,9 +137,23 @@ impl From for Element<'_> { #[derive(Clone, Debug)] pub struct Padding; -/// A text [`Element`] to start a [`Group`] +/// A title that introduces a [`Group`], describing the main point +/// +/// To create a `Title`, see [`Level::primary_title`] or [`Level::secondary_title`]. +/// +/// # Example /// -/// See [`Level::primary_title`] to create this. +/// ```rust +/// # use annotate_snippets::*; +/// let report = &[ +/// Group::with_title( +/// Level::ERROR.primary_title("mismatched types").id("E0308") +/// ), +/// Group::with_title( +/// Level::HELP.secondary_title("function defined here") +/// ), +/// ]; +/// ``` #[derive(Clone, Debug)] pub struct Title<'a> { pub(crate) level: Level<'a>, @@ -142,15 +163,14 @@ pub struct Title<'a> { } impl<'a> Title<'a> { - ///
+ /// The category for this [`Report`] /// - /// This is only relevant if the title is the first element of a group. + /// Useful for looking searching for more information to resolve the diagnostic. /// - ///
///
/// /// Text passed to this function is considered "untrusted input", as such - /// all text is passed through a normalization function. Pre-styled text is + /// all text is passed through a normalization function. Styled text is /// not allowed to be passed to this function. /// ///
@@ -159,10 +179,11 @@ impl<'a> Title<'a> { self } + /// Provide a URL for [`Title::id`] for more information on this diagnostic + /// ///
/// - /// This is only relevant if the title is the first element of a group and - /// `id` present + /// This is only relevant if `id` is present /// ///
pub fn id_url(mut self, url: impl Into>) -> Self { @@ -183,6 +204,10 @@ pub struct Message<'a> { /// A source view [`Element`] in a [`Group`] /// /// If you do not have [source][Snippet::source] available, see instead [`Origin`] +/// +/// `Snippet`s come in the following styles (`T`): +/// - With [`Annotation`]s, see [`Snippet::annotation`] +/// - With [`Patch`]s, see [`Snippet::patch`] #[derive(Clone, Debug)] pub struct Snippet<'a, T> { pub(crate) path: Option>, @@ -233,7 +258,11 @@ impl<'a, T: Clone> Snippet<'a, T> { self } - /// Hide lines without [`Annotation`]s + /// Control whether lines without [`Annotation`]s are shown + /// + /// The default is `fold(true)`, collapsing uninteresting lines. + /// + /// See [`AnnotationKind::Visible`] to force specific spans to be shown. pub fn fold(mut self, fold: bool) -> Self { self.fold = fold; self @@ -268,9 +297,18 @@ impl<'a> Snippet<'a, Patch<'a>> { } } -/// Highlighted and describe a span of text within a [`Snippet`] +/// Highlight and describe a span of text within a [`Snippet`] /// /// See [`AnnotationKind`] to create an annotation. +/// +/// # Example +/// +/// ```rust +/// # #[allow(clippy::needless_doctest_main)] +#[doc = include_str!("../examples/expected_type.rs")] +/// ``` +/// +#[doc = include_str!("../examples/expected_type.svg")] #[derive(Clone, Debug)] pub struct Annotation<'a> { pub(crate) span: Range, @@ -297,21 +335,23 @@ impl<'a> Annotation<'a> { } /// Style the source according to the [`AnnotationKind`] + /// + /// This gives extra emphasis to this annotation pub fn highlight_source(mut self, highlight_source: bool) -> Self { self.highlight_source = highlight_source; self } } -/// The category of the [`Annotation`] +/// The type of [`Annotation`] being applied to a [`Snippet`] #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] #[non_exhaustive] pub enum AnnotationKind { - /// Shows the source that the [Group's Title][Group::with_title] references + /// For showing the source that the [Group's Title][Group::with_title] references /// /// For [`Title`]-less groups, see [`Group::with_level`] Primary, - /// Additional context to explain the [`Primary`][Self::Primary] + /// Additional context to better understand the [`Primary`][Self::Primary] /// [`Annotation`] /// /// See also [`Renderer::context`]. @@ -339,6 +379,7 @@ pub enum AnnotationKind { } impl AnnotationKind { + /// Annotate a byte span within [`Snippet`] pub fn span<'a>(self, span: Range) -> Annotation<'a> { Annotation { span, @@ -354,6 +395,17 @@ impl AnnotationKind { } /// Suggested edit to the [`Snippet`] +/// +/// See [`Snippet::patch`] +/// +/// # Example +/// +/// ```rust +/// # #[allow(clippy::needless_doctest_main)] +#[doc = include_str!("../examples/multi_suggestion.rs")] +/// ``` +/// +#[doc = include_str!("../examples/multi_suggestion.svg")] #[derive(Clone, Debug)] pub struct Patch<'a> { pub(crate) span: Range, @@ -361,7 +413,7 @@ pub struct Patch<'a> { } impl<'a> Patch<'a> { - /// Splice `replacement` into the [`Snippet`] at the `span` + /// Splice `replacement` into the [`Snippet`] at the specified byte span /// ///
/// @@ -433,7 +485,7 @@ impl<'a> Patch<'a> { /// /// ```rust /// # use annotate_snippets::{Group, Snippet, AnnotationKind, Level, Origin}; -/// let input = &[ +/// let report = &[ /// Group::with_title(Level::ERROR.primary_title("mismatched types").id("E0308")) /// .element( /// Origin::path("$DIR/mismatched-types.rs") diff --git a/tests/examples.rs b/tests/examples.rs index 6345559b..f607b157 100644 --- a/tests/examples.rs +++ b/tests/examples.rs @@ -70,6 +70,13 @@ fn multislice() { assert_example(target, expected); } +#[test] +fn multi_suggestion() { + let target = "multi_suggestion"; + let expected = snapbox::file!["../examples/multi_suggestion.svg": TermSvg]; + assert_example(target, expected); +} + #[test] fn struct_name_as_context() { let target = "struct_name_as_context"; From 2ac8e572269a24c3165dff16b92f1467a0384367 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Wed, 27 Aug 2025 14:49:13 -0500 Subject: [PATCH 2/4] docs(examples): Tie the term 'report' into examples --- examples/custom_error.rs | 4 ++-- examples/custom_level.rs | 4 ++-- examples/elide_header.rs | 4 ++-- examples/footer.rs | 4 ++-- examples/format.rs | 4 ++-- examples/highlight_message.rs | 4 ++-- examples/highlight_source.rs | 4 ++-- examples/id_hyperlink.rs | 4 ++-- examples/multi_suggestion.rs | 4 ++-- examples/multislice.rs | 4 ++-- examples/struct_name_as_context.rs | 4 ++-- 11 files changed, 22 insertions(+), 22 deletions(-) diff --git a/examples/custom_error.rs b/examples/custom_error.rs index f7dd385e..067b3369 100644 --- a/examples/custom_error.rs +++ b/examples/custom_error.rs @@ -15,7 +15,7 @@ fn main() { pub static C: u32 = 0 - 1; //~^ ERROR could not evaluate static initializer "#; - let message = &[Group::with_title( + let report = &[Group::with_title( Level::ERROR .with_name(Some("error: internal compiler error")) .primary_title("could not evaluate static initializer") @@ -30,5 +30,5 @@ pub static C: u32 = 0 - 1; )]; let renderer = Renderer::styled().decor_style(DecorStyle::Unicode); - anstream::println!("{}", renderer.render(message)); + anstream::println!("{}", renderer.render(report)); } diff --git a/examples/custom_level.rs b/examples/custom_level.rs index dc3bd952..0cc79850 100644 --- a/examples/custom_level.rs +++ b/examples/custom_level.rs @@ -29,7 +29,7 @@ fn main() { } } "#; - let message = &[ + let report = &[ Group::with_title( Level::ERROR .primary_title("`break` with value from a `while` loop") @@ -64,5 +64,5 @@ fn main() { ]; let renderer = Renderer::styled().decor_style(DecorStyle::Unicode); - anstream::println!("{}", renderer.render(message)); + anstream::println!("{}", renderer.render(report)); } diff --git a/examples/elide_header.rs b/examples/elide_header.rs index c7deda16..7ea2cf04 100644 --- a/examples/elide_header.rs +++ b/examples/elide_header.rs @@ -8,7 +8,7 @@ def foobar(door, bar={}): """ "#; - let message = &[Group::with_level(Level::NOTE) + let report = &[Group::with_level(Level::NOTE) .element( Snippet::source(source) .fold(false) @@ -17,5 +17,5 @@ def foobar(door, bar={}): .element(Level::HELP.message("Replace with `None`; initialize within function"))]; let renderer = Renderer::styled(); - anstream::println!("{}", renderer.render(message)); + anstream::println!("{}", renderer.render(report)); } diff --git a/examples/footer.rs b/examples/footer.rs index 77854b2d..a2b1ac35 100644 --- a/examples/footer.rs +++ b/examples/footer.rs @@ -1,7 +1,7 @@ use annotate_snippets::{AnnotationKind, Group, Level, Renderer, Snippet}; fn main() { - let message = + let report = &[ Group::with_title(Level::ERROR.primary_title("mismatched types").id("E0308")).element( Snippet::source(" slices: vec![\"A\",") @@ -17,5 +17,5 @@ fn main() { ]; let renderer = Renderer::styled(); - anstream::println!("{}", renderer.render(message)); + anstream::println!("{}", renderer.render(report)); } diff --git a/examples/format.rs b/examples/format.rs index 072552eb..d9cef1b6 100644 --- a/examples/format.rs +++ b/examples/format.rs @@ -23,7 +23,7 @@ fn main() { _ => continue, } }"#; - let message = &[ + let report = &[ Group::with_title(Level::ERROR.primary_title("mismatched types").id("E0308")).element( Snippet::source(source) .line_start(51) @@ -43,5 +43,5 @@ fn main() { ]; let renderer = Renderer::styled(); - anstream::println!("{}", renderer.render(message)); + anstream::println!("{}", renderer.render(report)); } diff --git a/examples/highlight_message.rs b/examples/highlight_message.rs index 32a1cc82..baed3c8b 100644 --- a/examples/highlight_message.rs +++ b/examples/highlight_message.rs @@ -33,7 +33,7 @@ fn main() { found fn item `fn(Box<{MAGENTA}(dyn Any + Send + 'static){MAGENTA:#}>) -> Pin<_> {MAGENTA}{{wrapped_fn}}{MAGENTA:#}`", ); - let message = &[ + let report = &[ Group::with_title(Level::ERROR.primary_title("mismatched types").id("E0308")) .element( Snippet::source(source) @@ -59,5 +59,5 @@ fn main() { ]; let renderer = Renderer::styled().anonymized_line_numbers(true); - anstream::println!("{}", renderer.render(message)); + anstream::println!("{}", renderer.render(report)); } diff --git a/examples/highlight_source.rs b/examples/highlight_source.rs index c50250ac..514f6b7c 100644 --- a/examples/highlight_source.rs +++ b/examples/highlight_source.rs @@ -9,7 +9,7 @@ const CON: Vec = vec![1, 2, 3]; //~ ERROR E0010 //~| ERROR cannot call non-const method fn main() {} "#; - let message = &[Group::with_title(Level::ERROR.primary_title("allocations are not allowed in constants") + let report = &[Group::with_title(Level::ERROR.primary_title("allocations are not allowed in constants") .id("E0010")) .element( Snippet::source(source) @@ -27,5 +27,5 @@ fn main() {} )]; let renderer = Renderer::styled().anonymized_line_numbers(true); - anstream::println!("{}", renderer.render(message)); + anstream::println!("{}", renderer.render(report)); } diff --git a/examples/id_hyperlink.rs b/examples/id_hyperlink.rs index d4cc3e96..a03938a9 100644 --- a/examples/id_hyperlink.rs +++ b/examples/id_hyperlink.rs @@ -7,7 +7,7 @@ fn main() { let () = 4; //~ ERROR } "#; - let message = &[Group::with_title( + let report = &[Group::with_title( Level::ERROR .primary_title("mismatched types") .id("E0308") @@ -30,5 +30,5 @@ fn main() { )]; let renderer = Renderer::styled().decor_style(DecorStyle::Unicode); - anstream::println!("{}", renderer.render(message)); + anstream::println!("{}", renderer.render(report)); } diff --git a/examples/multi_suggestion.rs b/examples/multi_suggestion.rs index b2b8f50b..7d9eee12 100644 --- a/examples/multi_suggestion.rs +++ b/examples/multi_suggestion.rs @@ -23,7 +23,7 @@ fn main() { } "#; - let message = &[ + let report = &[ Group::with_title(Level::ERROR.primary_title( "cannot construct `Box<_, _>` with struct literal syntax due to private fields", )) @@ -70,5 +70,5 @@ fn main() { ]; let renderer = Renderer::styled().decor_style(DecorStyle::Unicode); - anstream::println!("{}", renderer.render(message)); + anstream::println!("{}", renderer.render(report)); } diff --git a/examples/multislice.rs b/examples/multislice.rs index 575bb6f7..3a2e7fa0 100644 --- a/examples/multislice.rs +++ b/examples/multislice.rs @@ -1,7 +1,7 @@ use annotate_snippets::{Annotation, Group, Level, Renderer, Snippet}; fn main() { - let message = &[ + let report = &[ Group::with_title(Level::ERROR.primary_title("mismatched types")) .element( Snippet::>::source("Foo") @@ -18,5 +18,5 @@ fn main() { ]; let renderer = Renderer::styled(); - anstream::println!("{}", renderer.render(message)); + anstream::println!("{}", renderer.render(report)); } diff --git a/examples/struct_name_as_context.rs b/examples/struct_name_as_context.rs index 6c25f1b4..0271e04b 100644 --- a/examples/struct_name_as_context.rs +++ b/examples/struct_name_as_context.rs @@ -9,7 +9,7 @@ fn main() { field6: usize, } "#; - let message = &[Group::with_title( + let report = &[Group::with_title( Level::ERROR.primary_title("functions are not allowed in struct definitions"), ) .element( @@ -23,5 +23,5 @@ fn main() { )]; let renderer = Renderer::styled(); - anstream::println!("{}", renderer.render(message)); + anstream::println!("{}", renderer.render(report)); } From 923a5f118bfd722ab3689e664ef3ee8e5fd047db Mon Sep 17 00:00:00 2001 From: Ed Page Date: Wed, 27 Aug 2025 14:50:46 -0500 Subject: [PATCH 3/4] docs: Dont bury Renderer::render --- src/renderer/mod.rs | 135 ++++++++++++++++++++++---------------------- 1 file changed, 69 insertions(+), 66 deletions(-) diff --git a/src/renderer/mod.rs b/src/renderer/mod.rs index 64ec89fd..afdd4356 100644 --- a/src/renderer/mod.rs +++ b/src/renderer/mod.rs @@ -174,72 +174,6 @@ impl Renderer { self.decor_style = decor_style; self } - - /// Override the output style for `error` - pub const fn error(mut self, style: Style) -> Self { - self.stylesheet.error = style; - self - } - - /// Override the output style for `warning` - pub const fn warning(mut self, style: Style) -> Self { - self.stylesheet.warning = style; - self - } - - /// Override the output style for `info` - pub const fn info(mut self, style: Style) -> Self { - self.stylesheet.info = style; - self - } - - /// Override the output style for `note` - pub const fn note(mut self, style: Style) -> Self { - self.stylesheet.note = style; - self - } - - /// Override the output style for `help` - pub const fn help(mut self, style: Style) -> Self { - self.stylesheet.help = style; - self - } - - /// Override the output style for line numbers - pub const fn line_num(mut self, style: Style) -> Self { - self.stylesheet.line_num = style; - self - } - - /// Override the output style for emphasis - pub const fn emphasis(mut self, style: Style) -> Self { - self.stylesheet.emphasis = style; - self - } - - /// Override the output style for none - pub const fn none(mut self, style: Style) -> Self { - self.stylesheet.none = style; - self - } - - /// Override the output style for [`AnnotationKind::Context`] - pub const fn context(mut self, style: Style) -> Self { - self.stylesheet.context = style; - self - } - - /// Override the output style for additions - pub const fn addition(mut self, style: Style) -> Self { - self.stylesheet.addition = style; - self - } - - /// Override the output style for removals - pub const fn removal(mut self, style: Style) -> Self { - self.stylesheet.removal = style; - self - } } impl Renderer { @@ -2620,6 +2554,75 @@ impl Renderer { } } +/// Customize [`Renderer::styled`] +impl Renderer { + /// Override the output style for `error` + pub const fn error(mut self, style: Style) -> Self { + self.stylesheet.error = style; + self + } + + /// Override the output style for `warning` + pub const fn warning(mut self, style: Style) -> Self { + self.stylesheet.warning = style; + self + } + + /// Override the output style for `info` + pub const fn info(mut self, style: Style) -> Self { + self.stylesheet.info = style; + self + } + + /// Override the output style for `note` + pub const fn note(mut self, style: Style) -> Self { + self.stylesheet.note = style; + self + } + + /// Override the output style for `help` + pub const fn help(mut self, style: Style) -> Self { + self.stylesheet.help = style; + self + } + + /// Override the output style for line numbers + pub const fn line_num(mut self, style: Style) -> Self { + self.stylesheet.line_num = style; + self + } + + /// Override the output style for emphasis + pub const fn emphasis(mut self, style: Style) -> Self { + self.stylesheet.emphasis = style; + self + } + + /// Override the output style for none + pub const fn none(mut self, style: Style) -> Self { + self.stylesheet.none = style; + self + } + + /// Override the output style for [`AnnotationKind::Context`] + pub const fn context(mut self, style: Style) -> Self { + self.stylesheet.context = style; + self + } + + /// Override the output style for additions + pub const fn addition(mut self, style: Style) -> Self { + self.stylesheet.addition = style; + self + } + + /// Override the output style for removals + pub const fn removal(mut self, style: Style) -> Self { + self.stylesheet.removal = style; + self + } +} + trait MessageOrTitle { fn level(&self) -> &Level<'_>; fn id(&self) -> Option<&Id<'_>>; From d95a5adf0e6455e656bcf31ea469c2180ecb759f Mon Sep 17 00:00:00 2001 From: Ed Page Date: Wed, 27 Aug 2025 14:51:03 -0500 Subject: [PATCH 4/4] docs: De-prioritze testing logic --- src/renderer/mod.rs | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/src/renderer/mod.rs b/src/renderer/mod.rs index afdd4356..0815cdcf 100644 --- a/src/renderer/mod.rs +++ b/src/renderer/mod.rs @@ -137,24 +137,6 @@ impl Renderer { } } - /// Anonymize line numbers - /// - /// When enabled, line numbers are replaced with `LL` which is useful for tests. - /// - /// # Example - /// - /// ```text - /// --> $DIR/whitespace-trimming.rs:4:193 - /// | - /// LL | ... let _: () = 42; - /// | ^^ expected (), found integer - /// | - /// ``` - pub const fn anonymized_line_numbers(mut self, anonymized_line_numbers: bool) -> Self { - self.anonymized_line_numbers = anonymized_line_numbers; - self - } - /// Abbreviate the message pub const fn short_message(mut self, short_message: bool) -> Self { self.short_message = short_message; @@ -174,6 +156,24 @@ impl Renderer { self.decor_style = decor_style; self } + + /// Anonymize line numbers + /// + /// When enabled, line numbers are replaced with `LL` which is useful for tests. + /// + /// # Example + /// + /// ```text + /// --> $DIR/whitespace-trimming.rs:4:193 + /// | + /// LL | ... let _: () = 42; + /// | ^^ expected (), found integer + /// | + /// ``` + pub const fn anonymized_line_numbers(mut self, anonymized_line_numbers: bool) -> Self { + self.anonymized_line_numbers = anonymized_line_numbers; + self + } } impl Renderer {