use std::borrow::Cow;
use std::io::Write as _;
use crossterm::queue;
use crossterm::style::{Color as CtColor, Print, ResetColor, SetForegroundColor};
use crate::tty::caps::{protocol_mode, LineCaps};
use crate::tty::{Sink, Terminal};
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum Level {
Info,
Ok,
Warn,
Loud,
Dim,
Debug,
}
impl Level {
fn hue(self) -> Option<CtColor> {
match self {
Self::Info => None,
Self::Ok => Some(CtColor::DarkGreen),
Self::Warn => Some(CtColor::DarkYellow),
Self::Loud => Some(CtColor::Red),
Self::Dim | Self::Debug => Some(CtColor::DarkGrey),
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Notice<'a> {
pub level: Level,
pub glyph: &'a str,
pub gap: usize,
pub text: Cow<'a, str>,
}
impl<'a> Notice<'a> {
pub fn new(level: Level, glyph: &'a str, text: impl Into<Cow<'a, str>>) -> Self {
Self {
level,
glyph,
gap: 1,
text: text.into(),
}
}
#[must_use]
pub fn gap(mut self, gap: usize) -> Self {
self.gap = gap;
self
}
pub fn line(&self) -> String {
if self.glyph.is_empty() {
return self.text.to_string();
}
format!("{}{}{}", self.glyph, " ".repeat(self.gap), self.text)
}
pub fn emit(&self, caps: LineCaps, sink: Sink, color: bool) {
if protocol_mode() || !caps.can_own() {
return;
}
let line = self.line();
let hue = if color { self.level.hue() } else { None };
Terminal::emit_line(sink, move |w| match hue {
Some(h) => queue!(
w,
SetForegroundColor(h),
Print(&line),
ResetColor,
Print("\n"),
),
None => writeln!(w, "{line}"),
});
}
}
#[cfg(test)]
mod tests {
use super::{Level, Notice};
#[test]
fn line_is_glyph_then_gap_then_text() {
assert_eq!(Notice::new(Level::Warn, "⚠", "boom").line(), "⚠ boom");
assert_eq!(Notice::new(Level::Ok, "✓", "done").gap(2).line(), "✓ done");
}
#[test]
fn an_empty_glyph_contributes_no_gap() {
assert_eq!(
Notice::new(Level::Warn, "", "⚠ already marked").line(),
"⚠ already marked"
);
}
#[test]
fn line_carries_no_ansi() {
for level in [
Level::Info,
Level::Ok,
Level::Warn,
Level::Loud,
Level::Dim,
Level::Debug,
] {
assert!(!Notice::new(level, "⚠", "text").line().contains('\u{1b}'));
}
}
#[test]
fn warn_is_the_single_amber() {
use crossterm::style::Color as CtColor;
assert_eq!(Level::Warn.hue(), Some(CtColor::DarkYellow));
assert_eq!(Level::Info.hue(), None, "narration keeps the default fg");
}
}