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