mouse.rs

  1use std::cmp::{self, max, min};
  2use std::iter::repeat;
  3
  4use alacritty_terminal::grid::Dimensions;
  5/// Most of the code, and specifically the constants, in this are copied from Alacritty,
  6/// with modifications for our circumstances
  7use alacritty_terminal::index::{Column as GridCol, Line as GridLine, Point as AlacPoint, Side};
  8use alacritty_terminal::term::TermMode;
  9use gpui::{px, Modifiers, MouseButton, MouseMoveEvent, Pixels, Point, ScrollWheelEvent};
 10
 11use crate::TerminalSize;
 12
 13enum MouseFormat {
 14    SGR,
 15    Normal(bool),
 16}
 17
 18impl MouseFormat {
 19    fn from_mode(mode: TermMode) -> Self {
 20        if mode.contains(TermMode::SGR_MOUSE) {
 21            MouseFormat::SGR
 22        } else if mode.contains(TermMode::UTF8_MOUSE) {
 23            MouseFormat::Normal(true)
 24        } else {
 25            MouseFormat::Normal(false)
 26        }
 27    }
 28}
 29
 30#[derive(Debug)]
 31enum AlacMouseButton {
 32    LeftButton = 0,
 33    MiddleButton = 1,
 34    RightButton = 2,
 35    LeftMove = 32,
 36    MiddleMove = 33,
 37    RightMove = 34,
 38    NoneMove = 35,
 39    ScrollUp = 64,
 40    ScrollDown = 65,
 41    Other = 99,
 42}
 43
 44impl AlacMouseButton {
 45    fn from_move(e: &MouseMoveEvent) -> Self {
 46        match e.pressed_button {
 47            Some(b) => match b {
 48                gpui::MouseButton::Left => AlacMouseButton::LeftMove,
 49                gpui::MouseButton::Middle => AlacMouseButton::MiddleMove,
 50                gpui::MouseButton::Right => AlacMouseButton::RightMove,
 51                gpui::MouseButton::Navigate(_) => AlacMouseButton::Other,
 52            },
 53            None => AlacMouseButton::NoneMove,
 54        }
 55    }
 56
 57    fn from_button(e: MouseButton) -> Self {
 58        match e {
 59            gpui::MouseButton::Left => AlacMouseButton::LeftButton,
 60            gpui::MouseButton::Right => AlacMouseButton::MiddleButton,
 61            gpui::MouseButton::Middle => AlacMouseButton::RightButton,
 62            gpui::MouseButton::Navigate(_) => AlacMouseButton::Other,
 63        }
 64    }
 65
 66    fn from_scroll(e: &ScrollWheelEvent) -> Self {
 67        let is_positive = match e.delta {
 68            gpui::ScrollDelta::Pixels(pixels) => pixels.y > px(0.),
 69            gpui::ScrollDelta::Lines(lines) => lines.y > 0.,
 70        };
 71
 72        if is_positive {
 73            AlacMouseButton::ScrollUp
 74        } else {
 75            AlacMouseButton::ScrollDown
 76        }
 77    }
 78
 79    fn is_other(&self) -> bool {
 80        match self {
 81            AlacMouseButton::Other => true,
 82            _ => false,
 83        }
 84    }
 85}
 86
 87pub fn scroll_report(
 88    point: AlacPoint,
 89    scroll_lines: i32,
 90    e: &ScrollWheelEvent,
 91    mode: TermMode,
 92) -> Option<impl Iterator<Item = Vec<u8>>> {
 93    if mode.intersects(TermMode::MOUSE_MODE) {
 94        mouse_report(
 95            point,
 96            AlacMouseButton::from_scroll(e),
 97            true,
 98            e.modifiers,
 99            MouseFormat::from_mode(mode),
100        )
101        .map(|report| repeat(report).take(max(scroll_lines, 1) as usize))
102    } else {
103        None
104    }
105}
106
107pub fn alt_scroll(scroll_lines: i32) -> Vec<u8> {
108    let cmd = if scroll_lines > 0 { b'A' } else { b'B' };
109
110    let mut content = Vec::with_capacity(scroll_lines.unsigned_abs() as usize * 3);
111    for _ in 0..scroll_lines.abs() {
112        content.push(0x1b);
113        content.push(b'O');
114        content.push(cmd);
115    }
116    content
117}
118
119pub fn mouse_button_report(
120    point: AlacPoint,
121    button: gpui::MouseButton,
122    modifiers: Modifiers,
123    pressed: bool,
124    mode: TermMode,
125) -> Option<Vec<u8>> {
126    let button = AlacMouseButton::from_button(button);
127    if !button.is_other() && mode.intersects(TermMode::MOUSE_MODE) {
128        mouse_report(
129            point,
130            button,
131            pressed,
132            modifiers,
133            MouseFormat::from_mode(mode),
134        )
135    } else {
136        None
137    }
138}
139
140pub fn mouse_moved_report(point: AlacPoint, e: &MouseMoveEvent, mode: TermMode) -> Option<Vec<u8>> {
141    let button = AlacMouseButton::from_move(e);
142
143    if !button.is_other() && mode.intersects(TermMode::MOUSE_MOTION | TermMode::MOUSE_DRAG) {
144        //Only drags are reported in drag mode, so block NoneMove.
145        if mode.contains(TermMode::MOUSE_DRAG) && matches!(button, AlacMouseButton::NoneMove) {
146            None
147        } else {
148            mouse_report(
149                point,
150                button,
151                true,
152                e.modifiers,
153                MouseFormat::from_mode(mode),
154            )
155        }
156    } else {
157        None
158    }
159}
160
161pub fn grid_point(pos: Point<Pixels>, cur_size: TerminalSize, display_offset: usize) -> AlacPoint {
162    grid_point_and_side(pos, cur_size, display_offset).0
163}
164
165pub fn grid_point_and_side(
166    pos: Point<Pixels>,
167    cur_size: TerminalSize,
168    display_offset: usize,
169) -> (AlacPoint, Side) {
170    let mut col = GridCol((pos.x / cur_size.cell_width) as usize);
171    let cell_x = cmp::max(px(0.), pos.x) % cur_size.cell_width;
172    let half_cell_width = cur_size.cell_width / 2.0;
173    let mut side = if cell_x > half_cell_width {
174        Side::Right
175    } else {
176        Side::Left
177    };
178
179    if col > cur_size.last_column() {
180        col = cur_size.last_column();
181        side = Side::Right;
182    }
183    let col = min(col, cur_size.last_column());
184    let mut line = (pos.y / cur_size.line_height) as i32;
185    if line > cur_size.bottommost_line() {
186        line = cur_size.bottommost_line().0;
187        side = Side::Right;
188    } else if line < 0 {
189        side = Side::Left;
190    }
191
192    (
193        AlacPoint::new(GridLine(line - display_offset as i32), col),
194        side,
195    )
196}
197
198///Generate the bytes to send to the terminal, from the cell location, a mouse event, and the terminal mode
199fn mouse_report(
200    point: AlacPoint,
201    button: AlacMouseButton,
202    pressed: bool,
203    modifiers: Modifiers,
204    format: MouseFormat,
205) -> Option<Vec<u8>> {
206    if point.line < 0 {
207        return None;
208    }
209
210    let mut mods = 0;
211    if modifiers.shift {
212        mods += 4;
213    }
214    if modifiers.alt {
215        mods += 8;
216    }
217    if modifiers.control {
218        mods += 16;
219    }
220
221    match format {
222        MouseFormat::SGR => {
223            Some(sgr_mouse_report(point, button as u8 + mods, pressed).into_bytes())
224        }
225        MouseFormat::Normal(utf8) => {
226            if pressed {
227                normal_mouse_report(point, button as u8 + mods, utf8)
228            } else {
229                normal_mouse_report(point, 3 + mods, utf8)
230            }
231        }
232    }
233}
234
235fn normal_mouse_report(point: AlacPoint, button: u8, utf8: bool) -> Option<Vec<u8>> {
236    let AlacPoint { line, column } = point;
237    let max_point = if utf8 { 2015 } else { 223 };
238
239    if line >= max_point || column >= max_point {
240        return None;
241    }
242
243    let mut msg = vec![b'\x1b', b'[', b'M', 32 + button];
244
245    let mouse_pos_encode = |pos: usize| -> Vec<u8> {
246        let pos = 32 + 1 + pos;
247        let first = 0xC0 + pos / 64;
248        let second = 0x80 + (pos & 63);
249        vec![first as u8, second as u8]
250    };
251
252    if utf8 && column >= 95 {
253        msg.append(&mut mouse_pos_encode(column.0));
254    } else {
255        msg.push(32 + 1 + column.0 as u8);
256    }
257
258    if utf8 && line >= 95 {
259        msg.append(&mut mouse_pos_encode(line.0 as usize));
260    } else {
261        msg.push(32 + 1 + line.0 as u8);
262    }
263
264    Some(msg)
265}
266
267fn sgr_mouse_report(point: AlacPoint, button: u8, pressed: bool) -> String {
268    let c = if pressed { 'M' } else { 'm' };
269
270    let msg = format!(
271        "\x1b[<{};{};{}{}",
272        button,
273        point.column + 1,
274        point.line + 1,
275        c
276    );
277
278    msg
279}