summaryrefslogtreecommitdiff
path: root/src/terminal.zig
diff options
context:
space:
mode:
Diffstat (limited to 'src/terminal.zig')
-rw-r--r--src/terminal.zig59
1 files changed, 59 insertions, 0 deletions
diff --git a/src/terminal.zig b/src/terminal.zig
index 63c5479f..af540202 100644
--- a/src/terminal.zig
+++ b/src/terminal.zig
@@ -380,6 +380,13 @@ pub fn reportsMouse(pane: *const Pane) bool {
return state.vt.flags.mouse_event != .none;
}
+/// DECSET 1004: the program wants to hear when it gains and loses focus.
+pub fn reportsFocus(pane: *const Pane) bool {
+ if (comptime !enabled) return false;
+ const state = pane.terminal orelse return false;
+ return state.vt.modes.get(.focus_event);
+}
+
/// ...every move of it, a button down or not (1003).
pub fn tracksAllMotion(pane: *const Pane) bool {
if (comptime !enabled) return false;
@@ -2738,3 +2745,55 @@ const DeviceAttrs = @typeInfo(@typeInfo(@typeInfo(
pub fn ptyDeviceAttrs(_: *ghostty_vt.TerminalStream.Handler) DeviceAttrs {
return .{};
}
+
+/// Every byte the core wrote to each pane's pty since the last call, `\n`
+/// between panes' shares: "0:<bytes>\n1:<bytes>\n" for panes that got any.
+fn ptyWritesByPane(p: *Pardes, out: *std.ArrayList(u8)) ![]const u8 {
+ out.clearRetainingCapacity();
+ var per: [pardes.MAX_PANES]std.ArrayList(u8) = @splat(.empty);
+ defer for (&per) |*b| b.deinit(p.gpa);
+ while (p.nextEffect()) |effect| {
+ if (effect == .write) try per[effect.write.pane].appendSlice(p.gpa, effect.write.bytes.slice());
+ }
+ for (per, 0..) |b, id| if (b.items.len > 0) try out.print(p.gpa, "{d}:{s}\n", .{ id, b.items });
+ return out.items;
+}
+
+test "a program that asked for focus reports hears CSI I and CSI O as its pane takes and gives up the keyboard, and as the window does" {
+ if (comptime !enabled) return error.SkipZigTest;
+ const gpa = std.testing.allocator;
+ const p = try Pardes.init(gpa, .{ .cols = 90, .rows = 30, .shells = 3 });
+ defer p.deinit();
+ var frame = std.heap.ArenaAllocator.init(gpa);
+ defer frame.deinit();
+ _ = try p.render(frame.allocator());
+ var out: std.ArrayList(u8) = .empty;
+ defer out.deinit(gpa);
+ _ = try ptyWritesByPane(p, &out);
+ p.active = 0;
+ // Asking while its pane has the keyboard: told at once that it has.
+ p.update(.{ .output = .{ .pane = 0, .bytes = "\x1b[?1004h" } });
+ try std.testing.expectEqualStrings("0:\x1b[I\n", try ptyWritesByPane(p, &out));
+ p.update(.{ .output = .{ .pane = 1, .bytes = "\x1b[?1004h" } });
+ try std.testing.expectEqualStrings("", try ptyWritesByPane(p, &out));
+ // A click into the other pane moves the keyboard: out of one, into the
+ // other.
+ const r = p.rects[1];
+ const at_col = r.x + config.GUTTER + 2;
+ const at_row = p.bodyTop(p.panes[1].?, r) + 1;
+ p.update(.{ .mouse = .{ .button = .left, .kind = .press, .col = at_col, .row = at_row } });
+ p.update(.{ .mouse = .{ .button = .left, .kind = .release, .col = at_col, .row = at_row } });
+ try std.testing.expectEqual(@as(usize, 1), p.active);
+ try std.testing.expectEqualStrings("0:\x1b[O\n1:\x1b[I\n", try ptyWritesByPane(p, &out));
+ // The window: away and back.
+ p.update(.{ .focus = false });
+ try std.testing.expectEqualStrings("1:\x1b[O\n", try ptyWritesByPane(p, &out));
+ p.update(.{ .focus = false });
+ try std.testing.expectEqualStrings("", try ptyWritesByPane(p, &out));
+ p.update(.{ .focus = true });
+ try std.testing.expectEqualStrings("1:\x1b[I\n", try ptyWritesByPane(p, &out));
+ // A program that did not ask hears nothing: pane 2.
+ p.active = 2;
+ p.update(.tick);
+ try std.testing.expectEqualStrings("1:\x1b[O\n", try ptyWritesByPane(p, &out));
+}