1/// Most of the code, and specifically the constants, in this are copied from Alacritty,
2/// with modifications for our circumstances
3use alacritty_terminal::{index::Point, term::TermMode};
4use gpui::{MouseButtonEvent, MouseMovedEvent, ScrollWheelEvent};
5
6pub struct Modifiers {
7 ctrl: bool,
8 shift: bool,
9 alt: bool,
10}
11
12impl Modifiers {
13 pub fn from_moved(e: &MouseMovedEvent) -> Self {
14 Modifiers {
15 ctrl: e.ctrl,
16 shift: e.shift,
17 alt: e.alt,
18 }
19 }
20
21 pub fn from_button(e: &MouseButtonEvent) -> Self {
22 Modifiers {
23 ctrl: e.ctrl,
24 shift: e.shift,
25 alt: e.alt,
26 }
27 }
28
29 //TODO: Determine if I should add modifiers into the ScrollWheelEvent type
30 pub fn from_scroll() -> Self {
31 Modifiers {
32 ctrl: false,
33 shift: false,
34 alt: false,
35 }
36 }
37}
38
39pub enum MouseFormat {
40 SGR,
41 Normal(bool),
42}
43
44impl MouseFormat {
45 pub fn from_mode(mode: TermMode) -> Self {
46 if mode.contains(TermMode::SGR_MOUSE) {
47 MouseFormat::SGR
48 } else if mode.contains(TermMode::UTF8_MOUSE) {
49 MouseFormat::Normal(true)
50 } else {
51 MouseFormat::Normal(false)
52 }
53 }
54}
55
56pub enum MouseButton {
57 LeftButton = 0,
58 MiddleButton = 1,
59 RightButton = 2,
60 LeftMove = 32,
61 MiddleMove = 33,
62 RightMove = 34,
63 NoneMove = 35,
64 ScrollUp = 64,
65 ScrollDown = 65,
66 Other = 99,
67}
68
69impl MouseButton {
70 pub fn from_move(e: &MouseMovedEvent) -> Self {
71 match e.pressed_button {
72 Some(b) => match b {
73 gpui::MouseButton::Left => MouseButton::LeftMove,
74 gpui::MouseButton::Middle => MouseButton::MiddleMove,
75 gpui::MouseButton::Right => MouseButton::RightMove,
76 gpui::MouseButton::Navigate(_) => MouseButton::Other,
77 },
78 None => MouseButton::NoneMove,
79 }
80 }
81
82 pub fn from_button(e: &MouseButtonEvent) -> Self {
83 match e.button {
84 gpui::MouseButton::Left => MouseButton::LeftButton,
85 gpui::MouseButton::Right => MouseButton::MiddleButton,
86 gpui::MouseButton::Middle => MouseButton::RightButton,
87 gpui::MouseButton::Navigate(_) => MouseButton::Other,
88 }
89 }
90
91 pub fn from_scroll(e: &ScrollWheelEvent) -> Self {
92 if e.delta.y() > 0. {
93 MouseButton::ScrollUp
94 } else {
95 MouseButton::ScrollDown
96 }
97 }
98
99 pub fn is_other(&self) -> bool {
100 match self {
101 MouseButton::Other => true,
102 _ => false,
103 }
104 }
105}
106
107pub fn scroll_report(
108 point: Point,
109 scroll_lines: i32,
110 e: &ScrollWheelEvent,
111 mode: TermMode,
112) -> Option<Vec<Vec<u8>>> {
113 if mode.intersects(TermMode::MOUSE_MODE) && scroll_lines >= 1 {
114 if let Some(report) = mouse_report(
115 point,
116 MouseButton::from_scroll(e),
117 true,
118 Modifiers::from_scroll(),
119 MouseFormat::from_mode(mode),
120 ) {
121 let mut res = vec![];
122 for _ in 0..scroll_lines.abs() {
123 res.push(report.clone());
124 }
125 return Some(res);
126 }
127 }
128
129 None
130}
131
132pub fn mouse_button_report(
133 point: Point,
134 e: &MouseButtonEvent,
135 pressed: bool,
136 mode: TermMode,
137) -> Option<Vec<u8>> {
138 let button = MouseButton::from_button(e);
139 if !button.is_other() && mode.intersects(TermMode::MOUSE_MODE) {
140 mouse_report(
141 point,
142 button,
143 pressed,
144 Modifiers::from_button(e),
145 MouseFormat::from_mode(mode),
146 )
147 } else {
148 None
149 }
150}
151
152pub fn mouse_moved_report(point: Point, e: &MouseMovedEvent, mode: TermMode) -> Option<Vec<u8>> {
153 let button = MouseButton::from_move(e);
154 if !button.is_other() && mode.intersects(TermMode::MOUSE_MOTION | TermMode::MOUSE_DRAG) {
155 mouse_report(
156 point,
157 button,
158 true,
159 Modifiers::from_moved(e),
160 MouseFormat::from_mode(mode),
161 )
162 } else {
163 None
164 }
165}
166
167///Generate the bytes to send to the terminal, from the cell location, a mouse event, and the terminal mode
168fn mouse_report(
169 point: Point,
170 button: MouseButton,
171 pressed: bool,
172 modifiers: Modifiers,
173 format: MouseFormat,
174) -> Option<Vec<u8>> {
175 if point.line < 0 {
176 return None;
177 }
178
179 let mut mods = 0;
180 if modifiers.shift {
181 mods += 4;
182 }
183 if modifiers.alt {
184 mods += 8;
185 }
186 if modifiers.ctrl {
187 mods += 16;
188 }
189
190 match format {
191 MouseFormat::SGR => {
192 Some(sgr_mouse_report(point, button as u8 + mods, pressed).into_bytes())
193 }
194 MouseFormat::Normal(utf8) => {
195 if pressed {
196 normal_mouse_report(point, button as u8 + mods, utf8)
197 } else {
198 normal_mouse_report(point, 3 + mods, utf8)
199 }
200 }
201 }
202}
203
204fn normal_mouse_report(point: Point, button: u8, utf8: bool) -> Option<Vec<u8>> {
205 let Point { line, column } = point;
206 let max_point = if utf8 { 2015 } else { 223 };
207
208 if line >= max_point || column >= max_point {
209 return None;
210 }
211
212 let mut msg = vec![b'\x1b', b'[', b'M', 32 + button];
213
214 let mouse_pos_encode = |pos: usize| -> Vec<u8> {
215 let pos = 32 + 1 + pos;
216 let first = 0xC0 + pos / 64;
217 let second = 0x80 + (pos & 63);
218 vec![first as u8, second as u8]
219 };
220
221 if utf8 && column >= 95 {
222 msg.append(&mut mouse_pos_encode(column.0));
223 } else {
224 msg.push(32 + 1 + column.0 as u8);
225 }
226
227 if utf8 && line >= 95 {
228 msg.append(&mut mouse_pos_encode(line.0 as usize));
229 } else {
230 msg.push(32 + 1 + line.0 as u8);
231 }
232
233 Some(msg)
234}
235
236fn sgr_mouse_report(point: Point, button: u8, pressed: bool) -> String {
237 let c = if pressed { 'M' } else { 'm' };
238
239 let msg = format!(
240 "\x1b[<{};{};{}{}",
241 button,
242 point.column + 1,
243 point.line + 1,
244 c
245 );
246
247 msg
248}