From 7141779153743cb98771c6582a1feff32ec2e494 Mon Sep 17 00:00:00 2001 From: Aram Drevekenin Date: Wed, 23 Mar 2022 09:08:35 +0100 Subject: [PATCH] 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 --- Cargo.lock | 378 ++++++++++++++++-- src/tests/e2e/cases.rs | 29 +- zellij-client/src/input_handler.rs | 70 ++-- zellij-client/src/lib.rs | 5 +- zellij-client/src/os_input_output.rs | 24 +- zellij-client/src/stdin_handler.rs | 98 ++--- zellij-client/src/unit/input_handler_tests.rs | 24 +- zellij-utils/Cargo.toml | 3 +- zellij-utils/src/input/mod.rs | 72 ++-- zellij-utils/src/input/mouse.rs | 88 ++-- zellij-utils/src/lib.rs | 2 +- 11 files changed, 584 insertions(+), 209 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 32664a5b0..5e2cab67d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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", diff --git a/src/tests/e2e/cases.rs b/src/tests/e2e/cases.rs index 8bb4d8980..19579f55a 100644 --- a/src/tests/e2e/cases.rs +++ b/src/tests/e2e/cases.rs @@ -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 { +pub fn sgr_mouse_report(position: Position, button: u8) -> Vec { + // 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; } diff --git a/zellij-client/src/input_handler.rs b/zellij-client/src/input_handler.rs index 020c55165..250485eb9 100644 --- a/zellij-client/src/input_handler.rs +++ b/zellij-client/src/input_handler.rs @@ -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, 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) { - 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) { 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; } } } diff --git a/zellij-client/src/lib.rs b/zellij-client/src/lib.rs index 207216638..a17845320 100644 --- a/zellij-client/src/lib.rs +++ b/zellij-client/src/lib.rs @@ -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), + KeyEvent(InputEvent, Vec), SwitchToMode(InputMode), - PastedText(Vec), } pub fn start_client( diff --git a/zellij-client/src/os_input_output.rs b/zellij-client/src/os_input_output.rs index 5d5fa5fee..136e89eb3 100644 --- a/zellij-client/src/os_input_output.rs +++ b/zellij-client/src/os_input_output.rs @@ -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>, send_instructions_to_server: Arc>>>, receive_instructions_from_server: Arc>>>, - mouse_term: Arc>>>, } /// 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 { pub fn get_client_os_input() -> Result { 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, }) } diff --git a/zellij-client/src/stdin_handler.rs b/zellij-client/src/stdin_handler.rs index 778a9029a..d83214639 100644 --- a/zellij-client/src/stdin_handler.rs +++ b/zellij-client/src/stdin_handler.rs @@ -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, send_input_instructions: SenderWithContext, ) { - 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 = 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 +} diff --git a/zellij-client/src/unit/input_handler_tests.rs b/zellij-client/src/unit/input_handler_tests.rs index 017ec8ab6..a83719a9d 100644 --- a/zellij-client/src/unit/input_handler_tests.rs +++ b/zellij-client/src/unit/input_handler_tests.rs @@ -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![])); diff --git a/zellij-utils/Cargo.toml b/zellij-utils/Cargo.toml index 383f0765d..e65a432f1 100644 --- a/zellij-utils/Cargo.toml +++ b/zellij-utils/Cargo.toml @@ -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" diff --git a/zellij-utils/src/input/mod.rs b/zellij-utils/src/input/mod.rs index 289e52caf..869c513a0 100644 --- a/zellij-utils/src/input/mod.rs +++ b/zellij-utils/src/input/mod.rs @@ -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 { - 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 } } diff --git a/zellij-utils/src/input/mouse.rs b/zellij-utils/src/input/mouse.rs index d5dc43304..0a63ac2df 100644 --- a/zellij-utils/src/input/mouse.rs +++ b/zellij-utils/src/input/mouse.rs @@ -19,21 +19,67 @@ pub enum MouseEvent { Hold(Position), } -impl From 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 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 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, - } - } -} diff --git a/zellij-utils/src/lib.rs b/zellij-utils/src/lib.rs index 7eb9825ab..5e42edf19 100644 --- a/zellij-utils/src/lib.rs +++ b/zellij-utils/src/lib.rs @@ -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;