1use std::convert::TryFrom;
7
8use proc_macro2::Span;
9
10use crate::error::{Error, SpanError};
11
12#[derive(Debug, PartialEq, Default)]
14pub struct Context<'a>(Vec<ColorTag<'a>>);
15
16impl<'a> Context<'a> {
17 pub fn new() -> Self {
18 Self::default()
19 }
20
21 #[cfg(feature = "terminfo")]
29 pub fn terminfo_apply_tags(
30 &mut self,
31 tag_group: Vec<ColorTag<'a>>,
32 ) -> Result<Vec<String>, SpanError> {
33 let state_diff = self.apply_tags_and_get_diff(tag_group)?;
34 Ok(state_diff.terminfo_token_streams())
35 }
36
37 #[cfg(not(feature = "terminfo"))]
44 pub fn ansi_apply_tags(&mut self, tag_group: Vec<ColorTag<'a>>) -> Result<String, SpanError> {
45 let state_diff = self.apply_tags_and_get_diff(tag_group)?;
46 Ok(state_diff.ansi_string())
47 }
48
49 pub fn apply_tags(&mut self, tag_group: Vec<ColorTag<'a>>) -> Result<(), SpanError> {
56 self.apply_tags_and_get_diff(tag_group).map(|_| ())
57 }
58
59 pub fn state(&self) -> State {
62 let mut state = State::default();
63 for tag in &self.0 {
64 if let Some(ref color) = tag.change_set.foreground {
65 state.foreground = ExtColor::Color(color.clone());
66 }
67 if let Some(ref color) = tag.change_set.background {
68 state.background = ExtColor::Color(color.clone());
69 }
70 state.bold |= tag.change_set.bold;
71 state.dim |= tag.change_set.dim;
72 state.underline |= tag.change_set.underline;
73 state.italics |= tag.change_set.italics;
74 state.blink |= tag.change_set.blink;
75 state.strike |= tag.change_set.strike;
76 state.reverse |= tag.change_set.reverse;
77 state.conceal |= tag.change_set.conceal;
78 }
79 state
80 }
81
82 #[allow(rustdoc::broken_intra_doc_links)]
83 fn apply_tags_and_get_diff(&mut self, tags: Vec<ColorTag<'a>>) -> Result<StateDiff, SpanError> {
85 let old_state = self.state();
86
87 for tag in tags {
88 if tag.is_close {
89 let last_tag = self.0.last()
90 .ok_or_else(|| SpanError::new(Error::NoTagToClose, tag.span))?;
91 if !tag.change_set.is_void() && last_tag.change_set != tag.change_set {
94 let (last_src, src) = (
95 last_tag.source.unwrap(),
99 tag.source.unwrap(),
103 );
104 return Err(SpanError::new(
105 Error::MismatchCloseTag(last_src.to_owned(), src.to_owned()),
106 tag.span,
107 ));
108 }
109 self.0.pop().unwrap();
110 } else {
111 self.0.push(tag);
112 }
113 }
114
115 let new_state = self.state();
116 Ok(StateDiff::from_diff(&old_state, &new_state))
117 }
118}
119
120#[derive(Debug, PartialEq, Default)]
123pub struct State {
124 foreground: ExtColor,
125 background: ExtColor,
126 bold: bool,
127 dim: bool,
128 underline: bool,
129 italics: bool,
130 blink: bool,
131 strike: bool,
132 reverse: bool,
133 conceal: bool,
134}
135
136#[derive(Debug)]
141pub struct StateDiff {
142 foreground: Action<ExtColor>,
143 background: Action<ExtColor>,
144 bold: Action<bool>,
145 dim: Action<bool>,
146 underline: Action<bool>,
147 italics: Action<bool>,
148 blink: Action<bool>,
149 #[cfg(not(feature = "terminfo"))]
150 strike: Action<bool>,
151 reverse: Action<bool>,
152 #[cfg(not(feature = "terminfo"))]
153 conceal: Action<bool>,
154}
155
156impl StateDiff {
157 pub fn from_diff(old: &State, new: &State) -> Self {
159 StateDiff {
160 foreground: Action::from_diff(Some(old.foreground.clone()), Some(new.foreground.clone())),
161 background: Action::from_diff(Some(old.background.clone()), Some(new.background.clone())),
162 bold: Action::from_diff(Some(old.bold), Some(new.bold)),
163 dim: Action::from_diff(Some(old.dim), Some(new.dim)),
164 underline: Action::from_diff(Some(old.underline), Some(new.underline)),
165 italics: Action::from_diff(Some(old.italics), Some(new.italics)),
166 blink: Action::from_diff(Some(old.blink), Some(new.blink)),
167 #[cfg(not(feature = "terminfo"))]
168 strike: Action::from_diff(Some(old.strike), Some(new.strike)),
169 reverse: Action::from_diff(Some(old.reverse), Some(new.reverse)),
170 #[cfg(not(feature = "terminfo"))]
171 conceal: Action::from_diff(Some(old.conceal), Some(new.conceal)),
172 }
173 }
174
175 #[cfg(feature = "terminfo")]
178 pub fn terminfo_token_streams(&self) -> Vec<String> {
179 let mut constants = vec![];
180
181 macro_rules! push_constant {
182 ($s:expr) => {{
183 constants.push($s.to_owned());
184 }};
185 }
186
187 let have_to_reset = or!(
188 matches!(self.foreground, Action::Change(ExtColor::Normal)),
189 matches!(self.background, Action::Change(ExtColor::Normal)),
190 matches!(self.bold, Action::Change(false)),
191 matches!(self.dim, Action::Change(false)),
192 matches!(self.blink, Action::Change(false)),
193 matches!(self.reverse, Action::Change(false)),
194 );
195
196 if have_to_reset {
197 push_constant!("CLEAR");
198 if let Some(ExtColor::Color(Color::Color16(color))) = self.foreground.actual_value() {
199 push_constant!(color.terminfo_constant(true));
200 }
201 if let Some(ExtColor::Color(Color::Color16(color))) = self.background.actual_value() {
202 push_constant!(color.terminfo_constant(false));
203 }
204 if matches!(self.bold.actual_value(), Some(true)) {
205 push_constant!("BOLD");
206 }
207 if matches!(self.dim.actual_value(), Some(true)) {
208 push_constant!("DIM");
209 }
210 if matches!(self.blink.actual_value(), Some(true)) {
211 push_constant!("BLINK");
212 }
213 if matches!(self.underline.actual_value(), Some(true)) {
214 push_constant!("UNDERLINE");
215 }
216 if matches!(self.italics.actual_value(), Some(true)) {
217 push_constant!("ITALICS");
218 }
219 if matches!(self.reverse.actual_value(), Some(true)) {
220 push_constant!("REVERSE");
221 }
222 } else {
223 if let Action::Change(ExtColor::Color(Color::Color16(ref color))) = self.foreground {
224 push_constant!(color.terminfo_constant(true));
225 }
226 if let Action::Change(ExtColor::Color(Color::Color16(ref color))) = self.background {
227 push_constant!(color.terminfo_constant(false));
228 }
229 if let Action::Change(true) = self.bold {
230 push_constant!("BOLD");
231 }
232 if let Action::Change(true) = self.dim {
233 push_constant!("DIM");
234 }
235 if let Action::Change(true) = self.blink {
236 push_constant!("BLINK");
237 }
238 if let Action::Change(true) = self.reverse {
239 push_constant!("REVERSE");
240 }
241 if let Action::Change(underline) = self.underline {
242 let constant = if underline { "UNDERLINE" } else { "NO_UNDERLINE" };
243 push_constant!(constant);
244 }
245 if let Action::Change(italics) = self.italics {
246 let constant = if italics { "ITALICS" } else { "NO_ITALICS" };
247 push_constant!(constant);
248 }
249 }
250
251 constants
252 }
253
254 #[cfg(not(feature = "terminfo"))]
257 pub fn ansi_string(&self) -> String {
258 use crate::ansi_constants::*;
259
260 let mut output = String::new();
261
262 macro_rules! push_code {
263 ($($codes:expr),*) => { output.push_str(&generate_ansi_code(&[$($codes),*])) };
264 }
265
266 if let Action::Change(ref ext_color) = self.foreground {
267 match ext_color {
268 ExtColor::Normal => push_code!(DEFAULT_FOREGROUND),
269 ExtColor::Color(Color::Color16(color)) => match color.intensity {
270 Intensity::Normal => {
271 push_code!(SET_FOREGROUND_BASE + color.base_color.index())
272 }
273 Intensity::Bright => {
274 push_code!(SET_BRIGHT_FOREGROUND_BASE + color.base_color.index())
275 }
276 },
277 ExtColor::Color(Color::Color256(color)) => {
278 push_code!(SET_FOREGROUND, 5, color.0);
279 },
280 ExtColor::Color(Color::ColorRgb(color)) => {
281 push_code!(SET_FOREGROUND, 2, color.r, color.g, color.b);
282 },
283 }
284 }
285
286 if let Action::Change(ref ext_color) = self.background {
287 match ext_color {
288 ExtColor::Normal => push_code!(DEFAULT_BACKGROUND),
289 ExtColor::Color(Color::Color16(color)) => match color.intensity {
290 Intensity::Normal => {
291 push_code!(SET_BACKGROUND_BASE + color.base_color.index())
292 }
293 Intensity::Bright => {
294 push_code!(SET_BRIGHT_BACKGROUND_BASE + color.base_color.index())
295 }
296 },
297 ExtColor::Color(Color::Color256(color)) => {
298 push_code!(SET_BACKGROUND, 5, color.0);
299 },
300 ExtColor::Color(Color::ColorRgb(color)) => {
301 push_code!(SET_BACKGROUND, 2, color.r, color.g, color.b);
302 },
303 }
304 }
305
306 macro_rules! handle_attr {
307 ($attr:expr, $true_val:expr, $false_val:expr) => {
308 match $attr {
309 Action::Change(true) => push_code!($true_val),
310 Action::Change(false) => push_code!($false_val),
311 _ => (),
312 }
313 };
314 }
315
316 handle_attr!(self.bold, BOLD, NO_BOLD);
317 handle_attr!(self.dim, DIM, NO_BOLD);
318 handle_attr!(self.underline, UNDERLINE, NO_UNDERLINE);
319 handle_attr!(self.italics, ITALIC, NO_ITALIC);
320 handle_attr!(self.blink, BLINK, NO_BLINK);
321 handle_attr!(self.strike, STRIKE, NO_STRIKE);
322 handle_attr!(self.reverse, REVERSE, NO_REVERSE);
323 handle_attr!(self.conceal, CONCEAL, NO_CONCEAL);
324
325 output
326 }
327}
328
329#[derive(Debug, PartialEq)]
331pub enum Action<T> {
332 None,
334 Keep(T),
338 Change(T),
340}
341
342#[cfg(feature = "terminfo")]
343impl<T> Action<T> {
344 pub fn actual_value(&self) -> Option<&T> {
345 match self {
346 Action::Keep(val) | Action::Change(val) => Some(val),
347 Action::None => None,
348 }
349 }
350}
351
352impl<T> Action<T>
353where
354 T: PartialEq,
355{
356 pub fn from_diff(old: Option<T>, new: Option<T>) -> Self {
358 let eq = old == new;
359 match (old, new, eq) {
360 (Some(old_val), Some(_), true) | (Some(old_val), None, _) => Action::Keep(old_val),
361 (_, Some(new_val), _) => Action::Change(new_val),
362 _ => Action::None,
363 }
364 }
365}
366
367#[derive(Debug, Default)]
369pub struct ColorTag<'a> {
370 pub source: Option<&'a str>,
372 pub span: Option<Span>,
374 pub is_close: bool,
376 pub change_set: ChangeSet,
378}
379
380impl<'a> PartialEq for ColorTag<'a> {
381 fn eq(&self, other: &ColorTag<'a>) -> bool {
382 and!(
383 self.source == other.source,
384 self.is_close == other.is_close,
385 self.change_set == other.change_set,
386 )
387 }
388}
389
390impl<'a> ColorTag<'a> {
391 pub fn new_close() -> Self {
394 ColorTag {
395 source: None,
396 span: None,
397 is_close: true,
398 change_set: ChangeSet::default(),
399 }
400 }
401
402 pub fn set_span(&mut self, span: Span) {
404 self.span = Some(span);
405 }
406}
407
408#[derive(Debug, PartialEq, Default)]
410pub struct ChangeSet {
411 pub foreground: Option<Color>,
413 pub background: Option<Color>,
415 pub bold: bool,
417 pub dim: bool,
419 pub underline: bool,
421 pub italics: bool,
423 pub blink: bool,
425 pub strike: bool,
427 pub reverse: bool,
429 pub conceal: bool,
431}
432
433impl ChangeSet {
434 pub fn is_void(&self) -> bool {
436 and!(
437 self.foreground.is_none(),
438 self.background.is_none(),
439 !self.bold,
440 !self.dim,
441 !self.underline,
442 !self.italics,
443 !self.blink,
444 !self.strike,
445 !self.reverse,
446 !self.conceal,
447 )
448 }
449}
450
451impl From<&[Change]> for ChangeSet {
452 fn from(changes: &[Change]) -> ChangeSet {
453 let mut change_set = ChangeSet::default();
454 for change in changes {
455 match change {
456 Change::Foreground(color) => change_set.foreground = Some(color.clone()),
457 Change::Background(color) => change_set.background = Some(color.clone()),
458 Change::Bold => change_set.bold = true,
459 Change::Dim => change_set.dim = true,
460 Change::Underline => change_set.underline = true,
461 Change::Italics => change_set.italics = true,
462 Change::Blink => change_set.blink = true,
463 Change::Strike => change_set.strike = true,
464 Change::Reverse => change_set.reverse = true,
465 Change::Conceal => change_set.conceal = true,
466 }
467 }
468 change_set
469 }
470}
471
472#[derive(Debug, PartialEq, Clone)]
475pub enum Change {
476 Foreground(Color),
477 Background(Color),
478 Bold,
479 Dim,
480 Underline,
481 Italics,
482 Blink,
483 Strike,
484 Reverse,
485 Conceal,
486}
487
488impl TryFrom<&str> for Change {
489 type Error = ();
490
491 #[rustfmt::skip]
493 fn try_from(input: &str) -> Result<Self, Self::Error> {
494 macro_rules! color16 {
495 ($kind:ident $intensity:ident $base_color:ident) => {
496 Change::$kind(Color::Color16(Color16::new(
497 BaseColor::$base_color,
498 Intensity::$intensity,
499 )))
500 };
501 }
502
503 let change = match input {
504 "s" | "strong" | "bold" | "em" => Change::Bold,
505 "dim" => Change::Dim,
506 "u" | "underline" => Change::Underline,
507 "i" | "italic" | "italics" => Change::Italics,
508 "blink" => Change::Blink,
509 "strike" => Change::Strike,
510 "reverse" | "rev" => Change::Reverse,
511 "conceal" | "hide" => Change::Conceal,
512
513 "k" | "black" => color16!(Foreground Normal Black),
514 "r" | "red" => color16!(Foreground Normal Red),
515 "g" | "green" => color16!(Foreground Normal Green),
516 "y" | "yellow" => color16!(Foreground Normal Yellow),
517 "b" | "blue" => color16!(Foreground Normal Blue),
518 "m" | "magenta" => color16!(Foreground Normal Magenta),
519 "c" | "cyan" => color16!(Foreground Normal Cyan),
520 "w" | "white" => color16!(Foreground Normal White),
521
522 "k!" | "black!" | "bright-black" => color16!(Foreground Bright Black),
523 "r!" | "red!" | "bright-red" => color16!(Foreground Bright Red),
524 "g!" | "green!" | "bright-green" => color16!(Foreground Bright Green),
525 "y!" | "yellow!" | "bright-yellow" => color16!(Foreground Bright Yellow),
526 "b!" | "blue!" | "bright-blue" => color16!(Foreground Bright Blue),
527 "m!" | "magenta!" | "bright-magenta" => color16!(Foreground Bright Magenta),
528 "c!" | "cyan!" | "bright-cyan" => color16!(Foreground Bright Cyan),
529 "w!" | "white!" | "bright-white" => color16!(Foreground Bright White),
530
531 "K" | "bg-black" => color16!(Background Normal Black),
532 "R" | "bg-red" => color16!(Background Normal Red),
533 "G" | "bg-green" => color16!(Background Normal Green),
534 "Y" | "bg-yellow" => color16!(Background Normal Yellow),
535 "B" | "bg-blue" => color16!(Background Normal Blue),
536 "M" | "bg-magenta" => color16!(Background Normal Magenta),
537 "C" | "bg-cyan" => color16!(Background Normal Cyan),
538 "W" | "bg-white" => color16!(Background Normal White),
539
540 "K!" | "bg-black!" | "bg-bright-black" => color16!(Background Bright Black),
541 "R!" | "bg-red!" | "bg-bright-red" => color16!(Background Bright Red),
542 "G!" | "bg-green!" | "bg-bright-green" => color16!(Background Bright Green),
543 "Y!" | "bg-yellow!" | "bg-bright-yellow" => color16!(Background Bright Yellow),
544 "B!" | "bg-blue!" | "bg-bright-blue" => color16!(Background Bright Blue),
545 "M!" | "bg-magenta!" | "bg-bright-magenta" => color16!(Background Bright Magenta),
546 "C!" | "bg-cyan!" | "bg-bright-cyan" => color16!(Background Bright Cyan),
547 "W!" | "bg-white!" | "bg-bright-white" => color16!(Background Bright White),
548
549 _ => return Err(()),
550 };
551
552 Ok(change)
553 }
554}
555
556#[derive(Debug, PartialEq, Clone)]
558pub enum ColorKind {
559 Background,
560 Foreground,
561}
562
563impl ColorKind {
564 pub fn to_change(&self, color: Color) -> Change {
565 match self {
566 Self::Foreground => Change::Foreground(color),
567 Self::Background => Change::Background(color),
568 }
569 }
570}
571
572#[derive(Debug, PartialEq, Clone)]
574pub enum ExtColor {
575 Normal,
576 Color(Color),
577}
578
579impl Default for ExtColor {
580 fn default() -> Self {
581 Self::Normal
582 }
583}
584
585#[derive(Debug, PartialEq, Clone)]
586#[allow(clippy::enum_variant_names)]
587pub enum Color {
588 Color16(Color16),
589 Color256(Color256),
590 ColorRgb(ColorRgb),
591}
592
593#[derive(Debug, PartialEq, Clone)]
595pub struct Color16 {
596 base_color: BaseColor,
597 intensity: Intensity,
598}
599
600impl Color16 {
601 pub fn new(base_color: BaseColor, intensity: Intensity) -> Self {
602 Self { base_color, intensity }
603 }
604
605 #[cfg(feature = "terminfo")]
607 pub fn terminfo_constant(&self, is_foreground: bool) -> String {
608 let mut constant = if is_foreground {
609 String::new()
610 } else {
611 "BG_".to_string()
612 };
613
614 if matches!(self.intensity, Intensity::Bright) {
615 constant.push_str("BRIGHT_");
616 }
617
618 constant.push_str(self.base_color.uppercase_str());
619
620 constant
621 }
622}
623
624#[derive(Debug, PartialEq, Copy, Clone)]
626pub enum Intensity {
627 Normal,
628 Bright,
629}
630
631impl Intensity {
632 pub fn new(is_bright: bool) -> Self {
633 if is_bright {
634 Self::Bright
635 } else {
636 Self::Normal
637 }
638 }
639}
640
641#[derive(Debug, PartialEq, Copy, Clone)]
644pub enum BaseColor {
645 Black,
646 Red,
647 Green,
648 Yellow,
649 Blue,
650 Magenta,
651 Cyan,
652 White,
653}
654
655impl BaseColor {
656 #[cfg(not(feature = "terminfo"))]
658 pub fn index(&self) -> u8 {
659 match self {
660 Self::Black => 0,
661 Self::Red => 1,
662 Self::Green => 2,
663 Self::Yellow => 3,
664 Self::Blue => 4,
665 Self::Magenta => 5,
666 Self::Cyan => 6,
667 Self::White => 7,
668 }
669 }
670
671 #[cfg(feature = "terminfo")]
673 pub fn uppercase_str(&self) -> &'static str {
674 match self {
675 Self::Black => "BLACK",
676 Self::Red => "RED",
677 Self::Green => "GREEN",
678 Self::Yellow => "YELLOW",
679 Self::Blue => "BLUE",
680 Self::Magenta => "MAGENTA",
681 Self::Cyan => "CYAN",
682 Self::White => "WHITE",
683 }
684 }
685}
686
687#[derive(Debug, PartialEq, Clone)]
689pub struct Color256(pub u8);
690
691#[derive(Debug, PartialEq, Clone)]
693pub struct ColorRgb {
694 pub r: u8,
695 pub g: u8,
696 pub b: u8,
697}
698
699#[cfg(test)]
700mod tests {
701 #[cfg(feature = "terminfo")]
702 use super::*;
703 #[cfg(feature = "terminfo")]
704 use crate::parse::color_tag;
705
706 #[test]
707 #[cfg(feature = "terminfo")]
708 fn terminfo_apply_tag_to_context() {
709 let mut context = Context::new();
710
711 macro_rules! apply_tag {
712 ($s:expr) => {
713 context
714 .terminfo_apply_tags(vec![color_tag($s).unwrap().1])
715 .unwrap()
716 };
717 }
718
719 let constants = apply_tag!("<r>");
720 assert_eq!(constants, ["RED"]);
721 let constants = apply_tag!("</r>");
722 assert_eq!(constants, ["CLEAR"]);
723 let constants = apply_tag!("<r>");
724 assert_eq!(constants, ["RED"]);
725 let constants = apply_tag!("<s>");
726 assert_eq!(constants, ["BOLD"]);
727 let constants = apply_tag!("</s>");
728 assert_eq!(constants, ["CLEAR", "RED"]);
729 let constants = apply_tag!("</r>");
730 assert_eq!(constants, ["CLEAR"]);
731 }
732
733 #[test]
734 #[cfg(feature = "terminfo")]
735 fn terminfo_apply_tag_to_context_2() {
736 let mut context = Context::new();
737
738 macro_rules! apply_tag {
739 ($s:expr) => {
740 context
741 .terminfo_apply_tags(vec![color_tag($s).unwrap().1])
742 .unwrap()
743 };
744 }
745
746 let constants = apply_tag!("<r>");
747 assert_eq!(constants, ["RED"]);
748 let constants = apply_tag!("<Y>");
749 assert_eq!(constants, ["BG_YELLOW"]);
750 let constants = apply_tag!("<s>");
751 assert_eq!(constants, ["BOLD"]);
752 let constants = apply_tag!("<u>");
753 assert_eq!(constants, ["UNDERLINE"]);
754 let constants = apply_tag!("</u>");
755 assert_eq!(constants, ["NO_UNDERLINE"]);
756 let constants = apply_tag!("</s>");
757 assert_eq!(constants, ["CLEAR", "RED", "BG_YELLOW"]);
758 }
759
760 #[test]
761 #[cfg(feature = "terminfo")]
762 fn terminfo_apply_tag_to_context_3() {
763 let mut context = Context::new();
764
765 macro_rules! apply_tag {
766 ($s:expr) => {
767 context.terminfo_apply_tags(vec![color_tag($s).unwrap().1])
768 };
769 }
770
771 let res = apply_tag!("</r>");
772 assert_eq!(res, Err(SpanError::new(Error::NoTagToClose, None)));
773 apply_tag!("<r>").unwrap();
774 let res = apply_tag!("</s>");
775 assert_eq!(
776 res,
777 Err(SpanError::new(
778 Error::MismatchCloseTag("<r>".to_owned(), "</s>".to_owned()),
779 None
780 ))
781 );
782 }
783}