chore(deps): move from termion to termwiz for STDIN handling (#1249)

* fix(deps): switch from termion to termwiz for STDIN parsing

* style(fmt): clippy

* style(fmt): moar clippy

* style(fmt): rustfmt

* fix(tests): e2e mouse press

* style(fmt): rustfmt

* bring back polling

* fmt fmt fmt

* fix some e2e flakiness
This commit is contained in:
Aram Drevekenin 2022-03-23 09:08:35 +01:00 committed by GitHub
parent d394617a3d
commit 7141779153
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 584 additions and 209 deletions

378
Cargo.lock generated
View File

@ -144,7 +144,7 @@ dependencies = [
"futures-lite",
"libc",
"once_cell",
"signal-hook",
"signal-hook 0.3.13",
"winapi",
]
@ -252,6 +252,15 @@ version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "block-buffer"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4"
dependencies = [
"generic-array",
]
[[package]]
name = "blocking"
version = "1.2.0"
@ -423,6 +432,15 @@ version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc"
[[package]]
name = "cpufeatures"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95059428f66df56b63431fdb4e1947ed2190586af5c5a8a8b71122bdf5a7f469"
dependencies = [
"libc",
]
[[package]]
name = "cranelift-bforest"
version = "0.68.0"
@ -644,6 +662,15 @@ dependencies = [
"zeroize",
]
[[package]]
name = "digest"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066"
dependencies = [
"generic-array",
]
[[package]]
name = "directories-next"
version = "2.0.0"
@ -654,6 +681,27 @@ dependencies = [
"dirs-sys-next",
]
[[package]]
name = "dirs"
version = "2.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13aea89a5c93364a98e9b37b2fa237effbb694d5cfe01c5b70941f7eb087d5e3"
dependencies = [
"cfg-if 0.1.10",
"dirs-sys",
]
[[package]]
name = "dirs-sys"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6"
dependencies = [
"libc",
"redox_users",
"winapi",
]
[[package]]
name = "dirs-sys-next"
version = "0.1.2"
@ -728,6 +776,17 @@ dependencies = [
"instant",
]
[[package]]
name = "filedescriptor"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7199d965852c3bac31f779ef99cbb4537f80e952e2d6aa0ffeb30cce00f4f46e"
dependencies = [
"libc",
"thiserror",
"winapi",
]
[[package]]
name = "fnv"
version = "1.0.7"
@ -864,6 +923,16 @@ dependencies = [
"serde",
]
[[package]]
name = "generic-array"
version = "0.14.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd48d33ec7f05fbfa152300fdad764757cbded343c1aa1cff2fbaf4134851803"
dependencies = [
"typenum",
"version_check",
]
[[package]]
name = "getopts"
version = "0.2.21"
@ -873,6 +942,17 @@ dependencies = [
"unicode-width",
]
[[package]]
name = "getrandom"
version = "0.1.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce"
dependencies = [
"cfg-if 1.0.0",
"libc",
"wasi 0.9.0+wasi-snapshot-preview1",
]
[[package]]
name = "getrandom"
version = "0.2.5"
@ -881,7 +961,7 @@ checksum = "d39cd93900197114fa1fcb7ae84ca742095eed9442088988ae74fa744e930e77"
dependencies = [
"cfg-if 1.0.0",
"libc",
"wasi",
"wasi 0.10.0+wasi-snapshot-preview1",
]
[[package]]
@ -954,6 +1034,12 @@ dependencies = [
"libc",
]
[[package]]
name = "hex"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
[[package]]
name = "highway"
version = "0.6.4"
@ -1229,6 +1315,12 @@ dependencies = [
"libc",
]
[[package]]
name = "memmem"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a64a92489e2744ce060c349162be1c5f33c6969234104dbd99ddb5feb08b8c15"
[[package]]
name = "memoffset"
version = "0.6.5"
@ -1328,6 +1420,16 @@ dependencies = [
"memoffset",
]
[[package]]
name = "nom"
version = "5.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ffb4262d26ed83a1c0a33a38fe2bb15797329c85770da05e6b828ddb782627af"
dependencies = [
"memchr",
"version_check",
]
[[package]]
name = "ntapi"
version = "0.3.7"
@ -1337,6 +1439,17 @@ dependencies = [
"winapi",
]
[[package]]
name = "num-derive"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "876a53fff98e03a936a674b29568b0e605f06b29372c2489ff4de23f1949743d"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "num-integer"
version = "0.1.44"
@ -1366,12 +1479,6 @@ dependencies = [
"libc",
]
[[package]]
name = "numtoa"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8f8bdf33df195859076e54ab11ee78a1b208382d3a26ec40d142ffc1ecc49ef"
[[package]]
name = "object"
version = "0.22.0"
@ -1397,6 +1504,12 @@ version = "1.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "87f3e037eac156d1775da914196f0f37741a274155e34a0b7e427c35d2a2ecb9"
[[package]]
name = "opaque-debug"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5"
[[package]]
name = "openssl-sys"
version = "0.9.72"
@ -1471,6 +1584,53 @@ version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e"
[[package]]
name = "pest"
version = "2.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "10f4872ae94d7b90ae48754df22fd42ad52ce740b8f370b03da4835417403e53"
dependencies = [
"ucd-trie",
]
[[package]]
name = "phf"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3dfb61232e34fcb633f43d12c58f83c1df82962dcdfa565a4e866ffc17dafe12"
dependencies = [
"phf_shared",
]
[[package]]
name = "phf_codegen"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cbffee61585b0411840d3ece935cce9cb6321f01c45477d30066498cd5e1a815"
dependencies = [
"phf_generator",
"phf_shared",
]
[[package]]
name = "phf_generator"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "17367f0cc86f2d25802b2c26ee58a7b23faeccf78a396094c13dced0d0182526"
dependencies = [
"phf_shared",
"rand 0.7.3",
]
[[package]]
name = "phf_shared"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c00cf8b9eafe68dde5e9eaa2cef8ee84a9336a47d566ec55ca16589633b65af7"
dependencies = [
"siphasher",
]
[[package]]
name = "pin-project-lite"
version = "0.2.8"
@ -1583,6 +1743,20 @@ dependencies = [
"winapi",
]
[[package]]
name = "rand"
version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03"
dependencies = [
"getrandom 0.1.16",
"libc",
"rand_chacha 0.2.2",
"rand_core 0.5.1",
"rand_hc",
"rand_pcg",
]
[[package]]
name = "rand"
version = "0.8.5"
@ -1590,10 +1764,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
dependencies = [
"libc",
"rand_chacha",
"rand_chacha 0.3.1",
"rand_core 0.6.3",
]
[[package]]
name = "rand_chacha"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402"
dependencies = [
"ppv-lite86",
"rand_core 0.5.1",
]
[[package]]
name = "rand_chacha"
version = "0.3.1"
@ -1619,13 +1803,40 @@ version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc"
[[package]]
name = "rand_core"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19"
dependencies = [
"getrandom 0.1.16",
]
[[package]]
name = "rand_core"
version = "0.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7"
dependencies = [
"getrandom",
"getrandom 0.2.5",
]
[[package]]
name = "rand_hc"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c"
dependencies = [
"rand_core 0.5.1",
]
[[package]]
name = "rand_pcg"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "16abd0c1b639e9eb4d7c50c0b8100b0d0f849be2349829c740fe8e6eb4816429"
dependencies = [
"rand_core 0.5.1",
]
[[package]]
@ -1677,22 +1888,13 @@ dependencies = [
"bitflags",
]
[[package]]
name = "redox_termios"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8440d8acb4fd3d277125b4bd01a6f38aee8d814b3b5fc09b3f2b825d37d3fe8f"
dependencies = [
"redox_syscall 0.2.11",
]
[[package]]
name = "redox_users"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7776223e2696f1aa4c6b0170e83212f47296a00424305117d013dfe86fb0fe55"
dependencies = [
"getrandom",
"getrandom 0.2.5",
"redox_syscall 0.2.11",
"thiserror",
]
@ -1770,6 +1972,24 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
[[package]]
name = "semver"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f301af10236f6df4160f7c3f04eec6dbc70ace82d23326abad5edee88801c6b6"
dependencies = [
"semver-parser",
]
[[package]]
name = "semver-parser"
version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "00b0bef5b7f9e0df16536d3961cfb6e84331c065b4066afb39768d0e319411f7"
dependencies = [
"pest",
]
[[package]]
name = "serde"
version = "1.0.136"
@ -1832,6 +2052,29 @@ dependencies = [
"yaml-rust",
]
[[package]]
name = "sha2"
version = "0.9.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800"
dependencies = [
"block-buffer",
"cfg-if 1.0.0",
"cpufeatures",
"digest",
"opaque-debug",
]
[[package]]
name = "signal-hook"
version = "0.1.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7e31d442c16f047a671b5a71e2161d6e68814012b7f5379d269ebd915fac2729"
dependencies = [
"libc",
"signal-hook-registry",
]
[[package]]
name = "signal-hook"
version = "0.3.13"
@ -1857,6 +2100,12 @@ version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2e24979f63a11545f5f2c60141afe249d4f19f84581ea2138065e400941d83d3"
[[package]]
name = "siphasher"
version = "0.3.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7bd3e3206899af3f8b12af284fafc038cc1dc2b41d1b89dd17297221c5d225de"
[[package]]
name = "slab"
version = "0.4.5"
@ -2084,15 +2333,57 @@ dependencies = [
]
[[package]]
name = "termion"
version = "1.5.6"
name = "terminfo"
version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "077185e2eac69c3f8379a4298e1e07cd36beb962290d4a51199acf0fdc10607e"
checksum = "76971977e6121664ec1b960d1313aacfa75642adc93b9d4d53b247bd4cb1747e"
dependencies = [
"dirs",
"fnv",
"nom",
"phf",
"phf_codegen",
]
[[package]]
name = "termios"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "411c5bf740737c7918b8b1fe232dca4dc9f8e754b8ad5e20966814001ed0ac6b"
dependencies = [
"libc",
"numtoa",
"redox_syscall 0.2.11",
"redox_termios",
]
[[package]]
name = "termwiz"
version = "0.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "31ef6892cc0348a9b3b8c377addba91e0f6365863d92354bf27559dca81ee8c5"
dependencies = [
"anyhow",
"base64",
"bitflags",
"cfg-if 1.0.0",
"filedescriptor",
"hex",
"lazy_static",
"libc",
"log",
"memmem",
"num-derive",
"num-traits",
"ordered-float",
"regex",
"semver",
"sha2",
"signal-hook 0.1.17",
"terminfo",
"termios",
"thiserror",
"ucd-trie",
"unicode-segmentation",
"vtparse",
"winapi",
]
[[package]]
@ -2150,7 +2441,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255"
dependencies = [
"libc",
"wasi",
"wasi 0.10.0+wasi-snapshot-preview1",
"winapi",
]
@ -2217,6 +2508,12 @@ dependencies = [
"unsafe-any",
]
[[package]]
name = "typenum"
version = "1.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987"
[[package]]
name = "typetag"
version = "0.1.8"
@ -2241,6 +2538,12 @@ dependencies = [
"syn",
]
[[package]]
name = "ucd-trie"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56dee185309b50d1f11bfedef0fe6d036842e3fb77413abef29f8f8d1c5d4c1c"
[[package]]
name = "unicode-bidi"
version = "0.3.7"
@ -2354,12 +2657,27 @@ dependencies = [
"quote",
]
[[package]]
name = "vtparse"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f41c9314c4dde1f43dd0c46c67bb5ae73850ce11eebaf7d8b912e178bda5401"
dependencies = [
"utf8parse",
]
[[package]]
name = "waker-fn"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9d5b2c62b4012a3e1eca5a7e077d13b3bf498c4073e33ccd58626607748ceeca"
[[package]]
name = "wasi"
version = "0.9.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519"
[[package]]
name = "wasi"
version = "0.10.0+wasi-snapshot-preview1"
@ -2615,7 +2933,7 @@ dependencies = [
"bincode",
"byteorder",
"generational-arena",
"getrandom",
"getrandom 0.2.5",
"libc",
"serde",
"thiserror",
@ -2819,11 +3137,11 @@ dependencies = [
"serde",
"serde_json",
"serde_yaml",
"signal-hook",
"signal-hook 0.3.13",
"strip-ansi-escapes",
"strum",
"tempfile",
"termion",
"termwiz",
"thiserror",
"unicode-width",
"url",

View File

@ -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
@ -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;
}
@ -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
@ -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
@ -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
@ -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;
}

View File

@ -5,7 +5,8 @@ use zellij_utils::{
mouse::{MouseButton, MouseEvent},
options::Options,
},
termion, zellij_tile,
termwiz::input::InputEvent,
zellij_tile,
};
use crate::{
@ -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},
};
@ -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 {
@ -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) {
@ -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)) => {
@ -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) {
@ -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;
}
}
}

View File

@ -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};
@ -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(

View File

@ -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};
@ -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);
@ -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
@ -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 {
@ -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,
})
}

View File

@ -1,51 +1,25 @@
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;
use zellij_utils::input::mouse::{MouseButton, MouseEvent};
use zellij_utils::termwiz::input::{InputEvent, InputParser};
pub(crate) fn stdin_loop(
os_input: Box<dyn ClientOsApi>,
send_input_instructions: SenderWithContext<InputInstruction>,
) {
let mut pasting = false;
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];
for key_result in os_input.get_stdin_reader().events_and_raw() {
let (key_event, mut raw_bytes) = key_result.unwrap();
if key_event == bracketed_paste_start {
pasting = true;
continue;
} else if pasting && key_event == bracketed_paste_end {
pasting = false;
let pasted_text: Vec<u8> = pasted_text.drain(..).collect();
send_input_instructions
.send(InputInstruction::PastedText(pasted_text))
.unwrap();
continue;
} else if pasting {
pasted_text.append(&mut raw_bytes);
continue;
}
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 holding_mouse = false;
let mut input_parser = InputParser::new();
let mut current_buffer = vec![];
loop {
let buf = os_input.read_from_stdin();
current_buffer.append(&mut buf.to_vec());
let maybe_more = false; // read_from_stdin should (hopefully) always empty the STDIN buffer completely
let parse_input_event = |input_event: InputEvent| {
holding_mouse = should_hold_mouse(input_event.clone());
if holding_mouse {
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 {
@ -53,29 +27,35 @@ pub(crate) fn stdin_loop(
}
send_input_instructions
.send(InputInstruction::KeyEvent(
key_event.clone(),
raw_bytes.clone(),
input_event.clone(),
current_buffer.clone(),
))
.unwrap();
}
continue;
} else {
send_input_instructions
.send(InputInstruction::KeyEvent(
input_event,
current_buffer.drain(..).collect(),
))
.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();
};
input_parser.parse(&buf, parse_input_event, maybe_more);
}
}
fn should_hold_mouse(input_event: InputEvent) -> bool {
if let InputEvent::Mouse(mouse_event) = input_event {
let mouse_event = zellij_utils::input::mouse::MouseEvent::from(mouse_event);
if let MouseEvent::Press(button, _point) = mouse_event {
match button {
MouseButton::Left | MouseButton::Right => {
return true;
}
_ => {}
}
}
}
false
}

View File

@ -3,8 +3,7 @@ use zellij_utils::input::actions::{Action, Direction};
use zellij_utils::input::config::Config;
use zellij_utils::input::options::Options;
use zellij_utils::pane_size::Size;
use zellij_utils::termion::event::Event;
use zellij_utils::termion::event::Key;
use zellij_utils::termwiz::input::{InputEvent, KeyCode, KeyEvent, Modifiers};
use zellij_utils::zellij_tile::data::Palette;
use crate::InputInstruction;
@ -158,7 +157,13 @@ fn extract_actions_sent_to_server(
#[test]
pub fn quit_breaks_input_loop() {
let stdin_events = vec![(commands::QUIT.to_vec(), Event::Key(Key::Ctrl('q')))];
let stdin_events = vec![(
commands::QUIT.to_vec(),
InputEvent::Key(KeyEvent {
key: KeyCode::Char('q'),
modifiers: Modifiers::CTRL,
}),
)];
let events_sent_to_server = Arc::new(Mutex::new(vec![]));
let command_is_executing = CommandIsExecuting::new();
let client_os_api = Box::new(FakeClientOsApi::new(
@ -206,9 +211,18 @@ pub fn move_focus_left_in_normal_mode() {
let stdin_events = vec![
(
commands::MOVE_FOCUS_LEFT_IN_NORMAL_MODE.to_vec(),
Event::Key(Key::Alt('h')),
InputEvent::Key(KeyEvent {
key: KeyCode::Char('h'),
modifiers: Modifiers::ALT,
}),
),
(
commands::QUIT.to_vec(),
InputEvent::Key(KeyEvent {
key: KeyCode::Char('q'),
modifiers: Modifiers::CTRL,
}),
),
(commands::QUIT.to_vec(), Event::Key(Key::Ctrl('q'))),
];
let events_sent_to_server = Arc::new(Mutex::new(vec![]));

View File

@ -29,7 +29,6 @@ serde_json = "1.0"
signal-hook = "0.3"
strip-ansi-escapes = "0.1.0"
strum = "0.20.0"
termion = "1.5.0"
thiserror = "1.0.30"
url = { version = "2.2.2", features = ["serde"] }
vte = "0.10.1"
@ -38,6 +37,8 @@ log = "0.4.14"
log4rs = "1.0.0"
unicode-width = "0.1.8"
miette = { version = "3.3.0", features = ["fancy"] }
termwiz = "0.15.0"
[dependencies.async-std]
version = "1.3.0"

View File

@ -11,7 +11,7 @@ pub mod plugins;
pub mod theme;
use crate::envs;
use termion::input::TermRead;
use termwiz::input::{InputEvent, InputParser, KeyCode, KeyEvent, Modifiers};
use zellij_tile::{
data::{InputMode, Key, ModeInfo, PluginCapabilities},
prelude::Style,
@ -83,33 +83,57 @@ pub fn get_mode_info(mode: InputMode, style: Style, capabilities: PluginCapabili
}
pub fn parse_keys(input_bytes: &[u8]) -> Vec<Key> {
input_bytes.keys().flatten().map(cast_termion_key).collect()
let mut ret = vec![];
let mut input_parser = InputParser::new(); // this is the termwiz InputParser
let maybe_more = false;
let parse_input_event = |input_event: InputEvent| {
if let InputEvent::Key(key_event) = input_event {
ret.push(cast_termwiz_key(key_event, input_bytes));
}
};
input_parser.parse(input_bytes, parse_input_event, maybe_more);
ret
}
// FIXME: This is an absolutely cursed function that should be destroyed as soon
// as an alternative that doesn't touch zellij-tile can be developed...
pub fn cast_termion_key(event: termion::event::Key) -> Key {
match event {
termion::event::Key::Backspace => Key::Backspace,
termion::event::Key::Left => Key::Left,
termion::event::Key::Right => Key::Right,
termion::event::Key::Up => Key::Up,
termion::event::Key::Down => Key::Down,
termion::event::Key::Home => Key::Home,
termion::event::Key::End => Key::End,
termion::event::Key::PageUp => Key::PageUp,
termion::event::Key::PageDown => Key::PageDown,
termion::event::Key::BackTab => Key::BackTab,
termion::event::Key::Delete => Key::Delete,
termion::event::Key::Insert => Key::Insert,
termion::event::Key::F(n) => Key::F(n),
termion::event::Key::Char(c) => Key::Char(c),
termion::event::Key::Alt(c) => Key::Alt(c),
termion::event::Key::Ctrl(c) => Key::Ctrl(c),
termion::event::Key::Null => Key::Null,
termion::event::Key::Esc => Key::Esc,
_ => {
unimplemented!("Encountered an unknown key!")
pub fn cast_termwiz_key(event: KeyEvent, raw_bytes: &[u8]) -> Key {
let modifiers = event.modifiers;
// *** THIS IS WHERE WE SHOULD WORK AROUND ISSUES WITH TERMWIZ ***
if raw_bytes == [8] {
return Key::Ctrl('h');
};
match event.key {
KeyCode::Char(c) => {
if modifiers.contains(Modifiers::CTRL) {
Key::Ctrl(c.to_lowercase().next().unwrap_or_default())
} else if modifiers.contains(Modifiers::ALT) {
Key::Alt(c.to_lowercase().next().unwrap_or_default())
} else {
Key::Char(c)
}
}
KeyCode::Backspace => Key::Backspace,
KeyCode::LeftArrow => Key::Left,
KeyCode::ApplicationLeftArrow => Key::Left,
KeyCode::RightArrow => Key::Right,
KeyCode::ApplicationRightArrow => Key::Right,
KeyCode::UpArrow => Key::Up,
KeyCode::ApplicationUpArrow => Key::Up,
KeyCode::DownArrow => Key::Down,
KeyCode::ApplicationDownArrow => Key::Down,
KeyCode::Home => Key::Home,
KeyCode::End => Key::End,
KeyCode::PageUp => Key::PageUp,
KeyCode::PageDown => Key::PageDown,
KeyCode::Tab => Key::BackTab, // TODO: ???
KeyCode::Delete => Key::Delete,
KeyCode::Insert => Key::Insert,
KeyCode::Function(n) => Key::F(n),
KeyCode::Escape => Key::Esc,
KeyCode::Enter => Key::Char('\n'),
_ => Key::Esc, // there are other keys we can implement here, but we might need additional terminal support to implement them, not just exhausting this enum
}
}

View File

@ -19,21 +19,67 @@ pub enum MouseEvent {
Hold(Position),
}
impl From<termion::event::MouseEvent> for MouseEvent {
fn from(event: termion::event::MouseEvent) -> Self {
match event {
termion::event::MouseEvent::Press(button, x, y) => Self::Press(
MouseButton::from(button),
Position::new((y.saturating_sub(1)) as i32, x.saturating_sub(1)),
),
termion::event::MouseEvent::Release(x, y) => Self::Release(Position::new(
(y.saturating_sub(1)) as i32,
x.saturating_sub(1),
)),
termion::event::MouseEvent::Hold(x, y) => Self::Hold(Position::new(
(y.saturating_sub(1)) as i32,
x.saturating_sub(1),
)),
impl From<termwiz::input::MouseEvent> for MouseEvent {
fn from(event: termwiz::input::MouseEvent) -> Self {
#[allow(clippy::if_same_then_else)]
if event
.mouse_buttons
.contains(termwiz::input::MouseButtons::LEFT)
{
MouseEvent::Press(
MouseButton::Left,
Position::new(event.y.saturating_sub(1) as i32, event.x.saturating_sub(1)),
)
} else if event
.mouse_buttons
.contains(termwiz::input::MouseButtons::RIGHT)
{
MouseEvent::Press(
MouseButton::Right,
Position::new(event.y.saturating_sub(1) as i32, event.x.saturating_sub(1)),
)
} else if event
.mouse_buttons
.contains(termwiz::input::MouseButtons::MIDDLE)
{
MouseEvent::Press(
MouseButton::Middle,
Position::new(event.y.saturating_sub(1) as i32, event.x.saturating_sub(1)),
)
} else if event
.mouse_buttons
.contains(termwiz::input::MouseButtons::VERT_WHEEL)
{
if event
.mouse_buttons
.contains(termwiz::input::MouseButtons::WHEEL_POSITIVE)
{
MouseEvent::Press(
MouseButton::WheelUp,
Position::new(event.y.saturating_sub(1) as i32, event.x.saturating_sub(1)),
)
} else {
MouseEvent::Press(
MouseButton::WheelDown,
Position::new(event.y.saturating_sub(1) as i32, event.x.saturating_sub(1)),
)
}
} else if event
.mouse_buttons
.contains(termwiz::input::MouseButtons::NONE)
{
// release
MouseEvent::Release(Position::new(
event.y.saturating_sub(1) as i32,
event.x.saturating_sub(1),
))
} else {
// this is an unsupported event, we just do this in order to send "something", but if
// something happens here, we might want to add more specific support
MouseEvent::Release(Position::new(
event.y.saturating_sub(1) as i32,
event.x.saturating_sub(1),
))
}
}
}
@ -55,15 +101,3 @@ pub enum MouseButton {
/// This event is typically only used with Mouse::Press.
WheelDown,
}
impl From<termion::event::MouseButton> for MouseButton {
fn from(button: termion::event::MouseButton) -> Self {
match button {
termion::event::MouseButton::Left => Self::Left,
termion::event::MouseButton::Right => Self::Right,
termion::event::MouseButton::Middle => Self::Middle,
termion::event::MouseButton::WheelUp => Self::WheelUp,
termion::event::MouseButton::WheelDown => Self::WheelDown,
}
}
}

View File

@ -20,6 +20,6 @@ pub use nix;
pub use serde;
pub use serde_yaml;
pub use signal_hook;
pub use termion;
pub use termwiz;
pub use vte;
pub use zellij_tile;