summaryrefslogtreecommitdiffstats
path: root/zellij-client/src
diff options
context:
space:
mode:
authorAram Drevekenin <aram@poor.dev>2022-04-12 18:07:32 +0200
committerGitHub <noreply@github.com>2022-04-12 18:07:32 +0200
commit19adb29be516a871620071b289594bacf3d3056c (patch)
tree3ff60c31f6b7fc460089bb5d2da4ad81acc2ec8d /zellij-client/src
parent028885c82273c2f6c7ce080edeba097ca5f5dfa4 (diff)
feat(signals): support XTWINOPS 14 and 16 (and query the terminal for them on startup and SIGWINCH) (#1316)
* feat(signals): get pixel info from terminal emulator * feat(signals): query for pixel info on sigwinch * feat(signals): reply to csi 14t and csi 16t * style(fmt): rustfmt * style(comments): remove outdated
Diffstat (limited to 'zellij-client/src')
-rw-r--r--zellij-client/src/input_handler.rs47
-rw-r--r--zellij-client/src/lib.rs5
-rw-r--r--zellij-client/src/pixel_csi_parser.rs146
-rw-r--r--zellij-client/src/unit/input_handler_tests.rs474
4 files changed, 666 insertions, 6 deletions
diff --git a/zellij-client/src/input_handler.rs b/zellij-client/src/input_handler.rs
index 250485eb9..b2ef1e587 100644
--- a/zellij-client/src/input_handler.rs
+++ b/zellij-client/src/input_handler.rs
@@ -1,5 +1,4 @@
//! Main input logic.
-
use zellij_utils::{
input::{
mouse::{MouseButton, MouseEvent},
@@ -10,7 +9,9 @@ use zellij_utils::{
};
use crate::{
- os_input_output::ClientOsApi, ClientInstruction, CommandIsExecuting, InputInstruction,
+ os_input_output::ClientOsApi,
+ pixel_csi_parser::{PixelCsiParser, PixelDimensionsOrKeys},
+ ClientInstruction, CommandIsExecuting, InputInstruction,
};
use zellij_utils::{
channels::{Receiver, SenderWithContext, OPENCALLS},
@@ -70,6 +71,15 @@ 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
+ let get_cell_pixel_info = "\u{1b}[14t\u{1b}[16t";
+ let _ = self
+ .os_input
+ .get_stdout_writer()
+ .write(get_cell_pixel_info.as_bytes())
+ .unwrap();
+ let mut pixel_csi_parser = PixelCsiParser::new();
+ pixel_csi_parser.increment_expected_csi_instructions(2);
loop {
if self.should_exit {
break;
@@ -79,7 +89,13 @@ impl InputHandler {
match input_event {
InputEvent::Key(key_event) => {
let key = cast_termwiz_key(key_event, &raw_bytes);
- self.handle_key(&key, raw_bytes);
+ if pixel_csi_parser.expected_instructions() > 0 {
+ self.handle_possible_pixel_instruction(
+ pixel_csi_parser.parse(key, raw_bytes),
+ );
+ } else {
+ self.handle_key(&key, raw_bytes);
+ }
}
InputEvent::Mouse(mouse_event) => {
let mouse_event =
@@ -101,6 +117,14 @@ 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();
+ pixel_csi_parser.increment_expected_csi_instructions(2);
+ }
Err(err) => panic!("Encountered read error: {:?}", err),
}
}
@@ -114,6 +138,23 @@ impl InputHandler {
}
}
}
+ fn handle_possible_pixel_instruction(
+ &mut self,
+ pixel_instruction_or_keys: Option<PixelDimensionsOrKeys>,
+ ) {
+ match pixel_instruction_or_keys {
+ Some(PixelDimensionsOrKeys::PixelDimensions(pixel_dimensions)) => {
+ self.os_input
+ .send_to_server(ClientToServerMsg::TerminalPixelDimensions(pixel_dimensions));
+ }
+ Some(PixelDimensionsOrKeys::Keys(keys)) => {
+ for (key, raw_bytes) in keys {
+ self.handle_key(&key, raw_bytes);
+ }
+ }
+ None => {}
+ }
+ }
fn handle_mouse_event(&mut self, mouse_event: &MouseEvent) {
match *mouse_event {
MouseEvent::Press(button, point) => match button {
diff --git a/zellij-client/src/lib.rs b/zellij-client/src/lib.rs
index a17845320..556f18120 100644
--- a/zellij-client/src/lib.rs
+++ b/zellij-client/src/lib.rs
@@ -2,6 +2,7 @@ pub mod os_input_output;
mod command_is_executing;
mod input_handler;
+mod pixel_csi_parser;
mod stdin_handler;
use log::info;
@@ -108,6 +109,7 @@ impl ClientInfo {
pub(crate) enum InputInstruction {
KeyEvent(InputEvent, Vec<u8>),
SwitchToMode(InputMode),
+ PossiblePixelRatioChange,
}
pub fn start_client(
@@ -237,6 +239,7 @@ 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(
@@ -246,6 +249,8 @@ 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);
}
}),
Box::new({
diff --git a/zellij-client/src/pixel_csi_parser.rs b/zellij-client/src/pixel_csi_parser.rs
new file mode 100644
index 000000000..fc2df627a
--- /dev/null
+++ b/zellij-client/src/pixel_csi_parser.rs
@@ -0,0 +1,146 @@
+use zellij_utils::pane_size::SizeInPixels;
+
+use zellij_utils::{ipc::PixelDimensions, lazy_static::lazy_static, regex::Regex};
+
+use zellij_tile::data::Key;
+
+pub struct PixelCsiParser {
+ expected_pixel_csi_instructions: usize,
+ current_buffer: Vec<(Key, Vec<u8>)>,
+}
+
+impl PixelCsiParser {
+ pub fn new() -> Self {
+ PixelCsiParser {
+ expected_pixel_csi_instructions: 0,
+ current_buffer: vec![],
+ }
+ }
+ pub fn increment_expected_csi_instructions(&mut self, by: usize) {
+ self.expected_pixel_csi_instructions += by;
+ }
+ pub fn decrement_expected_csi_instructions(&mut self, by: usize) {
+ self.expected_pixel_csi_instructions =
+ self.expected_pixel_csi_instructions.saturating_sub(by);
+ }
+ pub fn expected_instructions(&self) -> usize {
+ self.expected_pixel_csi_instructions
+ }
+ pub fn parse(&mut self, key: Key, raw_bytes: Vec<u8>) -> Option<PixelDimensionsOrKeys> {
+ if let Key::Char('t') = key {
+ self.current_buffer.push((key, raw_bytes));
+ match PixelDimensionsOrKeys::pixel_dimensions_from_keys(&self.current_buffer) {
+ Ok(pixel_instruction) => {
+ self.decrement_expected_csi_instructions(1);
+ self.current_buffer.clear();
+ Some(pixel_instruction)
+ }
+ Err(_) => {
+ self.expected_pixel_csi_instructions = 0;
+ Some(PixelDimensionsOrKeys::Keys(
+ self.current_buffer.drain(..).collect(),
+ ))
+ }
+ }
+ } else if self.key_is_valid(key) {
+ self.current_buffer.push((key, raw_bytes));
+ None
+ } else {
+ self.current_buffer.push((key, raw_bytes));
+ self.expected_pixel_csi_instructions = 0;
+ Some(PixelDimensionsOrKeys::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 pixel instructions" mode
+ if self
+ .current_buffer
+ .iter()
+ .find(|(key, _)| *key == Key::Esc)
+ .is_none()
+ {
+ true
+ } else {
+ false
+ }
+ }
+ Key::Char(';') | Key::Char('[') => true,
+ Key::Char(c) => {
+ if let '0'..='9' = c {
+ true
+ } else {
+ false
+ }
+ }
+ _ => false,
+ }
+ }
+}
+
+#[derive(Debug)]
+pub enum PixelDimensionsOrKeys {
+ // TODO: rename to PixelDimensionsOrKeys
+ PixelDimensions(PixelDimensions),
+ Keys(Vec<(Key, Vec<u8>)>),
+}
+
+impl PixelDimensionsOrKeys {
+ pub fn pixel_dimensions_from_keys(keys: &Vec<(Key, Vec<u8>)>) -> Result<Self, &'static str> {
+ 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(PixelDimensionsOrKeys::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(PixelDimensionsOrKeys::PixelDimensions(PixelDimensions {
+ character_cell_size: Some(SizeInPixels {
+ height: first_field.unwrap(),
+ width: second_field.unwrap(),
+ }),
+ text_area_size: None,
+ }))
+ }
+ _ => Err("invalid sequence"),
+ }
+ } else {
+ Err("invalid sequence")
+ }
+ }
+}
diff --git a/zellij-client/src/unit/input_handler_tests.rs b/zellij-client/src/unit/input_handler_tests.rs
index a83719a9d..1c5180c81 100644
--- a/zellij-client/src/unit/input_handler_tests.rs
+++ b/zellij-client/src/unit/input_handler_tests.rs
@@ -2,7 +2,7 @@ use super::input_loop;
use zellij_utils::input::actions::{Action, Direction};
use zellij_utils::input::config::Config;
use zellij_utils::input::options::Options;
-use zellij_utils::pane_size::Size;
+use zellij_utils::pane_size::{Size, SizeInPixels};
use zellij_utils::termwiz::input::{InputEvent, KeyCode, KeyEvent, Modifiers};
use zellij_utils::zellij_tile::data::Palette;
@@ -22,7 +22,7 @@ use std::sync::{Arc, Mutex};
use zellij_tile::data::InputMode;
use zellij_utils::{
errors::ErrorContext,
- ipc::{ClientToServerMsg, ServerToClientMsg},
+ ipc::{ClientToServerMsg, PixelDimensions, ServerToClientMsg},
};
use zellij_utils::channels::{self, ChannelWithContext, SenderWithContext};
@@ -71,9 +71,30 @@ pub mod commands {
pub const SLEEP: [u8; 0] = [];
}
+#[derive(Default, Clone)]
+struct FakeStdoutWriter {
+ buffer: Arc<Mutex<Vec<u8>>>,
+}
+impl FakeStdoutWriter {
+ pub fn new(buffer: Arc<Mutex<Vec<u8>>>) -> Self {
+ FakeStdoutWriter { buffer }
+ }
+}
+impl io::Write for FakeStdoutWriter {
+ fn write(&mut self, mut buf: &[u8]) -> Result<usize, io::Error> {
+ self.buffer.lock().unwrap().extend_from_slice(&mut buf);
+ Ok(buf.len())
+ }
+ fn flush(&mut self) -> Result<(), io::Error> {
+ Ok(())
+ }
+}
+
+#[derive(Clone)]
struct FakeClientOsApi {
events_sent_to_server: Arc<Mutex<Vec<ClientToServerMsg>>>,
command_is_executing: Arc<Mutex<CommandIsExecuting>>,
+ stdout_buffer: Arc<Mutex<Vec<u8>>>,
}
impl FakeClientOsApi {
@@ -85,11 +106,16 @@ impl FakeClientOsApi {
// Arc<Mutex> here because we need interior mutability, otherwise we'll have to change the
// ClientOsApi trait, and that will cause a lot of havoc
let command_is_executing = Arc::new(Mutex::new(command_is_executing));
+ let stdout_buffer = Arc::new(Mutex::new(vec![]));
FakeClientOsApi {
events_sent_to_server,
command_is_executing,
+ stdout_buffer,
}
}
+ pub fn stdout_buffer(&self) -> Vec<u8> {
+ self.stdout_buffer.lock().unwrap().drain(..).collect()
+ }
}
impl ClientOsApi for FakeClientOsApi {
@@ -103,7 +129,8 @@ impl ClientOsApi for FakeClientOsApi {
unimplemented!()
}
fn get_stdout_writer(&self) -> Box<dyn io::Write> {
- unimplemented!()
+ let fake_stdout_writer = FakeStdoutWriter::new(self.stdout_buffer.clone());
+ Box::new(fake_stdout_writer)
}
fn get_stdin_reader(&self) -> Box<dyn io::Read> {
unimplemented!()
@@ -155,6 +182,18 @@ fn extract_actions_sent_to_server(
})
}
+fn extract_pixel_events_sent_to_server(
+ events_sent_to_server: Arc<Mutex<Vec<ClientToServerMsg>>>,
+) -> Vec<PixelDimensions> {
+ let events_sent_to_server = events_sent_to_server.lock().unwrap();
+ events_sent_to_server.iter().fold(vec![], |mut acc, event| {
+ if let ClientToServerMsg::TerminalPixelDimensions(pixel_dimensions) = event {
+ acc.push(pixel_dimensions.clone());
+ }
+ acc
+ })
+}
+
#[test]
pub fn quit_breaks_input_loop() {
let stdin_events = vec![(
@@ -267,3 +306,432 @@ pub fn move_focus_left_in_normal_mode() {
"All actions sent to server properly"
);
}
+
+#[test]
+pub fn pixel_info_queried_from_terminal_emulator() {
+ let stdin_events = vec![(
+ commands::QUIT.to_vec(),
+ InputEvent::Key(KeyEvent {
+ key: KeyCode::Char('q'),
+ modifiers: Modifiers::CTRL,
+ }),
+ )];
+
+ let events_sent_to_server = Arc::new(Mutex::new(vec![]));
+ let command_is_executing = CommandIsExecuting::new();
+ let client_os_api =
+ FakeClientOsApi::new(events_sent_to_server.clone(), command_is_executing.clone());
+ let config = Config::from_default_assets().unwrap();
+ let options = Options::default();
+
+ let (send_client_instructions, _receive_client_instructions): ChannelWithContext<
+ ClientInstruction,
+ > = channels::bounded(50);
+ let send_client_instructions = SenderWithContext::new(send_client_instructions);
+
+ let (send_input_instructions, receive_input_instructions): ChannelWithContext<
+ InputInstruction,
+ > = channels::bounded(50);
+ let send_input_instructions = SenderWithContext::new(send_input_instructions);
+ for event in stdin_events {
+ send_input_instructions
+ .send(InputInstruction::KeyEvent(event.1, event.0))
+ .unwrap();
+ }
+
+ let default_mode = InputMode::Normal;
+ let client_os_api_clone = client_os_api.clone();
+ input_loop(
+ Box::new(client_os_api),
+ config,
+ options,
+ command_is_executing,
+ send_client_instructions,
+ default_mode,
+ receive_input_instructions,
+ );
+ let extracted_stdout_buffer = client_os_api_clone.stdout_buffer();
+ assert_eq!(
+ String::from_utf8(extracted_stdout_buffer),
+ Ok(String::from("\u{1b}[14t\u{1b}[16t")),
+ );
+}
+
+#[test]
+pub fn pixel_info_sent_to_server() {
+ let stdin_events = vec![
+ (
+ vec![27],
+ InputEvent::Key(KeyEvent {
+ key: KeyCode::Escape,
+ modifiers: Modifiers::NONE,
+ }),
+ ),
+ (
+ "[".as_bytes().to_vec(),
+ InputEvent::Key(KeyEvent {
+ key: KeyCode::Char('['),
+ modifiers: Modifiers::NONE,
+ }),
+ ),
+ (
+ "6".as_bytes().to_vec(),
+ InputEvent::Key(KeyEvent {
+ key: KeyCode::Char('6'),
+ modifiers: Modifiers::NONE,
+ }),
+ ),
+ (
+ ";".as_bytes().to_vec(),
+ InputEvent::Key(KeyEvent {
+ key: KeyCode::Char(';'),
+ modifiers: Modifiers::NONE,
+ }),
+ ),
+ (
+ "1".as_bytes().to_vec(),
+ InputEvent::Key(KeyEvent {
+ key: KeyCode::Char('1'),
+ modifiers: Modifiers::NONE,
+ }),
+ ),
+ (
+ "0".as_bytes().to_vec(),
+ InputEvent::Key(KeyEvent {
+ key: KeyCode::Char('0'),
+ modifiers: Modifiers::NONE,
+ }),
+ ),
+ (
+ ";".as_bytes().to_vec(),
+ InputEvent::Key(KeyEvent {
+ key: KeyCode::Char(';'),
+ modifiers: Modifiers::NONE,
+ }),
+ ),
+ (
+ "5".as_bytes().to_vec(),
+ InputEvent::Key(KeyEvent {
+ key: KeyCode::Char('5'),
+ modifiers: Modifiers::NONE,
+ }),
+ ),
+ (
+ "t".as_bytes().to_vec(),
+ InputEvent::Key(KeyEvent {
+ key: KeyCode::Char('t'),
+ modifiers: Modifiers::NONE,
+ }),
+ ),
+ (
+ commands::QUIT.to_vec(),
+ InputEvent::Key(KeyEvent {
+ key: KeyCode::Char('q'),
+ modifiers: Modifiers::CTRL,
+ }),
+ ),
+ ];
+
+ let events_sent_to_server = Arc::new(Mutex::new(vec![]));
+ let command_is_executing = CommandIsExecuting::new();
+ let client_os_api =
+ FakeClientOsApi::new(events_sent_to_server.clone(), command_is_executing.clone());
+ let config = Config::from_default_assets().unwrap();
+ let options = Options::default();
+
+ let (send_client_instructions, _receive_client_instructions): ChannelWithContext<
+ ClientInstruction,
+ > = channels::bounded(50);
+ let send_client_instructions = SenderWithContext::new(send_client_instructions);
+
+ let (send_input_instructions, receive_input_instructions): ChannelWithContext<
+ InputInstruction,
+ > = channels::bounded(50);
+ let send_input_instructions = SenderWithContext::new(send_input_instructions);
+ for event in stdin_events {
+ send_input_instructions
+ .send(InputInstruction::KeyEvent(event.1, event.0))
+ .unwrap();
+ }
+
+ let default_mode = InputMode::Normal;
+ input_loop(
+ Box::new(client_os_api),
+ config,
+ options,
+ command_is_executing,
+ send_client_instructions,
+ default_mode,
+ receive_input_instructions,
+ );
+ let actions_sent_to_server = extract_actions_sent_to_server(events_sent_to_server.clone());
+ let pixel_events_sent_to_server =
+ extract_pixel_events_sent_to_server(events_sent_to_server.clone());
+ assert_eq!(actions_sent_to_server, vec![Action::Quit]);
+ assert_eq!(
+ pixel_events_sent_to_server,
+ vec![PixelDimensions {
+ character_cell_size: Some(SizeInPixels {
+ height: 10,
+ width: 5
+ }),
+ text_area_size: None
+ }],
+ );
+}
+
+#[test]
+pub fn corrupted_pixel_info_sent_as_key_events() {
+ let stdin_events = vec![
+ (
+ vec![27],
+ InputEvent::Key(KeyEvent {
+ key: KeyCode::Escape,
+ modifiers: Modifiers::NONE,
+ }),
+ ),
+ (
+ "[".as_bytes().to_vec(),
+ InputEvent::Key(KeyEvent {
+ key: KeyCode::Char('['),
+ modifiers: Modifiers::NONE,
+ }),
+ ),
+ (
+ "f".as_bytes().to_vec(),
+ InputEvent::Key(KeyEvent {
+ key: KeyCode::Char('f'),
+ modifiers: Modifiers::NONE,
+ }),
+ ),
+ (
+ ";".as_bytes().to_vec(),
+ InputEvent::Key(KeyEvent {
+ key: KeyCode::Char(';'),
+ modifiers: Modifiers::NONE,
+ }),
+ ),
+ (
+ "1".as_bytes().to_vec(),
+ InputEvent::Key(KeyEvent {
+ key: KeyCode::Char('1'),
+ modifiers: Modifiers::NONE,
+ }),
+ ),
+ (
+ "0".as_bytes().to_vec(),
+ InputEvent::Key(KeyEvent {
+ key: KeyCode::Char('0'),
+ modifiers: Modifiers::NONE,
+ }),
+ ),
+ (
+ ";".as_bytes().to_vec(),
+ InputEvent::Key(KeyEvent {
+ key: KeyCode::Char(';'),
+ modifiers: Modifiers::NONE,
+ }),
+ ),
+ (
+ "5".as_bytes().to_vec(),
+ InputEvent::Key(KeyEvent {
+ key: KeyCode::Char('5'),
+ modifiers: Modifiers::NONE,
+ }),
+ ),
+ (
+ "t".as_bytes().to_vec(),
+ InputEvent::Key(KeyEvent {
+ key: KeyCode::Char('t'),
+ modifiers: Modifiers::NONE,
+ }),
+ ),
+ (
+ commands::QUIT.to_vec(),
+ InputEvent::Key(KeyEvent {
+ key: KeyCode::Char('q'),
+ modifiers: Modifiers::CTRL,
+ }),
+ ),
+ ];
+
+ let events_sent_to_server = Arc::new(Mutex::new(vec![]));
+ let command_is_executing = CommandIsExecuting::new();
+ let client_os_api =
+ FakeClientOsApi::new(events_sent_to_server.clone(), command_is_executing.clone());
+ let config = Config::from_default_assets().unwrap();
+ let options = Options::default();
+
+ let (send_client_instructions, _receive_client_instructions): ChannelWithContext<
+ ClientInstruction,
+ > = channels::bounded(50);
+ let send_client_instructions = SenderWithContext::new(send_client_instructions);
+
+ let (send_input_instructions, receive_input_instructions): ChannelWithContext<
+ InputInstruction,
+ > = channels::bounded(50);
+ let send_input_instructions = SenderWithContext::new(send_input_instructions);
+ for event in stdin_events {
+ send_input_instructions
+ .send(InputInstruction::KeyEvent(event.1, event.0))
+ .unwrap();
+ }
+
+ let default_mode = InputMode::Normal;
+ input_loop(
+ Box::new(client_os_api),
+ config,
+ options,
+ command_is_executing,
+ send_client_instructions,
+ default_mode,
+ receive_input_instructions,
+ );
+ let actions_sent_to_server = extract_actions_sent_to_server(events_sent_to_server.clone());
+ let pixel_events_sent_to_server =
+ extract_pixel_events_sent_to_server(events_sent_to_server.clone());
+ assert_eq!(
+ actions_sent_to_server,
+ vec![
+ Action::Write(vec![27]),
+ Action::Write(vec![b'[']),
+ Action::Write(vec![b'f']),
+ Action::Write(vec![b';']),
+ Action::Write(vec![b'1']),
+ Action::Write(vec![b'0']),
+ Action::Write(vec![b';']),
+ Action::Write(vec![b'5']),
+ Action::Write(vec![b't']),
+ Action::Quit
+ ]
+ );
+ assert_eq!(pixel_events_sent_to_server, vec![],);
+}
+
+#[test]
+pub fn esc_in_the_middle_of_pixelinfo_breaks_out_of_it() {
+ let stdin_events = vec![
+ (
+ vec![27],
+ InputEvent::Key(KeyEvent {
+ key: KeyCode::Escape,
+ modifiers: Modifiers::NONE,
+ }),
+ ),
+ (
+ "[".as_bytes().to_vec(),
+ InputEvent::Key(KeyEvent {
+ key: KeyCode::Char('['),
+ modifiers: Modifiers::NONE,
+ }),
+ ),
+ (
+ vec![27],
+ InputEvent::Key(KeyEvent {<