fix(input): handle discontiguous STDIN input (#1119)

* log stdin reads and events

* attempt fix for incomplete mouse sequences

* read events directly from stdin

* fix flaky e2e test

* bring back bracketed paste

* rustfmt

* remove unused

Co-authored-by: Thomas Linford <linford.t@gmail.com>
This commit is contained in:
Aram Drevekenin 2022-02-25 14:34:55 +01:00 committed by GitHub
parent abbf40e014
commit 843bf58f51
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 70 additions and 143 deletions

View file

@ -1733,7 +1733,7 @@ pub fn focus_tab_with_layout() {
let mut step_is_complete = false; let mut step_is_complete = false;
if remote_terminal.status_bar_appears() if remote_terminal.status_bar_appears()
&& remote_terminal.tip_appears() && remote_terminal.tip_appears()
&& remote_terminal.snapshot_contains("Tab #3") && remote_terminal.snapshot_contains("Tab #9")
&& remote_terminal.cursor_position_is(63, 2) && remote_terminal.cursor_position_is(63, 2)
{ {
step_is_complete = true; step_is_complete = true;

View file

@ -95,29 +95,9 @@ impl InputHandler {
} }
} }
} }
Ok(( Ok((InputInstruction::PastedText(raw_bytes), _error_context)) => {
InputInstruction::PastedText((
send_bracketed_paste_start,
raw_bytes,
send_bracketed_paste_end,
)),
_error_context,
)) => {
if self.mode == InputMode::Normal || self.mode == InputMode::Locked { if self.mode == InputMode::Normal || self.mode == InputMode::Locked {
if send_bracketed_paste_start { self.dispatch_action(Action::Write(raw_bytes));
let bracketed_paste_start = vec![27, 91, 50, 48, 48, 126]; // \u{1b}[200~
let paste_start_action = Action::Write(bracketed_paste_start);
self.dispatch_action(paste_start_action);
}
let pasted_text_action = Action::Write(raw_bytes);
self.dispatch_action(pasted_text_action);
if send_bracketed_paste_end {
let bracketed_paste_end = vec![27, 91, 50, 48, 49, 126]; // \u{1b}[201~
let paste_end_action = Action::Write(bracketed_paste_end);
self.dispatch_action(paste_end_action);
}
} }
} }
Ok((InputInstruction::SwitchToMode(input_mode), _error_context)) => { Ok((InputInstruction::SwitchToMode(input_mode), _error_context)) => {

View file

@ -107,7 +107,7 @@ impl ClientInfo {
pub(crate) enum InputInstruction { pub(crate) enum InputInstruction {
KeyEvent(termion::event::Event, Vec<u8>), KeyEvent(termion::event::Event, Vec<u8>),
SwitchToMode(InputMode), SwitchToMode(InputMode),
PastedText((bool, Vec<u8>, bool)), // (send_brackted_paste_start, pasted_text, send_bracketed_paste_end) PastedText(Vec<u8>),
} }
pub fn start_client( pub fn start_client(

View file

@ -82,6 +82,7 @@ pub trait ClientOsApi: Send + Sync {
fn unset_raw_mode(&self, fd: RawFd); fn unset_raw_mode(&self, fd: RawFd);
/// Returns the writer that allows writing to standard output. /// Returns the writer that allows writing to standard output.
fn get_stdout_writer(&self) -> Box<dyn io::Write>; 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. /// Returns the raw contents of standard input.
fn read_from_stdin(&self) -> Vec<u8>; fn read_from_stdin(&self) -> Vec<u8>;
/// Returns a [`Box`] pointer to this [`ClientOsApi`] struct. /// Returns a [`Box`] pointer to this [`ClientOsApi`] struct.
@ -128,6 +129,11 @@ impl ClientOsApi for ClientOsInputOutput {
let stdout = ::std::io::stdout(); let stdout = ::std::io::stdout();
Box::new(stdout) Box::new(stdout)
} }
fn get_stdin_reader(&self) -> Box<dyn io::Read> {
let stdin = ::std::io::stdin();
Box::new(stdin)
}
fn send_to_server(&self, msg: ClientToServerMsg) { fn send_to_server(&self, msg: ClientToServerMsg) {
self.send_instructions_to_server self.send_instructions_to_server
.lock() .lock()

View file

@ -29,97 +29,36 @@ fn keys_to_adjust() -> HashMap<Vec<u8>, Vec<u8>> {
keys_to_adjust keys_to_adjust
} }
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() {
break;
}
} else {
bp_position = 0;
position = None;
}
}
if bp_position == bracketed_paste_end.len() {
position
} else {
None
}
}
pub(crate) fn stdin_loop( pub(crate) fn stdin_loop(
os_input: Box<dyn ClientOsApi>, os_input: Box<dyn ClientOsApi>,
send_input_instructions: SenderWithContext<InputInstruction>, send_input_instructions: SenderWithContext<InputInstruction>,
) { ) {
let mut pasting = false; let mut pasting = false;
let bracketed_paste_start = vec![27, 91, 50, 48, 48, 126]; // \u{1b}[200~ let mut pasted_text = vec![];
let bracketed_paste_start = termion::event::Event::Unsupported(vec![27, 91, 50, 48, 48, 126]); // \u{1b}[200~
let bracketed_paste_end = termion::event::Event::Unsupported(vec![27, 91, 50, 48, 49, 126]); // \u{1b}[201~
let csi_mouse_sgr_start = vec![27, 91, 60]; let csi_mouse_sgr_start = vec![27, 91, 60];
let adjusted_keys = keys_to_adjust(); let adjusted_keys = keys_to_adjust();
loop { for key_result in os_input.get_stdin_reader().events_and_raw() {
let mut stdin_buffer = os_input.read_from_stdin(); let (key_event, mut raw_bytes) = key_result.unwrap();
if pasting
|| (stdin_buffer.len() > bracketed_paste_start.len()
&& stdin_buffer
.iter()
.take(bracketed_paste_start.len())
.eq(&bracketed_paste_start))
{
match bracketed_paste_end_position(&stdin_buffer) {
Some(paste_end_position) => {
let starts_with_bracketed_paste_start = stdin_buffer
.iter()
.take(bracketed_paste_start.len())
.eq(&bracketed_paste_start);
let ends_with_bracketed_paste_end = true; if key_event == bracketed_paste_start {
let mut pasted_input: Vec<u8> =
stdin_buffer.drain(..=paste_end_position).collect();
if starts_with_bracketed_paste_start {
drop(pasted_input.drain(..6)); // bracketed paste start
}
drop(pasted_input.drain(pasted_input.len() - 6..)); // bracketed paste end
send_input_instructions
.send(InputInstruction::PastedText((
starts_with_bracketed_paste_start,
pasted_input,
ends_with_bracketed_paste_end,
)))
.unwrap();
pasting = false;
}
None => {
let starts_with_bracketed_paste_start = stdin_buffer
.iter()
.take(bracketed_paste_start.len())
.eq(&bracketed_paste_start);
if starts_with_bracketed_paste_start {
drop(stdin_buffer.drain(..6)); // bracketed paste start
}
send_input_instructions
.send(InputInstruction::PastedText((
starts_with_bracketed_paste_start,
stdin_buffer,
false,
)))
.unwrap();
pasting = true; pasting = true;
pasted_text.append(&mut raw_bytes);
continue;
} else if pasting && key_event == bracketed_paste_end {
pasting = false;
let mut pasted_text: Vec<u8> = pasted_text.drain(..).collect();
pasted_text.append(&mut raw_bytes);
send_input_instructions
.send(InputInstruction::PastedText(pasted_text))
.unwrap();
continue;
} else if pasting {
pasted_text.append(&mut raw_bytes);
continue; continue;
} }
}
}
if stdin_buffer.is_empty() {
continue;
}
for key_result in stdin_buffer.events_and_raw() {
let (key_event, raw_bytes) = key_result.unwrap();
let raw_bytes = adjusted_keys.get(&raw_bytes).cloned().unwrap_or(raw_bytes); let raw_bytes = adjusted_keys.get(&raw_bytes).cloned().unwrap_or(raw_bytes);
if let termion::event::Event::Mouse(me) = key_event { if let termion::event::Event::Mouse(me) = key_event {
let mouse_event = zellij_utils::input::mouse::MouseEvent::from(me); let mouse_event = zellij_utils::input::mouse::MouseEvent::from(me);
@ -168,4 +107,3 @@ pub(crate) fn stdin_loop(
.unwrap(); .unwrap();
} }
} }
}

View file

@ -106,6 +106,9 @@ impl ClientOsApi for FakeClientOsApi {
fn get_stdout_writer(&self) -> Box<dyn io::Write> { fn get_stdout_writer(&self) -> Box<dyn io::Write> {
unimplemented!() unimplemented!()
} }
fn get_stdin_reader(&self) -> Box<dyn io::Read> {
unimplemented!()
}
fn read_from_stdin(&self) -> Vec<u8> { fn read_from_stdin(&self) -> Vec<u8> {
unimplemented!() unimplemented!()
} }