diff options
| author | Gabriel Schneider <[email protected]> | 2026-08-10 17:23:18 -0300 |
|---|---|---|
| committer | Gabriel Schneider <[email protected]> | 2026-08-11 09:58:59 -0300 |
| commit | a797a1ab2f648e773f7a1b28d12bf9441b9f13f4 (patch) | |
| tree | 45db37a3d190425b3ca2453d46c57d2af27cfc93 /src/user_config.zig | |
| parent | eb11ab331b4e13e2b9e5a673a4c012d22fdd1d9c (diff) | |
| download | pardes-a797a1ab2f648e773f7a1b28d12bf9441b9f13f4.tar.gz pardes-a797a1ab2f648e773f7a1b28d12bf9441b9f13f4.zip | |
macos: pixel attachments, live theming, and a signed app
The AppKit shell now draws what the core renders, follows the theme without a
relaunch, and builds into something you can hand to someone.
- Pixel attachments. Surface.images was dropped on the floor here, so a PDF
pane showed nothing at all: native_images is now set, pardes_image_s carries
the geometry the core already clipped, and PardesView keeps one CGImage per
(serial, page, revision) so scrolling costs a draw and not a decode. Image
panes get real pixels instead of the petscii fallback.
- Themes take hold live. pardes_tick never advanced the chrome animation, so
every tagline kept the previous theme's colours until the next launch and
the 16 ms re-pump spun for the rest of the session. pardes_theme_bg retires
the hand-agreed #121212 and drives the window background and the titlebar
appearance; a theme with no background of its own now gets a transparent
window over an NSVisualEffectView.
- The cell snaps to whole DEVICE pixels rather than whole points. Monaco
advances 8.4014pt at 14, so ceiling to 9 spaced every column 7.1% wider than
the face was drawn for.
- The dial is one notch per 10 degrees instead of 20, and a release keeps
turning in proportion to how hard it was thrown -- ramping up from zero at
the floor, so a slow twist coasts not a little but not at all.
- A file dropped on the grid is a click plus Look, so it opens beside the pane
it was dropped on. No drop concept was added to the core.
- The titlebar follows the focused pane: proxy icon, filename, and the dirty
dot. File.saved_revision is the watermark that last one needed.
- Config (SPC f c) prints the resolved startup config path.
- build.zig assembles, signs and packages the bundle itself; build-app.sh is
gone. -Dmacos-identity= takes a Developer ID, macos-dmg makes the image, and
the icon is Glenda.
Diffstat (limited to 'src/user_config.zig')
| -rw-r--r-- | src/user_config.zig | 37 |
1 files changed, 26 insertions, 11 deletions
diff --git a/src/user_config.zig b/src/user_config.zig index 455e8a3c..389188c1 100644 --- a/src/user_config.zig +++ b/src/user_config.zig @@ -31,17 +31,26 @@ pub fn path(gpa: std.mem.Allocator, env: *const std.process.Environ.Map) !?[]u8 return try std.fs.path.join(gpa, &.{ home, ".config", "pardes" }); } -/// Missing, unreadable, oversized, or otherwise unusable config is simply no -/// config. The arena passed by main owns successful bytes for the process. +/// The config file: WHERE it was looked for, and what was there. Missing, +/// unreadable, oversized, or otherwise unusable config is simply no config — +/// but the path resolves either way, because "nothing is there yet" is the +/// answer the Config builtin exists to give and a null would erase it. The +/// arena passed by the launcher owns both for the process. +pub const Found = struct { + path: ?[]const u8 = null, + bytes: ?[]const u8 = null, +}; + pub fn load( io: std.Io, gpa: std.mem.Allocator, env: *const std.process.Environ.Map, -) ?[]u8 { - const config_path = path(gpa, env) catch return null; - defer if (config_path) |p| gpa.free(p); - const p = config_path orelse return null; - return std.Io.Dir.cwd().readFileAlloc(io, p, gpa, .limited(max_bytes)) catch null; +) Found { + const config_path = (path(gpa, env) catch return .{}) orelse return .{}; + return .{ + .path = config_path, + .bytes = std.Io.Dir.cwd().readFileAlloc(io, config_path, gpa, .limited(max_bytes)) catch null, + }; } fn nonEmpty(value: ?[]const u8) ?[]const u8 { @@ -88,10 +97,16 @@ test "config loader is silent when missing and returns exact file bytes" { defer env.deinit(); try env.put("XDG_CONFIG_HOME", base_buf[0..base_len]); - try std.testing.expect(load(std.testing.io, std.testing.allocator, &env) == null); + const missing = load(std.testing.io, std.testing.allocator, &env); + defer std.testing.allocator.free(missing.path.?); + const expected = try std.fs.path.join(std.testing.allocator, &.{ base_buf[0..base_len], "pardes" }); + defer std.testing.allocator.free(expected); + try std.testing.expectEqualStrings(expected, missing.path.?); + try std.testing.expect(missing.bytes == null); const source = "Theme dark\nUnknown command\nTheme acme\n"; try tmp.dir.writeFile(std.testing.io, .{ .sub_path = "pardes", .data = source }); - const bytes = load(std.testing.io, std.testing.allocator, &env).?; - defer std.testing.allocator.free(bytes); - try std.testing.expectEqualStrings(source, bytes); + const found = load(std.testing.io, std.testing.allocator, &env); + defer std.testing.allocator.free(found.path.?); + defer std.testing.allocator.free(found.bytes.?); + try std.testing.expectEqualStrings(source, found.bytes.?); } |
