Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .config/dotnet-tools.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"isRoot": true,
"tools": {
"vpk": {
"version": "0.0.1298",
"version": "1.2.161",
"commands": [
"vpk"
],
Expand Down
12 changes: 9 additions & 3 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ jobs:

- uses: actions/setup-dotnet@v4
with:
dotnet-version: "8.0"
dotnet-version: "10.0" # vpk 1.2 targets .NET 10

# Prefetch before msvcup/package so lazy deps (e.g. zigwin32 from GitHub) get retry
# backoff; zig build msvcup-setup alone can hit HttpConnectionClosing with no retries.
Expand Down Expand Up @@ -278,7 +278,7 @@ jobs:

- uses: actions/setup-dotnet@v4
with:
dotnet-version: "8.0"
dotnet-version: "10.0" # vpk 1.2 targets .NET 10

- name: Fetch deps with retries
shell: bash
Expand Down Expand Up @@ -349,7 +349,13 @@ jobs:
log="$RUNNER_TEMP/package-${{ matrix.channel }}.log"
n=0
until [ "$n" -ge 3 ]; do
if zig build package -Doptimize=ReleaseFast "-Dtarget=${{ matrix.target }}" 2>&1 | tee "$log"; then
# Signing as build options: zig build caches its configuration without regard to the
# environment, so build.zig does not read the FIZZY_MACOS_* variables itself.
sign=()
[ -n "${FIZZY_MACOS_SIGN_APP:-}" ] && sign+=("-Dmacos-sign-app=$FIZZY_MACOS_SIGN_APP")
[ -n "${FIZZY_MACOS_SIGN_INSTALLER:-}" ] && sign+=("-Dmacos-sign-installer=$FIZZY_MACOS_SIGN_INSTALLER")
[ -n "${FIZZY_MACOS_NOTARY_PROFILE:-}" ] && sign+=("-Dmacos-notary-profile=$FIZZY_MACOS_NOTARY_PROFILE")
if zig build package -Doptimize=ReleaseFast "-Dtarget=${{ matrix.target }}" ${sign[@]+"${sign[@]}"} 2>&1 | tee "$log"; then
exit 0
fi
if ! grep -Eq 'HttpConnectionClosing|invalid HTTP response|TlsInitializationFailed|ConnectionResetByPeer|TemporaryNameServerFailure|unable to fetch|connection reset' "$log"; then
Expand Down
74 changes: 4 additions & 70 deletions app/Recents.zig
Original file line number Diff line number Diff line change
Expand Up @@ -49,15 +49,10 @@ pub fn load(allocator: std.mem.Allocator, path: []const u8) !Recents {
if (core.fs.readZ(allocator, dvui.io, path) catch null) |data| {
defer allocator.free(data);

if ((zig017_zon: {
var zig017_zd: std.zon.parse.Diagnostics = undefined;
var zig017_arena: std.heap.ArenaAllocator = .init(allocator);
defer zig017_arena.deinit();
const zig017_r = std.zon.parse.fromSlice(Disk, .{ .gpa = allocator, .arena = zig017_arena.allocator(), .source = data, .diagnostics = &zig017_zd, .ignore_unknown_fields = true });
break :zig017_zon if (zig017_r) |zig017_v| zig017ZonDupe(Disk, allocator, zig017_v) else |zig017_e| zig017_e;
}) catch null) |disk| {
defer zig017ZonFree(allocator, disk);

var disk_arena: std.heap.ArenaAllocator = .init(allocator);
defer disk_arena.deinit();
var disk_diag: std.zon.parse.Diagnostics = undefined;
if (std.zon.parse.fromSlice(Disk, .{ .gpa = allocator, .arena = disk_arena.allocator(), .source = data, .diagnostics = &disk_diag, .ignore_unknown_fields = true }) catch null) |disk| {
for (disk.folders) |folder| {
// A folder on a mount (`gdrive://…`) is not the disk's to check: it exists
// whenever its plugin is signed in, which is decided at open time.
Expand Down Expand Up @@ -172,64 +167,3 @@ pub fn deinit(recents: *Recents, allocator: std.mem.Allocator) void {

recents.commands.deinit(allocator);
}

// ZIG017 stand-in: a deep copy of a zon result out of the parse's arena into the caller's
// allocator, where the code before 0.17 expects to free it. Goes with the zon arena PR.
fn zig017ZonDupe(comptime T: type, gpa: std.mem.Allocator, value: T) error{OutOfMemory}!T {
switch (@typeInfo(T)) {
.pointer => |p| switch (p.size) {
.one => {
const out = try gpa.create(p.child);
out.* = try zig017ZonDupe(p.child, gpa, value.*);
return out;
},
.slice => {
const out = if (comptime p.sentinel()) |s| try gpa.allocSentinel(p.child, value.len, s) else try gpa.alloc(p.child, value.len);
for (out, value) |*o, v| o.* = try zig017ZonDupe(p.child, gpa, v);
return out;
},
else => comptime unreachable,
},
.array => |a| {
var out: T = undefined;
for (&out, value) |*o, v| o.* = try zig017ZonDupe(a.child, gpa, v);
return out;
},
.@"struct" => |s| {
var out: T = value;
inline for (s.field_names, s.field_types, s.field_attrs) |n, FT, attrs| {
if (!attrs.@"comptime") @field(out, n) = try zig017ZonDupe(FT, gpa, @field(value, n));
}
return out;
},
.@"union" => |u| if (u.tag_type == null) return value else switch (value) {
inline else => |payload, tag| return @unionInit(T, @tagName(tag), try zig017ZonDupe(@TypeOf(payload), gpa, payload)),
},
.optional => |o| return if (value) |v| try zig017ZonDupe(o.child, gpa, v) else null,
else => return value,
}
}
// ZIG017 stand-in: 0.16's `std.zon.parse.free`, which 0.17 removed (results now come from an
// arena the caller owns). Goes with the zon arena PR.
fn zig017ZonFree(gpa: std.mem.Allocator, value: anytype) void {
switch (@typeInfo(@TypeOf(value))) {
.pointer => |p| switch (p.size) {
.one => {
zig017ZonFree(gpa, value.*);
gpa.destroy(value);
},
.slice => {
for (value) |item| zig017ZonFree(gpa, item);
gpa.free(value);
},
else => comptime unreachable,
},
.array => for (value) |item| zig017ZonFree(gpa, item),
.@"struct" => |s| inline for (s.field_names) |n| zig017ZonFree(gpa, @field(value, n)),
.@"union" => |u| if (u.tag_type != null) switch (value) {
inline else => |payload| zig017ZonFree(gpa, payload),
},
.optional => if (value) |some| zig017ZonFree(gpa, some),
else => {},
}
}
100 changes: 14 additions & 86 deletions app/settings/Settings.zig
Original file line number Diff line number Diff line change
Expand Up @@ -175,47 +175,36 @@ pub fn load(allocator: std.mem.Allocator, path: []const u8, plugins_dir: ?[]cons
const data = core.fs.readZ(allocator, dvui.io, path) catch return default(allocator);
defer allocator.free(data);

const parsed = parseOnly(allocator, data) catch |err| {
var arena: std.heap.ArenaAllocator = .init(allocator);
defer arena.deinit();
const parsed = parseOnly(allocator, arena.allocator(), data) catch |err| {
dvui.log.warn("Could not parse settings.zon ({s}); using defaults.", .{@errorName(err)});
return default(allocator);
};

defer freeParsed(allocator, parsed);

var result = parsed;
// Own `theme` for the process lifetime (freed in `deinit`); every other field is scalar.
result.theme = try allocator.dupe(u8, parsed.theme);
return result;
}

/// Frees a `parseOnly`/`load` result. Not `std.zon.parse.free` directly: since R12 the file only
/// records non-default fields, so a missing `theme` is filled by `std.zon.parse` with *this
/// struct's declared default* — a comptime string literal, not an allocation — and handing that
/// pointer to the allocator would be an invalid free. Zeroing it first makes the whole-struct
/// free a no-op for that field (`Allocator.free` returns early on an empty slice) while still
/// covering any other allocated field.
pub fn freeParsed(allocator: std.mem.Allocator, parsed: Settings) void {
var value = parsed;
if (value.theme.ptr == default_theme.ptr) value.theme = "";
zig017ZonFree(allocator, value);
}

/// Parses `data` (already-read `settings.zon` bytes) into a `Settings` value, ignoring unknown
/// fields (forward-compat + `.plugins` is handled separately — see `load`'s doc comment above).
/// Unlike `load`, this returns the raw parse error on failure instead of falling back to
/// defaults — defaulting is only correct when there's no existing live state to fall back *to*
/// (startup); `Editor.reconcileExternalSettingsChange` (external hand-edit reconciliation, R11)
/// has existing state and must not let a transient torn-write read reset every fizzy field and
/// every plugin's settings. Caller frees the result with `freeParsed` (not `Settings.deinit`,
/// which owns the live value's long-lived `theme`).
pub fn parseOnly(allocator: std.mem.Allocator, data: [:0]const u8) !Settings {
/// every plugin's settings. The result's strings live in `arena` (`allocator` is the parse's
/// scratch); `load` copies out the one it keeps.
pub fn parseOnly(allocator: std.mem.Allocator, arena: std.mem.Allocator, data: [:0]const u8) !Settings {
@setEvalBranchQuota(10_000);
var parsed = try (zig017_zon: {
var zig017_zd: std.zon.parse.Diagnostics = undefined;
var zig017_arena: std.heap.ArenaAllocator = .init(allocator);
defer zig017_arena.deinit();
const zig017_r = std.zon.parse.fromSlice(Settings, .{ .gpa = allocator, .arena = zig017_arena.allocator(), .source = data, .diagnostics = &zig017_zd, .ignore_unknown_fields = true });
break :zig017_zon if (zig017_r) |zig017_v| zig017ZonDupe(Settings, allocator, zig017_v) else |zig017_e| zig017_e;
var diag: std.zon.parse.Diagnostics = undefined;
var parsed = try std.zon.parse.fromSlice(Settings, .{
.gpa = allocator,
.arena = arena,
.source = data,
.diagnostics = &diag,
.ignore_unknown_fields = true,
});
SettingsMigration.windowGlass(allocator, data, &parsed);
return parsed;
Expand All @@ -238,7 +227,7 @@ fn fieldEqual(comptime FT: type, a: FT, b: FT) bool {
/// halves of `settings.zon` record just what the user actually changed. An untouched fizzy
/// serializes to `.{}`; a field hand-written back at its default drops out on the next write.
/// Reading back is unaffected: `load`/`parseOnly` fill every missing field from these same
/// defaults (see `freeParsed` for the one ownership wrinkle that creates).
/// defaults (they are the declared defaults themselves: `load` copies `theme` before keeping it).
///
/// This is *not* the whole `settings.zon` file — plugin settings are a separate
/// `.plugins = .{ .<id> = .{...}, ... }` field spliced on afterward by
Expand Down Expand Up @@ -269,64 +258,3 @@ pub fn serialize(settings: *const Settings, allocator: std.mem.Allocator) ![]u8
pub fn deinit(settings: *Settings, allocator: std.mem.Allocator) void {
allocator.free(settings.theme);
}

// ZIG017 stand-in: a deep copy of a zon result out of the parse's arena into the caller's
// allocator, where the code before 0.17 expects to free it. Goes with the zon arena PR.
fn zig017ZonDupe(comptime T: type, gpa: std.mem.Allocator, value: T) error{OutOfMemory}!T {
switch (@typeInfo(T)) {
.pointer => |p| switch (p.size) {
.one => {
const out = try gpa.create(p.child);
out.* = try zig017ZonDupe(p.child, gpa, value.*);
return out;
},
.slice => {
const out = if (comptime p.sentinel()) |s| try gpa.allocSentinel(p.child, value.len, s) else try gpa.alloc(p.child, value.len);
for (out, value) |*o, v| o.* = try zig017ZonDupe(p.child, gpa, v);
return out;
},
else => comptime unreachable,
},
.array => |a| {
var out: T = undefined;
for (&out, value) |*o, v| o.* = try zig017ZonDupe(a.child, gpa, v);
return out;
},
.@"struct" => |s| {
var out: T = value;
inline for (s.field_names, s.field_types, s.field_attrs) |n, FT, attrs| {
if (!attrs.@"comptime") @field(out, n) = try zig017ZonDupe(FT, gpa, @field(value, n));
}
return out;
},
.@"union" => |u| if (u.tag_type == null) return value else switch (value) {
inline else => |payload, tag| return @unionInit(T, @tagName(tag), try zig017ZonDupe(@TypeOf(payload), gpa, payload)),
},
.optional => |o| return if (value) |v| try zig017ZonDupe(o.child, gpa, v) else null,
else => return value,
}
}
// ZIG017 stand-in: 0.16's `std.zon.parse.free`, which 0.17 removed (results now come from an
// arena the caller owns). Goes with the zon arena PR.
fn zig017ZonFree(gpa: std.mem.Allocator, value: anytype) void {
switch (@typeInfo(@TypeOf(value))) {
.pointer => |p| switch (p.size) {
.one => {
zig017ZonFree(gpa, value.*);
gpa.destroy(value);
},
.slice => {
for (value) |item| zig017ZonFree(gpa, item);
gpa.free(value);
},
else => comptime unreachable,
},
.array => for (value) |item| zig017ZonFree(gpa, item),
.@"struct" => |s| inline for (s.field_names) |n| zig017ZonFree(gpa, @field(value, n)),
.@"union" => |u| if (u.tag_type != null) switch (value) {
inline else => |payload| zig017ZonFree(gpa, payload),
},
.optional => if (value) |some| zig017ZonFree(gpa, some),
else => {},
}
}
90 changes: 11 additions & 79 deletions app/settings/SettingsMigration.zig
Original file line number Diff line number Diff line change
Expand Up @@ -75,17 +75,13 @@ pub fn migrateToPerPluginEnabled(allocator: std.mem.Allocator, settings_zon_path
defer allocator.free(data);

@setEvalBranchQuota(10_000);
const legacy = (zig017_zon: {
var zig017_zd: std.zon.parse.Diagnostics = undefined;
var zig017_arena: std.heap.ArenaAllocator = .init(allocator);
defer zig017_arena.deinit();
const zig017_r = std.zon.parse.fromSlice(LegacyDisk, .{ .gpa = allocator, .arena = zig017_arena.allocator(), .source = data, .diagnostics = &zig017_zd, .ignore_unknown_fields = true });
break :zig017_zon if (zig017_r) |zig017_v| zig017ZonDupe(LegacyDisk, allocator, zig017_v) else |zig017_e| zig017_e;
}) catch |err| {
var legacy_arena: std.heap.ArenaAllocator = .init(allocator);
defer legacy_arena.deinit();
var legacy_diag: std.zon.parse.Diagnostics = undefined;
const legacy = std.zon.parse.fromSlice(LegacyDisk, .{ .gpa = allocator, .arena = legacy_arena.allocator(), .source = data, .diagnostics = &legacy_diag, .ignore_unknown_fields = true }) catch |err| {
dvui.log.warn("settings: could not parse settings.zon for R12 enabled migration ({s}); skipping", .{@errorName(err)});
return;
};
defer zig017ZonFree(allocator, legacy);

const blocks = SettingsPluginsZon.listPluginBlocks(allocator, data) catch return;
defer SettingsPluginsZon.freeEntries(allocator, blocks);
Expand Down Expand Up @@ -161,11 +157,12 @@ pub fn migrateToPerPluginEnabled(allocator: std.mem.Allocator, settings_zon_path

// Regenerate fizzy's own fields from a Settings parse (unknown fields — including the legacy
// `disabled_plugins` list — are ignored), so the rewritten file drops that list for free.
const parsed = Settings.parseOnly(allocator, data) catch |err| {
var parsed_arena: std.heap.ArenaAllocator = .init(allocator);
defer parsed_arena.deinit();
const parsed = Settings.parseOnly(allocator, parsed_arena.allocator(), data) catch |err| {
dvui.log.warn("settings: could not re-serialize fizzy's own fields for R12 migration ({s}); skipping", .{@errorName(err)});
return;
};
defer Settings.freeParsed(allocator, parsed);

const fizzy_text = Settings.serialize(&parsed, allocator) catch |err| {
dvui.log.warn("settings: could not serialize fizzy's own fields for R12 migration ({s}); skipping", .{@errorName(err)});
Expand Down Expand Up @@ -258,14 +255,10 @@ const LegacyGlass = struct {
/// dialogs' blur goes with them, as the roughness that followed it does.
pub fn windowGlass(allocator: std.mem.Allocator, data: [:0]const u8, settings: *Settings) void {
@setEvalBranchQuota(10_000);
const legacy = (zig017_zon: {
var zig017_zd: std.zon.parse.Diagnostics = undefined;
var zig017_arena: std.heap.ArenaAllocator = .init(allocator);
defer zig017_arena.deinit();
const zig017_r = std.zon.parse.fromSlice(LegacyGlass, .{ .gpa = allocator, .arena = zig017_arena.allocator(), .source = data, .diagnostics = &zig017_zd, .ignore_unknown_fields = true });
break :zig017_zon if (zig017_r) |zig017_v| zig017ZonDupe(LegacyGlass, allocator, zig017_v) else |zig017_e| zig017_e;
}) catch return;
defer zig017ZonFree(allocator, legacy);
var legacy_arena: std.heap.ArenaAllocator = .init(allocator);
defer legacy_arena.deinit();
var legacy_diag: std.zon.parse.Diagnostics = undefined;
const legacy = std.zon.parse.fromSlice(LegacyGlass, .{ .gpa = allocator, .arena = legacy_arena.allocator(), .source = data, .diagnostics = &legacy_diag, .ignore_unknown_fields = true }) catch return;
if (legacy.dialog_opacity == null and legacy.dialog_blur == null and legacy.dialog_lift == null and
legacy.dialog_detail == null and legacy.dialog_refraction == null) return;
const opaque_window = comptime builtin.target.cpu.arch == .wasm32 or (builtin.target.os.tag != .macos and builtin.target.os.tag != .windows);
Expand All @@ -274,64 +267,3 @@ pub fn windowGlass(allocator: std.mem.Allocator, data: [:0]const u8, settings: *
settings.window_opacity_light = std.math.clamp(o, 0, 1);
};
}

// ZIG017 stand-in: a deep copy of a zon result out of the parse's arena into the caller's
// allocator, where the code before 0.17 expects to free it. Goes with the zon arena PR.
fn zig017ZonDupe(comptime T: type, gpa: std.mem.Allocator, value: T) error{OutOfMemory}!T {
switch (@typeInfo(T)) {
.pointer => |p| switch (p.size) {
.one => {
const out = try gpa.create(p.child);
out.* = try zig017ZonDupe(p.child, gpa, value.*);
return out;
},
.slice => {
const out = if (comptime p.sentinel()) |s| try gpa.allocSentinel(p.child, value.len, s) else try gpa.alloc(p.child, value.len);
for (out, value) |*o, v| o.* = try zig017ZonDupe(p.child, gpa, v);
return out;
},
else => comptime unreachable,
},
.array => |a| {
var out: T = undefined;
for (&out, value) |*o, v| o.* = try zig017ZonDupe(a.child, gpa, v);
return out;
},
.@"struct" => |s| {
var out: T = value;
inline for (s.field_names, s.field_types, s.field_attrs) |n, FT, attrs| {
if (!attrs.@"comptime") @field(out, n) = try zig017ZonDupe(FT, gpa, @field(value, n));
}
return out;
},
.@"union" => |u| if (u.tag_type == null) return value else switch (value) {
inline else => |payload, tag| return @unionInit(T, @tagName(tag), try zig017ZonDupe(@TypeOf(payload), gpa, payload)),
},
.optional => |o| return if (value) |v| try zig017ZonDupe(o.child, gpa, v) else null,
else => return value,
}
}
// ZIG017 stand-in: 0.16's `std.zon.parse.free`, which 0.17 removed (results now come from an
// arena the caller owns). Goes with the zon arena PR.
fn zig017ZonFree(gpa: std.mem.Allocator, value: anytype) void {
switch (@typeInfo(@TypeOf(value))) {
.pointer => |p| switch (p.size) {
.one => {
zig017ZonFree(gpa, value.*);
gpa.destroy(value);
},
.slice => {
for (value) |item| zig017ZonFree(gpa, item);
gpa.free(value);
},
else => comptime unreachable,
},
.array => for (value) |item| zig017ZonFree(gpa, item),
.@"struct" => |s| inline for (s.field_names) |n| zig017ZonFree(gpa, @field(value, n)),
.@"union" => |u| if (u.tag_type != null) switch (value) {
inline else => |payload| zig017ZonFree(gpa, payload),
},
.optional => if (value) |some| zig017ZonFree(gpa, some),
else => {},
}
}
Loading
Loading