Skip to main content

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_red, bg_green, bg_blue] = Srgb::from(bg).to_u8();
55					let [fg_red, fg_green, fg_blue] = Srgb::from(fg).to_u8();
56					self.err = write!(
57						&mut self.writer,
58						"{}",
59						c.to_string().truecolor(fg_red, fg_green, fg_blue).on_truecolor(bg_red, bg_green, bg_blue)
60					);
61				}
62			} else {
63				#[cfg(feature = "anstyle")]
64				{
65					let style = self.theme.get_anstyle(highlight.kind(), highlight.modifier());
66					self.err = write!(&mut self.writer, "{style}{c}{style:#}");
67				}
68				#[cfg(feature = "owo-colors")]
69				#[cfg(not(feature = "anstyle"))]
70				{
71					let style = self.theme.get_owo_style(highlight.kind(), highlight.modifier());
72					self.err = write!(&mut self.writer, "{}", style.style(c.to_string()));
73				}
74			}
75		} else {
76			self.err = write!(&mut self.writer, "{c}");
77		}
78	}
79}