summaryrefslogtreecommitdiff
path: root/replay.zig
diff options
context:
space:
mode:
Diffstat (limited to 'replay.zig')
-rw-r--r--replay.zig786
1 files changed, 0 insertions, 786 deletions
diff --git a/replay.zig b/replay.zig
deleted file mode 100644
index 4add78f0..00000000
--- a/replay.zig
+++ /dev/null
@@ -1,786 +0,0 @@
-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 pane_ui = @import("pane_ui.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 = pane_ui.TOPBAR_H;
-const GUTTER: u16 = pane_ui.GUTTER;
-const BOX_H: u16 = pane_ui.BOX_H;
-const MINW: u16 = pane_ui.MINW;
-const MINH: u16 = pane_ui.MINH;
-const PREFIX_W: u16 = pane_ui.PREFIX_W;
-
-const Rect = pane_ui.Rect;
-
-const ReplayTerm = struct {
- term: ghostty_vt.Terminal,
- stream: ghostty_vt.TerminalStream,
-};
-
-const Pane = struct {
- kind: dump.PaneKind,
- tag: []const u8,
- content: []const u8,
- owned_content: ?[]u8 = null,
- path: []const u8,
- terminal: ?ReplayTerm = null,
- image_view: ?image.View = null,
- image_bytes: []const u8 = &.{},
- tty_mode: bool = false,
- highlights: []u8 = &.{},
- scroll: usize,
- cols: u16,
- rows: u16,
- vweight: f32,
-
- fn isTerminal(self: *const Pane) bool {
- return self.terminal != null;
- }
-};
-
-const Layout = pane_ui.Layout(dump.max_cols, dump.max_panes);
-const moveTerm = pane_ui.moveTerm;
-
-const Drag = union(enum) {
- none,
- border_v: struct { left_col: usize, cur_x: u16 },
- border_h: struct { col: usize, top_idx: usize, cur_y: u16 },
- move: struct { id: usize, cur_x: u16, cur_y: u16 },
-};
-
-pub const Command = struct {
- prev: ?*Command = null,
- pub var value: union(enum) {
- close,
- nop,
- quit,
- tick,
- key_press: ui.Key,
- winsize: ui.Winsize,
- mouse: ui.Mouse,
- paste: []const u8,
- pinch: f32,
- touch_scroll_delta: f32,
- } = .nop;
-};
-
-const Loop = ui.Loop(@TypeOf(Command.value));
-
-const accent = paint.accent;
-const accent_dim = paint.accent_dim;
-const T = paint.T;
-const rgbColor = paint.rgbColor;
-const colorOpt = paint.colorOpt;
-const synStyle = paint.synStyle;
-const highlightFile = paint.highlightFile;
-const cellStyle = paint.cellStyle;
-
-const SelectAction = enum { none, colors, nextcolor };
-const Builtin = struct { name: []const u8, action: SelectAction };
-const builtins = [_]Builtin{
- .{ .name = "Colors", .action = .colors },
- .{ .name = "NextColor", .action = .nextcolor },
-};
-
-fn topbarText(state: dump.State) []const u8 {
- return if (state.topbar.len > 0) state.topbar else "pardes replay";
-}
-
-fn wordAtCol(str: []const u8, col: u16) []const u8 {
- const idx: usize = col;
- if (idx >= str.len or str[idx] == ' ') return "";
- var lo: usize = idx;
- while (lo > 0 and str[lo - 1] != ' ') lo -= 1;
- var hi: usize = idx;
- while (hi < str.len and str[hi] != ' ') hi += 1;
- return str[lo..hi];
-}
-
-fn onSelect(text: []const u8) SelectAction {
- const cmd = std.mem.trim(u8, text, " \t\r\n");
- if (cmd.len == 0) return .none;
- inline for (builtins) |b| {
- if (std.mem.eql(u8, cmd, b.name)) return b.action;
- }
- return .none;
-}
-
-fn printUsage(io: std.Io) !void {
- try std.Io.File.stdout().writeStreamingAll(io, "Usage: pardes-replay <dump.zon>\n");
-}
-
-fn lineCount(text: []const u8) usize {
- return std.mem.count(u8, text, "\n") + 1;
-}
-
-fn lineAt(text: []const u8, row: usize) []const u8 {
- var it = std.mem.splitScalar(u8, text, '\n');
- var i: usize = 0;
- while (it.next()) |line| : (i += 1) {
- if (i == row) return line;
- }
- return "";
-}
-
-fn clampScroll(p: *Pane) void {
- if (p.isTerminal()) return;
- const total = lineCount(p.content);
- const max_scroll = total -| @max(1, p.rows);
- if (p.scroll > max_scroll) p.scroll = max_scroll;
-}
-
-fn scrollBy(p: *Pane, delta: i32) void {
- if (p.terminal) |*rt| {
- rt.term.screens.active.scroll(.{ .delta_row = @intCast(delta) });
- return;
- }
- const total = lineCount(p.content);
- const max_scroll: i64 = @intCast(total -| @max(1, p.rows));
- const next = std.math.clamp(@as(i64, @intCast(p.scroll)) + delta, 0, max_scroll);
- p.scroll = @intCast(next);
-}
-
-fn promptFlags(rt: *ReplayTerm, flags: []bool) void {
- @memset(flags, false);
- var it = rt.term.screens.active.pages.rowIterator(.right_down, .{ .viewport = .{} }, null);
- var i: usize = 0;
- while (it.next()) |pin| : (i += 1) {
- if (i >= flags.len) break;
- flags[i] = pin.rowAndCell().row.semantic_prompt != .none;
- }
-}
-
-fn terminalBodyText(alloc: std.mem.Allocator, p: *Pane) ![]const u8 {
- const rt = &p.terminal.?;
- const body = try rt.term.plainString(alloc);
- var flagbuf: [512]bool = undefined;
- const nrows: usize = @min(p.rows, flagbuf.len);
- const flags = flagbuf[0..nrows];
- if (p.tty_mode) @memset(flags, false) else promptFlags(rt, flags);
-
- var out: std.ArrayList(u8) = .empty;
- errdefer out.deinit(alloc);
- var it = std.mem.splitAny(u8, body, "\n");
- var i: usize = 0;
- while (i < nrows) : (i += 1) {
- if (i > 0) try out.append(alloc, '\n');
- const raw = it.next() orelse "";
- try out.appendSlice(alloc, if (flags[i]) "" else raw);
- }
- return out.toOwnedSlice(alloc);
-}
-
-fn paneVWeight(panes: []const Pane, id: usize) ?f32 {
- return panes[id].vweight;
-}
-
-fn setPaneVWeight(panes: []Pane, id: usize, weight: f32) void {
- panes[id].vweight = weight;
-}
-
-fn renderPane(alloc: std.mem.Allocator, root: ui.Window, p: *Pane, r: Rect, active: bool, colors_on: bool) !void {
- if (r.w <= GUTTER or r.h == 0) return;
- const text = root.child(.{ .x_off = r.x + GUTTER, .y_off = r.y, .width = r.w - GUTTER, .height = r.h });
- text.clear();
- if (T().bg) |bg| text.fill(.{ .char = .{ .grapheme = " " }, .style = .{ .bg = rgbColor(bg) } });
-
- const tagw = text.child(.{ .height = BOX_H });
- tagw.fill(.{ .char = .{ .grapheme = " " }, .style = .{ .bg = rgbColor(T().tag_bg) } });
- _ = tagw.printSegment(.{ .text = p.tag, .style = .{ .fg = rgbColor(T().tag_fg), .bg = rgbColor(T().tag_bg) } }, .{ .wrap = .none });
-
- const body_h = if (r.h > BOX_H) r.h - BOX_H else 0;
- const body_w = r.w - GUTTER;
- const body_style: ui.Style = .{ .fg = colorOpt(T().fg), .bg = colorOpt(T().bg) };
-
- if (p.image_view) |*iv| {
- if (body_h > 0) image.draw(iv, text.child(.{ .y_off = BOX_H, .height = body_h }), body_w, body_h);
- pane_ui.drawGutter(root, r, active, accent, accent_dim, T().scroll_track, T().scroll_thumb, null);
- return;
- }
-
- if (p.terminal != null) {
- const body = try terminalBodyText(alloc, p);
- var it = std.mem.splitAny(u8, body, "\n");
- var vr: u16 = 0;
- while (it.next()) |line| : (vr += 1) {
- if (vr >= body_h) break;
- _ = text.printSegment(.{ .text = line, .style = body_style }, .{ .row_offset = BOX_H + vr, .wrap = .none });
- }
-
- if (colors_on) {
- const rt = &p.terminal.?;
- vr = 0;
- while (vr < body_h and vr < p.rows) : (vr += 1) {
- var c: u16 = 0;
- while (c < body_w) : (c += 1) {
- const ci = rt.term.screens.active.pages.getCell(.{ .viewport = .{ .x = @intCast(c), .y = @intCast(vr) } }) orelse continue;
- if (ci.cell.wide == .spacer_tail) continue;
- if (text.readCell(c, BOX_H + vr)) |cell| {
- var nc = cell;
- nc.default = false;
- nc.style = cellStyle(ci);
- text.writeCell(c, BOX_H + vr, nc);
- }
- }
- }
- }
- } else if (p.kind == .file) {
- var vr: u16 = 0;
- while (vr < body_h) : (vr += 1) {
- const abs = p.scroll + vr;
- const prefix = try std.fmt.allocPrint(alloc, "{d: >4} ", .{abs + 1});
- _ = text.printSegment(.{ .text = prefix, .style = body_style }, .{ .row_offset = BOX_H + vr, .wrap = .none });
- _ = text.printSegment(.{ .text = lineAt(p.content, abs), .style = body_style }, .{ .row_offset = BOX_H + vr, .col_offset = PREFIX_W, .wrap = .none });
- }
-
- const ln_fg = rgbColor(T().lineno);
- vr = 0;
- while (vr < body_h) : (vr += 1) {
- var c: u16 = 0;
- while (c < PREFIX_W and c < body_w) : (c += 1) {
- if (text.readCell(c, BOX_H + vr)) |cell| {
- var nc = cell;
- nc.default = false;
- nc.style.fg = ln_fg;
- text.writeCell(c, BOX_H + vr, nc);
- }
- }
- }
-
- if (colors_on and p.highlights.len > 0) {
- var lines = std.mem.splitScalar(u8, p.content, '\n');
- var skip: usize = 0;
- while (skip < p.scroll) : (skip += 1) if (lines.next() == null) break;
- vr = 0;
- while (vr < body_h) : (vr += 1) {
- const line = lines.next() orelse break;
- const base = @intFromPtr(line.ptr) - @intFromPtr(p.content.ptr);
- var c: usize = 0;
- while (c < line.len and PREFIX_W + c < body_w and base + c < p.highlights.len) : (c += 1) {
- const ss = synStyle(@enumFromInt(p.highlights[base + c])) orelse continue;
- const cx: u16 = @intCast(PREFIX_W + c);
- if (text.readCell(cx, BOX_H + vr)) |cell| {
- var nc = cell;
- nc.default = false;
- nc.style.fg = rgbColor(ss.fg);
- nc.style.bold = ss.bold;
- text.writeCell(cx, BOX_H + vr, nc);
- }
- }
- }
- }
- } else {
- var vr: u16 = 0;
- while (vr < body_h) : (vr += 1) {
- const abs = p.scroll + vr;
- _ = text.printSegment(.{ .text = lineAt(p.content, abs), .style = body_style }, .{ .row_offset = BOX_H + vr, .wrap = .none });
- }
- }
-
- const scrollbar: pane_ui.Scrollbar = if (p.terminal) |*rt| blk: {
- const sb = rt.term.screens.active.pages.scrollbar();
- break :blk .{ .total = sb.total, .offset = sb.offset, .len = sb.len };
- } else .{
- .total = lineCount(p.content),
- .offset = p.scroll,
- .len = p.rows,
- };
- pane_ui.drawGutter(root, r, active, accent, accent_dim, T().scroll_track, T().scroll_thumb, scrollbar);
-}
-
-fn render(
- vx: *ui.Vaxis,
- gpa: std.mem.Allocator,
- writer: *std.Io.Writer,
- state: dump.State,
- panes: []Pane,
- rects: *[dump.max_panes]Rect,
- layout: *const Layout,
- col_x: *const [dump.max_cols]u16,
- col_w: *const [dump.max_cols]u16,
- screen_w: u16,
- screen_h: u16,
- active: usize,
- drag: Drag,
- hover_col: u16,
- hover_row: u16,
- ov_pinch_scale: f32,
- ov_touch_scroll_delta: f32,
- colors_on: bool,
-) !void {
- var arena: std.heap.ArenaAllocator = .init(gpa);
- defer arena.deinit();
- const alloc = arena.allocator();
-
- const win = vx.window();
- win.clear();
- win.fill(.{ .char = .{ .grapheme = " " }, .style = .{ .bg = rgbColor(T().scroll_track) } });
-
- for (panes, 0..) |*p, id| {
- if (p.image_view) |*iv| {
- image.ensureLoadedFromMemory(iv, p.image_bytes, vx, gpa, writer);
- image.ensureGrid(iv, gpa, p.cols, p.rows);
- }
- try renderPane(alloc, win, p, rects[id], id == active, colors_on);
- }
-
- const bar = win.child(.{ .x_off = 0, .y_off = 0, .width = screen_w, .height = TOPBAR_H });
- bar.fill(.{ .char = .{ .grapheme = " " }, .style = .{ .bg = rgbColor(T().tag_bg) } });
- const topbar = topbarText(state);
- _ = bar.printSegment(.{ .text = topbar, .style = .{ .fg = rgbColor(T().tag_fg), .bg = rgbColor(T().tag_bg) } }, .{ .wrap = .none });
-
- pane_ui.drawInteractionPreview(win, layout, rects, col_x, col_w, screen_h, drag, hover_col, hover_row, T().scroll_thumb);
-
- // debug overlay with gesture telemetry
- if (ov_pinch_scale != 1.0 or ov_touch_scroll_delta != 0.0) {
- var ov_buf: [96]u8 = undefined;
- const s = std.fmt.bufPrint(&ov_buf, "pinch {d:6.4}\ntscroll {d:7.4}", .{ ov_pinch_scale, ov_touch_scroll_delta }) catch "";
- if (s.len > 0) {
- const ow: u16 = 18;
- const oh: u16 = 3;
- const ox: u16 = if (screen_w > ow) screen_w - ow else 0;
- const ov = win.child(.{ .x_off = ox, .y_off = TOPBAR_H, .width = ow, .height = oh, .border = .{ .where = .all } });
- ov.fill(.{ .char = .{ .grapheme = " " }, .style = .{ .bg = rgbColor(T().scroll_track) } });
- _ = ov.printSegment(.{ .text = s, .style = .{ .fg = rgbColor(accent) } }, .{});
- }
- }
-
- try vx.render(writer);
-}
-
-fn deinitPanes(gpa: std.mem.Allocator, panes: []Pane) void {
- for (panes) |*p| {
- if (p.terminal) |*rt| {
- rt.stream.deinit();
- rt.term.deinit(gpa);
- }
- if (p.image_view) |*iv| image.deinitView(iv, gpa);
- if (p.highlights.len > 0) gpa.free(p.highlights);
- if (p.owned_content) |bytes| gpa.free(bytes);
- }
-}
-
-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,
- .tag = src.tag,
- .content = src.body,
- .path = "",
- .tty_mode = std.mem.startsWith(u8, src.tag, "sy "),
- .scroll = src.scroll,
- .cols = src.cols,
- .rows = src.rows,
- .vweight = src.vweight,
- };
-
- switch (src.kind) {
- .file => if (src.file) |f| {
- out.path = f.path;
- if (f.content_b64.len > 0) {
- const bytes = try dump.decodeBytes(gpa, f.content_b64);
- out.owned_content = bytes;
- out.content = bytes;
- } else {
- out.content = f.content;
- }
- out.highlights = highlightFile(gpa, out.path, out.content) catch &.{};
- },
- .terminal => if (src.terminal) |t| {
- out.path = t.cwd;
- if (t.stream_b64.len > 0) {
- const bytes = try dump.decodeBytes(gpa, t.stream_b64);
- out.owned_content = bytes;
- out.content = bytes;
- out.terminal = .{
- .term = try ghostty_vt.Terminal.init(gpa, .{
- .cols = @max(1, src.cols),
- .rows = @max(1, src.rows),
- .max_scrollback = terminalScrollbackLimit(src),
- }),
- .stream = undefined,
- };
- if (out.terminal) |*rt| {
- rt.stream = rt.term.vtStream();
- rt.stream.nextSlice(bytes);
- rt.term.screens.active.scroll(.active);
- if (src.scroll > 0) rt.term.screens.active.scroll(.{ .delta_row = -@as(isize, @intCast(src.scroll)) });
- }
- } else {
- out.content = if (t.stream.len > 0) t.stream else src.body;
- }
- },
- .image => if (src.image) |im| {
- out.path = im.path;
- out.content = src.body;
- if (im.bytes_b64.len > 0) {
- const bytes = try dump.decodeBytes(gpa, im.bytes_b64);
- out.owned_content = bytes;
- out.image_bytes = bytes;
- }
- out.image_view = .{ .path = im.path };
- },
- }
- clampScroll(out);
-}
-
-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,
- hover_col: u16 = 0,
- hover_row: u16 = 0,
- ov_pinch_scale: f32 = 1.0,
- ov_touch_scroll_delta: f32 = 0.0,
- colors_on: bool = true,
-
- 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;
- }
-
- 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);
-
- 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.hover_col = 0;
- self.hover_row = 0;
- self.ov_touch_scroll_delta = 0.0;
- self.colors_on = true;
-
- 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,
- });
- }
-
- self.loop = .init(io, &self.tty, &self.vx);
- try self.loop.start();
- try self.loop.installResizeHandler();
-
- pane_ui.computeGeom(&self.layout, self.panes, self.screen_w, self.screen_h, &self.rects, &self.col_x, &self.col_w, paneVWeight);
- }
-
- 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);
- }
-
- fn run(self: *App) !void {
- while (try self.step(try self.loop.nextEvent())) {}
- }
-
- 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;
- }
-
- fn step(self: *App, event: @TypeOf(Command.value)) !bool {
- switch (event) {
- .winsize => |ws| {
- self.screen_w = ws.cols;
- self.screen_h = ws.rows;
- try self.vx.resize(self.gpa, self.tty.writer(), ws);
- },
- .mouse => |mouse| {
- const mpos = pane_ui.clampMousePos(self.screen_w, self.screen_h, mouse);
- const mcol = mpos.col;
- const mrow = mpos.row;
- self.hover_col = mcol;
- self.hover_row = mrow;
- const hovered = pane_ui.paneAt(&self.rects, self.panes.len, mcol, mrow);
-
- if (mouse.button == .wheel_down and mouse.type == .press) {
- if (hovered) |id| scrollBy(&self.panes[id], 1);
- } else if (mouse.button == .wheel_up and mouse.type == .press) {
- if (hovered) |id| scrollBy(&self.panes[id], -1);
- } else if (mouse.type == .press and mouse.button == .middle and mrow < TOPBAR_H) {
- self.actOnTopbarWord(mcol);
- } else if (mouse.type == .press and mouse.button == .left) {
- var handled = false;
- var c: usize = 0;
- 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 < 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 < 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) {
- self.drag = .{ .border_h = .{ .col = cc, .top_idx = k, .cur_y = mrow } };
- handled = true;
- break :outer;
- }
- }
- }
- }
- if (!handled) if (hovered) |id| {
- self.active = id;
- const r = self.rects[id];
- if (pane_ui.inMoveBox(r, mcol, mrow)) {
- self.drag = .{ .move = .{ .id = id, .cur_x = mcol, .cur_y = mrow } };
- } else if (pane_ui.scrollbarClickDelta(r, .left, mcol, mrow)) |delta| {
- scrollBy(&self.panes[id], delta);
- }
- };
- } else if (mouse.type == .press and (mouse.button == .middle or mouse.button == .right)) {
- if (hovered) |id| {
- const r = self.rects[id];
- const sb_button: pane_ui.ScrollbarButton = if (mouse.button == .right) .right else .middle;
- if (pane_ui.scrollbarClickDelta(r, sb_button, mcol, mrow)) |delta| {
- if (mouse.button == .right) self.active = id;
- scrollBy(&self.panes[id], delta);
- } else {
- self.active = id;
- }
- }
- } else if (mouse.type == .press) {
- if (hovered) |id| self.active = id;
- } else if (mouse.type == .drag) {
- switch (self.drag) {
- .border_v => |*d| {
- const c = d.left_col;
- 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 < 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;
- }
- },
- .move => |*d| {
- d.cur_x = mcol;
- d.cur_y = mrow;
- },
- .none => {},
- }
- } else if (mouse.type == .release) {
- switch (self.drag) {
- .border_v => |d| {
- const c = d.left_col;
- 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 = 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 < 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 = 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(&self.layout, self.panes, &self.rects, &self.col_x, &self.col_w, d.id, d.cur_x, d.cur_y, setPaneVWeight),
- .none => {},
- }
- self.drag = .none;
- }
- },
- .key_press => |key| {
- if (key.matches('q', .{}) or key.matches(ui.Key.escape, .{}) or key.matches('c', .{ .ctrl = true })) return false;
- if (key.matches(ui.Key.tab, .{}) and self.hover_row < TOPBAR_H) self.actOnTopbarWord(self.hover_col);
- },
- .paste => |text| self.gpa.free(text),
- .pinch => |scale| self.ov_pinch_scale = scale,
- .touch_scroll_delta => |delta| self.ov_touch_scroll_delta = delta,
- .quit, .close => return false,
- else => {},
- }
-
- pane_ui.computeGeom(&self.layout, self.panes, self.screen_w, self.screen_h, &self.rects, &self.col_x, &self.col_w, paneVWeight);
- 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(self.gpa, cols, rows) catch {};
- }
- p.cols = cols;
- p.rows = rows;
- clampScroll(p);
- }
- try render(&self.vx, self.gpa, self.tty.writer(), self.state, self.panes, &self.rects, &self.layout, &self.col_x, &self.col_w, self.screen_w, self.screen_h, self.active, self.drag, self.hover_col, self.hover_row, self.ov_pinch_scale, self.ov_touch_scroll_delta, self.colors_on);
- return true;
- }
-
- fn actOnTopbarWord(self: *App, col: u16) void {
- switch (onSelect(wordAtCol(topbarText(self.state), col))) {
- .none => {},
- .colors => self.colors_on = !self.colors_on,
- .nextcolor => paint.nextTheme(),
- }
- }
-};
-
-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);
-}