Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Move from termion to termwiz for STDIN handling #1249

Merged
merged 10 commits into from
Mar 23, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
378 changes: 348 additions & 30 deletions Cargo.lock

Large diffs are not rendered by default.

29 changes: 17 additions & 12 deletions src/tests/e2e/cases.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,15 +58,16 @@ pub const BRACKETED_PASTE_START: [u8; 6] = [27, 91, 50, 48, 48, 126]; // \u{1b}[
pub const BRACKETED_PASTE_END: [u8; 6] = [27, 91, 50, 48, 49, 126]; // \u{1b}[201
pub const SLEEP: [u8; 0] = [];

// simplified, slighty adapted version of alacritty mouse reporting code
pub fn normal_mouse_report(position: Position, button: u8) -> Vec<u8> {
pub fn sgr_mouse_report(position: Position, button: u8) -> Vec<u8> {
// button: (release is with lower case m, not supported here yet)
// 0 => left click
// 2 => right click
// 64 => scroll up
// 65 => scroll down
let Position { line, column } = position;

let mut command = vec![b'\x1b', b'[', b'M', 32 + button];
command.push(32 + 1 + column.0 as u8);
command.push(32 + 1 + line.0 as u8);

command
format!("\u{1b}[<{};{};{}M", button, column.0, line.0)
.as_bytes()
.to_vec()
}

// All the E2E tests are marked as "ignored" so that they can be run separately from the normal
Expand Down Expand Up @@ -247,7 +248,9 @@ pub fn scrolling_inside_a_pane() {
write!(&mut content_to_send, "{:0<58}", "line19 ").unwrap();
write!(&mut content_to_send, "{:0<57}", "line20 ").unwrap();

remote_terminal.send_key(&BRACKETED_PASTE_START);
remote_terminal.send_key(content_to_send.as_bytes());
remote_terminal.send_key(&BRACKETED_PASTE_END);

step_is_complete = true;
}
Expand Down Expand Up @@ -1030,7 +1033,7 @@ fn focus_pane_with_mouse() {
instruction: |mut remote_terminal: RemoteTerminal| -> bool {
let mut step_is_complete = false;
if remote_terminal.cursor_position_is(63, 2) && remote_terminal.tip_appears() {
remote_terminal.send_key(&normal_mouse_report(Position::new(5, 2), 0));
remote_terminal.send_key(&sgr_mouse_report(Position::new(5, 2), 0));
step_is_complete = true;
}
step_is_complete
Expand Down Expand Up @@ -1121,7 +1124,7 @@ pub fn scrolling_inside_a_pane_with_mouse() {
let mut step_is_complete = false;
if remote_terminal.cursor_position_is(118, 20) {
// all lines have been written to the pane
remote_terminal.send_key(&normal_mouse_report(Position::new(2, 64), 64));
remote_terminal.send_key(&sgr_mouse_report(Position::new(2, 64), 64));
step_is_complete = true;
}
step_is_complete
Expand Down Expand Up @@ -1638,8 +1641,10 @@ pub fn bracketed_paste() {
remote_terminal.send_key(&BRACKETED_PASTE_START);
remote_terminal.send_key(&TAB_MODE);
remote_terminal.send_key(&NEW_TAB_IN_TAB_MODE);
remote_terminal.send_key("a".as_bytes());
remote_terminal.send_key("b".as_bytes());
remote_terminal.send_key("c".as_bytes());
remote_terminal.send_key(&BRACKETED_PASTE_END);
remote_terminal.send_key("abc".as_bytes());
step_is_complete = true;
}
step_is_complete
Expand All @@ -1651,7 +1656,7 @@ pub fn bracketed_paste() {
name: "Wait for terminal to render sent keys",
instruction: |remote_terminal: RemoteTerminal| -> bool {
let mut step_is_complete = false;
if remote_terminal.cursor_position_is(9, 2) {
if remote_terminal.snapshot_contains("abc") {
// text has been entered into the only terminal pane
step_is_complete = true;
}
Expand Down
70 changes: 35 additions & 35 deletions zellij-client/src/input_handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ use zellij_utils::{
mouse::{MouseButton, MouseEvent},
options::Options,
},
termion, zellij_tile,
termwiz::input::InputEvent,
zellij_tile,
};

use crate::{
Expand All @@ -14,7 +15,7 @@ use crate::{
use zellij_utils::{
channels::{Receiver, SenderWithContext, OPENCALLS},
errors::{ContextType, ErrorContext},
input::{actions::Action, cast_termion_key, config::Config, keybinds::Keybinds},
input::{actions::Action, cast_termwiz_key, config::Config, keybinds::Keybinds},
ipc::{ClientToServerMsg, ExitReason},
};

Expand All @@ -32,6 +33,7 @@ struct InputHandler {
send_client_instructions: SenderWithContext<ClientInstruction>,
should_exit: bool,
receive_input_instructions: Receiver<(InputInstruction, ErrorContext)>,
holding_mouse: bool,
}

impl InputHandler {
Expand All @@ -54,15 +56,15 @@ impl InputHandler {
send_client_instructions,
should_exit: false,
receive_input_instructions,
holding_mouse: false,
}
}

/// Main input event loop. Interprets the terminal [`Event`](termion::event::Event)s
/// Main input event loop. Interprets the terminal Event
/// as [`Action`]s according to the current [`InputMode`], and dispatches those actions.
fn handle_input(&mut self) {
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.mouse_mode.unwrap_or(true) {
Expand All @@ -73,35 +75,27 @@ impl InputHandler {
break;
}
match self.receive_input_instructions.recv() {
Ok((InputInstruction::KeyEvent(event, raw_bytes), _error_context)) => {
match event {
termion::event::Event::Key(key) => {
let key = cast_termion_key(key);
Ok((InputInstruction::KeyEvent(input_event, raw_bytes), _error_context)) => {
match input_event {
InputEvent::Key(key_event) => {
let key = cast_termwiz_key(key_event, &raw_bytes);
self.handle_key(&key, raw_bytes);
}
termion::event::Event::Mouse(me) => {
let mouse_event = zellij_utils::input::mouse::MouseEvent::from(me);
InputEvent::Mouse(mouse_event) => {
let mouse_event =
zellij_utils::input::mouse::MouseEvent::from(mouse_event);
self.handle_mouse_event(&mouse_event);
}
termion::event::Event::Unsupported(unsupported_key) => {
// we have to do this because of a bug in termion
// this should be a key event and not an unsupported event
if unsupported_key == alt_left_bracket {
let key = Key::Alt('[');
self.handle_key(&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
self.handle_unknown_key(raw_bytes);
InputEvent::Paste(pasted_text) => {
if self.mode == InputMode::Normal || self.mode == InputMode::Locked {
self.dispatch_action(Action::Write(bracketed_paste_start.clone()));
self.dispatch_action(Action::Write(
pasted_text.as_bytes().to_vec(),
));
self.dispatch_action(Action::Write(bracketed_paste_end.clone()));
}
}
}
}
Ok((InputInstruction::PastedText(raw_bytes), _error_context)) => {
if self.mode == InputMode::Normal || self.mode == InputMode::Locked {
self.dispatch_action(Action::Write(bracketed_paste_start.clone()));
self.dispatch_action(Action::Write(raw_bytes));
self.dispatch_action(Action::Write(bracketed_paste_end.clone()));
_ => {}
}
}
Ok((InputInstruction::SwitchToMode(input_mode), _error_context)) => {
Expand All @@ -111,12 +105,6 @@ impl InputHandler {
}
}
}
fn handle_unknown_key(&mut self, raw_bytes: Vec<u8>) {
if self.mode == InputMode::Normal || self.mode == InputMode::Locked {
let action = Action::Write(raw_bytes);
self.dispatch_action(action);
}
}
fn handle_key(&mut self, key: &Key, raw_bytes: Vec<u8>) {
let keybinds = &self.config.keybinds;
for action in Keybinds::key_to_actions(key, raw_bytes, &self.mode, keybinds) {
Expand All @@ -136,18 +124,30 @@ impl InputHandler {
self.dispatch_action(Action::ScrollDownAt(point));
}
MouseButton::Left => {
self.dispatch_action(Action::LeftClick(point));
if self.holding_mouse {
self.dispatch_action(Action::MouseHold(point));
} else {
self.dispatch_action(Action::LeftClick(point));
}
self.holding_mouse = true;
}
MouseButton::Right => {
self.dispatch_action(Action::RightClick(point));
if self.holding_mouse {
self.dispatch_action(Action::MouseHold(point));
} else {
self.dispatch_action(Action::RightClick(point));
}
self.holding_mouse = true;
}
_ => {}
},
MouseEvent::Release(point) => {
self.dispatch_action(Action::MouseRelease(point));
self.holding_mouse = false;
}
MouseEvent::Hold(point) => {
self.dispatch_action(Action::MouseHold(point));
self.holding_mouse = true;
}
}
}
Expand Down
5 changes: 2 additions & 3 deletions zellij-client/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ use zellij_utils::{
errors::{ClientContext, ContextType, ErrorInstruction},
input::{actions::Action, config::Config, options::Options},
ipc::{ClientAttributes, ClientToServerMsg, ExitReason, ServerToClientMsg},
termion,
termwiz::input::InputEvent,
};
use zellij_utils::{cli::CliArgs, input::layout::LayoutFromYaml};

Expand Down Expand Up @@ -106,9 +106,8 @@ impl ClientInfo {

#[derive(Debug, Clone)]
pub(crate) enum InputInstruction {
KeyEvent(termion::event::Event, Vec<u8>),
KeyEvent(InputEvent, Vec<u8>),
SwitchToMode(InputMode),
PastedText(Vec<u8>),
}

pub fn start_client(
Expand Down
24 changes: 12 additions & 12 deletions zellij-client/src/os_input_output.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
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, zellij_tile};

use interprocess::local_socket::LocalSocketStream;
use mio::{unix::SourceFd, Events, Interest, Poll, Token};
Expand All @@ -20,6 +20,9 @@ use zellij_utils::{

const SIGWINCH_CB_THROTTLE_DURATION: time::Duration = time::Duration::from_millis(50);

const ENABLE_MOUSE_SUPPORT: &str = "\u{1b}[?1000h\u{1b}[?1002h\u{1b}[?1015h\u{1b}[?1006h";
const DISABLE_MOUSE_SUPPORT: &str = "\u{1b}[?1006l\u{1b}[?1015l\u{1b}[?1002l\u{1b}[?1000l";

fn into_raw_mode(pid: RawFd) {
let mut tio = termios::tcgetattr(pid).expect("could not get terminal attribute");
termios::cfmakeraw(&mut tio);
Expand Down Expand Up @@ -66,7 +69,6 @@ pub struct ClientOsInputOutput {
orig_termios: Arc<Mutex<termios::Termios>>,
send_instructions_to_server: Arc<Mutex<Option<IpcSenderWithContext<ClientToServerMsg>>>>,
receive_instructions_from_server: Arc<Mutex<Option<IpcReceiverWithContext<ServerToClientMsg>>>>,
mouse_term: Arc<Mutex<Option<termion::input::MouseTerminal<std::io::Stdout>>>>,
}

/// The `ClientOsApi` trait represents an abstract interface to the features of an operating system that
Expand Down Expand Up @@ -204,17 +206,17 @@ impl ClientOsApi for ClientOsInputOutput {
default_palette()
}
fn enable_mouse(&self) {
let mut mouse_term = self.mouse_term.lock().unwrap();
if mouse_term.is_none() {
*mouse_term = Some(termion::input::MouseTerminal::from(std::io::stdout()));
}
let _ = self
.get_stdout_writer()
.write(ENABLE_MOUSE_SUPPORT.as_bytes())
.unwrap();
}

fn disable_mouse(&self) {
let mut mouse_term = self.mouse_term.lock().unwrap();
if mouse_term.is_some() {
*mouse_term = None;
}
let _ = self
.get_stdout_writer()
.write(DISABLE_MOUSE_SUPPORT.as_bytes())
.unwrap();
}

fn stdin_poller(&self) -> StdinPoller {
Expand All @@ -231,12 +233,10 @@ impl Clone for Box<dyn ClientOsApi> {
pub fn get_client_os_input() -> Result<ClientOsInputOutput, nix::Error> {
let current_termios = termios::tcgetattr(0)?;
let orig_termios = Arc::new(Mutex::new(current_termios));
let mouse_term = Arc::new(Mutex::new(None));
Ok(ClientOsInputOutput {
orig_termios,
send_instructions_to_server: Arc::new(Mutex::new(None)),
receive_instructions_from_server: Arc::new(Mutex::new(None)),
mouse_term,
})
}

Expand Down
Loading