mirror of
https://github.com/zellij-org/zellij.git
synced 2024-11-22 22:26:54 +03:00
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:
parent
84e14d1479
commit
ebbd46ea3b
@ -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
|
||||
|
@ -1931,6 +1931,9 @@ impl Grid {
|
||||
},
|
||||
}
|
||||
}
|
||||
pub fn is_alternate_mode_active(&self) -> bool {
|
||||
self.alternate_screen_state.is_some()
|
||||
}
|
||||
}
|
||||
|
||||
impl Perform for Grid {
|
||||
|
@ -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 {
|
||||
|
@ -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) => {
|
||||
|
@ -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() {
|
||||
|
@ -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);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user