From ebbd46ea3bcc10f003ca4b83b28c550b953dc2a1 Mon Sep 17 00:00:00 2001 From: Thomas Linford Date: Sun, 4 Sep 2022 16:26:15 +0200 Subject: [PATCH] feat(compatibility): mouse wheel faux scrolling in alternate screen (#1678) * implement faux scrolling * update changelog * fix tests * cursor keys mode handling * add integration test * undo changelog reformatting --- CHANGELOG.md | 1 + zellij-server/src/panes/grid.rs | 3 + zellij-server/src/panes/terminal_pane.rs | 3 + zellij-server/src/screen.rs | 4 +- zellij-server/src/tab/mod.rs | 20 ++++- .../src/tab/unit/tab_integration_tests.rs | 80 +++++++++++++++++-- 6 files changed, 99 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9970ba2f9..50d0ccab7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) * fix: occasional startup crashes (https://github.com/zellij-org/zellij/pull/1706) * fix: gracefully handle SSH disconnects (https://github.com/zellij-org/zellij/pull/1710) * fix: handle osc params larger than 1024 bytes (https://github.com/zellij-org/zellij/pull/1711) +* Terminal compatibility: implement faux scrolling when in alternate screen mode(https://github.com/zellij-org/zellij/pull/1678) ## [0.31.3] - 2022-08-18 * HOTFIX: fix up-arrow regression diff --git a/zellij-server/src/panes/grid.rs b/zellij-server/src/panes/grid.rs index 98ea652ba..677eed163 100644 --- a/zellij-server/src/panes/grid.rs +++ b/zellij-server/src/panes/grid.rs @@ -1931,6 +1931,9 @@ impl Grid { }, } } + pub fn is_alternate_mode_active(&self) -> bool { + self.alternate_screen_state.is_some() + } } impl Perform for Grid { diff --git a/zellij-server/src/panes/terminal_pane.rs b/zellij-server/src/panes/terminal_pane.rs index 5be454269..d8b6c6054 100644 --- a/zellij-server/src/panes/terminal_pane.rs +++ b/zellij-server/src/panes/terminal_pane.rs @@ -650,6 +650,9 @@ impl Pane for TerminalPane { self.grid.clear_search(); self.search_term.clear(); } + fn is_alternate_mode_active(&self) -> bool { + self.grid.is_alternate_mode_active() + } } impl TerminalPane { diff --git a/zellij-server/src/screen.rs b/zellij-server/src/screen.rs index deba6eb9c..e3323b41e 100644 --- a/zellij-server/src/screen.rs +++ b/zellij-server/src/screen.rs @@ -1137,7 +1137,7 @@ pub(crate) fn screen_thread_main( }, ScreenInstruction::ScrollUpAt(point, client_id) => { active_tab!(screen, client_id, |tab: &mut Tab| tab - .scroll_terminal_up(&point, 3, client_id)); + .handle_scrollwheel_up(&point, 3, client_id)); screen.render(); }, ScreenInstruction::ScrollDown(client_id) => { @@ -1147,7 +1147,7 @@ pub(crate) fn screen_thread_main( }, ScreenInstruction::ScrollDownAt(point, client_id) => { active_tab!(screen, client_id, |tab: &mut Tab| tab - .scroll_terminal_down(&point, 3, client_id)); + .handle_scrollwheel_down(&point, 3, client_id)); screen.render(); }, ScreenInstruction::ScrollToBottom(client_id) => { diff --git a/zellij-server/src/tab/mod.rs b/zellij-server/src/tab/mod.rs index 750f49447..04626bef8 100644 --- a/zellij-server/src/tab/mod.rs +++ b/zellij-server/src/tab/mod.rs @@ -346,6 +346,10 @@ pub trait Pane { fn clear_search(&mut self) { // No-op by default (only terminal-panes currently have search capability) } + fn is_alternate_mode_active(&self) -> bool { + // False by default (only terminal-panes support alternate mode) + false + } } impl Tab { @@ -1690,21 +1694,33 @@ impl Tab { } } } - pub fn scroll_terminal_up(&mut self, point: &Position, lines: usize, client_id: ClientId) { + pub fn handle_scrollwheel_up(&mut self, point: &Position, lines: usize, client_id: ClientId) { if let Some(pane) = self.get_pane_at(point, false) { let relative_position = pane.relative_position(point); if let Some(mouse_event) = pane.mouse_scroll_up(&relative_position) { self.write_to_terminal_at(mouse_event.into_bytes(), point); + } else if pane.is_alternate_mode_active() { + // faux scrolling, send UP n times + // do n separate writes to make sure the sequence gets adjusted for cursor keys mode + for _ in 0..lines { + self.write_to_terminal_at("\u{1b}[A".as_bytes().to_owned(), point) + } } else { pane.scroll_up(lines, client_id); } } } - pub fn scroll_terminal_down(&mut self, point: &Position, lines: usize, client_id: ClientId) { + pub fn handle_scrollwheel_down(&mut self, point: &Position, lines: usize, client_id: ClientId) { if let Some(pane) = self.get_pane_at(point, false) { let relative_position = pane.relative_position(point); if let Some(mouse_event) = pane.mouse_scroll_down(&relative_position) { self.write_to_terminal_at(mouse_event.into_bytes(), point); + } else if pane.is_alternate_mode_active() { + // faux scrolling, send DOWN n times + // do n separate writes to make sure the sequence gets adjusted for cursor keys mode + for _ in 0..lines { + self.write_to_terminal_at("\u{1b}[B".as_bytes().to_owned(), point) + } } else { pane.scroll_down(lines, client_id); if !pane.is_scrolled() { diff --git a/zellij-server/src/tab/unit/tab_integration_tests.rs b/zellij-server/src/tab/unit/tab_integration_tests.rs index 41e2fee44..2803529f3 100644 --- a/zellij-server/src/tab/unit/tab_integration_tests.rs +++ b/zellij-server/src/tab/unit/tab_integration_tests.rs @@ -1830,8 +1830,8 @@ fn pane_in_sgr_button_event_tracking_mouse_mode() { tab.handle_middle_click(&Position::new(5, 71), client_id); tab.handle_mouse_hold_middle(&Position::new(9, 72), client_id); tab.handle_middle_mouse_release(&Position::new(7, 75), client_id); - tab.scroll_terminal_up(&Position::new(5, 71), 1, client_id); - tab.scroll_terminal_down(&Position::new(5, 71), 1, client_id); + tab.handle_scrollwheel_up(&Position::new(5, 71), 1, client_id); + tab.handle_scrollwheel_down(&Position::new(5, 71), 1, client_id); std::thread::sleep(std::time::Duration::from_millis(100)); // give time for messages to arrive assert_eq!( *messages_to_pty_writer.lock().unwrap(), @@ -1896,8 +1896,8 @@ fn pane_in_sgr_normal_event_tracking_mouse_mode() { tab.handle_middle_click(&Position::new(5, 71), client_id); tab.handle_mouse_hold_middle(&Position::new(9, 72), client_id); tab.handle_middle_mouse_release(&Position::new(7, 75), client_id); - tab.scroll_terminal_up(&Position::new(5, 71), 1, client_id); - tab.scroll_terminal_down(&Position::new(5, 71), 1, client_id); + tab.handle_scrollwheel_up(&Position::new(5, 71), 1, client_id); + tab.handle_scrollwheel_down(&Position::new(5, 71), 1, client_id); std::thread::sleep(std::time::Duration::from_millis(100)); // give time for messages to arrive assert_eq!( *messages_to_pty_writer.lock().unwrap(), @@ -1962,8 +1962,8 @@ fn pane_in_utf8_button_event_tracking_mouse_mode() { tab.handle_middle_click(&Position::new(5, 71), client_id); tab.handle_mouse_hold_middle(&Position::new(9, 72), client_id); tab.handle_middle_mouse_release(&Position::new(7, 75), client_id); - tab.scroll_terminal_up(&Position::new(5, 71), 1, client_id); - tab.scroll_terminal_down(&Position::new(5, 71), 1, client_id); + tab.handle_scrollwheel_up(&Position::new(5, 71), 1, client_id); + tab.handle_scrollwheel_down(&Position::new(5, 71), 1, client_id); std::thread::sleep(std::time::Duration::from_millis(100)); // give time for messages to arrive assert_eq!( *messages_to_pty_writer.lock().unwrap(), @@ -2028,8 +2028,8 @@ fn pane_in_utf8_normal_event_tracking_mouse_mode() { tab.handle_middle_click(&Position::new(5, 71), client_id); tab.handle_mouse_hold_middle(&Position::new(9, 72), client_id); tab.handle_middle_mouse_release(&Position::new(7, 75), client_id); - tab.scroll_terminal_up(&Position::new(5, 71), 1, client_id); - tab.scroll_terminal_down(&Position::new(5, 71), 1, client_id); + tab.handle_scrollwheel_up(&Position::new(5, 71), 1, client_id); + tab.handle_scrollwheel_down(&Position::new(5, 71), 1, client_id); std::thread::sleep(std::time::Duration::from_millis(100)); // give time for messages to arrive assert_eq!( *messages_to_pty_writer.lock().unwrap(), @@ -2096,3 +2096,67 @@ fn pane_bracketed_paste_ignored_when_not_in_bracketed_paste_mode() { vec!["", "test", ""] ); } + +#[test] +fn pane_faux_scrolling_in_alternate_mode() { + let size = Size { + cols: 121, + rows: 20, + }; + let client_id: u16 = 1; + let lines_to_scroll = 3; + + let messages_to_pty_writer = Arc::new(Mutex::new(vec![])); + let (to_pty_writer, pty_writer_receiver): ChannelWithContext = + channels::unbounded(); + let to_pty_writer = SenderWithContext::new(to_pty_writer); + let mut tab = + create_new_tab_with_mock_pty_writer(size, ModeInfo::default(), to_pty_writer.clone()); + + let _pty_writer_thread = std::thread::Builder::new() + .name("pty_writer".to_string()) + .spawn({ + let messages_to_pty_writer = messages_to_pty_writer.clone(); + move || loop { + let (event, _err_ctx) = pty_writer_receiver + .recv() + .expect("failed to receive event on channel"); + match event { + PtyWriteInstruction::Write(msg, _) => messages_to_pty_writer + .lock() + .unwrap() + .push(String::from_utf8_lossy(&msg).to_string()), + PtyWriteInstruction::Exit => break, + } + } + }); + + let enable_alternate_screen = String::from("\u{1b}[?1049h"); // CSI ? 1049 h -> switch to the Alternate Screen Buffer + let set_application_mode = String::from("\u{1b}[?1h"); + + // no output since alternate scren not active yet + tab.handle_scrollwheel_up(&Position::new(1, 1), lines_to_scroll, client_id); + tab.handle_scrollwheel_down(&Position::new(1, 1), lines_to_scroll, client_id); + + tab.handle_pty_bytes(1, enable_alternate_screen.as_bytes().to_vec()); + // CSI A * lines_to_scroll, CSI B * lines_to_scroll + tab.handle_scrollwheel_up(&Position::new(1, 1), lines_to_scroll, client_id); + tab.handle_scrollwheel_down(&Position::new(1, 1), lines_to_scroll, client_id); + + tab.handle_pty_bytes(1, set_application_mode.as_bytes().to_vec()); + // SS3 A * lines_to_scroll, SS3 B * lines_to_scroll + tab.handle_scrollwheel_up(&Position::new(1, 1), lines_to_scroll, client_id); + tab.handle_scrollwheel_down(&Position::new(1, 1), lines_to_scroll, client_id); + + to_pty_writer.send(PtyWriteInstruction::Exit).unwrap(); + + std::thread::sleep(std::time::Duration::from_millis(100)); // give time for messages to arrive + + let mut expected: Vec<&str> = Vec::new(); + expected.append(&mut vec!["\u{1b}[A"; lines_to_scroll]); + expected.append(&mut vec!["\u{1b}[B"; lines_to_scroll]); + expected.append(&mut vec!["\u{1b}OA"; lines_to_scroll]); + expected.append(&mut vec!["\u{1b}OB"; lines_to_scroll]); + + assert_eq!(*messages_to_pty_writer.lock().unwrap(), expected); +}