summaryrefslogtreecommitdiff
path: root/src/terminal.zig
diff options
context:
space:
mode:
Diffstat (limited to 'src/terminal.zig')
-rw-r--r--src/terminal.zig49
1 files changed, 49 insertions, 0 deletions
diff --git a/src/terminal.zig b/src/terminal.zig
index 1a3b6a21..4751a09f 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