summaryrefslogtreecommitdiffstats
path: root/zellij-client
diff options
context:
space:
mode:
authorAram Drevekenin <aram@poor.dev>2022-07-08 17:19:42 +0200
committerGitHub <noreply@github.com>2022-07-08 17:19:42 +0200
commitc89b416d764d80a72130821506f36157a08321e9 (patch)
tree6d1cc2d7fd5fddbb33fcbff192153700a9788bec /zellij-client
parent61deca80edb1632eb8ca22f627c6001b757021dc (diff)
feat(terminal): sixel support (#1557)
* work * work * work * work * work * more work * work * work * work * hack around stdin repeater * refactor(sixel): rename sixel structs * feat(sixel): render text above images * fix(sixel): reap images once they're past the end of the scrollbuffer * fix(sixel): display images in the middle of the line * fix(sixel): render crash * fix(sixel): react to SIGWINCH * fix(sixel): behave properly in alternate screen mode * fix(sixel): reap images on terminal reset * feat(sixel): handle DECSDM * fix(terminal): properly respond to XTSMGRAPHICS and device attributes with Sixel * Add comment * fix(sixel): hack for unknown event overflow until we fix the api * feat(input): query terminal for all OSC 4 colors and respond to them in a buggy way * fix(sixel): do not render corrupted image * feat(input): improve STDIN queries * fix(client): mistake in clear terminal attributes string * fix(ansi): report correct number of supported color registers * fix(sixel): reap images that are completely covered * style(comment): fix name * test(sixel): infra * test(sixel): cases and fixes * fix(sixel): forward dcs bytes to sixel parser * refactor(client): ansi stdin parser * refactor(output): cleanup * some refactorings * fix test * refactor(grid): sixel-grid / sixel-image-store * refactor(grid): grid debug method * refactor(grid): move various logic to sixel.rs * refactor(grid): remove unused methods * fix(sixel): work with multiple users * refactor(pane): remove unused z_index * style(fmt): prepend unused variable * style(fmt): rustfmt * fix(tests): various apis * chore(dependencies): use published version of sixel crates * style(fmt): rustfmt * style(fmt): rustfmt * style(lint): make clippy happy * style(lint): make clippy happy... again * style(lint): make clippy happy... again (chapter 2) * style(comment): remove unused * fix(colors): export COLORTERM and respond to XTVERSION * fix(test): color register count * fix(stdin): adjust STDIN sleep times
Diffstat (limited to 'zellij-client')
-rw-r--r--zellij-client/src/fake_client.rs8
-rw-r--r--zellij-client/src/input_handler.rs66
-rw-r--r--zellij-client/src/lib.rs29
-rw-r--r--zellij-client/src/os_input_output.rs4
-rw-r--r--zellij-client/src/stdin_ansi_parser.rs342
-rw-r--r--zellij-client/src/stdin_handler.rs31
-rw-r--r--zellij-client/src/unit/input_handler_tests.rs733
-rw-r--r--zellij-client/src/unit/snapshots/zellij_client__input_handler__input_handler_tests__pixel_info_sent_to_server.snap6
-rw-r--r--zellij-client/src/unit/snapshots/zellij_client__stdin_tests__pixel_info_sent_to_server.snap6
-rw-r--r--zellij-client/src/unit/stdin_tests.rs409
10 files changed, 687 insertions, 947 deletions
diff --git a/zellij-client/src/fake_client.rs b/zellij-client/src/fake_client.rs
index 7338d633a..88b41c9e8 100644
--- a/zellij-client/src/fake_client.rs
+++ b/zellij-client/src/fake_client.rs
@@ -2,12 +2,13 @@
//! and dispatch actions, that are specified through the command line.
//! Multiple actions at the same time can be dispatched.
use log::debug;
+use std::sync::{Arc, Mutex};
use std::{fs, path::PathBuf, thread};
use crate::{
command_is_executing::CommandIsExecuting, input_handler::input_actions,
- os_input_output::ClientOsApi, stdin_handler::stdin_loop, ClientInfo, ClientInstruction,
- InputInstruction,
+ os_input_output::ClientOsApi, stdin_ansi_parser::StdinAnsiParser, stdin_handler::stdin_loop,
+ ClientInfo, ClientInstruction, InputInstruction,
};
use zellij_utils::{
channels::{self, ChannelWithContext, SenderWithContext},
@@ -82,12 +83,13 @@ pub fn start_fake_client(
})
});
+ let stdin_ansi_parser = Arc::new(Mutex::new(StdinAnsiParser::new()));
let _stdin_thread = thread::Builder::new()
.name("stdin_handler".to_string())
.spawn({
let os_input = os_input.clone();
let send_input_instructions = send_input_instructions.clone();
- move || stdin_loop(os_input, send_input_instructions)
+ move || stdin_loop(os_input, send_input_instructions, stdin_ansi_parser)
});
let clients: Vec<ClientId>;
diff --git a/zellij-client/src/input_handler.rs b/zellij-client/src/input_handler.rs
index 3f92c5376..5f8cc0639 100644
--- a/zellij-client/src/input_handler.rs
+++ b/zellij-client/src/input_handler.rs
@@ -1,8 +1,7 @@
//! Main input logic.
use crate::{
- os_input_output::ClientOsApi,
- stdin_ansi_parser::{AnsiStdinInstructionOrKeys, StdinAnsiParser},
- ClientId, ClientInstruction, CommandIsExecuting, InputInstruction,
+ os_input_output::ClientOsApi, stdin_ansi_parser::AnsiStdinInstruction, ClientId,
+ ClientInstruction, CommandIsExecuting, InputInstruction,
};
use zellij_utils::{
channels::{Receiver, SenderWithContext, OPENCALLS},
@@ -69,19 +68,6 @@ impl InputHandler {
if self.options.mouse_mode.unwrap_or(true) {
self.os_input.enable_mouse();
}
- // <ESC>[14t => get text area size in pixels,
- // <ESC>[16t => get character cell size in pixels
- // <ESC>]11;?<ESC>\ => get background color
- // <ESC>]10;?<ESC>\ => get foreground color
- let get_cell_pixel_info =
- "\u{1b}[14t\u{1b}[16t\u{1b}]11;?\u{1b}\u{5c}\u{1b}]10;?\u{1b}\u{5c}";
- let _ = self
- .os_input
- .get_stdout_writer()
- .write(get_cell_pixel_info.as_bytes())
- .unwrap();
- let mut ansi_stdin_parser = StdinAnsiParser::new();
- ansi_stdin_parser.increment_expected_ansi_instructions(4);
loop {
if self.should_exit {
break;
@@ -91,13 +77,7 @@ impl InputHandler {
match input_event {
InputEvent::Key(key_event) => {
let key = cast_termwiz_key(key_event, &raw_bytes);
- if ansi_stdin_parser.expected_instructions() > 0 {
- self.handle_possible_pixel_instruction(
- ansi_stdin_parser.parse(key, raw_bytes),
- );
- } else {
- self.handle_key(&key, raw_bytes);
- }
+ self.handle_key(&key, raw_bytes);
},
InputEvent::Mouse(mouse_event) => {
let mouse_event =
@@ -126,13 +106,13 @@ impl InputHandler {
Ok((InputInstruction::SwitchToMode(input_mode), _error_context)) => {
self.mode = input_mode;
},
- Ok((InputInstruction::PossiblePixelRatioChange, _error_context)) => {
- let _ = self
- .os_input
- .get_stdout_writer()
- .write(get_cell_pixel_info.as_bytes())
- .unwrap();
- ansi_stdin_parser.increment_expected_ansi_instructions(4);
+ Ok((
+ InputInstruction::AnsiStdinInstructions(ansi_stdin_instructions),
+ _error_context,
+ )) => {
+ for ansi_instruction in ansi_stdin_instructions {
+ self.handle_stdin_ansi_instruction(ansi_instruction);
+ }
},
Err(err) => panic!("Encountered read error: {:?}", err),
}
@@ -147,33 +127,28 @@ impl InputHandler {
}
}
}
- fn handle_possible_pixel_instruction(
- &mut self,
- pixel_instruction_or_keys: Option<AnsiStdinInstructionOrKeys>,
- ) {
- match pixel_instruction_or_keys {
- Some(AnsiStdinInstructionOrKeys::PixelDimensions(pixel_dimensions)) => {
+ fn handle_stdin_ansi_instruction(&mut self, ansi_stdin_instructions: AnsiStdinInstruction) {
+ match ansi_stdin_instructions {
+ AnsiStdinInstruction::PixelDimensions(pixel_dimensions) => {
self.os_input
.send_to_server(ClientToServerMsg::TerminalPixelDimensions(pixel_dimensions));
},
- Some(AnsiStdinInstructionOrKeys::BackgroundColor(background_color_instruction)) => {
+ AnsiStdinInstruction::BackgroundColor(background_color_instruction) => {
self.os_input
.send_to_server(ClientToServerMsg::BackgroundColor(
background_color_instruction,
));
},
- Some(AnsiStdinInstructionOrKeys::ForegroundColor(foreground_color_instruction)) => {
+ AnsiStdinInstruction::ForegroundColor(foreground_color_instruction) => {
self.os_input
.send_to_server(ClientToServerMsg::ForegroundColor(
foreground_color_instruction,
));
},
- Some(AnsiStdinInstructionOrKeys::Keys(keys)) => {
- for (key, raw_bytes) in keys {
- self.handle_key(&key, raw_bytes);
- }
+ AnsiStdinInstruction::ColorRegisters(color_registers) => {
+ self.os_input
+ .send_to_server(ClientToServerMsg::ColorRegisters(color_registers));
},
- None => {},
}
}
fn handle_mouse_event(&mut self, mouse_event: &MouseEvent) {
@@ -352,7 +327,6 @@ pub(crate) fn input_loop(
)
.handle_input();
}
-
/// Entry point to the module. Instantiates an [`InputHandler`] and starts
/// its [`InputHandler::handle_input()`] loop.
#[allow(clippy::too_many_arguments)]
@@ -379,7 +353,3 @@ pub(crate) fn input_actions(
)
.handle_actions(actions, &session_name, clients);
}
-
-#[cfg(test)]
-#[path = "./unit/input_handler_tests.rs"]
-mod input_handler_tests;
diff --git a/zellij-client/src/lib.rs b/zellij-client/src/lib.rs
index 9790984ee..3b81694b4 100644
--- a/zellij-client/src/lib.rs
+++ b/zellij-client/src/lib.rs
@@ -13,8 +13,10 @@ use std::env::current_exe;
use std::io::{self, Write};
use std::path::Path;
use std::process::Command;
+use std::sync::{Arc, Mutex};
use std::thread;
+use crate::stdin_ansi_parser::{AnsiStdinInstruction, StdinAnsiParser};
use crate::{
command_is_executing::CommandIsExecuting, input_handler::input_loop,
os_input_output::ClientOsApi, stdin_handler::stdin_loop,
@@ -114,7 +116,7 @@ impl ClientInfo {
pub(crate) enum InputInstruction {
KeyEvent(InputEvent, Vec<u8>),
SwitchToMode(InputMode),
- PossiblePixelRatioChange,
+ AnsiStdinInstructions(Vec<AnsiStdinInstruction>),
}
pub fn start_client(
@@ -126,7 +128,7 @@ pub fn start_client(
layout: Option<LayoutFromYaml>,
) {
info!("Starting Zellij client!");
- let clear_client_terminal_attributes = "\u{1b}[?1l\u{1b}=\u{1b}[r\u{1b}12l\u{1b}[?1000l\u{1b}[?1002l\u{1b}[?1003l\u{1b}[?1005l\u{1b}[?1006l\u{1b}[?12l";
+ let clear_client_terminal_attributes = "\u{1b}[?1l\u{1b}=\u{1b}[r\u{1b}[?1000l\u{1b}[?1002l\u{1b}[?1003l\u{1b}[?1005l\u{1b}[?1006l\u{1b}[?12l";
let take_snapshot = "\u{1b}[?1049h";
let bracketed_paste = "\u{1b}[?2004h";
os_input.unset_raw_mode(0).unwrap();
@@ -162,11 +164,13 @@ pub fn start_client(
let first_msg = match info {
ClientInfo::Attach(name, config_options) => {
envs::set_session_name(name);
+ envs::set_initial_environment_vars();
ClientToServerMsg::AttachClient(client_attributes, config_options)
},
ClientInfo::New(name) => {
envs::set_session_name(name);
+ envs::set_initial_environment_vars();
spawn_server(&*ZELLIJ_IPC_PIPE).unwrap();
@@ -214,13 +218,15 @@ pub fn start_client(
});
let on_force_close = config_options.on_force_close.unwrap_or_default();
+ let stdin_ansi_parser = Arc::new(Mutex::new(StdinAnsiParser::new()));
let _stdin_thread = thread::Builder::new()
.name("stdin_handler".to_string())
.spawn({
let os_input = os_input.clone();
let send_input_instructions = send_input_instructions.clone();
- move || stdin_loop(os_input, send_input_instructions)
+ let stdin_ansi_parser = stdin_ansi_parser.clone();
+ move || stdin_loop(os_input, send_input_instructions, stdin_ansi_parser)
});
let _input_thread = thread::Builder::new()
@@ -246,7 +252,6 @@ pub fn start_client(
let _signal_thread = thread::Builder::new()
.name("signal_listener".to_string())
.spawn({
- let send_input_instructions = send_input_instructions.clone();
let os_input = os_input.clone();
move || {
os_input.handle_signals(
@@ -256,8 +261,16 @@ pub fn start_client(
os_api.send_to_server(ClientToServerMsg::TerminalResize(
os_api.get_terminal_size_using_fd(0),
));
- let _ = send_input_instructions
- .send(InputInstruction::PossiblePixelRatioChange);
+ // send a query to the terminal emulator in case the font size changed
+ // as well - we'll parse the response through STDIN
+ let terminal_emulator_query_string = stdin_ansi_parser
+ .lock()
+ .unwrap()
+ .window_size_change_query_string();
+ let _ = os_api
+ .get_stdout_writer()
+ .write(terminal_emulator_query_string.as_bytes())
+ .unwrap();
}
}),
Box::new({
@@ -385,3 +398,7 @@ pub fn start_client(
let _ = stdout.write(goodbye_message.as_bytes()).unwrap();
stdout.flush().unwrap();
}
+
+#[cfg(test)]
+#[path = "./unit/stdin_tests.rs"]
+mod stdin_tests;
diff --git a/zellij-client/src/os_input_output.rs b/zellij-client/src/os_input_output.rs
index 60c22cc98..f9f3c590b 100644
--- a/zellij-client/src/os_input_output.rs
+++ b/zellij-client/src/os_input_output.rs
@@ -94,7 +94,7 @@ pub trait ClientOsApi: Send + Sync {
fn get_stdout_writer(&self) -> Box<dyn io::Write>;
fn get_stdin_reader(&self) -> Box<dyn io::Read>;
/// Returns the raw contents of standard input.
- fn read_from_stdin(&self) -> Vec<u8>;
+ fn read_from_stdin(&mut self) -> Vec<u8>;
/// Returns a [`Box`] pointer to this [`ClientOsApi`] struct.
fn box_clone(&self) -> Box<dyn ClientOsApi>;
/// Sends a message to the server.
@@ -126,7 +126,7 @@ impl ClientOsApi for ClientOsInputOutput {
fn box_clone(&self) -> Box<dyn ClientOsApi> {
Box::new((*self).clone())
}
- fn read_from_stdin(&self) -> Vec<u8> {
+ fn read_from_stdin(&mut self) -> Vec<u8> {
let stdin = std::io::stdin();
let mut stdin = stdin.lock();
let buffer = stdin.fill_buf().unwrap();
diff --git a/zellij-client/src/stdin_ansi_parser.rs b/zellij-client/src/stdin_ansi_parser.rs
index 25f0e4fbb..09247bf9b 100644
--- a/zellij-client/src/stdin_ansi_parser.rs
+++ b/zellij-client/src/stdin_ansi_parser.rs
@@ -1,200 +1,234 @@
+use std::time::{Duration, Instant};
+
+const STARTUP_PARSE_DEADLINE_MS: u64 = 500;
+const SIGWINCH_PARSE_DEADLINE_MS: u64 = 200;
use zellij_utils::{
- data::{CharOrArrow, Key},
- ipc::PixelDimensions,
- lazy_static::lazy_static,
- pane_size::SizeInPixels,
- regex::Regex,
+ ipc::PixelDimensions, lazy_static::lazy_static, pane_size::SizeInPixels, regex::Regex,
};
+#[derive(Debug)]
pub struct StdinAnsiParser {
- expected_ansi_instructions: usize,
- current_buffer: Vec<(Key, Vec<u8>)>,
+ raw_buffer: Vec<u8>,
+ pending_color_sequences: Vec<(usize, String)>,
+ pending_events: Vec<AnsiStdinInstruction>,
+ parse_deadline: Option<Instant>,
}
impl StdinAnsiParser {
pub fn new() -> Self {
StdinAnsiParser {
- expected_ansi_instructions: 0,
- current_buffer: vec![],
+ raw_buffer: vec![],
+ pending_color_sequences: vec![],
+ pending_events: vec![],
+ parse_deadline: None,
}
}
- pub fn increment_expected_ansi_instructions(&mut self, to: usize) {
- self.expected_ansi_instructions += to;
- }
- pub fn decrement_expected_ansi_instructions(&mut self, by: usize) {
- self.expected_ansi_instructions = self.expected_ansi_instructions.saturating_sub(by);
+ pub fn terminal_emulator_query_string(&mut self) -> String {
+ // note that this assumes the String will be sent to the terminal emulator and so starts a
+ // deadline timeout (self.parse_deadline)
+
+ // <ESC>[14t => get text area size in pixels,
+ // <ESC>[16t => get character cell size in pixels
+ // <ESC>]11;?<ESC>\ => get background color
+ // <ESC>]10;?<ESC>\ => get foreground color
+ let mut query_string =
+ String::from("\u{1b}[14t\u{1b}[16t\u{1b}]11;?\u{1b}\u{5c}\u{1b}]10;?\u{1b}\u{5c}");
+
+ // query colors
+ // eg. <ESC>]4;5;?<ESC>\ => query color register number 5
+ for i in 0..256 {
+ query_string.push_str(&format!("\u{1b}]4;{};?\u{1b}\u{5c}", i));
+ }
+ self.parse_deadline =
+ Some(Instant::now() + Duration::from_millis(STARTUP_PARSE_DEADLINE_MS));
+ query_string
}
- pub fn expected_instructions(&self) -> usize {
- self.expected_ansi_instructions
+ pub fn window_size_change_query_string(&mut self) -> String {
+ // note that this assumes the String will be sent to the terminal emulator and so starts a
+ // deadline timeout (self.parse_deadline)
+
+ // <ESC>[14t => get text area size in pixels,
+ // <ESC>[16t => get character cell size in pixels
+ let query_string = String::from("\u{1b}[14t\u{1b}[16t");
+
+ self.parse_deadline =
+ Some(Instant::now() + Duration::from_millis(SIGWINCH_PARSE_DEADLINE_MS));
+ query_string
}
- pub fn parse(&mut self, key: Key, raw_bytes: Vec<u8>) -> Option<AnsiStdinInstructionOrKeys> {
- if self.current_buffer.is_empty()
- && (key != Key::Esc && key != Key::Alt(CharOrArrow::Char(']')))
+ fn drain_pending_events(&mut self) -> Vec<AnsiStdinInstruction> {
+ let mut events = vec![];
+ events.append(&mut self.pending_events);
+ if let Some(color_registers) =
+ AnsiStdinInstruction::color_registers_from_bytes(&mut self.pending_color_sequences)
{
- // the first key of a sequence is always Esc, but termwiz interprets esc + ] as Alt+]
- self.current_buffer.push((key, raw_bytes));
- self.expected_ansi_instructions = 0;
- return Some(AnsiStdinInstructionOrKeys::Keys(
- self.current_buffer.drain(..).collect(),
- ));
- }
- if let Key::Char('t') = key {
- self.current_buffer.push((key, raw_bytes));
- match AnsiStdinInstructionOrKeys::pixel_dimensions_from_keys(&self.current_buffer) {
- Ok(pixel_instruction) => {
- self.decrement_expected_ansi_instructions(1);
- self.current_buffer.clear();
- Some(pixel_instruction)
- },
- Err(_) => {
- self.expected_ansi_instructions = 0;
- Some(AnsiStdinInstructionOrKeys::Keys(
- self.current_buffer.drain(..).collect(),
- ))
- },
+ events.push(color_registers);
+ }
+ events
+ }
+ pub fn should_parse(&self) -> bool {
+ if let Some(parse_deadline) = self.parse_deadline {
+ if parse_deadline >= Instant::now() {
+ return true;
}
- } else if let Key::Alt(CharOrArrow::Char('\\')) | Key::Ctrl('g') = key {
- match AnsiStdinInstructionOrKeys::color_sequence_from_keys(&self.current_buffer) {
- Ok(color_instruction) => {
- self.decrement_expected_ansi_instructions(1);
- self.current_buffer.clear();
- Some(color_instruction)
+ }
+ false
+ }
+ pub fn parse(&mut self, mut raw_bytes: Vec<u8>) -> Vec<AnsiStdinInstruction> {
+ for byte in raw_bytes.drain(..) {
+ self.parse_byte(byte);
+ }
+ self.drain_pending_events()
+ }
+ fn parse_byte(&mut self, byte: u8) {
+ if byte == b't' {
+ self.raw_buffer.push(byte);
+ match AnsiStdinInstruction::pixel_dimensions_from_bytes(&self.raw_buffer) {
+ Ok(ansi_sequence) => {
+ self.pending_events.push(ansi_sequence);
+ self.raw_buffer.clear();
},
Err(_) => {
- self.expected_ansi_instructions = 0;
- Some(AnsiStdinInstructionOrKeys::Keys(
- self.current_buffer.drain(..).collect(),
- ))
+ self.raw_buffer.clear();
},
}
- } else if self.key_is_valid(key) {
- self.current_buffer.push((key, raw_bytes));
- None
+ } else if byte == b'\\' {
+ self.raw_buffer.push(byte);
+ if let Ok(ansi_sequence) = AnsiStdinInstruction::bg_or_fg_from_bytes(&self.raw_buffer) {
+ self.pending_events.push(ansi_sequence);
+ self.raw_buffer.clear();
+ } else if let Ok((color_register, color_sequence)) =
+ color_sequence_from_bytes(&self.raw_buffer)
+ {
+ self.raw_buffer.clear();
+ self.pending_color_sequences
+ .push((color_register, color_sequence));
+ } else {
+ self.raw_buffer.clear();
+ }
} else {
- self.current_buffer.push((key, raw_bytes));
- self.expected_ansi_instructions = 0;
- Some(AnsiStdinInstructionOrKeys::Keys(
- self.current_buffer.drain(..).collect(),
- ))
- }
- }
- fn key_is_valid(&self, key: Key) -> bool {
- match key {
- Key::Esc => {
- // this is a UX improvement
- // in case the user's terminal doesn't support one or more of these signals,
- // if they spam ESC they need to be able to get back to normal mode and not "us
- // waiting for ansi instructions" mode
- !self.current_buffer.iter().any(|(key, _)| *key == Key::Esc)
- },
- Key::Char(';')
- | Key::Char('[')
- | Key::Char(']')
- | Key::Char('r')
- | Key::Char('g')
- | Key::Char('b')
- | Key::Char('\\')
- | Key::Char(':')
- | Key::Char('/') => true,
- Key::Alt(CharOrArrow::Char(']')) => true,
- Key::Alt(CharOrArrow::Char('\\')) => true,
- Key::Char(c) => {
- matches!(c, '0'..='9' | 'a'..='f')
- },
- _ => false,
+ self.raw_buffer.push(byte);
}
}
}
-#[derive(Debug)]
-pub enum AnsiStdinInstructionOrKeys {
+#[derive(Debug, Clone)]
+pub enum AnsiStdinInstruction {
PixelDimensions(PixelDimensions),
BackgroundColor(String),
ForegroundColor(String),
- Keys(Vec<(Key, Vec<u8>)>),
+ ColorRegisters(Vec<(usize, String)>),
}
-impl AnsiStdinInstructionOrKeys {
- pub fn pixel_dimensions_from_keys(keys: &[(Key, Vec<u8>)]) -> Result<Self, &'static str> {
+impl AnsiStdinInstruction {
+ pub fn pixel_dimensions_from_bytes(bytes: &[u8]) -> Result<Self, &'static str> {
+ // eg. <ESC>[4;21;8t
lazy_static! {
static ref RE: Regex = Regex::new(r"^\u{1b}\[(\d+);(\d+);(\d+)t$").unwrap();
}
- let key_sequence: Vec<Option<char>> = keys
- .iter()
- .map(|(key, _)| match key {
- Key::Char(c) => Some(*c),
- Key::Esc => Some('\u{1b}'),
- _ => None,
- })
- .collect();
- if key_sequence.iter().all(|k| k.is_some()) {
- let key_string: String = key_sequence.iter().map(|k| k.unwrap()).collect();
- let captures = RE
- .captures_iter(&key_string)
- .next()
- .ok_or("invalid_instruction")?;
- let csi_index = captures[1].parse::<usize>();
- let first_field = captures[2].parse::<usize>();
- let second_field = captures[3].parse::<usize>();
- if csi_index.is_err() || first_field.is_err() || second_field.is_err() {
- return Err("invalid_instruction");
- }
- match csi_index {
- Ok(4) => {
- // text area size
- Ok(AnsiStdinInstructionOrKeys::PixelDimensions(
- PixelDimensions {
- character_cell_size: None,
- text_area_size: Some(SizeInPixels {
- height: first_field.unwrap(),
- width: second_field.unwrap(),
- }),
- },
- ))
- },
- Ok(6) => {
- // character cell size
- Ok(AnsiStdinInstru