csskit_highlight/
ansi_highlight_cursor_stream.rs

1use crate::{AnsiTheme, SemanticDecoration, TokenHighlighter};
2use chromashift::{Named, WcagColorContrast};
3use css_lexer::{ToSpan, Token};
4use css_parse::{SourceCursor, SourceCursorSink};
5use std::fmt::{Result, Write};
6
7#[cfg(feature = "anstyle")]
8use anstyle::Style;
9#[cfg(feature = "owo-colors")]
10#[cfg(not(feature = "anstyle"))]
11use owo_colors::OwoColorize;
12
13pub struct AnsiHighlightCursorStream<'h, W: Write, T: AnsiTheme> {
14	writer: W,
15	theme: T,
16	highlighter: &'h TokenHighlighter,
17	last_token: Option<Token>,
18	err: Result,
19}
20
21impl<'h, W: Write, T: AnsiTheme> AnsiHighlightCursorStream<'h, W, T> {
22	pub fn new(writer: W, highlighter: &'h TokenHighlighter, theme: T) -> Self {
23		Self { writer, highlighter, theme, last_token: None, err: Ok(()) }
24	}
25}
26
27impl<'a, 'h, W: Write, T: AnsiTheme> SourceCursorSink<'a> for AnsiHighlightCursorStream<'h, W, T> {
28	fn append(&mut self, c: SourceCursor<'a>) {
29		if self.err.is_err() {
30			return;
31		}
32		if let Some(last) = self.last_token
33			&& last.needs_separator_for(c.token())
34		{
35			self.err = self.writer.write_char(' ');
36		}
37		self.last_token = Some(c.token());
38		if let Some(highlight) = self.highlighter.get(c.to_span()) {
39			if let SemanticDecoration::BackgroundColor(bg) = highlight.decoration() {
40				let fg = if bg.wcag_contrast_ratio(Named::White) > bg.wcag_contrast_ratio(Named::Black) {
41					Named::White
42				} else {
43					Named::Black
44				};
45				#[cfg(feature = "anstyle")]
46				{
47					let style = Style::new().bg_color(Some(bg.into())).fg_color(Some(fg.into()));
48					self.err = write!(&mut self.writer, "{style}{c}{style:#}");
49				}
50				#[cfg(feature = "owo-colors")]
51				#[cfg(not(feature = "anstyle"))]
52				{
53					use chromashift::Srgb;
54					let bg_srgb: Srgb = bg.into();
55					let fg_srgb: Srgb = fg.into();
56					self.err = write!(
57						&mut self.writer,
58						"{}",
59						c.to_string().truecolor(fg_srgb.red, fg_srgb.green, fg_srgb.blue).on_truecolor(
60							bg_srgb.red,
61							bg_srgb.green,
62							bg_srgb.blue
63						)
64					);
65				}
66			} else {
67				#[cfg(feature = "anstyle")]
68				{
69					let style = self.theme.get_anstyle(highlight.kind(), highlight.modifier());
70					self.err = write!(&mut self.writer, "{style}{c}{style:#}");
71				}
72				#[cfg(feature = "owo-colors")]
73				#[cfg(not(feature = "anstyle"))]
74				{
75					let style = self.theme.get_owo_style(highlight.kind(), highlight.modifier());
76					self.err = write!(&mut self.writer, "{}", style.style(c.to_string()));
77				}
78			}
79		} else {
80			self.err = write!(&mut self.writer, "{c}");
81		}
82	}
83}