Skip to repository content319 lines · 8.7 KB · rust
tenant.openagents/omega
No repository description is available.
OpenAgents Git authority 2026-07-28T04:11:07.448Z Public web read
NIP-34 coordinate
30617:7649603503856e5148d571eac2766b288a8ff1e9e35d380337a1d2b0015b4f92:omegaMaintainersHidden in public view
References2 branches · 1 tag
Read-only clone
git clone https://openagents.com/git/tenant.openagents/omega.gitBrowse files
mouse.rs
1use std::cmp::{self, min};
2use std::iter::repeat;
3
4/// Most of the code, and specifically the constants, in this are copied from Alacritty,
5/// with modifications for our circumstances
6use gpui::{Modifiers, MouseButton, Pixels, Point as GpuiPoint, ScrollWheelEvent, px};
7
8use crate::{Modes, Point, SelectionSide, TerminalBounds};
9
10enum MouseFormat {
11 Sgr,
12 Normal(bool),
13}
14
15impl MouseFormat {
16 fn from_mode(mode: Modes) -> Self {
17 if mode.contains(Modes::SGR_MOUSE) {
18 MouseFormat::Sgr
19 } else if mode.contains(Modes::UTF8_MOUSE) {
20 MouseFormat::Normal(true)
21 } else {
22 MouseFormat::Normal(false)
23 }
24 }
25}
26
27#[derive(Debug)]
28enum MouseButtonCode {
29 LeftButton = 0,
30 MiddleButton = 1,
31 RightButton = 2,
32 LeftMove = 32,
33 MiddleMove = 33,
34 RightMove = 34,
35 NoneMove = 35,
36 ScrollUp = 64,
37 ScrollDown = 65,
38 Other = 99,
39}
40
41impl MouseButtonCode {
42 fn from_move_button(e: Option<MouseButton>) -> Self {
43 match e {
44 Some(gpui::MouseButton::Left) => MouseButtonCode::LeftMove,
45 Some(gpui::MouseButton::Middle) => MouseButtonCode::MiddleMove,
46 Some(gpui::MouseButton::Right) => MouseButtonCode::RightMove,
47 Some(gpui::MouseButton::Navigate(_)) => MouseButtonCode::Other,
48 None => MouseButtonCode::NoneMove,
49 }
50 }
51
52 fn from_button(e: MouseButton) -> Self {
53 match e {
54 gpui::MouseButton::Left => MouseButtonCode::LeftButton,
55 gpui::MouseButton::Middle => MouseButtonCode::MiddleButton,
56 gpui::MouseButton::Right => MouseButtonCode::RightButton,
57 gpui::MouseButton::Navigate(_) => MouseButtonCode::Other,
58 }
59 }
60
61 fn from_scroll(e: &ScrollWheelEvent) -> Self {
62 let is_positive = match e.delta {
63 gpui::ScrollDelta::Pixels(pixels) => pixels.y > px(0.),
64 gpui::ScrollDelta::Lines(lines) => lines.y > 0.,
65 };
66
67 if is_positive {
68 MouseButtonCode::ScrollUp
69 } else {
70 MouseButtonCode::ScrollDown
71 }
72 }
73
74 fn is_other(&self) -> bool {
75 matches!(self, MouseButtonCode::Other)
76 }
77}
78
79pub(crate) fn scroll_report(
80 point: Point,
81 scroll_lines: i32,
82 e: &ScrollWheelEvent,
83 mode: Modes,
84) -> Option<impl Iterator<Item = Vec<u8>>> {
85 if mode.intersects(Modes::MOUSE_MODE) {
86 mouse_report(
87 point,
88 MouseButtonCode::from_scroll(e),
89 true,
90 e.modifiers,
91 MouseFormat::from_mode(mode),
92 )
93 .map(|report| repeat(report).take(scroll_lines.unsigned_abs() as usize))
94 } else {
95 None
96 }
97}
98
99#[cfg(test)]
100mod tests {
101 use super::*;
102 use gpui::{ScrollDelta, TouchPhase, point};
103
104 #[test]
105 fn scroll_report_repeats_for_negative_scroll_lines() {
106 let grid_point = Point::new(0, 0);
107
108 let scroll_event = ScrollWheelEvent {
109 delta: ScrollDelta::Lines(point(0., -1.)),
110 touch_phase: TouchPhase::Moved,
111 ..Default::default()
112 };
113
114 let mode = Modes::MOUSE_MODE;
115 let reports: Vec<Vec<u8>> = scroll_report(grid_point, -3, &scroll_event, mode)
116 .expect("mouse mode should produce a scroll report")
117 .collect();
118
119 assert_eq!(reports.len(), 3);
120 }
121
122 #[test]
123 fn scroll_report_repeats_for_positive_scroll_lines() {
124 let grid_point = Point::new(0, 0);
125
126 let scroll_event = ScrollWheelEvent {
127 delta: ScrollDelta::Lines(point(0., 1.)),
128 touch_phase: TouchPhase::Moved,
129 ..Default::default()
130 };
131
132 let mode = Modes::MOUSE_MODE;
133 let reports: Vec<Vec<u8>> = scroll_report(grid_point, 3, &scroll_event, mode)
134 .expect("mouse mode should produce a scroll report")
135 .collect();
136
137 assert_eq!(reports.len(), 3);
138 }
139}
140
141pub(crate) fn alt_scroll(scroll_lines: i32) -> Vec<u8> {
142 let cmd = if scroll_lines > 0 { b'A' } else { b'B' };
143
144 let mut content = Vec::with_capacity(scroll_lines.unsigned_abs() as usize * 3);
145 for _ in 0..scroll_lines.abs() {
146 content.push(0x1b);
147 content.push(b'O');
148 content.push(cmd);
149 }
150 content
151}
152
153pub(crate) fn mouse_button_report(
154 point: Point,
155 button: gpui::MouseButton,
156 modifiers: Modifiers,
157 pressed: bool,
158 mode: Modes,
159) -> Option<Vec<u8>> {
160 let button = MouseButtonCode::from_button(button);
161 if !button.is_other() && mode.intersects(Modes::MOUSE_MODE) {
162 mouse_report(
163 point,
164 button,
165 pressed,
166 modifiers,
167 MouseFormat::from_mode(mode),
168 )
169 } else {
170 None
171 }
172}
173
174pub(crate) fn mouse_moved_report(
175 point: Point,
176 button: Option<MouseButton>,
177 modifiers: Modifiers,
178 mode: Modes,
179) -> Option<Vec<u8>> {
180 let button = MouseButtonCode::from_move_button(button);
181
182 if !button.is_other() && mode.intersects(Modes::MOUSE_MOTION | Modes::MOUSE_DRAG) {
183 //Only drags are reported in drag mode, so block NoneMove.
184 if mode.contains(Modes::MOUSE_DRAG) && matches!(button, MouseButtonCode::NoneMove) {
185 None
186 } else {
187 mouse_report(point, button, true, modifiers, MouseFormat::from_mode(mode))
188 }
189 } else {
190 None
191 }
192}
193
194pub(crate) fn grid_point(
195 pos: GpuiPoint<Pixels>,
196 cur_size: TerminalBounds,
197 display_offset: usize,
198) -> Point {
199 grid_point_and_side(pos, cur_size, display_offset).0
200}
201
202pub(crate) fn grid_point_and_side(
203 pos: GpuiPoint<Pixels>,
204 cur_size: TerminalBounds,
205 display_offset: usize,
206) -> (Point, SelectionSide) {
207 let mut column = (pos.x / cur_size.cell_width) as usize;
208 let cell_x = cmp::max(px(0.), pos.x) % cur_size.cell_width;
209 let half_cell_width = cur_size.cell_width / 2.0;
210 let mut side = if cell_x > half_cell_width {
211 SelectionSide::Right
212 } else {
213 SelectionSide::Left
214 };
215
216 let last_column = cur_size.num_columns().saturating_sub(1);
217 if column > last_column {
218 column = last_column;
219 side = SelectionSide::Right;
220 }
221 let column = min(column, last_column);
222 let mut line = (pos.y / cur_size.line_height) as i32;
223 let bottommost_line = i32::try_from(cur_size.num_lines().saturating_sub(1)).unwrap_or(i32::MAX);
224 if line > bottommost_line {
225 line = bottommost_line;
226 side = SelectionSide::Right;
227 } else if line < 0 {
228 side = SelectionSide::Left;
229 }
230
231 let display_offset = i32::try_from(display_offset).unwrap_or(i32::MAX);
232 (
233 Point::new(line.saturating_sub(display_offset), column),
234 side,
235 )
236}
237
238///Generate the bytes to send to the terminal, from the cell location, a mouse event, and the terminal mode
239fn mouse_report(
240 point: Point,
241 button: MouseButtonCode,
242 pressed: bool,
243 modifiers: Modifiers,
244 format: MouseFormat,
245) -> Option<Vec<u8>> {
246 if point.line < 0 {
247 return None;
248 }
249
250 let mut mods = 0;
251 if modifiers.shift {
252 mods += 4;
253 }
254 if modifiers.alt {
255 mods += 8;
256 }
257 if modifiers.control {
258 mods += 16;
259 }
260
261 match format {
262 MouseFormat::Sgr => {
263 Some(sgr_mouse_report(point, button as u8 + mods, pressed).into_bytes())
264 }
265 MouseFormat::Normal(utf8) => {
266 if pressed {
267 normal_mouse_report(point, button as u8 + mods, utf8)
268 } else {
269 normal_mouse_report(point, 3 + mods, utf8)
270 }
271 }
272 }
273}
274
275fn normal_mouse_report(point: Point, button: u8, utf8: bool) -> Option<Vec<u8>> {
276 let max_point = if utf8 { 2015 } else { 223 };
277
278 if point.line >= max_point || point.column >= max_point as usize {
279 return None;
280 }
281
282 let mut msg = vec![b'\x1b', b'[', b'M', 32 + button];
283
284 let mouse_pos_encode = |pos: usize| -> Vec<u8> {
285 let pos = 32 + 1 + pos;
286 let first = 0xC0 + pos / 64;
287 let second = 0x80 + (pos & 63);
288 vec![first as u8, second as u8]
289 };
290
291 if utf8 && point.column >= 95 {
292 msg.append(&mut mouse_pos_encode(point.column));
293 } else {
294 msg.push(32 + 1 + point.column as u8);
295 }
296
297 if utf8 && point.line >= 95 {
298 msg.append(&mut mouse_pos_encode(point.line as usize));
299 } else {
300 msg.push(32 + 1 + point.line as u8);
301 }
302
303 Some(msg)
304}
305
306fn sgr_mouse_report(point: Point, button: u8, pressed: bool) -> String {
307 let c = if pressed { 'M' } else { 'm' };
308
309 let msg = format!(
310 "\x1b[<{};{};{}{}",
311 button,
312 point.column + 1,
313 point.line + 1,
314 c
315 );
316
317 msg
318}
319