summaryrefslogtreecommitdiff
path: root/replay.zig
diff options
context:
space:
mode:
Diffstat (limited to 'replay.zig')
-rw-r--r--replay.zig345
1 files changed, 239 insertions, 106 deletions
diff --git a/replay.zig b/replay.zig
index 75227c42..18deab13 100644
--- a/replay.zig
+++ b/replay.zig
@@ -1,10 +1,30 @@
const std = @import("std");
+const builtin = @import("builtin");
const ui = @import("ui");
const ghostty_vt = @import("ghostty-vt");
+const replay_config = @import("replay_config");
const dump = @import("dump.zig");
const paint = @import("paint.zig");
const image = @import("image.zig");
+const is_emscripten = builtin.os.tag == .emscripten;
+const embedded_dump = if (replay_config.embedded_dump) @embedFile("replay.dump.zon") else "";
+const wasm_gpa_buffer_size = 1024 * 1024 * 1024;
+
+var wasm_gpa_buffer: [if (is_emscripten) wasm_gpa_buffer_size else 0]u8 align(16) = undefined;
+var wasm_gpa_fba: std.heap.FixedBufferAllocator = undefined;
+
+extern "c" fn emscripten_console_error(utf8String: [*:0]const u8) void;
+
+pub const std_options_debug_threaded_io: ?*std.Io.Threaded = if (is_emscripten)
+ null
+else
+ std.Io.Threaded.global_single_threaded;
+pub const std_options_debug_io: std.Io = if (is_emscripten)
+ std.Io.failing
+else
+ std_options_debug_threaded_io.?.io();
+
const TOPBAR_H: u16 = 1;
const GUTTER: u16 = 2;
const BOX_H: u16 = 1;
@@ -455,6 +475,15 @@ fn deinitPanes(gpa: std.mem.Allocator, panes: []Pane) void {
}
}
+fn terminalScrollbackLimit(src: dump.Pane) usize {
+ if (!is_emscripten) return 16 * 1024 * 1024;
+ const cols: usize = @max(1, src.cols);
+ const rows: usize = @max(1, src.rows);
+ const visible_and_scrolled = rows + src.scroll + 8;
+ const estimated = visible_and_scrolled * cols * 128;
+ return std.math.clamp(estimated, 64 * 1024, 1024 * 1024);
+}
+
fn buildPane(gpa: std.mem.Allocator, src: dump.Pane, out: *Pane) !void {
out.* = .{
.kind = src.kind,
@@ -490,7 +519,7 @@ fn buildPane(gpa: std.mem.Allocator, src: dump.Pane, out: *Pane) !void {
.term = try ghostty_vt.Terminal.init(gpa, .{
.cols = @max(1, src.cols),
.rows = @max(1, src.rows),
- .max_scrollback = 16 * 1024 * 1024,
+ .max_scrollback = terminalScrollbackLimit(src),
}),
.stream = undefined,
};
@@ -518,97 +547,148 @@ fn buildPane(gpa: std.mem.Allocator, src: dump.Pane, out: *Pane) !void {
clampScroll(out);
}
-pub fn main(init: std.process.Init) !void {
- const io = init.io;
- const gpa = init.gpa;
- const args = try init.minimal.args.toSlice(init.arena.allocator());
- if (args.len != 2) {
- try printUsage(io);
- return error.BadArgs;
- }
+const App = struct {
+ io: std.Io,
+ gpa: std.mem.Allocator,
+ state: dump.State,
+ panes_buf: [dump.max_panes]Pane,
+ panes: []Pane,
+ layout: Layout,
+ screen_w: u16,
+ screen_h: u16,
+ active: usize,
+ tty_buf: [0x10000]u8,
+ tty: ui.Tty,
+ vx: ui.Vaxis,
+ loop: Loop,
+ rects: [dump.max_panes]Rect,
+ col_x: [dump.max_cols]u16,
+ col_w: [dump.max_cols]u16,
+ drag: Drag = .none,
- const state = try dump.readFile(io, gpa, args[1]);
- defer dump.free(gpa, state);
- _ = paint.setThemeByName(state.theme);
- image.start(io, gpa);
- defer image.stop();
+ fn init(
+ self: *App,
+ io: std.Io,
+ gpa: std.mem.Allocator,
+ env_map: *std.process.Environ.Map,
+ args: []const [:0]const u8,
+ ) !void {
+ if (!replay_config.embedded_dump and args.len != 2) {
+ try printUsage(io);
+ return error.BadArgs;
+ }
- var panes_buf: [dump.max_panes]Pane = undefined;
- for (state.panes, 0..) |src, i| try buildPane(gpa, src, &panes_buf[i]);
- var panes = panes_buf[0..state.panes.len];
- defer deinitPanes(gpa, panes);
+ self.io = io;
+ self.gpa = gpa;
+ self.state = if (replay_config.embedded_dump)
+ try dump.readZonZ(gpa, embedded_dump, "embedded replay dump")
+ else
+ try dump.readFile(io, gpa, args[1]);
+ _ = paint.setThemeByName(self.state.theme);
+ image.start(io, gpa);
- var layout: Layout = .{
- .ncol = state.columns.len,
- .col_weight = @splat(1),
- .col_terms = undefined,
- .col_n = @splat(0),
- };
- for (state.columns, 0..) |col, c| {
- layout.col_weight[c] = col.weight;
- layout.col_n[c] = col.panes.len;
- for (col.panes, 0..) |pane, k| layout.col_terms[c][k] = pane;
- }
+ for (self.state.panes, 0..) |src, i| {
+ try buildPane(gpa, src, &self.panes_buf[i]);
+ }
+ self.panes = self.panes_buf[0..self.state.panes.len];
+
+ self.layout = .{
+ .ncol = self.state.columns.len,
+ .col_weight = @splat(1),
+ .col_terms = undefined,
+ .col_n = @splat(0),
+ };
+ for (self.state.columns, 0..) |col, c| {
+ self.layout.col_weight[c] = col.weight;
+ self.layout.col_n[c] = col.panes.len;
+ for (col.panes, 0..) |pane, k| self.layout.col_terms[c][k] = pane;
+ }
+
+ self.screen_w = if (self.state.screen.cols > 0) self.state.screen.cols else 80;
+ self.screen_h = if (self.state.screen.rows > 0) self.state.screen.rows else 24;
+ self.active = self.state.active;
+ self.drag = .none;
+
+ self.tty = try ui.Tty.init(io, &self.tty_buf);
+ self.vx = try ui.init(io, gpa, env_map, .{});
+ try self.vx.setMouseMode(self.tty.writer(), true);
+ try self.vx.enterAltScreen(self.tty.writer());
+ if (is_emscripten) {
+ try self.vx.resize(gpa, self.tty.writer(), .{
+ .cols = self.screen_w,
+ .rows = self.screen_h,
+ .x_pixel = 0,
+ .y_pixel = 0,
+ });
+ }
- var screen_w: u16 = if (state.screen.cols > 0) state.screen.cols else 80;
- var screen_h: u16 = if (state.screen.rows > 0) state.screen.rows else 24;
- var active = state.active;
+ self.loop = .init(io, &self.tty, &self.vx);
+ try self.loop.start();
+ try self.loop.installResizeHandler();
- var tty_buf: [0x10000]u8 = undefined;
- var tty = try ui.Tty.init(io, &tty_buf);
- var vx = try ui.init(io, gpa, init.environ_map, .{});
- defer vx.deinit(gpa, tty.writer());
- try vx.setMouseMode(tty.writer(), true);
- try vx.enterAltScreen(tty.writer());
- defer vx.exitAltScreen(tty.writer()) catch {};
+ computeGeom(&self.layout, self.panes, self.screen_w, self.screen_h, &self.rects, &self.col_x, &self.col_w);
+ }
+
+ fn deinit(self: *App) void {
+ self.loop.stop();
+ self.vx.exitAltScreen(self.tty.writer()) catch {};
+ self.vx.deinit(self.gpa, self.tty.writer());
+ deinitPanes(self.gpa, self.panes);
+ image.stop();
+ dump.free(self.gpa, self.state);
+ }
- var loop: Loop = .init(io, &tty, &vx);
- try loop.start();
- defer loop.stop();
- try loop.installResizeHandler();
+ fn run(self: *App) !void {
+ while (try self.step(try self.loop.nextEvent())) {}
+ }
- var rects: [dump.max_panes]Rect = undefined;
- var col_x: [dump.max_cols]u16 = undefined;
- var col_w: [dump.max_cols]u16 = undefined;
- computeGeom(&layout, panes, screen_w, screen_h, &rects, &col_x, &col_w);
+ fn frame(self: *App) !bool {
+ var had_event = false;
+ while (try self.loop.tryEvent()) |event| {
+ had_event = true;
+ if (!try self.step(event)) return false;
+ }
+ if (!had_event) return try self.step(.tick);
+ return true;
+ }
- var drag: Drag = .none;
- while (true) {
- const event = try loop.nextEvent();
+ fn step(self: *App, event: @TypeOf(Command.value)) !bool {
switch (event) {
.winsize => |ws| {
- screen_w = ws.cols;
- screen_h = ws.rows;
- try vx.resize(gpa, tty.writer(), ws);
+ if (!is_emscripten) {
+ self.screen_w = ws.cols;
+ self.screen_h = ws.rows;
+ try self.vx.resize(self.gpa, self.tty.writer(), ws);
+ }
},
.mouse => |mouse| {
- const mcol: u16 = if (mouse.col < 0) 0 else @min(@as(u16, @intCast(mouse.col)), screen_w -| 1);
- const mrow: u16 = if (mouse.row < 0) 0 else @min(@as(u16, @intCast(mouse.row)), screen_h -| 1);
- const hovered = paneAt(&rects, panes.len, mcol, mrow);
+ const mcol: u16 = if (mouse.col < 0) 0 else @min(@as(u16, @intCast(mouse.col)), self.screen_w -| 1);
+ const mrow: u16 = if (mouse.row < 0) 0 else @min(@as(u16, @intCast(mouse.row)), self.screen_h -| 1);
+ const hovered = paneAt(&self.rects, self.panes.len, mcol, mrow);
if (mouse.button == .wheel_down and mouse.type == .press) {
- if (hovered) |id| scrollBy(&panes[id], 1);
+ if (hovered) |id| scrollBy(&self.panes[id], 1);
} else if (mouse.button == .wheel_up and mouse.type == .press) {
- if (hovered) |id| scrollBy(&panes[id], -1);
+ if (hovered) |id| scrollBy(&self.panes[id], -1);
} else if (mouse.type == .press and mouse.button == .left) {
var handled = false;
var c: usize = 0;
- while (c + 1 < layout.ncol and !handled) : (c += 1) {
- if (mcol == col_x[c] + col_w[c] -| 1) {
- drag = .{ .border_v = .{ .left_col = c, .cur_x = mcol } };
+ while (c + 1 < self.layout.ncol and !handled) : (c += 1) {
+ if (mcol == self.col_x[c] + self.col_w[c] -| 1) {
+ self.drag = .{ .border_v = .{ .left_col = c, .cur_x = mcol } };
handled = true;
}
}
if (!handled) {
var cc: usize = 0;
- outer: while (cc < layout.ncol) : (cc += 1) {
- if (mcol < col_x[cc] or mcol >= col_x[cc] + col_w[cc]) continue;
+ outer: while (cc < self.layout.ncol) : (cc += 1) {
+ if (mcol < self.col_x[cc] or mcol >= self.col_x[cc] + self.col_w[cc]) continue;
var k: usize = 0;
- while (k + 1 < layout.col_n[cc]) : (k += 1) {
- const id = layout.col_terms[cc][k];
- const r = rects[id];
+ while (k + 1 < self.layout.col_n[cc]) : (k += 1) {
+ const id = self.layout.col_terms[cc][k];
+ const r = self.rects[id];
if (mrow == r.y + r.h -| 1) {
- drag = .{ .border_h = .{ .col = cc, .top_idx = k, .cur_y = mrow } };
+ self.drag = .{ .border_h = .{ .col = cc, .top_idx = k, .cur_y = mrow } };
handled = true;
break :outer;
}
@@ -616,35 +696,35 @@ pub fn main(init: std.process.Init) !void {
}
}
if (!handled) if (hovered) |id| {
- active = id;
- const r = rects[id];
+ self.active = id;
+ const r = self.rects[id];
if (mcol < r.x + GUTTER and mrow < r.y + BOX_H) {
- drag = .{ .move = .{ .id = id, .cur_x = mcol, .cur_y = mrow } };
+ self.drag = .{ .move = .{ .id = id, .cur_x = mcol, .cur_y = mrow } };
} else if (mcol < r.x + GUTTER) {
const local: i32 = @as(i32, mrow) - @as(i32, r.y + BOX_H);
- scrollBy(&panes[id], -local);
+ scrollBy(&self.panes[id], -local);
}
};
} else if (mouse.type == .press) {
- if (hovered) |id| active = id;
+ if (hovered) |id| self.active = id;
} else if (mouse.type == .drag) {
- switch (drag) {
+ switch (self.drag) {
.border_v => |*d| {
const c = d.left_col;
- if (c + 1 < layout.ncol) {
- const lo = col_x[c] + MINW;
- const hi = col_x[c] +| col_w[c] +| col_w[c + 1] -| MINW;
+ if (c + 1 < self.layout.ncol) {
+ const lo = self.col_x[c] + MINW;
+ const hi = self.col_x[c] +| self.col_w[c] +| self.col_w[c + 1] -| MINW;
d.cur_x = if (lo <= hi) std.math.clamp(mcol, lo, hi) else mcol;
}
},
.border_h => |*d| {
const c = d.col;
const k = d.top_idx;
- if (k + 1 < layout.col_n[c]) {
- const a = layout.col_terms[c][k];
- const b = layout.col_terms[c][k + 1];
- const lo = rects[a].y + MINH;
- const hi = rects[a].y +| rects[a].h +| rects[b].h -| MINH;
+ if (k + 1 < self.layout.col_n[c]) {
+ const a = self.layout.col_terms[c][k];
+ const b = self.layout.col_terms[c][k + 1];
+ const lo = self.rects[a].y + MINH;
+ const hi = self.rects[a].y +| self.rects[a].h +| self.rects[b].h -| MINH;
d.cur_y = if (lo <= hi) std.math.clamp(mrow, lo, hi) else mrow;
}
},
@@ -655,58 +735,111 @@ pub fn main(init: std.process.Init) !void {
.none => {},
}
} else if (mouse.type == .release) {
- switch (drag) {
+ switch (self.drag) {
.border_v => |d| {
const c = d.left_col;
- if (c + 1 < layout.ncol) {
- const combined: f32 = @floatFromInt(col_w[c] + col_w[c + 1]);
- var nl: f32 = @floatFromInt((d.cur_x + 1) -| col_x[c]);
+ if (c + 1 < self.layout.ncol) {
+ const combined: f32 = @floatFromInt(self.col_w[c] + self.col_w[c + 1]);
+ var nl: f32 = @floatFromInt((d.cur_x + 1) -| self.col_x[c]);
nl = std.math.clamp(nl, @as(f32, MINW), @max(@as(f32, MINW), combined - @as(f32, MINW)));
- const pair = layout.col_weight[c] + layout.col_weight[c + 1];
- layout.col_weight[c] = pair * (nl / combined);
- layout.col_weight[c + 1] = pair - layout.col_weight[c];
+ const pair = self.layout.col_weight[c] + self.layout.col_weight[c + 1];
+ self.layout.col_weight[c] = pair * (nl / combined);
+ self.layout.col_weight[c + 1] = pair - self.layout.col_weight[c];
}
},
.border_h => |d| {
const c = d.col;
const k = d.top_idx;
- if (k + 1 < layout.col_n[c]) {
- const a = layout.col_terms[c][k];
- const b = layout.col_terms[c][k + 1];
- const combined: f32 = @floatFromInt(rects[a].h + rects[b].h);
- var nt: f32 = @floatFromInt((d.cur_y + 1) -| rects[a].y);
+ if (k + 1 < self.layout.col_n[c]) {
+ const a = self.layout.col_terms[c][k];
+ const b = self.layout.col_terms[c][k + 1];
+ const combined: f32 = @floatFromInt(self.rects[a].h + self.rects[b].h);
+ var nt: f32 = @floatFromInt((d.cur_y + 1) -| self.rects[a].y);
nt = std.math.clamp(nt, @as(f32, MINH), @max(@as(f32, MINH), combined - @as(f32, MINH)));
- const pair = panes[a].vweight + panes[b].vweight;
- panes[a].vweight = pair * (nt / combined);
- panes[b].vweight = pair - panes[a].vweight;
+ const pair = self.panes[a].vweight + self.panes[b].vweight;
+ self.panes[a].vweight = pair * (nt / combined);
+ self.panes[b].vweight = pair - self.panes[a].vweight;
}
},
- .move => |d| moveTerm(&layout, &rects, &col_x, &col_w, d.id, d.cur_x, d.cur_y),
+ .move => |d| moveTerm(&self.layout, &self.rects, &self.col_x, &self.col_w, d.id, d.cur_x, d.cur_y),
.none => {},
}
- drag = .none;
+ self.drag = .none;
}
},
.key_press => |key| {
- if (key.matches('q', .{}) or key.matches(ui.Key.escape, .{}) or key.matches('c', .{ .ctrl = true })) break;
+ if (key.matches('q', .{}) or key.matches(ui.Key.escape, .{}) or key.matches('c', .{ .ctrl = true })) return false;
},
- .paste => |text| gpa.free(text),
- .quit, .close => break,
+ .paste => |text| self.gpa.free(text),
+ .quit, .close => return false,
else => {},
}
- computeGeom(&layout, panes, screen_w, screen_h, &rects, &col_x, &col_w);
- for (panes, 0..) |*p, id| {
- const r = rects[id];
+ computeGeom(&self.layout, self.panes, self.screen_w, self.screen_h, &self.rects, &self.col_x, &self.col_w);
+ for (self.panes, 0..) |*p, id| {
+ const r = self.rects[id];
const cols = @max(1, r.w -| GUTTER);
const rows = @max(1, r.h -| BOX_H);
if (p.terminal) |*rt| {
- if (cols != p.cols or rows != p.rows) rt.term.resize(gpa, cols, rows) catch {};
+ if (cols != p.cols or rows != p.rows) rt.term.resize(self.gpa, cols, rows) catch {};
}
p.cols = cols;
p.rows = rows;
clampScroll(p);
}
- try render(&vx, gpa, tty.writer(), state, panes, &rects, screen_w, active);
+ try render(&self.vx, self.gpa, self.tty.writer(), self.state, self.panes, &self.rects, self.screen_w, self.active);
+ return true;
}
+};
+
+fn wasmFrame(arg: ?*anyopaque) callconv(.c) void {
+ const app: *App = @ptrCast(@alignCast(arg.?));
+ if (app.frame() catch |err| {
+ std.log.err("replay frame failed: {s}", .{@errorName(err)});
+ std.os.emscripten.emscripten_cancel_main_loop();
+ app.deinit();
+ app.gpa.destroy(app);
+ return;
+ }) return;
+ std.os.emscripten.emscripten_cancel_main_loop();
+ app.deinit();
+ app.gpa.destroy(app);
+}
+
+pub const main = if (is_emscripten) wasmMain else nativeMain;
+
+comptime {
+ if (is_emscripten) @export(&emscriptenMain, .{ .name = "main" });
+}
+
+fn nativeMain(init: std.process.Init) !void {
+ const args = try init.minimal.args.toSlice(init.arena.allocator());
+ var app: App = undefined;
+ try app.init(init.io, init.gpa, init.environ_map, args);
+ defer app.deinit();
+ try app.run();
+}
+
+fn emscriptenMain(argc: c_int, argv: [*]?[*:0]u8) callconv(.c) c_int {
+ _ = argc;
+ _ = argv;
+ wasmMain() catch |err| {
+ var buf: [128]u8 = undefined;
+ const msg = std.fmt.bufPrintZ(&buf, "pardes replay wasm init failed: {s}", .{@errorName(err)}) catch "pardes replay wasm init failed";
+ emscripten_console_error(msg.ptr);
+ return 1;
+ };
+ return 0;
+}
+
+fn wasmMain() !void {
+ wasm_gpa_fba = std.heap.FixedBufferAllocator.init(&wasm_gpa_buffer);
+ const gpa = wasm_gpa_fba.allocator();
+ var env_map = std.process.Environ.Map.init(gpa);
+ defer env_map.deinit();
+
+ const app = try gpa.create(App);
+ errdefer gpa.destroy(app);
+ try app.init(std.Io.failing, gpa, &env_map, &.{});
+ std.os.emscripten.emscripten_set_main_loop_arg(wasmFrame, app, 0, 0);
}