term_model/
ansi.rs

1// Copyright 2016 Joe Wilm, The Alacritty Project Contributors
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14//
15//! ANSI Terminal Stream Parsing
16use std::io;
17use std::str;
18
19use log::{debug, trace};
20use serde::{Deserialize, Serialize};
21
22use crate::index::{Column, Line};
23use crate::term::color::Rgb;
24
25// Parse colors in XParseColor format
26fn xparse_color(color: &[u8]) -> Option<Rgb> {
27    if !color.is_empty() && color[0] == b'#' {
28        parse_legacy_color(&color[1..])
29    } else if color.len() >= 4 && &color[..4] == b"rgb:" {
30        parse_rgb_color(&color[4..])
31    } else {
32        None
33    }
34}
35
36// Parse colors in `rgb:r(rrr)/g(ggg)/b(bbb)` format
37fn parse_rgb_color(color: &[u8]) -> Option<Rgb> {
38    let colors = str::from_utf8(color).ok()?.split('/').collect::<Vec<_>>();
39
40    if colors.len() != 3 {
41        return None;
42    }
43
44    // Scale values instead of filling with `0`s
45    let scale = |input: &str| {
46        let max = u32::pow(16, input.len() as u32) - 1;
47        let value = u32::from_str_radix(input, 16).ok()?;
48        Some((255 * value / max) as u8)
49    };
50
51    Some(Rgb { r: scale(colors[0])?, g: scale(colors[1])?, b: scale(colors[2])? })
52}
53
54// Parse colors in `#r(rrr)g(ggg)b(bbb)` format
55fn parse_legacy_color(color: &[u8]) -> Option<Rgb> {
56    let item_len = color.len() / 3;
57
58    // Truncate/Fill to two byte precision
59    let color_from_slice = |slice: &[u8]| {
60        let col = usize::from_str_radix(str::from_utf8(slice).ok()?, 16).ok()? << 4;
61        Some((col >> (4 * slice.len().saturating_sub(1))) as u8)
62    };
63
64    Some(Rgb {
65        r: color_from_slice(&color[0..item_len])?,
66        g: color_from_slice(&color[item_len..item_len * 2])?,
67        b: color_from_slice(&color[item_len * 2..])?,
68    })
69}
70
71fn parse_number(input: &[u8]) -> Option<u8> {
72    if input.is_empty() {
73        return None;
74    }
75    let mut num: u8 = 0;
76    for c in input {
77        let c = *c as char;
78        if let Some(digit) = c.to_digit(10) {
79            num = match num.checked_mul(10).and_then(|v| v.checked_add(digit as u8)) {
80                Some(v) => v,
81                None => return None,
82            }
83        } else {
84            return None;
85        }
86    }
87    Some(num)
88}
89
90/// The processor wraps a `vte::Parser` to ultimately call methods on a Handler
91pub struct Processor {
92    state: ProcessorState,
93    parser: vte::Parser,
94}
95
96/// Internal state for VTE processor
97struct ProcessorState {
98    preceding_char: Option<char>,
99}
100
101/// Helper type that implements `vte::Perform`.
102///
103/// Processor creates a Performer when running advance and passes the Performer
104/// to `vte::Parser`.
105struct Performer<'a, H: Handler + TermInfo, W: io::Write> {
106    state: &'a mut ProcessorState,
107    handler: &'a mut H,
108    writer: &'a mut W,
109}
110
111impl<'a, H: Handler + TermInfo + 'a, W: io::Write> Performer<'a, H, W> {
112    /// Create a performer
113    #[inline]
114    pub fn new<'b>(
115        state: &'b mut ProcessorState,
116        handler: &'b mut H,
117        writer: &'b mut W,
118    ) -> Performer<'b, H, W> {
119        Performer { state, handler, writer }
120    }
121}
122
123impl Default for Processor {
124    fn default() -> Processor {
125        Processor { state: ProcessorState { preceding_char: None }, parser: vte::Parser::new() }
126    }
127}
128
129impl Processor {
130    pub fn new() -> Processor {
131        Default::default()
132    }
133
134    #[inline]
135    pub fn advance<H, W>(&mut self, handler: &mut H, byte: u8, writer: &mut W)
136    where
137        H: Handler + TermInfo,
138        W: io::Write,
139    {
140        let mut performer = Performer::new(&mut self.state, handler, writer);
141        self.parser.advance(&mut performer, byte);
142    }
143}
144
145/// Trait that provides properties of terminal
146pub trait TermInfo {
147    fn lines(&self) -> Line;
148    fn cols(&self) -> Column;
149}
150
151/// Type that handles actions from the parser
152///
153/// XXX Should probably not provide default impls for everything, but it makes
154/// writing specific handler impls for tests far easier.
155pub trait Handler {
156    /// OSC to set window title
157    fn set_title(&mut self, _: &str) {}
158
159    /// Set the cursor style
160    fn set_cursor_style(&mut self, _: Option<CursorStyle>) {}
161
162    /// A character to be displayed
163    fn input(&mut self, _c: char) {}
164
165    /// Set cursor to position
166    fn goto(&mut self, _: Line, _: Column) {}
167
168    /// Set cursor to specific row
169    fn goto_line(&mut self, _: Line) {}
170
171    /// Set cursor to specific column
172    fn goto_col(&mut self, _: Column) {}
173
174    /// Insert blank characters in current line starting from cursor
175    fn insert_blank(&mut self, _: Column) {}
176
177    /// Move cursor up `rows`
178    fn move_up(&mut self, _: Line) {}
179
180    /// Move cursor down `rows`
181    fn move_down(&mut self, _: Line) {}
182
183    /// Identify the terminal (should write back to the pty stream)
184    ///
185    /// TODO this should probably return an io::Result
186    fn identify_terminal<W: io::Write>(&mut self, _: &mut W) {}
187
188    // Report device status
189    fn device_status<W: io::Write>(&mut self, _: &mut W, _: usize) {}
190
191    /// Move cursor forward `cols`
192    fn move_forward(&mut self, _: Column) {}
193
194    /// Move cursor backward `cols`
195    fn move_backward(&mut self, _: Column) {}
196
197    /// Move cursor down `rows` and set to column 1
198    fn move_down_and_cr(&mut self, _: Line) {}
199
200    /// Move cursor up `rows` and set to column 1
201    fn move_up_and_cr(&mut self, _: Line) {}
202
203    /// Put `count` tabs
204    fn put_tab(&mut self, _count: i64) {}
205
206    /// Backspace `count` characters
207    fn backspace(&mut self) {}
208
209    /// Carriage return
210    fn carriage_return(&mut self) {}
211
212    /// Linefeed
213    fn linefeed(&mut self) {}
214
215    /// Ring the bell
216    ///
217    /// Hopefully this is never implemented
218    fn bell(&mut self) {}
219
220    /// Substitute char under cursor
221    fn substitute(&mut self) {}
222
223    /// Newline
224    fn newline(&mut self) {}
225
226    /// Set current position as a tabstop
227    fn set_horizontal_tabstop(&mut self) {}
228
229    /// Scroll up `rows` rows
230    fn scroll_up(&mut self, _: Line) {}
231
232    /// Scroll down `rows` rows
233    fn scroll_down(&mut self, _: Line) {}
234
235    /// Insert `count` blank lines
236    fn insert_blank_lines(&mut self, _: Line) {}
237
238    /// Delete `count` lines
239    fn delete_lines(&mut self, _: Line) {}
240
241    /// Erase `count` chars in current line following cursor
242    ///
243    /// Erase means resetting to the default state (default colors, no content,
244    /// no mode flags)
245    fn erase_chars(&mut self, _: Column) {}
246
247    /// Delete `count` chars
248    ///
249    /// Deleting a character is like the delete key on the keyboard - everything
250    /// to the right of the deleted things is shifted left.
251    fn delete_chars(&mut self, _: Column) {}
252
253    /// Move backward `count` tabs
254    fn move_backward_tabs(&mut self, _count: i64) {}
255
256    /// Move forward `count` tabs
257    fn move_forward_tabs(&mut self, _count: i64) {}
258
259    /// Save current cursor position
260    fn save_cursor_position(&mut self) {}
261
262    /// Restore cursor position
263    fn restore_cursor_position(&mut self) {}
264
265    /// Clear current line
266    fn clear_line(&mut self, _mode: LineClearMode) {}
267
268    /// Clear screen
269    fn clear_screen(&mut self, _mode: ClearMode) {}
270
271    /// Clear tab stops
272    fn clear_tabs(&mut self, _mode: TabulationClearMode) {}
273
274    /// Reset terminal state
275    fn reset_state(&mut self) {}
276
277    /// Reverse Index
278    ///
279    /// Move the active position to the same horizontal position on the
280    /// preceding line. If the active position is at the top margin, a scroll
281    /// down is performed
282    fn reverse_index(&mut self) {}
283
284    /// set a terminal attribute
285    fn terminal_attribute(&mut self, _attr: Attr) {}
286
287    /// Set mode
288    fn set_mode(&mut self, _mode: Mode) {}
289
290    /// Unset mode
291    fn unset_mode(&mut self, _: Mode) {}
292
293    /// DECSTBM - Set the terminal scrolling region
294    fn set_scrolling_region(&mut self, _top: usize, _bottom: usize) {}
295
296    /// DECKPAM - Set keypad to applications mode (ESCape instead of digits)
297    fn set_keypad_application_mode(&mut self) {}
298
299    /// DECKPNM - Set keypad to numeric mode (digits instead of ESCape seq)
300    fn unset_keypad_application_mode(&mut self) {}
301
302    /// Set one of the graphic character sets, G0 to G3, as the active charset.
303    ///
304    /// 'Invoke' one of G0 to G3 in the GL area. Also referred to as shift in,
305    /// shift out and locking shift depending on the set being activated
306    fn set_active_charset(&mut self, _: CharsetIndex) {}
307
308    /// Assign a graphic character set to G0, G1, G2 or G3
309    ///
310    /// 'Designate' a graphic character set as one of G0 to G3, so that it can
311    /// later be 'invoked' by `set_active_charset`
312    fn configure_charset(&mut self, _: CharsetIndex, _: StandardCharset) {}
313
314    /// Set an indexed color value
315    fn set_color(&mut self, _: usize, _: Rgb) {}
316
317    /// Write a foreground/background color escape sequence with the current color
318    fn dynamic_color_sequence<W: io::Write>(&mut self, _: &mut W, _: u8, _: usize) {}
319
320    /// Reset an indexed color to original value
321    fn reset_color(&mut self, _: usize) {}
322
323    /// Set the clipboard
324    fn set_clipboard(&mut self, _: u8, _: &[u8]) {}
325
326    /// Write clipboard data to child.
327    fn write_clipboard<W: io::Write>(&mut self, _: u8, _: &mut W) {}
328
329    /// Run the decaln routine.
330    fn decaln(&mut self) {}
331
332    /// Push a title onto the stack
333    fn push_title(&mut self) {}
334
335    /// Pop the last title from the stack
336    fn pop_title(&mut self) {}
337}
338
339/// Describes shape of cursor
340#[derive(Debug, Eq, PartialEq, Copy, Clone, Hash, Deserialize)]
341pub enum CursorStyle {
342    /// Cursor is a block like `▒`
343    Block,
344
345    /// Cursor is an underscore like `_`
346    Underline,
347
348    /// Cursor is a vertical bar `⎸`
349    Beam,
350
351    /// Cursor is a box like `☐`
352    HollowBlock,
353
354    /// Invisible cursor
355    Hidden,
356}
357
358impl Default for CursorStyle {
359    fn default() -> CursorStyle {
360        CursorStyle::Block
361    }
362}
363
364/// Terminal modes
365#[derive(Debug, Eq, PartialEq)]
366pub enum Mode {
367    /// ?1
368    CursorKeys = 1,
369    /// Select 80 or 132 columns per page
370    ///
371    /// CSI ? 3 h -> set 132 column font
372    /// CSI ? 3 l -> reset 80 column font
373    ///
374    /// Additionally,
375    ///
376    /// * set margins to default positions
377    /// * erases all data in page memory
378    /// * resets DECLRMM to unavailable
379    /// * clears data from the status line (if set to host-writable)
380    DECCOLM = 3,
381    /// IRM Insert Mode
382    ///
383    /// NB should be part of non-private mode enum
384    ///
385    /// * `CSI 4 h` change to insert mode
386    /// * `CSI 4 l` reset to replacement mode
387    Insert = 4,
388    /// ?6
389    Origin = 6,
390    /// ?7
391    LineWrap = 7,
392    /// ?12
393    BlinkingCursor = 12,
394    /// 20
395    ///
396    /// NB This is actually a private mode. We should consider adding a second
397    /// enumeration for public/private modesets.
398    LineFeedNewLine = 20,
399    /// ?25
400    ShowCursor = 25,
401    /// ?1000
402    ReportMouseClicks = 1000,
403    /// ?1002
404    ReportCellMouseMotion = 1002,
405    /// ?1003
406    ReportAllMouseMotion = 1003,
407    /// ?1004
408    ReportFocusInOut = 1004,
409    /// ?1005
410    Utf8Mouse = 1005,
411    /// ?1006
412    SgrMouse = 1006,
413    /// ?1007
414    AlternateScroll = 1007,
415    /// ?1049
416    SwapScreenAndSetRestoreCursor = 1049,
417    /// ?2004
418    BracketedPaste = 2004,
419}
420
421impl Mode {
422    /// Create mode from a primitive
423    ///
424    /// TODO lots of unhandled values..
425    pub fn from_primitive(intermediate: Option<&u8>, num: i64) -> Option<Mode> {
426        let private = match intermediate {
427            Some(b'?') => true,
428            None => false,
429            _ => return None,
430        };
431
432        if private {
433            Some(match num {
434                1 => Mode::CursorKeys,
435                3 => Mode::DECCOLM,
436                6 => Mode::Origin,
437                7 => Mode::LineWrap,
438                12 => Mode::BlinkingCursor,
439                25 => Mode::ShowCursor,
440                1000 => Mode::ReportMouseClicks,
441                1002 => Mode::ReportCellMouseMotion,
442                1003 => Mode::ReportAllMouseMotion,
443                1004 => Mode::ReportFocusInOut,
444                1005 => Mode::Utf8Mouse,
445                1006 => Mode::SgrMouse,
446                1007 => Mode::AlternateScroll,
447                1049 => Mode::SwapScreenAndSetRestoreCursor,
448                2004 => Mode::BracketedPaste,
449                _ => {
450                    trace!("[unimplemented] primitive mode: {}", num);
451                    return None;
452                },
453            })
454        } else {
455            Some(match num {
456                4 => Mode::Insert,
457                20 => Mode::LineFeedNewLine,
458                _ => return None,
459            })
460        }
461    }
462}
463
464/// Mode for clearing line
465///
466/// Relative to cursor
467#[derive(Debug)]
468pub enum LineClearMode {
469    /// Clear right of cursor
470    Right,
471    /// Clear left of cursor
472    Left,
473    /// Clear entire line
474    All,
475}
476
477/// Mode for clearing terminal
478///
479/// Relative to cursor
480#[derive(Debug)]
481pub enum ClearMode {
482    /// Clear below cursor
483    Below,
484    /// Clear above cursor
485    Above,
486    /// Clear entire terminal
487    All,
488    /// Clear 'saved' lines (scrollback)
489    Saved,
490}
491
492/// Mode for clearing tab stops
493#[derive(Debug)]
494pub enum TabulationClearMode {
495    /// Clear stop under cursor
496    Current,
497    /// Clear all stops
498    All,
499}
500
501/// Standard colors
502///
503/// The order here matters since the enum should be castable to a `usize` for
504/// indexing a color list.
505#[derive(Debug, Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize)]
506pub enum NamedColor {
507    /// Black
508    Black = 0,
509    /// Red
510    Red,
511    /// Green
512    Green,
513    /// Yellow
514    Yellow,
515    /// Blue
516    Blue,
517    /// Magenta
518    Magenta,
519    /// Cyan
520    Cyan,
521    /// White
522    White,
523    /// Bright black
524    BrightBlack,
525    /// Bright red
526    BrightRed,
527    /// Bright green
528    BrightGreen,
529    /// Bright yellow
530    BrightYellow,
531    /// Bright blue
532    BrightBlue,
533    /// Bright magenta
534    BrightMagenta,
535    /// Bright cyan
536    BrightCyan,
537    /// Bright white
538    BrightWhite,
539    /// The foreground color
540    Foreground = 256,
541    /// The background color
542    Background,
543    /// Color for the cursor itself
544    Cursor,
545    /// Dim black
546    DimBlack,
547    /// Dim red
548    DimRed,
549    /// Dim green
550    DimGreen,
551    /// Dim yellow
552    DimYellow,
553    /// Dim blue
554    DimBlue,
555    /// Dim magenta
556    DimMagenta,
557    /// Dim cyan
558    DimCyan,
559    /// Dim white
560    DimWhite,
561    /// The bright foreground color
562    BrightForeground,
563    /// Dim foreground
564    DimForeground,
565}
566
567impl NamedColor {
568    pub fn to_bright(self) -> Self {
569        match self {
570            NamedColor::Foreground => NamedColor::BrightForeground,
571            NamedColor::Black => NamedColor::BrightBlack,
572            NamedColor::Red => NamedColor::BrightRed,
573            NamedColor::Green => NamedColor::BrightGreen,
574            NamedColor::Yellow => NamedColor::BrightYellow,
575            NamedColor::Blue => NamedColor::BrightBlue,
576            NamedColor::Magenta => NamedColor::BrightMagenta,
577            NamedColor::Cyan => NamedColor::BrightCyan,
578            NamedColor::White => NamedColor::BrightWhite,
579            NamedColor::DimForeground => NamedColor::Foreground,
580            NamedColor::DimBlack => NamedColor::Black,
581            NamedColor::DimRed => NamedColor::Red,
582            NamedColor::DimGreen => NamedColor::Green,
583            NamedColor::DimYellow => NamedColor::Yellow,
584            NamedColor::DimBlue => NamedColor::Blue,
585            NamedColor::DimMagenta => NamedColor::Magenta,
586            NamedColor::DimCyan => NamedColor::Cyan,
587            NamedColor::DimWhite => NamedColor::White,
588            val => val,
589        }
590    }
591
592    pub fn to_dim(self) -> Self {
593        match self {
594            NamedColor::Black => NamedColor::DimBlack,
595            NamedColor::Red => NamedColor::DimRed,
596            NamedColor::Green => NamedColor::DimGreen,
597            NamedColor::Yellow => NamedColor::DimYellow,
598            NamedColor::Blue => NamedColor::DimBlue,
599            NamedColor::Magenta => NamedColor::DimMagenta,
600            NamedColor::Cyan => NamedColor::DimCyan,
601            NamedColor::White => NamedColor::DimWhite,
602            NamedColor::Foreground => NamedColor::DimForeground,
603            NamedColor::BrightBlack => NamedColor::Black,
604            NamedColor::BrightRed => NamedColor::Red,
605            NamedColor::BrightGreen => NamedColor::Green,
606            NamedColor::BrightYellow => NamedColor::Yellow,
607            NamedColor::BrightBlue => NamedColor::Blue,
608            NamedColor::BrightMagenta => NamedColor::Magenta,
609            NamedColor::BrightCyan => NamedColor::Cyan,
610            NamedColor::BrightWhite => NamedColor::White,
611            NamedColor::BrightForeground => NamedColor::Foreground,
612            val => val,
613        }
614    }
615}
616
617#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
618pub enum Color {
619    Named(NamedColor),
620    Spec(Rgb),
621    Indexed(u8),
622}
623
624/// Terminal character attributes
625#[derive(Debug, Eq, PartialEq)]
626pub enum Attr {
627    /// Clear all special abilities
628    Reset,
629    /// Bold text
630    Bold,
631    /// Dim or secondary color
632    Dim,
633    /// Italic text
634    Italic,
635    /// Underline text
636    Underline,
637    /// Blink cursor slowly
638    BlinkSlow,
639    /// Blink cursor fast
640    BlinkFast,
641    /// Invert colors
642    Reverse,
643    /// Do not display characters
644    Hidden,
645    /// Strikeout text
646    Strike,
647    /// Cancel bold
648    CancelBold,
649    /// Cancel bold and dim
650    CancelBoldDim,
651    /// Cancel italic
652    CancelItalic,
653    /// Cancel underline
654    CancelUnderline,
655    /// Cancel blink
656    CancelBlink,
657    /// Cancel inversion
658    CancelReverse,
659    /// Cancel text hiding
660    CancelHidden,
661    /// Cancel strikeout
662    CancelStrike,
663    /// Set indexed foreground color
664    Foreground(Color),
665    /// Set indexed background color
666    Background(Color),
667}
668
669/// Identifiers which can be assigned to a graphic character set
670#[derive(Clone, Copy, Debug, Eq, PartialEq)]
671pub enum CharsetIndex {
672    /// Default set, is designated as ASCII at startup
673    G0,
674    G1,
675    G2,
676    G3,
677}
678
679impl Default for CharsetIndex {
680    fn default() -> Self {
681        CharsetIndex::G0
682    }
683}
684
685/// Standard or common character sets which can be designated as G0-G3
686#[derive(Clone, Copy, Debug, Eq, PartialEq)]
687pub enum StandardCharset {
688    Ascii,
689    SpecialCharacterAndLineDrawing,
690}
691
692impl Default for StandardCharset {
693    fn default() -> Self {
694        StandardCharset::Ascii
695    }
696}
697
698impl<'a, H, W> vte::Perform for Performer<'a, H, W>
699where
700    H: Handler + TermInfo + 'a,
701    W: io::Write + 'a,
702{
703    #[inline]
704    fn print(&mut self, c: char) {
705        self.handler.input(c);
706        self.state.preceding_char = Some(c);
707    }
708
709    #[inline]
710    fn execute(&mut self, byte: u8) {
711        match byte {
712            C0::HT => self.handler.put_tab(1),
713            C0::BS => self.handler.backspace(),
714            C0::CR => self.handler.carriage_return(),
715            C0::LF | C0::VT | C0::FF => self.handler.linefeed(),
716            C0::BEL => self.handler.bell(),
717            C0::SUB => self.handler.substitute(),
718            C0::SI => self.handler.set_active_charset(CharsetIndex::G0),
719            C0::SO => self.handler.set_active_charset(CharsetIndex::G1),
720            _ => debug!("[unhandled] execute byte={:02x}", byte),
721        }
722    }
723
724    #[inline]
725    fn hook(&mut self, params: &[i64], intermediates: &[u8], ignore: bool) {
726        debug!(
727            "[unhandled hook] params={:?}, ints: {:?}, ignore: {:?}",
728            params, intermediates, ignore
729        );
730    }
731
732    #[inline]
733    fn put(&mut self, byte: u8) {
734        debug!("[unhandled put] byte={:?}", byte);
735    }
736
737    #[inline]
738    fn unhook(&mut self) {
739        debug!("[unhandled unhook]");
740    }
741
742    // TODO replace OSC parsing with parser combinators
743    #[inline]
744    fn osc_dispatch(&mut self, params: &[&[u8]]) {
745        let writer = &mut self.writer;
746
747        fn unhandled(params: &[&[u8]]) {
748            let mut buf = String::new();
749            for items in params {
750                buf.push_str("[");
751                for item in *items {
752                    buf.push_str(&format!("{:?},", *item as char));
753                }
754                buf.push_str("],");
755            }
756            debug!("[unhandled osc_dispatch]: [{}] at line {}", &buf, line!());
757        }
758
759        if params.is_empty() || params[0].is_empty() {
760            return;
761        }
762
763        match params[0] {
764            // Set window title
765            b"0" | b"2" => {
766                if params.len() >= 2 {
767                    let title = params[1..]
768                        .iter()
769                        .flat_map(|x| str::from_utf8(x))
770                        .collect::<Vec<&str>>()
771                        .join(";");
772                    self.handler.set_title(&title);
773                    return;
774                }
775                unhandled(params);
776            },
777
778            // Set icon name
779            // This is ignored, since alacritty has no concept of tabs
780            b"1" => (),
781
782            // Set color index
783            b"4" => {
784                if params.len() > 1 && params.len() % 2 != 0 {
785                    for chunk in params[1..].chunks(2) {
786                        let index = parse_number(chunk[0]);
787                        let color = xparse_color(chunk[1]);
788                        if let (Some(i), Some(c)) = (index, color) {
789                            self.handler.set_color(i as usize, c);
790                            return;
791                        }
792                    }
793                }
794                unhandled(params);
795            },
796
797            // Get/set Foreground, Background, Cursor colors
798            b"10" | b"11" | b"12" => {
799                if params.len() >= 2 {
800                    if let Some(mut dynamic_code) = parse_number(params[0]) {
801                        for param in &params[1..] {
802                            // 10 is the first dynamic color, also the foreground
803                            let offset = dynamic_code as usize - 10;
804                            let index = NamedColor::Foreground as usize + offset;
805
806                            // End of setting dynamic colors
807                            if index > NamedColor::Cursor as usize {
808                                unhandled(params);
809                                break;
810                            }
811
812                            if let Some(color) = xparse_color(param) {
813                                self.handler.set_color(index, color);
814                            } else if param == b"?" {
815                                self.handler.dynamic_color_sequence(writer, dynamic_code, index);
816                            } else {
817                                unhandled(params);
818                            }
819                            dynamic_code += 1;
820                        }
821                        return;
822                    }
823                }
824                unhandled(params);
825            },
826
827            // Set cursor style
828            b"50" => {
829                if params.len() >= 2
830                    && params[1].len() >= 13
831                    && params[1][0..12] == *b"CursorShape="
832                {
833                    let style = match params[1][12] as char {
834                        '0' => CursorStyle::Block,
835                        '1' => CursorStyle::Beam,
836                        '2' => CursorStyle::Underline,
837                        _ => return unhandled(params),
838                    };
839                    self.handler.set_cursor_style(Some(style));
840                    return;
841                }
842                unhandled(params);
843            },
844
845            // Set clipboard
846            b"52" => {
847                if params.len() < 3 {
848                    return unhandled(params);
849                }
850
851                let clipboard = params[1].get(0).unwrap_or(&b'c');
852                match params[2] {
853                    b"?" => self.handler.write_clipboard(*clipboard, writer),
854                    base64 => self.handler.set_clipboard(*clipboard, base64),
855                }
856            },
857
858            // Reset color index
859            b"104" => {
860                // Reset all color indexes when no parameters are given
861                if params.len() == 1 {
862                    for i in 0..256 {
863                        self.handler.reset_color(i);
864                    }
865                    return;
866                }
867
868                // Reset color indexes given as parameters
869                for param in &params[1..] {
870                    match parse_number(param) {
871                        Some(index) => self.handler.reset_color(index as usize),
872                        None => unhandled(params),
873                    }
874                }
875            },
876
877            // Reset foreground color
878            b"110" => self.handler.reset_color(NamedColor::Foreground as usize),
879
880            // Reset background color
881            b"111" => self.handler.reset_color(NamedColor::Background as usize),
882
883            // Reset text cursor color
884            b"112" => self.handler.reset_color(NamedColor::Cursor as usize),
885
886            _ => unhandled(params),
887        }
888    }
889
890    #[inline]
891    fn csi_dispatch(
892        &mut self,
893        args: &[i64],
894        intermediates: &[u8],
895        has_ignored_intermediates: bool,
896        action: char,
897    ) {
898        macro_rules! unhandled {
899            () => {{
900                debug!(
901                    "[Unhandled CSI] action={:?}, args={:?}, intermediates={:?}",
902                    action, args, intermediates
903                );
904            }};
905        }
906
907        macro_rules! arg_or_default {
908            (idx: $idx:expr, default: $default:expr) => {
909                args.get($idx)
910                    .and_then(|v| if *v == 0 { None } else { Some(*v) })
911                    .unwrap_or($default)
912            };
913        }
914
915        if has_ignored_intermediates || intermediates.len() > 1 {
916            unhandled!();
917            return;
918        }
919
920        let handler = &mut self.handler;
921        let writer = &mut self.writer;
922
923        match (action, intermediates.get(0)) {
924            ('@', None) => {
925                handler.insert_blank(Column(arg_or_default!(idx: 0, default: 1) as usize))
926            },
927            ('A', None) => {
928                handler.move_up(Line(arg_or_default!(idx: 0, default: 1) as usize));
929            },
930            ('b', None) => {
931                if let Some(c) = self.state.preceding_char {
932                    for _ in 0..arg_or_default!(idx: 0, default: 1) {
933                        handler.input(c);
934                    }
935                } else {
936                    debug!("tried to repeat with no preceding char");
937                }
938            },
939            ('B', None) | ('e', None) => {
940                handler.move_down(Line(arg_or_default!(idx: 0, default: 1) as usize))
941            },
942            ('c', None) if arg_or_default!(idx: 0, default: 0) == 0 => {
943                handler.identify_terminal(writer)
944            },
945            ('C', None) | ('a', None) => {
946                handler.move_forward(Column(arg_or_default!(idx: 0, default: 1) as usize))
947            },
948            ('D', None) => {
949                handler.move_backward(Column(arg_or_default!(idx: 0, default: 1) as usize))
950            },
951            ('E', None) => {
952                handler.move_down_and_cr(Line(arg_or_default!(idx: 0, default: 1) as usize))
953            },
954            ('F', None) => {
955                handler.move_up_and_cr(Line(arg_or_default!(idx: 0, default: 1) as usize))
956            },
957            ('g', None) => {
958                let mode = match arg_or_default!(idx: 0, default: 0) {
959                    0 => TabulationClearMode::Current,
960                    3 => TabulationClearMode::All,
961                    _ => {
962                        unhandled!();
963                        return;
964                    },
965                };
966
967                handler.clear_tabs(mode);
968            },
969            ('G', None) | ('`', None) => {
970                handler.goto_col(Column(arg_or_default!(idx: 0, default: 1) as usize - 1))
971            },
972            ('H', None) | ('f', None) => {
973                let y = arg_or_default!(idx: 0, default: 1) as usize;
974                let x = arg_or_default!(idx: 1, default: 1) as usize;
975                handler.goto(Line(y - 1), Column(x - 1));
976            },
977            ('I', None) => handler.move_forward_tabs(arg_or_default!(idx: 0, default: 1)),
978            ('J', None) => {
979                let mode = match arg_or_default!(idx: 0, default: 0) {
980                    0 => ClearMode::Below,
981                    1 => ClearMode::Above,
982                    2 => ClearMode::All,
983                    3 => ClearMode::Saved,
984                    _ => {
985                        unhandled!();
986                        return;
987                    },
988                };
989
990                handler.clear_screen(mode);
991            },
992            ('K', None) => {
993                let mode = match arg_or_default!(idx: 0, default: 0) {
994                    0 => LineClearMode::Right,
995                    1 => LineClearMode::Left,
996                    2 => LineClearMode::All,
997                    _ => {
998                        unhandled!();
999                        return;
1000                    },
1001                };
1002
1003                handler.clear_line(mode);
1004            },
1005            ('S', None) => handler.scroll_up(Line(arg_or_default!(idx: 0, default: 1) as usize)),
1006            ('t', None) => match arg_or_default!(idx: 0, default: 1) as usize {
1007                22 => handler.push_title(),
1008                23 => handler.pop_title(),
1009                _ => unhandled!(),
1010            },
1011            ('T', None) => handler.scroll_down(Line(arg_or_default!(idx: 0, default: 1) as usize)),
1012            ('L', None) => {
1013                handler.insert_blank_lines(Line(arg_or_default!(idx: 0, default: 1) as usize))
1014            },
1015            ('l', intermediate) => {
1016                for arg in args {
1017                    match Mode::from_primitive(intermediate, *arg) {
1018                        Some(mode) => handler.unset_mode(mode),
1019                        None => {
1020                            unhandled!();
1021                            return;
1022                        },
1023                    }
1024                }
1025            },
1026            ('M', None) => handler.delete_lines(Line(arg_or_default!(idx: 0, default: 1) as usize)),
1027            ('X', None) => {
1028                handler.erase_chars(Column(arg_or_default!(idx: 0, default: 1) as usize))
1029            },
1030            ('P', None) => {
1031                handler.delete_chars(Column(arg_or_default!(idx: 0, default: 1) as usize))
1032            },
1033            ('Z', None) => handler.move_backward_tabs(arg_or_default!(idx: 0, default: 1)),
1034            ('d', None) => {
1035                handler.goto_line(Line(arg_or_default!(idx: 0, default: 1) as usize - 1))
1036            },
1037            ('h', intermediate) => {
1038                for arg in args {
1039                    match Mode::from_primitive(intermediate, *arg) {
1040                        Some(mode) => handler.set_mode(mode),
1041                        None => {
1042                            unhandled!();
1043                            return;
1044                        },
1045                    }
1046                }
1047            },
1048            ('m', None) => {
1049                if args.is_empty() {
1050                    handler.terminal_attribute(Attr::Reset);
1051                } else {
1052                    for attr in attrs_from_sgr_parameters(args) {
1053                        match attr {
1054                            Some(attr) => handler.terminal_attribute(attr),
1055                            None => {
1056                                unhandled!();
1057                                return;
1058                            },
1059                        }
1060                    }
1061                }
1062            },
1063            ('n', None) => {
1064                handler.device_status(writer, arg_or_default!(idx: 0, default: 0) as usize)
1065            },
1066            ('q', Some(b' ')) => {
1067                // DECSCUSR (CSI Ps SP q) -- Set Cursor Style
1068                let style = match arg_or_default!(idx: 0, default: 0) {
1069                    0 => None,
1070                    1 | 2 => Some(CursorStyle::Block),
1071                    3 | 4 => Some(CursorStyle::Underline),
1072                    5 | 6 => Some(CursorStyle::Beam),
1073                    _ => {
1074                        unhandled!();
1075                        return;
1076                    },
1077                };
1078
1079                handler.set_cursor_style(style);
1080            },
1081            ('r', None) => {
1082                let top = arg_or_default!(idx: 0, default: 1) as usize;
1083                let bottom = arg_or_default!(idx: 1, default: handler.lines().0 as _) as usize;
1084
1085                handler.set_scrolling_region(top, bottom);
1086            },
1087            ('s', None) => handler.save_cursor_position(),
1088            ('u', None) => handler.restore_cursor_position(),
1089            _ => unhandled!(),
1090        }
1091    }
1092
1093    #[inline]
1094    fn esc_dispatch(&mut self, params: &[i64], intermediates: &[u8], _ignore: bool, byte: u8) {
1095        macro_rules! unhandled {
1096            () => {{
1097                debug!(
1098                    "[unhandled] esc_dispatch params={:?}, ints={:?}, byte={:?} ({:02x})",
1099                    params, intermediates, byte as char, byte
1100                );
1101            }};
1102        }
1103
1104        macro_rules! configure_charset {
1105            ($charset:path, $intermediate:expr) => {{
1106                let index: CharsetIndex = match $intermediate {
1107                    Some(b'(') => CharsetIndex::G0,
1108                    Some(b')') => CharsetIndex::G1,
1109                    Some(b'*') => CharsetIndex::G2,
1110                    Some(b'+') => CharsetIndex::G3,
1111                    _ => {
1112                        unhandled!();
1113                        return;
1114                    },
1115                };
1116                self.handler.configure_charset(index, $charset)
1117            }};
1118        }
1119
1120        match (byte, intermediates.get(0)) {
1121            (b'B', intermediate) => configure_charset!(StandardCharset::Ascii, intermediate),
1122            (b'D', None) => self.handler.linefeed(),
1123            (b'E', None) => {
1124                self.handler.linefeed();
1125                self.handler.carriage_return();
1126            },
1127            (b'H', None) => self.handler.set_horizontal_tabstop(),
1128            (b'M', None) => self.handler.reverse_index(),
1129            (b'Z', None) => self.handler.identify_terminal(self.writer),
1130            (b'c', None) => self.handler.reset_state(),
1131            (b'0', intermediate) => {
1132                configure_charset!(StandardCharset::SpecialCharacterAndLineDrawing, intermediate)
1133            },
1134            (b'7', None) => self.handler.save_cursor_position(),
1135            (b'8', Some(b'#')) => self.handler.decaln(),
1136            (b'8', None) => self.handler.restore_cursor_position(),
1137            (b'=', None) => self.handler.set_keypad_application_mode(),
1138            (b'>', None) => self.handler.unset_keypad_application_mode(),
1139            // String terminator, do nothing (parser handles as string terminator)
1140            (b'\\', None) => (),
1141            _ => unhandled!(),
1142        }
1143    }
1144}
1145
1146fn attrs_from_sgr_parameters(parameters: &[i64]) -> Vec<Option<Attr>> {
1147    // Sometimes a C-style for loop is just what you need
1148    let mut i = 0; // C-for initializer
1149    let mut attrs = Vec::with_capacity(parameters.len());
1150    loop {
1151        if i >= parameters.len() {
1152            // C-for condition
1153            break;
1154        }
1155
1156        let attr = match parameters[i] {
1157            0 => Some(Attr::Reset),
1158            1 => Some(Attr::Bold),
1159            2 => Some(Attr::Dim),
1160            3 => Some(Attr::Italic),
1161            4 => Some(Attr::Underline),
1162            5 => Some(Attr::BlinkSlow),
1163            6 => Some(Attr::BlinkFast),
1164            7 => Some(Attr::Reverse),
1165            8 => Some(Attr::Hidden),
1166            9 => Some(Attr::Strike),
1167            21 => Some(Attr::CancelBold),
1168            22 => Some(Attr::CancelBoldDim),
1169            23 => Some(Attr::CancelItalic),
1170            24 => Some(Attr::CancelUnderline),
1171            25 => Some(Attr::CancelBlink),
1172            27 => Some(Attr::CancelReverse),
1173            28 => Some(Attr::CancelHidden),
1174            29 => Some(Attr::CancelStrike),
1175            30 => Some(Attr::Foreground(Color::Named(NamedColor::Black))),
1176            31 => Some(Attr::Foreground(Color::Named(NamedColor::Red))),
1177            32 => Some(Attr::Foreground(Color::Named(NamedColor::Green))),
1178            33 => Some(Attr::Foreground(Color::Named(NamedColor::Yellow))),
1179            34 => Some(Attr::Foreground(Color::Named(NamedColor::Blue))),
1180            35 => Some(Attr::Foreground(Color::Named(NamedColor::Magenta))),
1181            36 => Some(Attr::Foreground(Color::Named(NamedColor::Cyan))),
1182            37 => Some(Attr::Foreground(Color::Named(NamedColor::White))),
1183            38 => {
1184                let mut start = 0;
1185                if let Some(color) = parse_sgr_color(&parameters[i..], &mut start) {
1186                    i += start;
1187                    Some(Attr::Foreground(color))
1188                } else {
1189                    None
1190                }
1191            },
1192            39 => Some(Attr::Foreground(Color::Named(NamedColor::Foreground))),
1193            40 => Some(Attr::Background(Color::Named(NamedColor::Black))),
1194            41 => Some(Attr::Background(Color::Named(NamedColor::Red))),
1195            42 => Some(Attr::Background(Color::Named(NamedColor::Green))),
1196            43 => Some(Attr::Background(Color::Named(NamedColor::Yellow))),
1197            44 => Some(Attr::Background(Color::Named(NamedColor::Blue))),
1198            45 => Some(Attr::Background(Color::Named(NamedColor::Magenta))),
1199            46 => Some(Attr::Background(Color::Named(NamedColor::Cyan))),
1200            47 => Some(Attr::Background(Color::Named(NamedColor::White))),
1201            48 => {
1202                let mut start = 0;
1203                if let Some(color) = parse_sgr_color(&parameters[i..], &mut start) {
1204                    i += start;
1205                    Some(Attr::Background(color))
1206                } else {
1207                    None
1208                }
1209            },
1210            49 => Some(Attr::Background(Color::Named(NamedColor::Background))),
1211            90 => Some(Attr::Foreground(Color::Named(NamedColor::BrightBlack))),
1212            91 => Some(Attr::Foreground(Color::Named(NamedColor::BrightRed))),
1213            92 => Some(Attr::Foreground(Color::Named(NamedColor::BrightGreen))),
1214            93 => Some(Attr::Foreground(Color::Named(NamedColor::BrightYellow))),
1215            94 => Some(Attr::Foreground(Color::Named(NamedColor::BrightBlue))),
1216            95 => Some(Attr::Foreground(Color::Named(NamedColor::BrightMagenta))),
1217            96 => Some(Attr::Foreground(Color::Named(NamedColor::BrightCyan))),
1218            97 => Some(Attr::Foreground(Color::Named(NamedColor::BrightWhite))),
1219            100 => Some(Attr::Background(Color::Named(NamedColor::BrightBlack))),
1220            101 => Some(Attr::Background(Color::Named(NamedColor::BrightRed))),
1221            102 => Some(Attr::Background(Color::Named(NamedColor::BrightGreen))),
1222            103 => Some(Attr::Background(Color::Named(NamedColor::BrightYellow))),
1223            104 => Some(Attr::Background(Color::Named(NamedColor::BrightBlue))),
1224            105 => Some(Attr::Background(Color::Named(NamedColor::BrightMagenta))),
1225            106 => Some(Attr::Background(Color::Named(NamedColor::BrightCyan))),
1226            107 => Some(Attr::Background(Color::Named(NamedColor::BrightWhite))),
1227            _ => None,
1228        };
1229
1230        attrs.push(attr);
1231
1232        i += 1; // C-for expr
1233    }
1234    attrs
1235}
1236
1237/// Parse a color specifier from list of attributes
1238fn parse_sgr_color(attrs: &[i64], i: &mut usize) -> Option<Color> {
1239    if attrs.len() < 2 {
1240        return None;
1241    }
1242
1243    match attrs[*i + 1] {
1244        2 => {
1245            // RGB color spec
1246            if attrs.len() < 5 {
1247                debug!("Expected RGB color spec; got {:?}", attrs);
1248                return None;
1249            }
1250
1251            let r = attrs[*i + 2];
1252            let g = attrs[*i + 3];
1253            let b = attrs[*i + 4];
1254
1255            *i += 4;
1256
1257            let range = 0..256;
1258            if !range.contains(&r) || !range.contains(&g) || !range.contains(&b) {
1259                debug!("Invalid RGB color spec: ({}, {}, {})", r, g, b);
1260                return None;
1261            }
1262
1263            Some(Color::Spec(Rgb { r: r as u8, g: g as u8, b: b as u8 }))
1264        },
1265        5 => {
1266            if attrs.len() < 3 {
1267                debug!("Expected color index; got {:?}", attrs);
1268                None
1269            } else {
1270                *i += 2;
1271                let idx = attrs[*i];
1272                match idx {
1273                    0..=255 => Some(Color::Indexed(idx as u8)),
1274                    _ => {
1275                        debug!("Invalid color index: {}", idx);
1276                        None
1277                    },
1278                }
1279            }
1280        },
1281        _ => {
1282            debug!("Unexpected color attr: {}", attrs[*i + 1]);
1283            None
1284        },
1285    }
1286}
1287
1288/// C0 set of 7-bit control characters (from ANSI X3.4-1977).
1289#[allow(non_snake_case)]
1290pub mod C0 {
1291    /// Null filler, terminal should ignore this character
1292    pub const NUL: u8 = 0x00;
1293    /// Start of Header
1294    pub const SOH: u8 = 0x01;
1295    /// Start of Text, implied end of header
1296    pub const STX: u8 = 0x02;
1297    /// End of Text, causes some terminal to respond with ACK or NAK
1298    pub const ETX: u8 = 0x03;
1299    /// End of Transmission
1300    pub const EOT: u8 = 0x04;
1301    /// Enquiry, causes terminal to send ANSWER-BACK ID
1302    pub const ENQ: u8 = 0x05;
1303    /// Acknowledge, usually sent by terminal in response to ETX
1304    pub const ACK: u8 = 0x06;
1305    /// Bell, triggers the bell, buzzer, or beeper on the terminal
1306    pub const BEL: u8 = 0x07;
1307    /// Backspace, can be used to define overstruck characters
1308    pub const BS: u8 = 0x08;
1309    /// Horizontal Tabulation, move to next predetermined position
1310    pub const HT: u8 = 0x09;
1311    /// Linefeed, move to same position on next line (see also NL)
1312    pub const LF: u8 = 0x0A;
1313    /// Vertical Tabulation, move to next predetermined line
1314    pub const VT: u8 = 0x0B;
1315    /// Form Feed, move to next form or page
1316    pub const FF: u8 = 0x0C;
1317    /// Carriage Return, move to first character of current line
1318    pub const CR: u8 = 0x0D;
1319    /// Shift Out, switch to G1 (other half of character set)
1320    pub const SO: u8 = 0x0E;
1321    /// Shift In, switch to G0 (normal half of character set)
1322    pub const SI: u8 = 0x0F;
1323    /// Data Link Escape, interpret next control character specially
1324    pub const DLE: u8 = 0x10;
1325    /// (DC1) Terminal is allowed to resume transmitting
1326    pub const XON: u8 = 0x11;
1327    /// Device Control 2, causes ASR-33 to activate paper-tape reader
1328    pub const DC2: u8 = 0x12;
1329    /// (DC2) Terminal must pause and refrain from transmitting
1330    pub const XOFF: u8 = 0x13;
1331    /// Device Control 4, causes ASR-33 to deactivate paper-tape reader
1332    pub const DC4: u8 = 0x14;
1333    /// Negative Acknowledge, used sometimes with ETX and ACK
1334    pub const NAK: u8 = 0x15;
1335    /// Synchronous Idle, used to maintain timing in Sync communication
1336    pub const SYN: u8 = 0x16;
1337    /// End of Transmission block
1338    pub const ETB: u8 = 0x17;
1339    /// Cancel (makes VT100 abort current escape sequence if any)
1340    pub const CAN: u8 = 0x18;
1341    /// End of Medium
1342    pub const EM: u8 = 0x19;
1343    /// Substitute (VT100 uses this to display parity errors)
1344    pub const SUB: u8 = 0x1A;
1345    /// Prefix to an escape sequence
1346    pub const ESC: u8 = 0x1B;
1347    /// File Separator
1348    pub const FS: u8 = 0x1C;
1349    /// Group Separator
1350    pub const GS: u8 = 0x1D;
1351    /// Record Separator (sent by VT132 in block-transfer mode)
1352    pub const RS: u8 = 0x1E;
1353    /// Unit Separator
1354    pub const US: u8 = 0x1F;
1355    /// Delete, should be ignored by terminal
1356    pub const DEL: u8 = 0x7f;
1357}
1358
1359// Tests for parsing escape sequences
1360//
1361// Byte sequences used in these tests are recording of pty stdout.
1362#[cfg(test)]
1363mod tests {
1364    use super::{
1365        parse_number, xparse_color, Attr, CharsetIndex, Color, Handler, Processor, StandardCharset,
1366        TermInfo,
1367    };
1368    use crate::index::{Column, Line};
1369    use crate::term::color::Rgb;
1370    use std::io;
1371
1372    struct MockHandler {
1373        index: CharsetIndex,
1374        charset: StandardCharset,
1375        attr: Option<Attr>,
1376        identity_reported: bool,
1377    }
1378
1379    impl Handler for MockHandler {
1380        fn terminal_attribute(&mut self, attr: Attr) {
1381            self.attr = Some(attr);
1382        }
1383
1384        fn configure_charset(&mut self, index: CharsetIndex, charset: StandardCharset) {
1385            self.index = index;
1386            self.charset = charset;
1387        }
1388
1389        fn set_active_charset(&mut self, index: CharsetIndex) {
1390            self.index = index;
1391        }
1392
1393        fn identify_terminal<W: io::Write>(&mut self, _: &mut W) {
1394            self.identity_reported = true;
1395        }
1396
1397        fn reset_state(&mut self) {
1398            *self = Self::default();
1399        }
1400    }
1401
1402    impl TermInfo for MockHandler {
1403        fn lines(&self) -> Line {
1404            Line(200)
1405        }
1406
1407        fn cols(&self) -> Column {
1408            Column(90)
1409        }
1410    }
1411
1412    impl Default for MockHandler {
1413        fn default() -> MockHandler {
1414            MockHandler {
1415                index: CharsetIndex::G0,
1416                charset: StandardCharset::Ascii,
1417                attr: None,
1418                identity_reported: false,
1419            }
1420        }
1421    }
1422
1423    #[test]
1424    fn parse_control_attribute() {
1425        static BYTES: &[u8] = &[0x1b, b'[', b'1', b'm'];
1426
1427        let mut parser = Processor::new();
1428        let mut handler = MockHandler::default();
1429
1430        for byte in &BYTES[..] {
1431            parser.advance(&mut handler, *byte, &mut io::sink());
1432        }
1433
1434        assert_eq!(handler.attr, Some(Attr::Bold));
1435    }
1436
1437    #[test]
1438    fn parse_terminal_identity_csi() {
1439        let bytes: &[u8] = &[0x1b, b'[', b'1', b'c'];
1440
1441        let mut parser = Processor::new();
1442        let mut handler = MockHandler::default();
1443
1444        for byte in &bytes[..] {
1445            parser.advance(&mut handler, *byte, &mut io::sink());
1446        }
1447
1448        assert!(!handler.identity_reported);
1449        handler.reset_state();
1450
1451        let bytes: &[u8] = &[0x1b, b'[', b'c'];
1452
1453        for byte in &bytes[..] {
1454            parser.advance(&mut handler, *byte, &mut io::sink());
1455        }
1456
1457        assert!(handler.identity_reported);
1458        handler.reset_state();
1459
1460        let bytes: &[u8] = &[0x1b, b'[', b'0', b'c'];
1461
1462        for byte in &bytes[..] {
1463            parser.advance(&mut handler, *byte, &mut io::sink());
1464        }
1465
1466        assert!(handler.identity_reported);
1467    }
1468
1469    #[test]
1470    fn parse_terminal_identity_esc() {
1471        let bytes: &[u8] = &[0x1b, b'Z'];
1472
1473        let mut parser = Processor::new();
1474        let mut handler = MockHandler::default();
1475
1476        for byte in &bytes[..] {
1477            parser.advance(&mut handler, *byte, &mut io::sink());
1478        }
1479
1480        assert!(handler.identity_reported);
1481        handler.reset_state();
1482
1483        let bytes: &[u8] = &[0x1b, b'#', b'Z'];
1484
1485        let mut parser = Processor::new();
1486        let mut handler = MockHandler::default();
1487
1488        for byte in &bytes[..] {
1489            parser.advance(&mut handler, *byte, &mut io::sink());
1490        }
1491
1492        assert!(!handler.identity_reported);
1493        handler.reset_state();
1494    }
1495
1496    #[test]
1497    fn parse_truecolor_attr() {
1498        static BYTES: &[u8] = &[
1499            0x1b, b'[', b'3', b'8', b';', b'2', b';', b'1', b'2', b'8', b';', b'6', b'6', b';',
1500            b'2', b'5', b'5', b'm',
1501        ];
1502
1503        let mut parser = Processor::new();
1504        let mut handler = MockHandler::default();
1505
1506        for byte in &BYTES[..] {
1507            parser.advance(&mut handler, *byte, &mut io::sink());
1508        }
1509
1510        let spec = Rgb { r: 128, g: 66, b: 255 };
1511
1512        assert_eq!(handler.attr, Some(Attr::Foreground(Color::Spec(spec))));
1513    }
1514
1515    /// No exactly a test; useful for debugging
1516    #[test]
1517    fn parse_zsh_startup() {
1518        static BYTES: &[u8] = &[
1519            0x1b, b'[', b'1', b'm', 0x1b, b'[', b'7', b'm', b'%', 0x1b, b'[', b'2', b'7', b'm',
1520            0x1b, b'[', b'1', b'm', 0x1b, b'[', b'0', b'm', b' ', b' ', b' ', b' ', b' ', b' ',
1521            b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ',
1522            b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ',
1523            b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ',
1524            b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ',
1525            b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ',
1526            b' ', b' ', b' ', b'\r', b' ', b'\r', b'\r', 0x1b, b'[', b'0', b'm', 0x1b, b'[', b'2',
1527            b'7', b'm', 0x1b, b'[', b'2', b'4', b'm', 0x1b, b'[', b'J', b'j', b'w', b'i', b'l',
1528            b'm', b'@', b'j', b'w', b'i', b'l', b'm', b'-', b'd', b'e', b's', b'k', b' ', 0x1b,
1529            b'[', b'0', b'1', b';', b'3', b'2', b'm', 0xe2, 0x9e, 0x9c, b' ', 0x1b, b'[', b'0',
1530            b'1', b';', b'3', b'2', b'm', b' ', 0x1b, b'[', b'3', b'6', b'm', b'~', b'/', b'c',
1531            b'o', b'd', b'e',
1532        ];
1533
1534        let mut handler = MockHandler::default();
1535        let mut parser = Processor::new();
1536
1537        for byte in &BYTES[..] {
1538            parser.advance(&mut handler, *byte, &mut io::sink());
1539        }
1540    }
1541
1542    #[test]
1543    fn parse_designate_g0_as_line_drawing() {
1544        static BYTES: &[u8] = &[0x1b, b'(', b'0'];
1545        let mut parser = Processor::new();
1546        let mut handler = MockHandler::default();
1547
1548        for byte in &BYTES[..] {
1549            parser.advance(&mut handler, *byte, &mut io::sink());
1550        }
1551
1552        assert_eq!(handler.index, CharsetIndex::G0);
1553        assert_eq!(handler.charset, StandardCharset::SpecialCharacterAndLineDrawing);
1554    }
1555
1556    #[test]
1557    fn parse_designate_g1_as_line_drawing_and_invoke() {
1558        static BYTES: &[u8] = &[0x1b, b')', b'0', 0x0e];
1559        let mut parser = Processor::new();
1560        let mut handler = MockHandler::default();
1561
1562        for byte in &BYTES[..3] {
1563            parser.advance(&mut handler, *byte, &mut io::sink());
1564        }
1565
1566        assert_eq!(handler.index, CharsetIndex::G1);
1567        assert_eq!(handler.charset, StandardCharset::SpecialCharacterAndLineDrawing);
1568
1569        let mut handler = MockHandler::default();
1570        parser.advance(&mut handler, BYTES[3], &mut io::sink());
1571
1572        assert_eq!(handler.index, CharsetIndex::G1);
1573    }
1574
1575    #[test]
1576    fn parse_valid_rgb_colors() {
1577        assert_eq!(xparse_color(b"rgb:f/e/d"), Some(Rgb { r: 0xff, g: 0xee, b: 0xdd }));
1578        assert_eq!(xparse_color(b"rgb:11/aa/ff"), Some(Rgb { r: 0x11, g: 0xaa, b: 0xff }));
1579        assert_eq!(xparse_color(b"rgb:f/ed1/cb23"), Some(Rgb { r: 0xff, g: 0xec, b: 0xca }));
1580        assert_eq!(xparse_color(b"rgb:ffff/0/0"), Some(Rgb { r: 0xff, g: 0x0, b: 0x0 }));
1581    }
1582
1583    #[test]
1584    fn parse_valid_legacy_rgb_colors() {
1585        assert_eq!(xparse_color(b"#1af"), Some(Rgb { r: 0x10, g: 0xa0, b: 0xf0 }));
1586        assert_eq!(xparse_color(b"#11aaff"), Some(Rgb { r: 0x11, g: 0xaa, b: 0xff }));
1587        assert_eq!(xparse_color(b"#110aa0ff0"), Some(Rgb { r: 0x11, g: 0xaa, b: 0xff }));
1588        assert_eq!(xparse_color(b"#1100aa00ff00"), Some(Rgb { r: 0x11, g: 0xaa, b: 0xff }));
1589    }
1590
1591    #[test]
1592    fn parse_invalid_rgb_colors() {
1593        assert_eq!(xparse_color(b"rgb:0//"), None);
1594        assert_eq!(xparse_color(b"rgb://///"), None);
1595    }
1596
1597    #[test]
1598    fn parse_invalid_legacy_rgb_colors() {
1599        assert_eq!(xparse_color(b"#"), None);
1600        assert_eq!(xparse_color(b"#f"), None);
1601    }
1602
1603    #[test]
1604    fn parse_invalid_number() {
1605        assert_eq!(parse_number(b"1abc"), None);
1606    }
1607
1608    #[test]
1609    fn parse_valid_number() {
1610        assert_eq!(parse_number(b"123"), Some(123));
1611    }
1612
1613    #[test]
1614    fn parse_number_too_large() {
1615        assert_eq!(parse_number(b"321"), None);
1616    }
1617}