diff --git a/examples/elide_header.rs b/examples/elide_header.rs
index 436bb25..c7deda1 100644
--- a/examples/elide_header.rs
+++ b/examples/elide_header.rs
@@ -14,7 +14,7 @@ def foobar(door, bar={}):
.fold(false)
.annotation(AnnotationKind::Primary.span(56..58).label("B006")),
)
- .element(Level::HELP.title("Replace with `None`; initialize within function"))];
+ .element(Level::HELP.message("Replace with `None`; initialize within function"))];
let renderer = Renderer::styled();
anstream::println!("{}", renderer.render(message));
diff --git a/examples/highlight_title.rs b/examples/highlight_message.rs
similarity index 96%
rename from examples/highlight_title.rs
rename to examples/highlight_message.rs
index 1de99cf..aaf4910 100644
--- a/examples/highlight_title.rs
+++ b/examples/highlight_message.rs
@@ -28,7 +28,7 @@ fn main() {
let magenta = annotate_snippets::renderer::AnsiColor::Magenta
.on_default()
.effects(Effects::BOLD);
- let title = format!(
+ let message = format!(
"expected fn pointer `{}for<'a>{} fn(Box<{}(dyn Any + Send + 'a){}>) -> Pin<_>`
found fn item `fn(Box<{}(dyn Any + Send + 'static){}>) -> Pin<_> {}{{wrapped_fn}}{}`",
magenta.render(),
@@ -57,7 +57,7 @@ fn main() {
.label("arguments to this function are incorrect"),
),
)
- .element(Level::NOTE.pre_styled_title(&title)),
+ .element(Level::NOTE.message(&message)),
Group::with_title(Level::NOTE.title("function defined here")).element(
Snippet::source(source)
.path("$DIR/highlighting.rs")
diff --git a/examples/highlight_title.svg b/examples/highlight_message.svg
similarity index 100%
rename from examples/highlight_title.svg
rename to examples/highlight_message.svg
diff --git a/src/level.rs b/src/level.rs
index 036cf7d..8eaaa87 100644
--- a/src/level.rs
+++ b/src/level.rs
@@ -2,7 +2,7 @@
use crate::renderer::stylesheet::Stylesheet;
use crate::snippet::{ERROR_TXT, HELP_TXT, INFO_TXT, NOTE_TXT, WARNING_TXT};
-use crate::{OptionCow, Title};
+use crate::{Message, OptionCow, Title};
use anstyle::Style;
use std::borrow::Cow;
@@ -73,6 +73,10 @@ impl<'a> Level<'a> {
}
impl<'a> Level<'a> {
+ /// A text [`Element`][crate::Element] to start a [`Group`][crate::Group]
+ ///
+ /// See [`Group::with_title`][crate::Group::with_title]
+ ///
///
///
/// Text passed to this function is considered "untrusted input", as such
@@ -80,15 +84,16 @@ impl<'a> Level<'a> {
/// not allowed to be passed to this function.
///
///
- pub fn title(self, title: impl Into>) -> Title<'a> {
+ pub fn title(self, text: impl Into>) -> Title<'a> {
Title {
level: self,
id: None,
- title: title.into(),
- is_pre_styled: false,
+ text: text.into(),
}
}
+ /// A text [`Element`][crate::Element] in a [`Group`][crate::Group]
+ ///
///
///
/// Text passed to this function is allowed to be pre-styled, as such all
@@ -97,12 +102,10 @@ impl<'a> Level<'a> {
/// used to normalize untrusted text before it is passed to this function.
///
///
- pub fn pre_styled_title(self, title: impl Into>) -> Title<'a> {
- Title {
+ pub fn message(self, text: impl Into>) -> Message<'a> {
+ Message {
level: self,
- id: None,
- title: title.into(),
- is_pre_styled: true,
+ text: text.into(),
}
}
diff --git a/src/renderer/mod.rs b/src/renderer/mod.rs
index 608613b..b5eff06 100644
--- a/src/renderer/mod.rs
+++ b/src/renderer/mod.rs
@@ -48,7 +48,7 @@ use crate::renderer::source_map::{
};
use crate::renderer::styled_buffer::StyledBuffer;
use crate::snippet::Id;
-use crate::{Annotation, AnnotationKind, Element, Group, Origin, Patch, Snippet, Title};
+use crate::{Annotation, AnnotationKind, Element, Group, Message, Origin, Patch, Snippet, Title};
pub use anstyle::*;
use margin::Margin;
use std::borrow::Cow;
@@ -303,7 +303,20 @@ impl Renderer {
title,
max_line_num_len,
title_style,
- matches!(peek, Some(Element::Title(_))),
+ matches!(peek, Some(Element::Title(_) | Element::Message(_))),
+ buffer_msg_line_offset,
+ );
+ last_was_suggestion = false;
+ }
+ Element::Message(title) => {
+ let title_style = TitleStyle::Secondary;
+ let buffer_msg_line_offset = buffer.num_lines();
+ self.render_title(
+ &mut buffer,
+ title,
+ max_line_num_len,
+ title_style,
+ matches!(peek, Some(Element::Title(_) | Element::Message(_))),
buffer_msg_line_offset,
);
last_was_suggestion = false;
@@ -336,6 +349,16 @@ impl Renderer {
);
}
+ Some(Element::Message(level))
+ if level.level.name != Some(None) =>
+ {
+ self.draw_col_separator_no_space(
+ &mut buffer,
+ current_line,
+ max_line_num_len + 1,
+ );
+ }
+
None if group_len > 1 => self.draw_col_separator_end(
&mut buffer,
current_line,
@@ -384,7 +407,8 @@ impl Renderer {
if g == 0
&& (matches!(section, Element::Origin(_))
|| (matches!(section, Element::Title(_)) && i == 0)
- || matches!(section, Element::Title(level) if level.level.name == Some(None)))
+ || matches!(section, Element::Title(level) if level.level.name == Some(None))
+ || matches!(section, Element::Message(level) if level.level.name == Some(None)))
{
let current_line = buffer.num_lines();
if peek.is_none() && group_len > 1 {
@@ -394,6 +418,13 @@ impl Renderer {
max_line_num_len + 1,
);
} else if matches!(peek, Some(Element::Title(level)) if level.level.name != Some(None))
+ {
+ self.draw_col_separator_no_space(
+ &mut buffer,
+ current_line,
+ max_line_num_len + 1,
+ );
+ } else if matches!(peek, Some(Element::Message(level)) if level.level.name != Some(None))
{
self.draw_col_separator_no_space(
&mut buffer,
@@ -503,7 +534,7 @@ impl Renderer {
fn render_title(
&self,
buffer: &mut StyledBuffer,
- title: &Title<'_>,
+ title: &dyn MessageOrTitle,
max_line_num_len: usize,
title_style: TitleStyle,
is_cont: bool,
@@ -511,7 +542,7 @@ impl Renderer {
) {
let (label_style, title_element_style) = match title_style {
TitleStyle::MainHeader => (
- ElementStyle::Level(title.level.level),
+ ElementStyle::Level(title.level().level),
if self.short_message {
ElementStyle::NoStyle
} else {
@@ -519,7 +550,7 @@ impl Renderer {
},
),
TitleStyle::Header => (
- ElementStyle::Level(title.level.level),
+ ElementStyle::Level(title.level().level),
ElementStyle::HeaderMsg,
),
TitleStyle::Secondary => {
@@ -538,10 +569,10 @@ impl Renderer {
};
let mut label_width = 0;
- if title.level.name != Some(None) {
- buffer.append(buffer_msg_line_offset, title.level.as_str(), label_style);
- label_width += title.level.as_str().len();
- if let Some(Id { id: Some(id), url }) = &title.id {
+ if title.level().name != Some(None) {
+ buffer.append(buffer_msg_line_offset, title.level().as_str(), label_style);
+ label_width += title.level().as_str().len();
+ if let Some(Id { id: Some(id), url }) = &title.id() {
buffer.append(buffer_msg_line_offset, "[", label_style);
if let Some(url) = url.as_ref() {
buffer.append(
@@ -584,10 +615,10 @@ impl Renderer {
label_width
});
- let (title_str, style) = if title.is_pre_styled {
- (title.title.to_string(), ElementStyle::NoStyle)
+ let (title_str, style) = if title.is_pre_styled() {
+ (title.text().to_owned(), ElementStyle::NoStyle)
} else {
- (normalize_whitespace(&title.title), title_element_style)
+ (normalize_whitespace(title.text()), title_element_style)
};
for (i, text) in title_str.lines().enumerate() {
if i != 0 {
@@ -2532,6 +2563,43 @@ impl Renderer {
}
}
+trait MessageOrTitle {
+ fn level(&self) -> &Level<'_>;
+ fn id(&self) -> Option<&Id<'_>>;
+ fn text(&self) -> &str;
+ fn is_pre_styled(&self) -> bool;
+}
+
+impl MessageOrTitle for Title<'_> {
+ fn level(&self) -> &Level<'_> {
+ &self.level
+ }
+ fn id(&self) -> Option<&Id<'_>> {
+ self.id.as_ref()
+ }
+ fn text(&self) -> &str {
+ self.text.as_ref()
+ }
+ fn is_pre_styled(&self) -> bool {
+ false
+ }
+}
+
+impl MessageOrTitle for Message<'_> {
+ fn level(&self) -> &Level<'_> {
+ &self.level
+ }
+ fn id(&self) -> Option<&Id<'_>> {
+ None
+ }
+ fn text(&self) -> &str {
+ self.text.as_ref()
+ }
+ fn is_pre_styled(&self) -> bool {
+ true
+ }
+}
+
// instead of taking the String length or dividing by 10 while > 0, we multiply a limit by 10 until
// we're higher. If the loop isn't exited by the `return`, the last multiplication will wrap, which
// is OK, because while we cannot fit a higher power of 10 in a usize, the loop will end anyway.
@@ -2846,7 +2914,10 @@ fn max_line_number(groups: &[Group<'_>]) -> usize {
v.elements
.iter()
.map(|s| match s {
- Element::Title(_) | Element::Origin(_) | Element::Padding(_) => 0,
+ Element::Title(_)
+ | Element::Message(_)
+ | Element::Origin(_)
+ | Element::Padding(_) => 0,
Element::Cause(cause) => {
let end = cause
.markers
diff --git a/src/snippet.rs b/src/snippet.rs
index af06326..ef92ff4 100644
--- a/src/snippet.rs
+++ b/src/snippet.rs
@@ -63,6 +63,7 @@ impl<'a> Group<'a> {
#[non_exhaustive]
pub enum Element<'a> {
Title(Title<'a>),
+ Message(Message<'a>),
Cause(Snippet<'a, Annotation<'a>>),
Suggestion(Snippet<'a, Patch<'a>>),
Origin(Origin<'a>),
@@ -75,6 +76,12 @@ impl<'a> From> for Element<'a> {
}
}
+impl<'a> From> for Element<'a> {
+ fn from(value: Message<'a>) -> Self {
+ Element::Message(value)
+ }
+}
+
impl<'a> From>> for Element<'a> {
fn from(value: Snippet<'a, Annotation<'a>>) -> Self {
Element::Cause(value)
@@ -103,15 +110,14 @@ impl From for Element<'_> {
#[derive(Clone, Debug)]
pub struct Padding;
-/// A text [`Element`] in a [`Group`]
+/// A text [`Element`] to start a [`Group`]
///
/// See [`Level::title`] to create this.
#[derive(Clone, Debug)]
pub struct Title<'a> {
pub(crate) level: Level<'a>,
pub(crate) id: Option>,
- pub(crate) title: Cow<'a, str>,
- pub(crate) is_pre_styled: bool,
+ pub(crate) text: Cow<'a, str>,
}
impl<'a> Title<'a> {
@@ -144,6 +150,15 @@ impl<'a> Title<'a> {
}
}
+/// A text [`Element`] in a [`Group`]
+///
+/// See [`Level::message`] to create this.
+#[derive(Clone, Debug)]
+pub struct Message<'a> {
+ pub(crate) level: Level<'a>,
+ pub(crate) text: Cow<'a, str>,
+}
+
/// A source view [`Element`] in a [`Group`]
///
/// If you do not have [source][Snippet::source] available, see instead [`Origin`]
diff --git a/tests/examples.rs b/tests/examples.rs
index db00bc1..226c31f 100644
--- a/tests/examples.rs
+++ b/tests/examples.rs
@@ -50,9 +50,9 @@ fn highlight_source() {
}
#[test]
-fn highlight_title() {
- let target = "highlight_title";
- let expected = snapbox::file!["../examples/highlight_title.svg": TermSvg];
+fn highlight_message() {
+ let target = "highlight_message";
+ let expected = snapbox::file!["../examples/highlight_message.svg": TermSvg];
assert_example(target, expected);
}
diff --git a/tests/formatter.rs b/tests/formatter.rs
index 6ebfd94..fe16ca9 100644
--- a/tests/formatter.rs
+++ b/tests/formatter.rs
@@ -199,7 +199,7 @@ error:
#[test]
fn test_format_footer_title() {
let input = &[Group::with_title(Level::ERROR.title(""))
- .element(Level::ERROR.title("This __is__ a title"))];
+ .element(Level::ERROR.message("This __is__ a title"))];
let expected = str![[r#"
error:
|
@@ -2258,7 +2258,9 @@ fn main() {
.label("`+` cannot be used to concatenate two `&str` strings"),
),
)
- .element(Level::NOTE.title("string concatenation requires an owned `String` on the left")),
+ .element(
+ Level::NOTE.message("string concatenation requires an owned `String` on the left"),
+ ),
Group::with_title(Level::HELP.title("create an owned `String` from a string reference"))
.element(
Snippet::source(source)
@@ -2333,7 +2335,7 @@ fn foo() {
.annotation(AnnotationKind::Primary.span(0..0)),
)
- .element(Level::NOTE.title("this error originates in the macro `include` (in Nightly builds, run with -Z macro-backtrace for more info)")),
+ .element(Level::NOTE.message("this error originates in the macro `include` (in Nightly builds, run with -Z macro-backtrace for more info)")),
];
let expected_ascii = str![[r#"
diff --git a/tests/rustc_tests.rs b/tests/rustc_tests.rs
index 4b1944a..c9bba62 100644
--- a/tests/rustc_tests.rs
+++ b/tests/rustc_tests.rs
@@ -1659,8 +1659,8 @@ fn main() {}
.annotation(AnnotationKind::Context.span(878..880).label("not covered"))
.annotation(AnnotationKind::Context.span(890..892).label("not covered"))
)
- .element(Level::NOTE.title("the matched value is of type `NonEmptyEnum5`"))
- .element(Level::NOTE.title("match arms with guards don't count towards exhaustivity")
+ .element(Level::NOTE.message("the matched value is of type `NonEmptyEnum5`"))
+ .element(Level::NOTE.message("match arms with guards don't count towards exhaustivity")
),
Group::with_title(
Level::HELP
@@ -1749,7 +1749,7 @@ fn main() {
.primary(true)
)
.element(Padding)
- .element(Level::NOTE.title("...because it uses `Self` as a type parameter"))
+ .element(Level::NOTE.message("...because it uses `Self` as a type parameter"))
.element(
Snippet::source(source)
.line_start(1)
@@ -2795,9 +2795,9 @@ fn main() {
.path("lint_example.rs")
.annotation(AnnotationKind::Primary.span(40..49)),
)
- .element(Level::WARNING.title("this changes meaning in Rust 2021"))
- .element(Level::NOTE.title(long_title2))
- .element(Level::NOTE.title("`#[warn(array_into_iter)]` on by default")),
+ .element(Level::WARNING.message("this changes meaning in Rust 2021"))
+ .element(Level::NOTE.message(long_title2))
+ .element(Level::NOTE.message("`#[warn(array_into_iter)]` on by default")),
Group::with_title(
Level::HELP.title("use `.iter()` instead of `.into_iter()` to avoid ambiguity"),
)