Skip to main content

freya_terminal/
parser.rs

1use alacritty_terminal::term::TermMode;
2
3/// Mouse button for terminal encoding.
4#[derive(Debug, Clone, Copy, PartialEq)]
5pub enum TerminalMouseButton {
6    Left,
7    Middle,
8    Right,
9}
10
11impl TerminalMouseButton {
12    /// X11/SGR button code (without modifier bits).
13    fn code(self) -> u8 {
14        match self {
15            Self::Left => 0,
16            Self::Middle => 1,
17            Self::Right => 2,
18        }
19    }
20}
21
22/// Encode a mouse event to the byte sequence the running app expects.
23///
24/// `sgr_code` is the value emitted in SGR (1006) encoding; `x11_code` is the
25/// value emitted in classic X11 encoding before the mandatory `+32` offset.
26/// `release_in_sgr` only affects SGR encoding (lowercase `m` vs uppercase
27/// `M`); X11 release uses a fixed button byte and is selected by passing
28/// `x11_code = 3`.
29fn encode(
30    sgr_code: u8,
31    x11_code: u8,
32    row: usize,
33    col: usize,
34    mode: TermMode,
35    release_in_sgr: bool,
36) -> String {
37    let row = row.saturating_add(1);
38    let col = col.saturating_add(1);
39    if mode.contains(TermMode::SGR_MOUSE) {
40        let action = if release_in_sgr { 'm' } else { 'M' };
41        format!("\x1b[<{sgr_code};{col};{row}{action}")
42    } else {
43        let button_byte = x11_code.saturating_add(32);
44        let col_byte = col.min(255) as u8;
45        let row_byte = row.min(255) as u8;
46        format!(
47            "\x1b[M{}{}{}",
48            button_byte as char, col_byte as char, row_byte as char
49        )
50    }
51}
52
53pub fn encode_mouse_press(
54    row: usize,
55    col: usize,
56    button: TerminalMouseButton,
57    mode: TermMode,
58) -> String {
59    encode(button.code(), button.code(), row, col, mode, false)
60}
61
62pub fn encode_mouse_release(
63    row: usize,
64    col: usize,
65    button: TerminalMouseButton,
66    mode: TermMode,
67) -> String {
68    // X11 collapses release into a single "button 3" byte; SGR keeps the
69    // original button code but switches `M` to `m`.
70    encode(button.code(), 3, row, col, mode, true)
71}
72
73/// Encode a mouse motion event. `None` for `button` means hover (no button).
74pub fn encode_mouse_move(
75    row: usize,
76    col: usize,
77    button: Option<TerminalMouseButton>,
78    mode: TermMode,
79) -> String {
80    let code = button.map_or(3, TerminalMouseButton::code) + 32;
81    encode(code, code, row, col, mode, false)
82}
83
84/// Encode a mouse wheel event. Positive `delta_y` = wheel up.
85pub fn encode_wheel_event(row: usize, col: usize, delta_y: f64, mode: TermMode) -> String {
86    let code = if delta_y > 0.0 { 64 } else { 65 };
87    encode(code, code, row, col, mode, false)
88}