mirror of
https://github.com/zellij-org/zellij.git
synced 2024-11-22 22:26:54 +03:00
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:
parent
abbf40e014
commit
843bf58f51
@ -1733,7 +1733,7 @@ pub fn focus_tab_with_layout() {
|
||||
let mut step_is_complete = false;
|
||||
if remote_terminal.status_bar_appears()
|
||||
&& remote_terminal.tip_appears()
|
||||
&& remote_terminal.snapshot_contains("Tab #3")
|
||||
&& remote_terminal.snapshot_contains("Tab #9")
|
||||
&& remote_terminal.cursor_position_is(63, 2)
|
||||
{
|
||||
step_is_complete = true;
|
||||
|
@ -95,29 +95,9 @@ impl InputHandler {
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok((
|
||||
InputInstruction::PastedText((
|
||||
send_bracketed_paste_start,
|
||||
raw_bytes,
|
||||
send_bracketed_paste_end,
|
||||
)),
|
||||
_error_context,
|
||||
)) => {
|
||||
Ok((InputInstruction::PastedText(raw_bytes), _error_context)) => {
|
||||
if self.mode == InputMode::Normal || self.mode == InputMode::Locked {
|
||||
if send_bracketed_paste_start {
|
||||
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);
|
||||
}
|
||||
self.dispatch_action(Action::Write(raw_bytes));
|
||||
}
|
||||
}
|
||||
Ok((InputInstruction::SwitchToMode(input_mode), _error_context)) => {
|
||||
|
@ -107,7 +107,7 @@ impl ClientInfo {
|
||||
pub(crate) enum InputInstruction {
|
||||
KeyEvent(termion::event::Event, Vec<u8>),
|
||||
SwitchToMode(InputMode),
|
||||
PastedText((bool, Vec<u8>, bool)), // (send_brackted_paste_start, pasted_text, send_bracketed_paste_end)
|
||||
PastedText(Vec<u8>),
|
||||
}
|
||||
|
||||
pub fn start_client(
|
||||
|
@ -82,6 +82,7 @@ pub trait ClientOsApi: Send + Sync {
|
||||
fn unset_raw_mode(&self, fd: RawFd);
|
||||
/// Returns the writer that allows writing to standard output.
|
||||
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>;
|
||||
/// Returns a [`Box`] pointer to this [`ClientOsApi`] struct.
|
||||
@ -128,6 +129,11 @@ impl ClientOsApi for ClientOsInputOutput {
|
||||
let stdout = ::std::io::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) {
|
||||
self.send_instructions_to_server
|
||||
.lock()
|
||||
|
@ -29,143 +29,81 @@ fn keys_to_adjust() -> HashMap<Vec<u8>, Vec<u8>> {
|
||||
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(
|
||||
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~
|
||||
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 adjusted_keys = keys_to_adjust();
|
||||
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))
|
||||
{
|
||||
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);
|
||||
for key_result in os_input.get_stdin_reader().events_and_raw() {
|
||||
let (key_event, mut raw_bytes) = key_result.unwrap();
|
||||
|
||||
let ends_with_bracketed_paste_end = true;
|
||||
|
||||
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;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
if stdin_buffer.is_empty() {
|
||||
if key_event == bracketed_paste_start {
|
||||
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;
|
||||
}
|
||||
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);
|
||||
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();
|
||||
|
||||
let raw_bytes = adjusted_keys.get(&raw_bytes).cloned().unwrap_or(raw_bytes);
|
||||
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();
|
||||
loop {
|
||||
let ready = poller.ready();
|
||||
if ready {
|
||||
break;
|
||||
}
|
||||
send_input_instructions
|
||||
.send(InputInstruction::KeyEvent(
|
||||
key_event.clone(),
|
||||
raw_bytes.clone(),
|
||||
))
|
||||
.unwrap();
|
||||
}
|
||||
continue;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// FIXME: termion does not properly parse some csi sgr mouse sequences
|
||||
// like ctrl + click.
|
||||
// As a workaround, to avoid writing these sequences to tty stdin,
|
||||
// we discard them.
|
||||
if let termion::event::Event::Unsupported(_) = key_event {
|
||||
if raw_bytes.len() > csi_mouse_sgr_start.len()
|
||||
&& raw_bytes[0..csi_mouse_sgr_start.len()] == csi_mouse_sgr_start
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
send_input_instructions
|
||||
.send(InputInstruction::KeyEvent(key_event, raw_bytes))
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
// FIXME: termion does not properly parse some csi sgr mouse sequences
|
||||
// like ctrl + click.
|
||||
// As a workaround, to avoid writing these sequences to tty stdin,
|
||||
// we discard them.
|
||||
if let termion::event::Event::Unsupported(_) = key_event {
|
||||
if raw_bytes.len() > csi_mouse_sgr_start.len()
|
||||
&& raw_bytes[0..csi_mouse_sgr_start.len()] == csi_mouse_sgr_start
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
send_input_instructions
|
||||
.send(InputInstruction::KeyEvent(key_event, raw_bytes))
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
|
@ -106,6 +106,9 @@ impl ClientOsApi for FakeClientOsApi {
|
||||
fn get_stdout_writer(&self) -> Box<dyn io::Write> {
|
||||
unimplemented!()
|
||||
}
|
||||
fn get_stdin_reader(&self) -> Box<dyn io::Read> {
|
||||
unimplemented!()
|
||||
}
|
||||
fn read_from_stdin(&self) -> Vec<u8> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user