aboutsummaryrefslogtreecommitdiffstats
path: root/zellij-client
diff options
context:
space:
mode:
authorAram Drevekenin <aram@poor.dev>2021-10-27 19:20:43 +0200
committerGitHub <noreply@github.com>2021-10-27 19:20:43 +0200
commit21e5ffdfd8dce7b2cd1a2a74e948e8e071d22161 (patch)
tree7f3da35f3f3960edd90ee880c618b1770d233f12 /zellij-client
parentchore(docs): update changelog (diff)
downloadsl0ppij-21e5ffdfd8dce7b2cd1a2a74e948e8e071d22161.tar.gz
sl0ppij-21e5ffdfd8dce7b2cd1a2a74e948e8e071d22161.tar.xz
sl0ppij-21e5ffdfd8dce7b2cd1a2a74e948e8e071d22161.zip
fix(input): properly handle bracketed paste (#810)
* fix(input): properly handle bracketed paste * style(fmt): make rustfmt happy
Diffstat (limited to 'zellij-client')
-rw-r--r--zellij-client/src/input_handler.rs35
-rw-r--r--zellij-client/src/lib.rs48
-rw-r--r--zellij-client/src/stdin_handler.rs102
-rw-r--r--zellij-client/src/unit/input_handler_tests.rs64
4 files changed, 118 insertions, 131 deletions
diff --git a/zellij-client/src/input_handler.rs b/zellij-client/src/input_handler.rs
index 78869fd5..2b89741e 100644
--- a/zellij-client/src/input_handler.rs
+++ b/zellij-client/src/input_handler.rs
@@ -31,7 +31,6 @@ struct InputHandler {
command_is_executing: CommandIsExecuting,
send_client_instructions: SenderWithContext<ClientInstruction>,
should_exit: bool,
- pasting: bool,
receive_input_instructions: Receiver<(InputInstruction, ErrorContext)>,
}
@@ -54,7 +53,6 @@ impl InputHandler {
command_is_executing,
send_client_instructions,
should_exit: false,
- pasting: false,
receive_input_instructions,
}
}
@@ -65,9 +63,6 @@ impl InputHandler {
let mut err_ctx = OPENCALLS.with(|ctx| *ctx.borrow());
err_ctx.add_call(ContextType::StdinHandler);
let alt_left_bracket = vec![27, 91];
- let bracketed_paste_start = vec![27, 91, 50, 48, 48, 126]; // \u{1b}[200~
- let bracketed_paste_end = vec![27, 91, 50, 48, 49, 126]; // \u{1b}[201
-
if !self.options.disable_mouse_mode {
self.os_input.enable_mouse();
}
@@ -92,12 +87,6 @@ impl InputHandler {
if unsupported_key == alt_left_bracket {
let key = Key::Alt('[');
self.handle_key(&key, raw_bytes);
- } else if unsupported_key == bracketed_paste_start {
- self.pasting = true;
- self.handle_unknown_key(raw_bytes);
- } else if unsupported_key == bracketed_paste_end {
- self.pasting = false;
- self.handle_unknown_key(raw_bytes);
} else {
// this is a hack because termion doesn't recognize certain keys
// in this case we just forward it to the terminal
@@ -106,6 +95,12 @@ impl InputHandler {
}
}
}
+ Ok((InputInstruction::PastedText(raw_bytes), _error_context)) => {
+ if self.mode == InputMode::Normal || self.mode == InputMode::Locked {
+ let action = Action::Write(raw_bytes);
+ self.dispatch_action(action);
+ }
+ }
Ok((InputInstruction::SwitchToMode(input_mode), _error_context)) => {
self.mode = input_mode;
}
@@ -121,20 +116,10 @@ impl InputHandler {
}
fn handle_key(&mut self, key: &Key, raw_bytes: Vec<u8>) {
let keybinds = &self.config.keybinds;
- if self.pasting {
- // we're inside a paste block, if we're in a mode that allows sending text to the
- // terminal, send all text directly without interpreting it
- // otherwise, just discard the input
- if self.mode == InputMode::Normal || self.mode == InputMode::Locked {
- let action = Action::Write(raw_bytes);
- self.dispatch_action(action);
- }
- } else {
- for action in Keybinds::key_to_actions(key, raw_bytes, &self.mode, keybinds) {
- let should_exit = self.dispatch_action(action);
- if should_exit {
- self.should_exit = true;
- }
+ for action in Keybinds::key_to_actions(key, raw_bytes, &self.mode, keybinds) {
+ let should_exit = self.dispatch_action(action);
+ if should_exit {
+ self.should_exit = true;
}
}
}
diff --git a/zellij-client/src/lib.rs b/zellij-client/src/lib.rs
index 06f7e32e..45842fec 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 stdin_handler;
use log::info;
use std::env::current_exe;
@@ -12,15 +13,14 @@ use std::thread;
use crate::{
command_is_executing::CommandIsExecuting, input_handler::input_loop,
- os_input_output::ClientOsApi,
+ os_input_output::ClientOsApi, stdin_handler::stdin_loop,
};
-use termion::input::TermReadEventsAndRaw;
use zellij_tile::data::InputMode;
use zellij_utils::{
channels::{self, ChannelWithContext, SenderWithContext},
consts::{SESSION_NAME, ZELLIJ_IPC_PIPE},
errors::{ClientContext, ContextType, ErrorInstruction},
- input::{actions::Action, config::Config, mouse::MouseEvent, options::Options},
+ input::{actions::Action, config::Config, options::Options},
ipc::{ClientAttributes, ClientToServerMsg, ExitReason, ServerToClientMsg},
termion,
};
@@ -91,9 +91,10 @@ pub enum ClientInfo {
}
#[derive(Debug, Clone)]
-pub enum InputInstruction {
+pub(crate) enum InputInstruction {
KeyEvent(termion::event::Event, Vec<u8>),
SwitchToMode(InputMode),
+ PastedText(Vec<u8>),
}
pub fn start_client(
@@ -193,44 +194,7 @@ pub fn start_client(
.spawn({
let os_input = os_input.clone();
let send_input_instructions = send_input_instructions.clone();
- move || loop {
- let stdin_buffer = os_input.read_from_stdin();
- for key_result in stdin_buffer.events_and_raw() {
- let (key_event, raw_bytes) = key_result.unwrap();
- if let termion::event::Event::Mouse(me) = key_event {
- let mouse_event = zellij_utils::input::mouse::MouseEvent::from(me);
- if let MouseEvent::Hold(_) = mouse_event {
- // as long as the user is holding the mouse down (no other stdin, eg.
- // MouseRelease) we need to keep sending this instruction to the app,
- // because the app itself doesn't have an event loop in the proper
- // place
- let mut poller = os_input.stdin_poller();
- send_input_instructions
- .send(InputInstruction::KeyEvent(
- key_event.clone(),
- raw_bytes.clone(),
- ))
- .unwrap();
- loop {
- let ready = poller.ready();
- if ready {
- break;
- }
- send_input_instructions
- .send(InputInstruction::KeyEvent(
- key_event.clone(),
- raw_bytes.clone(),
- ))
- .unwrap();
- }
- continue;
- }
- }
- send_input_instructions
- .send(InputInstruction::KeyEvent(key_event, raw_bytes))
- .unwrap();
- }
- }
+ move || stdin_loop(os_input, send_input_instructions)
});
let _input_thread = thread::Builder::new()
diff --git a/zellij-client/src/stdin_handler.rs b/zellij-client/src/stdin_handler.rs
new file mode 100644
index 00000000..932550c0
--- /dev/null
+++ b/zellij-client/src/stdin_handler.rs
@@ -0,0 +1,102 @@
+use crate::os_input_output::ClientOsApi;
+use crate::InputInstruction;
+use termion::input::TermReadEventsAndRaw;
+use zellij_utils::channels::SenderWithContext;
+use zellij_utils::input::mouse::MouseEvent;
+use zellij_utils::termion;
+
+fn bracketed_paste_end_position(stdin_buffer: &[u8]) -> Option<usize> {
+ let bracketed_paste_end = vec![27, 91, 50, 48, 49, 126]; // \u{1b}[201
+ let mut bp_position = 0;
+ let mut position = None;
+ for (i, byte) in stdin_buffer.iter().enumerate() {
+ if Some(byte) == bracketed_paste_end.get(bp_position) {
+ position = Some(i);
+ bp_position += 1;
+ if bp_position == bracketed_paste_end.len() - 1 {
+ break;
+ }
+ } else {
+ bp_position = 0;
+ position = None;
+ }
+ }
+ if bp_position == bracketed_paste_end.len() - 1 {
+ position
+ } else {
+ None
+ }
+}
+
+pub(crate) fn stdin_loop(
+ os_input: Box<dyn ClientOsApi>,
+ send_input_instructions: SenderWithContext<InputInstruction>,
+) {
+ let mut pasting = false;
+ let bracketed_paste_start = vec![27, 91, 50, 48, 48, 126]; // \u{1b}[200~
+ loop {
+ let mut stdin_buffer = os_input.read_from_stdin();
+ if pasting
+ || (stdin_buffer.len() > bracketed_paste_start.len()
+ && stdin_buffer
+ .iter()
+ .take(bracketed_paste_start.len())
+ .eq(bracketed_paste_start.iter()))
+ {
+ match bracketed_paste_end_position(&stdin_buffer) {
+ Some(paste_end_position) => {
+ let pasted_input = stdin_buffer.drain(..=paste_end_position).collect();
+ send_input_instructions
+ .send(InputInstruction::PastedText(pasted_input))
+ .unwrap();
+ pasting = false;
+ }
+ None => {
+ send_input_instructions
+ .send(InputInstruction::PastedText(stdin_buffer))
+ .unwrap();
+ pasting = true;
+ continue;
+ }
+ }
+ }
+ if stdin_buffer.is_empty() {
+ continue;
+ }
+ for key_result in stdin_buffer.events_and_raw() {
+ let (key_event, raw_bytes) = key_result.unwrap();
+ if let termion::event::Event::Mouse(me) = key_event {
+ let mouse_event = zellij_utils::input::mouse::MouseEvent::from(me);
+ if let MouseEvent::Hold(_) = mouse_event {
+ // as long as the user is holding the mouse down (no other stdin, eg.
+ // MouseRelease) we need to keep sending this instruction to the app,
+ // because the app itself doesn't have an event loop in the proper
+ // place
+ let mut poller = os_input.stdin_poller();
+ send_input_instructions
+ .send(InputInstruction::KeyEvent(
+ key_event.clone(),
+ raw_bytes.clone(),
+ ))
+ .unwrap();
+ loop {
+ let ready = poller.ready();
+ if ready {
+ break;
+ }
+ send_input_instructions
+ .send(InputInstruction::KeyEvent(
+ key_event.clone(),
+ raw_bytes.clone(),
+ ))
+ .unwrap();
+ }
+ continue;
+ }
+ }
+ send_input_instructions
+ .send(InputInstruction::KeyEvent(key_event, raw_bytes))
+ .unwrap();
+ }
+ }
+}
diff --git a/zellij-client/src/unit/input_handler_tests.rs b/zellij-client/src/unit/input_handler_tests.rs
index f4beb53a..881c7f63 100644
--- a/zellij-client/src/unit/input_handler_tests.rs
+++ b/zellij-client/src/unit/input_handler_tests.rs
@@ -250,67 +250,3 @@ pub fn move_focus_left_in_normal_mode() {
"All actions sent to server properly"
);
}
-
-#[test]
-pub fn bracketed_paste() {
- let stdin_events = vec![
- (
- commands::BRACKETED_PASTE_START.to_vec(),
- Event::Unsupported(commands::BRACKETED_PASTE_START.to_vec()),
- ),
- (
- commands::MOVE_FOCUS_LEFT_IN_NORMAL_MODE.to_vec(),
- Event::Key(Key::Alt('h')),
- ),
- (
- commands::BRACKETED_PASTE_END.to_vec(),
- Event::Unsupported(commands::BRACKETED_PASTE_END.to_vec()),
- ),
- (commands::QUIT.to_vec(), Event::Key(Key::Ctrl('q'))),
- ];
- 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::Write(commands::BRACKETED_PASTE_START.to_vec()),
- Action::Write(commands::MOVE_FOCUS_LEFT_IN_NORMAL_MODE.to_vec()), // keys were directly written to server and not interpreted
- Action::Write(commands::BRACKETED_PASTE_END.to_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"
- );
-}