Skip to repository content

tenant.openagents/omega

No repository description is available.

OpenAgents Git authority 2026-07-28T05:16:02.640Z Public web read
NIP-34 coordinate30617:7649603503856e5148d571eac2766b288a8ff1e9e35d380337a1d2b0015b4f92:omega
MaintainersHidden in public view
References2 branches · 1 tag
Read-only clonegit clone https://openagents.com/git/tenant.openagents/omega.git
Browse files

mouse.rs

319 lines · 8.7 KB · rust
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
Served at tenant.openagents/omega Member data and write actions are omitted.