aboutsummaryrefslogtreecommitdiffstats
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
parentfix: typo (#1567) (diff)
downloadsl0ppij-c89b416d764d80a72130821506f36157a08321e9.tar.gz
sl0ppij-c89b416d764d80a72130821506f36157a08321e9.tar.xz
sl0ppij-c89b416d764d80a72130821506f36157a08321e9.zip
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.rs338
-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, 685 insertions, 945 deletions
diff --git a/zellij-client/src/fake_client.rs b/zellij-client/src/fake_client.rs
index 7338d633..88b41c9e 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 3f92c537..5f8cc063 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 9790984e..3b81694b 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 60c22cc9..f9f3c590 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 25f0e4fb..09247bf9 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(),
- ));
+ events.push(color_registers);
}
- 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
+ }
+ 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(AnsiStdinInstructionOrKeys::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")
+ let key_string = String::from_utf8_lossy(bytes); // TODO: handle error
+ 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(AnsiStdinInstruction::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(AnsiStdinInstruction::PixelDimensions(PixelDimensions {
+ character_cell_size: Some(SizeInPixels {
+ height: first_field.unwrap(),
+ width: second_field.unwrap(),
+ }),
+ text_area_size: None,
+ }))
+ },
+ _ => Err("invalid sequence"),
}
}
- pub fn color_sequence_from_keys(keys: &[(Key, Vec<u8>)]) -> Result<Self, &'static str> {
+ pub fn bg_or_fg_from_bytes(bytes: &[u8]) -> Result<Self, &'static str> {
+ // eg. <ESC>]11;rgb:0000/0000/0000\
lazy_static! {
- static ref BACKGROUND_RE: Regex = Regex::new(r"11;(.*)$").unwrap();
+ static ref BACKGROUND_RE: Regex = Regex::new(r"\]11;(.*)\u{1b}\\$").unwrap();
}
+ // eg. <ESC>]10;rgb:ffff/ffff/ffff\
lazy_static! {
- static ref FOREGROUND_RE: Regex = Regex::new(r"10;(.*)$").unwrap();
+ static ref FOREGROUND_RE: Regex = Regex::new(r"\]10;(.*)\u{1b}\\$").unwrap();
}
- let key_string = keys.iter().fold(String::new(), |mut acc, (key, _)| {
- if let Key::Char(c) = key {
- acc.push(*c)
- };
- acc
- });
+ let key_string = String::from_utf8_lossy(bytes);
if let Some(captures) = BACKGROUND_RE.captures_iter(&key_string).next() {
let background_query_response = captures[1].parse::<String>();
- Ok(AnsiStdinInstructionOrKeys::BackgroundColor(
- background_query_response.unwrap(),
- ))
+ match background_query_response {
+ Ok(background_query_response) => Ok(AnsiStdinInstruction::BackgroundColor(
+ background_query_response,
+ )),
+ _ => Err("invalid_instruction"),
+ }
} else if let Some(captures) = FOREGROUND_RE.captures_iter(&key_string).next() {
let foreground_query_response = captures[1].parse::<String>();
- Ok(AnsiStdinInstructionOrKeys::ForegroundColor(
- foreground_query_response.unwrap(),
- ))
+ match foreground_query_response {
+ Ok(foreground_query_response) => Ok(AnsiStdinInstruction::ForegroundColor(
+ foreground_query_response,
+ )),
+ _ => Err("invalid_instruction"),
+ }
} else {
Err("invalid_instruction")
}
}
+ pub fn color_registers_from_bytes(color_sequences: &mut Vec<(usize, String)>) -> Option<Self> {
+ if color_sequences.is_empty() {
+ return None;
+ }
+ let mut registers = vec![];
+ for (color_register, color_sequence) in color_sequences.drain(..) {
+ registers.push((color_register, color_sequence));
+ }
+ Some(AnsiStdinInstruction::ColorRegisters(registers))
+ }
+}
+
+fn color_sequence_from_bytes(bytes: &[u8]) -> Result<(usize, String), &'static str> {
+ lazy_static! {
+ static ref COLOR_REGISTER_RE: Regex = Regex::new(r"\]4;(.*);(.*)\u{1b}\\$").unwrap();
+ }
+ lazy_static! {
+ // this form is used by eg. Alacritty, where the leading 4 is dropped in the response
+ static ref ALTERNATIVE_COLOR_REGISTER_RE: Regex = Regex::new(r"\](.*);(.*)\u{1b}\\$").unwrap();
+ }
+ let key_string = String::from_utf8_lossy(bytes);
+ if let Some(captures) = COLOR_REGISTER_RE.captures_iter(&key_string).next() {
+ let color_register_response = captures[1].parse::<usize>();
+ let color_response = captures[2].parse::<String>();
+ match (color_register_response, color_response) {
+ (Ok(crr), Ok(cr)) => Ok((crr, cr)),
+ _ => Err("invalid_instruction"),
+ }
+ } else if let Some(captures) = ALTERNATIVE_COLOR_REGISTER_RE
+ .captures_iter(&key_string)
+ .next()
+ {
+ let color_register_response = captures[1].parse::<usize>();
+ let color_response = captures[2].parse::<String>();
+ match (color_register_response, color_response) {
+ (Ok(crr), Ok(cr)) => Ok((crr, cr)),
+ _ => Err("invalid_instruction"),
+ }
+ } else {
+ Err("invalid_instruction")
+ }
}
diff --git a/zellij-client/src/stdin_handler.rs b/zellij-client/src/stdin_handler.rs
index 687ecd10..8d6e51b0 100644
--- a/zellij-client/src/stdin_handler.rs
+++ b/zellij-client/src/stdin_handler.rs
@@ -1,17 +1,46 @@
use crate::os_input_output::ClientOsApi;
+use crate::stdin_ansi_parser::StdinAnsiParser;
use crate::InputInstruction;
+use std::sync::{Arc, Mutex};
use zellij_utils::channels::SenderWithContext;
use zellij_utils::termwiz::input::{InputEvent, InputParser, MouseButtons};
pub(crate) fn stdin_loop(
- os_input: Box<dyn ClientOsApi>,
+ mut os_input: Box<dyn ClientOsApi>,
send_input_instructions: SenderWithContext<InputInstruction>,
+ stdin_ansi_parser: Arc<Mutex<StdinAnsiParser>>,
) {
let mut holding_mouse = false;
let mut input_parser = InputParser::new();
let mut current_buffer = vec![];
+ // on startup we send a query to the terminal emulator for stuff like the pixel size and colors
+ // we get a response through STDIN, so it makes sense to do this here
+ let terminal_emulator_query_string = stdin_ansi_parser
+ .lock()
+ .unwrap()
+ .terminal_emulator_query_string();
+ let _ = os_input
+ .get_stdout_writer()
+ .write(terminal_emulator_query_string.as_bytes())
+ .unwrap();
loop {
let buf = os_input.read_from_stdin();
+ {
+ // here we check if we need to parse specialized ANSI instructions sent over STDIN
+ // this happens either on startup (see above) or on SIGWINCH
+ //
+ // if we need to parse them, we do so with an internal timeout - anything else we
+ // receive on STDIN during that timeout is unceremoniously dropped
+ let mut stdin_ansi_parser = stdin_ansi_parser.lock().unwrap();
+ if stdin_ansi_parser.should_parse() {
+ let events = stdin_ansi_parser.parse(buf);
+ if !events.is_empty() {
+ let _ = send_input_instructions
+ .send(InputInstruction::AnsiStdinInstructions(events));
+ }
+ continue;
+ }
+ }
current_buffer.append(&mut buf.to_vec());
let maybe_more = false; // read_from_stdin should (hopefully) always empty the STDIN buffer completely
let mut events = vec![];
diff --git a/zellij-client/src/unit/input_handler_tests.rs b/zellij-client/src/unit/input_handler_tests.rs
deleted file mode 100644
index b772eb5c..00000000
--- a/zellij-client/src/unit/input_handler_tests.rs
+++ /dev/null
@@ -1,733 +0,0 @@
-use super::input_loop;
-use zellij_utils::data::{InputMode, Palette};
-use zellij_utils::input::actions::{Action, Direction};
-use zellij_utils::input::config::Config;
-use zellij_utils::input::options::Options;
-use zellij_utils::nix;
-use zellij_utils::pane_size::{Size, SizeInPixels};
-use zellij_utils::termwiz::input::{InputEvent, KeyCode, KeyEvent, Modifiers};
-
-use crate::InputInstruction;
-use crate::{
- os_input_output::{ClientOsApi, StdinPoller},
- ClientInstruction, CommandIsExecuting,
-};
-
-use std::path::Path;
-
-use std::io;
-use std::os::unix::io::RawFd;
-use std::sync::{Arc, Mutex};
-use zellij_utils::{
- errors::ErrorContext,
- ipc::{ClientToServerMsg, PixelDimensions, ServerToClientMsg},
-};
-
-use zellij_utils::channels::{self, ChannelWithContext, SenderWithContext};
-
-#[allow(unused)]
-pub mod commands {
- pub const QUIT: [u8; 1] = [17]; // ctrl-q
- pub const ESC: [u8; 1] = [27];
- pub const ENTER: [u8; 1] = [10]; // char '\n'
-
- pub const MOVE_FOCUS_LEFT_IN_NORMAL_MODE: [u8; 2] = [27, 104]; // alt-h
- pub const MOVE_FOCUS_RIGHT_IN_NORMAL_MODE: [u8; 2] = [27, 108]; // alt-l
-
- pub const PANE_MODE: [u8; 1] = [16]; // ctrl-p
- pub const SPAWN_TERMINAL_IN_PANE_MODE: [u8; 1] = [110]; // n
- pub const MOVE_FOCUS_IN_PANE_MODE: [u8; 1] = [112]; // p
- pub const SPLIT_DOWN_IN_PANE_MODE: [u8; 1] = [100]; // d
- pub const SPLIT_RIGHT_IN_PANE_MODE: [u8; 1] = [114]; // r
- pub const TOGGLE_ACTIVE_TERMINAL_FULLSCREEN_IN_PANE_MODE: [u8; 1] = [102]; // f
- pub const CLOSE_PANE_IN_PANE_MODE: [u8; 1] = [120]; // x
- pub const MOVE_FOCUS_DOWN_IN_PANE_MODE: [u8; 1] = [106]; // j
- pub const MOVE_FOCUS_UP_IN_PANE_MODE: [u8; 1] = [107]; // k
- pub const MOVE_FOCUS_LEFT_IN_PANE_MODE: [u8; 1] = [104]; // h
- pub const MOVE_FOCUS_RIGHT_IN_PANE_MODE: [u8; 1] = [108]; // l
-
- pub const SCROLL_MODE: [u8; 1] = [19]; // ctrl-s
- pub const SCROLL_UP_IN_SCROLL_MODE: [u8; 1] = [107]; // k
- pub const SCROLL_DOWN_IN_SCROLL_MODE: [u8; 1] = [106]; // j
- pub const SCROLL_PAGE_UP_IN_SCROLL_MODE: [u8; 1] = [2]; // ctrl-b
- pub const SCROLL_PAGE_DOWN_IN_SCROLL_MODE: [u8; 1] = [6]; // ctrl-f
-
- pub const RESIZE_MODE: [u8; 1] = [18]; // ctrl-r
- pub const RESIZE_DOWN_IN_RESIZE_MODE: [u8; 1] = [106]; // j
- pub const RESIZE_UP_IN_RESIZE_MODE: [u8; 1] = [107]; // k
- pub const RESIZE_LEFT_IN_RESIZE_MODE: [u8; 1] = [104]; // h
- pub const RESIZE_RIGHT_IN_RESIZE_MODE: [u8; 1] = [108]; // l
-
- pub const TAB_MODE: [u8; 1] = [20]; // ctrl-t
- pub const NEW_TAB_IN_TAB_MODE: [u8; 1] = [110]; // n
- pub const SWITCH_NEXT_TAB_IN_TAB_MODE: [u8; 1] = [108]; // l
- pub const SWITCH_PREV_TAB_IN_TAB_MODE: [u8; 1] = [104]; // h
- pub const CLOSE_TAB_IN_TAB_MODE: [u8; 1] = [120]; // x
-
- pub const BRACKETED_PASTE_START: [u8; 6] = [27, 91, 50, 48, 48, 126]; // \u{1b}[200~
- pub const BRACKETED_PASTE_END: [u8; 6] = [27, 91, 50, 48, 49, 126]; // \u{1b}[201
- 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, buf: &[u8]) -> Result<usize, io::Error> {
- self.buffer.lock().unwrap().extend_from_slice(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 {
- pub fn new(
- events_sent_to_server: Arc<Mutex<Vec<ClientToServerMsg>>>,
- command_is_executing: CommandIsExecuting,
- ) -> Self {
- // while command_is_executing itself is implemented with an Arc<Mutex>, we have to have an
- // 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 {
- fn get_terminal_size_using_fd(&self, _fd: RawFd) -> Size {
- unimplemented!()
- }
- fn set_raw_mode(&mut self, _fd: RawFd) {
- unimplemented!()
- }
- fn unset_raw_mode(&self, _fd: RawFd) -> Result<(), nix::Error> {
- unimplemented!()
- }
- fn get_stdout_writer(&self) -> Box<dyn io::Write> {
- 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!()
- }
- fn read_from_stdin(&self) -> Vec<u8> {
- unimplemented!()
- }
- fn box_clone(&self) -> Box<dyn ClientOsApi> {
- unimplemented!()
- }
- fn send_to_server(&self, msg: ClientToServerMsg) {
- {
- let mut events_sent_to_server = self.events_sent_to_server.lock().unwrap();
- events_sent_to_server.push(msg);
- }
- {
- let mut command_is_executing = self.command_is_executing.lock().unwrap();
- command_is_executing.unblock_input_thread();
- }
- }
- fn recv_from_server(&self) -> Option<(ServerToClientMsg, ErrorContext)> {
- unimplemented!()
- }
- fn handle_signals(&self, _sigwinch_cb: Box<dyn Fn()>, _quit_cb: Box<dyn Fn()>) {
- unimplemented!()
- }
- fn connect_to_server(&self, _path: &Path) {
- unimplemented!()
- }
- fn load_palette(&self) -> Palette {
- unimplemented!()
- }
- fn enable_mouse(&self) {}
- fn disable_mouse(&self) {}
- fn stdin_poller(&self) -> StdinPoller {
- unimplemented!()
- }
-}
-
-fn extract_actions_sent_to_server(
- events_sent_to_server: Arc<Mutex<Vec<ClientToServerMsg>>>,
-) -> Vec<Action> {
- let events_sent_to_server = events_sent_to_server.lock().unwrap();
- events_sent_to_server.iter().fold(vec![], |mut acc, event| {
- if let ClientToServerMsg::Action(action, None) = event {
- acc.push(action.clone());
- }
- acc
- })
-}
-
-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);
- }
- acc
- })
-}
-
-#[test]
-pub fn quit_breaks_input_loop() {
- 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 = Box::new(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(
- client_os_api,
- config,
- options,
- command_is_executing,
- send_client_instructions,
- default_mode,
- receive_input_instructions,
- );
- let expected_actions_sent_to_server = vec![Action::Quit];
- let received_actions = extract_actions_sent_to_server(events_sent_to_server);
- assert_eq!(
- expected_actions_sent_to_server, received_actions,
- "All actions sent to server properly"
- );
-}
-
-#[test]
-pub fn move_focus_left_in_normal_mode() {
- let stdin_events = vec![
- (
- commands::MOVE_FOCUS_LEFT_IN_NORMAL_MODE.to_vec(),
- InputEvent::Key(KeyEvent {
- key: KeyCode::Char('h'),
- modifiers: Modifiers::ALT,
- }),
- ),
- (
- 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 = Box::new(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(
- client_os_api,
- config,
- options,
- command_is_executing,
- send_client_instructions,
- default_mode,
- receive_input_instructions,
- );
- let expected_actions_sent_to_server =
- vec![Action::MoveFocusOrTab(Direction::Left), Action::Quit];
- let received_actions = extract_actions_sent_to_server(events_sent_to_server);
- assert_eq!(
- expected_actions_sent_to_server, received_actions,
- "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, 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\u{1b}]11;?\u{1b}\\\u{1b}]10;?\u{1b}\\"
- )),
- );
-}
-
-#[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);
- 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);
- 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 {
- key: KeyCode::Escape,
- 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);
- assert_eq!(
- actions_sent_to_server,
- vec![
- Action::Write(vec![27]),
- Action::Write(vec![b'[']),
- Action::Write(vec![27]),
- 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![],);
-}
diff --git a/zellij-client/src/unit/snapshots/zellij_client__input_handler__input_handler_tests__pixel_info_sent_to_server.snap b/zellij-client/src/unit/snapshots/zellij_client__input_handler__input_handler_tests__pixel_info_sent_to_server.snap
new file mode 100644
index 00000000..14e5d101
--- /dev/null
+++ b/zellij-client/src/unit/snapshots/zellij_client__input_handler__input_handler_tests__pixel_info_sent_to_server.snap
@@ -0,0 +1,6 @@
+---
+source: zellij-client/src/./unit/input_handler_tests.rs
+assertion_line: 429
+expression: "*format!(\"{:?}\", events_sent_to_server.lock().unwrap())"
+---
+[TerminalPixelDimensions(PixelDimensions { text_area_size: Some(SizeInPixels { height: 401, width: 1566 }), character_cell_size: None }), TerminalPixelDimensions(PixelDimensions { text_area_size: None, character_cell_size: Some(SizeInPixels { height: 21, width: 8 }) }), BackgroundColor("rgb:0000/0000/0000"), ForegroundColor("rgb:ffff/ffff/ffff"), ColorRegisters([(0, "rgb:0000/0000/0000"), (1, "rgb:cdcd/0000/0000"), (2, "rgb:0000/cdcd/0000"), (3, "rgb:cdcd/cdcd/0000"), (4, "rgb:0000/0000/eeee"), (5, "rgb:cdcd/0000/cdcd"), (6, "rgb:0000/cdcd/cdcd"), (7, "rgb:e5e5/e5e5/e5e5"), (8, "rgb:7f7f/7f7f/7f7f"), (9, "rgb:ffff/0000/0000"), (10, "rgb:0000/ffff/0000"), (11, "rgb:ffff/ffff/0000"), (12, "rgb:5c5c/5c5c/ffff"), (13, "rgb:ffff/0000/ffff"), (14, "rgb:0000/ffff/ffff"), (15, "rgb:ffff/ffff/ffff"), (16, "rgb:0000/0000/0000"), (17, "rgb:0000/0000/5f5f"), (18, "rgb:0000/0000/8787"), (19, "rgb:0000/0000/afaf"), (20, "rgb:0000/0000/d7d7"), (21, "rgb:0000/0000/ffff"), (22, "rgb:0000/5f5f/0000"), (23, "rgb:0000/5f5f/5f5f"), (24, "rgb:0000/5f5f/8787"), (25, "rgb:0000/5f5f/afaf"), (26, "rgb:0000/5f5f/d7d7"), (27, "rgb:0000/5f5f/ffff"), (28, "rgb:0000/8787/0000"), (29, "rgb:0000/8787/5f5f"), (30, "rgb:0000/8787/8787"), (31, "rgb:0000/8787/afaf"), (32, "rgb:0000/8787/d7d7"), (33, "rgb:0000/8787/ffff"), (34, "rgb:0000/afaf/0000"), (35, "rgb:0000/afaf/5f5f"), (36, "rgb:0000/afaf/8787"), (37, "rgb:0000/afaf/afaf"), (38, "rgb:0000/afaf/d7d7"), (39, "rgb:0000/afaf/ffff"), (40, "rgb:0000/d7d7/0000"), (41, "rgb:0000/d7d7/5f5f"), (42, "rgb:0000/d7d7/8787"), (43, "rgb:0000/d7d7/afaf"), (44, "rgb:0000/d7d7/d7d7"), (45, "rgb:0000/d7d7/ffff"), (46, "rgb:0000/ffff/0000"), (47, "rgb:0000/ffff/5f5f"), (48, "rgb:0000/ffff/8787"), (49, "rgb:0000/ffff/afaf"), (50, "rgb:0000/ffff/d7d7"), (51, "rgb:0000/ffff/ffff"), (52, "rgb:5f5f/0000/0000"), (53, "rgb:5f5f/0000/5f5f"), (54, "rgb:5f5f/0000/8787"), (55, "rgb:5f5f/0000/afaf"), (56, "rgb:5f5f/0000/d7d7"), (57, "rgb:5f5f/0000/ffff"), (58, "rgb:5f5f/5f5f/0000"), (59, "rgb:5f5f/5f5f/5f5f"), (60, "rgb:5f5f/5f5f/8787"), (61, "rgb:5f5f/5f5f/afaf"), (62, "rgb:5f5f/5f5f/d7d7"), (63, "rgb:5f5f/5f5f/ffff"), (64, "rgb:5f5f/8787/0000"), (65, "rgb:5f5f/8787/5f5f"), (66, "rgb:5f5f/8787/8787"), (67, "rgb:5f5f/8787/afaf"), (68, "rgb:5f5f/8787/d7d7"), (69, "rgb:5f5f/8787/ffff"), (70, "rgb:5f5f/afaf/0000"), (71, "rgb:5f5f/afaf/5f5f"), (72, "rgb:5f5f/afaf/8787"), (73, "rgb:5f5f/afaf/afaf"), (74, "rgb:5f5f/afaf/d7d7"), (75, "rgb:5f5f/afaf/ffff"), (76, "rgb:5f5f/d7d7/0000"), (77, "rgb:5f5f/d7d7/5f5f"), (78, "rgb:5f5f/d7d7/8787"), (79, "rgb:5f5f/d7d7/afaf"), (80, "rgb:5f5f/d7d7/d7d7"), (81, "rgb:5f5f/d7d7/ffff"), (82, "rgb:5f5f/ffff/0000"), (83, "rgb:5f5f/ffff/5f5f"), (84, "rgb:5f5f/ffff/8787"), (85, "rgb:5f5f/ffff/afaf"), (86, "rgb:5f5f/ffff/d7d7"), (87, "rgb:5f5f/ffff/ffff"), (88, "rgb:8787/0000/0000"), (89, "rgb:8787/0000/5f5f"), (90, "rgb:8787/0000/8787"), (91, "rgb:8787/0000/afaf"), (92, "rgb:8787/0000/d7d7"), (93, "rgb:8787/0000/ffff"), (94, "rgb:8787/5f5f/0000"), (95, "rgb:8787/5f5f/5f5f"), (96, "rgb:8787/5f5f/8787"), (97, "rgb:8787/5f5f/afaf"), (98, "rgb:8787/5f5f/d7d7"), (99, "rgb:8787/5f5f/ffff"), (100, "rgb:8787/8787/0000"), (101, "rgb:8787/8787/5f5f"), (102, "rgb:8787/8787/8787"), (103, "rgb:8787/8787/afaf"), (104, "rgb:8787/8787/d7d7"), (105, "rgb:8787/8787/ffff"), (106, "rgb:8787/afaf/0000"), (107, "rgb:8787/afaf/5f5f"), (108, "rgb:8787/afaf/8787"), (109, "rgb:8787/afaf/afaf"), (110, "rgb:8787/afaf/d7d7"), (111, "rgb:8787/afaf/ffff"), (112, "rgb:8787/d7d7/0000"), (113, "rgb:8787/d7d7/5f5f"), (114, "rgb:8787/d7d7/8787"), (115, "rgb:8787/d7d7/afaf"), (116, "rgb:8787/d7d7/d7d7"), (117, "rgb:8787/d7d7/ffff"), (118, "rgb:8787/ffff/0000"), (119, "rgb:8787/ffff/5f5f"), (120, "rgb:8787/ffff/8787"), (121, "rgb:8787/ffff/afaf"), (122, "rgb:8787/ffff/d7d7"), (123, "rgb:8787/ffff/ffff"), (124, "rgb:afaf/0000/0000"), (125, "rgb:afaf/0000/5f5f"), (126, "rgb:afaf/0000/8787"), (127, "rgb:afaf/0000/afaf"), (128, "rgb:afaf/0000/d7d7"), (129, "rgb:afaf/0000/ffff"), (130, "rgb:afaf/5f5f/0000"), (131, "rgb:afaf/5f5f/5f5f"), (132, "rgb:afaf/5f5f/8787"), (133, "rgb:afaf/5f5f/afaf"), (134, "rgb:afaf/5f5f/d7d7"), (135, "rgb:afaf/5f5f/ffff"), (136, "rgb:afaf/8787/0000"), (137, "rgb:afaf/8787/5f5f"), (138, "rgb:afaf/8787/8787"), (139, "rgb:afaf/8787/afaf"), (140, "rgb:afaf/8787/d7d7"), (141, "rgb:afaf/8787/ffff"), (142, "rgb:afaf/afaf/0000"), (143, "rgb:afaf/afaf/5f5f"), (144, "rgb:afaf/afaf/8787"), (145, "rgb:afaf/afaf/afaf"), (146, "rgb:afaf/afaf/d7d7"), (147, "rgb:afaf/afaf/ffff"), (148, "rgb:afaf/d7d7/0000"), (149, "rgb:afaf/d7d7/5f5f"), (150, "rgb:afaf/d7d7/8787"), (151, "rgb:afaf/d7d7/afaf"), (152, "rgb:afaf/d7d7/d7d7"), (153, "rgb:afaf/d7d7/ffff"), (154, "rgb:afaf/ffff/0000"), (155, "rgb:afaf/ffff/5f5f"), (156, "rgb:afaf/ffff/8787"), (157, "rgb:afaf/ffff/afaf"), (158, "rgb:afaf/ffff/d7d7"), (159, "rgb:afaf/ffff/ffff"), (160, "rgb:d7d7/0000/0000"), (161, "rgb:d7d7/0000/5f5f"), (162, "rgb:d7d7/0000/8787"), (163, "rgb:d7d7/0000/afaf"), (164, "rgb:d7d7/0000/d7d7"), (165, "rgb:d7d7/0000/ffff"), (166, "rgb:d7d7/5f5f/0000"), (167, "rgb:d7d7/5f5f/5f5f"), (168, "rgb:d7d7/5f5f/8787"), (169, "rgb:d7d7/5f5f/afaf"), (170, "rgb:d7d7/5f5f/d7d7"), (171, "rgb:d7d7/5f5f/ffff"), (172, "rgb:d7d7/8787/0000"), (173, "rgb:d7d7/8787/5f5f"), (174, "rgb:d7d7/8787/8787"), (175, "rgb:d7d7/8787/afaf"), (176, "rgb:d7d7/8787/d7d7"), (177, "rgb:d7d7/8787/ffff"), (178, "rgb:d7d7/afaf/0000"), (179, "rgb:d7d7/afaf/5f5f"), (180, "rgb:d7d7/afaf/8787"), (181, "rgb:d7d7/afaf/afaf"), (182, "rgb:d7d7/afaf/d7d7"), (183, "rgb:d7d7/afaf/ffff"), (184, "rgb:d7d7/d7d7/0000"), (185, "rgb:d7d7/d7d7/5f5f"), (186, "rgb:d7d7/d7d7/8787"), (187, "rgb:d7d7/d7d7/afaf"), (188, "rgb:d7d7/d7d7/d7d7"), (189, "rgb:d7d7/d7d7/ffff"), (190, "rgb:d7d7/ffff/0000"), (191, "rgb:d7d7/ffff/5f5f"), (192, "rgb:d7d7/ffff/8787"), (193, "rgb:d7d7/ffff/afaf"), (194, "rgb:d7d7/ffff/d7d7"), (195, "rgb:d7d7/ffff/ffff"), (196, "rgb:ffff/0000/0000"), (197, "rgb:ffff/0000/5f5f"), (198, "rgb:ffff/0000/8787"), (199, "rgb:ffff/0000/afaf"), (200, "rgb:ffff/0000/d7d7"), (201, "rgb:ffff/0000/ffff"), (202, "rgb:ffff/5f5f/0000"), (203, "rgb:ffff/5f5f/5f5f"), (204, "rgb:ffff/5f5f/8787"), (205, "rgb:ffff/5f5f/afaf"), (206, "rgb:ffff/5f5f/d7d7"), (207, "rgb:ffff/5f5f/ffff"), (208, "rgb:ffff/8787/0000"), (209, "rgb:ffff/8787/5f5f"), (210, "rgb:ffff/8787/8787"), (211, "rgb:ffff/8787/afaf"), (212, "rgb:ffff/8787/d7d7"), (213, "rgb:ffff/8787/ffff"), (214, "rgb:ffff/afaf/0000"), (215, "rgb:ffff/afaf/5f5f"), (216, "rgb:ffff/afaf/8787"), (217, "rgb:ffff/afaf/afaf"), (218, "rgb:ffff/afaf/d7d7"), (219, "rgb:ffff/afaf/ffff"), (220, "rgb:ffff/d7d7/0000"), (221, "rgb:ffff/d7d7/5f5f"), (222, "rgb:ffff/d7d7/8787"), (223, "rgb:ffff/d7d7/afaf"), (224, "rgb:ffff/d7d7/d7d7"), (225, "rgb:ffff/d7d7/ffff"), (226, "rgb:ffff/ffff/0000"), (227, "rgb:ffff/ffff/5f5f"), (228, "rgb:ffff/ffff/8787"), (229, "rgb:ffff/ffff/afaf"), (230, "rgb:ffff/ffff/d7d7"), (231, "rgb:ffff/ffff/ffff"), (232, "rgb:0808/0808/0808"), (233, "rgb:1212/1212/1212"), (234, "rgb:1c1c/1c1c/1c1c"), (235, "rgb:2626/2626/2626"), (236, "rgb:3030/3030/3030"), (237, "rgb:3a3a/3a3a/3a3a"), (238, "rgb:4444/4444/4444"), (239, "rgb:4e4e/4e4e/4e4e"), (240, "rgb:5858/5858/5858"), (241, "rgb:6262/6262/6262"), (242, "rgb:6c6c/6c6c/6c6c"), (243, "rgb:7676/7676/7676"), (244, "rgb:8080/8080/8080"), (245, "rgb:8a8a/8a8a/8a8a"), (246, "rgb:9494/9494/9494"), (247, "rgb:9e9e/9e9e/9e9e"), (248, "rgb:a8a8/a8a8/a8a8"), (249, "rgb:b2b2/b2b2/b2b2"), (250, "rgb:bcbc/bcbc/bcbc"), (251, "rgb:c6c6/c6c6/c6c6"), (252, "rgb:d0d0/d0d0/d0d0"), (253, "rgb:dada/dada/dada"), (254, "rgb:e4e4/e4e4/e4e4"), (255, "rgb:eeee/eeee/eeee")])]
diff --git a/zellij-client/src/unit/snapshots/zellij_client__stdin_tests__pixel_info_sent_to_server.snap b/zellij-client/src/unit/snapshots/zellij_client__stdin_tests__pixel_info_sent_to_server.snap
new file mode 100644
index 00000000..c58a2f5a
--- /dev/null
+++ b/zellij-client/src/unit/snapshots/zellij_client__stdin_tests__pixel_info_sent_to_server.snap
@@ -0,0 +1,6 @@
+---
+source: zellij-client/src/./unit/stdin_tests.rs
+assertion_line: 399
+expression: "*format!(\"{:?}\", events_sent_to_server.lock().unwrap())"
+---
+[TerminalPixelDimensions(PixelDimensions { text_area_size: Some(SizeInPixels { height: 401, width: 1566 }), character_cell_size: None }), TerminalPixelDimensions(PixelDimensions { text_area_size: None, character_cell_size: Some(SizeInPixels { height: 21, width: 8 }) }), BackgroundColor("rgb:0000/0000/0000"), ForegroundColor("rgb:ffff/ffff/ffff"), ColorRegisters([(0, "rgb:0000/0000/0000"), (1, "rgb:cdcd/0000/0000"), (2, "rgb:0000/cdcd/0000"), (3, "rgb:cdcd/cdcd/0000"), (4, "rgb:0000/0000/eeee"), (5, "rgb:cdcd/0000/cdcd"), (6, "rgb:0000/cdcd/cdcd"), (7, "rgb:e5e5/e5e5/e5e5"), (8, "rgb:7f7f/7f7f/7f7f"), (9, "rgb:ffff/0000/0000"), (10, "rgb:0000/ffff/0000"), (11, "rgb:ffff/ffff/0000"), (12, "rgb:5c5c/5c5c/ffff"), (13, "rgb:ffff/0000/ffff"), (14, "rgb:0000/ffff/ffff"), (15, "rgb:ffff/ffff/ffff"), (16, "rgb:0000/0000/0000"), (17, "rgb:0000/0000/5f5f"), (18, "rgb:0000/0000/8787"), (19, "rgb:0000/0000/afaf"), (20, "rgb:0000/0000/d7d7"), (21, "rgb:0000/0000/ffff"), (22, "rgb:0000/5f5f/0000"), (23, "rgb:0000/5f5f/5f5f"), (24, "rgb:0000/5f5f/8787"), (25, "rgb:0000/5f5f/afaf"), (26, "rgb:0000/5f5f/d7d7"), (27, "rgb:0000/5f5f/ffff"), (28, "rgb:0000/8787/0000"), (29, "rgb:0000/8787/5f5f"), (30, "rgb:0000/8787/8787"), (31, "rgb:0000/8787/afaf"), (32, "rgb:0000/8787/d7d7"), (33, "rgb:0000/8787/ffff"), (34, "rgb:0000/afaf/0000"), (35, "rgb:0000/afaf/5f5f"), (36, "rgb:0000/afaf/8787"), (37, "rgb:0000/afaf/afaf"), (38, "rgb:0000/afaf/d7d7"), (39, "rgb:0000/afaf/ffff"), (40, "rgb:0000/d7d7/0000"), (41, "rgb:0000/d7d7/5f5f"), (42, "rgb:0000/d7d7/8787"), (43, "rgb:0000/d7d7/afaf"), (44, "rgb:0000/d7d7/d7d7"), (45, "rgb:0000/d7d7/ffff"), (46, "rgb:0000/ffff/0000"), (47, "rgb:0000/ffff/5f5f"), (48, "rgb:0000/ffff/8787"), (49, "rgb:0000/ffff/afaf"), (50, "rgb:0000/ffff/d7d7"), (51, "rgb:0000/ffff/ffff"), (52, "rgb:5f5f/0000/0000"), (53, "rgb:5f5f/0000/5f5f"), (54, "rgb:5f5f/0000/8787"), (55, "rgb:5f5f/0000/afaf"), (56, "rgb:5f5f/0000/d7d7"), (57, "rgb:5f5f/0000/ffff"), (58, "rgb:5f5f/5f5f/0000"), (59, "rgb:5f5f/5f5f/5f5f"), (60, "rgb:5f5f/5f5f/8787"), (61, "rgb:5f5f/5f5f/afaf"), (62, "rgb:5f5f/5f5f/d7d7"), (63, "rgb:5f5f/5f5f/ffff"), (64, "rgb:5f5f/8787/0000"), (65, "rgb:5f5f/8787/5f5f"), (66, "rgb:5f5f/8787/8787"), (67, "rgb:5f5f/8787/afaf"), (68, "rgb:5f5f/8787/d7d7"), (69, "rgb:5f5f/8787/ffff"), (70, "rgb:5f5f/afaf/0000"), (71, "rgb:5f5f/afaf/5f5f"), (72, "rgb:5f5f/afaf/8787"), (73, "rgb:5f5f/afaf/afaf"), (74, "rgb:5f5f/afaf/d7d7"), (75, "rgb:5f5f/afaf/ffff"), (76, "rgb:5f5f/d7d7/0000"), (77, "rgb:5f5f/d7d7/5f5f"), (78, "rgb:5f5f/d7d7/8787"), (79, "rgb:5f5f/d7d7/afaf"), (80, "rgb:5f5f/d7d7/d7d7"), (81, "rgb:5f5f/d7d7/ffff"), (82, "rgb:5f5f/ffff/0000"), (83, "rgb:5f5f/ffff/5f5f"), (84, "rgb:5f5f/ffff/8787"), (85, "rgb:5f5f/ffff/afaf"), (86, "rgb:5f5f/ffff/d7d7"), (87, "rgb:5f5f/ffff/ffff"), (88, "rgb:8787/0000/0000"), (89, "rgb:8787/0000/5f5f"), (90, "rgb:8787/0000/8787"), (91, "rgb:8787/0000/afaf"), (92, "rgb:8787/0000/d7d7"), (93, "rgb:8787/0000/ffff"), (94, "rgb:8787/5f5f/0000"), (95, "rgb:8787/5f5f/5f5f"), (96, "rgb:8787/5f5f/8787"), (97, "rgb:8787/5f5f/afaf"), (98, "rgb:8787/5f5f/d7d7"), (99, "rgb:8787/5f5f/ffff"), (100, "rgb:8787/8787/0000"), (101, "rgb:8787/8787/5f5f"), (102, "rgb:8787/8787/8787"), (103, "rgb:8787/8787/afaf"), (104, "rgb:8787/8787/d7d7"), (105, "rgb:8787/8787/ffff"), (106, "rgb:8787/afaf/0000"), (107, "rgb:8787/afaf/5f5f"), (108, "rgb:8787/afaf/8787"), (109, "rgb:8787/afaf/afaf"), (110, "rgb:8787/afaf/d7d7"), (111, "rgb:8787/afaf/ffff"), (112, "rgb:8787/d7d7/0000"), (113, "rgb:8787/d7d7/5f5f"), (114, "rgb:8787/d7d7/8787"), (115, "rgb:8787/d7d7/afaf"), (116, "rgb:8787/d7d7/d7d7"), (117, "rgb:8787/d7d7/ffff"), (118, "rgb:8787/ffff/0000"), (119, "rgb:8787/ffff/5f5f"), (120, "rgb:8787/ffff/8787"), (121, "rgb:8787/ffff/afaf"), (122, "rgb:8787/ffff/d7d7"), (123, "rgb:8787/ffff/ffff"), (124, "rgb:afaf/0000/0000"), (125, "rgb:afaf/0000/5f5f"), (126, "rgb:afaf/0000/8787"), (127, "rgb:afaf/0000/afaf"), (128, "rgb:afaf/0000/d7d7"), (129, "rgb:afaf/0000/ffff"), (130, "rgb:afaf/5f5f/0000"), (131, "rgb:afaf/5f5f/5f5f"), (132, "rgb:afaf/5f5f/8787"), (133, "rgb:afaf/5f5f/afaf"), (134, "rgb:afaf/5f5f/d7d7"), (135, "rgb:afaf/5f5f/ffff"), (136, "rgb:afaf/8787/0000"), (137, "rgb:afaf/8787/5f5f"), (138, "rgb:afaf/8787/8787"), (139, "rgb:afaf/8787/afaf"), (140, "rgb:afaf/8787/d7d7"), (141, "rgb:afaf/8787/ffff"), (142, "rgb:afaf/afaf/0000"), (143, "rgb:afaf/afaf/5f5f"), (144, "rgb:afaf/afaf/8787"), (145, "rgb:afaf/afaf/afaf"), (146, "rgb:afaf/afaf/d7d7"), (147, "rgb:afaf/afaf/ffff"), (148, "rgb:afaf/d7d7/0000"), (149, "rgb:afaf/d7d7/5f5f"), (150, "rgb:afaf/d7d7/8787"), (151, "rgb:afaf/d7d7/afaf"), (152, "rgb:afaf/d7d7/d7d7"), (153, "rgb:afaf/d7d7/ffff"), (154, "rgb:afaf/ffff/0000"), (155, "rgb:afaf/ffff/5f5f"), (156, "rgb:afaf/ffff/8787"), (157, "rgb:afaf/ffff/afaf"), (158, "rgb:afaf/ffff/d7d7"), (159, "rgb:afaf/ffff/ffff"), (160, "rgb:d7d7/0000/0000"), (161, "rgb:d7d7/0000/5f5f"), (162, "rgb:d7d7/0000/8787"), (163, "rgb:d7d7/0000/afaf"), (164, "rgb:d7d7/0000/d7d7"), (165, "rgb:d7d7/0000/ffff"), (166, "rgb:d7d7/5f5f/0000"), (167, "rgb:d7d7/5f5f/5f5f"), (168, "rgb:d7d7/5f5f/8787"), (169, "rgb:d7d7/5f5f/afaf"), (170, "rgb:d7d7/5f5f/d7d7"), (171, "rgb:d7d7/5f5f/ffff"), (172, "rgb:d7d7/8787/0000"), (173, "rgb:d7d7/8787/5f5f"), (174, "rgb:d7d7/8787/8787"), (175, "rgb:d7d7/8787/afaf"), (176, "rgb:d7d7/8787/d7d7"), (177, "rgb:d7d7/8787/ffff"), (178, "rgb:d7d7/afaf/0000"), (179, "rgb:d7d7/afaf/5f5f"), (180, "rgb:d7d7/afaf/8787"), (181, "rgb:d7d7/afaf/afaf"), (182, "rgb:d7d7/afaf/d7d7"), (183, "rgb:d7d7/afaf/ffff"), (184, "rgb:d7d7/d7d7/0000"), (185, "rgb:d7d7/d7d7/5f5f"), (186, "rgb:d7d7/d7d7/8787"), (187, "rgb:d7d7/d7d7/afaf"), (188, "rgb:d7d7/d7d7/d7d7"), (189, "rgb:d7d7/d7d7/ffff"), (190, "rgb:d7d7/ffff/0000"), (191, "rgb:d7d7/ffff/5f5f"), (192, "rgb:d7d7/ffff/8787"), (193, "rgb:d7d7/ffff/afaf"), (194, "rgb:d7d7/ffff/d7d7"), (195, "rgb:d7d7/ffff/ffff"), (196, "rgb:ffff/0000/0000"), (197, "rgb:ffff/0000/5f5f"), (198, "rgb:ffff/0000/8787"), (199, "rgb:ffff/0000/afaf"), (200, "rgb:ffff/0000/d7d7"), (201, "rgb:ffff/0000/ffff"), (202, "rgb:ffff/5f5f/0000"), (203, "rgb:ffff/5f5f/5f5f"), (204, "rgb:ffff/5f5f/8787"), (205, "rgb:ffff/5f5f/afaf"), (206, "rgb:ffff/5f5f/d7d7"), (207, "rgb:ffff/5f5f/ffff"), (208, "rgb:ffff/8787/0000"), (209, "rgb:ffff/8787/5f5f"), (210, "rgb:ffff/8787/8787"), (211, "rgb:ffff/8787/afaf"), (212, "rgb:ffff/8787/d7d7"), (213, "rgb:ffff/8787/ffff"), (214, "rgb:ffff/afaf/0000"), (215, "rgb:ffff/afaf/5f5f"), (216, "rgb:ffff/afaf/8787"), (217, "rgb:ffff/afaf/afaf"), (218, "rgb:ffff/afaf/d7d7"), (219, "rgb:ffff/afaf/ffff"), (220, "rgb:ffff/d7d7/0000"), (221, "rgb:ffff/d7d7/5f5f"), (222, "rgb:ffff/d7d7/8787"), (223, "rgb:ffff/d7d7/afaf"), (224, "rgb:ffff/d7d7/d7d7"), (225, "rgb:ffff/d7d7/ffff"), (226, "rgb:ffff/ffff/0000"), (227, "rgb:ffff/ffff/5f5f"), (228, "rgb:ffff/ffff/8787"), (229, "rgb:ffff/ffff/afaf"), (230, "rgb:ffff/ffff/d7d7"), (231, "rgb:ffff/ffff/ffff"), (232, "rgb:0808/0808/0808"), (233, "rgb:1212/1212/1212"), (234, "rgb:1c1c/1c1c/1c1c"), (235, "rgb:2626/2626/2626"), (236, "rgb:3030/3030/3030"), (237, "rgb:3a3a/3a3a/3a3a"), (238, "rgb:4444/4444/4444"), (239, "rgb:4e4e/4e4e/4e4e"), (240, "rgb:5858/5858/5858"), (241, "rgb:6262/6262/6262"), (242, "rgb:6c6c/6c6c/6c6c"), (243, "rgb:7676/7676/7676"), (244, "rgb:8080/8080/8080"), (245, "rgb:8a8a/8a8a/8a8a"), (246, "rgb:9494/9494/9494"), (247, "rgb:9e9e/9e9e/9e9e"), (248, "rgb:a8a8/a8a8/a8a8"), (249, "rgb:b2b2/b2b2/b2b2"), (250, "rgb:bcbc/bcbc/bcbc"), (251, "rgb:c6c6/c6c6/c6c6"), (252, "rgb:d0d0/d0d0/d0d0"), (253, "rgb:dada/dada/dada"), (254, "rgb:e4e4/e4e4/e4e4"), (255, "rgb:eeee/eeee/eeee")])]
diff --git a/zellij-client/src/unit/stdin_tests.rs b/zellij-client/src/unit/stdin_tests.rs
new file mode 100644
index 00000000..d51734ac
--- /dev/null
+++ b/zellij-client/src/unit/stdin_tests.rs
@@ -0,0 +1,409 @@
+use super::input_loop;
+use crate::stdin_ansi_parser::StdinAnsiParser;
+use crate::stdin_loop;
+use zellij_utils::data::{InputMode, Palette};
+use zellij_utils::input::actions::{Action, Direction};
+use zellij_utils::input::config::Config;
+use zellij_utils::input::options::Options;
+use zellij_utils::nix;
+use zellij_utils::pane_size::Size;
+use zellij_utils::termwiz::input::{InputEvent, KeyCode, KeyEvent, Modifiers};
+
+use crate::InputInstruction;
+use crate::{
+ os_input_output::{ClientOsApi, StdinPoller},
+ ClientInstruction, CommandIsExecuting,
+};
+
+use ::insta::assert_snapshot;
+use std::path::Path;
+
+use std::io;
+use std::os::unix::io::RawFd;
+use std::sync::{Arc, Mutex};
+use std::thread;
+use zellij_utils::{
+ errors::ErrorContext,
+ ipc::{ClientToServerMsg, ServerToClientMsg},
+};
+
+use zellij_utils::channels::{self, ChannelWithContext, SenderWithContext};
+
+fn read_fixture(fixture_name: &str) -> Vec<u8> {
+ let mut path_to_file = std::path::PathBuf::new();
+ path_to_file.push("../src");
+ path_to_file.push("tests");
+ path_to_file.push("fixtures");
+ path_to_file.push(fixture_name);
+ std::fs::read(path_to_file)
+ .unwrap_or_else(|_| panic!("could not read fixture {:?}", &fixture_name))
+}
+
+#[allow(unused)]
+pub mod commands {
+ pub const QUIT: [u8; 1] = [17]; // ctrl-q
+ pub const ESC: [u8; 1] = [27];
+ pub const ENTER: [u8; 1] = [10]; // char '\n'
+
+ pub const MOVE_FOCUS_LEFT_IN_NORMAL_MODE: [u8; 2] = [27, 104]; // alt-h
+ pub const MOVE_FOCUS_RIGHT_IN_NORMAL_MODE: [u8; 2] = [27, 108]; // alt-l
+
+ pub const PANE_MODE: [u8; 1] = [16]; // ctrl-p
+ pub const SPAWN_TERMINAL_IN_PANE_MODE: [u8; 1] = [110]; // n
+ pub const MOVE_FOCUS_IN_PANE_MODE: [u8; 1] = [112]; // p
+ pub const SPLIT_DOWN_IN_PANE_MODE: [u8; 1] = [100]; // d
+ pub const SPLIT_RIGHT_IN_PANE_MODE: [u8; 1] = [114]; // r
+ pub const TOGGLE_ACTIVE_TERMINAL_FULLSCREEN_IN_PANE_MODE: [u8; 1] = [102]; // f
+ pub const CLOSE_PANE_IN_PANE_MODE: [u8; 1] = [120]; // x
+ pub const MOVE_FOCUS_DOWN_IN_PANE_MODE: [u8; 1] = [106]; // j
+ pub const MOVE_FOCUS_UP_IN_PANE_MODE: [u8; 1] = [107]; // k
+ pub const MOVE_FOCUS_LEFT_IN_PANE_MODE: [u8; 1] = [104]; // h
+ pub const MOVE_FOCUS_RIGHT_IN_PANE_MODE: [u8; 1] = [108]; // l
+
+ pub const SCROLL_MODE: [u8; 1] = [19]; // ctrl-s
+ pub const SCROLL_UP_IN_SCROLL_MODE: [u8; 1] = [107]; // k
+ pub const SCROLL_DOWN_IN_SCROLL_MODE: [u8; 1] = [106]; // j
+ pub const SCROLL_PAGE_UP_IN_SCROLL_MODE: [u8; 1] = [2]; // ctrl-b
+ pub const SCROLL_PAGE_DOWN_IN_SCROLL_MODE: [u8; 1] = [6]; // ctrl-f
+
+ pub const RESIZE_MODE: [u8; 1] = [18]; // ctrl-r
+ pub const RESIZE_DOWN_IN_RESIZE_MODE: [u8; 1] = [106]; // j
+ pub const RESIZE_UP_IN_RESIZE_MODE: [u8; 1] = [107]; // k
+ pub const RESIZE_LEFT_IN_RESIZE_MODE: [u8; 1] = [104]; // h
+ pub const RESIZE_RIGHT_IN_RESIZE_MODE: [u8; 1] = [108]; // l
+
+ pub const TAB_MODE: [u8; 1] = [20]; // ctrl-t
+ pub const NEW_TAB_IN_TAB_MODE: [u8; 1] = [110]; // n
+ pub const SWITCH_NEXT_TAB_IN_TAB_MODE: [u8; 1] = [108]; // l
+ pub const SWITCH_PREV_TAB_IN_TAB_MODE: [u8; 1] = [104]; // h
+ pub const CLOSE_TAB_IN_TAB_MODE: [u8; 1] = [120]; // x
+
+ pub const BRACKETED_PASTE_START: [u8; 6] = [27, 91, 50, 48, 48, 126]; // \u{1b}[200~
+ pub const BRACKETED_PASTE_END: [u8; 6] = [27, 91, 50, 48, 49, 126]; // \u{1b}[201
+ 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, buf: &[u8]) -> Result<usize, io::Error> {
+ self.buffer.lock().unwrap().extend_from_slice(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>>>,
+ stdin_buffer: Vec<u8>,
+}
+
+impl FakeClientOsApi {
+ pub fn new(
+ events_sent_to_server: Arc<Mutex<Vec<ClientToServerMsg>>>,
+ command_is_executing: CommandIsExecuting,
+ ) -> Self {
+ // while command_is_executing itself is implemented with an Arc<Mutex>, we have to have an
+ // 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,
+ stdin_buffer: vec![],
+ }
+ }
+ pub fn with_stdin_buffer(mut self, stdin_buffer: Vec<u8>) -> Self {
+ self.stdin_buffer = stdin_buffer;
+ self
+ }
+ pub fn stdout_buffer(&self) -> Vec<u8> {
+ self.stdout_buffer.lock().unwrap().drain(..).collect()
+ }
+}
+
+impl ClientOsApi for FakeClientOsApi {
+ fn get_terminal_size_using_fd(&self, _fd: RawFd) -> Size {
+ unimplemented!()
+ }
+ fn set_raw_mode(&mut self, _fd: RawFd) {
+ unimplemented!()
+ }
+ fn unset_raw_mode(&self, _fd: RawFd) -> Result<(), nix::Error> {
+ unimplemented!()
+ }
+ fn get_stdout_writer(&self) -> Box<dyn io::Write> {
+ 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!()
+ }
+ fn read_from_stdin(&mut self) -> Vec<u8> {
+ self.stdin_buffer.drain(..).collect()
+ }
+ fn box_clone(&self) -> Box<dyn ClientOsApi> {
+ unimplemented!()
+ }
+ fn send_to_server(&self, msg: ClientToServerMsg) {
+ {
+ let mut events_sent_to_server = self.events_sent_to_server.lock().unwrap();
+ events_sent_to_server.push(msg);
+ }
+ {
+ let mut command_is_executing = self.command_is_executing.lock().unwrap();
+ command_is_executing.unblock_input_thread();
+ }
+ }
+ fn recv_from_server(&self) -> Option<(ServerToClientMsg, ErrorContext)> {
+ unimplemented!()
+ }
+ fn handle_signals(&self, _sigwinch_cb: Box<dyn Fn()>, _quit_cb: Box<dyn Fn()>) {
+ unimplemented!()
+ }
+ fn connect_to_server(&self, _path: &Path) {
+ unimplemented!()
+ }
+ fn load_palette(&self) -> Palette {
+ unimplemented!()
+ }
+ fn enable_mouse(&self) {}
+ fn disable_mouse(&self) {}
+ fn stdin_poller(&self) -> StdinPoller {
+ unimplemented!()
+ }
+}
+
+fn extract_actions_sent_to_server(
+ events_sent_to_server: Arc<Mutex<Vec<ClientToServerMsg>>>,
+) -> Vec<Action> {
+ let events_sent_to_server = events_sent_to_server.lock().unwrap();
+ events_sent_to_server.iter().fold(vec![], |mut acc, event| {
+ if let ClientToServerMsg::Action(action, None) = event {
+ acc.push(action.clone());
+ }
+ acc
+ })
+}
+
+#[test]
+pub fn quit_breaks_input_loop() {
+ 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 = Box::new(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(
+ client_os_api,
+ config,
+ options,
+ command_is_executing,
+ send_client_instructions,
+ default_mode,
+ receive_input_instructions,
+ );
+ let expected_actions_sent_to_server = vec![Action::Quit];
+ let received_actions = extract_actions_sent_to_server(events_sent_to_server);
+ assert_eq!(
+ expected_actions_sent_to_server, received_actions,
+ "All actions sent to server properly"
+ );
+}
+
+#[test]
+pub fn move_focus_left_in_normal_mode() {
+ let stdin_events = vec![
+ (
+ commands::MOVE_FOCUS_LEFT_IN_NORMAL_MODE.to_vec(),
+ InputEvent::Key(KeyEvent {
+ key: KeyCode::Char('h'),
+ modifiers: Modifiers::ALT,
+ }),
+ ),
+ (
+ 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 = Box::new(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(
+ client_os_api,
+ config,
+ options,
+ command_is_executing,
+ send_client_instructions,
+ default_mode,
+ receive_input_instructions,
+ );
+ let expected_actions_sent_to_server =
+ vec![Action::MoveFocusOrTab(Direction::Left), Action::Quit];
+ let received_actions = extract_actions_sent_to_server(events_sent_to_server);
+ assert_eq!(
+ expected_actions_sent_to_server, received_actions,
+ "All actions sent to server properly"
+ );
+}
+
+#[test]
+pub fn terminal_info_queried_from_terminal_emulator() {
+ 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, command_is_executing);
+
+ let client_os_api_clone = client_os_api.clone();
+ let (send_input_instructions, _receive_input_instructions): ChannelWithContext<
+ InputInstruction,
+ > = channels::bounded(50);
+ let send_input_instructions = SenderWithContext::new(send_input_instructions);
+ let stdin_ansi_parser = Arc::new(Mutex::new(StdinAnsiParser::new()));
+
+ let stdin_thread = thread::Builder::new()
+ .name("stdin_handler".to_string())
+ .spawn({
+ move || {
+ stdin_loop(
+ Box::new(client_os_api),
+ send_input_instructions,
+ stdin_ansi_parser,
+ )
+ }
+ });
+ std::thread::sleep(std::time::Duration::from_millis(500)); // wait for initial query to be sent
+
+ let extracted_stdout_buffer = client_os_api_clone.stdout_buffer();
+ let mut expected_query =
+ String::from("\u{1b}[14t\u{1b}[16t\u{1b}]11;?\u{1b}\u{5c}\u{1b}]10;?\u{1b}\u{5c}");
+ for i in 0..256 {
+ expected_query.push_str(&format!("\u{1b}]4;{};?\u{1b}\u{5c}", i));
+ }
+ assert_eq!(
+ String::from_utf8(extracted_stdout_buffer),
+ Ok(expected_query),
+ );
+ drop(stdin_thread);
+}
+
+#[test]
+pub fn pixel_info_sent_to_server() {
+ let fake_stdin_buffer = read_fixture("terminal_emulator_startup_response");
+ 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())
+ .with_stdin_buffer(fake_stdin_buffer);
+ 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);
+ let stdin_ansi_parser = Arc::new(Mutex::new(StdinAnsiParser::new()));
+ let stdin_thread = thread::Builder::new()
+ .name("stdin_handler".to_string())
+ .spawn({
+ let client_os_api = client_os_api.clone();
+ move || {
+ stdin_loop(
+ Box::new(client_os_api),
+ send_input_instructions,
+ stdin_ansi_parser,
+ )
+ }
+ });
+
+ let default_mode = InputMode::Normal;
+ let input_thread = thread::Builder::new()
+ .name("input_handler".to_string())
+ .spawn({
+ move || {
+ input_loop(
+ Box::new(client_os_api),
+ config,
+ options,
+ command_is_executing,
+ send_client_instructions,
+ default_mode,
+ receive_input_instructions,
+ )
+ }
+ });
+ std::thread::sleep(std::time::Duration::from_millis(1000)); // wait for initial query to be sent
+ assert_snapshot!(*format!("{:?}", events_sent_to_server.lock().unwrap()));
+ drop(stdin_thread);
+ drop(input_thread);
+}