From 33cfc52909bda1e2d5ceeae5397f254509d00539 Mon Sep 17 00:00:00 2001 From: Koichi Murase Date: Wed, 7 Aug 2019 07:59:16 +0900 Subject: [PATCH] Ignore unsupported CSI sequences Instead of ignoring unexpected intermediates in CSI escape sequences, the intermediates are now explicitly checked and the escape sequence is rejected when an unexpected intermediate is found. Fixes #2171. --- CHANGELOG.md | 1 + alacritty_terminal/src/ansi.rs | 298 ++++++++++-------- alacritty_terminal/tests/ref.rs | 1 + .../ref/selective_erasure/alacritty.recording | 2 + .../tests/ref/selective_erasure/config.json | 1 + .../tests/ref/selective_erasure/grid.json | 1 + .../tests/ref/selective_erasure/size.json | 1 + 7 files changed, 167 insertions(+), 138 deletions(-) create mode 100644 alacritty_terminal/tests/ref/selective_erasure/alacritty.recording create mode 100644 alacritty_terminal/tests/ref/selective_erasure/config.json create mode 100644 alacritty_terminal/tests/ref/selective_erasure/grid.json create mode 100644 alacritty_terminal/tests/ref/selective_erasure/size.json 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