Skip to main content

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