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
This commit is contained in:
Thomas Linford 2022-09-04 16:26:15 +02:00 committed by GitHub
parent 84e14d1479
commit ebbd46ea3b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 99 additions and 12 deletions

View File

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

View File

@ -1931,6 +1931,9 @@ impl Grid {
},
}
}
pub fn is_alternate_mode_active(&self) -> bool {
self.alternate_screen_state.is_some()
}
}
impl Perform for Grid {

View File

@ -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 {

View File

@ -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) => {

View File

@ -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() {

View File

@ -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<PtyWriteInstruction> =
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);
}