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
6 changed files with 99 additions and 12 deletions
|
|
@ -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: 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: 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)
|
* 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
|
## [0.31.3] - 2022-08-18
|
||||||
* HOTFIX: fix up-arrow regression
|
* 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 {
|
impl Perform for Grid {
|
||||||
|
|
|
||||||
|
|
@ -650,6 +650,9 @@ impl Pane for TerminalPane {
|
||||||
self.grid.clear_search();
|
self.grid.clear_search();
|
||||||
self.search_term.clear();
|
self.search_term.clear();
|
||||||
}
|
}
|
||||||
|
fn is_alternate_mode_active(&self) -> bool {
|
||||||
|
self.grid.is_alternate_mode_active()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TerminalPane {
|
impl TerminalPane {
|
||||||
|
|
|
||||||
|
|
@ -1137,7 +1137,7 @@ pub(crate) fn screen_thread_main(
|
||||||
},
|
},
|
||||||
ScreenInstruction::ScrollUpAt(point, client_id) => {
|
ScreenInstruction::ScrollUpAt(point, client_id) => {
|
||||||
active_tab!(screen, client_id, |tab: &mut Tab| tab
|
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();
|
screen.render();
|
||||||
},
|
},
|
||||||
ScreenInstruction::ScrollDown(client_id) => {
|
ScreenInstruction::ScrollDown(client_id) => {
|
||||||
|
|
@ -1147,7 +1147,7 @@ pub(crate) fn screen_thread_main(
|
||||||
},
|
},
|
||||||
ScreenInstruction::ScrollDownAt(point, client_id) => {
|
ScreenInstruction::ScrollDownAt(point, client_id) => {
|
||||||
active_tab!(screen, client_id, |tab: &mut Tab| tab
|
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();
|
screen.render();
|
||||||
},
|
},
|
||||||
ScreenInstruction::ScrollToBottom(client_id) => {
|
ScreenInstruction::ScrollToBottom(client_id) => {
|
||||||
|
|
|
||||||
|
|
@ -346,6 +346,10 @@ pub trait Pane {
|
||||||
fn clear_search(&mut self) {
|
fn clear_search(&mut self) {
|
||||||
// No-op by default (only terminal-panes currently have search capability)
|
// 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 {
|
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) {
|
if let Some(pane) = self.get_pane_at(point, false) {
|
||||||
let relative_position = pane.relative_position(point);
|
let relative_position = pane.relative_position(point);
|
||||||
if let Some(mouse_event) = pane.mouse_scroll_up(&relative_position) {
|
if let Some(mouse_event) = pane.mouse_scroll_up(&relative_position) {
|
||||||
self.write_to_terminal_at(mouse_event.into_bytes(), point);
|
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 {
|
} else {
|
||||||
pane.scroll_up(lines, client_id);
|
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) {
|
if let Some(pane) = self.get_pane_at(point, false) {
|
||||||
let relative_position = pane.relative_position(point);
|
let relative_position = pane.relative_position(point);
|
||||||
if let Some(mouse_event) = pane.mouse_scroll_down(&relative_position) {
|
if let Some(mouse_event) = pane.mouse_scroll_down(&relative_position) {
|
||||||
self.write_to_terminal_at(mouse_event.into_bytes(), point);
|
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 {
|
} else {
|
||||||
pane.scroll_down(lines, client_id);
|
pane.scroll_down(lines, client_id);
|
||||||
if !pane.is_scrolled() {
|
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_middle_click(&Position::new(5, 71), client_id);
|
||||||
tab.handle_mouse_hold_middle(&Position::new(9, 72), 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.handle_middle_mouse_release(&Position::new(7, 75), client_id);
|
||||||
tab.scroll_terminal_up(&Position::new(5, 71), 1, client_id);
|
tab.handle_scrollwheel_up(&Position::new(5, 71), 1, client_id);
|
||||||
tab.scroll_terminal_down(&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
|
std::thread::sleep(std::time::Duration::from_millis(100)); // give time for messages to arrive
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
*messages_to_pty_writer.lock().unwrap(),
|
*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_middle_click(&Position::new(5, 71), client_id);
|
||||||
tab.handle_mouse_hold_middle(&Position::new(9, 72), 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.handle_middle_mouse_release(&Position::new(7, 75), client_id);
|
||||||
tab.scroll_terminal_up(&Position::new(5, 71), 1, client_id);
|
tab.handle_scrollwheel_up(&Position::new(5, 71), 1, client_id);
|
||||||
tab.scroll_terminal_down(&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
|
std::thread::sleep(std::time::Duration::from_millis(100)); // give time for messages to arrive
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
*messages_to_pty_writer.lock().unwrap(),
|
*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_middle_click(&Position::new(5, 71), client_id);
|
||||||
tab.handle_mouse_hold_middle(&Position::new(9, 72), 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.handle_middle_mouse_release(&Position::new(7, 75), client_id);
|
||||||
tab.scroll_terminal_up(&Position::new(5, 71), 1, client_id);
|
tab.handle_scrollwheel_up(&Position::new(5, 71), 1, client_id);
|
||||||
tab.scroll_terminal_down(&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
|
std::thread::sleep(std::time::Duration::from_millis(100)); // give time for messages to arrive
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
*messages_to_pty_writer.lock().unwrap(),
|
*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_middle_click(&Position::new(5, 71), client_id);
|
||||||
tab.handle_mouse_hold_middle(&Position::new(9, 72), 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.handle_middle_mouse_release(&Position::new(7, 75), client_id);
|
||||||
tab.scroll_terminal_up(&Position::new(5, 71), 1, client_id);
|
tab.handle_scrollwheel_up(&Position::new(5, 71), 1, client_id);
|
||||||
tab.scroll_terminal_down(&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
|
std::thread::sleep(std::time::Duration::from_millis(100)); // give time for messages to arrive
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
*messages_to_pty_writer.lock().unwrap(),
|
*messages_to_pty_writer.lock().unwrap(),
|
||||||
|
|
@ -2096,3 +2096,67 @@ fn pane_bracketed_paste_ignored_when_not_in_bracketed_paste_mode() {
|
||||||
vec!["", "test", ""]
|
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…
Add table
Reference in a new issue