diff options
Diffstat (limited to 'src/terminal.zig')
| -rw-r--r-- | src/terminal.zig | 59 |
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)); +} |
