zng_wgt_ansi_text/
lib.rs

1#![doc(html_favicon_url = "https://zng-ui.github.io/res/zng-logo-icon.png")]
2#![doc(html_logo_url = "https://zng-ui.github.io/res/zng-logo.png")]
3//!
4//! ANSI text widget, properties and nodes.
5//!
6//! # Crate
7//!
8#![doc = include_str!(concat!("../", std::env!("CARGO_PKG_README")))]
9#![warn(unused_extern_crates)]
10#![warn(missing_docs)]
11
12zng_wgt::enable_widget_macros!();
13
14use zng_ext_font::*;
15use zng_wgt::{prelude::*, *};
16use zng_wgt_fill::*;
17use zng_wgt_filter::*;
18use zng_wgt_input::{CursorIcon, cursor};
19use zng_wgt_scroll::{LazyMode, lazy};
20use zng_wgt_stack::{Stack, StackDirection};
21use zng_wgt_text::*;
22
23#[doc(hidden)]
24pub use zng_wgt_text::__formatx;
25
26/// Render text styled using ANSI escape sequences.
27///
28/// Supports color, weight, italic and more, see [`AnsiStyle`] for the full style supported.
29#[widget($crate::AnsiText {
30    ($txt:literal) => {
31        txt = $crate::__formatx!($txt);
32    };
33    ($txt:expr) => {
34        txt = $txt;
35    };
36    ($txt:tt, $($format:tt)*) => {
37        txt = $crate::__formatx!($txt, $($format)*);
38    };
39})]
40#[rustfmt::skip]
41pub struct AnsiText(
42    FontMix<
43    TextSpacingMix<
44    ParagraphMix<
45    LangMix<
46    WidgetBase
47    >>>>
48);
49impl AnsiText {
50    fn widget_intrinsic(&mut self) {
51        widget_set! {
52            self;
53            font_family = ["JetBrains Mono", "Consolas", "monospace"];
54            rich_text = true;
55
56            when #txt_selectable {
57                cursor = CursorIcon::Text;
58            }
59        };
60
61        self.widget_builder().push_build_action(|wgt| {
62            let txt = wgt.capture_var_or_default(property_id!(txt));
63            let child = ansi_node(txt);
64            wgt.set_child(child);
65        });
66    }
67
68    widget_impl! {
69        /// ANSI text.
70        pub txt(text: impl IntoVar<Txt>);
71
72        /// Enable text selection, copy.
73        ///
74        /// Note that the copy is only in plain text, without the ANSI escape codes.
75        pub zng_wgt_text::txt_selectable(enabled: impl IntoVar<bool>);
76    }
77}
78
79pub use ansi_parse::*;
80mod ansi_parse {
81
82    use super::*;
83
84    /// Represents a segment of ANSI styled text that shares the same style.
85    #[derive(Debug)]
86    #[non_exhaustive]
87    pub struct AnsiTxt<'a> {
88        /// Text run.
89        pub txt: &'a str,
90        /// Text style.
91        pub style: AnsiStyle,
92    }
93
94    /// Represents the ANSI style of a text run.
95    ///
96    /// See [`AnsiText`](struct@super::AnsiText) for more details.
97    #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
98    #[non_exhaustive]
99    pub struct AnsiStyle {
100        /// Background color.
101        pub background_color: AnsiColor,
102        /// Font color.
103        pub color: AnsiColor,
104        /// Font weight.
105        pub weight: AnsiWeight,
106        /// Font italic.
107        pub italic: bool,
108        /// Underline.
109        pub underline: bool,
110        /// Strikethrough.
111        pub strikethrough: bool,
112        /// Negative color.
113        pub invert_color: bool,
114        /// Visibility.
115        pub hidden: bool,
116        /// Blink animation.
117        pub blink: bool,
118    }
119    impl Default for AnsiStyle {
120        fn default() -> Self {
121            Self {
122                background_color: AnsiColor::Black,
123                color: AnsiColor::White,
124                weight: Default::default(),
125                italic: false,
126                underline: false,
127                strikethrough: false,
128                invert_color: false,
129                hidden: false,
130                blink: false,
131            }
132        }
133    }
134
135    /// Named ANSI color.
136    ///
137    /// See [`AnsiStyle`] for more details.
138    #[allow(missing_docs)]
139    #[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
140    pub enum AnsiColor {
141        Black,
142        Red,
143        Green,
144        Yellow,
145        Blue,
146        Magenta,
147        Cyan,
148        White,
149        /// Gray
150        BrightBlack,
151        BrightRed,
152        BrightGreen,
153        BrightYellow,
154        BrightBlue,
155        BrightMagenta,
156        BrightCyan,
157        BrightWhite,
158        /// 8-bit lookup.
159        Ansi256(u8),
160        /// RGB
161        TrueColor(u8, u8, u8),
162    }
163    impl_from_and_into_var! {
164        fn from(color: AnsiColor) -> Rgba {
165            match color {
166                AnsiColor::Black => rgb(0, 0, 0),
167                AnsiColor::Red => rgb(205, 49, 49),
168                AnsiColor::Green => rgb(13, 188, 121),
169                AnsiColor::Yellow => rgb(229, 229, 16),
170                AnsiColor::Blue => rgb(36, 114, 200),
171                AnsiColor::Magenta => rgb(188, 63, 188),
172                AnsiColor::Cyan => rgb(17, 168, 205),
173                AnsiColor::White => rgb(229, 229, 229),
174                AnsiColor::BrightBlack => rgb(102, 102, 102),
175                AnsiColor::BrightRed => rgb(241, 76, 76),
176                AnsiColor::BrightGreen => rgb(35, 209, 139),
177                AnsiColor::BrightYellow => rgb(245, 245, 67),
178                AnsiColor::BrightBlue => rgb(59, 142, 234),
179                AnsiColor::BrightMagenta => rgb(214, 112, 214),
180                AnsiColor::BrightCyan => rgb(41, 184, 219),
181                AnsiColor::BrightWhite => rgb(229, 229, 229),
182                AnsiColor::Ansi256(c) => {
183                    let (r, g, b) = X_TERM_256[c as usize];
184                    rgb(r, g, b)
185                }
186                AnsiColor::TrueColor(r, g, b) => rgb(r, g, b),
187            }
188        }
189    }
190
191    /// Font weight defined by ANSI escape codes.
192    ///
193    /// See [`AnsiStyle`] for more details.
194    #[derive(Debug, Clone, Copy, PartialEq, Eq, Default, serde::Serialize, serde::Deserialize)]
195    pub enum AnsiWeight {
196        /// Normal.
197        #[default]
198        Normal,
199        /// Bold.
200        Bold,
201        /// Light.
202        Faint,
203    }
204    impl_from_and_into_var! {
205        fn from(weight: AnsiWeight) -> FontWeight {
206            match weight {
207                AnsiWeight::Normal => FontWeight::NORMAL,
208                AnsiWeight::Bold => FontWeight::BOLD,
209                AnsiWeight::Faint => FontWeight::LIGHT,
210            }
211        }
212    }
213
214    /// Iterator that parses ANSI escape codes.
215    ///
216    /// This is the pull style parser used internally by the [`AnsiText!`] widget.
217    ///
218    /// [`AnsiText!`]: struct@crate::AnsiText
219    pub struct AnsiTextParser<'a> {
220        source: &'a str,
221        /// Current style.
222        pub style: AnsiStyle,
223    }
224    impl<'a> AnsiTextParser<'a> {
225        /// New parsing iterator.
226        pub fn new(source: &'a str) -> Self {
227            Self {
228                source,
229                style: AnsiStyle::default(),
230            }
231        }
232    }
233    impl<'a> Iterator for AnsiTextParser<'a> {
234        type Item = AnsiTxt<'a>;
235
236        fn next(&mut self) -> Option<Self::Item> {
237            const CSI: &str = "\x1b[";
238
239            fn is_esc_end(byte: u8) -> bool {
240                (0x40..=0x7e).contains(&byte)
241            }
242
243            loop {
244                if self.source.is_empty() {
245                    return None;
246                } else if let Some(source) = self.source.strip_prefix(CSI) {
247                    let mut esc_end = 0;
248                    while esc_end < source.len() && !is_esc_end(source.as_bytes()[esc_end]) {
249                        esc_end += 1;
250                    }
251                    esc_end += 1;
252
253                    let (esc, source) = source.split_at(esc_end);
254
255                    let esc = &esc[..(esc.len() - 1)];
256                    self.style.set(esc);
257
258                    self.source = source;
259                    continue;
260                } else if let Some(i) = self.source.find(CSI) {
261                    let (txt, source) = self.source.split_at(i);
262                    self.source = source;
263                    return Some(AnsiTxt {
264                        txt,
265                        style: self.style.clone(),
266                    });
267                } else {
268                    return Some(AnsiTxt {
269                        txt: std::mem::take(&mut self.source),
270                        style: self.style.clone(),
271                    });
272                }
273            }
274        }
275    }
276
277    impl AnsiStyle {
278        fn set(&mut self, esc_codes: &str) {
279            let mut esc_codes = esc_codes.split(';');
280            while let Some(code) = esc_codes.next() {
281                match code {
282                    "0" => *self = Self::default(),
283                    "1" => self.weight = AnsiWeight::Bold,
284                    "2" => self.weight = AnsiWeight::Faint,
285                    "3" => self.italic = true,
286                    "4" => self.underline = true,
287                    "5" => self.blink = true,
288                    "7" => self.invert_color = true,
289                    "8" => self.hidden = true,
290                    "9" => self.strikethrough = true,
291                    "22" => self.weight = AnsiWeight::Normal,
292                    "23" => self.italic = false,
293                    "24" => self.underline = false,
294                    "25" => self.blink = false,
295                    "27" => self.invert_color = false,
296                    "28" => self.hidden = false,
297                    "29" => self.strikethrough = false,
298                    "30" => self.color = AnsiColor::Black,
299                    "31" => self.color = AnsiColor::Red,
300                    "32" => self.color = AnsiColor::Green,
301                    "33" => self.color = AnsiColor::Yellow,
302                    "34" => self.color = AnsiColor::Blue,
303                    "35" => self.color = AnsiColor::Magenta,
304                    "36" => self.color = AnsiColor::Cyan,
305                    "37" => self.color = AnsiColor::White,
306                    "40" => self.color = AnsiColor::Black,
307                    "41" => self.color = AnsiColor::Red,
308                    "42" => self.color = AnsiColor::Green,
309                    "43" => self.color = AnsiColor::Yellow,
310                    "44" => self.color = AnsiColor::Blue,
311                    "45" => self.color = AnsiColor::Magenta,
312                    "46" => self.color = AnsiColor::Cyan,
313                    "47" => self.color = AnsiColor::White,
314                    "90" => self.color = AnsiColor::BrightBlack,
315                    "91" => self.color = AnsiColor::BrightRed,
316                    "92" => self.color = AnsiColor::BrightGreen,
317                    "93" => self.color = AnsiColor::BrightYellow,
318                    "94" => self.color = AnsiColor::BrightBlue,
319                    "95" => self.color = AnsiColor::BrightMagenta,
320                    "96" => self.color = AnsiColor::BrightCyan,
321                    "97" => self.color = AnsiColor::BrightWhite,
322                    "100" => self.background_color = AnsiColor::BrightBlack,
323                    "101" => self.background_color = AnsiColor::BrightRed,
324                    "102" => self.background_color = AnsiColor::BrightGreen,
325                    "103" => self.background_color = AnsiColor::BrightYellow,
326                    "104" => self.background_color = AnsiColor::BrightBlue,
327                    "105" => self.background_color = AnsiColor::BrightMagenta,
328                    "106" => self.background_color = AnsiColor::BrightCyan,
329                    "107" => self.background_color = AnsiColor::BrightWhite,
330                    "38" | "48" => {
331                        let target = if code == "38" {
332                            &mut self.color
333                        } else {
334                            &mut self.background_color
335                        };
336                        match esc_codes.next() {
337                            Some("5") => {
338                                let c = esc_codes.next().and_then(|c| c.parse().ok()).unwrap_or(0);
339                                *target = AnsiColor::Ansi256(c)
340                            }
341                            Some("2") => {
342                                let r = esc_codes.next().and_then(|c| c.parse().ok()).unwrap_or(0);
343                                let g = esc_codes.next().and_then(|c| c.parse().ok()).unwrap_or(0);
344                                let b = esc_codes.next().and_then(|c| c.parse().ok()).unwrap_or(0);
345
346                                *target = AnsiColor::TrueColor(r, g, b);
347                            }
348                            _ => {}
349                        }
350                    }
351                    _ => (),
352                }
353            }
354        }
355    }
356}
357
358pub use ansi_fn::*;
359mod ansi_fn {
360    use std::time::Duration;
361
362    use super::{AnsiColor, AnsiStyle, AnsiWeight};
363
364    use super::*;
365
366    /// Arguments for a widget function for an ANSI styled text fragment.
367    ///
368    /// See [`TEXT_FN_VAR`] for more details.
369    #[non_exhaustive]
370    pub struct TextFnArgs {
371        /// The text.
372        pub txt: Txt,
373        /// The ANSI style.
374        pub style: AnsiStyle,
375    }
376    impl TextFnArgs {
377        /// New from text and style.
378        pub fn new(txt: impl Into<Txt>, style: AnsiStyle) -> Self {
379            Self { txt: txt.into(), style }
380        }
381    }
382
383    /// Arguments for a widget function for a text line.
384    ///
385    /// See [`LINE_FN_VAR`] for more details.
386    #[non_exhaustive]
387    pub struct LineFnArgs {
388        /// Zero-counted global index of this line.
389        pub index: u32,
390        /// Zero-counted index of this line in the parent page.
391        pub page_index: u32,
392        /// Text segment widgets, generated by [`TEXT_FN_VAR`].
393        pub text: UiVec,
394    }
395
396    impl LineFnArgs {
397        /// New args.
398        pub fn new(index: u32, page_index: u32, text: UiVec) -> Self {
399            Self { index, page_index, text }
400        }
401    }
402
403    /// Arguments for a widget function for a stack of lines.
404    ///
405    /// See [`PAGE_FN_VAR`] for more details.
406    #[non_exhaustive]
407    pub struct PageFnArgs {
408        /// Zero-counted index of this page.
409        pub index: u32,
410
411        /// Line widgets, generated by [`LINE_FN_VAR`].
412        pub lines: UiVec,
413    }
414
415    impl PageFnArgs {
416        /// New args.
417        pub fn new(index: u32, lines: UiVec) -> Self {
418            Self { index, lines }
419        }
420    }
421
422    /// Arguments for a widget function for a stack of pages.
423    ///
424    /// See [`PANEL_FN_VAR`] for more details.
425    #[non_exhaustive]
426    pub struct PanelFnArgs {
427        /// Page widgets, generated by [`PAGE_FN_VAR`].
428        pub pages: UiVec,
429    }
430
431    impl PanelFnArgs {
432        /// New args.
433        pub fn new(pages: UiVec) -> Self {
434            Self { pages }
435        }
436    }
437
438    context_var! {
439        /// Widget function for [`TextFnArgs`].
440        ///
441        /// The returned widgets are layout by the [`LINE_FN_VAR`]. The default view is [`default_text_fn`].
442        pub static TEXT_FN_VAR: WidgetFn<TextFnArgs> = wgt_fn!(|args: TextFnArgs| { default_text_fn(args) });
443
444        /// Widget function for [`LineFnArgs`].
445        ///
446        /// The returned widgets are layout by the [`PAGE_FN_VAR`]. The default view is [`default_line_fn`].
447        pub static LINE_FN_VAR: WidgetFn<LineFnArgs> = wgt_fn!(|args: LineFnArgs| { default_line_fn(args) });
448
449        /// Widget function for [`PageFnArgs`].
450        ///
451        /// The returned widgets are layout by the [`PANEL_FN_VAR`] widget. The default view is [`default_page_fn`].
452        pub static PAGE_FN_VAR: WidgetFn<PageFnArgs> = wgt_fn!(|args: PageFnArgs| { default_page_fn(args) });
453
454        /// Widget function for [`PanelFnArgs`].
455        ///
456        /// The returned view is the [`AnsiText!`] child. The default is [`default_panel_fn`].
457        ///
458        /// [`AnsiText!`]: struct@super::AnsiText
459        pub static PANEL_FN_VAR: WidgetFn<PanelFnArgs> = wgt_fn!(|args: PanelFnArgs| { default_panel_fn(args) });
460
461        /// Duration the ANSI blink animation keeps the text visible for.
462        ///
463        /// Set to `ZERO` or `MAX` to disable animation.
464        pub static BLINK_INTERVAL_VAR: Duration = Duration::ZERO;
465
466        /// Maximum number of lines per [`PAGE_FN_VAR`].
467        ///
468        /// Is `200` by default.
469        pub static LINES_PER_PAGE_VAR: u32 = 200;
470    }
471
472    /// Default [`TEXT_FN_VAR`].
473    ///
474    /// This view is configured by contextual variables like [`BLINK_INTERVAL_VAR`] and all text variables that are
475    /// not overridden by the ANSI style, like the font.
476    ///
477    /// Returns a `Text!` with the text and style.
478    pub fn default_text_fn(args: TextFnArgs) -> UiNode {
479        let mut text = Text::widget_new();
480
481        widget_set! {
482            &mut text;
483            txt = args.txt;
484        }
485
486        if args.style.background_color != AnsiColor::Black {
487            widget_set! {
488                &mut text;
489                background_color = args.style.background_color;
490            }
491        }
492        if args.style.color != AnsiColor::White {
493            widget_set! {
494                &mut text;
495                font_color = args.style.color;
496            }
497        }
498
499        if args.style.weight != AnsiWeight::Normal {
500            widget_set! {
501                &mut text;
502                font_weight = args.style.weight;
503            }
504        }
505        if args.style.italic {
506            widget_set! {
507                &mut text;
508                font_style = FontStyle::Italic;
509            }
510        }
511
512        if args.style.underline {
513            widget_set! {
514                &mut text;
515                underline = 1, LineStyle::Solid;
516            }
517        }
518        if args.style.strikethrough {
519            widget_set! {
520                &mut text;
521                strikethrough = 1, LineStyle::Solid;
522            }
523        }
524
525        if args.style.invert_color {
526            widget_set! {
527                &mut text;
528                invert_color = true;
529            }
530        }
531
532        if args.style.hidden {
533            widget_set! {
534                &mut text;
535                visibility = Visibility::Hidden;
536            }
537        }
538        if args.style.blink && !args.style.hidden {
539            let opacity = var(1.fct());
540
541            let interval = BLINK_INTERVAL_VAR.get();
542            if interval != Duration::ZERO && interval != Duration::MAX {
543                opacity.step_oci(0.fct(), interval).perm();
544
545                widget_set! {
546                    &mut text;
547                    opacity;
548                }
549            }
550        }
551
552        text.widget_build()
553    }
554
555    /// Default [`LINE_FN_VAR`].
556    ///
557    /// Returns a `Wrap!` for text with multiple segments, or returns the single segment, or an empty text.
558    pub fn default_line_fn(mut args: LineFnArgs) -> UiNode {
559        if args.text.is_empty() {
560            Text!("")
561        } else if args.text.len() == 1 {
562            args.text.remove(0)
563        } else {
564            Stack! {
565                rich_text = true;
566                direction = StackDirection::start_to_end();
567                children = args.text;
568            }
569        }
570    }
571
572    /// Default [`PAGE_FN_VAR`].
573    ///
574    /// Returns a `Stack!` for multiple lines, or return the single line, or a nil node.
575    pub fn default_page_fn(mut args: PageFnArgs) -> UiNode {
576        use crate::prelude::*;
577
578        if args.lines.is_empty() {
579            UiNode::nil()
580        } else if args.lines.len() == 1 {
581            args.lines.remove(0)
582        } else {
583            let len = args.lines.len();
584            Stack! {
585                rich_text = true;
586                direction = StackDirection::top_to_bottom();
587                children = args.lines;
588                lazy = LazyMode::lazy_vertical(wgt_fn!(|_| {
589                    let height_sample = zng_wgt_text::node::line_placeholder(50);
590                    zng_wgt_stack::lazy_sample(len, StackDirection::top_to_bottom(), 0, height_sample)
591                }));
592            }
593        }
594    }
595
596    /// Default [`PANEL_FN_VAR`].
597    ///
598    /// Returns a `Stack!` for multiple pages, or returns the single page, or a nil node.
599    pub fn default_panel_fn(mut args: PanelFnArgs) -> UiNode {
600        use crate::prelude::*;
601
602        if args.pages.is_empty() {
603            UiNode::nil()
604        } else if args.pages.len() == 1 {
605            args.pages.remove(0)
606        } else {
607            Stack! {
608                rich_text = true;
609                direction = StackDirection::top_to_bottom();
610                children = args.pages;
611            }
612        }
613    }
614
615    /// ANSI blink animation interval.
616    ///
617    /// Set to `ZERO` to disable the blink animation.
618    ///
619    /// Sets the [`BLINK_INTERVAL_VAR`].
620    #[property(CONTEXT, default(BLINK_INTERVAL_VAR), widget_impl(AnsiText))]
621    pub fn blink_interval(child: impl IntoUiNode, interval: impl IntoVar<Duration>) -> UiNode {
622        with_context_var(child, BLINK_INTERVAL_VAR, interval)
623    }
624
625    /// Widget function that converts [`TextFnArgs`] to widgets.
626    ///
627    /// Sets the [`TEXT_FN_VAR`].
628    #[property(CONTEXT, default(TEXT_FN_VAR), widget_impl(AnsiText))]
629    pub fn text_fn(child: impl IntoUiNode, wgt_fn: impl IntoVar<WidgetFn<TextFnArgs>>) -> UiNode {
630        with_context_var(child, TEXT_FN_VAR, wgt_fn)
631    }
632
633    /// Widget function that converts [`LineFnArgs`] to widgets.
634    ///
635    /// Sets the [`LINE_FN_VAR`].
636    #[property(CONTEXT, default(LINE_FN_VAR), widget_impl(AnsiText))]
637    pub fn line_fn(child: impl IntoUiNode, wgt_fn: impl IntoVar<WidgetFn<LineFnArgs>>) -> UiNode {
638        with_context_var(child, LINE_FN_VAR, wgt_fn)
639    }
640
641    /// Widget function that converts [`PageFnArgs`] to widgets.
642    ///
643    /// A *page* is a stack of a maximum of [`lines_per_page`], the text is split in pages mostly for performance reasons.
644    ///
645    /// Sets the [`PAGE_FN_VAR`].
646    ///
647    /// [`lines_per_page`]: fn@lines_per_page
648    #[property(CONTEXT, default(PAGE_FN_VAR), widget_impl(AnsiText))]
649    pub fn page_fn(child: impl IntoUiNode, wgt_fn: impl IntoVar<WidgetFn<PageFnArgs>>) -> UiNode {
650        with_context_var(child, PAGE_FN_VAR, wgt_fn)
651    }
652
653    /// Widget function that converts [`PanelFnArgs`] to widgets.
654    #[property(CONTEXT, default(PANEL_FN_VAR), widget_impl(AnsiText))]
655    pub fn panel_fn(child: impl IntoUiNode, wgt_fn: impl IntoVar<WidgetFn<PanelFnArgs>>) -> UiNode {
656        with_context_var(child, PANEL_FN_VAR, wgt_fn)
657    }
658
659    /// Maximum number of lines per page view.
660    ///
661    /// Sets the [`LINES_PER_PAGE_VAR`].
662    #[property(CONTEXT, default(LINES_PER_PAGE_VAR), widget_impl(AnsiText))]
663    pub fn lines_per_page(child: impl IntoUiNode, count: impl IntoVar<u32>) -> UiNode {
664        with_context_var(child, LINES_PER_PAGE_VAR, count)
665    }
666}
667
668fn generate_ansi(txt: &Var<Txt>) -> UiNode {
669    use ansi_fn::*;
670    use std::mem;
671
672    txt.with(|txt| {
673        let text_fn = TEXT_FN_VAR.get();
674        let line_fn = LINE_FN_VAR.get();
675        let page_fn = PAGE_FN_VAR.get();
676        let panel_fn = PANEL_FN_VAR.get();
677        let lines_per_page = LINES_PER_PAGE_VAR.get() as usize;
678
679        let mut pages = Vec::with_capacity(4);
680        let mut lines = Vec::with_capacity(50);
681
682        for (i, line) in txt.lines().enumerate() {
683            let text = ansi_parse::AnsiTextParser::new(line)
684                .filter_map(|txt| {
685                    text_fn.call_checked(TextFnArgs {
686                        txt: txt.txt.to_txt(),
687                        style: txt.style,
688                    })
689                })
690                .collect();
691
692            lines.push(line_fn(LineFnArgs {
693                index: i as u32,
694                page_index: lines.len() as u32,
695                text,
696            }));
697
698            if lines.len() == lines_per_page {
699                let lines = mem::replace(&mut lines, Vec::with_capacity(50));
700                pages.push(page_fn(PageFnArgs {
701                    index: pages.len() as u32,
702                    lines: lines.into(),
703                }));
704            }
705        }
706
707        if !lines.is_empty() {
708            pages.push(page_fn(PageFnArgs {
709                index: pages.len() as u32,
710                lines: lines.into(),
711            }));
712        }
713
714        panel_fn(PanelFnArgs { pages: pages.into() })
715    })
716}
717
718/// Implements the ANSI parsing and view generation, configured by contextual properties.
719pub fn ansi_node(txt: impl IntoVar<Txt>) -> UiNode {
720    let txt = txt.into_var();
721    match_node(UiNode::nil(), move |c, op| match op {
722        UiNodeOp::Init => {
723            WIDGET
724                .sub_var(&txt)
725                .sub_var(&TEXT_FN_VAR)
726                .sub_var(&LINE_FN_VAR)
727                .sub_var(&PAGE_FN_VAR)
728                .sub_var(&PANEL_FN_VAR)
729                .sub_var(&LINES_PER_PAGE_VAR)
730                .sub_var(&BLINK_INTERVAL_VAR);
731
732            *c.node() = generate_ansi(&txt);
733        }
734        UiNodeOp::Deinit => {
735            c.deinit();
736            *c.node() = UiNode::nil();
737        }
738        UiNodeOp::Update { .. } => {
739            use ansi_fn::*;
740
741            if txt.is_new()
742                || TEXT_FN_VAR.is_new()
743                || LINE_FN_VAR.is_new()
744                || PAGE_FN_VAR.is_new()
745                || PANEL_FN_VAR.is_new()
746                || LINES_PER_PAGE_VAR.is_new()
747                || BLINK_INTERVAL_VAR.is_new()
748            {
749                c.node().deinit();
750                *c.node() = generate_ansi(&txt);
751                c.node().init();
752                WIDGET.update_info().layout().render();
753            }
754        }
755        _ => {}
756    })
757}
758
759static X_TERM_256: [(u8, u8, u8); 256] = [
760    (0, 0, 0),
761    (128, 0, 0),
762    (0, 128, 0),
763    (128, 128, 0),
764    (0, 0, 128),
765    (128, 0, 128),
766    (0, 128, 128),
767    (192, 192, 192),
768    (128, 128, 128),
769    (255, 0, 0),
770    (0, 255, 0),
771    (255, 255, 0),
772    (0, 0, 255),
773    (255, 0, 255),
774    (0, 255, 255),
775    (255, 255, 255),
776    (0, 0, 0),
777    (0, 0, 95),
778    (0, 0, 135),
779    (0, 0, 175),
780    (0, 0, 215),
781    (0, 0, 255),
782    (0, 95, 0),
783    (0, 95, 95),
784    (0, 95, 135),
785    (0, 95, 175),
786    (0, 95, 215),
787    (0, 95, 255),
788    (0, 135, 0),
789    (0, 135, 95),
790    (0, 135, 135),
791    (0, 135, 175),
792    (0, 135, 215),
793    (0, 135, 255),
794    (0, 175, 0),
795    (0, 175, 95),
796    (0, 175, 135),
797    (0, 175, 175),
798    (0, 175, 215),
799    (0, 175, 255),
800    (0, 215, 0),
801    (0, 215, 95),
802    (0, 215, 135),
803    (0, 215, 175),
804    (0, 215, 215),
805    (0, 215, 255),
806    (0, 255, 0),
807    (0, 255, 95),
808    (0, 255, 135),
809    (0, 255, 175),
810    (0, 255, 215),
811    (0, 255, 255),
812    (95, 0, 0),
813    (95, 0, 95),
814    (95, 0, 135),
815    (95, 0, 175),
816    (95, 0, 215),
817    (95, 0, 255),
818    (95, 95, 0),
819    (95, 95, 95),
820    (95, 95, 135),
821    (95, 95, 175),
822    (95, 95, 215),
823    (95, 95, 255),
824    (95, 135, 0),
825    (95, 135, 95),
826    (95, 135, 135),
827    (95, 135, 175),
828    (95, 135, 215),
829    (95, 135, 255),
830    (95, 175, 0),
831    (95, 175, 95),
832    (95, 175, 135),
833    (95, 175, 175),
834    (95, 175, 215),
835    (95, 175, 255),
836    (95, 215, 0),
837    (95, 215, 95),
838    (95, 215, 135),
839    (95, 215, 175),
840    (95, 215, 215),
841    (95, 215, 255),
842    (95, 255, 0),
843    (95, 255, 95),
844    (95, 255, 135),
845    (95, 255, 175),
846    (95, 255, 215),
847    (95, 255, 255),
848    (135, 0, 0),
849    (135, 0, 95),
850    (135, 0, 135),
851    (135, 0, 175),
852    (135, 0, 215),
853    (135, 0, 255),
854    (135, 95, 0),
855    (135, 95, 95),
856    (135, 95, 135),
857    (135, 95, 175),
858    (135, 95, 215),
859    (135, 95, 255),
860    (135, 135, 0),
861    (135, 135, 95),
862    (135, 135, 135),
863    (135, 135, 175),
864    (135, 135, 215),
865    (135, 135, 255),
866    (135, 175, 0),
867    (135, 175, 95),
868    (135, 175, 135),
869    (135, 175, 175),
870    (135, 175, 215),
871    (135, 175, 255),
872    (135, 215, 0),
873    (135, 215, 95),
874    (135, 215, 135),
875    (135, 215, 175),
876    (135, 215, 215),
877    (135, 215, 255),
878    (135, 255, 0),
879    (135, 255, 95),
880    (135, 255, 135),
881    (135, 255, 175),
882    (135, 255, 215),
883    (135, 255, 255),
884    (175, 0, 0),
885    (175, 0, 95),
886    (175, 0, 135),
887    (175, 0, 175),
888    (175, 0, 215),
889    (175, 0, 255),
890    (175, 95, 0),
891    (175, 95, 95),
892    (175, 95, 135),
893    (175, 95, 175),
894    (175, 95, 215),
895    (175, 95, 255),
896    (175, 135, 0),
897    (175, 135, 95),
898    (175, 135, 135),
899    (175, 135, 175),
900    (175, 135, 215),
901    (175, 135, 255),
902    (175, 175, 0),
903    (175, 175, 95),
904    (175, 175, 135),
905    (175, 175, 175),
906    (175, 175, 215),
907    (175, 175, 255),
908    (175, 215, 0),
909    (175, 215, 95),
910    (175, 215, 135),
911    (175, 215, 175),
912    (175, 215, 215),
913    (175, 215, 255),
914    (175, 255, 0),
915    (175, 255, 95),
916    (175, 255, 135),
917    (175, 255, 175),
918    (175, 255, 215),
919    (175, 255, 255),
920    (215, 0, 0),
921    (215, 0, 95),
922    (215, 0, 135),
923    (215, 0, 175),
924    (215, 0, 215),
925    (215, 0, 255),
926    (215, 95, 0),
927    (215, 95, 95),
928    (215, 95, 135),
929    (215, 95, 175),
930    (215, 95, 215),
931    (215, 95, 255),
932    (215, 135, 0),
933    (215, 135, 95),
934    (215, 135, 135),
935    (215, 135, 175),
936    (215, 135, 215),
937    (215, 135, 255),
938    (215, 175, 0),
939    (215, 175, 95),
940    (215, 175, 135),
941    (215, 175, 175),
942    (215, 175, 215),
943    (215, 175, 255),
944    (215, 215, 0),
945    (215, 215, 95),
946    (215, 215, 135),
947    (215, 215, 175),
948    (215, 215, 215),
949    (215, 215, 255),
950    (215, 255, 0),
951    (215, 255, 95),
952    (215, 255, 135),
953    (215, 255, 175),
954    (215, 255, 215),
955    (215, 255, 255),
956    (255, 0, 0),
957    (255, 0, 95),
958    (255, 0, 135),
959    (255, 0, 175),
960    (255, 0, 215),
961    (255, 0, 255),
962    (255, 95, 0),
963    (255, 95, 95),
964    (255, 95, 135),
965    (255, 95, 175),
966    (255, 95, 215),
967    (255, 95, 255),
968    (255, 135, 0),
969    (255, 135, 95),
970    (255, 135, 135),
971    (255, 135, 175),
972    (255, 135, 215),
973    (255, 135, 255),
974    (255, 175, 0),
975    (255, 175, 95),
976    (255, 175, 135),
977    (255, 175, 175),
978    (255, 175, 215),
979    (255, 175, 255),
980    (255, 215, 0),
981    (255, 215, 95),
982    (255, 215, 135),
983    (255, 215, 175),
984    (255, 215, 215),
985    (255, 215, 255),
986    (255, 255, 0),
987    (255, 255, 95),
988    (255, 255, 135),
989    (255, 255, 175),
990    (255, 255, 215),
991    (255, 255, 255),
992    (8, 8, 8),
993    (18, 18, 18),
994    (28, 28, 28),
995    (38, 38, 38),
996    (48, 48, 48),
997    (58, 58, 58),
998    (68, 68, 68),
999    (78, 78, 78),
1000    (88, 88, 88),
1001    (98, 98, 98),
1002    (108, 108, 108),
1003    (118, 118, 118),
1004    (128, 128, 128),
1005    (138, 138, 138),
1006    (148, 148, 148),
1007    (158, 158, 158),
1008    (168, 168, 168),
1009    (178, 178, 178),
1010    (188, 188, 188),
1011    (198, 198, 198),
1012    (208, 208, 208),
1013    (218, 218, 218),
1014    (228, 228, 228),
1015    (238, 238, 238),
1016];