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