diff options
| author | Gabriel Schneider <[email protected]> | 2026-07-05 20:21:50 -0300 |
|---|---|---|
| committer | Gabriel Schneider <[email protected]> | 2026-08-01 15:02:07 -0300 |
| commit | de4def3548a6729b0dfd2120495a61beef8c8c2c (patch) | |
| tree | 12144f0f64dc96bb4741459c1f9db57f44349330 /replay.zig | |
| parent | 7988d9bc6e31210ff13994f18d5622dd57ba9341 (diff) | |
| download | pardes-de4def3548a6729b0dfd2120495a61beef8c8c2c.tar.gz pardes-de4def3548a6729b0dfd2120495a61beef8c8c2c.zip | |
pardes v2: the rewrite, complete and organized. src/ (core + three shells), test/ (snapshot parity harness + 18 frozen goldens). One sans-IO core, vaxis tty + SDL3 GPU native + wasm web shells, 18/18 parity with the purged prototype, 7.6k lines vs 12.1k. Fix: gui shell pre-sized the core at init so the greet-releasing resize never fired (blank panes until first interaction); live sessions now init at defaults and get the real grid as a resize event (the shell contract, documented on Options).
Diffstat (limited to 'replay.zig')
| -rw-r--r-- | replay.zig | 786 |
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); -} |
