hotfix(stdin): poll for mouse hold in the stdin thread (#752)

* hotfix(stdin): poll for mouse hold in the stdin thread

* add missing dont panic

* style(fmt): make rustfmt happy
This commit is contained in:
Aram Drevekenin 2021-09-30 10:25:48 +02:00 committed by GitHub
parent f74af9c7bf
commit d7e4ec65db
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 96 additions and 28 deletions

12
Cargo.lock generated
View file

@ -2716,7 +2716,7 @@ dependencies = [
[[package]] [[package]]
name = "zellij" name = "zellij"
version = "0.18.0" version = "0.19.0"
dependencies = [ dependencies = [
"insta", "insta",
"log", "log",
@ -2730,7 +2730,7 @@ dependencies = [
[[package]] [[package]]
name = "zellij-client" name = "zellij-client"
version = "0.18.0" version = "0.19.0"
dependencies = [ dependencies = [
"insta", "insta",
"log", "log",
@ -2742,7 +2742,7 @@ dependencies = [
[[package]] [[package]]
name = "zellij-server" name = "zellij-server"
version = "0.18.0" version = "0.19.0"
dependencies = [ dependencies = [
"ansi_term 0.12.1", "ansi_term 0.12.1",
"async-trait", "async-trait",
@ -2765,7 +2765,7 @@ dependencies = [
[[package]] [[package]]
name = "zellij-tile" name = "zellij-tile"
version = "0.18.0" version = "0.19.0"
dependencies = [ dependencies = [
"serde", "serde",
"serde_json", "serde_json",
@ -2775,14 +2775,14 @@ dependencies = [
[[package]] [[package]]
name = "zellij-tile-utils" name = "zellij-tile-utils"
version = "0.18.0" version = "0.19.0"
dependencies = [ dependencies = [
"ansi_term 0.12.1", "ansi_term 0.12.1",
] ]
[[package]] [[package]]
name = "zellij-utils" name = "zellij-utils"
version = "0.18.0" version = "0.19.0"
dependencies = [ dependencies = [
"async-std", "async-std",
"backtrace", "backtrace",

View file

@ -926,6 +926,7 @@ pub fn mirrored_sessions() {
// if no test timed out, we break the loop and assert the snapshot // if no test timed out, we break the loop and assert the snapshot
let mut first_runner = let mut first_runner =
RemoteRunner::new_with_session_name("mirrored_sessions", fake_win_size, session_name) RemoteRunner::new_with_session_name("mirrored_sessions", fake_win_size, session_name)
.dont_panic()
.add_step(Step { .add_step(Step {
name: "Split pane to the right", name: "Split pane to the right",
instruction: |mut remote_terminal: RemoteTerminal| -> bool { instruction: |mut remote_terminal: RemoteTerminal| -> bool {
@ -959,6 +960,7 @@ pub fn mirrored_sessions() {
let mut second_runner = let mut second_runner =
RemoteRunner::new_existing_session("mirrored_sessions", fake_win_size, session_name) RemoteRunner::new_existing_session("mirrored_sessions", fake_win_size, session_name)
.dont_panic()
.add_step(Step { .add_step(Step {
name: "Make sure session appears correctly", name: "Make sure session appears correctly",
instruction: |remote_terminal: RemoteTerminal| -> bool { instruction: |remote_terminal: RemoteTerminal| -> bool {

View file

@ -211,6 +211,7 @@ pub struct RemoteRunner {
without_frames: bool, without_frames: bool,
session_name: Option<String>, session_name: Option<String>,
attach_to_existing: bool, attach_to_existing: bool,
panic_on_no_retries_left: bool,
pub test_timed_out: bool, pub test_timed_out: bool,
} }
@ -247,6 +248,7 @@ impl RemoteRunner {
session_name: None, session_name: None,
attach_to_existing: false, attach_to_existing: false,
test_timed_out: false, test_timed_out: false,
panic_on_no_retries_left: true,
} }
} }
pub fn new_with_session_name( pub fn new_with_session_name(
@ -285,6 +287,7 @@ impl RemoteRunner {
session_name: Some(String::from(session_name)), session_name: Some(String::from(session_name)),
attach_to_existing: false, attach_to_existing: false,
test_timed_out: false, test_timed_out: false,
panic_on_no_retries_left: true,
} }
} }
pub fn new_existing_session( pub fn new_existing_session(
@ -323,6 +326,7 @@ impl RemoteRunner {
session_name: Some(String::from(session_name)), session_name: Some(String::from(session_name)),
attach_to_existing: true, attach_to_existing: true,
test_timed_out: false, test_timed_out: false,
panic_on_no_retries_left: true,
} }
} }
pub fn new_without_frames(test_name: &'static str, win_size: Size) -> Self { pub fn new_without_frames(test_name: &'static str, win_size: Size) -> Self {
@ -357,6 +361,7 @@ impl RemoteRunner {
session_name: None, session_name: None,
attach_to_existing: false, attach_to_existing: false,
test_timed_out: false, test_timed_out: false,
panic_on_no_retries_left: true,
} }
} }
pub fn new_with_layout( pub fn new_with_layout(
@ -396,8 +401,13 @@ impl RemoteRunner {
session_name: None, session_name: None,
attach_to_existing: false, attach_to_existing: false,
test_timed_out: false, test_timed_out: false,
panic_on_no_retries_left: true,
} }
} }
pub fn dont_panic(mut self) -> Self {
self.panic_on_no_retries_left = false;
self
}
pub fn add_step(mut self, step: Step) -> Self { pub fn add_step(mut self, step: Step) -> Self {
self.steps.push(step); self.steps.push(step);
self self
@ -405,6 +415,35 @@ impl RemoteRunner {
pub fn replace_steps(&mut self, steps: Vec<Step>) { pub fn replace_steps(&mut self, steps: Vec<Step>) {
self.steps = steps; self.steps = steps;
} }
fn display_informative_error(&mut self) {
let test_name = self.test_name;
let current_step_name = self.currently_running_step.as_ref().cloned();
match current_step_name {
Some(current_step) => {
let remote_terminal = self.current_remote_terminal_state();
eprintln!("Timed out waiting for data on the SSH channel for test {}. Was waiting for step: {}", test_name, current_step);
eprintln!("{:?}", remote_terminal);
}
None => {
let remote_terminal = self.current_remote_terminal_state();
eprintln!("Timed out waiting for data on the SSH channel for test {}. Haven't begun running steps yet.", test_name);
eprintln!("{:?}", remote_terminal);
}
}
}
pub fn get_current_snapshot(&mut self) -> String {
take_snapshot(&mut self.terminal_output)
}
fn current_remote_terminal_state(&mut self) -> RemoteTerminal {
let current_snapshot = self.get_current_snapshot();
let (cursor_x, cursor_y) = self.terminal_output.cursor_coordinates().unwrap_or((0, 0));
RemoteTerminal {
cursor_x,
cursor_y,
current_snapshot,
channel: &mut self.channel,
}
}
pub fn run_next_step(&mut self) { pub fn run_next_step(&mut self) {
if let Some(next_step) = self.steps.get(self.current_step_index) { if let Some(next_step) = self.steps.get(self.current_step_index) {
let current_snapshot = take_snapshot(&mut self.terminal_output); let current_snapshot = take_snapshot(&mut self.terminal_output);
@ -482,11 +521,15 @@ impl RemoteRunner {
break; break;
} }
} }
Err(_e) => { Err(e) => {
if self.retries_left > 0 { if self.retries_left > 0 {
return self.restart_test(); return self.restart_test();
} }
self.test_timed_out = true; self.test_timed_out = true;
if self.panic_on_no_retries_left {
self.display_informative_error();
panic!("timed out waiting for test: {:?}", e);
}
} }
} }
} }

View file

@ -157,8 +157,6 @@ impl InputHandler {
} }
MouseEvent::Hold(point) => { MouseEvent::Hold(point) => {
self.dispatch_action(Action::MouseHold(point)); self.dispatch_action(Action::MouseHold(point));
self.os_input
.start_action_repeater(Action::MouseHold(point));
} }
} }
} }

View file

@ -20,7 +20,7 @@ use zellij_utils::{
channels::{self, ChannelWithContext, SenderWithContext}, channels::{self, ChannelWithContext, SenderWithContext},
consts::{SESSION_NAME, ZELLIJ_IPC_PIPE}, consts::{SESSION_NAME, ZELLIJ_IPC_PIPE},
errors::{ClientContext, ContextType, ErrorInstruction}, errors::{ClientContext, ContextType, ErrorInstruction},
input::{actions::Action, config::Config, options::Options}, input::{actions::Action, config::Config, mouse::MouseEvent, options::Options},
ipc::{ClientAttributes, ClientToServerMsg, ExitReason, ServerToClientMsg}, ipc::{ClientAttributes, ClientToServerMsg, ExitReason, ServerToClientMsg},
termion, termion,
}; };
@ -197,6 +197,35 @@ pub fn start_client(
let stdin_buffer = os_input.read_from_stdin(); let stdin_buffer = os_input.read_from_stdin();
for key_result in stdin_buffer.events_and_raw() { for key_result in stdin_buffer.events_and_raw() {
let (key_event, raw_bytes) = key_result.unwrap(); 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_input_instructions
.send(InputInstruction::KeyEvent(key_event, raw_bytes)) .send(InputInstruction::KeyEvent(key_event, raw_bytes))
.unwrap(); .unwrap();

View file

@ -1,4 +1,3 @@
use zellij_utils::input::actions::Action;
use zellij_utils::pane_size::Size; use zellij_utils::pane_size::Size;
use zellij_utils::{interprocess, libc, nix, signal_hook, termion, zellij_tile}; use zellij_utils::{interprocess, libc, nix, signal_hook, termion, zellij_tile};
@ -97,7 +96,7 @@ pub trait ClientOsApi: Send + Sync {
fn enable_mouse(&self); fn enable_mouse(&self);
fn disable_mouse(&self); fn disable_mouse(&self);
// Repeatedly send action, until stdin is readable again // Repeatedly send action, until stdin is readable again
fn start_action_repeater(&mut self, action: Action); fn stdin_poller(&self) -> StdinPoller;
} }
impl ClientOsApi for ClientOsInputOutput { impl ClientOsApi for ClientOsInputOutput {
@ -204,16 +203,8 @@ impl ClientOsApi for ClientOsInputOutput {
} }
} }
fn start_action_repeater(&mut self, action: Action) { fn stdin_poller(&self) -> StdinPoller {
let mut poller = StdinPoller::default(); StdinPoller::default()
loop {
let ready = poller.ready();
if ready {
break;
}
self.send_to_server(ClientToServerMsg::Action(action.clone()));
}
} }
} }
@ -237,7 +228,7 @@ pub fn get_client_os_input() -> Result<ClientOsInputOutput, nix::Error> {
pub const DEFAULT_STDIN_POLL_TIMEOUT_MS: u64 = 10; pub const DEFAULT_STDIN_POLL_TIMEOUT_MS: u64 = 10;
struct StdinPoller { pub struct StdinPoller {
poll: Poll, poll: Poll,
events: Events, events: Events,
timeout: time::Duration, timeout: time::Duration,
@ -245,7 +236,7 @@ struct StdinPoller {
impl StdinPoller { impl StdinPoller {
// use mio poll to check if stdin is readable without blocking // use mio poll to check if stdin is readable without blocking
fn ready(&mut self) -> bool { pub fn ready(&mut self) -> bool {
self.poll self.poll
.poll(&mut self.events, Some(self.timeout)) .poll(&mut self.events, Some(self.timeout))
.expect("could not poll stdin for readiness"); .expect("could not poll stdin for readiness");

View file

@ -8,7 +8,10 @@ use zellij_utils::termion::event::Key;
use zellij_utils::zellij_tile::data::Palette; use zellij_utils::zellij_tile::data::Palette;
use crate::InputInstruction; use crate::InputInstruction;
use crate::{os_input_output::ClientOsApi, ClientInstruction, CommandIsExecuting}; use crate::{
os_input_output::{ClientOsApi, StdinPoller},
ClientInstruction, CommandIsExecuting,
};
use std::path::Path; use std::path::Path;
@ -133,7 +136,9 @@ impl ClientOsApi for FakeClientOsApi {
} }
fn enable_mouse(&self) {} fn enable_mouse(&self) {}
fn disable_mouse(&self) {} fn disable_mouse(&self) {}
fn start_action_repeater(&mut self, _action: Action) {} fn stdin_poller(&self) -> StdinPoller {
unimplemented!()
}
} }
fn extract_actions_sent_to_server( fn extract_actions_sent_to_server(

View file

@ -104,7 +104,7 @@ pub fn get_current_ctx() -> ErrorContext {
} }
/// A representation of the call stack. /// A representation of the call stack.
#[derive(Clone, Copy, Serialize, Deserialize)] #[derive(Clone, Copy, Serialize, Deserialize, Debug)]
pub struct ErrorContext { pub struct ErrorContext {
calls: [ContextType; MAX_THREAD_CALL_STACK], calls: [ContextType; MAX_THREAD_CALL_STACK],
} }
@ -161,7 +161,7 @@ impl Display for ErrorContext {
/// Complex variants store a variant of a related enum, whose variants can be built from /// Complex variants store a variant of a related enum, whose variants can be built from
/// the corresponding Zellij MSPC instruction enum variants ([`ScreenInstruction`], /// the corresponding Zellij MSPC instruction enum variants ([`ScreenInstruction`],
/// [`PtyInstruction`], [`ClientInstruction`], etc). /// [`PtyInstruction`], [`ClientInstruction`], etc).
#[derive(Copy, Clone, PartialEq, Serialize, Deserialize)] #[derive(Copy, Clone, PartialEq, Serialize, Deserialize, Debug)]
pub enum ContextType { pub enum ContextType {
/// A screen-related call. /// A screen-related call.
Screen(ScreenContext), Screen(ScreenContext),