omnyssh_core/utils/
scroll.rs1use vt100::{MouseProtocolEncoding, MouseProtocolMode, Screen};
10
11#[derive(Debug, Clone, PartialEq, Eq)]
13pub enum ScrollAction {
14 Scrollback(i16),
17 Forward(Vec<u8>),
19}
20
21pub fn resolve_scroll(delta: i16, screen: &Screen) -> ScrollAction {
26 if delta == 0 || !screen.alternate_screen() {
27 return ScrollAction::Scrollback(delta);
28 }
29 let up = delta > 0;
30 if screen.mouse_protocol_mode() == MouseProtocolMode::None {
31 ScrollAction::Forward(arrow_key_seq(
33 up,
34 delta.unsigned_abs() as usize,
35 screen.application_cursor(),
36 ))
37 } else {
38 ScrollAction::Forward(wheel_mouse_seq(up, screen.mouse_protocol_encoding()))
40 }
41}
42
43fn wheel_mouse_seq(up: bool, encoding: MouseProtocolEncoding) -> Vec<u8> {
47 let button: u16 = if up { 64 } else { 65 };
49 match encoding {
50 MouseProtocolEncoding::Sgr => format!("\x1b[<{button};1;1M").into_bytes(),
51 _ => vec![0x1b, b'[', b'M', (button + 32) as u8, 1 + 32, 1 + 32],
55 }
56}
57
58fn arrow_key_seq(up: bool, count: usize, application_cursor: bool) -> Vec<u8> {
61 let seq: &[u8] = match (up, application_cursor) {
62 (true, false) => b"\x1b[A",
63 (false, false) => b"\x1b[B",
64 (true, true) => b"\x1bOA",
65 (false, true) => b"\x1bOB",
66 };
67 seq.repeat(count.max(1))
68}
69
70#[cfg(test)]
71mod tests {
72 use super::*;
73 use vt100::Parser;
74
75 fn parser_with(seqs: &[&[u8]]) -> Parser {
77 let mut p = Parser::new(24, 80, 1000);
78 for s in seqs {
79 p.process(s);
80 }
81 p
82 }
83
84 #[test]
85 fn normal_screen_scrolls_scrollback() {
86 let p = parser_with(&[]);
87 assert_eq!(resolve_scroll(3, p.screen()), ScrollAction::Scrollback(3));
88 assert_eq!(resolve_scroll(-3, p.screen()), ScrollAction::Scrollback(-3));
89 }
90
91 #[test]
92 fn zero_delta_is_noop_scrollback() {
93 let p = parser_with(&[b"\x1b[?1049h"]);
94 assert_eq!(resolve_scroll(0, p.screen()), ScrollAction::Scrollback(0));
95 }
96
97 #[test]
98 fn alt_screen_without_mouse_sends_arrow_keys() {
99 let p = parser_with(&[b"\x1b[?1049h"]);
100 assert_eq!(
101 resolve_scroll(3, p.screen()),
102 ScrollAction::Forward(b"\x1b[A\x1b[A\x1b[A".to_vec())
103 );
104 assert_eq!(
105 resolve_scroll(-3, p.screen()),
106 ScrollAction::Forward(b"\x1b[B\x1b[B\x1b[B".to_vec())
107 );
108 }
109
110 #[test]
111 fn alt_screen_with_application_cursor_uses_ss3() {
112 let p = parser_with(&[b"\x1b[?1049h", b"\x1b[?1h"]);
113 assert_eq!(
114 resolve_scroll(3, p.screen()),
115 ScrollAction::Forward(b"\x1bOA\x1bOA\x1bOA".to_vec())
116 );
117 }
118
119 #[test]
120 fn alt_screen_with_mouse_sends_x10_wheel() {
121 let p = parser_with(&[b"\x1b[?1049h", b"\x1b[?1000h"]);
122 assert_eq!(
123 resolve_scroll(3, p.screen()),
124 ScrollAction::Forward(vec![0x1b, b'[', b'M', 96, 33, 33])
125 );
126 assert_eq!(
127 resolve_scroll(-3, p.screen()),
128 ScrollAction::Forward(vec![0x1b, b'[', b'M', 97, 33, 33])
129 );
130 }
131
132 #[test]
133 fn alt_screen_with_sgr_mouse_sends_sgr_wheel() {
134 let p = parser_with(&[b"\x1b[?1049h", b"\x1b[?1000h", b"\x1b[?1006h"]);
135 assert_eq!(
136 resolve_scroll(3, p.screen()),
137 ScrollAction::Forward(b"\x1b[<64;1;1M".to_vec())
138 );
139 assert_eq!(
140 resolve_scroll(-3, p.screen()),
141 ScrollAction::Forward(b"\x1b[<65;1;1M".to_vec())
142 );
143 }
144}