summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorThomas Linford <tlinford@users.noreply.github.com>2021-07-02 16:40:50 +0200
committerGitHub <noreply@github.com>2021-07-02 16:40:50 +0200
commitf93308f211565fae527f9b1e5788e12d0ea887c4 (patch)
treeedb915be32a81c1caae2c7218398c96344a29bd0
parent2a024db839e3be60f9428d39f6e84c7e748162d0 (diff)
feat(ui): initial mouse support (#448)
* Initial mouse support * enable mouse support (termion MouseTerminal) * handle scroll up and down events * Allow enabling/disabling of mouse reporting Store the mouse terminal on OsInputOutput * WIP: switch pane focus with mouse * testing to get mouse character selection * wip mouse selection * wip: mouse selection - initial handling of mouse events for text selection within a pane - wide characters currently problematic - selection is not highlighted * highlight currently selected text * improve get currently selected text from TerminalPane * copy selection to clipboard (wayland only for now) * Add missing set_should_render on selection update * Improve text selection - Strip whitespace from end of lines - Insert newlines when selection spans multiple lines * Simplify selection logic - Remove Range struct - Selection is not an Option anymore, it's empty when start == end * copy selection to clipboard with OSC-52 sequence * Improve text selection with multiple panes - Constrain mouse hold and release events to currently active pane - Fix calculation of columns with side by side panes * fix for PositionAndSize changes * Fix mouse selection with full screen pane. - Transform mouse event positions to relative when passing to pane. * Move selection to grid, rework highlighting. - Mark selected lines for update in grid output buffer, for now in the simplest way possible, but can be made more efficient by calculating changed lines only. - Clear selection on pane resize. - Re-add logic to forward mouse hold and release events only to currently active pane. * Tidy up selection - add method to get selection line range - add method to sort the current selection * Grid: move current selection up/down when scrolling - Make the selection work with lines in lines_above and lines_below - Todo: update selection end when scrolling with mouse button being held - Todo: figure out how to move selection when new characters are added. * Grid: move selection when new lines are added * Keep track of selection being active - Handle the selection growing/shrinking when scrolling with mouse button held down but not yet released. * Improve selection end with multiple panes * Tidying up - remove logging statements, unused code * Add some unit tests for selection, move position to zellij-utils * Change Position::new to take i32 for line * Grid: add unit tests for copy from selection * add basic integration test for mouse pane focus * Add basic integration test for mouse scroll * Use color from palette for selection rendering * Improve performance of selection render - Try to minimize lines to update on selection start/update/end * fixes for updated start method * fix lines not in viewport being marked for rendering - the previous optimization to grid selection rendering was always adding the lines at the extremes of previous selection, causing problems when scrolling up or down - make sure to only add lines in viewport * Disable mouse mode on exit * use saturating_sub for usize subtractions * copy selection to clipboard on mouse release * disable mouse on exit after error * remove left-over comments * remove copy keybinding * add default impl for selection methods in Pane - remove the useless methods from Impl Pane in PluginPane * move line diff between selections to selection * add option to disable mouse mode * Allow scrolling with mouse while selecting. * move action repeater to os_input_output, change timeout to 10ms - change repeater to take an action instead of a position with hardcoded action * replace mouse mode integration tests with e2e tests * cleanup * cleanup * check if mouse mode is disabled from merged options * fix missing changes in tests, cleanup
-rw-r--r--Cargo.lock14
-rw-r--r--src/tests/e2e/cases.rs154
-rw-r--r--src/tests/e2e/snapshots/zellij__tests__e2e__cases__focus_pane_with_mouse.snap29
-rw-r--r--src/tests/e2e/snapshots/zellij__tests__e2e__cases__scrolling_inside_a_pane_with_mouse.snap29
-rwxr-xr-xsrc/tests/fixtures/grid_copy59
-rw-r--r--zellij-client/Cargo.toml1
-rw-r--r--zellij-client/src/input_handler.rs49
-rw-r--r--zellij-client/src/lib.rs3
-rw-r--r--zellij-client/src/os_input_output.rs81
-rw-r--r--zellij-client/src/unit/input_handler_tests.rs10
-rw-r--r--zellij-server/Cargo.toml1
-rw-r--r--zellij-server/src/panes/grid.rs109
-rw-r--r--zellij-server/src/panes/mod.rs1
-rw-r--r--zellij-server/src/panes/selection.rs135
-rw-r--r--zellij-server/src/panes/terminal_pane.rs57
-rw-r--r--zellij-server/src/panes/unit/grid_tests.rs64
-rw-r--r--zellij-server/src/panes/unit/selection_tests.rs197
-rw-r--r--zellij-server/src/route.rs36
-rw-r--r--zellij-server/src/screen.rs47
-rw-r--r--zellij-server/src/tab.rs112
-rw-r--r--zellij-utils/src/errors.rs7
-rw-r--r--zellij-utils/src/input/actions.rs10
-rw-r--r--zellij-utils/src/input/mod.rs1
-rw-r--r--zellij-utils/src/input/mouse.rs69
-rw-r--r--zellij-utils/src/input/options.rs10
-rw-r--r--zellij-utils/src/lib.rs1
-rw-r--r--zellij-utils/src/pane_size.rs10
-rw-r--r--zellij-utils/src/position.rs30
28 files changed, 1309 insertions, 17 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 264fb743c..df0db7317 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -190,12 +190,12 @@ checksum = "065374052e7df7ee4047b1160cca5e1467a12351a40b3da123c870ba0b8eda2a"
[[package]]
name = "atty"
-version = "0.2.14"
+version = "0.2.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
+checksum = "9a7d5b8723950951411ee34d271d99dddcc2035a16ab25310ea2c8cfd4369652"
dependencies = [
- "hermit-abi",
"libc",
+ "termion",
"winapi",
]
@@ -221,6 +221,12 @@ dependencies = [
]
[[package]]
+name = "base64"
+version = "0.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd"
+
+[[package]]
name = "bincode"
version = "1.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2457,6 +2463,7 @@ name = "zellij-client"
version = "0.14.0"
dependencies = [
"insta",
+ "mio",
"termbg",
"zellij-utils",
]
@@ -2467,6 +2474,7 @@ version = "0.14.0"
dependencies = [
"ansi_term 0.12.1",
"async-trait",
+ "base64",
"cassowary",
"daemonize",
"insta",
diff --git a/src/tests/e2e/cases.rs b/src/tests/e2e/cases.rs
index 69233f25a..8d41d44f9 100644
--- a/src/tests/e2e/cases.rs
+++ b/src/tests/e2e/cases.rs
@@ -1,7 +1,7 @@
#![allow(unused)]
use ::insta::assert_snapshot;
-use zellij_utils::pane_size::PositionAndSize;
+use zellij_utils::{pane_size::PositionAndSize, position::Position};
use rand::Rng;
@@ -54,6 +54,17 @@ 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> {
+ 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
+}
+
// All the E2E tests are marked as "ignored" so that they can be run separately from the normal
// tests
@@ -785,3 +796,144 @@ pub fn accepts_basic_layout() {
.run_all_steps();
assert_snapshot!(last_snapshot);
}
+
+#[test]
+#[ignore]
+fn focus_pane_with_mouse() {
+ let fake_win_size = PositionAndSize {
+ cols: 120,
+ rows: 24,
+ x: 0,
+ y: 0,
+ ..Default::default()
+ };
+
+ let last_snapshot = RemoteRunner::new("split_terminals_vertically", fake_win_size, None)
+ .add_step(Step {
+ name: "Split pane to the right",
+ instruction: |mut remote_terminal: RemoteTerminal| -> bool {
+ let mut step_is_complete = false;
+ if remote_terminal.status_bar_appears() && remote_terminal.cursor_position_is(2, 2)
+ {
+ remote_terminal.send_key(&PANE_MODE);
+ remote_terminal.send_key(&SPLIT_RIGHT_IN_PANE_MODE);
+ // back to normal mode after split
+ remote_terminal.send_key(&ENTER);
+ step_is_complete = true;
+ }
+ step_is_complete
+ },
+ })
+ .add_step(Step {
+ name: "Click left pane",
+ 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));
+ step_is_complete = true;
+ }
+ step_is_complete
+ },
+ })
+ .add_step(Step {
+ name: "Wait for left pane to be focused",
+ instruction: |remote_terminal: RemoteTerminal| -> bool {
+ let mut step_is_complete = false;
+ if remote_terminal.cursor_position_is(2, 2) && remote_terminal.tip_appears() {
+ // cursor is in the newly opened second pane
+ step_is_complete = true;
+ }
+ step_is_complete
+ },
+ })
+ .run_all_steps();
+ assert_snapshot!(last_snapshot);
+}
+
+#[test]
+#[ignore]
+pub fn scrolling_inside_a_pane_with_mouse() {
+ let fake_win_size = PositionAndSize {
+ cols: 120,
+ rows: 24,
+ x: 0,
+ y: 0,
+ ..Default::default()
+ };
+ let last_snapshot =
+ RemoteRunner::new("scrolling_inside_a_pane_with_mouse", fake_win_size, None)
+ .add_step(Step {
+ name: "Split pane to the right",
+ instruction: |mut remote_terminal: RemoteTerminal| -> bool {
+ let mut step_is_complete = false;
+ if remote_terminal.status_bar_appears()
+ && remote_terminal.cursor_position_is(2, 2)
+ {
+ remote_terminal.send_key(&PANE_MODE);
+ remote_terminal.send_key(&SPLIT_RIGHT_IN_PANE_MODE);
+ // back to normal mode after split
+ remote_terminal.send_key(&ENTER);
+ step_is_complete = true;
+ }
+ step_is_complete
+ },
+ })
+ .add_step(Step {
+ name: "Fill terminal with text",
+ instruction: |mut remote_terminal: RemoteTerminal| -> bool {
+ let mut step_is_complete = false;
+ if remote_terminal.cursor_position_is(63, 2) && remote_terminal.tip_appears() {
+ // cursor is in the newly opened second pane
+ remote_terminal.send_key(&format!("{:0<57}", "line1 ").as_bytes());
+ remote_terminal.send_key(&format!("{:0<59}", "line2 ").as_bytes());
+ remote_terminal.send_key(&format!("{:0<59}", "line3 ").as_bytes());
+ remote_terminal.send_key(&format!("{:0<59}", "line4 ").as_bytes());
+ remote_terminal.send_key(&format!("{:0<59}", "line5 ").as_bytes());
+ remote_terminal.send_key(&format!("{:0<59}", "line6 ").as_bytes());
+ remote_terminal.send_key(&format!("{:0<59}", "line7 ").as_bytes());
+ remote_terminal.send_key(&format!("{:0<59}", "line8 ").as_bytes());
+ remote_terminal.send_key(&format!("{:0<59}", "line9 ").as_bytes());
+ remote_terminal.send_key(&format!("{:0<59}", "line10 ").as_bytes());
+ remote_terminal.send_key(&format!("{:0<59}", "line11 ").as_bytes());
+ remote_terminal.send_key(&format!("{:0<59}", "line12 ").as_bytes());
+ remote_terminal.send_key(&format!("{:0<59}", "line13 ").as_bytes());
+ remote_terminal.send_key(&format!("{:0<59}", "line14 ").as_bytes());
+ remote_terminal.send_key(&format!("{:0<59}", "line15 ").as_bytes());
+ remote_terminal.send_key(&format!("{:0<59}", "line16 ").as_bytes());
+ remote_terminal.send_key(&format!("{:0<59}", "line17 ").as_bytes());
+ remote_terminal.send_key(&format!("{:0<59}", "line18 ").as_bytes());
+ remote_terminal.send_key(&format!("{:0<59}", "line19 ").as_bytes());
+ remote_terminal.send_key(&format!("{:0<58}", "line20 ").as_bytes());
+ step_is_complete = true;
+ }
+ step_is_complete
+ },
+ })
+ .add_step(Step {
+ name: "Scroll up inside pane",
+ instruction: |mut remote_terminal: RemoteTerminal| -> bool {
+ let mut step_is_complete = false;
+ if remote_terminal.cursor_position_is(119, 20) {
+ // all lines have been written to the pane
+ remote_terminal.send_key(&normal_mouse_report(Position::new(2, 64), 64));
+ step_is_complete = true;
+ }
+ step_is_complete
+ },
+ })
+ .add_step(Step {
+ name: "Wait for scroll to finish",
+ instruction: |remote_terminal: RemoteTerminal| -> bool {
+ let mut step_is_complete = false;
+ if remote_terminal.cursor_position_is(119, 20)
+ && remote_terminal.snapshot_contains("line1 ")
+ {
+ // scrolled up one line
+ step_is_complete = true;
+ }
+ step_is_complete
+ },
+ })
+ .run_all_steps();
+ assert_snapshot!(last_snapshot);
+}
diff --git a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__focus_pane_with_mouse.snap b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__focus_pane_with_mouse.snap
new file mode 100644
index 000000000..79e84810d
--- /dev/null
+++ b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__focus_pane_with_mouse.snap
@@ -0,0 +1,29 @@
+---
+source: src/tests/e2e/cases.rs
+expression: last_snapshot
+
+---
+ Zellij  Tab #1 
+
+$ █ │$
+ │
+ │
+ │
+ │
+ │
+ │
+ │
+ │
+ │
+ │
+ │
+ │
+ │
+ │
+ │
+ │
+ │
+ │
+
+ Ctrl + <g> LOCK  <p> PANE  <t> TAB  <r> RESIZE  <s> SCROLL  <o> SESSION  <q> QUIT 
+ Tip: Alt + n => open new pane. Alt + [] or hjkl => navigate between panes.
diff --git a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__scrolling_inside_a_pane_with_mouse.snap b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__scrolling_inside_a_pane_with_mouse.snap
new file mode 100644
index 000000000..bff403764
--- /dev/null
+++ b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__scrolling_inside_a_pane_with_mouse.snap
@@ -0,0 +1,29 @@
+---
+source: src/tests/e2e/cases.rs
+expression: last_snapshot
+
+---
+ Zellij  Tab #1 
+
+$ │$ line1 000000000000000000000000000000000000000000000000000
+ │line2 00000000000000000000000000000000000000000000000000000
+ │line3 00000000000000000000000000000000000000000000000000000
+ │line4 00000000000000000000000000000000000000000000000000000
+ │line5 00000000000000000000000000000000000000000000000000000
+ │line6 00000000000000000000000000000000000000000000000000000
+ │line7 00000000000000000000000000000000000000000000000000000
+ │line8 00000000000000000000000000000000000000000000000000000
+ │line9 00000000000000000000000000000000000000000000000000000
+ │line10 0000000000000000000000000000000000000000000000000000
+ │line11 0000000000000000000000000000000000000000000000000000
+ │line12 0000000000000000000000000000000000000000000000000000
+ │line13 0000000000000000000000000000000000000000000000000000
+ │line14 0000000000000000000000000000000000000000000000000000
+ │line15 0000000000000000000000000000000000000000000000000000
+ │line16 0000000000000000000000000000000000000000000000000000
+ │line17 0000000000000000000000000000000000000000000000000000
+ │line18 0000000000000000000000000000000000000000000000000000
+ │line19 000000000000000000000000000000000000000000000000000█
+
+ Ctrl + <g> LOCK  <p> PANE  <t> TAB  <r> RESIZE  <s> SCROLL  <o> SESSION  <q> QUIT 
+ Tip: Alt + n => open new pane. Alt + [] or hjkl => navigate between panes.
diff --git a/src/tests/fixtures/grid_copy b/src/tests/fixtures/grid_copy
new file mode 100755
index 000000000..6d093741a
--- /dev/null
+++ b/src/tests/fixtures/grid_copy
@@ -0,0 +1,59 @@
+⏎(B ⏎ Welcome to fish, the friendly interactive shell
+Type `help` for instructions on how to use fish
+[?2004h]0;fish /home/thomas/Projects/zellij(B
+zellij on  mouse-support [?] is 📦 v0.14.0 via 🦀 v1.53.0-beta.3 
+❯  cc(Bat test-input.txt(Bat test-input.txt(Bt test-input.txt(Bcat test-input.txt(B test-input.txt(B test-input.txt(B
+(B[?2004l]0;cat test-input.txt /home/thomas/Projects/zellij(B Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
+Velit ut tortor pretium viverra suspendisse potenti nullam ac tortor. Adipiscing elit ut aliquam purus sit amet luctus venenatis.
+Duis ut diam quam nulla porttitor massa id neque aliquam. Suspendisse potenti nullam ac tortor vitae purus faucibus ornare suspendisse.
+Vitae nunc sed velit dignissim sodales ut eu sem integer.
+Tortor id aliquet lectus proin nibh nisl.
+Commodo odio aenean sed adipiscing diam donec adipiscing tristique risus.
+Velit dignissim sodales ut eu sem. Lacus suspendisse faucibus interdum posuere lorem. Ac placerat vestibulum lectus mauris ultrices eros. Elementum integer enim neque volutpat ac. Augue interdum velit euismod in.
+
+Egestas sed sed risus pretium quam vulputate dignissim.
+Gravida rutrum quisque non tellus orci ac auctor augue.
+Risus nec feugiat in fermentum posuere urna nec tincidunt praesent.
+Elementum eu facilisis sed odio morbi quis.
+Mattis ullamcorper velit sed ullamcorper morbi.
+Dui vivamus arcu felis bibendum. Sit amet aliquam id diam.
+Suscipit tellus mauris a diam maecenas sed enim.
+Odio ut sem nulla pharetra.
+Cras ornare arcu dui vivamus arcu felis bibendum.
+Egestas fringilla phasellus faucibus scelerisque eleifend.
+Purus semper eget duis at tellus at urna condimentum.
+Aliquam etiam erat velit scelerisque in dictum non.
+Porta non pulvinar neque laoreet suspendisse interdum consectetur.
+Tempor nec feugiat nisl pretium. Sit amet consectetur adipiscing elit.
+Cras semper auctor neque vitae tempus quam pellentesque.
+Laoreet non curabitur gravida arcu ac tortor dignissim.
+Sed nisi lacus sed viverra tellus in.
+Rutrum tellus pellentesque eu tincidunt tortor aliquam nulla.
+
+Nascetur ridiculus mus mauris vitae ultricies leo integer malesuada.
+Interdum posuere lorem ipsum dolor sit amet consectetur.
+Porta non pulvinar neque laoreet suspendisse interdum.
+Fames ac turpis egestas integer eget aliquet nibh praesent.
+Congue nisi vitae suscipit tellus mauris a diam maecenas sed.
+Nec ultrices dui sapien eget mi proin sed libero enim.
+Tellus rutrum tellus pellentesque eu tincidunt.
+Ultrices eros in cursus turpis massa tincidunt dui ut ornare.
+Arcu cursus vitae congue mauris rhoncus aenean vel elit scelerisque.
+Viverra mauris in aliquam sem fringilla ut.
+Vulputate eu scelerisque felis imperdiet proin fermentum leo.
+Cursus risus at ultrices mi tempus.
+Laoreet id donec ultrices tincidunt arcu non sodales.
+Amet dictum sit amet justo donec enim.
+Hac habitasse platea dictumst vestibulum rhoncus est pellentesque.
+Facilisi cras fermentum odio eu feugiat.
+Elit ut aliquam purus sit amet luctus venena