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