terminal/tmux: capture both primary/alt screen
parent
a3e01581be
commit
50ac848672
|
|
@ -5,6 +5,7 @@ const testing = std.testing;
|
||||||
const assert = @import("../../quirks.zig").inlineAssert;
|
const assert = @import("../../quirks.zig").inlineAssert;
|
||||||
const CircBuf = @import("../../datastruct/main.zig").CircBuf;
|
const CircBuf = @import("../../datastruct/main.zig").CircBuf;
|
||||||
const Screen = @import("../Screen.zig");
|
const Screen = @import("../Screen.zig");
|
||||||
|
const ScreenSet = @import("../ScreenSet.zig");
|
||||||
const Terminal = @import("../Terminal.zig");
|
const Terminal = @import("../Terminal.zig");
|
||||||
const Layout = @import("layout.zig").Layout;
|
const Layout = @import("layout.zig").Layout;
|
||||||
const control = @import("control.zig");
|
const control = @import("control.zig");
|
||||||
|
|
@ -366,13 +367,15 @@ pub const Viewer = struct {
|
||||||
content,
|
content,
|
||||||
),
|
),
|
||||||
|
|
||||||
.pane_history => |id| try self.receivedPaneHistory(
|
.pane_history => |cap| try self.receivedPaneHistory(
|
||||||
id,
|
cap.screen_key,
|
||||||
|
cap.id,
|
||||||
content,
|
content,
|
||||||
),
|
),
|
||||||
|
|
||||||
.pane_visible => |id| try self.receivedPaneVisible(
|
.pane_visible => |cap| try self.receivedPaneVisible(
|
||||||
id,
|
cap.screen_key,
|
||||||
|
cap.id,
|
||||||
content,
|
content,
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
|
@ -494,8 +497,10 @@ pub const Viewer = struct {
|
||||||
const pane_id: usize = kv.key_ptr.*;
|
const pane_id: usize = kv.key_ptr.*;
|
||||||
if (self.panes.contains(pane_id)) continue;
|
if (self.panes.contains(pane_id)) continue;
|
||||||
try self.queueCommands(&.{
|
try self.queueCommands(&.{
|
||||||
.{ .pane_history = pane_id },
|
.{ .pane_history = .{ .id = pane_id, .screen_key = .primary } },
|
||||||
.{ .pane_visible = pane_id },
|
.{ .pane_visible = .{ .id = pane_id, .screen_key = .primary } },
|
||||||
|
.{ .pane_history = .{ .id = pane_id, .screen_key = .alternate } },
|
||||||
|
.{ .pane_visible = .{ .id = pane_id, .screen_key = .alternate } },
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -528,6 +533,7 @@ pub const Viewer = struct {
|
||||||
|
|
||||||
fn receivedPaneHistory(
|
fn receivedPaneHistory(
|
||||||
self: *Viewer,
|
self: *Viewer,
|
||||||
|
screen_key: ScreenSet.Key,
|
||||||
id: usize,
|
id: usize,
|
||||||
content: []const u8,
|
content: []const u8,
|
||||||
) !void {
|
) !void {
|
||||||
|
|
@ -538,6 +544,7 @@ pub const Viewer = struct {
|
||||||
};
|
};
|
||||||
const pane: *Pane = entry.value_ptr;
|
const pane: *Pane = entry.value_ptr;
|
||||||
const t: *Terminal = &pane.terminal;
|
const t: *Terminal = &pane.terminal;
|
||||||
|
_ = try t.switchScreen(screen_key);
|
||||||
const screen: *Screen = t.screens.active;
|
const screen: *Screen = t.screens.active;
|
||||||
|
|
||||||
// Get a VT stream from the terminal so we can send data as-is into
|
// Get a VT stream from the terminal so we can send data as-is into
|
||||||
|
|
@ -569,6 +576,7 @@ pub const Viewer = struct {
|
||||||
|
|
||||||
fn receivedPaneVisible(
|
fn receivedPaneVisible(
|
||||||
self: *Viewer,
|
self: *Viewer,
|
||||||
|
screen_key: ScreenSet.Key,
|
||||||
id: usize,
|
id: usize,
|
||||||
content: []const u8,
|
content: []const u8,
|
||||||
) !void {
|
) !void {
|
||||||
|
|
@ -578,13 +586,15 @@ pub const Viewer = struct {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
const pane: *Pane = entry.value_ptr;
|
const pane: *Pane = entry.value_ptr;
|
||||||
|
const t: *Terminal = &pane.terminal;
|
||||||
|
_ = try t.switchScreen(screen_key);
|
||||||
|
|
||||||
// Erase the active area and reset the cursor to the top-left
|
// Erase the active area and reset the cursor to the top-left
|
||||||
// before writing the visible content.
|
// before writing the visible content.
|
||||||
pane.terminal.eraseDisplay(.complete, false);
|
t.eraseDisplay(.complete, false);
|
||||||
pane.terminal.setCursorPos(1, 1);
|
t.setCursorPos(1, 1);
|
||||||
|
|
||||||
var stream = pane.terminal.vtStream();
|
var stream = t.vtStream();
|
||||||
defer stream.deinit();
|
defer stream.deinit();
|
||||||
stream.nextSlice(content) catch |err| {
|
stream.nextSlice(content) catch |err| {
|
||||||
log.info("failed to process pane visible for pane id={}: {}", .{ id, err });
|
log.info("failed to process pane visible for pane id={}: {}", .{ id, err });
|
||||||
|
|
@ -729,15 +739,20 @@ const Command = union(enum) {
|
||||||
list_windows,
|
list_windows,
|
||||||
|
|
||||||
/// Capture history for the given pane ID.
|
/// Capture history for the given pane ID.
|
||||||
pane_history: usize,
|
pane_history: CapturePane,
|
||||||
|
|
||||||
/// Capture visible area for the given pane ID.
|
/// Capture visible area for the given pane ID.
|
||||||
pane_visible: usize,
|
pane_visible: CapturePane,
|
||||||
|
|
||||||
/// User command. This is a command provided by the user. Since
|
/// User command. This is a command provided by the user. Since
|
||||||
/// this is user provided, we can't be sure what it is.
|
/// this is user provided, we can't be sure what it is.
|
||||||
user: []const u8,
|
user: []const u8,
|
||||||
|
|
||||||
|
const CapturePane = struct {
|
||||||
|
id: usize,
|
||||||
|
screen_key: ScreenSet.Key,
|
||||||
|
};
|
||||||
|
|
||||||
pub fn deinit(self: Command, alloc: Allocator) void {
|
pub fn deinit(self: Command, alloc: Allocator) void {
|
||||||
return switch (self) {
|
return switch (self) {
|
||||||
.list_windows,
|
.list_windows,
|
||||||
|
|
@ -761,24 +776,34 @@ const Command = union(enum) {
|
||||||
.{comptime Format.list_windows.comptimeFormat()},
|
.{comptime Format.list_windows.comptimeFormat()},
|
||||||
)),
|
)),
|
||||||
|
|
||||||
.pane_history => |id| try writer.print(
|
.pane_history => |cap| try writer.print(
|
||||||
// -p = output to stdout instead of buffer
|
// -p = output to stdout instead of buffer
|
||||||
// -e = output escape sequences for SGR
|
// -e = output escape sequences for SGR
|
||||||
|
// -a = capture alternate screen (only valid for alternate)
|
||||||
|
// -q = quiet, don't error if alternate screen doesn't exist
|
||||||
// -S - = start at the top of history ("-")
|
// -S - = start at the top of history ("-")
|
||||||
// -E -1 = end at the last line of history (1 before the
|
// -E -1 = end at the last line of history (1 before the
|
||||||
// visible area is -1).
|
// visible area is -1).
|
||||||
// -t %{d} = target a specific pane ID
|
// -t %{d} = target a specific pane ID
|
||||||
"capture-pane -p -e -S - -E -1 -t %{d}\n",
|
"capture-pane -p -e -q {s}-S - -E -1 -t %{d}\n",
|
||||||
.{id},
|
.{
|
||||||
|
if (cap.screen_key == .alternate) "-a " else "",
|
||||||
|
cap.id,
|
||||||
|
},
|
||||||
),
|
),
|
||||||
|
|
||||||
.pane_visible => |id| try writer.print(
|
.pane_visible => |cap| try writer.print(
|
||||||
// -p = output to stdout instead of buffer
|
// -p = output to stdout instead of buffer
|
||||||
// -e = output escape sequences for SGR
|
// -e = output escape sequences for SGR
|
||||||
|
// -a = capture alternate screen (only valid for alternate)
|
||||||
|
// -q = quiet, don't error if alternate screen doesn't exist
|
||||||
// -t %{d} = target a specific pane ID
|
// -t %{d} = target a specific pane ID
|
||||||
// (no -S/-E = capture visible area only)
|
// (no -S/-E = capture visible area only)
|
||||||
"capture-pane -p -e -t %{d}\n",
|
"capture-pane -p -e -q {s}-t %{d}\n",
|
||||||
.{id},
|
.{
|
||||||
|
if (cap.screen_key == .alternate) "-a " else "",
|
||||||
|
cap.id,
|
||||||
|
},
|
||||||
),
|
),
|
||||||
|
|
||||||
.user => |v| try writer.writeAll(v),
|
.user => |v| try writer.writeAll(v),
|
||||||
|
|
@ -938,9 +963,11 @@ test "initial flow" {
|
||||||
} },
|
} },
|
||||||
.contains_tags = &.{ .windows, .command },
|
.contains_tags = &.{ .windows, .command },
|
||||||
.contains_command = "capture-pane",
|
.contains_command = "capture-pane",
|
||||||
|
// pane_history for pane 0 (primary)
|
||||||
.check_command = (struct {
|
.check_command = (struct {
|
||||||
fn check(_: *Viewer, command: []const u8) anyerror!void {
|
fn check(_: *Viewer, command: []const u8) anyerror!void {
|
||||||
try testing.expect(std.mem.containsAtLeast(u8, command, 1, "-t %0"));
|
try testing.expect(std.mem.containsAtLeast(u8, command, 1, "-t %0"));
|
||||||
|
try testing.expect(!std.mem.containsAtLeast(u8, command, 1, "-a"));
|
||||||
}
|
}
|
||||||
}).check,
|
}).check,
|
||||||
},
|
},
|
||||||
|
|
@ -950,11 +977,12 @@ test "initial flow" {
|
||||||
\\Hello, world!
|
\\Hello, world!
|
||||||
,
|
,
|
||||||
} },
|
} },
|
||||||
// Moves on to pane_visible for pane 0
|
// Moves on to pane_visible for pane 0 (primary)
|
||||||
.contains_command = "capture-pane",
|
.contains_command = "capture-pane",
|
||||||
.check_command = (struct {
|
.check_command = (struct {
|
||||||
fn check(_: *Viewer, command: []const u8) anyerror!void {
|
fn check(_: *Viewer, command: []const u8) anyerror!void {
|
||||||
try testing.expect(std.mem.containsAtLeast(u8, command, 1, "-t %0"));
|
try testing.expect(std.mem.containsAtLeast(u8, command, 1, "-t %0"));
|
||||||
|
try testing.expect(!std.mem.containsAtLeast(u8, command, 1, "-a"));
|
||||||
}
|
}
|
||||||
}).check,
|
}).check,
|
||||||
.check = (struct {
|
.check = (struct {
|
||||||
|
|
@ -982,21 +1010,67 @@ test "initial flow" {
|
||||||
},
|
},
|
||||||
.{
|
.{
|
||||||
.input = .{ .tmux = .{ .block_end = "" } },
|
.input = .{ .tmux = .{ .block_end = "" } },
|
||||||
// Moves on to pane_history for pane 1
|
// Moves on to pane_history for pane 0 (alternate)
|
||||||
.contains_command = "capture-pane",
|
.contains_command = "capture-pane",
|
||||||
.check_command = (struct {
|
.check_command = (struct {
|
||||||
fn check(_: *Viewer, command: []const u8) anyerror!void {
|
fn check(_: *Viewer, command: []const u8) anyerror!void {
|
||||||
try testing.expect(std.mem.containsAtLeast(u8, command, 1, "-t %1"));
|
try testing.expect(std.mem.containsAtLeast(u8, command, 1, "-t %0"));
|
||||||
|
try testing.expect(std.mem.containsAtLeast(u8, command, 1, "-a"));
|
||||||
}
|
}
|
||||||
}).check,
|
}).check,
|
||||||
},
|
},
|
||||||
.{
|
.{
|
||||||
.input = .{ .tmux = .{ .block_end = "" } },
|
.input = .{ .tmux = .{ .block_end = "" } },
|
||||||
// Moves on to pane_visible for pane 1
|
// Moves on to pane_visible for pane 0 (alternate)
|
||||||
|
.contains_command = "capture-pane",
|
||||||
|
.check_command = (struct {
|
||||||
|
fn check(_: *Viewer, command: []const u8) anyerror!void {
|
||||||
|
try testing.expect(std.mem.containsAtLeast(u8, command, 1, "-t %0"));
|
||||||
|
try testing.expect(std.mem.containsAtLeast(u8, command, 1, "-a"));
|
||||||
|
}
|
||||||
|
}).check,
|
||||||
|
},
|
||||||
|
.{
|
||||||
|
.input = .{ .tmux = .{ .block_end = "" } },
|
||||||
|
// Moves on to pane_history for pane 1 (primary)
|
||||||
.contains_command = "capture-pane",
|
.contains_command = "capture-pane",
|
||||||
.check_command = (struct {
|
.check_command = (struct {
|
||||||
fn check(_: *Viewer, command: []const u8) anyerror!void {
|
fn check(_: *Viewer, command: []const u8) anyerror!void {
|
||||||
try testing.expect(std.mem.containsAtLeast(u8, command, 1, "-t %1"));
|
try testing.expect(std.mem.containsAtLeast(u8, command, 1, "-t %1"));
|
||||||
|
try testing.expect(!std.mem.containsAtLeast(u8, command, 1, "-a"));
|
||||||
|
}
|
||||||
|
}).check,
|
||||||
|
},
|
||||||
|
.{
|
||||||
|
.input = .{ .tmux = .{ .block_end = "" } },
|
||||||
|
// Moves on to pane_visible for pane 1 (primary)
|
||||||
|
.contains_command = "capture-pane",
|
||||||
|
.check_command = (struct {
|
||||||
|
fn check(_: *Viewer, command: []const u8) anyerror!void {
|
||||||
|
try testing.expect(std.mem.containsAtLeast(u8, command, 1, "-t %1"));
|
||||||
|
try testing.expect(!std.mem.containsAtLeast(u8, command, 1, "-a"));
|
||||||
|
}
|
||||||
|
}).check,
|
||||||
|
},
|
||||||
|
.{
|
||||||
|
.input = .{ .tmux = .{ .block_end = "" } },
|
||||||
|
// Moves on to pane_history for pane 1 (alternate)
|
||||||
|
.contains_command = "capture-pane",
|
||||||
|
.check_command = (struct {
|
||||||
|
fn check(_: *Viewer, command: []const u8) anyerror!void {
|
||||||
|
try testing.expect(std.mem.containsAtLeast(u8, command, 1, "-t %1"));
|
||||||
|
try testing.expect(std.mem.containsAtLeast(u8, command, 1, "-a"));
|
||||||
|
}
|
||||||
|
}).check,
|
||||||
|
},
|
||||||
|
.{
|
||||||
|
.input = .{ .tmux = .{ .block_end = "" } },
|
||||||
|
// Moves on to pane_visible for pane 1 (alternate)
|
||||||
|
.contains_command = "capture-pane",
|
||||||
|
.check_command = (struct {
|
||||||
|
fn check(_: *Viewer, command: []const u8) anyerror!void {
|
||||||
|
try testing.expect(std.mem.containsAtLeast(u8, command, 1, "-t %1"));
|
||||||
|
try testing.expect(std.mem.containsAtLeast(u8, command, 1, "-a"));
|
||||||
}
|
}
|
||||||
}).check,
|
}).check,
|
||||||
},
|
},
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue