diff --git a/crates/interface/src/diagnostics/emitter/human.rs b/crates/interface/src/diagnostics/emitter/human.rs index aaf388d1..255bf7c8 100644 --- a/crates/interface/src/diagnostics/emitter/human.rs +++ b/crates/interface/src/diagnostics/emitter/human.rs @@ -189,7 +189,7 @@ impl HumanEmitter { .children .iter() .filter(|sub| sub.span.is_dummy()) - .map(OwnedMessage::from_subdiagnostic) + .map(|sub| OwnedMessage::from_subdiagnostic(sub, self.supports_color())) .collect(); let snippet = title @@ -282,8 +282,12 @@ impl OwnedMessage { Self { id: diag.id(), label: diag.label().into_owned(), level: to_as_level(diag.level) } } - fn from_subdiagnostic(sub: &SubDiagnostic) -> Self { - Self { id: None, label: sub.label().into_owned(), level: to_as_level(sub.level) } + fn from_subdiagnostic(sub: &SubDiagnostic, supports_color: bool) -> Self { + Self { + id: None, + label: sub.label_with_style(supports_color).into_owned(), + level: to_as_level(sub.level), + } } fn as_ref(&self) -> Message<'_> { diff --git a/crates/interface/src/diagnostics/mod.rs b/crates/interface/src/diagnostics/mod.rs index fc36bbb1..448b22a5 100644 --- a/crates/interface/src/diagnostics/mod.rs +++ b/crates/interface/src/diagnostics/mod.rs @@ -4,7 +4,11 @@ use crate::Span; use anstyle::{AnsiColor, Color}; -use std::{borrow::Cow, fmt, panic::Location}; +use std::{ + borrow::Cow, + fmt::{self, Write}, + panic::Location, +}; mod builder; pub use builder::{DiagBuilder, EmissionGuarantee}; @@ -315,7 +319,12 @@ pub struct SubDiagnostic { impl SubDiagnostic { /// Formats the diagnostic messages into a single string. pub fn label(&self) -> Cow<'_, str> { - flatten_messages(&self.messages) + flatten_messages(&self.messages, false, self.level) + } + + /// Formats the diagnostic messages into a single string with ANSI color codes if applicable. + pub fn label_with_style(&self, supports_color: bool) -> Cow<'_, str> { + flatten_messages(&self.messages, supports_color, self.level) } } @@ -377,7 +386,12 @@ impl Diag { /// Formats the diagnostic messages into a single string. pub fn label(&self) -> Cow<'_, str> { - flatten_messages(&self.messages) + flatten_messages(&self.messages, false, self.level) + } + + /// Formats the diagnostic messages into a single string with ANSI color codes if applicable. + pub fn label_with_style(&self, supports_color: bool) -> Cow<'_, str> { + flatten_messages(&self.messages, supports_color, self.level) } /// Returns the messages of this diagnostic. @@ -554,11 +568,69 @@ impl Diag { } } -// TODO: Styles? -fn flatten_messages(messages: &[(DiagMsg, Style)]) -> Cow<'_, str> { - match messages { - [] => Cow::Borrowed(""), - [(message, _)] => Cow::Borrowed(message.as_str()), - messages => messages.iter().map(|(msg, _)| msg.as_str()).collect(), +/// Flattens diagnostic messages, applying ANSI styles if requested. +fn flatten_messages(messages: &[(DiagMsg, Style)], with_style: bool, level: Level) -> Cow<'_, str> { + if with_style { + match messages { + [] => Cow::Borrowed(""), + [(msg, Style::NoStyle)] => Cow::Borrowed(msg.as_str()), + [(msg, style)] => { + let mut res = String::new(); + write_fmt(&mut res, msg, style, level); + Cow::Owned(res) + } + messages => { + let mut res = String::new(); + for (msg, style) in messages { + match style { + Style::NoStyle => res.push_str(msg.as_str()), + _ => write_fmt(&mut res, msg, style, level), + } + } + Cow::Owned(res) + } + } + } else { + match messages { + [] => Cow::Borrowed(""), + [(message, _)] => Cow::Borrowed(message.as_str()), + messages => messages.iter().map(|(msg, _)| msg.as_str()).collect(), + } + } +} + +fn write_fmt(output: &mut String, msg: &DiagMsg, style: &Style, level: Level) { + let ansi_style = style.to_color_spec(level); + write!(output, "{}{}{}", ansi_style.render(), msg.as_str(), ansi_style.render_reset()).unwrap(); +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_styled_messages() { + // Create a diagnostic with styled messages + let mut diag = Diag::new(Level::Note, "test"); + + diag.highlighted_note(vec![ + ("plain text ", Style::NoStyle), + ("removed", Style::Removal), + (" middle ", Style::NoStyle), + ("added", Style::Addition), + ]); + + let sub = &diag.children[0]; + + // Without styles - just concatenated text + let plain = sub.label(); + assert_eq!(plain, "plain text removed middle added"); + + // With styles - includes ANSI escape codes + let styled = sub.label_with_style(true); + assert_eq!( + styled.to_string(), + "plain text \u{1b}[91mremoved\u{1b}[0m middle \u{1b}[92madded\u{1b}[0m".to_string() + ); } }