diff --git a/CHANGELOG.md b/CHANGELOG.md index 06862ac..1487179 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -38,6 +38,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - On Linux, respect fontconfig's `embeddedbitmap` configuration option - Selecting trailing tab with semantic expansion - URL parser incorrectly handling Markdown URLs and angled brackets +- Intermediate bytes of CSI sequences not checked ## 0.3.3 diff --git a/alacritty_terminal/src/ansi.rs b/alacritty_terminal/src/ansi.rs index b4cf447..1914e95 100644 --- a/alacritty_terminal/src/ansi.rs +++ b/alacritty_terminal/src/ansi.rs @@ -890,11 +890,7 @@ where } #[inline] - fn csi_dispatch(&mut self, args: &[i64], intermediates: &[u8], _ignore: bool, action: char) { - let private = intermediates.get(0).map(|b| *b == b'?').unwrap_or(false); - let handler = &mut self.handler; - let writer = &mut self.writer; - + fn csi_dispatch(&mut self, args: &[i64], intermediates: &[u8], has_ignored_intermediates: bool, action: char) { macro_rules! unhandled { () => {{ debug!( @@ -913,12 +909,19 @@ where }; } - match action { - '@' => handler.insert_blank(Column(arg_or_default!(idx: 0, default: 1) as usize)), - 'A' => { + if has_ignored_intermediates || intermediates.len() > 1 { + unhandled!(); + } + + let handler = &mut self.handler; + let writer = &mut self.writer; + + match (action, intermediates.get(0)) { + ('@', None) => handler.insert_blank(Column(arg_or_default!(idx: 0, default: 1) as usize)), + ('A', None) => { handler.move_up(Line(arg_or_default!(idx: 0, default: 1) as usize)); }, - 'b' => { + ('b', None) => { if let Some(c) = self._state.preceding_char { for _ in 0..arg_or_default!(idx: 0, default: 1) { handler.input(c); @@ -927,13 +930,13 @@ where debug!("tried to repeat with no preceding char"); } }, - 'B' | 'e' => handler.move_down(Line(arg_or_default!(idx: 0, default: 1) as usize)), - 'c' => handler.identify_terminal(writer), - 'C' | 'a' => handler.move_forward(Column(arg_or_default!(idx: 0, default: 1) as usize)), - 'D' => handler.move_backward(Column(arg_or_default!(idx: 0, default: 1) as usize)), - 'E' => handler.move_down_and_cr(Line(arg_or_default!(idx: 0, default: 1) as usize)), - 'F' => handler.move_up_and_cr(Line(arg_or_default!(idx: 0, default: 1) as usize)), - 'g' => { + ('B', None) | ('e', None) => handler.move_down(Line(arg_or_default!(idx: 0, default: 1) as usize)), + ('c', None) => handler.identify_terminal(writer), + ('C', None) | ('a', None) => handler.move_forward(Column(arg_or_default!(idx: 0, default: 1) as usize)), + ('D', None) => handler.move_backward(Column(arg_or_default!(idx: 0, default: 1) as usize)), + ('E', None) => handler.move_down_and_cr(Line(arg_or_default!(idx: 0, default: 1) as usize)), + ('F', None) => handler.move_up_and_cr(Line(arg_or_default!(idx: 0, default: 1) as usize)), + ('g', None) => { let mode = match arg_or_default!(idx: 0, default: 0) { 0 => TabulationClearMode::Current, 3 => TabulationClearMode::All, @@ -942,14 +945,14 @@ where handler.clear_tabs(mode); }, - 'G' | '`' => handler.goto_col(Column(arg_or_default!(idx: 0, default: 1) as usize - 1)), - 'H' | 'f' => { + ('G', None) | ('`', None) => handler.goto_col(Column(arg_or_default!(idx: 0, default: 1) as usize - 1)), + ('H', None) | ('f', None) => { let y = arg_or_default!(idx: 0, default: 1) as usize; let x = arg_or_default!(idx: 1, default: 1) as usize; handler.goto(Line(y - 1), Column(x - 1)); }, - 'I' => handler.move_forward_tabs(arg_or_default!(idx: 0, default: 1)), - 'J' => { + ('I', None) => handler.move_forward_tabs(arg_or_default!(idx: 0, default: 1)), + ('J', None) => { let mode = match arg_or_default!(idx: 0, default: 0) { 0 => ClearMode::Below, 1 => ClearMode::Above, @@ -960,7 +963,7 @@ where handler.clear_screen(mode); }, - 'K' => { + ('K', None) => { let mode = match arg_or_default!(idx: 0, default: 0) { 0 => LineClearMode::Right, 1 => LineClearMode::Left, @@ -970,129 +973,68 @@ where handler.clear_line(mode); }, - 'S' => handler.scroll_up(Line(arg_or_default!(idx: 0, default: 1) as usize)), - 'T' => handler.scroll_down(Line(arg_or_default!(idx: 0, default: 1) as usize)), - 'L' => handler.insert_blank_lines(Line(arg_or_default!(idx: 0, default: 1) as usize)), - 'l' => { + ('S', None) => handler.scroll_up(Line(arg_or_default!(idx: 0, default: 1) as usize)), + ('T', None) => handler.scroll_down(Line(arg_or_default!(idx: 0, default: 1) as usize)), + ('L', None) => handler.insert_blank_lines(Line(arg_or_default!(idx: 0, default: 1) as usize)), + ('l', intermediate) => { + let is_private_mode = match intermediate { + Some(b'?') => true, + None => false, + _ => unhandled!(), + }; for arg in args { - let mode = Mode::from_primitive(private, *arg); + let mode = Mode::from_primitive(is_private_mode, *arg); match mode { Some(mode) => handler.unset_mode(mode), None => unhandled!(), } } }, - 'M' => handler.delete_lines(Line(arg_or_default!(idx: 0, default: 1) as usize)), - 'X' => handler.erase_chars(Column(arg_or_default!(idx: 0, default: 1) as usize)), - 'P' => handler.delete_chars(Column(arg_or_default!(idx: 0, default: 1) as usize)), - 'Z' => handler.move_backward_tabs(arg_or_default!(idx: 0, default: 1)), - 'd' => handler.goto_line(Line(arg_or_default!(idx: 0, default: 1) as usize - 1)), - 'h' => { + ('M', None) => handler.delete_lines(Line(arg_or_default!(idx: 0, default: 1) as usize)), + ('X', None) => handler.erase_chars(Column(arg_or_default!(idx: 0, default: 1) as usize)), + ('P', None) => handler.delete_chars(Column(arg_or_default!(idx: 0, default: 1) as usize)), + ('Z', None) => handler.move_backward_tabs(arg_or_default!(idx: 0, default: 1)), + ('d', None) => handler.goto_line(Line(arg_or_default!(idx: 0, default: 1) as usize - 1)), + ('h', intermediate) => { + let is_private_mode = match intermediate { + Some(b'?') => true, + None => false, + _ => unhandled!(), + }; for arg in args { - let mode = Mode::from_primitive(private, *arg); + let mode = Mode::from_primitive(is_private_mode, *arg); match mode { Some(mode) => handler.set_mode(mode), None => unhandled!(), } } }, - 'm' => { - // Sometimes a C-style for loop is just what you need - let mut i = 0; // C-for initializer + ('m', None) => { if args.is_empty() { handler.terminal_attribute(Attr::Reset); - return; - } - loop { - if i >= args.len() { - // C-for condition - break; + } else { + for attr in attrs_from_sgr_parameters(args) { + match attr { + Some(attr) => handler.terminal_attribute(attr), + None => unhandled!(), + } } - - let attr = match args[i] { - 0 => Attr::Reset, - 1 => Attr::Bold, - 2 => Attr::Dim, - 3 => Attr::Italic, - 4 => Attr::Underscore, - 5 => Attr::BlinkSlow, - 6 => Attr::BlinkFast, - 7 => Attr::Reverse, - 8 => Attr::Hidden, - 9 => Attr::Strike, - 21 => Attr::CancelBold, - 22 => Attr::CancelBoldDim, - 23 => Attr::CancelItalic, - 24 => Attr::CancelUnderline, - 25 => Attr::CancelBlink, - 27 => Attr::CancelReverse, - 28 => Attr::CancelHidden, - 29 => Attr::CancelStrike, - 30 => Attr::Foreground(Color::Named(NamedColor::Black)), - 31 => Attr::Foreground(Color::Named(NamedColor::Red)), - 32 => Attr::Foreground(Color::Named(NamedColor::Green)), - 33 => Attr::Foreground(Color::Named(NamedColor::Yellow)), - 34 => Attr::Foreground(Color::Named(NamedColor::Blue)), - 35 => Attr::Foreground(Color::Named(NamedColor::Magenta)), - 36 => Attr::Foreground(Color::Named(NamedColor::Cyan)), - 37 => Attr::Foreground(Color::Named(NamedColor::White)), - 38 => { - let mut start = 0; - if let Some(color) = parse_color(&args[i..], &mut start) { - i += start; - Attr::Foreground(color) - } else { - break; - } - }, - 39 => Attr::Foreground(Color::Named(NamedColor::Foreground)), - 40 => Attr::Background(Color::Named(NamedColor::Black)), - 41 => Attr::Background(Color::Named(NamedColor::Red)), - 42 => Attr::Background(Color::Named(NamedColor::Green)), - 43 => Attr::Background(Color::Named(NamedColor::Yellow)), - 44 => Attr::Background(Color::Named(NamedColor::Blue)), - 45 => Attr::Background(Color::Named(NamedColor::Magenta)), - 46 => Attr::Background(Color::Named(NamedColor::Cyan)), - 47 => Attr::Background(Color::Named(NamedColor::White)), - 48 => { - let mut start = 0; - if let Some(color) = parse_color(&args[i..], &mut start) { - i += start; - Attr::Background(color) - } else { - break; - } - }, - 49 => Attr::Background(Color::Named(NamedColor::Background)), - 90 => Attr::Foreground(Color::Named(NamedColor::BrightBlack)), - 91 => Attr::Foreground(Color::Named(NamedColor::BrightRed)), - 92 => Attr::Foreground(Color::Named(NamedColor::BrightGreen)), - 93 => Attr::Foreground(Color::Named(NamedColor::BrightYellow)), - 94 => Attr::Foreground(Color::Named(NamedColor::BrightBlue)), - 95 => Attr::Foreground(Color::Named(NamedColor::BrightMagenta)), - 96 => Attr::Foreground(Color::Named(NamedColor::BrightCyan)), - 97 => Attr::Foreground(Color::Named(NamedColor::BrightWhite)), - 100 => Attr::Background(Color::Named(NamedColor::BrightBlack)), - 101 => Attr::Background(Color::Named(NamedColor::BrightRed)), - 102 => Attr::Background(Color::Named(NamedColor::BrightGreen)), - 103 => Attr::Background(Color::Named(NamedColor::BrightYellow)), - 104 => Attr::Background(Color::Named(NamedColor::BrightBlue)), - 105 => Attr::Background(Color::Named(NamedColor::BrightMagenta)), - 106 => Attr::Background(Color::Named(NamedColor::BrightCyan)), - 107 => Attr::Background(Color::Named(NamedColor::BrightWhite)), - _ => unhandled!(), - }; - - handler.terminal_attribute(attr); - - i += 1; // C-for expr } }, - 'n' => handler.device_status(writer, arg_or_default!(idx: 0, default: 0) as usize), - 'r' => { - if private { - unhandled!(); - } + ('n', None) => handler.device_status(writer, arg_or_default!(idx: 0, default: 0) as usize), + ('q', Some(b' ')) => { + // DECSCUSR (CSI Ps SP q) -- Set Cursor Style + let style = match arg_or_default!(idx: 0, default: 0) { + 0 => None, + 1 | 2 => Some(CursorStyle::Block), + 3 | 4 => Some(CursorStyle::Underline), + 5 | 6 => Some(CursorStyle::Beam), + _ => unhandled!(), + }; + + handler.set_cursor_style(style); + }, + ('r', None) => { let arg0 = arg_or_default!(idx: 0, default: 1) as usize; let top = Line(arg0 - 1); // Bottom should be included in the range, but range end is not @@ -1104,19 +1046,8 @@ where handler.set_scrolling_region(top..bottom); }, - 's' => handler.save_cursor_position(), - 'u' => handler.restore_cursor_position(), - 'q' => { - let style = match arg_or_default!(idx: 0, default: 0) { - 0 => None, - 1 | 2 => Some(CursorStyle::Block), - 3 | 4 => Some(CursorStyle::Underline), - 5 | 6 => Some(CursorStyle::Beam), - _ => unhandled!(), - }; - - handler.set_cursor_style(style); - }, + ('s', None) => handler.save_cursor_position(), + ('u', None) => handler.restore_cursor_position(), _ => unhandled!(), } } @@ -1174,6 +1105,97 @@ where } } +fn attrs_from_sgr_parameters(parameters: &[i64]) -> Vec> { + // Sometimes a C-style for loop is just what you need + let mut i = 0; // C-for initializer + let mut attrs = Vec::with_capacity(parameters.len()); + loop { + if i >= parameters.len() { + // C-for condition + break; + } + + let attr = match parameters[i] { + 0 => Some(Attr::Reset), + 1 => Some(Attr::Bold), + 2 => Some(Attr::Dim), + 3 => Some(Attr::Italic), + 4 => Some(Attr::Underscore), + 5 => Some(Attr::BlinkSlow), + 6 => Some(Attr::BlinkFast), + 7 => Some(Attr::Reverse), + 8 => Some(Attr::Hidden), + 9 => Some(Attr::Strike), + 21 => Some(Attr::CancelBold), + 22 => Some(Attr::CancelBoldDim), + 23 => Some(Attr::CancelItalic), + 24 => Some(Attr::CancelUnderline), + 25 => Some(Attr::CancelBlink), + 27 => Some(Attr::CancelReverse), + 28 => Some(Attr::CancelHidden), + 29 => Some(Attr::CancelStrike), + 30 => Some(Attr::Foreground(Color::Named(NamedColor::Black))), + 31 => Some(Attr::Foreground(Color::Named(NamedColor::Red))), + 32 => Some(Attr::Foreground(Color::Named(NamedColor::Green))), + 33 => Some(Attr::Foreground(Color::Named(NamedColor::Yellow))), + 34 => Some(Attr::Foreground(Color::Named(NamedColor::Blue))), + 35 => Some(Attr::Foreground(Color::Named(NamedColor::Magenta))), + 36 => Some(Attr::Foreground(Color::Named(NamedColor::Cyan))), + 37 => Some(Attr::Foreground(Color::Named(NamedColor::White))), + 38 => { + let mut start = 0; + if let Some(color) = parse_color(¶meters[i..], &mut start) { + i += start; + Some(Attr::Foreground(color)) + } else { + None + } + }, + 39 => Some(Attr::Foreground(Color::Named(NamedColor::Foreground))), + 40 => Some(Attr::Background(Color::Named(NamedColor::Black))), + 41 => Some(Attr::Background(Color::Named(NamedColor::Red))), + 42 => Some(Attr::Background(Color::Named(NamedColor::Green))), + 43 => Some(Attr::Background(Color::Named(NamedColor::Yellow))), + 44 => Some(Attr::Background(Color::Named(NamedColor::Blue))), + 45 => Some(Attr::Background(Color::Named(NamedColor::Magenta))), + 46 => Some(Attr::Background(Color::Named(NamedColor::Cyan))), + 47 => Some(Attr::Background(Color::Named(NamedColor::White))), + 48 => { + let mut start = 0; + if let Some(color) = parse_color(¶meters[i..], &mut start) { + i += start; + Some(Attr::Background(color)) + } else { + None + } + }, + 49 => Some(Attr::Background(Color::Named(NamedColor::Background))), + 90 => Some(Attr::Foreground(Color::Named(NamedColor::BrightBlack))), + 91 => Some(Attr::Foreground(Color::Named(NamedColor::BrightRed))), + 92 => Some(Attr::Foreground(Color::Named(NamedColor::BrightGreen))), + 93 => Some(Attr::Foreground(Color::Named(NamedColor::BrightYellow))), + 94 => Some(Attr::Foreground(Color::Named(NamedColor::BrightBlue))), + 95 => Some(Attr::Foreground(Color::Named(NamedColor::BrightMagenta))), + 96 => Some(Attr::Foreground(Color::Named(NamedColor::BrightCyan))), + 97 => Some(Attr::Foreground(Color::Named(NamedColor::BrightWhite))), + 100 => Some(Attr::Background(Color::Named(NamedColor::BrightBlack))), + 101 => Some(Attr::Background(Color::Named(NamedColor::BrightRed))), + 102 => Some(Attr::Background(Color::Named(NamedColor::BrightGreen))), + 103 => Some(Attr::Background(Color::Named(NamedColor::BrightYellow))), + 104 => Some(Attr::Background(Color::Named(NamedColor::BrightBlue))), + 105 => Some(Attr::Background(Color::Named(NamedColor::BrightMagenta))), + 106 => Some(Attr::Background(Color::Named(NamedColor::BrightCyan))), + 107 => Some(Attr::Background(Color::Named(NamedColor::BrightWhite))), + _ => None, + }; + + attrs.push(attr); + + i += 1; // C-for expr + } + attrs +} + /// Parse a color specifier from list of attributes fn parse_color(attrs: &[i64], i: &mut usize) -> Option { if attrs.len() < 2 { diff --git a/alacritty_terminal/tests/ref.rs b/alacritty_terminal/tests/ref.rs index fb87adb..b6efb6a 100644 --- a/alacritty_terminal/tests/ref.rs +++ b/alacritty_terminal/tests/ref.rs @@ -53,6 +53,7 @@ ref_tests! { grid_reset row_reset zerowidth + selective_erasure } fn read_u8

(path: P) -> Vec diff --git a/alacritty_terminal/tests/ref/selective_erasure/alacritty.recording b/alacritty_terminal/tests/ref/selective_erasure/alacritty.recording new file mode 100644 index 0000000..aae1560 --- /dev/null +++ b/alacritty_terminal/tests/ref/selective_erasure/alacritty.recording @@ -0,0 +1,2 @@ +A[1"qB[2"qC[?2J +A[1"qB[2"qC[?2K diff --git a/alacritty_terminal/tests/ref/selective_erasure/config.json b/alacritty_terminal/tests/ref/selective_erasure/config.json new file mode 100644 index 0000000..6fc49fd --- /dev/null +++ b/alacritty_terminal/tests/ref/selective_erasure/config.json @@ -0,0 +1 @@ +{"history_size":0} \ No newline at end of file diff --git a/alacritty_terminal/tests/ref/selective_erasure/grid.json b/alacritty_terminal/tests/ref/selective_erasure/grid.json new file mode 100644 index 0000000..08632f7 --- /dev/null +++ b/alacritty_terminal/tests/ref/selective_erasure/grid.json @@ -0,0 +1 @@ +{"raw":{"inner":[{"inner":[{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0},"extra":[" "," "," "," "," "]},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0},"extra":[" "," "," "," "," "]},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0},"extra":[" "," "," "," "," "]},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0},"extra":[" "," "," "," "," "]},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0},"extra":[" "," "," "," "," "]},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0},"extra":[" "," "," "," "," "]},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0},"extra":[" "," "," "," "," "]},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0},"extra":[" "," "," "," "," "]},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0},"extra":[" "," "," "," "," "]},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0},"extra":[" "," "," "," "," "]}],"occ":0},{"inner":[{"c":"A","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0},"extra":[" "," "," "," "," "]},{"c":"B","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0},"extra":[" "," "," "," "," "]},{"c":"C","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0},"extra":[" "," "," "," "," "]},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0},"extra":[" "," "," "," "," "]},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0},"extra":[" "," "," "," "," "]},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0},"extra":[" "," "," "," "," "]},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0},"extra":[" "," "," "," "," "]},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0},"extra":[" "," "," "," "," "]},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0},"extra":[" "," "," "," "," "]},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0},"extra":[" "," "," "," "," "]}],"occ":3},{"inner":[{"c":"A","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0},"extra":[" "," "," "," "," "]},{"c":"B","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0},"extra":[" "," "," "," "," "]},{"c":"C","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0},"extra":[" "," "," "," "," "]},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0},"extra":[" "," "," "," "," "]},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0},"extra":[" "," "," "," "," "]},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0},"extra":[" "," "," "," "," "]},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0},"extra":[" "," "," "," "," "]},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0},"extra":[" "," "," "," "," "]},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0},"extra":[" "," "," "," "," "]},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0},"extra":[" "," "," "," "," "]}],"occ":3}],"zero":0,"visible_lines":2,"len":3},"cols":10,"lines":3,"display_offset":0,"scroll_limit":0,"max_scroll_limit":0,"url_highlight":null} \ No newline at end of file diff --git a/alacritty_terminal/tests/ref/selective_erasure/size.json b/alacritty_terminal/tests/ref/selective_erasure/size.json new file mode 100644 index 0000000..58891d8 --- /dev/null +++ b/alacritty_terminal/tests/ref/selective_erasure/size.json @@ -0,0 +1 @@ +{"width":70.0,"height":63.0,"cell_width":7.0,"cell_height":21.0,"padding_x":0.0,"padding_y":0.0,"dpr":1.0} \ No newline at end of file