diff options
Diffstat (limited to 'src/terminal.zig')
| -rw-r--r-- | src/terminal.zig | 49 |
1 files changed, 49 insertions, 0 deletions
diff --git a/src/terminal.zig b/src/terminal.zig index 4cac6f1d..7874ca89 100644 --- a/src/terminal.zig +++ b/src/terminal.zig @@ -385,6 +385,55 @@ pub fn mouseFormatSgr(pane: *const Pane) bool { return state.vt.modes.get(.mouse_format_sgr); } +/// A wheel over the program is the program's: it tracks the mouse, or it is +/// on the alternate screen, which has no scrollback, with alternate scroll +/// (DECSET 1007, on unless turned off) asking for arrow keys instead. +pub fn ownsWheel(pane: *const Pane) bool { + if (comptime !enabled) return false; + const state = pane.terminal orelse return false; + return reportsMouse(pane) or + (state.vt.screens.active_key == .alternate and state.vt.modes.get(.mouse_alternate_scroll)); +} + +pub const Wheel = enum(u8) { up = 64, down = 65, left = 66, right = 67 }; + +/// The bytes a wheel turn at grid cell `col`,`row` (0-based) sends the +/// program, as xterm sends them: its mouse report in the format it asked +/// for, else, on the alternate screen, an arrow key (none for a sideways +/// turn). Null when the wheel is not the program's (`ownsWheel`) or the +/// cell cannot be said in the legacy format. +pub fn wheelBytes(pane: *const Pane, buf: *[32]u8, wheel: Wheel, col: u16, row: u16, ctrl: bool) ?[]const u8 { + if (comptime !enabled) return null; + const state = pane.terminal orelse return null; + if (!ownsWheel(pane)) return null; + const m = &state.vt.modes; + if (!reportsMouse(pane)) return switch (wheel) { + .up => if (m.get(.cursor_keys)) "\x1bOA" else "\x1b[A", + .down => if (m.get(.cursor_keys)) "\x1bOB" else "\x1b[B", + .left, .right => null, + }; + const code = @intFromEnum(wheel) + @as(u8, if (ctrl) 16 else 0); + const x = @as(u32, col) + 1; + const y = @as(u32, row) + 1; + if (m.get(.mouse_format_sgr)) return std.fmt.bufPrint(buf, "\x1b[<{d};{d};{d}M", .{ code, x, y }) catch null; + if (m.get(.mouse_format_urxvt)) return std.fmt.bufPrint(buf, "\x1b[{d};{d};{d}M", .{ 32 + @as(u32, code), x, y }) catch null; + buf[0..3].* = "\x1b[M".*; + buf[3] = 32 + code; + var n: usize = 4; + for ([_]u32{ x, y }) |v| { + const c = 32 + v; + if (m.get(.mouse_format_utf8)) { + if (c > 2047) return null; + n += std.unicode.utf8Encode(@intCast(c), buf[n..]) catch return null; + } else { + if (c > 255) return null; + buf[n] = @intCast(c); + n += 1; + } + } + return buf[0..n]; +} + /// The whole scrollback as plain text, `gpa`-owned: what a terminal's body /// reads and `Save` writes out. Logical lines: rows the terminal wrapped /// are joined again (ghostty's wrap flags, as pty/run's output is), and |
