This repository has no description
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338/// SurfaceMouse represents mouse helper functionality for the core surface.////// It's currently small in scope; its purpose is to isolate mouse logic that/// has gotten a bit complex (e.g. pointer shape handling for key events), but/// the intention is to grow it later so that we can better test said logic).const SurfaceMouse = @This();
const std = @import("std");const builtin = @import("builtin");const input = @import("input.zig");const apprt = @import("apprt.zig");const terminal = @import("terminal/main.zig");const MouseShape = @import("terminal/mouse_shape.zig").MouseShape;
/// For processing key events; the key that was physically pressed on the/// keyboard.physical_key: input.Key,
/// The mouse event tracking mode, if any.mouse_event: terminal.Terminal.MouseEvents,
/// The current terminal's mouse shape.mouse_shape: MouseShape,
/// The last mods state when the last mouse button (whatever it was) was/// pressed or release.mods: input.Mods,
/// True if the mouse position is currently over a link.over_link: bool,
/// True if the mouse pointer is currently hidden.hidden: bool,
/// Translates key state to mouse shape (cursor) state, based on a state/// machine.////// There are 4 current states:////// * text: starting state, displays a text bar./// * default: default state when in a mouse tracking mode. (e.g. vim, etc)./// Displays an arrow pointer./// * pointer: default state when over a link. Displays a pointing finger./// * crosshair: any above state can transition to this when the rectangle/// select keys are pressed (ctrl/super+alt).////// Additionally, default can transition back to text if one of the shift keys/// are pressed during mouse tracking mode.////// Any secondary state transitions back to its default state when the/// appropriate keys are released.////// null is returned when the mouse shape does not need changing.pub fn keyToMouseShape(self: SurfaceMouse) ?MouseShape { // Filter for appropriate key events if (!eligibleMouseShapeKeyEvent(self.physical_key)) return null;
// Exceptions: link hover or hidden state overrides any other shape // processing and does not change state. // // TODO: As we unravel mouse state, we can fix this to be more explicit. if (self.over_link or self.hidden) { return null; }
// Set our current default state var current_shape_state: MouseShape = undefined; if (self.mouse_event != .none) { // In mouse tracking mode, should be default (arrow pointer) current_shape_state = .default; } else { // Default terminal mode, should be text (text bar) current_shape_state = .text; }
// Transition table. // // TODO: This could be updated eventually to be a true transition table if // we move to a full stateful mouse surface, e.g. very specific inputs // transitioning state based on previous state, versus flags like "is the // mouse over a link", etc. switch (current_shape_state) { .default => { if (isMouseModeOverrideState(self.mods) and isRectangleSelectState(self.mods)) { // Crosshair (rectangle select), only set if we are also // overriding (e.g. shift+ctrl+alt) return .crosshair; } else if (isMouseModeOverrideState(self.mods)) { // Normal override state return .text; } else { return .default; } },
.text => { if (isRectangleSelectState(self.mods)) { // Crosshair (rectangle select) return .crosshair; } else { return .text; } },
// Fall back on default state else => unreachable, }}
fn eligibleMouseShapeKeyEvent(physical_key: input.Key) bool { return physical_key.ctrlOrSuper() or physical_key.leftOrRightShift() or physical_key.leftOrRightAlt();}
fn isMouseModeOverrideState(mods: input.Mods) bool { return mods.shift;}
/// Returns true if our modifiers put us in a state where dragging/// should cause a rectangle select.pub fn isRectangleSelectState(mods: input.Mods) bool { return if (comptime builtin.target.os.tag.isDarwin()) mods.alt else mods.ctrlOrSuper() and mods.alt;}
test "keyToMouseShape" { const testing = std.testing;
{ // No specific key pressed const m: SurfaceMouse = .{ .physical_key = .unidentified, .mouse_event = .none, .mouse_shape = .progress, .mods = .{}, .over_link = false, .hidden = false, };
const got = m.keyToMouseShape(); try testing.expect(got == null); }
{ // Over a link. NOTE: This tests that we don't touch the inbound state, // not necessarily if we're over a link. const m: SurfaceMouse = .{ .physical_key = .shift_left, .mouse_event = .none, .mouse_shape = .progress, .mods = .{}, .over_link = true, .hidden = false, };
const got = m.keyToMouseShape(); try testing.expect(got == null); }
{ // Mouse is currently hidden const m: SurfaceMouse = .{ .physical_key = .shift_left, .mouse_event = .none, .mouse_shape = .progress, .mods = .{}, .over_link = true, .hidden = true, };
const got = m.keyToMouseShape(); try testing.expect(got == null); }
{ // default, no mods (mouse tracking) const m: SurfaceMouse = .{ .physical_key = .shift_left, .mouse_event = .x10, .mouse_shape = .default, .mods = .{}, .over_link = false, .hidden = false, };
const want: MouseShape = .default; const got = m.keyToMouseShape(); try testing.expect(want == got); }
{ // default -> crosshair (mouse tracking) const m: SurfaceMouse = .{ .physical_key = .alt_left, .mouse_event = .x10, .mouse_shape = .default, .mods = .{ .ctrl = true, .super = true, .alt = true, .shift = true }, .over_link = false, .hidden = false, };
const want: MouseShape = .crosshair; const got = m.keyToMouseShape(); try testing.expect(want == got); }
{ // default -> text (mouse tracking) const m: SurfaceMouse = .{ .physical_key = .shift_left, .mouse_event = .x10, .mouse_shape = .default, .mods = .{ .shift = true }, .over_link = false, .hidden = false, };
const want: MouseShape = .text; const got = m.keyToMouseShape(); try testing.expect(want == got); }
{ // crosshair -> text (mouse tracking) const m: SurfaceMouse = .{ .physical_key = .alt_left, .mouse_event = .x10, .mouse_shape = .crosshair, .mods = .{ .shift = true }, .over_link = false, .hidden = false, };
const want: MouseShape = .text; const got = m.keyToMouseShape(); try testing.expect(want == got); }
{ // crosshair -> default (mouse tracking) const m: SurfaceMouse = .{ .physical_key = .alt_left, .mouse_event = .x10, .mouse_shape = .crosshair, .mods = .{}, .over_link = false, .hidden = false, };
const want: MouseShape = .default; const got = m.keyToMouseShape(); try testing.expect(want == got); }
{ // text -> crosshair (mouse tracking) const m: SurfaceMouse = .{ .physical_key = .alt_left, .mouse_event = .x10, .mouse_shape = .text, .mods = .{ .ctrl = true, .super = true, .alt = true, .shift = true }, .over_link = false, .hidden = false, };
const want: MouseShape = .crosshair; const got = m.keyToMouseShape(); try testing.expect(want == got); }
{ // text -> default (mouse tracking) const m: SurfaceMouse = .{ .physical_key = .shift_left, .mouse_event = .x10, .mouse_shape = .text, .mods = .{}, .over_link = false, .hidden = false, };
const want: MouseShape = .default; const got = m.keyToMouseShape(); try testing.expect(want == got); }
{ // text, no mods (no mouse tracking) const m: SurfaceMouse = .{ .physical_key = .shift_left, .mouse_event = .none, .mouse_shape = .text, .mods = .{}, .over_link = false, .hidden = false, };
const want: MouseShape = .text; const got = m.keyToMouseShape(); try testing.expect(want == got); }
{ // text -> crosshair (no mouse tracking) const m: SurfaceMouse = .{ .physical_key = .alt_left, .mouse_event = .none, .mouse_shape = .text, .mods = .{ .ctrl = true, .super = true, .alt = true }, .over_link = false, .hidden = false, };
const want: MouseShape = .crosshair; const got = m.keyToMouseShape(); try testing.expect(want == got); }
{ // crosshair -> text (no mouse tracking) const m: SurfaceMouse = .{ .physical_key = .alt_left, .mouse_event = .none, .mouse_shape = .crosshair, .mods = .{}, .over_link = false, .hidden = false, };
const want: MouseShape = .text; const got = m.keyToMouseShape(); try testing.expect(want == got); }}