From 60367d8fe23f6af98ec28e3cf6c2094dfe332df0 Mon Sep 17 00:00:00 2001 From: Gabriel Schneider Date: Sun, 6 Sep 2026 18:11:36 -0300 Subject: Refactor panes and filesystem; replace FUSE with 9P Consolidate pane, layout, memory and host code. Serve 9P by default over Unix sockets, with runtime mounts and optional TCP/QUIC transports. Remove FUSE and obsolete proof-of-concept examples. Fix highlighting and terminal-history performance, expand differential and stress-test infrastructure, sort navigation results while preserving the next occurrence, add syntax-colored Braille minimaps, remove SPC-k, and document 9P interaction as a repository skill. --- src/image_pane.zig | 262 ----------------------------------------------------- 1 file changed, 262 deletions(-) delete mode 100644 src/image_pane.zig (limited to 'src/image_pane.zig') diff --git a/src/image_pane.zig b/src/image_pane.zig deleted file mode 100644 index 71ef2228..00000000 --- a/src/image_pane.zig +++ /dev/null @@ -1,262 +0,0 @@ -//! Image panes: their owned pixels, PETSCII cache, lifecycle, and two render -//! paths. Core layout supplies the body rectangle; this module fills it or -//! attaches one native image to it. -const std = @import("std"); -const pardes = @import("pardes.zig"); -const image = @import("image.zig"); -const look = @import("look.zig"); -const dump = @import("dump.zig"); -const config = @import("config.zig"); - -const GridKey = struct { - cols: u16 = 0, - rows: u16 = 0, - palette: image.PaletteMode = .commodore, - ascii: bool = true, -}; - -/// The decoded, downscaled RGBA is retained so changing the pane size or one -/// of the glyph-renderer toggles can rebuild the PETSCII projection without -/// decoding the source again. All slices are owned by Pardes.image_gpa. -pub const State = struct { - path: []u8, - petscii: bool = false, - pmode: image.PaletteMode = .commodore, - ascii: bool = true, - tried: bool = false, - rgba: []u8 = &.{}, - iw: usize = 0, - ih: usize = 0, - /// Dump-loaded bytes, decoded lazily by the same path as a disk image. - raw: []u8 = &.{}, - grid: image.petscii.Grid = .{ .cells = &.{}, .gw = 0, .gh = 0 }, - grid_key: GridKey = .{}, - - pub fn deinit(state: *State, gpa: std.mem.Allocator) void { - gpa.free(state.path); - if (state.rgba.len > 0) gpa.free(state.rgba); - if (state.raw.len > 0) gpa.free(state.raw); - if (state.grid.cells.len > 0) gpa.free(state.grid.cells); - state.* = undefined; - } -}; - -/// Construct an image pane from a path and optionally transferred dump bytes. -/// `raw` must be image_gpa-owned and ownership transfers only on success. -pub fn create(p: *pardes.Pardes, id: usize, path: []const u8, raw: []u8) !*pardes.Pane { - const path_copy = try p.image_gpa.dupe(u8, path); - errdefer p.image_gpa.free(path_copy); - const pane = try p.newDocPane(id); - pane.image = .{ .path = path_copy, .raw = raw }; - return pane; -} - -/// Serialize the binary image record used by images and, for dump-schema -/// compatibility, PDFs. Common pane metadata is supplied by the core. -pub fn dumpPane( - arena: std.mem.Allocator, - pane: *const pardes.Pane, - tag: []const u8, - body: []const u8, - scroll: usize, - path: []const u8, - raw: []const u8, -) !dump.Pane { - const bytes = if (raw.len > 0) raw else look.readFile(arena, path) catch ""; - return .{ - .kind = .image, - .tag = tag, - .body = body, - .scroll = scroll, - .cols = pane.cols, - .rows = pane.rows, - .vweight = pane.vweight, - .image = .{ - .path = path, - .bytes_b64 = if (bytes.len > 0) try dump.encodeBytes(arena, bytes) else "", - .petscii = if (pane.image) |state| state.petscii else false, - .palette = if (pane.image) |state| switch (state.pmode) { - .commodore => .commodore, - .terminal => .terminal, - } else .commodore, - .ascii = if (pane.image) |state| state.ascii else true, - }, - }; -} - -/// Rebuild an ordinary raster image from its dump record. PDF records share -/// the schema for compatibility and are deliberately dispatched by the core -/// before reaching this function. -pub fn restore(p: *pardes.Pardes, id: usize, src: dump.Pane) !*pardes.Pane { - const saved = src.image.?; - var raw: []u8 = if (saved.bytes_b64.len > 0) - try dump.decodeBytes(p.image_gpa, saved.bytes_b64) - else - &.{}; - errdefer if (raw.len > 0) p.image_gpa.free(raw); - const pane = try create(p, id, saved.path, raw); - raw = &.{}; - pane.image.?.petscii = saved.petscii; - pane.image.?.pmode = switch (saved.palette) { - .commodore => .commodore, - .terminal => .terminal, - }; - pane.image.?.ascii = saved.ascii; - pane.cols = @max(1, src.cols); - pane.rows = @max(1, src.rows); - return pane; -} - -pub fn togglePetscii(state: *State) void { - state.petscii = !state.petscii; -} - -pub fn togglePalette(state: *State) void { - state.pmode = if (state.pmode == .commodore) .terminal else .commodore; -} - -pub fn toggleAscii(state: *State) void { - state.ascii = !state.ascii; -} - -/// Image renderer choices are pane-local, not global Config values. Keep them -/// queryable where they apply: the live tag beside the image's path. -pub fn tagPrefix(arena: std.mem.Allocator, state: *const State) ![]u8 { - return std.fmt.allocPrint( - arena, - "{s} petscii:{s} palette:{s} ascii:{s} {s}", - .{ - config.tag_image, - if (state.petscii) "on" else "off", - @tagName(state.pmode), - if (state.ascii) "on" else "off", - state.path, - }, - ); -} - -/// Prefix written by version-1 dumps before renderer choices became visible -/// in the live tag. Returned as a borrowed slice of saved_tag for one-time -/// custom-tail migration. -pub fn legacySavedPrefix(state: *const State, saved_tag: []const u8) ?[]const u8 { - const lead = config.tag_image ++ " "; - if (!std.mem.startsWith(u8, saved_tag, lead)) return null; - const path_at = lead.len; - if (!std.mem.startsWith(u8, saved_tag[path_at..], state.path)) return null; - return saved_tag[0 .. path_at + state.path.len]; -} - -fn ensureDecoded(p: *pardes.Pardes, state: *State) void { - if (state.tried) return; - state.tried = true; - const bytes: []const u8 = if (state.raw.len > 0) - state.raw - else - look.readFile(p.scratch.allocator(), state.path) catch ""; - if (image.decode(p.image_gpa, bytes)) |decoded| { - state.rgba = decoded.rgba; - state.iw = decoded.w; - state.ih = decoded.h; - } -} - -fn ensureGrid(p: *pardes.Pardes, state: *State, cols: u16, rows: u16) void { - const wanted = GridKey{ .cols = cols, .rows = rows, .palette = state.pmode, .ascii = state.ascii }; - if (state.grid.cells.len > 0 and std.meta.eql(state.grid_key, wanted)) return; - if (state.grid.cells.len > 0) p.image_gpa.free(state.grid.cells); - const palette = switch (state.pmode) { - .commodore => image.petscii.commodore, - .terminal => image.ansiPalette(), - }; - state.grid = image.petscii.render( - p.image_gpa, - state.rgba, - state.iw, - state.ih, - cols, - rows, - palette, - state.ascii, - ) catch .{ .cells = &.{}, .gw = 0, .gh = 0 }; - state.grid_key = wanted; -} - -/// Render the image body supplied by core layout. Native-capable hosts receive -/// a pixel attachment; other hosts and explicit PETSCII mode receive cells. -pub fn draw( - p: *pardes.Pardes, - state: *State, - pane_id: u8, - serial: u32, - x: u16, - y: u16, - cols: u16, - rows: u16, -) void { - ensureDecoded(p, state); - if (state.rgba.len == 0 or cols == 0 or rows == 0) return; - if (!state.petscii and p.native_images) { - _ = p.appendImagePlace(.{ - .pane = pane_id, - .serial = serial, - .x = x, - .y = y, - .w = cols, - .h = rows, - .rgba = state.rgba, - .iw = state.iw, - .ih = state.ih, - }); - return; - } - - ensureGrid(p, state, cols, rows); - if (state.grid.cells.len == 0) return; - const offx = if (cols > state.grid.gw) (@as(usize, cols) - state.grid.gw) / 2 else 0; - const offy = if (rows > state.grid.gh) (@as(usize, rows) - state.grid.gh) / 2 else 0; - for (0..state.grid.gh) |cy| for (0..state.grid.gw) |cx| { - const cell = &state.grid.cells[cy * state.grid.gw + cx]; - const fg: pardes.Color = switch (state.pmode) { - .commodore => .{ .rgb = image.petscii.commodore[cell.fg] }, - .terminal => .{ .index = cell.fg }, - }; - const bg: pardes.Color = switch (state.pmode) { - .commodore => .{ .rgb = image.petscii.commodore[cell.bg] }, - .terminal => .{ .index = cell.bg }, - }; - const sx = x + @as(u16, @intCast(offx + cx)); - const sy = y + @as(u16, @intCast(offy + cy)); - if (sx < p.surface.cols and sy < p.surface.rows) - p.surface.set(sx, sy, cell.glyph[0..cell.glen], .{ .fg = fg, .bg = bg }); - }; -} - -test "pane-local renderer choices are visible in the image tag" { - var state: State = .{ .path = @constCast("/tmp/picture.ppm") }; - togglePetscii(&state); - togglePalette(&state); - toggleAscii(&state); - const tag = try tagPrefix(std.testing.allocator, &state); - defer std.testing.allocator.free(tag); - try std.testing.expectEqualStrings( - config.tag_image ++ " petscii:on palette:terminal ascii:off /tmp/picture.ppm", - tag, - ); - try std.testing.expectEqualStrings( - "img /tmp/picture.ppm", - legacySavedPrefix(&state, "img /tmp/picture.ppm Keep Del").?, - ); -} - -test "dump restore propagates malformed embedded image bytes" { - const p = try pardes.Pardes.init(std.testing.allocator, .{ .tty_only = true }); - defer p.deinit(); - try std.testing.expect(p.panes[1] == null); - try std.testing.expectError(error.InvalidCharacter, restore(p, 1, .{ - .kind = .image, - .tag = "img /tmp/bad.ppm", - .body = "", - .image = .{ .path = "/tmp/bad.ppm", .bytes_b64 = "A..A" }, - })); - try std.testing.expect(p.panes[1] == null); -} -- cgit v1.3