mouse.rs

  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}