From 098b1b3bc7774aa0f9e6a4facf641c3d6e793e68 Mon Sep 17 00:00:00 2001 From: "fizzy-linux[bot]" <346940526+fizzy-linux[bot]@users.noreply.github.com> Date: Sat, 24 Oct 2026 07:57:09 -0500 Subject: [PATCH 1/9] lint: line-length (codex) Agent-Team: linux Worker: codex --- scripts/demos.zig | 22 ++++++++++++-- scripts/lint.zig | 60 ++++++++++++++++++++++++++++++--------- scripts/tests_reached.zig | 6 +++- 3 files changed, 70 insertions(+), 18 deletions(-) diff --git a/scripts/demos.zig b/scripts/demos.zig index 59bfb9ffd..b6b821731 100644 --- a/scripts/demos.zig +++ b/scripts/demos.zig @@ -83,19 +83,35 @@ pub fn main(init: std.process.Init) !void { /// Write `t` as `.tape` in `out`, having read it back from those bytes. The row is `arena`'s: /// the tape goes before the index is written. -fn emit(arena: std.mem.Allocator, gpa: std.mem.Allocator, io: std.Io, out: std.Io.Dir, rows: []const Row, name: []const u8, t: Tape, live: bool) !Row { +fn emit( + arena: std.mem.Allocator, + gpa: std.mem.Allocator, + io: std.Io, + out: std.Io.Dir, + rows: []const Row, + name: []const u8, + t: Tape, + live: bool, +) !Row { for (rows) |r| if (std.mem.eql(u8, r.name, name)) return fail("two tapes are named '{s}'", .{name}); var bytes: std.Io.Writer.Allocating = .init(arena); try tape.binary.write(gpa, t, &bytes.writer, .{ .deflate = true }); var c = check; c.live = live; - var back = tape.binary.read(gpa, bytes.written(), c) catch |err| return fail("{s}: its tape does not read back: {t}", .{ name, err }); + var back = tape.binary.read(gpa, bytes.written(), c) catch |err| + return fail("{s}: its tape does not read back: {t}", .{ name, err }); defer back.deinit(); if (back.tape.ops.len != t.ops.len or back.tape.duration() != t.duration()) return fail("{s}: its tape reads back as another", .{name}); const file = try arena.print("{s}.tape", .{name}); try out.writeFile(io, .{ .sub_path = file, .data = bytes.written() }); - return .{ .name = try arena.dupe(u8, name), .title = try arena.dupe(u8, t.title), .file = file, .ms = t.duration(), .live = live }; + return .{ + .name = try arena.dupe(u8, name), + .title = try arena.dupe(u8, t.title), + .file = file, + .ms = t.duration(), + .live = live, + }; } fn fail(comptime fmt: []const u8, args: anytype) error{DemoFailed} { diff --git a/scripts/lint.zig b/scripts/lint.zig index 2cc3087b4..10ef9af94 100644 --- a/scripts/lint.zig +++ b/scripts/lint.zig @@ -1,7 +1,14 @@ //! Repository lint with per-file budgets for existing findings. const std = @import("std"); -const Rule = enum { @"fn-length", @"create-needs-destroy", @"lint-allow-reason", @"file-alias", anyerror, @"deprecated-names-replacement" }; +const Rule = enum { + @"fn-length", + @"create-needs-destroy", + @"lint-allow-reason", + @"file-alias", + anyerror, + @"deprecated-names-replacement", +}; const Finding = struct { rule: Rule, line: usize, message: []const u8 }; const Allow = struct { rule: Rule, file: []const u8, count: usize }; const Suppression = struct { rule: []const u8, line: usize }; @@ -60,7 +67,10 @@ pub fn main(init: std.process.Init) !void { } } for (parsed, seen) |allow, visited| if (!visited) { - try output.writer.print("{s}:1: {s}: file not found; remove its allowance of {d}\n", .{ allow.file, @tagName(allow.rule), allow.count }); + try output.writer.print( + "{s}:1: {s}: file not found; remove its allowance of {d}\n", + .{ allow.file, @tagName(allow.rule), allow.count }, + ); failed = true; }; std.debug.print("{s}", .{output.written()}); @@ -98,11 +108,17 @@ fn reportFile( } switch (status) { .over => { - try writer.print("{s}:1: {s}: count {d} exceeds allowance {d}\n", .{ path, @tagName(rule), count, budget }); + try writer.print( + "{s}:1: {s}: count {d} exceeds allowance {d}\n", + .{ path, @tagName(rule), count, budget }, + ); failed = true; }, .under => { - try writer.print("{s}:1: {s}: count {d} is under allowance {d}; lower the allowance to {d}\n", .{ path, @tagName(rule), count, budget, count }); + try writer.print( + "{s}:1: {s}: count {d} is under allowance {d}; lower the allowance to {d}\n", + .{ path, @tagName(rule), count, budget, count }, + ); failed = true; }, .equal => {}, @@ -135,7 +151,11 @@ fn analyze(allocator: std.mem.Allocator, source: [:0]const u8) ![]Finding { const close = std.mem.findScalar(u8, text, ')') orelse text.len; const tail = if (close < text.len) std.mem.trim(u8, text[close + 1 ..], " \t\r") else ""; if (tail.len < 2 or tail[0] != ':' or std.mem.trim(u8, tail[1..], " \t\r").len == 0) { - try findings.append(allocator, .{ .rule = .@"lint-allow-reason", .line = line_number, .message = "lint-allow requires a reason after ':'" }); + try findings.append(allocator, .{ + .rule = .@"lint-allow-reason", + .line = line_number, + .message = "lint-allow requires a reason after ':'", + }); } else { try suppressions.append(allocator, .{ .rule = text[prefix.len..close], .line = line_number }); } @@ -175,7 +195,11 @@ fn analyze(allocator: std.mem.Allocator, source: [:0]const u8) ![]Finding { const end = tree.tokenLocation(0, tree.lastToken(node)).line + 1; const length = end - line + 1; if (length > 150 and !silenced(suppressions.items, .@"fn-length", line)) { - try findings.append(allocator, .{ .rule = .@"fn-length", .line = line, .message = try allocator.print("function {s} is {d} lines (limit 150)", .{ name, length }) }); + try findings.append(allocator, .{ + .rule = .@"fn-length", + .line = line, + .message = try allocator.print("function {s} is {d} lines (limit 150)", .{ name, length }), + }); } } if (create_line != null and !has_destroy) try findings.append(allocator, .{ @@ -264,11 +288,12 @@ fn deprecatedReplacement(allocator: std.mem.Allocator, source: [:0]const u8, pat } } const line = std.mem.count(u8, source[0..token.loc.start], "\n") + 1; - if (!replacement and !silenced(suppressions.items, .@"deprecated-names-replacement", line)) try findings.append(allocator, .{ - .rule = .@"deprecated-names-replacement", - .line = line, - .message = "Deprecated marker must name a backticked replacement", - }); + if (!replacement and !silenced(suppressions.items, .@"deprecated-names-replacement", line)) + try findings.append(allocator, .{ + .rule = .@"deprecated-names-replacement", + .line = line, + .message = "Deprecated marker must name a backticked replacement", + }); } return findings.toOwnedSlice(allocator); } @@ -535,14 +560,18 @@ test "SDK deprecated markers name a backticked replacement" { "const text = \"/// Deprecated\";", "/// DeprecatedThing is an identifier.\npub const old = 1;", "/// Foo\n/// Deprecated, see below\npub const old = 1;", - }) |source| try std.testing.expectEqual(@as(usize, 0), (try deprecatedReplacement(a, source, "sdk/src/old.zig")).len); + }) |source| try std.testing.expectEqual( + @as(usize, 0), + (try deprecatedReplacement(a, source, "sdk/src/old.zig")).len, + ); } test "deprecated marker honors lint-allow suppression and a file budget" { var arena = std.heap.ArenaAllocator.init(std.testing.allocator); defer arena.deinit(); const a = arena.allocator(); - const suppressed = "// lint-allow(deprecated-names-replacement): removed with the next SDK\n/// Deprecated\npub const old = 1;"; + const suppressed = "// lint-allow(deprecated-names-replacement): removed with the next SDK\n" ++ + "/// Deprecated\npub const old = 1;"; try std.testing.expectEqual(@as(usize, 0), (try deprecatedReplacement(a, suppressed, "sdk/src/old.zig")).len); const inside_doc = "/// Deprecated // lint-allow(deprecated-names-replacement): not a comment\npub const old = 1;"; try std.testing.expectEqual(@as(usize, 1), (try deprecatedReplacement(a, inside_doc, "sdk/src/old.zig")).len); @@ -553,7 +582,10 @@ test "deprecated marker honors lint-allow suppression and a file budget" { for ([_][:0]const u8{ "// lint-allow(anyerror): wrong rule\n/// Deprecated\npub const old = 1;", "// lint-allow(deprecated-names-replacement): too far\n\n/// Deprecated\npub const old = 1;", - }) |source| try std.testing.expectEqual(@as(usize, 1), (try deprecatedReplacement(a, source, "sdk/src/old.zig")).len); + }) |source| try std.testing.expectEqual( + @as(usize, 1), + (try deprecatedReplacement(a, source, "sdk/src/old.zig")).len, + ); const hit = try deprecatedReplacement(a, "/// Deprecated\npub const old = 1;", "sdk/src/old.zig"); const allowances = [_]Allow{ diff --git a/scripts/tests_reached.zig b/scripts/tests_reached.zig index 936257f51..34017de5d 100644 --- a/scripts/tests_reached.zig +++ b/scripts/tests_reached.zig @@ -72,7 +72,11 @@ pub fn main(init: std.process.Init) !void { const name = testName(arena, line) orelse continue; file_tests += 1; if (reached(arena, binaries.items, abs, name)) continue; - if (missing == 0) std.debug.print("Tests no test binary compiles (none of `zig build test` / `test-integration` reaches the file):\n", .{}); + if (missing == 0) std.debug.print( + "Tests no test binary compiles " ++ + "(none of `zig build test` / `test-integration` reaches the file):\n", + .{}, + ); std.debug.print(" {s}: {s} \"{s}\"\n", .{ rel, name.kind, name.name }); missing += 1; } From acea9acedec6a9d494bd257d7365ad8325282cf4 Mon Sep 17 00:00:00 2001 From: "fizzy-linux[bot]" <346940526+fizzy-linux[bot]@users.noreply.github.com> Date: Sun, 25 Oct 2026 03:28:04 -0500 Subject: [PATCH 2/9] lint: line-length (codex) Agent-Team: linux Worker: codex --- scripts/lint.zig | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/scripts/lint.zig b/scripts/lint.zig index 06e06fc5b..87549125d 100644 --- a/scripts/lint.zig +++ b/scripts/lint.zig @@ -745,14 +745,17 @@ test "changelog words report hits, honor line allows, and never fail the runner" "const s =\n \\\\// now fixed\n;", "// Unknown prefix and renewed storage.\nconst n = 1;", }) |source| try std.testing.expectEqual(@as(usize, 0), (try analyze(a, source)).len); - for ([_][]const u8{ "fixed", "NOW", "used to", "previously", "formerly", "no longer", "New:", "new:behavior" }) |word| { + for ([_][]const u8{ + "fixed", "NOW", "used to", "previously", "formerly", "no longer", "New:", "new:behavior", + }) |word| { const source = try a.printSentinel("//! {s} works.\n", .{word}, 0); try std.testing.expectEqual(@as(usize, 1), (try analyze(a, source)).len); } var output: std.Io.Writer.Allocating = .init(a); try std.testing.expect(!try reportFile(&output.writer, "hit.zig", hit, &.{}, &.{}, false)); try std.testing.expectEqualStrings( - "hit.zig:1: changelog-words: comment contains 'previously': Previously used a different buffer. (report-only)\n", + "hit.zig:1: changelog-words: comment contains 'previously': " ++ + "Previously used a different buffer. (report-only)\n", output.written(), ); } From bc3d8651f1132d94a7659d3b98cb83baad95468e Mon Sep 17 00:00:00 2001 From: "fizzy-linux[bot]" <346940526+fizzy-linux[bot]@users.noreply.github.com> Date: Sun, 25 Oct 2026 07:00:56 -0500 Subject: [PATCH 3/9] lint: line-length (codex) Agent-Team: linux Worker: codex --- build.zig | 99 ++++++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 84 insertions(+), 15 deletions(-) diff --git a/build.zig b/build.zig index fa9ff2827..7af90062b 100644 --- a/build.zig +++ b/build.zig @@ -23,23 +23,55 @@ pub fn build(b: *std.Build) !void { run_lint.has_side_effects = true; b.step("lint", "Check Zig source against lint budgets").dependOn(&run_lint.step); - const windows_msvc_libc_opt = b.option([]const u8, "windows-msvc-libc", "zig libc manifest for *-windows-msvc when cross-compiling; forwarded by packageall for Windows children") orelse null; - const fetch_msvc_opt = b.option(bool, "fetch-msvc", "If *-windows-msvc libc is missing under .velopack-msvc/, run msvcup-setup first (downloads MSVC+SDK; requires network). Defaults to true on Windows hosts targeting *-windows-msvc.") orelse null; + const windows_msvc_libc_opt = b.option( + []const u8, + "windows-msvc-libc", + "zig libc manifest for *-windows-msvc when cross-compiling; forwarded by " ++ + "packageall for Windows children", + ) orelse null; + const fetch_msvc_opt = b.option( + bool, + "fetch-msvc", + "If *-windows-msvc libc is missing under .velopack-msvc/, run msvcup-setup " ++ + "first (downloads MSVC+SDK; requires network). Defaults to true on Windows " ++ + "hosts targeting *-windows-msvc.", + ) orelse null; // Options only, never the environment: Zig 0.17 caches the build's configuration without // regard to it, so a variable read here would be seen once and then ignored. // `scripts/release.sh` and `release.yml` turn the FIZZY_MACOS_* variables into these. - const macos_sign_app_identity = b.option([]const u8, "macos-sign-app", "macOS codesign identity for the app bundle (e.g. 'Developer ID Application: NAME (TEAMID)')"); - const macos_sign_install_identity = b.option([]const u8, "macos-sign-installer", "macOS codesign identity for the installer pkg (e.g. 'Developer ID Installer: NAME (TEAMID)')"); - const macos_notary_profile = b.option([]const u8, "macos-notary-profile", "notarytool keychain profile name (run `xcrun notarytool store-credentials ` first)"); - const macos_sdk = b.option([]const u8, "macos-sdk", "macOS SDK path for an explicit -Dtarget=*-macos (default: `xcrun --sdk macosx --show-sdk-path`)"); + const macos_sign_app_identity = b.option( + []const u8, + "macos-sign-app", + "macOS codesign identity for the app bundle (e.g. 'Developer ID " ++ + "Application: NAME (TEAMID)')", + ); + const macos_sign_install_identity = b.option( + []const u8, + "macos-sign-installer", + "macOS codesign identity for the installer pkg (e.g. 'Developer ID " ++ + "Installer: NAME (TEAMID)')", + ); + const macos_notary_profile = b.option( + []const u8, + "macos-notary-profile", + "notarytool keychain profile name (run `xcrun notarytool store-credentials " ++ + "` first)", + ); + const macos_sdk = b.option( + []const u8, + "macos-sdk", + "macOS SDK path for an explicit -Dtarget=*-macos (default: `xcrun --sdk " ++ + "macosx --show-sdk-path`)", + ); // Windows 10 1803 at least: AF_UNIX arrived there, and std offers it only to a target whose // minimum is that (`std.Io.net.has_unix_sockets`) — the control server's socket // (`app/control/Server.zig`). Every Windows still supported by Microsoft is newer. const target = target: { const asked = b.standardTargetOptions(.{}); - if (asked.result.os.tag != .windows or asked.result.os.isAtLeast(.windows, .win10_rs4) == true) break :target asked; + if (asked.result.os.tag != .windows or + asked.result.os.isAtLeast(.windows, .win10_rs4) == true) break :target asked; if (asked.result.os.version_range.windows.max.isAtLeast(.win10_rs4) != true) std.debug.panic("fizzy needs Windows 10 1803 (win10_rs4) or newer; -Dtarget asks for an older range", .{}); var query = asked.query; @@ -84,7 +116,13 @@ pub fn build(b: *std.Build) !void { // `defer-app` here and calls `buildApp` below with its plugin modules instead. const app = @import("build/app.zig"); const cfg = try app.readConfig(b, target, base); - if (b.option(bool, "defer-app", "Do not build the application here; the consumer calls `buildApp` with its own plugins") orelse false) { + const defer_app = b.option( + bool, + "defer-app", + "Do not build the application here; the consumer calls `buildApp` with its " ++ + "own plugins", + ) orelse false; + if (defer_app) { deferred = .{ .target = target, .optimize = optimize, .base = base, .cfg = cfg }; return; } @@ -126,7 +164,13 @@ pub const plugins = @import("build/plugins.zig").entries; /// try fizzy.buildApp(fizzy_dep, &.{ /// fizzy.plugins.workbench, /// fizzy.plugins.text, -/// .{ .name = "pixi", .module = b.dependency("pixi", .{ .target = target, .optimize = optimize }).module("plugin") }, +/// .{ +/// .name = "pixi", +/// .module = b.dependency("pixi", .{ +/// .target = target, +/// .optimize = optimize, +/// }).module("plugin"), +/// }, /// }); /// b.installArtifact(fizzy_dep.artifact("myapp")); /// ``` @@ -136,7 +180,10 @@ pub const plugins = @import("build/plugins.zig").entries; /// (its root's `plugin_id`) and `module` its static module, which the plugin package exports and /// which fizzy wires to `dvui`, `core`, `fizzy_sdk` and `icons`. pub fn buildApp(fizzy_dep: *std.Build.Dependency, linked: []const BundledPlugin) !void { - const d = deferred orelse @panic("fizzy.buildApp: pass `.@\"defer-app\" = true` to b.dependency(\"fizzy\", …) first"); + const d = deferred orelse @panic( + "fizzy.buildApp: pass `.@\"defer-app\" = true` to b.dependency(\"fizzy\", " ++ + "…) first", + ); var opts = d.base; // Until fizzyedit/example-app lists fizzy's plugins itself (its CI and fizzy's build each // other's main): a list naming none of fizzy's gets all of them ahead of its own, as before. @@ -144,7 +191,11 @@ pub fn buildApp(fizzy_dep: *std.Build.Dependency, linked: []const BundledPlugin) const names_fizzys = for (linked) |p| { if (p.module == null) break true; } else false; - opts.app_plugins = if (names_fizzys) linked else try std.mem.concat(fizzy_dep.builder.allocator, BundledPlugin, &.{ &plugins.all, linked }); + opts.app_plugins = if (names_fizzys) linked else try std.mem.concat( + fizzy_dep.builder.allocator, + BundledPlugin, + &.{ &plugins.all, linked }, + ); try @import("build/app.zig").construct(fizzy_dep.builder, d.target, d.optimize, opts, d.cfg); deferred.?.built = true; } @@ -173,10 +224,17 @@ pub fn buildApp(fizzy_dep: *std.Build.Dependency, linked: []const BundledPlugin) /// Windows MSVC ones reach fizzy only if the app declares `windows-msvc-libc` / `fetch-msvc` and /// passes them on in `b.dependency`. Without them, a `*-windows-msvc` run fetches MSVC itself. pub fn addPackageSteps(b: *std.Build, fizzy_dep: *std.Build.Dependency) !void { - const d = deferred orelse @panic("fizzy.addPackageSteps: pass `.@\"defer-app\" = true` to b.dependency(\"fizzy\", …) and call fizzy.buildApp first"); + const d = deferred orelse @panic( + "fizzy.addPackageSteps: pass `.@\"defer-app\" = true` to " ++ + "b.dependency(\"fizzy\", …) and call fizzy.buildApp first", + ); const names = [_][]const u8{ "package", "desktop", "packageall" }; for (names) |name| if (b.top_level_steps.contains(name)) { - std.log.err("fizzy.addPackageSteps: this build already has a `{s}` step; `package`, `desktop` and `packageall` are the names it adds", .{name}); + std.log.err( + "fizzy.addPackageSteps: this build already has a `{s}` step; `package`, " ++ + "`desktop` and `packageall` are the names it adds", + .{name}, + ); return error.PackageStepTaken; }; if (!d.built) @panic("fizzy.addPackageSteps: call fizzy.buildApp first"); @@ -207,7 +265,10 @@ pub const NativeBackend = @import("build/exe.zig").NativeBackend; /// /// ```zig /// const backend = @import("backend"); -/// const viewports = if (@hasDecl(backend, "viewports")) backend.viewports else @import("viewports_none"); +/// const viewports = if (@hasDecl(backend, "viewports")) +/// backend.viewports +/// else +/// @import("viewports_none"); /// ``` /// /// On fizzy's backend it also gets `platform` (the window's chrome and state), and on macOS the @@ -225,6 +286,14 @@ pub fn addDvui(fizzy_dep: *std.Build.Dependency, root_module: *std.Build.Module, root_module.addImport("viewports_none", @import("fizzy_backend").viewportsNoneModule(backend_dep, native.dvui)); if (backend == .fizzy) { root_module.addImport("platform", exe.platformModule(b, native, target, optimize, macos_sdl_paths)); - @import("fizzy_backend").addPlatformObjC(backend_dep, root_module, exe.backendOptions(target, optimize, macos_sdl_paths)); + @import("fizzy_backend").addPlatformObjC( + backend_dep, + root_module, + exe.backendOptions( + target, + optimize, + macos_sdl_paths, + ), + ); } } From 4bfabbf28cb81fa26d1b0e8f93e11b00acf523b5 Mon Sep 17 00:00:00 2001 From: "fizzy-linux[bot]" <346940526+fizzy-linux[bot]@users.noreply.github.com> Date: Sun, 25 Oct 2026 19:18:31 -0500 Subject: [PATCH 4/9] lint: line-length (codex) Agent-Team: linux Worker: codex --- build/common.zig | 15 ++++- build/exe.zig | 130 +++++++++++++++++++++++++++++++++------ build/package.zig | 22 +++++-- build/plugins.zig | 99 ++++++++++++++++++++++++----- build/rehome_fixture.zig | 17 +++-- build/sdk.zig | 19 ++++-- build/set_aside.zig | 6 +- build/velopack.zig | 22 +++++-- build/web.zig | 77 ++++++++++++++++++++--- 9 files changed, 342 insertions(+), 65 deletions(-) diff --git a/build/common.zig b/build/common.zig index e8012b344..953f3c4bc 100644 --- a/build/common.zig +++ b/build/common.zig @@ -25,7 +25,8 @@ pub fn installDirPath(dir: std.Build.InstallDir) std.Build.LazyPath { }; } -/// Installed artifacts go under `zig-out//…` so `packageall` and parallel targets never clobber each other. +/// Installed artifacts go under `zig-out//…` so `packageall` and parallel targets never +/// clobber each other. pub fn zigOutSubdirForTarget(b: *std.Build, rt: std.Build.ResolvedTarget) []const u8 { const arch_name: []const u8 = switch (rt.result.cpu.arch) { .x86_64 => "x86-64", @@ -82,7 +83,11 @@ fn resolveMacosSdkPath(b: *std.Build, explicit: ?[]const u8) ![]const u8 { } switch (run.term) { .exited => |code| if (code != 0) { - std.log.err("SDL on macOS: explicit -Dtarget=*-macos needs an SDK path. xcrun exited with code {d}. Install Xcode Command Line Tools or pass -Dmacos-sdk=.", .{code}); + std.log.err( + "SDL on macOS: explicit -Dtarget=*-macos needs an SDK path. xcrun exited with " ++ + "code {d}. Install Xcode Command Line Tools or pass -Dmacos-sdk=.", + .{code}, + ); return error.MacosSdkPath; }, else => { @@ -95,7 +100,11 @@ fn resolveMacosSdkPath(b: *std.Build, explicit: ?[]const u8) ![]const u8 { return b.dupePath(path); } -pub fn macosSdlPathsForExplicitTarget(b: *std.Build, target: std.Build.ResolvedTarget, macos_sdk: ?[]const u8) !?MacosSdlPaths { +pub fn macosSdlPathsForExplicitTarget( + b: *std.Build, + target: std.Build.ResolvedTarget, + macos_sdk: ?[]const u8, +) !?MacosSdlPaths { if (target.result.os.tag != .macos) return null; if (b.graph.host.result.os.tag != .macos) return null; if (target.query.isNative()) return null; diff --git a/build/exe.zig b/build/exe.zig index 1039e2f80..ca0f19ceb 100644 --- a/build/exe.zig +++ b/build/exe.zig @@ -110,7 +110,10 @@ pub fn nativeDvui( .library_path = p.lib, }) else - sdk.dvuiDependency(b, .{ .target = target, .optimize = optimize, .backend = .sdl3, .accesskit = accesskit }); + sdk.dvuiDependency( + b, + .{ .target = target, .optimize = optimize, .backend = .sdl3, .accesskit = accesskit }, + ); break :blk .{ .dep = dep, .dvui = dep.module("dvui_sdl3"), .backend = dep.module("sdl3") }; }, }; @@ -138,7 +141,11 @@ fn fizzyNativeDvui( .@"tree-sitter" = true, }); const dvui_mod = dep.module("dvui"); - const backend = fizzy_backend.backendModule(backendDependency(b), dvui_mod, backendOptions(target, optimize, macos_sdl_paths)); + const backend = fizzy_backend.backendModule( + backendDependency(b), + dvui_mod, + backendOptions(target, optimize, macos_sdl_paths), + ); return .{ .dep = dep, .dvui = dvui_mod, .backend = backend }; } @@ -147,11 +154,19 @@ pub fn backendDependency(b: *std.Build) *std.Build.Dependency { return b.dependency("fizzy_backend", .{}); } -pub fn backendOptions(target: std.Build.ResolvedTarget, optimize: std.lang.OptimizeMode, macos_sdl_paths: ?MacosSdlPaths) fizzy_backend.Options { +pub fn backendOptions( + target: std.Build.ResolvedTarget, + optimize: std.lang.OptimizeMode, + macos_sdl_paths: ?MacosSdlPaths, +) fizzy_backend.Options { return .{ .target = target, .optimize = optimize, - .macos_sdk = if (macos_sdl_paths) |p| .{ .include = p.include, .framework = p.framework, .lib = p.lib } else null, + .macos_sdk = if (macos_sdl_paths) |p| .{ + .include = p.include, + .framework = p.framework, + .lib = p.lib, + } else null, }; } @@ -164,7 +179,12 @@ pub fn platformModule( optimize: std.lang.OptimizeMode, macos_sdl_paths: ?MacosSdlPaths, ) *std.Build.Module { - return fizzy_backend.platformModule(backendDependency(b), native.dvui, native.backend, backendOptions(target, optimize, macos_sdl_paths)); + return fizzy_backend.platformModule( + backendDependency(b), + native.dvui, + native.backend, + backendOptions(target, optimize, macos_sdl_paths), + ); } /// What every native executable of one target links and builds its plugins against: dvui on the @@ -189,7 +209,8 @@ pub fn nativeFramework( ) NativeFramework { const native = nativeDvui(b, native_backend, target, optimize, accesskit, macos_sdl_paths); const glass_look = fizzy_backend.glassLookModule(backendDependency(b)); - // Its in-app glass, the backend's (`glassModule`): `core` over the same dvui takes the same one. + // Its in-app glass, the backend's (`glassModule`): `core` over the same dvui takes the same + // one. const glass = fizzy_backend.glassModule(backendDependency(b), native.dvui); // Shared `core` module (gfx/math/fs/generated atlas/platform/paths/dvui hub + @@ -199,7 +220,16 @@ pub fn nativeFramework( .optimize = optimize, .root_source_file = b.path("core/core.zig"), }); - const icons = core_mod.addImports(b, core_module, native.dvui, b.dependency("fizzy_sdk", .{}).builder, target, optimize, glass_look, glass); + const icons = core_mod.addImports( + b, + core_module, + native.dvui, + b.dependency("fizzy_sdk", .{}).builder, + target, + optimize, + glass_look, + glass, + ); const proxy_bridge_host_mod = sdk.addProxyBridgeModule(b, target, optimize, native.dep, native.dvui); return .{ .native = native, @@ -281,7 +311,8 @@ pub fn addFizzyExecutableForTarget( .windows => { exe.subsystem = .windows; // MSVC's libcmt links `WinMainCRTStartup` (needs `WinMain`) for /SUBSYSTEM:WINDOWS. - // Fizzy exposes `main`, so force the C `main` entry which works for either subsystem. + // Fizzy exposes `main`, so force the C `main` entry which works for either + // subsystem. if (resolved_target.result.abi == .msvc) { exe.entry = .{ .symbol_name = "mainCRTStartup" }; } @@ -310,7 +341,16 @@ pub fn addFizzyExecutableForTarget( .optimize = optimize, .root_source_file = b.path("core/core.zig"), }); - _ = core_mod.addImports(b, core_proxy_module, dvui_proxy_mod, b.dependency("fizzy_sdk", .{}).builder, resolved_target, optimize, framework.glass_look, null); + _ = core_mod.addImports( + b, + core_proxy_module, + dvui_proxy_mod, + b.dependency("fizzy_sdk", .{}).builder, + resolved_target, + optimize, + framework.glass_look, + null, + ); // `macos_fsevents` is load-bearing for `FolderWatcher`: it watches a whole project folder, // and the kqueue fallback needs a file descriptor per directory *and* per file — exactly the @@ -326,7 +366,15 @@ pub fn addFizzyExecutableForTarget( const sdk_module = framework.sdk; exe.root_module.addImport("fizzy_sdk", sdk_module); - const sdk_proxy_module = sdk.wireSdkModule(b, resolved_target, optimize, dvui_proxy_mod, proxy_bridge_plugin_mod, core_proxy_module, null); + const sdk_proxy_module = sdk.wireSdkModule( + b, + resolved_target, + optimize, + dvui_proxy_mod, + proxy_bridge_plugin_mod, + core_proxy_module, + null, + ); // What this application links, as build data: a generated module whose `modules` tuple // names every statically linked plugin, so the runtime registers, probes and falls back // to whatever is listed here and never a plugin by name. An app built on fizzy lists its @@ -355,13 +403,38 @@ pub fn addFizzyExecutableForTarget( // The `app` framework module: the plugin store and what it needs. Fizzy is its first // consumer, not its owner — see `app/root.zig`. - const app_module = sdk.wireAppModule(b, resolved_target, optimize, dvui_mod, core_module, sdk_module, icons_module, markdown_module, if (nightwatch_dep) |dep| dep.module("nightwatch") else null, build_opts, singleton_app_dep.module("singleton_app"), exe.root_module); + const app_module = sdk.wireAppModule( + b, + resolved_target, + optimize, + dvui_mod, + core_module, + sdk_module, + icons_module, + markdown_module, + if (nightwatch_dep) |dep| dep.module("nightwatch") else null, + build_opts, + singleton_app_dep.module("singleton_app"), + exe.root_module, + ); app_module.addImport("bundled_plugins", bundled); - sdk.wireAppLayout(b, resolved_target, optimize, app_layout, dvui_mod, app_module, core_module, sdk_module, exe.root_module); + sdk.wireAppLayout( + b, + resolved_target, + optimize, + app_layout, + dvui_mod, + app_module, + core_module, + sdk_module, + exe.root_module, + ); var dylibs: std.ArrayList(Dylib) = .empty; - const workbench_dylib: ?*std.Build.Step.Compile = if (dynamic_plugins and resolved_target.result.cpu.arch != .wasm32) blk: { + const workbench_dylib: ?*std.Build.Step.Compile = if (dynamic_plugins and + resolved_target.result.cpu.arch != .wasm32) + blk: { break :blk workbench_plugin.addDylib(b, resolved_target, optimize, .{ .dvui = dvui_proxy_mod, .core = core_proxy_module, @@ -372,7 +445,9 @@ pub fn addFizzyExecutableForTarget( }, workbench_opts); } else null; - const text_dylib: ?*std.Build.Step.Compile = if (dynamic_plugins and resolved_target.result.cpu.arch != .wasm32 and plugins.linksFizzys(app_plugins, "text")) blk: { + const text_dylib: ?*std.Build.Step.Compile = if (dynamic_plugins and resolved_target.result.cpu.arch != .wasm32 and + plugins.linksFizzys(app_plugins, "text")) + blk: { break :blk text_plugin.addDylib(b, resolved_target, optimize, .{ .dvui = dvui_proxy_mod, .core = core_proxy_module, @@ -382,7 +457,10 @@ pub fn addFizzyExecutableForTarget( }); } else null; - const markdown_dylib: ?*std.Build.Step.Compile = if (dynamic_plugins and resolved_target.result.cpu.arch != .wasm32 and plugins.linksFizzys(app_plugins, "markdown")) blk: { + const markdown_dylib: ?*std.Build.Step.Compile = if (dynamic_plugins and + resolved_target.result.cpu.arch != .wasm32 and + plugins.linksFizzys(app_plugins, "markdown")) + blk: { break :blk markdown_plugin.addDylib(b, resolved_target, optimize, .{ .dvui = dvui_proxy_mod, .core = core_proxy_module, @@ -391,7 +469,9 @@ pub fn addFizzyExecutableForTarget( }); } else null; - const image_dylib: ?*std.Build.Step.Compile = if (dynamic_plugins and resolved_target.result.cpu.arch != .wasm32 and plugins.linksFizzys(app_plugins, "image")) blk: { + const image_dylib: ?*std.Build.Step.Compile = if (dynamic_plugins and resolved_target.result.cpu.arch != .wasm32 and + plugins.linksFizzys(app_plugins, "image")) + blk: { break :blk image_plugin.addDylib(b, resolved_target, optimize, .{ .dvui = dvui_proxy_mod, .core = core_proxy_module, @@ -419,10 +499,17 @@ pub fn addFizzyExecutableForTarget( } // zig-objc from the backend package's pin, and the platform's Objective-C, which calls back // into the platform's Zig and so is linked here, where the executable reaches it. - if (fizzy_backend.objcModule(backendDependency(b), backendOptions(resolved_target, optimize, macos_sdl_paths))) |objc| { + if (fizzy_backend.objcModule( + backendDependency(b), + backendOptions(resolved_target, optimize, macos_sdl_paths), + )) |objc| { exe.root_module.addImport("objc", objc); } - fizzy_backend.addPlatformObjC(backendDependency(b), exe.root_module, backendOptions(resolved_target, optimize, macos_sdl_paths)); + fizzy_backend.addPlatformObjC( + backendDependency(b), + exe.root_module, + backendOptions(resolved_target, optimize, macos_sdl_paths), + ); } else if (resolved_target.result.os.tag == .windows) { if (fizzy_backend.win32Module(backendDependency(b))) |win32| { exe.root_module.addImport("win32", win32); @@ -451,7 +538,12 @@ pub fn addFizzyExecutableForTarget( resolved_target.result.abi == .msvc; exe.root_module.link_libcpp = !exe_is_windows_msvc; if (velopack_enabled) if (vz) |v| { - try velopack.linkVelopack(b, v, exe, .{ .target = resolved_target, .optimize = optimize, .c_modules = &.{app_module} }); + try velopack.linkVelopack( + b, + v, + exe, + .{ .target = resolved_target, .optimize = optimize, .c_modules = &.{app_module} }, + ); }; return .{ diff --git a/build/package.zig b/build/package.zig index 4d264f985..c39247fe4 100644 --- a/build/package.zig +++ b/build/package.zig @@ -55,7 +55,9 @@ pub fn addSteps(opts: Options) *std.Build.Step { // installer, but the install hook never runs: Setup.exe hangs with "the // application install hook failed". Fail loudly instead of shipping that trap. const windows_non_msvc = target.result.os.tag == .windows and target.result.abi != .msvc; - const failure: ?*std.Build.Step = if (velopack_required_fail) |fail_step| fail_step else if (windows_non_msvc) &b.addFail( + const failure: ?*std.Build.Step = if (velopack_required_fail) |fail_step| + fail_step + else if (windows_non_msvc) &b.addFail( \\`zig build package` for Windows requires the MSVC ABI so Velopack is linked. \\The default native target resolves to x86_64-windows-gnu, which builds a binary \\WITHOUT the Velopack runtime. vpk would still wrap it as a Velopack installer, but @@ -190,7 +192,9 @@ pub fn addSteps(opts: Options) *std.Build.Step { // Without this, Apple's notary service rejects with "signature does not // include a secure timestamp" / "hardened runtime not enabled". vpk_pkg_sh.addArg("--signEntitlements"); - const entitlements_path = b.root.joinString(b.allocator, "assets/macos/Fizzy.entitlements") catch |e| std.debug.panic("entitlements path: {}", .{e}); + const entitlements_path = + b.root.joinString(b.allocator, "assets/macos/Fizzy.entitlements") catch |e| + std.debug.panic("entitlements path: {}", .{e}); vpk_pkg_sh.addArg(entitlements_path); } if (macos_sign_install_identity) |id| { @@ -257,7 +261,12 @@ pub fn addSteps(opts: Options) *std.Build.Step { /// fizzy's own (`build.zig`), or an app's built on fizzy (`fizzy.addPackageSteps`), whose /// `build.zig` passes fizzy its options again in each run. pub fn addPackageAllStep(b: *std.Build, no_emit: bool) void { - const packageall_step = b.step("packageall", "zig build package for each of the six release targets, with this build's -D options but the target's; -Dwindows-msvc-libc= and -Dfetch-msvc reach the *-windows-msvc runs only"); + const packageall_step = b.step( + "packageall", + "zig build package for each of the six release targets, with this build's -D " ++ + "options but the target's; -Dwindows-msvc-libc= and -Dfetch-msvc reach the " ++ + "*-windows-msvc runs only", + ); if (no_emit) { packageall_step.dependOn(&b.addFail("cannot run `packageall` with -Dno-emit").step); return; @@ -283,7 +292,8 @@ pub fn addPackageAllStep(b: *std.Build, no_emit: bool) void { switch (entry.value_ptr.*) { .flag => list.append(b.allocator, b.fmt("-D{s}", .{name})) catch @panic("OOM"), .scalar => |v| list.append(b.allocator, b.fmt("-D{s}={s}", .{ name, v })) catch @panic("OOM"), - .list => |l| for (l.items) |v| list.append(b.allocator, b.fmt("-D{s}={s}", .{ name, v })) catch @panic("OOM"), + .list => |l| for (l.items) |v| + list.append(b.allocator, b.fmt("-D{s}={s}", .{ name, v })) catch @panic("OOM"), // Only a `b.dependency` call passes these, never a command line. .map, .lazy_path, .lazy_path_list => {}, } @@ -311,7 +321,9 @@ pub fn addPackageAllStep(b: *std.Build, no_emit: bool) void { var prev_step: ?*std.Build.Step = null; for (packageall_triples) |triple| { - const zig_pkg_run = b.addSystemCommand(&.{ b.graph.zig_exe, "build", "package", b.fmt("-Dtarget={s}", .{triple}) }); + const zig_pkg_run = b.addSystemCommand( + &.{ b.graph.zig_exe, "build", "package", b.fmt("-Dtarget={s}", .{triple}) }, + ); zig_pkg_run.addArgs(common.items); if (std.mem.endsWith(u8, triple, "-windows-msvc")) zig_pkg_run.addArgs(msvc_only.items); // This build's own build file, from the cwd this build was run in (inherited): a diff --git a/build/plugins.zig b/build/plugins.zig index d001fee6a..2d6c6d219 100644 --- a/build/plugins.zig +++ b/build/plugins.zig @@ -28,7 +28,15 @@ pub const entries = struct { pub const guides: BundledPlugin = .{ .name = "guides" }; /// What fizzy itself links, in its sidebar order. - pub const all = [_]BundledPlugin{ @This().workbench, @This().text, @This().image, @This().archive, @This().markdown, @This().devtools, @This().guides }; + pub const all = [_]BundledPlugin{ + @This().workbench, + @This().text, + @This().image, + @This().archive, + @This().markdown, + @This().devtools, + @This().guides, + }; }; /// Whether `plugins` links fizzy's own `name`, rather than leaving it out or bringing a module of @@ -69,7 +77,11 @@ pub const Linked = struct { /// re-homed onto it. workbench must be listed, because the editor still holds its state /// (fizzyedit/plans#13). pub fn link(b: *std.Build, w: Wiring, plugins: []const BundledPlugin) Linked { - if (!linksFizzys(plugins, "workbench")) std.debug.panic("fizzy: the plugin list must include `fizzy.plugins.workbench`; the editor still holds its state (fizzyedit/plans#13)", .{}); + if (!linksFizzys(plugins, "workbench")) std.debug.panic( + "fizzy: the plugin list must include `fizzy.plugins.workbench`; the editor " ++ + "still holds its state (fizzyedit/plans#13)", + .{}, + ); const workbench_mod = workbench.addStaticModule(b, w.target, w.optimize, .{ .dvui = w.dvui, @@ -78,28 +90,69 @@ pub fn link(b: *std.Build, w: Wiring, plugins: []const BundledPlugin) Linked { .icons = w.icons, .backend = w.backend, }, w.workbench_opts, w.consumer); - const markdown_mod: ?*std.Build.Module = if (w.markdown) markdown.addStaticModule(b, w.target, w.optimize, .{ .dvui = w.dvui, .core = w.core, .sdk = w.sdk }, w.consumer) else null; + const markdown_mod: ?*std.Build.Module = if (w.markdown) markdown.addStaticModule( + b, + w.target, + w.optimize, + .{ .dvui = w.dvui, .core = w.core, .sdk = w.sdk }, + w.consumer, + ) else null; var graph: Rehome = .{ .b = b, .w = w }; defer graph.deinit(); var out: std.ArrayList(BundledPlugin) = .empty; for (plugins) |p| { - const m: ?*std.Build.Module = if (p.module) |src| graph.copy(p.name, src) else if (std.mem.eql(u8, p.name, "workbench")) + const m: ?*std.Build.Module = if (p.module) |src| + graph.copy(p.name, src) + else if (std.mem.eql(u8, p.name, "workbench")) workbench_mod else if (std.mem.eql(u8, p.name, "markdown")) markdown_mod else if (std.mem.eql(u8, p.name, "text")) - text.addStaticModule(b, w.target, w.optimize, .{ .dvui = w.dvui, .core = w.core, .sdk = w.sdk, .icons = w.icons }, w.consumer) + text.addStaticModule( + b, + w.target, + w.optimize, + .{ .dvui = w.dvui, .core = w.core, .sdk = w.sdk, .icons = w.icons }, + w.consumer, + ) else if (std.mem.eql(u8, p.name, "image")) - image.addStaticModule(b, w.target, w.optimize, .{ .dvui = w.dvui, .core = w.core, .sdk = w.sdk, .icons = w.icons }, w.consumer) + image.addStaticModule( + b, + w.target, + w.optimize, + .{ .dvui = w.dvui, .core = w.core, .sdk = w.sdk, .icons = w.icons }, + w.consumer, + ) else if (std.mem.eql(u8, p.name, "archive")) - archive.addStaticModule(b, w.target, w.optimize, .{ .dvui = w.dvui, .core = w.core, .sdk = w.sdk }, w.consumer) + archive.addStaticModule( + b, + w.target, + w.optimize, + .{ .dvui = w.dvui, .core = w.core, .sdk = w.sdk }, + w.consumer, + ) else if (std.mem.eql(u8, p.name, "devtools")) - devtools.addStaticModule(b, w.target, w.optimize, .{ .dvui = w.dvui, .core = w.core, .sdk = w.sdk }, w.consumer) + devtools.addStaticModule( + b, + w.target, + w.optimize, + .{ .dvui = w.dvui, .core = w.core, .sdk = w.sdk }, + w.consumer, + ) else if (std.mem.eql(u8, p.name, "guides")) - guides.addStaticModule(b, w.target, w.optimize, .{ .dvui = w.dvui, .core = w.core, .sdk = w.sdk }, w.consumer) + guides.addStaticModule( + b, + w.target, + w.optimize, + .{ .dvui = w.dvui, .core = w.core, .sdk = w.sdk }, + w.consumer, + ) else - std.debug.panic("fizzy: plugin '{s}' has no module and is not one of fizzy's own (`fizzy.plugins`)", .{p.name}); + std.debug.panic( + "fizzy: plugin '{s}' has no module and is not one of fizzy's own (`fizzy.plugins`)", + .{p.name}, + ); // A plugin this target does not build (markdown for the native exe on wasm) is left out. if (m) |module| { // Only an application's re-homed module is imported here, as `rehome` always did; @@ -165,7 +218,8 @@ const Rehome = struct { fn pinnedElsewhere(self: *const Rehome, m: *const std.Build.Module) bool { if (m.resolved_target) |t| { const mine = self.w.target.result; - if (t.result.cpu.arch != mine.cpu.arch or t.result.os.tag != mine.os.tag or t.result.abi != mine.abi) return true; + if (t.result.cpu.arch != mine.cpu.arch or + t.result.os.tag != mine.os.tag or t.result.abi != mine.abi) return true; } if (m.optimize) |o| if (o != self.w.optimize) return true; return false; @@ -220,7 +274,12 @@ const Rehome = struct { .root_source_file = src.root_source_file, .target = self.w.target, .optimize = self.w.optimize, - .link_libc = if (wasm) false else if (src == self.root) true else (src.link_libc orelse self.w.consumer.link_libc), + .link_libc = if (wasm) + false + else if (src == self.root) + true + else + (src.link_libc orelse self.w.consumer.link_libc), .link_libcpp = src.link_libcpp, .single_threaded = self.w.consumer.single_threaded, .strip = src.strip, @@ -243,11 +302,13 @@ const Rehome = struct { m.export_symbol_names = src.export_symbol_names; for (src.link_objects.items) |object| switch (object) { .c_source_file => |f| { - if (wasm and src.link_libc == true) std.debug.panic("fizzy: web module '{s}' has C sources requiring libc", .{name}); + if (wasm and src.link_libc == true) + std.debug.panic("fizzy: web module '{s}' has C sources requiring libc", .{name}); m.addCSourceFile(f.*); }, .c_source_files => |f| { - if (wasm and src.link_libc == true) std.debug.panic("fizzy: web module '{s}' has C sources requiring libc", .{name}); + if (wasm and src.link_libc == true) + std.debug.panic("fizzy: web module '{s}' has C sources requiring libc", .{name}); m.addCSourceFiles(.{ .root = f.root, .files = f.files, .flags = f.flags, .language = f.language }); }, .static_path => |p| m.addObjectFile(p), @@ -261,7 +322,11 @@ const Rehome = struct { }), .win32_resource_file => |f| m.addWin32ResourceFile(f.*), // A compile step is built for one target and cannot follow the module to another. - .other_step => std.debug.panic("fizzy: module '{s}' links a compile step, which cannot be re-homed to another target", .{name}), + .other_step => std.debug.panic( + "fizzy: module '{s}' links a compile step, which cannot be re-homed to another " ++ + "target", + .{name}, + ), }; for (src.include_dirs.items) |dir| switch (dir) { .path => |p| m.addIncludePath(p), @@ -301,7 +366,9 @@ const Rehome = struct { self.root = src; const m = self.copyAnalyzed(name, src); for (framework_names) |import_name| { - if (m.import_table.get(import_name) == null) if (self.framework(import_name)) |fw| m.addImport(import_name, fw); + if (m.import_table.get(import_name) == null) { + if (self.framework(import_name)) |fw| m.addImport(import_name, fw); + } } return m; } diff --git a/build/rehome_fixture.zig b/build/rehome_fixture.zig index 4ec996aab..4f63f8bb4 100644 --- a/build/rehome_fixture.zig +++ b/build/rehome_fixture.zig @@ -13,11 +13,16 @@ var chosen: ?Fault = null; fn fault(b: *std.Build) Fault { if (chosen) |f| return f; - chosen = b.option(Fault, "rehome-fixture-fault", "Break the rehome fixture on purpose (libc, cycle, plain); the build must fail") orelse .none; + chosen = b.option( + Fault, + "rehome-fixture-fault", + "Break the rehome fixture on purpose (libc, cycle, plain); the build must fail", + ) orelse .none; return chosen.?; } -/// root -> child (dvui, C sources, framework-free "plain" module, and a way back to root) and root -> same_child. +/// root -> child (dvui, C sources, framework-free "plain" module, and a way back to root) and root +/// -> same_child. pub fn graph(b: *std.Build, target: std.Build.ResolvedTarget, optimize: std.lang.OptimizeMode) *std.Build.Module { const old_dvui = sdk.dvuiDependency(b, .{ .target = target, @@ -57,7 +62,8 @@ fn expect(ok: bool, what: []const u8) void { } /// `source` is what `create` made, `copy` what `rehome` made of it. `same_target` is whether the -/// copy was made for the target the source is pinned to, so its framework-free module may be shared. +/// copy was made for the target the source is pinned to, so its framework-free module may be +/// shared. pub fn check(b: *std.Build, source: *std.Build.Module, copy: *std.Build.Module, same_target: bool) void { const f = fault(b); const source_child = source.import_table.get("child").?; @@ -67,5 +73,8 @@ pub fn check(b: *std.Build, source: *std.Build.Module, copy: *std.Build.Module, const back = copied_child.import_table.get("back").?; expect(if (f == .cycle) back == source else back == copy, "a cycle maps to the copy of its root"); const shared = copied_child.import_table.get("plain") == source_child.import_table.get("plain"); - expect(if (f == .plain) shared != same_target else shared == same_target, "a framework-free module is shared only where the target is the same"); + expect( + if (f == .plain) shared != same_target else shared == same_target, + "a framework-free module is shared only where the target is the same", + ); } diff --git a/build/sdk.zig b/build/sdk.zig index 04d528b03..40e333e70 100644 --- a/build/sdk.zig +++ b/build/sdk.zig @@ -9,7 +9,8 @@ const std = @import("std"); /// /// Worth the indirection because the two are not free to disagree. dvui types reachable from the /// plugin boundary feed `dylib.sdk_shape_fingerprint`, which both the app build and the plugin-SDK -/// build check against the single `recorded_sdk_shape_fingerprint` literal in `sdk/src/version.zig`. +/// build check against the single `recorded_sdk_shape_fingerprint` literal in +/// `sdk/src/version.zig`. /// When each build compiled a different dvui, they computed different fingerprints from that one /// literal and no value satisfied both — every fix broke the other side, and the error blamed /// `sdk_version`, which a bump cannot repair. One pin makes that state unreachable rather than @@ -118,7 +119,10 @@ pub fn wireAppModule( app_module.addImport("crash", crashModule(b, target, optimize)); // The remote-view protocol (`sdk/remote/`): std-only, so a host application's adapter links the // same files. - app_module.addImport("remote", b.createModule(.{ .target = target, .optimize = optimize, .root_source_file = b.path("sdk/remote/root.zig") })); + app_module.addImport( + "remote", + b.createModule(.{ .target = target, .optimize = optimize, .root_source_file = b.path("sdk/remote/root.zig") }), + ); if (consumer) |c| { c.addImport("app", app_module); // The bundled demos' scripts (`src/editor/demos/`) need only `tape`, so they build on the @@ -183,7 +187,10 @@ pub const BundledPlugin = struct { /// The entry as `platform`'s executables link it, or null if they do not. pub fn forPlatform(self: BundledPlugin, platform: Platform) ?BundledPlugin { const targets = self.targets orelse return self; - for (targets) |t| if (t.target == platform) return .{ .name = self.name, .module = t.module orelse self.module }; + for (targets) |t| if (t.target == platform) return .{ + .name = self.name, + .module = t.module orelse self.module, + }; return null; } }; @@ -213,7 +220,11 @@ pub fn bundledPluginsModule( plugins: []const BundledPlugin, ) *std.Build.Module { var src = std.ArrayList(u8).empty; - src.appendSlice(b.allocator, "//! Generated by the build: the plugins this application links in.\npub const modules = .{\n") catch @panic("OOM"); + src.appendSlice( + b.allocator, + "//! Generated by the build: the plugins this application links in.\npub const " ++ + "modules = .{\n", + ) catch @panic("OOM"); for (plugins) |p| { if (p.module == null) continue; src.appendSlice(b.allocator, " @import(\"") catch @panic("OOM"); diff --git a/build/set_aside.zig b/build/set_aside.zig index 3a43137c5..2e52b5a47 100644 --- a/build/set_aside.zig +++ b/build/set_aside.zig @@ -21,7 +21,11 @@ pub fn main(init: std.process.Init) !void { // Held: a running app. Renamed, the install's path is free. const aside = try arena.print("{s}.{d}.old", .{ path, std.Io.Clock.real.now(io).nanoseconds }); std.Io.Dir.rename(cwd, path, cwd, aside, io) catch |rename_err| { - std.log.warn("{s} is in use and could not be set aside ({t}); the install will fail until it quits", .{ path, rename_err }); + std.log.warn( + "{s} is in use and could not be set aside ({t}); the install will fail until it " ++ + "quits", + .{ path, rename_err }, + ); return; }; std.log.info("{s} is running: set aside as {s}", .{ path, std.fs.path.basename(aside) }); diff --git a/build/velopack.zig b/build/velopack.zig index 9d965d875..335fa1d54 100644 --- a/build/velopack.zig +++ b/build/velopack.zig @@ -79,7 +79,12 @@ pub fn addVpkRun(b: *std.Build, vz: Dep, target: std.Build.ResolvedTarget) !*std /// `Velopack.h` as a Zig module, through translate-c: Zig 0.17 removed `@cImport`. Imported as /// `velopack_c` by the modules `linkVelopack` is given. -fn velopackCModule(b: *std.Build, vz: Dep, target: std.Build.ResolvedTarget, optimize: std.lang.OptimizeMode) *std.Build.Module { +fn velopackCModule( + b: *std.Build, + vz: Dep, + target: std.Build.ResolvedTarget, + optimize: std.lang.OptimizeMode, +) *std.Build.Module { const tc: Translator = .init(b.dependency("translate_c", .{}), .{ .c_source_file = vz.builder.dependency("velopack", .{}).path("include/Velopack.h"), .target = target, @@ -249,7 +254,10 @@ pub fn resolveWindowsMsvcLibc( return .{ .libc_path = null, .needs_setup = false }; if (opts.explicit_path) |p| { - const abs = if (std.fs.path.isAbsolute(p)) b.dupePath(p) else (b.root.joinString(b.allocator, p) catch @panic("OOM")); + const abs = if (std.fs.path.isAbsolute(p)) + b.dupePath(p) + else + (b.root.joinString(b.allocator, p) catch @panic("OOM")); b.dependOnFileContents(b.graph.cwdRelativePath(abs)); return .{ .libc_path = abs, .needs_setup = false }; } @@ -267,8 +275,14 @@ pub fn resolveWindowsMsvcLibc( break :blk true; }; - if (exists) return .{ .libc_path = (b.root.joinString(b.allocator, rel) catch @panic("OOM")), .needs_setup = false }; - if (opts.fetch_if_missing) return .{ .libc_path = (b.root.joinString(b.allocator, rel) catch @panic("OOM")), .needs_setup = true }; + if (exists) return .{ + .libc_path = (b.root.joinString(b.allocator, rel) catch @panic("OOM")), + .needs_setup = false, + }; + if (opts.fetch_if_missing) return .{ + .libc_path = (b.root.joinString(b.allocator, rel) catch @panic("OOM")), + .needs_setup = true, + }; return .{ .libc_path = null, .needs_setup = false }; } diff --git a/build/web.zig b/build/web.zig index 3163a0a5c..01ac9c1ef 100644 --- a/build/web.zig +++ b/build/web.zig @@ -73,7 +73,16 @@ pub fn framework(b: *std.Build, optimize: std.lang.OptimizeMode) Framework { .single_threaded = true, }); // What the page calls into (`web/web.js`): dvui's web exports. - web_backend.export_symbol_names = &.{ "dvui_init", "dvui_deinit", "dvui_update", "add_event", "arena_u8", "gpa_u8", "gpa_free", "new_font" }; + web_backend.export_symbol_names = &.{ + "dvui_init", + "dvui_deinit", + "dvui_update", + "add_event", + "arena_u8", + "gpa_u8", + "gpa_free", + "new_font", + }; web_backend.addImport("dvui", dvui_web); dvui_web.addImport("backend", web_backend); const dvui_web_proxy_bridge = sdk.addProxyBridgeModule(b, web_target, optimize, dvui_web_dep, dvui_web); @@ -86,7 +95,16 @@ pub fn framework(b: *std.Build, optimize: std.lang.OptimizeMode) Framework { .link_libc = false, .single_threaded = true, }); - const icons_web = core_mod.addImports(b, core_module_web, dvui_web, b.dependency("fizzy_sdk", .{}).builder, web_target, optimize, null, null); + const icons_web = core_mod.addImports( + b, + core_module_web, + dvui_web, + b.dependency("fizzy_sdk", .{}).builder, + web_target, + optimize, + null, + null, + ); return .{ .dvui_dep = dvui_web_dep, .dvui = dvui_web, @@ -135,7 +153,10 @@ pub fn build( web_exe.entry = .disabled; web_exe.root_module.addImport("dvui", dvui_web); // A page has one canvas: `backend_web.viewports`. - web_exe.root_module.addImport("viewports_none", @import("fizzy_backend").viewportsNoneModule(@import("exe.zig").backendDependency(b), dvui_web)); + web_exe.root_module.addImport( + "viewports_none", + @import("fizzy_backend").viewportsNoneModule(@import("exe.zig").backendDependency(b), dvui_web), + ); web_exe.root_module.addImport("web-backend", web_backend); // Extra wasm exports beyond dvui's own (`dvui_init`/`dvui_update`/etc.). The wasm @@ -253,7 +274,10 @@ pub fn build( // And the named dependencies, resolved directly for wasm. for (web_plugin_deps) |dep_name| { const dep = b.lazyDependency(dep_name, .{ .target = web_target, .optimize = optimize }) orelse continue; - bundled_list.append(b.allocator, .{ .name = dep_name, .module = plugins.rehome(b, wiring, dep_name, dep.module("plugin")) }) catch @panic("OOM"); + bundled_list.append( + b.allocator, + .{ .name = dep_name, .module = plugins.rehome(b, wiring, dep_name, dep.module("plugin")) }, + ) catch @panic("OOM"); } // A plugin may ship pages of its own beside the app (its `web/` directory → `plugins//`): // the far end of a popup round trip that is the plugin's, not fizzy's — a provider's @@ -301,9 +325,32 @@ pub fn build( // The `app` framework module (the plugin store). Wired exactly as the native build wires // it — one helper, so the two cannot drift. - const app_module_web = sdk.wireAppModule(b, web_target, optimize, dvui_web, core_module_web, sdk_module_web, icons_web, markdown_module_web, null, build_opts, null, web_exe.root_module); + const app_module_web = sdk.wireAppModule( + b, + web_target, + optimize, + dvui_web, + core_module_web, + sdk_module_web, + icons_web, + markdown_module_web, + null, + build_opts, + null, + web_exe.root_module, + ); app_module_web.addImport("bundled_plugins", bundled_web); - sdk.wireAppLayout(b, web_target, optimize, app_layout, dvui_web, app_module_web, core_module_web, sdk_module_web, web_exe.root_module); + sdk.wireAppLayout( + b, + web_target, + optimize, + app_layout, + dvui_web, + app_module_web, + core_module_web, + sdk_module_web, + web_exe.root_module, + ); // Cache-buster: stamps a 64-char hash into the index.html / web.js placeholders so // the browser picks up new wasm builds without manual hard-reloads. Re-implements @@ -355,7 +402,9 @@ pub fn build( const rehome_check: ?*std.Build.Step.Compile = if (own) blk: { const fixture = rehome_fixture.graph(b, b.resolveTargetQuery(.{}), optimize); var fixture_wiring = wiring; - fixture_wiring.consumer = b.createModule(.{ .target = web_target, .optimize = optimize, .link_libc = false, .single_threaded = true }); + fixture_wiring.consumer = b.createModule( + .{ .target = web_target, .optimize = optimize, .link_libc = false, .single_threaded = true }, + ); const module = plugins.rehome(b, fixture_wiring, "rehome_fixture", fixture); rehome_fixture.check(b, fixture, module, false); break :blk b.addObject(.{ .name = "rehome-fixture-web", .root_module = module }); @@ -433,14 +482,24 @@ pub fn install(b: *std.Build, web: Web) void { /// the browser's storage for it, so a test build served beside the real one — fizzyed.it/testapp/ /// beside /app/ — is built with a name of its own and keeps out of the real one's settings. fn stampStorage(b: *std.Build) std.Build.LazyPath { - const name = b.option([]const u8, "web-storage", "What the web app files its browser storage under (default \"fizzy\"); give a test build served beside the real one a name of its own") orelse "fizzy"; + const name = b.option( + []const u8, + "web-storage", + "What the web app files its browser storage under (default \"fizzy\"); give a " ++ + "test build served beside the real one a name of its own", + ) orelse "fizzy"; for (name) |c| switch (c) { 'a'...'z', '0'...'9', '-', '_' => {}, else => std.debug.panic("-Dweb-storage={s}: only a-z, 0-9, '-' and '_'", .{name}), }; const token = "__FIZZY_WEB_STORAGE__"; b.dependOnFileContents(b.path("web/index.html")); - const html = std.Io.Dir.cwd().readFileAlloc(b.graph.io, b.root.joinString(b.allocator, "web/index.html") catch @panic("OOM"), b.allocator, .unlimited) catch |err| + const html = std.Io.Dir.cwd().readFileAlloc( + b.graph.io, + b.root.joinString(b.allocator, "web/index.html") catch @panic("OOM"), + b.allocator, + .unlimited, + ) catch |err| std.debug.panic("web/index.html: {t}", .{err}); if (std.mem.find(u8, html, token) == null) std.debug.panic("web/index.html: no {s} to stamp", .{token}); const stamped = std.mem.replaceOwned(u8, b.allocator, html, token, name) catch @panic("OOM"); From 6dbe77af4d1ab0719cc0e605feab10a866d7f627 Mon Sep 17 00:00:00 2001 From: "fizzy-linux[bot]" <346940526+fizzy-linux[bot]@users.noreply.github.com> Date: Sun, 25 Oct 2026 23:41:15 -0500 Subject: [PATCH 5/9] lint: line-length (codex) Agent-Team: linux Worker: codex --- build.zig | 45 ++++- build/app.zig | 480 +++++++++++++++++++++++++++++++++++++++-------- scripts/lint.zig | 5 +- 3 files changed, 446 insertions(+), 84 deletions(-) diff --git a/build.zig b/build.zig index 7d1d47f83..60b6ac9fc 100644 --- a/build.zig +++ b/build.zig @@ -169,7 +169,9 @@ pub const Platform = @import("build/sdk.zig").Platform; /// const hello = b.dependency("hello", .{}); /// try fizzy.buildApp(fizzy_dep, &.{ /// fizzy.plugins.workbench, -/// .{ .name = "hello", .module = @import("hello").staticModule(hello.builder, fizzy.framework(fizzy_dep, .native)) }, +/// .{ .name = "hello", .module = @import("hello").staticModule( +/// hello.builder, fizzy.framework(fizzy_dep, .native), +/// ) }, /// }); /// ``` pub fn framework(fizzy_dep: *std.Build.Dependency, platform: Platform) Framework { @@ -179,11 +181,25 @@ pub fn framework(fizzy_dep: *std.Build.Dependency, platform: Platform) Framework switch (platform) { .native => { const fw = d.frameworks.getNative(b, d.target, d.optimize, d.cfg); - return .{ .target = d.target, .optimize = d.optimize, .dvui = fw.native.dvui, .core = fw.core, .sdk = fw.sdk, .icons = fw.icons }; + return .{ + .target = d.target, + .optimize = d.optimize, + .dvui = fw.native.dvui, + .core = fw.core, + .sdk = fw.sdk, + .icons = fw.icons, + }; }, .web => { const fw = d.frameworks.getWeb(b, d.optimize); - return .{ .target = @import("build/web.zig").target(b), .optimize = d.optimize, .dvui = fw.dvui, .core = fw.core, .sdk = fw.sdk, .icons = fw.icons }; + return .{ + .target = @import("build/web.zig").target(b), + .optimize = d.optimize, + .dvui = fw.dvui, + .core = fw.core, + .sdk = fw.sdk, + .icons = fw.icons, + }; }, } } @@ -244,17 +260,29 @@ pub const App = struct { /// const app = try fizzy.addApp(fizzy_dep, &.{ /// fizzy.plugins.workbench, /// .{ .name = "hello", .targets = &.{ -/// .{ .target = .native, .module = hello.staticModule(hello_dep.builder, fizzy.framework(fizzy_dep, .native)) }, -/// .{ .target = .web, .module = hello.staticModule(hello_dep.builder, fizzy.framework(fizzy_dep, .web)) }, +/// .{ .target = .native, .module = hello.staticModule( +/// hello_dep.builder, fizzy.framework(fizzy_dep, .native), +/// ) }, +/// .{ .target = .web, .module = hello.staticModule( +/// hello_dep.builder, fizzy.framework(fizzy_dep, .web), +/// ) }, /// } }, -/// .{ .name = "shell", .module = shell_dep.module("plugin"), .targets = &.{.{ .target = .native }} }, +/// .{ .name = "shell", .module = shell_dep.module("plugin"), +/// .targets = &.{.{ .target = .native }} }, /// }); /// ``` pub fn addApp(fizzy_dep: *std.Build.Dependency, linked: []const BundledPlugin) !App { const d = deferred orelse @panic("fizzy.addApp: pass `.@\"defer-app\" = true` to b.dependency(\"fizzy\", …) first"); var opts = d.base; opts.app_plugins = try withFizzys(fizzy_dep.builder, linked); - const built = try @import("build/app.zig").construct(fizzy_dep.builder, d.target, d.optimize, opts, d.cfg, &deferred.?.frameworks); + const built = try @import("build/app.zig").construct( + fizzy_dep.builder, + d.target, + d.optimize, + opts, + d.cfg, + &deferred.?.frameworks, + ); deferred.?.built = true; return .{ .exe = built.native.main.exe, @@ -354,7 +382,8 @@ pub fn addWebSteps(b: *std.Build, fizzy_dep: *std.Build.Dependency) !void { std.log.err("fizzy.addWebSteps: this build already has a `web` step", .{}); return error.WebStepTaken; } - const d = deferred orelse @panic("fizzy.addWebSteps: pass `.@\"defer-app\" = true` to b.dependency(\"fizzy\", …) and call fizzy.buildApp first"); + const d = deferred orelse @panic("fizzy.addWebSteps: pass `.@\"defer-app\" = true` to b.dependency(\"fizzy\"," ++ + " …) and call fizzy.buildApp first"); if (!d.built) @panic("fizzy.addWebSteps: call fizzy.buildApp first"); b.step("web", "Build the web (wasm) app into zig-out/web/").dependOn(&b.addInstallDirectory(.{ .source_dir = fizzy_dep.namedLazyPath("web"), diff --git a/build/app.zig b/build/app.zig index bc774487a..1085e4c31 100644 --- a/build/app.zig +++ b/build/app.zig @@ -167,12 +167,36 @@ pub const Own = struct { /// and a plist vpk writes. `-Dapp-windows-rc` / `-Dapp-macos-plist` override either. fn readBranding(b: *std.Build, id: Identity, own: ?Own, renamed: bool) Branding { const LazyPath = std.Build.LazyPath; - const png_opt = b.option(LazyPath, "app-icon", "PNG icon: the window's, the infobar's and the Linux package's (default: fizzy's)"); - const ico_opt = b.option(LazyPath, "app-icon-ico", "Windows .ico: the installer's, the Start Menu shortcut's, and the generated resource script's (default: fizzy's)"); + const png_opt = b.option( + LazyPath, + "app-icon", + "PNG icon: the window's, the infobar's and the Linux package's (default: fizzy's)", + ); + const ico_opt = b.option( + LazyPath, + "app-icon-ico", + "Windows .ico: the installer's, the Start Menu shortcut's, and the generated" ++ + " resource script's (default: fizzy's)", + ); const icns_opt = b.option(LazyPath, "app-icon-icns", "macOS .icns: the bundle's icon (default: fizzy's)"); - const rc_opt = b.option(LazyPath, "app-windows-rc", "Windows resource script embedded in the exe (default: fizzy's for fizzy, else one generated from the icon, names and version)"); - const plist_opt = b.option(LazyPath, "app-macos-plist", "macOS Info.plist (default: fizzy's for fizzy, else one vpk writes from app-bundle-id)"); - const package_title = b.option([]const u8, "app-package-title", "The installers' name for the app: the macOS bundle, the Windows shortcut (default: app-name)") orelse id.name; + const rc_opt = b.option( + LazyPath, + "app-windows-rc", + "Windows resource script embedded in the exe (default: fizzy's for fizzy, " ++ + "else one generated from the icon, names and version)", + ); + const plist_opt = b.option( + LazyPath, + "app-macos-plist", + "macOS Info.plist (default: fizzy's for fizzy, else one vpk writes from " ++ + "app-bundle-id)", + ); + const package_title = b.option( + []const u8, + "app-package-title", + "The installers' name for the app: the macOS bundle, the Windows shortcut " ++ + "(default: app-name)", + ) orelse id.name; const own_set = if (renamed or png_opt != null or ico_opt != null or icns_opt != null) null else own; if (own_set) |o| return .{ .png = b.path(o.png), @@ -200,7 +224,11 @@ fn readBranding(b: *std.Build, id: Identity, own: ?Own, renamed: bool) Branding fn windowsRc(b: *std.Build, id: Identity, ico: std.Build.LazyPath) std.Build.LazyPath { // `v1.2.3-dev+5` → 1,2,3,0: up to four numeric parts, anything else 0. var parts = [4]u16{ 0, 0, 0, 0 }; - const core = std.mem.trimStart(u8, id.version[0 .. std.mem.findAny(u8, id.version, "-+") orelse id.version.len], "vV"); + const core = std.mem.trimStart( + u8, + id.version[0 .. std.mem.findAny(u8, id.version, "-+") orelse id.version.len], + "vV", + ); var it = std.mem.splitScalar(u8, core, '.'); for (&parts) |*part| part.* = std.fmt.parseInt(u16, it.next() orelse break, 10) catch 0; // An rc string literal holds no control characters (they are dropped), doubles its quotes @@ -297,7 +325,8 @@ pub fn readConfig(b: *std.Build, target: std.Build.ResolvedTarget, opts: Options .fetch_if_missing = fetch_msvc, }); - // Only `-Dwindows-msvc-libc` or the managed `.velopack-msvc/` tree: Zig 0.17 has no global `--libc`. + // Only `-Dwindows-msvc-libc` or the managed `.velopack-msvc/` tree: Zig 0.17 has no global + // `--libc`. const effective_win_libc: ?[]const u8 = win_libc.libc_path; // Velopack in the dev/install exe is opt-in (`-Dvelopack=true`). Release @@ -309,12 +338,14 @@ pub fn readConfig(b: *std.Build, target: std.Build.ResolvedTarget, opts: Options const velopack_enabled = b.option( bool, "velopack", - "Link Velopack runtime in the install/run exe (auto-update). Default: false. `package` still produces a Velopack-linked binary when supported.", + "Link Velopack runtime in the install/run exe (auto-update). Default: false." ++ + " `package` still produces a Velopack-linked binary when supported.", ) orelse false; if (velopack_enabled and !velopack_supported_for_target) { std.log.err( - "-Dvelopack=true is unsupported for target ABI {s}: Velopack on Windows requires -Dtarget=x86_64-windows-msvc or -Dtarget=aarch64-windows-msvc.", + "-Dvelopack=true is unsupported for target ABI {s}: Velopack on Windows " ++ + "requires -Dtarget=x86_64-windows-msvc or -Dtarget=aarch64-windows-msvc.", .{@tagName(target.result.abi)}, ); return error.WindowsMsvcAbiRequired; @@ -327,26 +358,35 @@ pub fn readConfig(b: *std.Build, target: std.Build.ResolvedTarget, opts: Options // with `-Dfetch-msvc=false` — in which case Zig falls back to system Visual Studio // auto-detection, which we can't validate here. const velopack_required_fail: ?*std.Build.Step = if (cross_win_msvc and effective_win_libc == null) - &b.addFail( - \\*-windows-msvc needs MSVC + Windows SDK headers/libs. - \\ One-shot install (macOS/Linux/Windows): zig build msvcup-setup - \\ Then: zig build package -Dtarget=x86_64-windows-msvc (auto-uses .velopack-msvc/zig-libc-x64.ini) - \\ Or auto-download in this build: add -Dfetch-msvc (default on Windows hosts; forwards through packageall) - \\ Or pass: --libc path.ini / -Dwindows-msvc-libc=path.ini - ).step + &b.addFail("*-windows-msvc needs MSVC + Windows SDK headers/libs.\n" ++ + " One-shot install (macOS/Linux/Windows): zig build msvcup-setup\n" ++ + " Then: zig build package -Dtarget=x86_64-windows-msvc (auto-uses .velopack-msvc/zig-libc-x64.ini)\n" ++ + " Or auto-download in this build: add -Dfetch-msvc (default on " ++ + "Windows hosts; forwards through packageall)\n" ++ + " Or pass: --libc path.ini / -Dwindows-msvc-libc=path.ini").step else null; const no_emit = b.option(bool, "no-emit", "Check for compile errors without emitting any code") orelse false; - const app_version_opt = b.option([]const u8, "app_version", "App version for vpk packVersion and startup log (default: fizzy's VERSION file for fizzy, else 0.0.0)"); + const app_version_opt = b.option( + []const u8, + "app_version", + "App version for vpk packVersion and startup log (default: fizzy's VERSION " ++ + "file for fizzy, else 0.0.0)", + ); // GitHub repo URL baked into the binary so Velopack's auto-update can find // the latest release via the GitHub Releases API. Override at build time // with `-Drepo-url=...` (e.g. when shipping a fork). At runtime, the env // var `FIZZY_AUTOUPDATE_URL` still overrides this for local feed testing. // An app built on fizzy has no feed unless it names one: fizzy's releases are not its updates. - const repo_url_opt = b.option([]const u8, "repo-url", "GitHub repo URL used by Velopack auto-update (default: fizzy's for fizzy, else none)"); + const repo_url_opt = b.option( + []const u8, + "repo-url", + "GitHub repo URL used by Velopack auto-update (default: fizzy's for fizzy, " ++ + "else none)", + ); const app_repo_url = repo_url_opt orelse if (opts.own) |own| own.repo_url else ""; // Comma-separated fallback repo URLs checked (in order) after `app_repo_url` @@ -354,7 +394,12 @@ pub fn readConfig(b: *std.Build, target: std.Build.ResolvedTarget, opts: Options // whose primary points at the new home and whose fallback points at the old // one (where the transitional release is published), then transfer the repo. // Empty by default (no fallback). - const app_repo_url_fallback = b.option([]const u8, "repo-url-fallback", "Comma-separated fallback GitHub repo URLs for Velopack auto-update, tried after -Drepo-url") orelse ""; + const app_repo_url_fallback = b.option( + []const u8, + "repo-url-fallback", + "Comma-separated fallback GitHub repo URLs for Velopack auto-update, tried " ++ + "after -Drepo-url", + ) orelse ""; var version_owned: ?[]u8 = null; // lives as long as the build process @@ -362,7 +407,12 @@ pub fn readConfig(b: *std.Build, target: std.Build.ResolvedTarget, opts: Options // package's, so its VERSION file is out of reach here), else 0.0.0. const app_version: []const u8 = if (app_version_opt) |v| v else if (opts.own) |own| blk: { b.dependOnFileContents(b.path(own.version_file)); - const raw = std.Io.Dir.cwd().readFileAlloc(b.graph.io, b.root.joinString(b.allocator, own.version_file) catch @panic("OOM"), b.allocator, std.Io.Limit.limited(256)) catch |e| std.debug.panic("read VERSION: {}", .{e}); + const raw = std.Io.Dir.cwd().readFileAlloc( + b.graph.io, + b.root.joinString(b.allocator, own.version_file) catch @panic("OOM"), + b.allocator, + std.Io.Limit.limited(256), + ) catch |e| std.debug.panic("read VERSION: {}", .{e}); version_owned = raw; break :blk std.mem.trimEnd(u8, raw, "\r\n"); } else "0.0.0"; @@ -372,16 +422,41 @@ pub fn readConfig(b: *std.Build, target: std.Build.ResolvedTarget, opts: Options // Application identity (see app/AppInfo.zig). Options rather than literals so an app built // on fizzy as a library can set them; fizzy passes its own values. - const app_name_opt = b.option([]const u8, "app-name", "Short lowercase app identifier (exe name, packId, config dir)"); + const app_name_opt = b.option( + []const u8, + "app-name", + "Short lowercase app identifier (exe name, packId, config dir)", + ); const app_name = app_name_opt orelse "fizzy"; const app_display_name = b.option([]const u8, "app-display-name", "Human-facing application name") orelse "Fizzy"; // An app that names no bundle id gets one of its own, not fizzy's: `dev.fizzy.`, // with anything a bundle id cannot hold made `-`. - const app_bundle_id = b.option([]const u8, "app-bundle-id", "Reverse-DNS application identifier (default: fizzy's for fizzy, else dev.fizzy.)") orelse - if (opts.own) |own| own.bundle_id else b.fmt("dev.fizzy.{s}", .{@import("bundle_id.zig").component(b.allocator, app_name)}); - const app_config_dir = b.option([]const u8, "app-config-dir", "Config directory name (defaults to app-name)") orelse app_name; - const app_registry_url = b.option([]const u8, "app-registry-url", "Plugin registry catalog URL; empty disables the store") orelse "https://plugins.fizzyed.it/catalog"; - const branding = readBranding(b, .{ .name = app_name, .display_name = app_display_name, .version = app_version }, opts.own, app_name_opt != null); + const app_bundle_id = b.option( + []const u8, + "app-bundle-id", + "Reverse-DNS application identifier (default: fizzy's for fizzy, else " ++ + "dev.fizzy.)", + ) orelse + if (opts.own) |own| own.bundle_id else b.fmt( + "dev.fizzy.{s}", + .{@import("bundle_id.zig").component(b.allocator, app_name)}, + ); + const app_config_dir = b.option( + []const u8, + "app-config-dir", + "Config directory name (defaults to app-name)", + ) orelse app_name; + const app_registry_url = b.option( + []const u8, + "app-registry-url", + "Plugin registry catalog URL; empty disables the store", + ) orelse "https://plugins.fizzyed.it/catalog"; + const branding = readBranding( + b, + .{ .name = app_name, .display_name = app_display_name, .version = app_version }, + opts.own, + app_name_opt != null, + ); build_opts.addOption([]const u8, "app_name", app_name); build_opts.addOption([]const u8, "app_display_name", app_display_name); build_opts.addOption([]const u8, "app_bundle_id", app_bundle_id); @@ -395,7 +470,12 @@ pub fn readConfig(b: *std.Build, target: std.Build.ResolvedTarget, opts: Options const app_is_fizzy = opts.own != null; // Where Report a Bug goes. Not `repo-url` as such, which is the update feed and defaults to // fizzy's: an app reports to its own repo only when it named one (`issues_url.zig`). - const app_issues_url = b.option([]const u8, "app-issues-url", "Where Report a Bug goes; empty disables it (default: an explicit repo-url's issues, fizzy's for fizzy)") orelse + const app_issues_url = b.option( + []const u8, + "app-issues-url", + "Where Report a Bug goes; empty disables it (default: an explicit repo-url's" ++ + " issues, fizzy's for fizzy)", + ) orelse try @import("issues_url.zig").derive(b.allocator, repo_url_opt, app_is_fizzy); const app_repo_url_set = repo_url_opt != null and repo_url_opt.?.len > 0; build_opts.addOption(bool, "app_is_fizzy", app_is_fizzy); @@ -405,13 +485,22 @@ pub fn readConfig(b: *std.Build, target: std.Build.ResolvedTarget, opts: Options // Off, the app runs only the plugins it links (`buildApp`'s list): no plugin dylib is built, // and nothing loads at runtime — not fizzy's own dylibs, not the user plugins directory, not // the store, not a web page's `?plugin=`. Comptime, so none of it is checked per frame. - const dynamic_plugins = b.option(bool, "dynamic-plugins", "Load plugins at runtime: fizzy's own dylibs, the user plugins directory, the store (default: true)") orelse true; + const dynamic_plugins = b.option( + bool, + "dynamic-plugins", + "Load plugins at runtime: fizzy's own dylibs, the user plugins directory, " ++ + "the store (default: true)", + ) orelse true; build_opts.addOption(bool, "dynamic_plugins", dynamic_plugins); // A consumer that wants its own shape passes `-Dapp-layout=` (a LazyPath to // `pub fn layout(?*anyopaque, *Layout)`). Fizzy itself uses `src/editor/layout.zig`. // There is no `-Dlayout=` enum of shipped presets: the shapes are fizzyedit/example-app's. - const app_layout_path = b.option(std.Build.LazyPath, "app-layout", "App-owned layout file (pub fn layout(?*anyopaque, *Layout))"); + const app_layout_path = b.option( + std.Build.LazyPath, + "app-layout", + "App-owned layout file (pub fn layout(?*anyopaque, *Layout))", + ); build_opts.addOption(bool, "has_app_layout", app_layout_path != null); const static_workbench = b.option( bool, @@ -439,8 +528,15 @@ pub fn readConfig(b: *std.Build, target: std.Build.ResolvedTarget, opts: Options const workbench_opts = b.addOptions(); workbench_opts.addOption(bool, "file_tree", workbench_file_tree); - const msvcup_before_compile: ?*std.Build.Step.Run = if (vz) |v| velopack.addMsvcupSetupStep(b, v, ".velopack-msvc") else null; - const msvcup_setup_step = b.step("msvcup-setup", "Download MSVC SDK into .velopack-msvc/ via velopack-zig (writes zig-libc-*.ini)"); + const msvcup_before_compile: ?*std.Build.Step.Run = if (vz) |v| velopack.addMsvcupSetupStep( + b, + v, + ".velopack-msvc", + ) else null; + const msvcup_setup_step = b.step( + "msvcup-setup", + "Download MSVC SDK into .velopack-msvc/ via velopack-zig (writes zig-libc-*.ini)", + ); if (msvcup_before_compile) |m| msvcup_setup_step.dependOn(&m.step); const accesskit = b.option(dvui.AccesskitOptions, "accesskit", "Enable accesskit") orelse .off; @@ -448,7 +544,8 @@ pub fn readConfig(b: *std.Build, target: std.Build.ResolvedTarget, opts: Options const native_backend = b.option( fizzy_exe.NativeBackend, "native-backend", - "Native renderer: fizzy (fizzy's own SDL_GPU backend, custom programs) or sdl3 (dvui's SDL_Renderer backend). Default: fizzy", + "Native renderer: fizzy (fizzy's own SDL_GPU backend, custom programs) or " ++ + "sdl3 (dvui's SDL_Renderer backend). Default: fizzy", ) orelse fizzy_exe.defaultNativeBackend(target); const test_filters = b.option( @@ -459,8 +556,16 @@ pub fn readConfig(b: *std.Build, target: std.Build.ResolvedTarget, opts: Options // What an app's installers carry besides the executable and the plugin dylibs (a helper // program it starts, say), and what it tells vpk besides fizzy's own arguments. - const package_dirs = b.option([]const std.Build.LazyPath, "package-dirs", "Directories whose files `package` ships beside the executable") orelse &.{}; - const vpk_args = b.option([]const []const u8, "vpk-args", "Arguments `package` passes `vpk pack` after its own") orelse &.{}; + const package_dirs = b.option( + []const std.Build.LazyPath, + "package-dirs", + "Directories whose files `package` ships beside the executable", + ) orelse &.{}; + const vpk_args = b.option( + []const []const u8, + "vpk-args", + "Arguments `package` passes `vpk pack` after its own", + ) orelse &.{}; return .{ .vz = vz, .macos_sdl_paths = macos_sdl_paths, @@ -513,7 +618,14 @@ pub const Built = struct { /// Phase two of `build`: the executables, the web build, tests, packaging. `frameworks` holds any /// an app asked for first (`fizzy.framework`), and gets the rest. -pub fn construct(b: *std.Build, target: std.Build.ResolvedTarget, optimize: std.lang.OptimizeMode, opts: Options, cfg: Config, frameworks: *Frameworks) !Built { +pub fn construct( + b: *std.Build, + target: std.Build.ResolvedTarget, + optimize: std.lang.OptimizeMode, + opts: Options, + cfg: Config, + frameworks: *Frameworks, +) !Built { const assetpack = @import("assetpack"); const assets_module = assetpack.pack(b, b.path("assets"), .{}); // The window's icon, apart from `assets` so an app built on fizzy can give its own. @@ -528,10 +640,33 @@ pub fn construct(b: *std.Build, target: std.Build.ResolvedTarget, optimize: std. , .{cfg.branding.pixel_art})), }); - // `zig build web` produces `zig-out/web/{web.wasm, web.js, index.html, NotoSansKR-Regular.ttf}`, + // `zig build web` produces `zig-out/web/{web.wasm, web.js, index.html, + // NotoSansKR-Regular.ttf}`, // deployable as-is to a static host. - const w = web.build(b, frameworks.getWeb(b, optimize), optimize, cfg.build_opts, cfg.workbench_opts, assets_module, app_icon_module, cfg.app_layout_path, try @import("sdk.zig").pluginsFor(b, opts.app_plugins, .web), opts.web_plugin_deps, opts.web_plugin_dirs, opts.own != null); - const built = try native(b, target, optimize, opts, cfg, frameworks.getNative(b, target, optimize, cfg), assets_module, app_icon_module); + const w = web.build( + b, + frameworks.getWeb(b, optimize), + optimize, + cfg.build_opts, + cfg.workbench_opts, + assets_module, + app_icon_module, + cfg.app_layout_path, + try @import("sdk.zig").pluginsFor(b, opts.app_plugins, .web), + opts.web_plugin_deps, + opts.web_plugin_dirs, + opts.own != null, + ); + const built = try native( + b, + target, + optimize, + opts, + cfg, + frameworks.getNative(b, target, optimize, cfg), + assets_module, + app_icon_module, + ); install(b, target, optimize, cfg, w, built); try addTests(b, target, optimize, cfg, built, assets_module, app_icon_module); return .{ .native = built, .web = w }; @@ -544,8 +679,21 @@ pub const Frameworks = struct { native: ?NativeFramework = null, web: ?web.Framework = null, - pub fn getNative(self: *Frameworks, b: *std.Build, target: std.Build.ResolvedTarget, optimize: std.lang.OptimizeMode, cfg: Config) NativeFramework { - if (self.native == null) self.native = @import("exe.zig").nativeFramework(b, target, optimize, cfg.accesskit, cfg.native_backend, cfg.macos_sdl_paths); + pub fn getNative( + self: *Frameworks, + b: *std.Build, + target: std.Build.ResolvedTarget, + optimize: std.lang.OptimizeMode, + cfg: Config, + ) NativeFramework { + if (self.native == null) self.native = @import("exe.zig").nativeFramework( + b, + target, + optimize, + cfg.accesskit, + cfg.native_backend, + cfg.macos_sdl_paths, + ); return self.native.?; } @@ -562,7 +710,16 @@ pub const Native = struct { package: FizzyExecutable, }; -fn native(b: *std.Build, target: std.Build.ResolvedTarget, optimize: std.lang.OptimizeMode, opts: Options, cfg: Config, framework: NativeFramework, assets_module: *std.Build.Module, app_icon_module: *std.Build.Module) !Native { +fn native( + b: *std.Build, + target: std.Build.ResolvedTarget, + optimize: std.lang.OptimizeMode, + opts: Options, + cfg: Config, + framework: NativeFramework, + assets_module: *std.Build.Module, + app_icon_module: *std.Build.Module, +) !Native { const fizzy_exe = @import("exe.zig"); const vz = cfg.vz; const macos_sdl_paths = cfg.macos_sdl_paths; @@ -586,7 +743,25 @@ fn native(b: *std.Build, target: std.Build.ResolvedTarget, optimize: std.lang.Op const native_backend = cfg.native_backend; const app_plugins = try @import("sdk.zig").pluginsFor(b, opts.app_plugins, .native); - const main_fizzy = try fizzy_exe.addFizzyExecutableForTarget(b, vz, target, optimize, framework, native_backend, build_opts, workbench_opts, assets_module, app_icon_module, cfg.branding.rc, macos_sdl_paths, velopack_enabled, app_name, app_layout_path, app_plugins, dynamic_plugins); + const main_fizzy = try fizzy_exe.addFizzyExecutableForTarget( + b, + vz, + target, + optimize, + framework, + native_backend, + build_opts, + workbench_opts, + assets_module, + app_icon_module, + cfg.branding.rc, + macos_sdl_paths, + velopack_enabled, + app_name, + app_layout_path, + app_plugins, + dynamic_plugins, + ); const package_fizzy: FizzyExecutable = package_blk: { if (velopack_enabled) break :package_blk main_fizzy; @@ -612,14 +787,40 @@ fn native(b: *std.Build, target: std.Build.ResolvedTarget, optimize: std.lang.Op pack_opts.addOption(bool, "static_image", static_image); pack_opts.addOption(bool, "dynamic_plugins", dynamic_plugins); pack_opts.addOption(bool, "has_app_layout", app_layout_path != null); - break :package_blk try fizzy_exe.addFizzyExecutableForTarget(b, vz, target, optimize, framework, native_backend, pack_opts, workbench_opts, assets_module, app_icon_module, cfg.branding.rc, macos_sdl_paths, true, app_name, app_layout_path, app_plugins, dynamic_plugins); + break :package_blk try fizzy_exe.addFizzyExecutableForTarget( + b, + vz, + target, + optimize, + framework, + native_backend, + pack_opts, + workbench_opts, + assets_module, + app_icon_module, + cfg.branding.rc, + macos_sdl_paths, + true, + app_name, + app_layout_path, + app_plugins, + dynamic_plugins, + ); }; return .{ .main = main_fizzy, .package = package_fizzy }; } -/// The steps over what `web.build` and `native` made: the installs, `run`, the dylib steps, packaging. -fn install(b: *std.Build, target: std.Build.ResolvedTarget, optimize: std.lang.OptimizeMode, cfg: Config, w: web.Web, built: Native) void { +/// The steps over what `web.build` and `native` made: the installs, `run`, the dylib steps, +/// packaging. +fn install( + b: *std.Build, + target: std.Build.ResolvedTarget, + optimize: std.lang.OptimizeMode, + cfg: Config, + w: web.Web, + built: Native, +) void { const common = @import("common.zig"); const package = @import("package.zig"); const vz = cfg.vz; @@ -640,12 +841,19 @@ fn install(b: *std.Build, target: std.Build.ResolvedTarget, optimize: std.lang.O const plugins_install_dir: std.Build.InstallDir = .{ .custom = b.fmt("{s}/plugins", .{zig_out_subdir}) }; if (no_emit) { b.getInstallStep().dependOn(&exe.step); - for (built.main.dylibs) |dylib| common.attachBuiltinPluginInstall(b, b.getInstallStep(), dylib.compile, dylib.id, plugins_install_dir); + for (built.main.dylibs) |dylib| common.attachBuiltinPluginInstall( + b, + b.getInstallStep(), + dylib.compile, + dylib.id, + plugins_install_dir, + ); } else { const install_artifact = b.addInstallArtifact(exe, .{ .dest_dir = .{ .override = zig_out_install_dir }, }); - // A running fizzy holds its executable on Windows: set it aside first, or the install fails. + // A running fizzy holds its executable on Windows: set it aside first, or the install + // fails. if (b.graph.host.result.os.tag == .windows and target.result.os.tag == .windows) { const aside_exe = b.addExecutable(.{ .name = "set-aside", @@ -681,7 +889,11 @@ fn install(b: *std.Build, target: std.Build.ResolvedTarget, optimize: std.lang.O const install_dylib = plugin.installBuiltinPlugin(b, dylib.compile, dylib.id, plugins_install_dir); b.step( b.fmt("{s}-dylib", .{dylib.id}), - b.fmt("Build the {s} plugin as a dynamic library into zig-out//plugins/ (native only)", .{dylib.id}), + b.fmt( + "Build the {s} plugin as a dynamic library into zig-out//plugins/ " ++ + "(native only)", + .{dylib.id}, + ), ).dependOn(&install_dylib.step); } @@ -709,7 +921,15 @@ fn install(b: *std.Build, target: std.Build.ResolvedTarget, optimize: std.lang.O } /// The unit and integration tests, the benchmarks, and the checks over them. -fn addTests(b: *std.Build, target: std.Build.ResolvedTarget, optimize: std.lang.OptimizeMode, cfg: Config, built: Native, assets_module: *std.Build.Module, app_icon_module: *std.Build.Module) !void { +fn addTests( + b: *std.Build, + target: std.Build.ResolvedTarget, + optimize: std.lang.OptimizeMode, + cfg: Config, + built: Native, + assets_module: *std.Build.Module, + app_icon_module: *std.Build.Module, +) !void { const plugins = @import("plugins.zig"); const sdk = @import("sdk.zig"); const fizzy_exe = @import("exe.zig"); @@ -814,7 +1034,8 @@ fn addTests(b: *std.Build, target: std.Build.ResolvedTarget, optimize: std.lang. // Profiles: taking `--profile` out of argv, and each profile's own lock name. std-only; // where a profile's folders go is `single_instance` and the editor's, run by hand. .{ "fizzy-profile-tests", "app/profile.zig" }, - // `FIZZY_QUIET`: read from the environment (the flag is `single_instance.resolveArgs`). std-only. + // `FIZZY_QUIET`: read from the environment (the flag is `single_instance.resolveArgs`). + // std-only. .{ "fizzy-quiet-tests", "app/quiet.zig" }, // `` scanning for the markdown preview's raw-HTML blocks. Under plugins/ // but std-only by design (see html_images.zig), so it tests from the app build. @@ -874,7 +1095,8 @@ fn addTests(b: *std.Build, target: std.Build.ResolvedTarget, optimize: std.lang. // A state packed for a track file (zero runs, then deflate): lossless, so a canvas of // transparent pixels is not stored as tens of megabytes. std-only (see state_pack.zig). .{ "fizzy-state-pack-tests", "app/state_pack.zig" }, - // Image diff (RGBA8 to rects) and text diff (histogram lines, unified output). std-only (see + // Image diff (RGBA8 to rects) and text diff (histogram lines, unified output). std-only + // (see // core/diff.zig); `bench-diff` measures it. .{ "fizzy-diff-tests", "core/diff.zig" }, // When a document's transport shows and for how long, how far a held undo goes, which @@ -1100,7 +1322,10 @@ fn addTests(b: *std.Build, target: std.Build.ResolvedTarget, optimize: std.lang. // steps so `zig build test` stays MSVC-free on Windows CI runners. Skipped // when cross-compiling to *-windows-msvc without an MSVC libc INI. // --------------------------------------------------------------- - const test_integration_step = b.step("test-integration", "Run fizzy headless integration tests (dvui-testing; needs MSVC on Windows)"); + const test_integration_step = b.step( + "test-integration", + "Run fizzy headless integration tests (dvui-testing; needs MSVC on Windows)", + ); const check_integration_step = b.step("check-integration", "Compile fizzy integration tests without running them"); const test_all_step = b.step("test-all", "Run unit + integration tests"); test_all_step.dependOn(test_step); @@ -1128,7 +1353,13 @@ fn addTests(b: *std.Build, target: std.Build.ResolvedTarget, optimize: std.lang. .backend = .testing, .accesskit = accesskit, }); - const dvui_test_proxy_bridge = sdk.addProxyBridgeModule(b, target, optimize, dvui_testing_dep, dvui_testing_dep.module("dvui_testing")); + const dvui_test_proxy_bridge = sdk.addProxyBridgeModule( + b, + target, + optimize, + dvui_testing_dep, + dvui_testing_dep.module("dvui_testing"), + ); // Build a module rooted at `src/fizzy.zig` carrying all the same // imports the production exe carries. Because fizzy.zig's transitive @@ -1144,7 +1375,13 @@ fn addTests(b: *std.Build, target: std.Build.ResolvedTarget, optimize: std.lang. fizzy_test_module.addImport("dvui", dvui_testing_dep.module("dvui_testing")); fizzy_test_module.addImport("backend", dvui_testing_dep.module("testing")); // The testing backend has no viewports (`backend_web.viewports`). - fizzy_test_module.addImport("viewports_none", @import("fizzy_backend").viewportsNoneModule(fizzy_exe.backendDependency(b), dvui_testing_dep.module("dvui_testing"))); + fizzy_test_module.addImport( + "viewports_none", + @import("fizzy_backend").viewportsNoneModule( + fizzy_exe.backendDependency(b), + dvui_testing_dep.module("dvui_testing"), + ), + ); fizzy_test_module.addImport("assets", assets_module); fizzy_test_module.addImport("app_icon", app_icon_module); fizzy_test_module.addImport("build_opts", sdk.buildOptsModule(build_opts)); @@ -1155,7 +1392,16 @@ fn addTests(b: *std.Build, target: std.Build.ResolvedTarget, optimize: std.lang. .optimize = optimize, .root_source_file = b.path("core/core.zig"), }); - const icons_test = core_mod.addImports(b, core_module_test, dvui_testing_dep.module("dvui_testing"), b.dependency("fizzy_sdk", .{}).builder, target, optimize, null, null); + const icons_test = core_mod.addImports( + b, + core_module_test, + dvui_testing_dep.module("dvui_testing"), + b.dependency("fizzy_sdk", .{}).builder, + target, + optimize, + null, + null, + ); fizzy_test_module.addImport("core", core_module_test); if (icons_test) |icons| fizzy_test_module.addImport("icons", icons); @@ -1208,7 +1454,15 @@ fn addTests(b: *std.Build, target: std.Build.ResolvedTarget, optimize: std.lang. fizzy_test_module.addImport("nightwatch", dep.module("nightwatch")); } - const sdk_module_test = sdk.wireSdkModule(b, target, optimize, dvui_testing_dep.module("dvui_testing"), dvui_test_proxy_bridge, core_module_test, fizzy_test_module); + const sdk_module_test = sdk.wireSdkModule( + b, + target, + optimize, + dvui_testing_dep.module("dvui_testing"), + dvui_test_proxy_bridge, + core_module_test, + fizzy_test_module, + ); if (cfg.app_is_fizzy) { const fixture = rehome_fixture.graph(b, target, optimize); const consumer = b.createModule(.{ .target = target, .optimize = optimize, .link_libc = true }); @@ -1251,7 +1505,20 @@ fn addTests(b: *std.Build, target: std.Build.ResolvedTarget, optimize: std.lang. // The single-instance backend too, so its argv and profile handling are tested where the exe // is not built (`tests/integration.zig`, "profile"). const singleton_test_dep = b.dependency("dvui_singleton_app", .{ .target = target, .optimize = optimize }); - const app_module_test = sdk.wireAppModule(b, target, optimize, dvui_testing_dep.module("dvui_testing"), core_module_test, sdk_module_test, icons_test, markdown_module_test, if (nightwatch_test_dep) |dep| dep.module("nightwatch") else null, build_opts, singleton_test_dep.module("singleton_app"), fizzy_test_module); + const app_module_test = sdk.wireAppModule( + b, + target, + optimize, + dvui_testing_dep.module("dvui_testing"), + core_module_test, + sdk_module_test, + icons_test, + markdown_module_test, + if (nightwatch_test_dep) |dep| dep.module("nightwatch") else null, + build_opts, + singleton_test_dep.module("singleton_app"), + fizzy_test_module, + ); const image_module_test = image_plugin.addStaticModule(b, target, optimize, .{ .dvui = dvui_testing_dep.module("dvui_testing"), .core = core_module_test, @@ -1281,7 +1548,10 @@ fn addTests(b: *std.Build, target: std.Build.ResolvedTarget, optimize: std.lang. })); if (target.result.os.tag == .macos) { - if (@import("fizzy_backend").objcModule(fizzy_exe.backendDependency(b), fizzy_exe.backendOptions(target, optimize, macos_sdl_paths))) |objc| { + if (@import("fizzy_backend").objcModule( + fizzy_exe.backendDependency(b), + fizzy_exe.backendOptions(target, optimize, macos_sdl_paths), + )) |objc| { fizzy_test_module.addImport("objc", objc); } } else if (target.result.os.tag == .windows) { @@ -1328,12 +1598,19 @@ fn addTests(b: *std.Build, target: std.Build.ResolvedTarget, optimize: std.lang. integration_module.addImport("workbench", workbench_module_test); integration_module.addAnonymousImport("markdown_sample", .{ .root_source_file = b.path("docs/PLUGINS.md") }); // The synthetic document with the 48KB table of wrapping cells — the case table-row culling - // exists for, and the one it could get wrong. Generated by `markdown_sample_tables.py` beside it. - integration_module.addAnonymousImport("markdown_sample_tables", .{ .root_source_file = b.path("tests/data/markdown_sample_tables.md") }); + // exists for, and the one it could get wrong. Generated by `markdown_sample_tables.py` beside + // it. + integration_module.addAnonymousImport( + "markdown_sample_tables", + .{ .root_source_file = b.path("tests/data/markdown_sample_tables.md") }, + ); // The image-heavy fixture. Both docs above are prose and tables, so without this no test ever // laid out an image block — the one block kind whose height nothing in the source predicts // and which rescales with the pane right up until the pane is wider than the image. - integration_module.addAnonymousImport("markdown_sample_images", .{ .root_source_file = b.path("tests/data/markdown_sample_images.md") }); + integration_module.addAnonymousImport( + "markdown_sample_images", + .{ .root_source_file = b.path("tests/data/markdown_sample_images.md") }, + ); // A real tree-sitter grammar + queries, so the markdown code-fence highlighting path can be // executed rather than only compiled. The grammar itself is already linked into this binary // through the text module; the queries come from dvui's examples rather than a vendored copy, @@ -1349,7 +1626,10 @@ fn addTests(b: *std.Build, target: std.Build.ResolvedTarget, optimize: std.lang. // The hand-written sample tape `docs/AUTOMATION.md` points at, so it cannot rot. integration_module.addAnonymousImport("demo_sample_tape", .{ .root_source_file = b.path("docs/demos/hello.zon") }); integration_module.addAnonymousImport("soak_tape", .{ .root_source_file = b.path("tests/tapes/soak.zon") }); - integration_module.addAnonymousImport("soak_expect", .{ .root_source_file = b.path("tests/tapes/soak.expect.zon") }); + integration_module.addAnonymousImport( + "soak_expect", + .{ .root_source_file = b.path("tests/tapes/soak.expect.zon") }, + ); const integration_tests = b.addTest(.{ .name = "fizzy-integration-tests", @@ -1365,7 +1645,12 @@ fn addTests(b: *std.Build, target: std.Build.ResolvedTarget, optimize: std.lang. // off for the msvc ABI regardless of host (cross or native Windows). integration_tests.root_module.link_libcpp = !target_is_windows_msvc; if (velopack_enabled) if (vz) |v| { - try velopack.linkVelopack(b, v, integration_tests, .{ .target = target, .optimize = optimize, .c_modules = &.{app_module_test} }); + try velopack.linkVelopack( + b, + v, + integration_tests, + .{ .target = target, .optimize = optimize, .c_modules = &.{app_module_test} }, + ); }; test_integration_step.dependOn(&b.addRunArtifact(integration_tests).step); @@ -1393,7 +1678,12 @@ fn addTests(b: *std.Build, target: std.Build.ResolvedTarget, optimize: std.lang. if (target.result.os.tag == .windows) demo_tests.root_module.linkSystemLibrary("comctl32", .{}); demo_tests.root_module.link_libcpp = !target_is_windows_msvc; if (velopack_enabled) if (vz) |v| { - try velopack.linkVelopack(b, v, demo_tests, .{ .target = target, .optimize = optimize, .c_modules = &.{app_module_test} }); + try velopack.linkVelopack( + b, + v, + demo_tests, + .{ .target = target, .optimize = optimize, .c_modules = &.{app_module_test} }, + ); }; test_integration_step.dependOn(&b.addRunArtifact(demo_tests).step); check_integration_step.dependOn(&demo_tests.step); @@ -1418,7 +1708,12 @@ fn addTests(b: *std.Build, target: std.Build.ResolvedTarget, optimize: std.lang. if (target.result.os.tag == .windows) fuzz_tests.root_module.linkSystemLibrary("comctl32", .{}); fuzz_tests.root_module.link_libcpp = !target_is_windows_msvc; if (velopack_enabled) if (vz) |v| { - try velopack.linkVelopack(b, v, fuzz_tests, .{ .target = target, .optimize = optimize, .c_modules = &.{app_module_test} }); + try velopack.linkVelopack( + b, + v, + fuzz_tests, + .{ .target = target, .optimize = optimize, .c_modules = &.{app_module_test} }, + ); }; // Its output is the report, so a cached run must not hide it. const fuzz_run = b.addRunArtifact(fuzz_tests); @@ -1539,9 +1834,15 @@ fn addTests(b: *std.Build, target: std.Build.ResolvedTarget, optimize: std.lang. // reasoning as `bench-text`'s samples. `PLUGINS.md` is the document that prompted the // benchmark. bench_module.addAnonymousImport("sample_huge", .{ .root_source_file = b.path("docs/PLUGINS.md") }); - bench_module.addAnonymousImport("sample_prose", .{ .root_source_file = b.path("tests/data/markdown_sample_tables.md") }); + bench_module.addAnonymousImport( + "sample_prose", + .{ .root_source_file = b.path("tests/data/markdown_sample_tables.md") }, + ); bench_module.addAnonymousImport("sample_medium", .{ .root_source_file = b.path("AGENTS.md") }); - bench_module.addAnonymousImport("sample_small", .{ .root_source_file = b.path("docs/MODULARIZATION_RELEASE_NOTES.md") }); + bench_module.addAnonymousImport( + "sample_small", + .{ .root_source_file = b.path("docs/MODULARIZATION_RELEASE_NOTES.md") }, + ); const bench_markdown = b.addTest(.{ .name = "fizzy-bench-markdown", .root_module = bench_module }); bench_markdown.root_module.link_libcpp = !target_is_windows_msvc; @@ -1549,7 +1850,10 @@ fn addTests(b: *std.Build, target: std.Build.ResolvedTarget, optimize: std.lang. bench_markdown.root_module.linkSystemLibrary("comctl32", .{}); } - const bench_step = b.step("bench-markdown", "Benchmark the markdown preview's per-frame draw cost (prints timings)"); + const bench_step = b.step( + "bench-markdown", + "Benchmark the markdown preview's per-frame draw cost (prints timings)", + ); const run_bench = b.addRunArtifact(bench_markdown); run_bench.has_side_effects = true; bench_step.dependOn(&run_bench.step); @@ -1575,13 +1879,21 @@ fn addTests(b: *std.Build, target: std.Build.ResolvedTarget, optimize: std.lang. bench_replay.root_module.linkSystemLibrary("comctl32", .{}); } if (velopack_enabled) if (vz) |v| { - try velopack.linkVelopack(b, v, bench_replay, .{ .target = target, .optimize = optimize, .c_modules = &.{app_module_test} }); + try velopack.linkVelopack( + b, + v, + bench_replay, + .{ .target = target, .optimize = optimize, .c_modules = &.{app_module_test} }, + ); }; const check_bench_replay_step = b.step("check-bench-replay", "Compile fizzy bench-replay without running it"); check_bench_replay_step.dependOn(&bench_replay.step); - const bench_step = b.step("bench-replay", "Benchmark a demo seek, silent against shown frame by frame (prints timings)"); + const bench_step = b.step( + "bench-replay", + "Benchmark a demo seek, silent against shown frame by frame (prints timings)", + ); const run_bench = b.addRunArtifact(bench_replay); run_bench.has_side_effects = true; bench_step.dependOn(&run_bench.step); @@ -1607,13 +1919,17 @@ fn addTests(b: *std.Build, target: std.Build.ResolvedTarget, optimize: std.lang. try velopack.linkVelopack(b, v, bench_tracks, .{ .target = target, .optimize = optimize }); }; - const bench_step = b.step("bench-tracks", "Benchmark a document's track: steps, checkpoints, replays (prints timings)"); + const bench_step = b.step( + "bench-tracks", + "Benchmark a document's track: steps, checkpoints, replays (prints timings)", + ); const run_bench = b.addRunArtifact(bench_tracks); run_bench.has_side_effects = true; bench_step.dependOn(&run_bench.step); } - // `zig build bench-diff` — core.diff on a 4K image, a 2048x2048 sprite of 50 layers and 1 MB of text (prints + // `zig build bench-diff` — core.diff on a 4K image, a 2048x2048 sprite of 50 layers and 1 MB of + // text (prints // timings). Same rules as `bench-tape`; std-only, so it needs no window. { const bench_module = b.createModule(.{ @@ -1628,7 +1944,10 @@ fn addTests(b: *std.Build, target: std.Build.ResolvedTarget, optimize: std.lang. })); const bench_diff = b.addTest(.{ .name = "fizzy-bench-diff", .root_module = bench_module }); - const bench_step = b.step("bench-diff", "Benchmark core.diff: a 4K image, a 50-layer sprite, 1 MB of text (prints timings)"); + const bench_step = b.step( + "bench-diff", + "Benchmark core.diff: a 4K image, a 50-layer sprite, 1 MB of text (prints timings)", + ); const run_bench = b.addRunArtifact(bench_diff); run_bench.has_side_effects = true; bench_step.dependOn(&run_bench.step); @@ -1650,7 +1969,10 @@ fn addTests(b: *std.Build, target: std.Build.ResolvedTarget, optimize: std.lang. })); const bench_tape = b.addTest(.{ .name = "fizzy-bench-tape", .root_module = bench_module }); - const bench_step = b.step("bench-tape", "Benchmark saving and loading demo tapes, ZON against binary (prints timings)"); + const bench_step = b.step( + "bench-tape", + "Benchmark saving and loading demo tapes, ZON against binary (prints timings)", + ); const run_bench = b.addRunArtifact(bench_tape); run_bench.has_side_effects = true; bench_step.dependOn(&run_bench.step); @@ -1665,7 +1987,15 @@ fn addTests(b: *std.Build, target: std.Build.ResolvedTarget, optimize: std.lang. // why Manifest.zig has its own root in the unit list above); when adding tests // to a new SDK file, check the reported test count actually went up. { - const sdk_tests_module = sdk.wireSdkModule(b, target, optimize, dvui_testing_dep.module("dvui_testing"), dvui_test_proxy_bridge, core_module_test, null); + const sdk_tests_module = sdk.wireSdkModule( + b, + target, + optimize, + dvui_testing_dep.module("dvui_testing"), + dvui_test_proxy_bridge, + core_module_test, + null, + ); const plugin_loader_module = b.createModule(.{ .target = target, .optimize = optimize, diff --git a/scripts/lint.zig b/scripts/lint.zig index da4b18c18..b37aa16c5 100644 --- a/scripts/lint.zig +++ b/scripts/lint.zig @@ -272,7 +272,10 @@ fn rawTransientSurfaces( try findings.append(allocator, .{ .rule = .@"raw-transient-surface", .line = line, - .message = try allocator.print("dvui.{s} paints its own surface; use the core.widgets / core.dialogs wrapper", .{name}), + .message = try allocator.print( + "dvui.{s} paints its own surface; use the core.widgets / core.dialogs wrapper", + .{name}, + ), }); break; } From 50dfc9c1e87f157fb2ee90c4b1bb447e8bc3b5fd Mon Sep 17 00:00:00 2001 From: "fizzy-linux[bot]" <346940526+fizzy-linux[bot]@users.noreply.github.com> Date: Mon, 26 Oct 2026 01:01:07 -0500 Subject: [PATCH 6/9] lint: keep the multiline string literal in build/app.zig Agent-Team: linux Worker: claude --- build/app.zig | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/build/app.zig b/build/app.zig index 1085e4c31..814a182ab 100644 --- a/build/app.zig +++ b/build/app.zig @@ -358,12 +358,13 @@ pub fn readConfig(b: *std.Build, target: std.Build.ResolvedTarget, opts: Options // with `-Dfetch-msvc=false` — in which case Zig falls back to system Visual Studio // auto-detection, which we can't validate here. const velopack_required_fail: ?*std.Build.Step = if (cross_win_msvc and effective_win_libc == null) - &b.addFail("*-windows-msvc needs MSVC + Windows SDK headers/libs.\n" ++ - " One-shot install (macOS/Linux/Windows): zig build msvcup-setup\n" ++ - " Then: zig build package -Dtarget=x86_64-windows-msvc (auto-uses .velopack-msvc/zig-libc-x64.ini)\n" ++ - " Or auto-download in this build: add -Dfetch-msvc (default on " ++ - "Windows hosts; forwards through packageall)\n" ++ - " Or pass: --libc path.ini / -Dwindows-msvc-libc=path.ini").step + &b.addFail( + \\*-windows-msvc needs MSVC + Windows SDK headers/libs. + \\ One-shot install (macOS/Linux/Windows): zig build msvcup-setup + \\ Then: zig build package -Dtarget=x86_64-windows-msvc (auto-uses .velopack-msvc/zig-libc-x64.ini) + \\ Or auto-download in this build: add -Dfetch-msvc (default on Windows hosts; forwards through packageall) + \\ Or pass: --libc path.ini / -Dwindows-msvc-libc=path.ini + ).step else null; From 20f8e3bff5dd79e9734709e70ac7710cb1d25dac Mon Sep 17 00:00:00 2001 From: "fizzy-linux[bot]" <346940526+fizzy-linux[bot]@users.noreply.github.com> Date: Mon, 26 Oct 2026 02:24:55 -0500 Subject: [PATCH 7/9] lint: line-length (codex) Agent-Team: linux Worker: codex --- sdk/remote/Client.zig | 77 ++++++++++++++++++++++++++++------- sdk/remote/Connection.zig | 36 +++++++++++++---- sdk/remote/capi.zig | 24 ++++++++--- sdk/remote/fake_fizzy.zig | 35 ++++++++++++++-- sdk/remote/message.zig | 24 ++++++++++- sdk/remote/root.zig | 3 +- sdk/remote/tests.zig | 44 +++++++++++++++++--- sdk/tape/Script.zig | 31 +++++++++++--- sdk/tape/Sequencer.zig | 33 ++++++++++----- sdk/tape/Tape.zig | 85 ++++++++++++++++++++++++++++++++------- sdk/tape/binary.zig | 85 ++++++++++++++++++++++++++++++++------- sdk/tape/events.zig | 5 ++- sdk/tape/key.zig | 10 ++++- sdk/tape/ring.zig | 42 +++++++++++++++---- 14 files changed, 441 insertions(+), 93 deletions(-) diff --git a/sdk/remote/Client.zig b/sdk/remote/Client.zig index 1d93f469e..09d9bc1f7 100644 --- a/sdk/remote/Client.zig +++ b/sdk/remote/Client.zig @@ -135,17 +135,28 @@ pub fn deinit(self: *Client) void { if (self.socket_path) |p| self.gpa.free(p); } -/// Where fizzy would be installed on `env.os`, likeliest first; not `$PATH` (`onPath`). Touches nothing. +/// Where fizzy would be installed on `env.os`, likeliest first; not `$PATH` (`onPath`). Touches +/// nothing. pub fn candidates(arena: std.mem.Allocator, identity: Identity, env: Env) error{OutOfMemory}![]const Install { var list: std.ArrayList(Install) = .empty; const sep: []const u8 = if (env.os == .windows) "\\" else "/"; switch (env.os) { .macos => { - const roots = [_]?[]const u8{ "/Applications", if (env.home) |h| try join(arena, "/", &.{ h, "Applications" }) else null }; + const roots = [_]?[]const u8{ + "/Applications", + if (env.home) |h| try join( + arena, + "/", + &.{ h, "Applications" }, + ) else null, + }; for (roots) |root_opt| { const root = root_opt orelse continue; const app = try std.fmt.allocPrint(arena, "{s}/{s}.app", .{ root, identity.display_name }); - try list.append(arena, .{ .exe = try join(arena, "/", &.{ app, "Contents/MacOS", identity.name }), .bundle = app }); + try list.append( + arena, + .{ .exe = try join(arena, "/", &.{ app, "Contents/MacOS", identity.name }), .bundle = app }, + ); } }, .windows => if (env.local_app_data) |base| { @@ -209,7 +220,11 @@ fn lookupSystem(io: Io, arena: std.mem.Allocator, identity: Identity, env: Env) switch (env.os) { .macos => { if (std.mem.findAny(u8, identity.bundle_id, "\"\\") != null) return null; - const query = std.fmt.allocPrint(arena, "kMDItemCFBundleIdentifier == \"{s}\"", .{identity.bundle_id}) catch return null; + const query = std.fmt.allocPrint( + arena, + "kMDItemCFBundleIdentifier == \"{s}\"", + .{identity.bundle_id}, + ) catch return null; const out = runCapture(io, arena, &.{ "/usr/bin/mdfind", query }) orelse return null; const bundle = std.mem.trim(u8, firstLine(out), " \t\r"); if (bundle.len == 0) return null; @@ -217,7 +232,11 @@ fn lookupSystem(io: Io, arena: std.mem.Allocator, identity: Identity, env: Env) return if (exists(io, exe)) .{ .exe = exe, .bundle = bundle } else null; }, .windows => for ([_][]const u8{ "HKCU", "HKLM" }) |hive| { - const key = std.fmt.allocPrint(arena, "{s}\\Software\\Microsoft\\Windows\\CurrentVersion\\App Paths\\{s}.exe", .{ hive, identity.name }) catch return null; + const key = std.fmt.allocPrint( + arena, + "{s}\\Software\\Microsoft\\Windows\\CurrentVersion\\App Paths\\{s}.exe", + .{ hive, identity.name }, + ) catch return null; const out = runCapture(io, arena, &.{ "reg", "query", key, "/ve" }) orelse continue; const exe = parseRegValue(out) orelse continue; if (exists(io, exe)) return .{ .exe = exe }; @@ -241,7 +260,11 @@ fn firstLine(text: []const u8) []const u8 { /// Stdout of a run that exited 0, or null. fn runCapture(io: Io, arena: std.mem.Allocator, argv: []const []const u8) ?[]const u8 { - const result = std.process.run(arena, io, .{ .argv = argv, .timeout = .{ .duration = .{ .raw = .fromSeconds(5), .clock = .awake } } }) catch return null; + const result = std.process.run( + arena, + io, + .{ .argv = argv, .timeout = .{ .duration = .{ .raw = .fromSeconds(5), .clock = .awake } } }, + ) catch return null; return switch (result.term) { .exited => |code| if (code == 0) result.stdout else null, else => null, @@ -254,7 +277,11 @@ pub fn controlPath(io: Io, arena: std.mem.Allocator, install: Install, profile: &.{ install.exe, "--control-path", "--profile", p } else &.{ install.exe, "--control-path" }; - const result = std.process.run(arena, io, .{ .argv = argv, .timeout = .{ .duration = .{ .raw = .fromSeconds(10), .clock = .awake } } }) catch |err| return spawnError(err); + const result = std.process.run( + arena, + io, + .{ .argv = argv, .timeout = .{ .duration = .{ .raw = .fromSeconds(10), .clock = .awake } } }, + ) catch |err| return spawnError(err); // A fizzy that predates `--control-path` starts as an editor instead: no path to read. const ok = switch (result.term) { .exited => |code| code == 0, @@ -358,7 +385,10 @@ fn timeoutMs(ms: u32) Io.Timeout { /// `timeout` passed first. pub fn readSome(io: Io, stream: Io.net.Stream, buf: []u8, timeout: Io.Timeout) error{Closed}!?usize { var data: [1][]u8 = .{buf}; - const result = io.operateTimeout(.{ .net_read = .{ .socket_handle = stream.socket.handle, .data = &data } }, timeout) catch |err| switch (err) { + const result = io.operateTimeout( + .{ .net_read = .{ .socket_handle = stream.socket.handle, .data = &data } }, + timeout, + ) catch |err| switch (err) { error.Timeout => return null, else => return error.Closed, }; @@ -428,14 +458,23 @@ pub fn connect(self: *Client) Error!void { /// `GET /remote` on `stream`, then the hellos. Takes ownership of `stream`. fn upgrade(self: *Client, stream: Io.net.Stream, deadline: Deadline) Error!void { errdefer stream.close(self.io); - try writeAll(self.io, stream, "GET /remote HTTP/1.1\r\nHost: localhost\r\nConnection: Upgrade\r\nUpgrade: " ++ upgrade_token ++ "\r\n\r\n"); + try writeAll( + self.io, + stream, + "GET /remote HTTP/1.1\r\nHost: localhost\r\nConnection: Upgrade\r\nUpgrade: " ++ upgrade_token ++ "\r\n\r\n", + ); var head: [2048]u8 = undefined; var len: usize = 0; const body_at = while (true) { if (std.mem.find(u8, head[0..len], "\r\n\r\n")) |at| break at + 4; if (len == head.len) return error.Protocol; - const n = try readSome(self.io, stream, head[len..], timeoutMs(deadline.remainingMs())) orelse return error.TimedOut; + const n = try readSome( + self.io, + stream, + head[len..], + timeoutMs(deadline.remainingMs()), + ) orelse return error.TimedOut; if (n == 0) return error.Closed; len += n; }; @@ -509,7 +548,8 @@ pub fn connected(self: *const Client) bool { } /// Check `msg` against the conversation's rules and send it. A message the rules refuse is the -/// caller's mistake: it is not sent and the stream stays open. A write that fails closes the client. +/// caller's mistake: it is not sent and the stream stays open. A write that fails closes the +/// client. pub fn send(self: *Client, msg: message.Message) Error!void { const ep = &(self.endpoint orelse return error.Closed); if (self.stream == null) return self.closeStale(); @@ -598,7 +638,11 @@ test candidates { const mac_path = try onPath(arena, id, .{ .os = .macos, .path = "/usr/bin:/opt/bin" }); try std.testing.expectEqualStrings("/opt/bin/fizzy", mac_path[1].exe); - const win = try candidates(arena, id, .{ .os = .windows, .local_app_data = "C:\\Users\\a\\AppData\\Local", .path = "C:\\bin;D:\\tools" }); + const win = try candidates( + arena, + id, + .{ .os = .windows, .local_app_data = "C:\\Users\\a\\AppData\\Local", .path = "C:\\bin;D:\\tools" }, + ); try std.testing.expectEqualStrings("C:\\Users\\a\\AppData\\Local\\fizzy\\current\\fizzy.exe", win[0].exe); const win_path = try onPath(arena, id, .{ .os = .windows, .path = "C:\\bin;D:\\tools" }); try std.testing.expectEqualStrings("D:\\tools\\fizzy.exe", win_path[1].exe); @@ -607,11 +651,16 @@ test candidates { try std.testing.expectEqualStrings("/home/a/.local/share/paint/current/paint", linux[0].exe); try std.testing.expectEqualStrings("/home/a/Applications/paint.AppImage", linux[2].exe); try std.testing.expectEqual(@as(usize, 3), linux.len); - try std.testing.expectEqualStrings("/usr/bin/paint", (try onPath(arena, .{ .name = "paint" }, .{ .os = .linux, .path = "/usr/bin" }))[0].exe); + try std.testing.expectEqualStrings( + "/usr/bin/paint", + (try onPath(arena, .{ .name = "paint" }, .{ .os = .linux, .path = "/usr/bin" }))[0].exe, + ); } test parseRegValue { - const out = "\r\nHKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\CurrentVersion\\App Paths\\fizzy.exe\r\n (Default) REG_SZ C:\\Users\\a\\AppData\\Local\\fizzy\\current\\fizzy.exe\r\n"; + const out = "\r\nHKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\CurrentVersion" ++ + "\\App Paths\\fizzy.exe\r\n (Default) REG_SZ " ++ + "C:\\Users\\a\\AppData\\Local\\fizzy\\current\\fizzy.exe\r\n"; try std.testing.expectEqualStrings("C:\\Users\\a\\AppData\\Local\\fizzy\\current\\fizzy.exe", parseRegValue(out).?); try std.testing.expect(parseRegValue("ERROR: The system was unable to find the specified registry key") == null); } diff --git a/sdk/remote/Connection.zig b/sdk/remote/Connection.zig index 9ac3b3d4e..05a6fffd9 100644 --- a/sdk/remote/Connection.zig +++ b/sdk/remote/Connection.zig @@ -153,7 +153,11 @@ fn apply(self: *Connection, msg: Message, from: Role) Error!void { .create_view => |m| { if (m.view == 0) return error.UnknownView; if (self.views.contains(m.view)) return error.DuplicateView; - try self.views.put(self.gpa, m.view, .{ .tier = m.tier, .width = m.width, .height = m.height, .scale = m.scale }); + try self.views.put( + self.gpa, + m.view, + .{ .tier = m.tier, .width = m.width, .height = m.height, .scale = m.scale }, + ); }, .view_created => |m| { const v = self.views.getPtr(m.view) orelse return error.UnknownView; @@ -254,14 +258,23 @@ test "a view's life, and the frames that answer requests" { try say(&p.editor, &p.host, .{ .view_created = .{ .view = 7, .tier = .draw_list, .width = 100, .height = 50 } }); try t.expect(p.host.view(7).?.focused); - try t.expectError(error.UnexpectedFrame, p.editor.sending(.{ .frame = .{ .view = 7, .seq = 1, .tier = .draw_list } })); + try t.expectError( + error.UnexpectedFrame, + p.editor.sending(.{ .frame = .{ .view = 7, .seq = 1, .tier = .draw_list } }), + ); try say(&p.host, &p.editor, .{ .frame_request = .{ .view = 7, .seq = 1 } }); try t.expectError(error.SeqNotIncreasing, p.host.sending(.{ .frame_request = .{ .view = 7, .seq = 1 } })); try say(&p.host, &p.editor, .{ .frame_request = .{ .view = 7, .seq = 3 } }); // Fizzy may skip seq 1 and answer 3 only; 2 was never asked, and 3 cannot be answered twice. try say(&p.editor, &p.host, .{ .frame = .{ .view = 7, .seq = 3, .tier = .draw_list } }); - try t.expectError(error.UnexpectedFrame, p.editor.sending(.{ .frame = .{ .view = 7, .seq = 3, .tier = .draw_list } })); - try t.expectError(error.UnexpectedFrame, p.editor.sending(.{ .frame = .{ .view = 7, .seq = 4, .tier = .draw_list } })); + try t.expectError( + error.UnexpectedFrame, + p.editor.sending(.{ .frame = .{ .view = 7, .seq = 3, .tier = .draw_list } }), + ); + try t.expectError( + error.UnexpectedFrame, + p.editor.sending(.{ .frame = .{ .view = 7, .seq = 4, .tier = .draw_list } }), + ); try say(&p.host, &p.editor, .{ .close_view = .{ .view = 7 } }); try t.expectError(error.ViewClosing, p.host.sending(.{ .resize = .{ .view = 7, .width = 1, .height = 1 } })); @@ -287,7 +300,10 @@ test "each side may send only its own messages" { defer p.editor.deinit(); try open(&p); try t.expectError(error.WrongDirection, p.host.sending(.{ .cursor = .{ .view = 1, .shape = .arrow } })); - try t.expectError(error.WrongDirection, p.host.received(.{ .create_view = .{ .view = 1, .width = 1, .height = 1 } })); + try t.expectError( + error.WrongDirection, + p.host.received(.{ .create_view = .{ .view = 1, .width = 1, .height = 1 } }), + ); } test "no common version closes, naming who updates" { @@ -332,7 +348,13 @@ test "view 0 is no view, and a frame must match its view's tier" { try t.expectError(error.UnknownView, p.host.sending(.{ .create_view = .{ .view = 0, .width = 1, .height = 1 } })); try say(&p.host, &p.editor, .{ .create_view = .{ .view = 1, .width = 1, .height = 1 } }); try say(&p.host, &p.editor, .{ .frame_request = .{ .view = 1, .seq = 1 } }); - try t.expectError(error.UnexpectedFrame, p.editor.sending(.{ .frame = .{ .view = 1, .seq = 1, .tier = .draw_list } })); + try t.expectError( + error.UnexpectedFrame, + p.editor.sending(.{ .frame = .{ .view = 1, .seq = 1, .tier = .draw_list } }), + ); try say(&p.editor, &p.host, .{ .view_created = .{ .view = 1, .tier = .draw_list, .width = 1, .height = 1 } }); - try t.expectError(error.UnexpectedFrame, p.editor.sending(.{ .frame = .{ .view = 1, .seq = 1, .tier = .shared_texture } })); + try t.expectError( + error.UnexpectedFrame, + p.editor.sending(.{ .frame = .{ .view = 1, .seq = 1, .tier = .shared_texture } }), + ); } diff --git a/sdk/remote/capi.zig b/sdk/remote/capi.zig index 66a25cb20..0b9134a33 100644 --- a/sdk/remote/capi.zig +++ b/sdk/remote/capi.zig @@ -92,7 +92,8 @@ export fn fizzy_remote_open(options: ?*const Options, out: ?*?*Handle) callconv( const o = options orelse return @backingInt(Status.invalid_argument); const result = out orelse return @backingInt(Status.invalid_argument); result.* = null; - if (o.size < @offsetOf(Options, "socket_path") + @sizeOf(?[*:0]const u8)) return @backingInt(Status.invalid_argument); + if (o.size < @offsetOf(Options, "socket_path") + @sizeOf(?[*:0]const u8)) + return @backingInt(Status.invalid_argument); const handle = gpa.create(Handle) catch return @backingInt(Status.out_of_memory); handle.* = .{ .arena = .init(gpa), .threaded = .init(gpa, .{ .environ = processEnviron() }), .client = undefined }; @@ -156,8 +157,10 @@ export fn fizzy_remote_send(handle: ?*Handle, frame: ?[*]const u8, len: usize) c reader.push(bytes) catch return @backingInt(Status.out_of_memory); var arena: std.heap.ArenaAllocator = .init(gpa); defer arena.deinit(); - const frame_in = (reader.next() catch return @backingInt(Status.protocol)) orelse return @backingInt(Status.protocol); - const msg = (codec.decode(arena.allocator(), frame_in) catch return @backingInt(Status.protocol)) orelse return @backingInt(Status.protocol); + const frame_in = (reader.next() catch return @backingInt(Status.protocol)) orelse + return @backingInt(Status.protocol); + const msg = (codec.decode(arena.allocator(), frame_in) catch return @backingInt(Status.protocol)) orelse + return @backingInt(Status.protocol); h.client.send(msg) catch |err| return status(err); return 0; } @@ -165,7 +168,13 @@ export fn fizzy_remote_send(handle: ?*Handle, frame: ?[*]const u8, len: usize) c /// The next frame from fizzy into `buf`, waiting up to `timeout_ms` (negative: forever). /// `*len` is its length; 0 with `ok` means the wait passed. A frame larger than `cap` is kept: /// `buffer_too_small` with `*len` the size needed, then call again with room. -export fn fizzy_remote_recv(handle: ?*Handle, buf: ?[*]u8, cap: usize, len: ?*usize, timeout_ms: i32) callconv(.c) c_int { +export fn fizzy_remote_recv( + handle: ?*Handle, + buf: ?[*]u8, + cap: usize, + len: ?*usize, + timeout_ms: i32, +) callconv(.c) c_int { const h = handle orelse return @backingInt(Status.invalid_argument); const out_len = len orelse return @backingInt(Status.invalid_argument); out_len.* = 0; @@ -248,7 +257,12 @@ test "the C ABI opens, round trips a frame and closes" { var out: std.ArrayList(u8) = .empty; defer out.deinit(t.allocator); - try codec.encode(t.allocator, &out, .{ .create_view = .{ .view = 3, .width = 64, .height = 48 } }, codec.default_max_frame); + try codec.encode( + t.allocator, + &out, + .{ .create_view = .{ .view = 3, .width = 64, .height = 48 } }, + codec.default_max_frame, + ); try t.expectEqual(@as(c_int, 0), fizzy_remote_send(handle, out.items.ptr, out.items.len)); var small: [4]u8 = undefined; diff --git a/sdk/remote/fake_fizzy.zig b/sdk/remote/fake_fizzy.zig index 6324717b4..19974bda7 100644 --- a/sdk/remote/fake_fizzy.zig +++ b/sdk/remote/fake_fizzy.zig @@ -48,7 +48,12 @@ pub fn main(init: std.process.Init) !void { } if (quiet) try appendLaunch(gpa, io, dir); - const text = try Io.Dir.cwd().readFileAlloc(io, try std.fmt.allocPrint(gpa, "{s}/mode", .{dir}), gpa, .limited(256)); + const text = try Io.Dir.cwd().readFileAlloc( + io, + try std.fmt.allocPrint(gpa, "{s}/mode", .{dir}), + gpa, + .limited(256), + ); var words = std.mem.tokenizeAny(u8, text, " \r\n"); const mode = std.meta.stringToEnum(Mode, words.next() orelse "serve") orelse .serve; switch (mode) { @@ -91,9 +96,23 @@ fn talk(gpa: std.mem.Allocator, io: Io, stream: Io.net.Stream, mode: Mode, n: us len += got; }; if (mode == .noremote) return Client.writeAll(io, stream, "HTTP/1.1 404 Not Found\r\nContent-Length: 0\r\n\r\n"); - try Client.writeAll(io, stream, "HTTP/1.1 101 Switching Protocols\r\nConnection: Upgrade\r\nUpgrade: " ++ Client.upgrade_token ++ "\r\n\r\n"); + try Client.writeAll( + io, + stream, + "HTTP/1.1 101 Switching Protocols\r\nConnection: Upgrade\r\nUpgrade: " ++ Client.upgrade_token ++ "\r\n\r\n", + ); - var ep: Endpoint = .init(gpa, .editor, .{ .app_id = "fizzy-fake", .mode = .shared, .tiers_offered = .{ .draw_list = true }, .version_min = if (mode == .old) 9 else message.min_version, .version_max = if (mode == .old) 9 else message.max_version }); + var ep: Endpoint = .init( + gpa, + .editor, + .{ + .app_id = "fizzy-fake", + .mode = .shared, + .tiers_offered = .{ .draw_list = true }, + .version_min = if (mode == .old) 9 else message.min_version, + .version_max = if (mode == .old) 9 else message.max_version, + }, + ); defer ep.deinit(); try ep.start(); try ep.feed(head[body_at..len]); @@ -107,7 +126,15 @@ fn talk(gpa: std.mem.Allocator, io: Io, stream: Io.net.Stream, mode: Mode, n: us try Client.writeAll(io, stream, ep.pending()); return; }, - .create_view => |cv| if (mode == .bye) try ep.send(.{ .bye = .{ .reason = .shutdown, .text = "going away" } }) else try ep.send(.{ .view_created = .{ .view = cv.view, .tier = .draw_list, .width = cv.width, .height = cv.height } }), + .create_view => |cv| if (mode == .bye) + try ep.send(.{ .bye = .{ .reason = .shutdown, .text = "going away" } }) + else + try ep.send(.{ .view_created = .{ + .view = cv.view, + .tier = .draw_list, + .width = cv.width, + .height = cv.height, + } }), .bye => return, else => {}, }; diff --git a/sdk/remote/message.zig b/sdk/remote/message.zig index 9b9212802..fa90b8dd4 100644 --- a/sdk/remote/message.zig +++ b/sdk/remote/message.zig @@ -47,7 +47,16 @@ pub const Type = enum(u16) { /// know an optional type skips it by its length. Sent in the frame header. pub fn required(t: Type) bool { return switch (t) { - .hello, .bye, .create_view, .view_created, .close_view, .view_closed, .resize, .frame_request, .frame => true, + .hello, + .bye, + .create_view, + .view_created, + .close_view, + .view_closed, + .resize, + .frame_request, + .frame, + => true, else => false, }; } @@ -56,7 +65,18 @@ pub const Type = enum(u16) { pub fn sender(t: Type) Sender { return switch (t) { .hello, .bye => .either, - .view_created, .view_closed, .cursor, .ime_rect, .text_input_active, .clipboard_request, .wants_keys, .open_url, .drag_begin, .frame, .frame_ready => .editor, + .view_created, + .view_closed, + .cursor, + .ime_rect, + .text_input_active, + .clipboard_request, + .wants_keys, + .open_url, + .drag_begin, + .frame, + .frame_ready, + => .editor, else => .host, }; } diff --git a/sdk/remote/root.zig b/sdk/remote/root.zig index 0214b791a..2b9ffce6b 100644 --- a/sdk/remote/root.zig +++ b/sdk/remote/root.zig @@ -10,7 +10,8 @@ //! * `FrameReader` — cuts a byte stream into frames, in whatever chunks it arrives. //! * `Connection` — the rules of the conversation, with no bytes in it. //! * `Endpoint` — a `Connection` with the bytes around it, and a loopback `pump` for tests. -//! * `Client` — finds, quietly starts and connects to the installed fizzy; `capi.zig` is its C ABI. +//! * `Client` — finds, quietly starts and connects to the installed fizzy; `capi.zig` is its +//! C ABI. pub const message = @import("message.zig"); pub const codec = @import("codec.zig"); pub const FrameReader = @import("FrameReader.zig"); diff --git a/sdk/remote/tests.zig b/sdk/remote/tests.zig index e2d46f5ed..5eb83da72 100644 --- a/sdk/remote/tests.zig +++ b/sdk/remote/tests.zig @@ -11,15 +11,40 @@ fn sample(comptime tag: message.Type) Message { const rect: message.Rect = .{ .x = 1, .y = 2.5, .w = 3, .h = -4 }; const mods: message.Mods = .{ .shift = true, .super = true }; return switch (tag) { - .hello => .{ .hello = .{ .version_min = 1, .version_max = 3, .app_id = "it.fizzy", .app_version = "0.9.1", .tiers_offered = .{ .draw_list = true, .shared_texture = true }, .tiers_wanted = .{ .draw_list = true }, .features = 0x8000_0000_0000_0001, .mode = .standalone } }, + .hello => .{ .hello = .{ + .version_min = 1, + .version_max = 3, + .app_id = "it.fizzy", + .app_version = "0.9.1", + .tiers_offered = .{ .draw_list = true, .shared_texture = true }, + .tiers_wanted = .{ .draw_list = true }, + .features = 0x8000_0000_0000_0001, + .mode = .standalone, + } }, .bye => .{ .bye = .{ .reason = .shutdown, .text = "going away" } }, - .create_view => .{ .create_view = .{ .view = 9, .kind = .document, .target = "/a/b.md", .width = 1920, .height = 1080, .scale = 2, .tier = .native_surface, .native_handle = 0xdead_beef_cafe } }, + .create_view => .{ .create_view = .{ + .view = 9, + .kind = .document, + .target = "/a/b.md", + .width = 1920, + .height = 1080, + .scale = 2, + .tier = .native_surface, + .native_handle = 0xdead_beef_cafe, + } }, .view_created => .{ .view_created = .{ .view = 9, .tier = .shared_texture, .width = 10, .height = 20 } }, .close_view => .{ .close_view = .{ .view = 9 } }, .view_closed => .{ .view_closed = .{ .view = 9, .reason = .create_failed, .text = "no such region" } }, .resize => .{ .resize = .{ .view = 9, .width = 800, .height = 600, .scale = 1.5, .rect = rect } }, .pointer_motion => .{ .pointer_motion = .{ .view = 9, .x = 10.5, .y = -3, .mods = mods } }, - .pointer_button => .{ .pointer_button = .{ .view = 9, .button = 2, .pressed = true, .x = 1, .y = 2, .mods = mods } }, + .pointer_button => .{ .pointer_button = .{ + .view = 9, + .button = 2, + .pressed = true, + .x = 1, + .y = 2, + .mods = mods, + } }, .wheel => .{ .wheel = .{ .view = 9, .dx = 0, .dy = -120, .mods = mods } }, .touch => .{ .touch = .{ .view = 9, .id = 4, .phase = .move, .kind = .pen, .x = 5, .y = 6, .pressure = 0.25 } }, .key => .{ .key = .{ .view = 9, .down = true, .repeat = true, .host_code = 65, .code = 0x1e, .mods = mods } }, @@ -164,7 +189,10 @@ test "unknown types: optional skipped, required refused" { defer arena.deinit(); const unknown: message.Type = @fromBackingInt(@intCast(5000)); try t.expect(try codec.decode(arena.allocator(), .{ .type = unknown, .flags = 0, .payload = "abc" }) == null); - try t.expectError(error.UnknownRequired, codec.decode(arena.allocator(), .{ .type = unknown, .flags = codec.flag_required, .payload = "abc" })); + try t.expectError( + error.UnknownRequired, + codec.decode(arena.allocator(), .{ .type = unknown, .flags = codec.flag_required, .payload = "abc" }), + ); // Through a stream, the skipped frame does not disturb its neighbours. var bytes: std.ArrayList(u8) = .empty; @@ -208,7 +236,10 @@ test "oversize messages are refused and leave the buffer alone" { try codec.encode(t.allocator, &out, sample(.focus), 1024); const before = out.items.len; const big: [2000]u8 = @splat(0); - try t.expectError(error.FrameTooLarge, codec.encode(t.allocator, &out, .{ .text_input = .{ .view = 1, .text = &big } }, 1024)); + try t.expectError( + error.FrameTooLarge, + codec.encode(t.allocator, &out, .{ .text_input = .{ .view = 1, .text = &big } }, 1024), + ); try t.expectEqual(before, out.items.len); } @@ -225,7 +256,8 @@ test "fuzz: random and mutated frames never crash, only error" { for (0..300) |_| { const copy = try t.allocator.dupe(u8, bytes.items); defer t.allocator.free(copy); - for (0..rnd.intRangeAtMost(usize, 1, 4)) |_| copy[8 + rnd.uintLessThan(usize, copy.len - 8 + @intFromBool(copy.len == 8))] ^= rnd.int(u8); + for (0..rnd.intRangeAtMost(usize, 1, 4)) |_| + copy[8 + rnd.uintLessThan(usize, copy.len - 8 + @intFromBool(copy.len == 8))] ^= rnd.int(u8); _ = arena.reset(.retain_capacity); _ = codec.decode(arena.allocator(), frameOf(copy)) catch {}; } diff --git a/sdk/tape/Script.zig b/sdk/tape/Script.zig index 1f2cd865e..13188b602 100644 --- a/sdk/tape/Script.zig +++ b/sdk/tape/Script.zig @@ -142,9 +142,16 @@ pub fn keyframe(self: *Script, kf: Tape.Keyframe) !void { const open = try a.alloc([]const u8, kf.open.len); for (kf.open, open) |p, *out| out.* = try self.dupe(p); const settings = try a.alloc(Tape.Setting, kf.settings.len); - for (kf.settings, settings) |st, *out| out.* = .{ .owner = try self.dupe(st.owner), .key = try self.dupe(st.key), .value = try self.dupe(st.value) }; + for (kf.settings, settings) |st, *out| out.* = .{ + .owner = try self.dupe(st.owner), + .key = try self.dupe(st.key), + .value = try self.dupe(st.value), + }; const root = try self.dupe(kf.root); - try self.keyframes.append(a, .{ .root = root, .files = files, .open = open, .layout = kf.layout, .settings = settings }); + try self.keyframes.append( + a, + .{ .root = root, .files = files, .open = open, .layout = kf.layout, .settings = settings }, + ); try self.push(.{ .at = self.t, .do = .{ .keyframe = @intCast(self.keyframes.items.len - 1) } }); try self.push(.{ .at = self.t, .do = .{ .wait = .{ .until = .idle } } }); self.root = root; @@ -220,12 +227,18 @@ pub const WaitOptions = struct { /// Hold the demo until the tagged widget is drawn. pub fn waitFor(self: *Script, tag: []const u8, opts: WaitOptions) !void { - try self.push(.{ .at = self.t, .do = .{ .wait = .{ .until = .{ .shown = try self.dupe(tag) }, .timeout = opts.timeout } } }); + try self.push(.{ + .at = self.t, + .do = .{ .wait = .{ .until = .{ .shown = try self.dupe(tag) }, .timeout = opts.timeout } }, + }); } /// Hold the demo until the tagged widget is gone. pub fn waitGone(self: *Script, tag: []const u8, opts: WaitOptions) !void { - try self.push(.{ .at = self.t, .do = .{ .wait = .{ .until = .{ .gone = try self.dupe(tag) }, .timeout = opts.timeout } } }); + try self.push(.{ + .at = self.t, + .do = .{ .wait = .{ .until = .{ .gone = try self.dupe(tag) }, .timeout = opts.timeout } }, + }); } /// Hold the demo until the stage has nothing in flight. @@ -559,7 +572,13 @@ test "a caption shows for a time per word, never under the floor" { try testing.expectEqual(@as(u32, 2000), readingMs("", "a b c d e f")); try testing.expectEqual(@as(u32, 2100), readingMs("", "a b c d e f g")); // Title and text both count, and a long caption is not cut short. - try testing.expectEqual(@as(u32, 300 * 30), readingMs("one two three four five", "a b c d e f g h i j k l m n o p q r s t u v w x y")); + try testing.expectEqual( + @as(u32, 300 * 30), + readingMs("one two three four five", "a b c d e f g h i j k l m n o p q r s t u v w x y"), + ); // Past the ceiling a caption stops growing. - try testing.expectEqual(reading_max_ms, readingMs("", "w w w w w w w w w w w w w w w w w w w w w w w w w w w w w w w w w w w w w w w w w w")); + try testing.expectEqual( + reading_max_ms, + readingMs("", "w w w w w w w w w w w w w w w w w w w w w w w w w w w w w w w w w w w w w w w w w w"), + ); } diff --git a/sdk/tape/Sequencer.zig b/sdk/tape/Sequencer.zig index bfd190090..fabf38ddb 100644 --- a/sdk/tape/Sequencer.zig +++ b/sdk/tape/Sequencer.zig @@ -76,10 +76,10 @@ const Glide = struct { /// Demo time the pointer was last put down along the path. at: f64, /// Begun with a button held: carrying something, the pointer goes where the target was seen - /// to settle, and does not follow it after (`locked`). What it is carried over moves for it — a tab strip - /// makes room for the tab, a drop opens under it — and chasing that moved the room it made: - /// the tab carried at a tab went back and forth between two places every other frame. A person - /// aims where they saw it, too. + /// to settle, and does not follow it after (`locked`). What it is carried over moves for it — a + /// tab strip makes room for the tab, a drop opens under it — and chasing that moved the room + /// it made: the tab carried at a tab went back and forth between two places every other frame. + /// A person aims where they saw it, too. carried: bool = false, /// Carried, and the target seen in the same place on two frames: `to` is where it goes. locked: bool = false, @@ -312,7 +312,12 @@ pub fn advance(self: *Sequencer, until: f64, wall_ms: f64, sink: Sink) Progress // A move only: a stroke's points are on a canvas that is not going anywhere, and a // drawing has hundreds of them. if (op.do == .move) if (sink.vtable.still) |still| if (!still(sink.ctx, target)) return .holding; - self.glide = .{ .op = self.cursor, .from = self.pointer, .at = self.now, .carried = self.pressed.count() > 0 }; + self.glide = .{ + .op = self.cursor, + .from = self.pointer, + .at = self.now, + .carried = self.pressed.count() > 0, + }; self.cursor += 1; }, .type => { @@ -507,7 +512,8 @@ fn pathPoint(op: Tape.Op, from: Point, to: Point, f: f32) Point { } /// The pointer `f` of the way along a glide: eased in and out, on a slight bow rather than a ruled -/// line — a hand moving a mouse swings a little, and a dead-straight constant-speed pointer reads as +/// line — a hand moving a mouse swings a little, and a dead-straight constant-speed pointer reads +/// as /// a machine. pub fn glidePoint(from: Point, to: Point, f: f32) Point { const e = easeInOut(std.math.clamp(f, 0, 1)); @@ -966,7 +972,10 @@ test "a timed-out wait still releases a button held across it, but not a press i defer log.deinit(); var seq: Sequencer = .init(&tape); while (!seq.done()) _ = seq.advance(std.math.inf(f64), 30, log.sink()); - try testing.expectEqualStrings("keyframe demo://a\npress left\ntimeout 2 skipping 2\nrelease left\n", log.out.items); + try testing.expectEqualStrings( + "keyframe demo://a\npress left\ntimeout 2 skipping 2\nrelease left\n", + log.out.items, + ); } test "a timed-out wait skips a keyframe and the commands after it, up to the next chapter" { @@ -1000,7 +1009,10 @@ test "a timed-out wait releases the buttons held across it, whichever order the defer log.deinit(); var seq: Sequencer = .init(&tape); while (!seq.done()) _ = seq.advance(std.math.inf(f64), 30, log.sink()); - try testing.expectEqualStrings("keyframe demo://a\npress left\ntimeout 2 skipping 2\nrelease left\n", log.out.items); + try testing.expectEqualStrings( + "keyframe demo://a\npress left\ntimeout 2 skipping 2\nrelease left\n", + log.out.items, + ); try testing.expectEqual(@as(usize, 0), seq.pressed.count()); } @@ -1018,7 +1030,10 @@ test "a move to a target never drawn skips the chapter, so no press lands off ta defer log.deinit(); var seq: Sequencer = .init(&tape); while (!seq.done()) _ = seq.advance(std.math.inf(f64), 30, log.sink()); - try testing.expectEqualStrings("keyframe demo://a\nmove 0,0\nmissing missing 1 skipping 2\nkey plays\n", log.out.items); + try testing.expectEqualStrings( + "keyframe demo://a\nmove 0,0\nmissing missing 1 skipping 2\nkey plays\n", + log.out.items, + ); } test "rewinding to a keyframe and replaying fast lands where live play did" { diff --git a/sdk/tape/Tape.zig b/sdk/tape/Tape.zig index 2ff612cde..8ff0f51f0 100644 --- a/sdk/tape/Tape.zig +++ b/sdk/tape/Tape.zig @@ -22,8 +22,8 @@ //! **Steps** carry a document's own history rather than input: the effect of one change (the //! pixels a stroke set, the text a transaction inserted) in its owner's encoding, applied to a //! file the keyframe declared. Input replays only in a window laid out as it was recorded; an -//! effect replays exactly anywhere. What a step's bytes mean is the owner's, versioned by the file's -//! `format`, and whether the app can apply them is the app's to say (`Check.steps`). +//! effect replays exactly anywhere. What a step's bytes mean is the owner's, versioned by the +//! file's `format`, and whether the app can apply them is the app's to say (`Check.steps`). //! //! Everything here is plain data with no pointers into anything but its own strings, so a tape //! round-trips through ZON (`parse`, `write`): the same thing a script builds (`Script`) is what @@ -484,7 +484,8 @@ pub const Check = struct { /// Check what `Sequencer` relies on, once, at load — a tape that fails here would misplay rather /// than crash, which is worse to debug. pub fn validate(self: Tape, check: Check) Error!void { - if (!check.live and (self.ops.len == 0 or self.ops[0].at != 0 or self.ops[0].do != .keyframe)) return error.NoKeyframeAtStart; + if (!check.live and (self.ops.len == 0 or self.ops[0].at != 0 or self.ops[0].do != .keyframe)) + return error.NoKeyframeAtStart; var prev: u32 = 0; // The keyframe the ops are under: a step's file is one of its. var scene: ?*const Keyframe = null; @@ -542,7 +543,10 @@ pub fn parse(gpa: std.mem.Allocator, source: [:0]const u8, check: Check) !Owned var arena: std.heap.ArenaAllocator = .init(gpa); errdefer arena.deinit(); var diag: std.zon.parse.Diagnostics = undefined; - const tape = try std.zon.parse.fromSlice(Tape, .{ .gpa = gpa, .arena = arena.allocator(), .source = source, .diagnostics = &diag }); + const tape = try std.zon.parse.fromSlice( + Tape, + .{ .gpa = gpa, .arena = arena.allocator(), .source = source, .diagnostics = &diag }, + ); try tape.validate(check); return .{ .arena = arena, .tape = tape }; } @@ -573,7 +577,14 @@ fn sample() Tape { const chapters = [_]Chapter{ .{ .at = 0, .title = "one" }, .{ .at = 1000, .title = "two" } }; const captions = [_]Caption{.{ .at = 200, .ms = 2000, .text = "hi", .near = "button", .clear = &.{"field"} }}; }; - return .{ .name = "t", .ops = &S.ops, .keyframes = &S.keyframes, .chapters = &S.chapters, .captions = &S.captions, .home = "pane" }; + return .{ + .name = "t", + .ops = &S.ops, + .keyframes = &S.keyframes, + .chapters = &S.chapters, + .captions = &S.captions, + .home = "pane", + }; } test "a seek replays from the last keyframe at or before it" { @@ -693,8 +704,18 @@ test "a tape must open on a keyframe, in order, with chords the app can spell" { try testing.expectError(error.NoKeyframeAtStart, (Tape{ .name = "x", .ops = &bad_start }).validate(.{})); const kf = [_]Keyframe{.{ .root = "demo://x" }}; - const backwards = [_]Op{ .{ .at = 0, .do = .{ .keyframe = 0 } }, .{ .at = 50, .do = .{ .key = "a" } }, .{ .at = 10, .do = .{ .key = "b" } } }; - try testing.expectError(error.OpsOutOfOrder, (Tape{ .name = "x", .ops = &backwards, .keyframes = &kf }).validate(.{})); + const backwards = [_]Op{ + .{ + .at = 0, + .do = .{ .keyframe = 0 }, + }, + .{ .at = 50, .do = .{ .key = "a" } }, + .{ .at = 10, .do = .{ .key = "b" } }, + }; + try testing.expectError( + error.OpsOutOfOrder, + (Tape{ .name = "x", .ops = &backwards, .keyframes = &kf }).validate(.{}), + ); // Whether a chord is spelled right is the app's to say; unasked, it is not checked here. const bad_chord = [_]Op{ .{ .at = 0, .do = .{ .keyframe = 0 } }, .{ .at = 1, .do = .{ .key = "mod+nosuchkey" } } }; @@ -703,7 +724,10 @@ test "a tape must open on a keyframe, in order, with chords the app can spell" { try testing.expectError(error.BadChord, chords.validate(test_check)); const bad_index = [_]Op{.{ .at = 0, .do = .{ .keyframe = 3 } }}; - try testing.expectError(error.BadKeyframeIndex, (Tape{ .name = "x", .ops = &bad_index, .keyframes = &kf }).validate(.{})); + try testing.expectError( + error.BadKeyframeIndex, + (Tape{ .name = "x", .ops = &bad_index, .keyframes = &kf }).validate(.{}), + ); } test "a live tape plays on the app as it is: no keyframe, anywhere" { @@ -715,7 +739,10 @@ test "a live tape plays on the app as it is: no keyframe, anywhere" { try (Tape{ .name = "empty", .ops = &.{} }).validate(.{ .live = true }); const cut = [_]Op{ .{ .at = 0, .do = .{ .key = "a" } }, .{ .at = 10, .do = .{ .keyframe = 0 } } }; - try testing.expectError(error.KeyframeInLiveTape, (Tape{ .name = "x", .ops = &cut, .keyframes = &kf }).validate(.{ .live = true })); + try testing.expectError( + error.KeyframeInLiveTape, + (Tape{ .name = "x", .ops = &cut, .keyframes = &kf }).validate(.{ .live = true }), + ); } /// A spelling for tests: any chord whose last key is a single letter. @@ -808,7 +835,15 @@ pub const test_steps: Check = .{ .steps = struct { test "a step changes a file of the keyframe before it, whose owner the app can apply" { const sprite: [5]u8 = .{ 0x89, 'P', 0, 0xff, '\n' }; - const files = [_]File{ .{ .path = "a.pixi", .text = &sprite, .owner = "pixi", .format = 1 }, .{ .path = "notes.md", .text = "# hi" } }; + const files = [_]File{ + .{ + .path = "a.pixi", + .text = &sprite, + .owner = "pixi", + .format = 1, + }, + .{ .path = "notes.md", .text = "# hi" }, + }; const kf = [_]Keyframe{ .{ .root = "demo://s", .files = &files, .open = &.{"a.pixi"} }, .{ .root = "demo://s" } }; const ops = [_]Op{ .{ .at = 0, .do = .{ .keyframe = 0 } }, @@ -820,17 +855,37 @@ test "a step changes a file of the keyframe before it, whose owner the app can a // An app that says nothing of steps, or no to this owner's format, refuses the tape. try testing.expectError(error.StepsUnsupported, tape.validate(.{})); const other_format = [_]File{.{ .path = "a.pixi", .text = &sprite, .owner = "pixi", .format = 2 }}; - try testing.expectError(error.StepsUnsupported, (Tape{ .name = "s", .ops = &ops, .keyframes = &.{.{ .root = "demo://s", .files = &other_format }} }).validate(test_steps)); + try testing.expectError( + error.StepsUnsupported, + (Tape{ + .name = "s", + .ops = &ops, + .keyframes = &.{.{ .root = "demo://s", .files = &other_format }}, + }).validate(test_steps), + ); // A file that names its owner and takes no steps needs nothing of the app. - try (Tape{ .name = "s", .ops = ops[0..1], .keyframes = &.{.{ .root = "demo://s", .files = &other_format }} }).validate(.{}); + try (Tape{ + .name = "s", + .ops = ops[0..1], + .keyframes = &.{.{ .root = "demo://s", .files = &other_format }}, + }).validate(.{}); // Its file must be the keyframe's, and one with an owner to apply it. const elsewhere = [_]Op{ ops[0], .{ .at = 50, .do = .{ .keyframe = 1 } }, ops[1] }; - try testing.expectError(error.StepWithoutFile, (Tape{ .name = "s", .ops = &elsewhere, .keyframes = &kf }).validate(test_steps)); + try testing.expectError( + error.StepWithoutFile, + (Tape{ .name = "s", .ops = &elsewhere, .keyframes = &kf }).validate(test_steps), + ); const ownerless = [_]Op{ ops[0], .{ .at = 50, .do = .{ .step = .{ .file = "notes.md", .effect = "x" } } } }; - try testing.expectError(error.StepWithoutFile, (Tape{ .name = "s", .ops = &ownerless, .keyframes = &kf }).validate(test_steps)); + try testing.expectError( + error.StepWithoutFile, + (Tape{ .name = "s", .ops = &ownerless, .keyframes = &kf }).validate(test_steps), + ); // A live tape has no keyframe, so nothing to step. - try testing.expectError(error.StepWithoutFile, (Tape{ .name = "s", .ops = ops[1..] }).validate(.{ .live = true, .steps = test_steps.steps })); + try testing.expectError( + error.StepWithoutFile, + (Tape{ .name = "s", .ops = ops[1..] }).validate(.{ .live = true, .steps = test_steps.steps }), + ); // Through ZON, the binary file and the steps come back byte for byte. var out: std.Io.Writer.Allocating = .init(testing.allocator); diff --git a/sdk/tape/binary.zig b/sdk/tape/binary.zig index 5f940d88d..28daabb9a 100644 --- a/sdk/tape/binary.zig +++ b/sdk/tape/binary.zig @@ -230,7 +230,11 @@ fn writeBody(gpa: std.mem.Allocator, tape: Tape, w: *std.Io.Writer) !void { .blobs = count(blobs.items.len), .blob_bytes = count(blob_bytes), }; - inline for (@typeInfo(Header).@"struct".field_names, @typeInfo(Header).@"struct".field_types, @typeInfo(Header).@"struct".field_attrs) |f_name, _, _| try w.writeInt(u32, @field(h, f_name), .little); + inline for ( + @typeInfo(Header).@"struct".field_names, + @typeInfo(Header).@"struct".field_types, + @typeInfo(Header).@"struct".field_attrs, + ) |f_name, _, _| try w.writeInt(u32, @field(h, f_name), .little); try writeTable(w, table.list.items, table.bytes); try writeTable(w, blobs.items, blob_bytes); @@ -310,7 +314,14 @@ fn writeBody(gpa: std.mem.Allocator, tape: Tape, w: *std.Io.Writer) !void { try w.writeInt(u32, table.index(kf.root), .little); try w.writeByte(@backingInt(kf.layout)); try w.splatByteAll(0, 3); - for ([_]u32{ files_at, count(kf.files.len), open_at, count(kf.open.len), settings_at, count(kf.settings.len) }) |v| + for ([_]u32{ + files_at, + count(kf.files.len), + open_at, + count(kf.open.len), + settings_at, + count(kf.settings.len), + }) |v| try w.writeInt(u32, v, .little); files_at += count(kf.files.len); open_at += count(kf.open.len); @@ -502,7 +513,11 @@ pub fn read(gpa: std.mem.Allocator, bytes: []const u8, check: Tape.Check) ReadEr } }, .wait => .{ .wait = .{ .timeout = ib, .until = try untilOf(strings, small, ia) } }, .skip => .{ .skip = .{ .count = ib, .when = try untilOf(strings, small, ia) } }, - .step => .{ .step = .{ .file = try strings.get(ia), .effect = try blobs.get(ib), .motion = try blobs.get(words[0]) } }, + .step => .{ .step = .{ + .file = try strings.get(ia), + .effect = try blobs.get(ib), + .motion = try blobs.get(words[0]), + } }, } }; } @@ -523,7 +538,11 @@ pub fn read(gpa: std.mem.Allocator, bytes: []const u8, check: Tape.Check) ReadEr const settings = try a.alloc(Tape.Setting, h.settings); for (settings, 0..) |*s, i| { var f: Reader = .{ .bytes = setting_bytes[i * setting_size ..][0..setting_size] }; - s.* = .{ .owner = try strings.get(try f.int(u32)), .key = try strings.get(try f.int(u32)), .value = try strings.get(try f.int(u32)) }; + s.* = .{ + .owner = try strings.get(try f.int(u32)), + .key = try strings.get(try f.int(u32)), + .value = try strings.get(try f.int(u32)), + }; } const keyframes = try a.alloc(Tape.Keyframe, h.keyframes); for (keyframes, 0..) |*kf, i| { @@ -674,14 +693,29 @@ const testing = std.testing; const Everything = struct { const files = [_]Tape.File{ .{ .path = "a.md", .text = "# A\n" }, .{ .path = "b.md", .text = "" } }; const settings = [_]Tape.Setting{.{ .owner = "markdown", .key = "default_md_view", .value = ".split" }}; - const keyframe = Tape.Keyframe{ .root = "demo://t", .files = &files, .open = &.{ "a.md", "b.md" }, .settings = &settings, .layout = .reset }; + const keyframe = Tape.Keyframe{ + .root = "demo://t", + .files = &files, + .open = &.{ "a.md", "b.md" }, + .settings = &settings, + .layout = .reset, + }; /// A binary document, by its owner, and its steps. - const sprite = [_]Tape.File{.{ .path = "a.pixi", .text = &.{ 0x89, 'P', 0, 0xff, '\n', 0 }, .owner = "pixi", .format = 1 }}; + const sprite = [_]Tape.File{.{ + .path = "a.pixi", + .text = &.{ 0x89, 'P', 0, 0xff, '\n', 0 }, + .owner = "pixi", + .format = 1, + }}; const ops_v2 = [_]Tape.Op{ .{ .at = 0, .do = .{ .keyframe = 0 } }, .{ .at = 0, .do = .{ .wait = .{ .until = .idle } } }, .{ .at = 0, .do = .{ .wait = .{ .until = .{ .shown = "text.end:demo://t/a.md" }, .timeout = 2500 } } }, - .{ .at = 100, .ms = 400, .do = .{ .move = .{ .tag = "text.end:demo://t/a.md", .x = 0.25, .y = 0.75, .dx = -3.5, .dy = 8 } } }, + .{ + .at = 100, + .ms = 400, + .do = .{ .move = .{ .tag = "text.end:demo://t/a.md", .x = 0.25, .y = 0.75, .dx = -3.5, .dy = 8 } }, + }, .{ .at = 500, .do = .{ .press = .right } }, .{ .at = 560, .do = .{ .release = .right } }, .{ .at = 600, .do = .{ .scroll = .{ .x = 1, .y = -3 } } }, @@ -695,7 +729,11 @@ const Everything = struct { .{ .at = 1700, .ms = 80, .do = .{ .trace = .{ .tag = "pixi.canvas:demo://t/a.pixi", .x = 0.4, .y = 0.6 } } }, }; const ops = ops_v2 ++ [_]Tape.Op{ - .{ .at = 1800, .ms = 250, .do = .{ .step = .{ .file = "a.pixi", .effect = "\x00\x01\xff", .motion = "\x02\x00" } } }, + .{ + .at = 1800, + .ms = 250, + .do = .{ .step = .{ .file = "a.pixi", .effect = "\x00\x01\xff", .motion = "\x02\x00" } }, + }, // The same effect again is stored again; one with no motion stores an empty one. .{ .at = 2100, .do = .{ .step = .{ .file = "a.pixi", .effect = "\x00\x01\xff" } } }, .{ .at = 2200, .do = .{ .skip = .{ .when = .{ .shown = "fizzy.rail:workbench.files" }, .count = 3 } } }, @@ -703,12 +741,26 @@ const Everything = struct { }; const captions = [_]Tape.Caption{ .{ .at = 0, .ms = 900, .title = "Hi", .text = "Welcome.", .place = .middle }, - .{ .at = 900, .ms = 600, .text = "Beside it.", .near = "text.end:demo://t/a.md", .clear = &.{ "text.body:demo://t/a.md", "text.preview:demo://t/a.md" } }, + .{ + .at = 900, + .ms = 600, + .text = "Beside it.", + .near = "text.end:demo://t/a.md", + .clear = &.{ "text.body:demo://t/a.md", "text.preview:demo://t/a.md" }, + }, }; const chapters = [_]Tape.Chapter{ .{ .at = 0, .title = "One" }, .{ .at = 1400, .title = "Two" } }; fn tape(ops_: []const Tape.Op, keyframes: []const Tape.Keyframe) Tape { - return .{ .name = "t", .title = "Everything", .home = "region:Main", .ops = ops_, .keyframes = keyframes, .captions = &captions, .chapters = &chapters }; + return .{ + .name = "t", + .title = "Everything", + .home = "region:Main", + .ops = ops_, + .keyframes = keyframes, + .captions = &captions, + .chapters = &chapters, + }; } }; @@ -907,10 +959,13 @@ test "a version-1 tape still reads, its commands without arguments" { test "a recording's key chords and steps are checked against the app, as in ZON" { const bytes = try encode(testing.allocator, everything(), .{}); defer testing.allocator.free(bytes); - try testing.expectError(error.BadChord, read(testing.allocator, bytes, .{ .steps = steps_check.steps, .key = struct { - fn ok(c: []const u8) bool { - return std.mem.findScalar(u8, c, ' ') == null; - } - }.ok })); + try testing.expectError(error.BadChord, read(testing.allocator, bytes, .{ + .steps = steps_check.steps, + .key = struct { + fn ok(c: []const u8) bool { + return std.mem.findScalar(u8, c, ' ') == null; + } + }.ok, + })); try testing.expectError(error.StepsUnsupported, read(testing.allocator, bytes, .{})); } diff --git a/sdk/tape/events.zig b/sdk/tape/events.zig index 2f67e62a8..7923b24d0 100644 --- a/sdk/tape/events.zig +++ b/sdk/tape/events.zig @@ -83,7 +83,10 @@ pub fn selectionBytes(start: u64, end: u64) [16]u8 { pub fn selection(payload: []const u8) ?struct { start: u64, end: u64 } { if (payload.len != 16) return null; - return .{ .start = std.mem.readInt(u64, payload[0..8], .little), .end = std.mem.readInt(u64, payload[8..16], .little) }; + return .{ + .start = std.mem.readInt(u64, payload[0..8], .little), + .end = std.mem.readInt(u64, payload[8..16], .little), + }; } fn testFrame(t: i64) Frame { diff --git a/sdk/tape/key.zig b/sdk/tape/key.zig index 467d5dd75..07e68ef46 100644 --- a/sdk/tape/key.zig +++ b/sdk/tape/key.zig @@ -136,7 +136,15 @@ pub const Key = enum { /// ever fire together with itself, which is never what anyone means. pub fn isModifier(k: Key) bool { return switch (k) { - .left_shift, .right_shift, .left_control, .right_control, .left_alt, .right_alt, .left_command, .right_command => true, + .left_shift, + .right_shift, + .left_control, + .right_control, + .left_alt, + .right_alt, + .left_command, + .right_command, + => true, else => false, }; } diff --git a/sdk/tape/ring.zig b/sdk/tape/ring.zig index e423ac0f4..bebfe6bc4 100644 --- a/sdk/tape/ring.zig +++ b/sdk/tape/ring.zig @@ -77,8 +77,10 @@ pub fn header(bytes: []const u8) ReadError!Header { pub fn Ring(comptime name: []const u8, comptime Frame: type, comptime Record: type, comptime caps: Caps) type { comptime { - for (.{ "t_ns", "first", "count" }) |f| if (!@hasField(Frame, f)) @compileError(@typeName(Frame) ++ " has no `" ++ f ++ "`"); - for (.{ "bytes_at", "bytes_len" }) |f| if (!@hasField(Record, f)) @compileError(@typeName(Record) ++ " has no `" ++ f ++ "`"); + for (.{ "t_ns", "first", "count" }) |f| + if (!@hasField(Frame, f)) @compileError(@typeName(Frame) ++ " has no `" ++ f ++ "`"); + for (.{ "bytes_at", "bytes_len" }) |f| + if (!@hasField(Record, f)) @compileError(@typeName(Record) ++ " has no `" ++ f ++ "`"); } return struct { const Self = @This(); @@ -208,7 +210,8 @@ pub fn Ring(comptime name: []const u8, comptime Frame: type, comptime Record: ty for (0..fr.count) |k| { const r = self.records[@intCast((fr.first + k) % caps.records)]; var b: u64 = 0; - while (b < r.bytes_len) : (b += 1) try w.writeByte(self.bytes[@intCast((r.bytes_at + b) % caps.bytes)]); + while (b < r.bytes_len) : (b += 1) + try w.writeByte(self.bytes[@intCast((r.bytes_at + b) % caps.bytes)]); } } } @@ -226,7 +229,18 @@ pub fn Ring(comptime name: []const u8, comptime Frame: type, comptime Record: ty /// A stretch of nothing: what a capture holds when nothing was recorded. pub fn writeEmpty(w: *std.Io.Writer) !void { - try w.writeStruct(Header{ .stream = stream, .frame_size = @sizeOf(Frame), .record_size = @sizeOf(Record), .frames = 0, .records = 0, .bytes = 0, .first_frame = 0 }, .little); + try w.writeStruct( + Header{ + .stream = stream, + .frame_size = @sizeOf(Frame), + .record_size = @sizeOf(Record), + .frames = 0, + .records = 0, + .bytes = 0, + .first_frame = 0, + }, + .little, + ); } /// A file `write` made, read in place: its arrays are views into the bytes, checked to fit. @@ -250,7 +264,9 @@ pub fn Ring(comptime name: []const u8, comptime Frame: type, comptime Record: ty pub fn read(bytes: []const u8) ReadError!Stretch { const h = try header(bytes); - if (!std.mem.eql(u8, &h.stream, &stream) or h.frame_size != @sizeOf(Frame) or h.record_size != @sizeOf(Record)) return error.OtherStream; + if (!std.mem.eql(u8, &h.stream, &stream) or + h.frame_size != @sizeOf(Frame) or h.record_size != @sizeOf(Record)) + return error.OtherStream; const frames_bytes = @as(usize, h.frames) * @sizeOf(Frame); const records_bytes = @as(usize, h.records) * @sizeOf(Record); if (bytes.len < @sizeOf(Header) + frames_bytes + records_bytes + h.bytes) return error.Truncated; @@ -312,7 +328,11 @@ test "the rings keep the newest, and a stretch starts at the oldest frame still defer std.testing.allocator.destroy(ring); ring.* = .{}; // More frames than the ring holds, each with one record. - for (0..TestRing.frames_cap + 10) |i| ring.appendFrame(.{ .t_ns = @intCast(i) }, &.{.{ .button = @intCast(i % 16) }}, &.{}); + for (0..TestRing.frames_cap + 10) |i| ring.appendFrame( + .{ .t_ns = @intCast(i) }, + &.{.{ .button = @intCast(i % 16) }}, + &.{}, + ); try std.testing.expectEqual(@as(u64, 10), ring.oldestWhole()); // Payloads outrunning their ring: a frame whose bytes were overwritten is not whole. for (0..8) |_| ring.appendFrame(.{ .t_ns = 100 }, &.{.{ .button = 1 }}, &.{&@as([64]u8, @splat('x'))}); @@ -326,7 +346,15 @@ test "the rings keep the newest, and a stretch starts at the oldest frame still test "a file that lies about its sizes is refused" { try std.testing.expectError(error.Truncated, TestRing.read("FZR")); - var bad: [@sizeOf(Header)]u8 = std.mem.toBytes(Header{ .stream = TestRing.stream, .frame_size = @sizeOf(TestFrame), .record_size = @sizeOf(TestRecord), .frames = 5, .records = 0, .bytes = 0, .first_frame = 0 }); + var bad: [@sizeOf(Header)]u8 = std.mem.toBytes(Header{ + .stream = TestRing.stream, + .frame_size = @sizeOf(TestFrame), + .record_size = @sizeOf(TestRecord), + .frames = 5, + .records = 0, + .bytes = 0, + .first_frame = 0, + }); try std.testing.expectError(error.Truncated, TestRing.read(&bad)); bad[0] = 'X'; try std.testing.expectError(error.NotARecording, TestRing.read(&bad)); From 7944ca6f9d0ae2b4546602be2b4acacaf2d302ad Mon Sep 17 00:00:00 2001 From: "fizzy-linux[bot]" <346940526+fizzy-linux[bot]@users.noreply.github.com> Date: Mon, 26 Oct 2026 04:59:57 -0500 Subject: [PATCH 8/9] lint: line-length (codex) Agent-Team: linux Worker: codex --- src/backend/WebBackend.zig | 213 +++++++++++++++++++++------------ src/backend/backend_native.zig | 32 +++-- src/backend/backend_web.zig | 3 +- src/backend/file_assoc.zig | 4 +- src/backend/layout_file.zig | 10 +- 5 files changed, 177 insertions(+), 85 deletions(-) diff --git a/src/backend/WebBackend.zig b/src/backend/WebBackend.zig index 347441aac..bf18287b2 100644 --- a/src/backend/WebBackend.zig +++ b/src/backend/WebBackend.zig @@ -200,17 +200,50 @@ pub const wasm = if (!builtin.is_test) struct { pub extern "dvui" fn wasm_canvas_height() f32; pub extern "dvui" fn wasm_frame_buffer() u8; - pub extern "dvui" fn wasm_textureCreate(pixels: [*]const u8, width: u32, height: u32, interp: u8, wrap_u: u8, wrap_v: u8) u32; + pub extern "dvui" fn wasm_textureCreate( + pixels: [*]const u8, + width: u32, + height: u32, + interp: u8, + wrap_u: u8, + wrap_v: u8, + ) u32; pub extern "dvui" fn wasm_textureCreateTarget(width: u32, height: u32, interp: u8, wrap_u: u8, wrap_v: u8) u32; - pub extern "dvui" fn wasm_textureCreatePreciseTarget(width: u32, height: u32, interp: u8, wrap_u: u8, wrap_v: u8) u32; + pub extern "dvui" fn wasm_textureCreatePreciseTarget( + width: u32, + height: u32, + interp: u8, + wrap_u: u8, + wrap_v: u8, + ) u32; pub extern "dvui" fn wasm_textureBlend(texture: u32, mode: u8) u8; pub extern "dvui" fn wasm_textureUpdate(texture: u32, pixels: [*]const u8) u8; - pub extern "dvui" fn wasm_textureUpdateSubRect(texture: u32, pixels: [*]const u8, x: u32, y: u32, w: u32, h: u32) u8; + pub extern "dvui" fn wasm_textureUpdateSubRect( + texture: u32, + pixels: [*]const u8, + x: u32, + y: u32, + w: u32, + h: u32, + ) u8; pub extern "dvui" fn wasm_textureClearTarget(u32) void; pub extern "dvui" fn wasm_textureRead(texture: u32, pixels_out: [*]u8, width: u32, height: u32) void; pub extern "dvui" fn wasm_renderTarget(u32) void; pub extern "dvui" fn wasm_textureDestroy(u32) void; - pub extern "dvui" fn wasm_renderStream(index_ptr: [*]const u8, index_len: usize, vertex_ptr: [*]const u8, vertex_len: usize, sizeof_vertex: u8, offset_pos: u8, offset_col: u8, offset_uv: u8, cmd_ptr: [*]const u8, cmd_count: usize, uniform_ptr: [*]const u8, uniform_floats: usize) void; + pub extern "dvui" fn wasm_renderStream( + index_ptr: [*]const u8, + index_len: usize, + vertex_ptr: [*]const u8, + vertex_len: usize, + sizeof_vertex: u8, + offset_pos: u8, + offset_col: u8, + offset_uv: u8, + cmd_ptr: [*]const u8, + cmd_count: usize, + uniform_ptr: [*]const u8, + uniform_floats: usize, + ) void; pub extern "dvui" fn wasm_programCreate(glsl: [*]const u8, len: usize, textures: u32, uniform_vec4s: u32) u32; pub extern "dvui" fn wasm_programStatus(id: u32) u8; pub extern "dvui" fn wasm_programMaxVec4() u32; @@ -220,11 +253,21 @@ pub const wasm = if (!builtin.is_test) struct { pub extern "dvui" fn wasm_open_url(ptr: [*]const u8, len: usize, new_window: bool) void; pub extern "dvui" fn wasm_preferred_color_scheme() u8; pub extern "dvui" fn wasm_prefers_reduced_motion() u8; - pub extern "dvui" fn wasm_download_data(name_ptr: [*]const u8, name_len: usize, data_ptr: [*]const u8, data_len: usize) void; + pub extern "dvui" fn wasm_download_data( + name_ptr: [*]const u8, + name_len: usize, + data_ptr: [*]const u8, + data_len: usize, + ) void; pub extern "dvui" fn wasm_clipboardTextSet(ptr: [*]const u8, len: usize) void; // NOTE: bool in extern becomes 0 and 1 in js, which is falsy and truthy respectively - pub extern "dvui" fn wasm_open_file_picker(id: u64, accept_ptr: [*]const u8, accept_len: usize, multiple: bool) void; + pub extern "dvui" fn wasm_open_file_picker( + id: u64, + accept_ptr: [*]const u8, + accept_len: usize, + multiple: bool, + ) void; pub extern "dvui" fn wasm_get_number_of_files_available(id: u64) usize; pub extern "dvui" fn wasm_get_file_name(id: u64, file_index: usize) [*:0]u8; pub extern "dvui" fn wasm_get_file_size(id: u64, file_index: usize) isize; @@ -284,7 +327,20 @@ pub const wasm = if (!builtin.is_test) struct { pub fn wasm_textureRead(_: u32, _: [*]u8, _: u32, _: u32) void {} pub fn wasm_renderTarget(_: u32) void {} pub fn wasm_textureDestroy(_: u32) void {} - pub fn wasm_renderStream(_: [*]const u8, _: usize, _: [*]const u8, _: usize, _: u8, _: u8, _: u8, _: u8, _: [*]const u8, _: usize, _: [*]const u8, _: usize) void {} + pub fn wasm_renderStream( + _: [*]const u8, + _: usize, + _: [*]const u8, + _: usize, + _: u8, + _: u8, + _: u8, + _: u8, + _: [*]const u8, + _: usize, + _: [*]const u8, + _: usize, + ) void {} pub fn wasm_programCreate(_: [*]const u8, _: usize, _: u32, _: u32) u32 { return 1; } @@ -361,7 +417,6 @@ export fn dvui_c_realloc_sized(ptr: ?*anyopaque, oldsize: usize, newsize: usize) dvui_c_free(ptr); return newptr; - //std.mem.writeInt(usize, slice[0..@sizeOf(usize)], slice.len, builtin.target.cpu.arch.endian()); //return slice.ptr + 16; } @@ -475,7 +530,11 @@ fn add_event_raw(w: *dvui.Window, which: u8, int1: u32, int2: u32, float1: f32, w.modifiers = web_mod_code_to_dvui(@intCast(int2)); _ = try w.addEventMouseButton(buttonFromJS(int1), .release); }, - 4 => _ = try w.addEventMouseWheel(float1 * dvui.scroll_speed, if (int1 > 0) .vertical else .horizontal, if (int2 == 0) .mouse else .trackpad), + 4 => _ = try w.addEventMouseWheel( + float1 * dvui.scroll_speed, + if (int1 > 0) .vertical else .horizontal, + if (int2 == 0) .mouse else .trackpad, + ), 5 => { const str = @as([*]u8, @ptrFromInt(int1))[0..int2]; _ = try w.addEventKey(.{ @@ -503,7 +562,11 @@ fn add_event_raw(w: *dvui.Window, which: u8, int1: u32, int2: u32, float1: f32, }, 9 => { const touch: dvui.enums.Button = @fromBackingInt(@intCast(@backingInt(dvui.enums.Button.touch0) + int1)); - _ = try w.addEventPointer(.{ .button = touch, .action = .release, .xynorm = .{ .x = float1, .y = float2 } }); + _ = try w.addEventPointer(.{ + .button = touch, + .action = .release, + .xynorm = .{ .x = float1, .y = float2 }, + }); touchPoints[int1] = null; }, 10 => { @@ -673,61 +736,6 @@ fn web_mod_code_to_dvui(wmod: u8) dvui.enums.Mod { return @as(dvui.enums.Mod, @fromBackingInt(@intCast(m))); } -//pub fn addAllEvents(self: *WebBackend, win: *dvui.Window) !void { -// for (event_temps.items) |e| { -// switch (e.kind) { -// 1 => _ = try win.addEventMouseMotion(e.float1, e.float2), -// 2 => _ = try win.addEventMouseButton(buttonFromJS(e.int1), .press), -// 3 => _ = try win.addEventMouseButton(buttonFromJS(e.int1), .release), -// 4 => _ = try win.addEventMouseWheel(if (e.float1 > 0) -20 else 20, .vertical), -// 5 => { -// const str = @as([*]u8, @ptrFromInt(e.int1))[0..e.int2]; -// _ = try win.addEventKey(.{ -// .action = if (e.float1 > 0) .repeat else .down, -// .code = web_key_code_to_dvui(str), -// .mod = web_mod_code_to_dvui(@trunc(e.float2)), -// }); -// }, -// 6 => { -// const str = @as([*]u8, @ptrFromInt(e.int1))[0..e.int2]; -// _ = try win.addEventKey(.{ -// .action = .up, -// .code = web_key_code_to_dvui(str), -// .mod = web_mod_code_to_dvui(@trunc(e.float2)), -// }); -// }, -// 7 => { -// const str = @as([*]u8, @ptrFromInt(e.int1))[0..e.int2]; -// _ = try win.addEventText(str); -// }, -// 8 => { -// const touch: dvui.enums.Button = @enumFromInt(@intFromEnum(dvui.enums.Button.touch0) + e.int1); -// _ = try win.addEventPointer(touch, .press, .{ .x = e.float1, .y = e.float2 }); -// self.touchPoints[e.int1] = .{ .x = e.float1, .y = e.float2 }; -// }, -// 9 => { -// const touch: dvui.enums.Button = @enumFromInt(@intFromEnum(dvui.enums.Button.touch0) + e.int1); -// _ = try win.addEventPointer(touch, .release, .{ .x = e.float1, .y = e.float2 }); -// self.touchPoints[e.int1] = null; -// }, -// 10 => { -// const touch: dvui.enums.Button = @enumFromInt(@intFromEnum(dvui.enums.Button.touch0) + e.int1); -// var dx: f32 = 0; -// var dy: f32 = 0; -// if (self.touchPoints[e.int1]) |p| { -// dx = e.float1 - p.x; -// dy = e.float2 - p.y; -// } -// _ = try win.addEventTouchMotion(touch, e.float1, e.float2, dx, dy); -// self.touchPoints[e.int1] = .{ .x = e.float1, .y = e.float2 }; -// }, -// else => log.debug("addAllEvents unknown event kind {d}", .{e.kind}), -// } -// } -// -// event_temps.clearRetainingCapacity(); -//} - pub fn init() !WebBackend { const ret: WebBackend = .{}; return ret; @@ -752,7 +760,13 @@ pub fn sleep(_: *WebBackend, ns: u64) void { /// The kinds of window material the web can show (fizzy's `core.material`, field for field): /// every one. Its glass is all drawn in the canvas, the one surface it puts glass on (Liquid Glass /// by its own shader), and the page behind a clear canvas shows through. -pub const MaterialKinds = struct { none: bool = true, transparent: bool = true, blur: bool = true, frost: bool = true, liquid: bool = true }; +pub const MaterialKinds = struct { + none: bool = true, + transparent: bool = true, + blur: bool = true, + frost: bool = true, + liquid: bool = true, +}; pub fn materialKinds(_: *WebBackend) MaterialKinds { return .{}; @@ -785,7 +799,13 @@ pub fn contentScale(_: *WebBackend) f32 { return 1.0; // comes through windowSize/pixelSize } -pub fn drawClippedTriangles(self: *WebBackend, texture: ?dvui.Texture, vtx: []const dvui.Vertex, idx: []const dvui.Vertex.Index, maybe_clipr: ?dvui.Rect.Physical) !void { +pub fn drawClippedTriangles( + self: *WebBackend, + texture: ?dvui.Texture, + vtx: []const dvui.Vertex, + idx: []const dvui.Vertex.Index, + maybe_clipr: ?dvui.Rect.Physical, +) !void { if (self.unseen and !target_is_fb) return; if (batch.vtx.items.len + vtx.len > std.math.maxInt(dvui.Vertex.Index)) flushBatch(); @@ -844,7 +864,16 @@ fn command(texture: ?dvui.Texture, maybe_clipr: ?dvui.Rect.Physical, index_start /// A switch or clear (`Command.clip`) of `texture` (0 the screen), in order with the draws. fn marker(step: i32, texture: i32) !void { - try batch.cmds.append(arena, .{ .texture = texture, .clip = step, .x = 0, .y = 0, .w = 0, .h = 0, .index_start = 0, .index_count = 0 }); + try batch.cmds.append(arena, .{ + .texture = texture, + .clip = step, + .x = 0, + .y = 0, + .w = 0, + .h = 0, + .index_start = 0, + .index_count = 0, + }); } /// Whether anything waiting in the stream uses texture `id` — draws from it, draws into it, or @@ -901,7 +930,16 @@ fn programMarker() !void { const p = &(program orelse return); const at: i32 = @intCast(batch.uniforms.items.len); try batch.uniforms.appendSlice(arena, p.uniforms[0..p.n]); - try batch.cmds.append(arena, .{ .texture = p.id, .clip = Command.use_program, .x = at, .y = @intCast(p.n), .w = p.tex1, .h = p.tex2, .index_start = 0, .index_count = 0 }); + try batch.cmds.append(arena, .{ + .texture = p.id, + .clip = Command.use_program, + .x = at, + .y = @intCast(p.n), + .w = p.tex1, + .h = p.tex2, + .index_start = 0, + .index_count = 0, + }); } /// Custom programs (`core.gfx.programs`): compiled by `web.js`, switched to in the stream. @@ -923,7 +961,13 @@ pub const program_api = struct { return wasm.wasm_programStatus(id); } - pub fn begin(id: u32, textures: [*]const ?*anyopaque, n_textures: u32, uniforms: [*]const [4]f32, n_uniforms: u32) callconv(.c) bool { + pub fn begin( + id: u32, + textures: [*]const ?*anyopaque, + n_textures: u32, + uniforms: [*]const [4]f32, + n_uniforms: u32, + ) callconv(.c) bool { if (n_uniforms > uniform_cap or n_textures > 2) return false; if (wasm.wasm_programStatus(id) != 2) return false; const tex = struct { @@ -954,7 +998,16 @@ pub const program_api = struct { blend_override = null; if (program == null) return; program = null; - batch.cmds.append(arena, .{ .texture = 0, .clip = Command.use_program, .x = 0, .y = 0, .w = 0, .h = 0, .index_start = 0, .index_count = 0 }) catch {}; + batch.cmds.append(arena, .{ + .texture = 0, + .clip = Command.use_program, + .x = 0, + .y = 0, + .w = 0, + .h = 0, + .index_start = 0, + .index_count = 0, + }) catch {}; } }; @@ -994,15 +1047,27 @@ pub fn textureCreate(_: *WebBackend, pixels: [*]const u8, options: dvui.Texture. /// See `dvui.Backend.textureUpdate`. `texSubImage2D` over the whole texture. pub fn textureUpdate(_: *WebBackend, texture: dvui.Texture, pixels: [*]const u8) !void { if (pendingUses(@intCast(@intFromPtr(texture.ptr)))) flushBatch(); - if (wasm.wasm_textureUpdate(@intCast(@intFromPtr(texture.ptr)), pixels) == 0) return dvui.Backend.TextureError.TextureUpdate; + if (wasm.wasm_textureUpdate(@intCast(@intFromPtr(texture.ptr)), pixels) == 0) { + return dvui.Backend.TextureError.TextureUpdate; + } } /// See `dvui.Backend.textureUpdateSubRect`. `pixels` is the full texture's buffer; only the /// rect is uploaded (WebGL2 reads it in place through the unpack row length and skips; WebGL1 /// copies the rect's rows out first). -pub fn textureUpdateSubRect(_: *WebBackend, texture: dvui.Texture, pixels: [*]const u8, x: u32, y: u32, w: u32, h: u32) !void { +pub fn textureUpdateSubRect( + _: *WebBackend, + texture: dvui.Texture, + pixels: [*]const u8, + x: u32, + y: u32, + w: u32, + h: u32, +) !void { if (pendingUses(@intCast(@intFromPtr(texture.ptr)))) flushBatch(); - if (wasm.wasm_textureUpdateSubRect(@intCast(@intFromPtr(texture.ptr)), pixels, x, y, w, h) == 0) return dvui.Backend.TextureError.TextureUpdate; + if (wasm.wasm_textureUpdateSubRect(@intCast(@intFromPtr(texture.ptr)), pixels, x, y, w, h) == 0) { + return dvui.Backend.TextureError.TextureUpdate; + } } /// See `dvui.Backend.support_precise_targets`. `precision = .high` is RGBA16F on WebGL2 with diff --git a/src/backend/backend_native.zig b/src/backend/backend_native.zig index cd56b91a2..f1141e167 100644 --- a/src/backend/backend_native.zig +++ b/src/backend/backend_native.zig @@ -1,5 +1,6 @@ -//! Fizzy's own use of its window: the platform pieces any app gets (the backend package's `platform` -//! — dialogs, files from the OS, gestures, chrome, geometry, the window monitor, the menu bar) with +//! Fizzy's own use of its window: the platform pieces any app gets (the backend package's +//! `platform` — dialogs, files from the OS, gestures, chrome, geometry, the window monitor, +//! the menu bar) with //! fizzy's policy over them — where dialogs start, where geometry is kept (`layout.zon`), how high //! the titlebar strip is, what its menus hold and when their items are enabled. The web build has //! the same surface in `backend_web.zig`. @@ -16,8 +17,9 @@ const KeybindSettings = @import("../editor/KeybindSettings.zig"); const menu_model = @import("../editor/menu_model.zig"); const AppInfo = @import("app").AppInfo; -/// The window and platform pieces any app on these backends gets (the backend package's `platform`): -/// dialogs, files from the OS, gestures, the window's chrome and state, Windows' title bar. What +/// The window and platform pieces any app on these backends gets (the backend package's +/// `platform`): dialogs, files from the OS, gestures, the window's chrome and state, +/// Windows' title bar. What /// follows them here is fizzy's own use of them. const platform = @import("platform"); @@ -150,7 +152,10 @@ pub const WindowGlassLook = platform.window.WindowGlassLook; pub fn windowMaterialBase(win: *dvui.Window, fill: dvui.Color, opacity: f32, look: WindowGlassLook) ?dvui.Color { if (comptime builtin.target.os.tag != .macos) return null; if (comptime !@hasDecl(dvui.backend, "setWindowLook")) { - return if (platform.window.liquidGlassLook(win.backend.impl.window, look.native())) dvui.Color{ .r = 0, .g = 0, .b = 0, .a = 0 } else null; + if (platform.window.liquidGlassLook(win.backend.impl.window, look.native())) { + return dvui.Color{ .r = 0, .g = 0, .b = 0, .a = 0 }; + } + return null; } dvui.backend.setWindowLook(fill, opacity); return dvui.backend.materialBase(win.backend.impl.effectiveMaterial(), fill); @@ -195,7 +200,11 @@ fn layoutStoreSave(_: ?*anyopaque, g: platform.geometry.Geometry) void { /// pane mid-transition; a zoom without a Space keeps the full strip. Fizzy's titlebar heights /// over the window's state (`platform.macos_monitor.titlebarState`). pub fn titlebarStripHeight(win: *dvui.Window) f32 { - return platform.macos_monitor.titlebarStrip(win.backend.impl.window, Constants.titlebar_height, Constants.titlebar_top_buffer); + return platform.macos_monitor.titlebarStrip( + win.backend.impl.window, + Constants.titlebar_height, + Constants.titlebar_top_buffer, + ); } // ---- The native menu bar: fizzy's menus (`menu_model`) on the platform's (`platform.menu`) ---- @@ -231,7 +240,9 @@ const native_menus: []const platform.menu.Menu = blk: { n += 1; }, .command => |cmd| { - entries[n] = .{ .command = .{ .title = cmd.title.resolveStatic(), .tag = tag, .symbol = cmd.sf_symbol } }; + entries[n] = .{ + .command = .{ .title = cmd.title.resolveStatic(), .tag = tag, .symbol = cmd.sf_symbol }, + }; n += 1; tag += 1; }, @@ -334,7 +345,12 @@ pub fn rebuildDynamicNativeMenus() void { } for (host.native_menu_items.items, 0..) |ni, i| { if (ni.hidden) continue; - items.append(alloc(), .{ .menu_id = ni.parent_menu_id, .title = ni.title, .symbol = ni.sf_symbol, .tag = @intCast(i) }) catch {}; + items.append(alloc(), .{ + .menu_id = ni.parent_menu_id, + .title = ni.title, + .symbol = ni.sf_symbol, + .tag = @intCast(i), + }) catch {}; } platform.menu.setExtras(menus.items, items.items); // The items are built with no key equivalent; this is what puts the keymap's chords on them. diff --git a/src/backend/backend_web.zig b/src/backend/backend_web.zig index 8835edfe8..893bd54bb 100644 --- a/src/backend/backend_web.zig +++ b/src/backend/backend_web.zig @@ -237,7 +237,8 @@ pub const modifier_control: c_ulong = 0; /// Web's dialog callbacks run synchronously within the frame already (`WebSaveAs.callAfter`), so /// there's nothing to drain here. Kept symmetric with the native backend's deferred-dispatch -/// queue (see `backend_native.pollPendingDialogResult`) so `Editor.tick` can call it unconditionally. +/// queue (see `backend_native.pollPendingDialogResult`) so `Editor.tick` can call it +/// unconditionally. pub const PendingDialogResult = struct { callback: *const fn (?[][:0]const u8) void, files: ?[][:0]const u8, diff --git a/src/backend/file_assoc.zig b/src/backend/file_assoc.zig index 150850d57..c05d5e8a5 100644 --- a/src/backend/file_assoc.zig +++ b/src/backend/file_assoc.zig @@ -295,7 +295,9 @@ const WindowsImpl = struct { ); if (status != .NO_ERROR or hkey == null) return; defer _ = registry.RegCloseKey(hkey); - _ = expected_progid; // Best-effort: just clear the default; a UserChoice in HKCU\…\Explorer\FileExts will win regardless. + // Best-effort: just clear the default; a UserChoice in HKCU\…\Explorer\FileExts will win + // regardless. + _ = expected_progid; // Clear the (default) value by writing empty REG_SZ. var empty: [1]u16 = .{0}; _ = registry.RegSetValueExW(hkey, null, 0, .SZ, @ptrCast(&empty), @sizeOf(u16)); diff --git a/src/backend/layout_file.zig b/src/backend/layout_file.zig index 5f8f849cf..130a34747 100644 --- a/src/backend/layout_file.zig +++ b/src/backend/layout_file.zig @@ -241,7 +241,15 @@ pub fn loadRegions(gpa: std.mem.Allocator, dir: []const u8, shape: []const u8) [ // A float whose home fails to copy floats still; closed, its view goes to its keywords. var floating = r.floating; if (floating) |*fl| fl.home = gpa.dupe(u8, fl.home) catch ""; - out[n] = .{ .name = name, .extent = r.extent, .surfaces = surfaces, .parent = parent, .from = from, .order = order, .floating = floating }; + out[n] = .{ + .name = name, + .extent = r.extent, + .surfaces = surfaces, + .parent = parent, + .from = from, + .order = order, + .floating = floating, + }; n += 1; } return out[0..n]; From d2981385ae94ed6c16b52710afa5e419e8c76bab Mon Sep 17 00:00:00 2001 From: "fizzy-linux[bot]" <346940526+fizzy-linux[bot]@users.noreply.github.com> Date: Mon, 26 Oct 2026 09:12:21 -0500 Subject: [PATCH 9/9] lint: line-length (codex) Agent-Team: linux Worker: codex --- sdk/replay/BlackBox.zig | 30 +++++++++++++---- sdk/replay/Capture.zig | 11 +++++-- sdk/replay/Input.zig | 9 ++++- sdk/replay/LiveDriver.zig | 26 +++++++-------- sdk/replay/Player.zig | 69 +++++++++++++++++++++++++++++---------- sdk/replay/Snapshot.zig | 69 ++++++++++++++++++++++++++++++++++----- sdk/replay/Stage.zig | 6 ++-- sdk/replay/overlay.zig | 29 +++++++++++++--- 8 files changed, 192 insertions(+), 57 deletions(-) diff --git a/sdk/replay/BlackBox.zig b/sdk/replay/BlackBox.zig index eec1711e5..5c3890c7e 100644 --- a/sdk/replay/BlackBox.zig +++ b/sdk/replay/BlackBox.zig @@ -6,9 +6,9 @@ //! person's input and any tape's. Each dvui event becomes a fixed record in `tape.events`' rings: //! pointer motion and buttons where they were, in the window's pixels, wheel ticks, keys down, //! repeated and up with their modifiers, text as it arrived. Focus and position events are left -//! out: dvui makes them from the others, and a replay's dvui will again. A frame costs a copy of its -//! events (ReleaseFast, measured: 0.5 ns a frame with none, 5 ns with four); the rings are 1.3 MB, -//! made the first time it records. +//! out: dvui makes them from the others, and a replay's dvui will again. A frame costs a copy of +//! its events (ReleaseFast, measured: 0.5 ns a frame with none, 5 ns with four); the rings are 1.3 +//! MB, made the first time it records. //! //! Any dvui app can keep one: dvui and `tape` only. Fizzy writes its stretches as captures //! (`fizzy.capture`) and replays them (plans/RECORDER_PLAN.md, step 3). A stream that is not dvui's @@ -68,9 +68,27 @@ pub fn record(self: *BlackBox, gpa: std.mem.Allocator) void { fn toRecord(e: *const dvui.Event) ?events.Event { return switch (e.evt) { .mouse => |me| switch (me.action) { - .motion => .{ .kind = .motion, .button = @backingInt(me.button), .mod = @backingInt(me.mod), .x = me.p.x, .y = me.p.y }, - .press => .{ .kind = .press, .button = @backingInt(me.button), .mod = @backingInt(me.mod), .x = me.p.x, .y = me.p.y }, - .release => .{ .kind = .release, .button = @backingInt(me.button), .mod = @backingInt(me.mod), .x = me.p.x, .y = me.p.y }, + .motion => .{ + .kind = .motion, + .button = @backingInt(me.button), + .mod = @backingInt(me.mod), + .x = me.p.x, + .y = me.p.y, + }, + .press => .{ + .kind = .press, + .button = @backingInt(me.button), + .mod = @backingInt(me.mod), + .x = me.p.x, + .y = me.p.y, + }, + .release => .{ + .kind = .release, + .button = @backingInt(me.button), + .mod = @backingInt(me.mod), + .x = me.p.x, + .y = me.p.y, + }, .wheel_x => |ticks| .{ .kind = .wheel_x, .mod = @backingInt(me.mod), .x = ticks }, .wheel_y => |ticks| .{ .kind = .wheel_y, .mod = @backingInt(me.mod), .x = ticks }, .focus, .position => null, diff --git a/sdk/replay/Capture.zig b/sdk/replay/Capture.zig index 762c015cc..ad1b12f8c 100644 --- a/sdk/replay/Capture.zig +++ b/sdk/replay/Capture.zig @@ -12,8 +12,8 @@ //! and every window at once, laid out as they lie on the desktop round the main one, by `all`. //! //! Asked for like a `Snapshot` (`request`, then `get` once taken): the next frame the app draws -//! is read back at its end (`endFrame`) and kept, as the raw premultiplied pixels it drew, until the -//! next is taken. Nothing is encoded here: the frame pays for one read back and one copy, and +//! is read back at its end (`endFrame`) and kept, as the raw premultiplied pixels it drew, until +//! the next is taken. Nothing is encoded here: the frame pays for one read back and one copy, and //! whoever wants an image file (the agent's bridge, in its own process) scales and encodes it. const Capture = @This(); @@ -148,7 +148,12 @@ const max_side: f32 = 8192; /// Every window, each where it lies round the main window, a later one over an earlier. fn everyWindow(arena: std.mem.Allocator, sources: []const Source) !Pixels { var span: dvui.Rect.Physical = .{ .w = @floatFromInt(sources[0].w), .h = @floatFromInt(sources[0].h) }; - for (sources[1..]) |s| span = span.unionWith(.{ .x = s.at.x, .y = s.at.y, .w = @floatFromInt(s.w), .h = @floatFromInt(s.h) }); + for (sources[1..]) |s| span = span.unionWith(.{ + .x = s.at.x, + .y = s.at.y, + .w = @floatFromInt(s.w), + .h = @floatFromInt(s.h), + }); const w: u32 = @intFromFloat(@min(@round(span.w), max_side)); const h: u32 = @intFromFloat(@min(@round(span.h), max_side)); const out = try arena.alloc(u8, @as(usize, w) * h * 4); diff --git a/sdk/replay/Input.zig b/sdk/replay/Input.zig index 611a8cc64..5720c22d3 100644 --- a/sdk/replay/Input.zig +++ b/sdk/replay/Input.zig @@ -192,7 +192,14 @@ fn pressChord(c: chord.Chord) void { // dvui keeps the last key event's modifiers as the window's, and every pointer event after // carries them: let go of the modifier, as a hand does, or the next click is a Ctrl-click. if (mod != .none) { - const modifier: dvui.enums.Key = if (c.mods.command) .left_command else if (c.mods.ctrl) .left_control else if (c.mods.alt) .left_alt else .left_shift; + const modifier: dvui.enums.Key = if (c.mods.command) + .left_command + else if (c.mods.ctrl) + .left_control + else if (c.mods.alt) + .left_alt + else + .left_shift; addKey(cw, .{ .code = modifier, .mod = .none, .action = .up }); } } diff --git a/sdk/replay/LiveDriver.zig b/sdk/replay/LiveDriver.zig index fbc58c1f2..4ae34ec03 100644 --- a/sdk/replay/LiveDriver.zig +++ b/sdk/replay/LiveDriver.zig @@ -2,12 +2,12 @@ //! landed. The other way to play a tape besides a demo, and a driver of its own rather than a //! mode of the `Player` (`plans/AGENTS_PLAN.md`, "Driving a live tape"): //! -//! | | a demo (`Player`) | a live tape (this) | -//! |-----------------------|-------------------------------------|-----------------------------------| -//! | starts from | a keyframe; the session set aside | the app as it is (`Check.live`) | -//! | pacing | the tape's authored times | each op once the app has settled | -//! | a person steps in | pause, and replay on resume | stop, and say before which op | -//! | going back | seek: snapshots, keyframes | none — undo, through commands | +//! | | a demo (`Player`) | a live tape (this) | +//! | --- | --- | --- | +//! | starts from | a keyframe; the session set aside | the app as it is (`Check.live`) | +//! | pacing | the tape's authored times | each op once the app has settled | +//! | a person steps in | pause, and replay on resume | stop, and say before which op | +//! | going back | seek: snapshots, keyframes | none — undo, through commands | //! //! Consumers: a plugin's tests, a macro, a scripted tutorial, anything driving fizzy the way a //! person does. Plugins reach it through the `automation` service (`Service`). @@ -18,13 +18,13 @@ //! live tape while a demo is loaded, and fizzy's `Demo` a demo while a live tape plays). //! //! **Pacing.** Authored times are ignored: each frame the driver asks the sequencer for the next -//! moment anything happens (`Sequencer.nextAt`) and applies it, and goes on only while the stage -//! is idle — nothing it was asked to load still loading. Except a drag: a glide with a button -//! held plays at its authored pace, a frame at a time, as a hand moves (`drag_step_ms`). The sequencer still yields after -//! everything the app has to draw before the next op can land (a press, a key, the end of a -//! glide), so a click on a menu the last click opened finds it open. A tape's own waits hold as -//! they do in a demo, and one that gives up stops the tape: a live tape that has lost its place -//! is better stopped and reported than carried on blind. +//! moment anything happens (`Sequencer.nextAt`) and applies it, and goes on only while the stage is +//! idle — nothing it was asked to load still loading. Except a drag: a glide with a button held +//! plays at its authored pace, a frame at a time, as a hand moves (`drag_step_ms`). The sequencer +//! still yields after everything the app has to draw before the next op can land (a press, a key, +//! the end of a glide), so a click on a menu the last click opened finds it open. A tape's own +//! waits hold as they do in a demo, and one that gives up stops the tape: a live tape that has lost +//! its place is better stopped and reported than carried on blind. //! //! **Interruption.** A real click, tap, scroll, key or typed text stops it, and the person's //! input goes on to do what they meant. Real pointer motion is held off while it plays, as a diff --git a/sdk/replay/Player.zig b/sdk/replay/Player.zig index 769208328..d7f96c0e7 100644 --- a/sdk/replay/Player.zig +++ b/sdk/replay/Player.zig @@ -42,14 +42,15 @@ //! and the backend's clock is moved on by as much (`frames`' `clock`) so dvui's stays monotonic //! and continuous after. The player's own chrome (the transport bar) runs on the wall (`wallNs`). //! -//! **Closing** always leaves the app as it is (`Stage.keep`), and "Revert to before" (`revert`) is the one way back to the session -//! the demo set aside. `play` after a person changed things carries on from there as a new branch -//! (`branched`) rather than replaying over their work; seeking back is the history. +//! **Closing** always leaves the app as it is (`Stage.keep`), and "Revert to before" (`revert`) is +//! the one way back to the session the demo set aside. `play` after a person changed things carries +//! on from there as a new branch (`branched`) rather than replaying over their work; seeking back +//! is the history. //! //! **Interruption**: while playing, a real click, tap, scroll or key pauses the demo — a pointer //! event goes on to do what the person meant, the first key is only taken as "stop". Real pointer -//! motion is held off (the tape owns the pointer while it plays). Anything real that reaches the app -//! while paused marks the app `diverged` from the tape. +//! motion is held off (the tape owns the pointer while it plays). Anything real that reaches the +//! app while paused marks the app `diverged` from the tape. const Player = @This(); const std = @import("std"); @@ -227,8 +228,8 @@ pub const Transport = struct { /// physical, as far round it as the pointer is on it: what the bar hides into, and a press /// there brings it back. Null when it was not drawn. rest: ?dvui.Rect.Physical = null, - /// Sent into where it rests (its tab): the bar stays there, playing or not, until that brings it - /// back (a press here, the pointer held on it in the overlay). + /// Sent into where it rests (its tab): the bar stays there, playing or not, until that brings + /// it back (a press here, the pointer held on it in the overlay). tucked: bool = false, /// The speed slider, label and line (`Sequencer.speed`), whose notches are `Sequencer.speeds`. speed: dvui.Rect.Physical = .{}, @@ -373,7 +374,8 @@ pub fn revert(self: *Player) void { self.dismiss(.revert); } -/// Stop at once and keep the app as it is, as `close` does: the command's way, with no bar to run back. +/// Stop at once and keep the app as it is, as `close` does: the command's way, with no bar to run +/// back. pub fn stop(self: *Player) void { self.release(.keep); } @@ -717,7 +719,10 @@ fn verify(self: *Player, s: Snapshot) void { if (got == want) return; self.mismatches += 1; const name = if (self.tape()) |t| t.name else "?"; - log.warn("demo '{s}': replaying to {d:.0} ms did not reach what playing did — the tape does not replay exactly", .{ name, s.at }); + log.warn("demo '{s}': replaying to {d:.0} ms did not reach what playing did — the tape does not replay exactly", .{ + name, + s.at, + }); } fn dropSnapshots(self: *Player) void { @@ -733,7 +738,10 @@ fn dropSnapshots(self: *Player) void { pub fn frame(self: *Player) void { // Whether this run is seen, once the seek it asks for (or lands) is known: a run while a seek // catches up draws nothing, but the one `frames` ends it with. - defer self.run_unseen = if (self.set_unseen) |set| set((self.state == .seeking or self.catching_up) and !self.showing) else false; + defer self.run_unseen = if (self.set_unseen) |set| + set((self.state == .seeking or self.catching_up) and !self.showing) + else + false; if (self.owned == null) return; // Widgets name themselves for the tape while one is loaded (`anchor`). anchor.want(); @@ -814,7 +822,12 @@ pub fn frame(self: *Player) void { /// the runs took the app's clock past the wall — and, a seek still in flight, to the moment it /// goes on from — so the next frame carries on from there. Without one a silent run steps the /// clock the least dvui accepts, a microsecond, and timers wait for the wall. -pub fn frames(self: *Player, win: *dvui.Window, frame_fn: *const fn () anyerror!dvui.App.Result, clock: ?*i128) anyerror!dvui.App.Result { +pub fn frames( + self: *Player, + win: *dvui.Window, + frame_fn: *const fn () anyerror!dvui.App.Result, + clock: ?*i128, +) anyerror!dvui.App.Result { // Counted before the run, which may land the seek, and after it, which may have asked for one. const was_seeking = self.state == .seeking; if (was_seeking) self.seek_stats.shown += 1; @@ -869,7 +882,10 @@ fn runAt(self: *const Player, win: *dvui.Window, clock: ?*i128, base_ns: i128, b // Waiting on a glide's target to stop moving (`targetStill`), a run is a frame of motion where // the backend's clock can be moved on: on a microsecond's clock, a place sliding open would // never move between runs, and look still. - const soonest = win.frame_time_ns + if (self.aiming != null and clock != null) msToNs(Sequencer.path_step_ms) else std.time.ns_per_us; + const soonest = win.frame_time_ns + if (self.aiming != null and clock != null) + msToNs(Sequencer.path_step_ms) + else + std.time.ns_per_us; if (clock == null or self.state != .seeking) return soonest; return @max(soonest, base_ns + msToNs(self.nextMoment() - base_ms)); } @@ -1057,7 +1073,10 @@ fn transportTakes(self: *Player, e: *dvui.Event, me: dvui.Event.Mouse) bool { self.seq.setSpeedIndex(tr.speedAt(me.p.x)); } else if (tr.track.contains(me.p)) { tr.scrub = tr.timeAt(me.p.x, self.duration()); - self.scrub_after = if (self.state == .playing or (self.state == .seeking and self.after_seek == .play)) .play else .pause; + self.scrub_after = if (self.state == .playing or (self.state == .seeking and self.after_seek == .play)) + .play + else + .pause; self.scrubbed_to = null; } else if (tr.prev.contains(me.p)) { self.stepChapter(-1); @@ -1204,11 +1223,27 @@ fn failed(ctx: *anyopaque, failure: Sequencer.Failure) void { const at = if (self.tape()) |t| t.ops[failure.op].at else 0; switch (failure.what) { .wait => |until| switch (until) { - .idle => dvui.log.warn("demo '{s}': gave up waiting for the app to settle (op {d}, {d} ms); skipping {d} ops to the next chapter", .{ name, failure.op, at, failure.skipped }), - .shown => |tag| dvui.log.warn("demo '{s}': gave up waiting for '{s}' to be drawn (op {d}, {d} ms); skipping {d} ops to the next chapter", .{ name, tag, failure.op, at, failure.skipped }), - .gone => |tag| dvui.log.warn("demo '{s}': gave up waiting for '{s}' to go (op {d}, {d} ms); skipping {d} ops to the next chapter", .{ name, tag, failure.op, at, failure.skipped }), + .idle => dvui.log.warn( + "demo '{s}': gave up waiting for the app to settle (op {d}, {d} ms); " ++ + "skipping {d} ops to the next chapter", + .{ name, failure.op, at, failure.skipped }, + ), + .shown => |tag| dvui.log.warn( + "demo '{s}': gave up waiting for '{s}' to be drawn (op {d}, {d} ms); " ++ + "skipping {d} ops to the next chapter", + .{ name, tag, failure.op, at, failure.skipped }, + ), + .gone => |tag| dvui.log.warn( + "demo '{s}': gave up waiting for '{s}' to go (op {d}, {d} ms); " ++ + "skipping {d} ops to the next chapter", + .{ name, tag, failure.op, at, failure.skipped }, + ), }, - .target => |t| dvui.log.warn("demo '{s}': '{s}' was never drawn to move to (op {d}, {d} ms); skipping {d} ops to the next chapter", .{ name, t.tag, failure.op, at, failure.skipped }), + .target => |t| dvui.log.warn( + "demo '{s}': '{s}' was never drawn to move to (op {d}, {d} ms); " ++ + "skipping {d} ops to the next chapter", + .{ name, t.tag, failure.op, at, failure.skipped }, + ), } } diff --git a/sdk/replay/Snapshot.zig b/sdk/replay/Snapshot.zig index b5992b422..fb26f25e1 100644 --- a/sdk/replay/Snapshot.zig +++ b/sdk/replay/Snapshot.zig @@ -7,7 +7,10 @@ //! //! ``` //! .{ -//! .{ .role = "tab", .name = "Files", .tag = "fizzy.rail:files", .rect = .{ .x = 8, .y = 52, .w = 40, .h = 40 } }, +//! .{ +//! .role = "tab", .name = "Files", .tag = "fizzy.rail:files", +//! .rect = .{ .x = 8, .y = 52, .w = 40, .h = 40 }, +//! }, //! .{ .role = "button", .name = "Close Tab", .rect = .{ … }, .parent = 3 }, //! } //! ``` @@ -271,7 +274,23 @@ pub const Node = struct { /// The roles a person acts on: a widget with one is a node even with no tag. pub fn acted(role: dvui.AccessKit.Role) bool { return switch (role) { - .button, .default_button, .tab, .check_box, .radio_button, .link, .menu_item, .list_box_option, .tree_item, .text_input, .multiline_text_input, .search_input, .slider, .combo_box, .dialog, .alert_dialog => true, + .button, + .default_button, + .tab, + .check_box, + .radio_button, + .link, + .menu_item, + .list_box_option, + .tree_item, + .text_input, + .multiline_text_input, + .search_input, + .slider, + .combo_box, + .dialog, + .alert_dialog, + => true, else => false, }; } @@ -344,7 +363,12 @@ pub fn build(arena: std.mem.Allocator, widgets: []const CapturedWidget, windows: const owner = index_of.get(p.data.id); const within = if (owner) |i| node_of[i] else null; const subwindow = if (owner) |i| widgets[i].subwindow_id else null; - try nodes.append(arena, .{ .tag = p.name, .rect = round(p.data.rect), .parent = within, .window = windowOf(windows, p.data.id, subwindow, p.data.rect.center()) }); + try nodes.append(arena, .{ + .tag = p.name, + .rect = round(p.data.rect), + .parent = within, + .window = windowOf(windows, p.data.id, subwindow, p.data.rect.center()), + }); } return nodes.items; } @@ -361,7 +385,12 @@ pub fn write(nodes: []const Node, w: *std.Io.Writer) !void { } fn round(r: dvui.Rect.Physical) Node.Rect { - return .{ .x = @intFromFloat(@round(r.x)), .y = @intFromFloat(@round(r.y)), .w = @intFromFloat(@round(r.w)), .h = @intFromFloat(@round(r.h)) }; + return .{ + .x = @intFromFloat(@round(r.x)), + .y = @intFromFloat(@round(r.y)), + .w = @intFromFloat(@round(r.w)), + .h = @intFromFloat(@round(r.h)), + }; } /// The name widget `i` goes by, or null when it has none: its `label` text, the label widget it @@ -413,7 +442,11 @@ fn testFrame() !dvui.App.Result { defer box.deinit(); _ = dvui.button(@src(), "Save", .{}, .{ .tag = "demo.save" }); // An icon-only button: named by its label, not by anything it shows. - var close = dvui.box(@src(), .{}, .{ .role = .button, .label = .{ .text = "Close" }, .min_size_content = .{ .w = 10, .h = 10 } }); + var close = dvui.box(@src(), .{}, .{ + .role = .button, + .label = .{ .text = "Close" }, + .min_size_content = .{ .w = 10, .h = 10 }, + }); close.deinit(); // A mark, named from data, only while someone asked. var area = dvui.box(@src(), .{}, .{ .min_size_content = .{ .w = 20, .h = 20 } }); @@ -445,7 +478,12 @@ test "a snapshot names what a person acts on, and what a tape aims at" { var arena: std.heap.ArenaAllocator = .init(std.testing.allocator); defer arena.deinit(); var diag: std.zon.parse.Diagnostics = undefined; - const nodes = try std.zon.parse.fromSlice([]const Node, .{ .gpa = std.testing.allocator, .arena = arena.allocator(), .source = try arena.allocator().dupeSentinel(u8, text, 0), .diagnostics = &diag }); + const nodes = try std.zon.parse.fromSlice([]const Node, .{ + .gpa = std.testing.allocator, + .arena = arena.allocator(), + .source = try arena.allocator().dupeSentinel(u8, text, 0), + .diagnostics = &diag, + }); var buttons: usize = 0; var save = false; var close = false; @@ -476,7 +514,10 @@ fn testDialog(id: dvui.Id) anyerror!void { const began = scopeBegin(); defer scopeEnd(began); // lint-allow(raw-transient-surface): snapshot test fixture, not a shown surface - var win = dvui.floatingWindow(@src(), .{}, .{ .id_extra = id.asUsize(), .rect = .{ .x = 10, .y = 10, .w = 80, .h = 40 } }); + var win = dvui.floatingWindow(@src(), .{}, .{ + .id_extra = id.asUsize(), + .rect = .{ .x = 10, .y = 10, .w = 80, .h = 40 }, + }); defer win.deinit(); test_dialog_window = win.data().id; _ = dvui.button(@src(), "Go ahead", .{}, .{}); @@ -506,7 +547,12 @@ fn testWindows(_: *anyopaque, arena: std.mem.Allocator) []const Window { const list = arena.dupe(Window, &.{ .{ .tag = "app.window:main", .title = "App", .rect = dvui.windowRectPixels() }, .{ .tag = "app.window:band", .rect = test_band, .area = true }, - .{ .tag = "app.window:dialog", .title = "Ask", .rect = .{ .x = 0, .y = 0, .w = 100, .h = 100 }, .subwindow = test_dialog_window }, + .{ + .tag = "app.window:dialog", + .title = "Ask", + .rect = .{ .x = 0, .y = 0, .w = 100, .h = 100 }, + .subwindow = test_dialog_window, + }, }) catch return &.{}; return list; } @@ -532,7 +578,12 @@ test "a snapshot has the dialogs dvui shows after its capture, and says which wi var arena: std.heap.ArenaAllocator = .init(std.testing.allocator); defer arena.deinit(); var diag: std.zon.parse.Diagnostics = undefined; - const nodes = try std.zon.parse.fromSlice([]const Node, .{ .gpa = std.testing.allocator, .arena = arena.allocator(), .source = try arena.allocator().dupeSentinel(u8, text, 0), .diagnostics = &diag }); + const nodes = try std.zon.parse.fromSlice([]const Node, .{ + .gpa = std.testing.allocator, + .arena = arena.allocator(), + .source = try arena.allocator().dupeSentinel(u8, text, 0), + .diagnostics = &diag, + }); // The windows first, each a node. try std.testing.expect(nodes.len > 3); for (nodes[0..3], [_][]const u8{ "app.window:main", "app.window:band", "app.window:dialog" }) |node, tag| { diff --git a/sdk/replay/Stage.zig b/sdk/replay/Stage.zig index 9c423298c..44618b870 100644 --- a/sdk/replay/Stage.zig +++ b/sdk/replay/Stage.zig @@ -49,9 +49,9 @@ pub const VTable = struct { chordFor: ?*const fn (ctx: *anyopaque, id: []const u8) ?chord.Stroke = null, /// What a person would call a command ("Format Document"), for the keystroke display. commandTitle: ?*const fn (ctx: *anyopaque, id: []const u8) ?[]const u8 = null, - /// The live name of the place `role` stands for (`documents`, `explorer`, `panel`), written into - /// `buf`, for a tag like `region:role:documents`: a demo aims at what a place is, not at the - /// name the person's layout gave it. Null when the app has no such place now. Null hook: no + /// The live name of the place `role` stands for (`documents`, `explorer`, `panel`), written + /// into `buf`, for a tag like `region:role:documents`: a demo aims at what a place is, not at + /// the name the person's layout gave it. Null when the app has no such place now. Null hook: no /// roles. resolveRole: ?*const fn (ctx: *anyopaque, role: []const u8, buf: []u8) ?[]const u8 = null, /// Replaying fast (a seek): turn animation off so every replayed frame lands where it is diff --git a/sdk/replay/overlay.zig b/sdk/replay/overlay.zig index 775266f80..138055832 100644 --- a/sdk/replay/overlay.zig +++ b/sdk/replay/overlay.zig @@ -36,7 +36,12 @@ pub fn drawLive(driver: *const LiveDriver, opts: Options) void { // A cursor the app draws itself where the pointer is (a paint tool's pencil) is the pointer // there: two at one place would be one too many, so this one gives way (`anchor.own_cursor`). const own = dvui.currentWindow().tags.containsUsed(anchor.own_cursor) orelse false; - paintPointer(driver.seq.pointer, ripple, if (own) 0 else if (hand.typing) 1 - in else in, driver.input.held.count() > 0); + paintPointer( + driver.seq.pointer, + ripple, + if (own) 0 else if (hand.typing) 1 - in else in, + driver.input.held.count() > 0, + ); } /// A demo's pointer, while it drives, timed in demo time: a seek shows what playing showed. @@ -44,10 +49,16 @@ pub fn drawPlayer(player: *Player, opts: Options) void { if (!player.driving()) return; const fw = layer(@src(), .{ .rect = pointerRect(player.seq.pointer), .name = "DemoPointer" }, .{}); defer fw.deinit(); - const ripple: ?Ripple = if (player.last_press) |press| .{ .pt = press.pt, .age_ms = player.seq.now - press.at } else null; + const ripple: ?Ripple = if (player.last_press) |press| .{ + .pt = press.pt, + .age_ms = player.seq.now - press.at, + } else null; // Out of the way of the words while the tape types, as a desktop's pointer is, and of a // cursor the app draws itself (`drawLive`). - const shown = if (dvui.currentWindow().tags.containsUsed(anchor.own_cursor) orelse false) 0 else player.pointerShown(opts.fade_ms); + const shown = if (dvui.currentWindow().tags.containsUsed(anchor.own_cursor) orelse false) + 0 + else + player.pointerShown(opts.fade_ms); paintPointer(player.seq.pointer, ripple, shown, player.input.held.count() > 0); } @@ -63,7 +74,11 @@ fn pointerRect(p: Sequencer.Point) dvui.Rect { /// A floating widget that takes no input, raised to the top. A floating widget otherwise stays /// just above the window it was made in, under anything opened after it; re-added as a subwindow /// of its own it can be raised, and each call puts the new one above the last. -pub fn layer(src: std.lang.SourceLocation, opts: dvui.Options, init: dvui.FloatingWidget.InitOptions) *dvui.FloatingWidget { +pub fn layer( + src: std.lang.SourceLocation, + opts: dvui.Options, + init: dvui.FloatingWidget.InitOptions, +) *dvui.FloatingWidget { var init_opts = init; init_opts.mouse_events = false; const fw = dvui.widgetAlloc(dvui.FloatingWidget); @@ -118,7 +133,11 @@ pub fn paintPointer(p: Sequencer.Point, ripple: ?Ripple, shown: f32, pressed: bo dvui.Path.fill(&.{built}, .{ .color = .{ .color = dvui.Color.black.opacity(0.28) }, .fade = 2.5 * s }); } else { dvui.Path.fill(&.{built}, .{ .color = .{ .color = .white }, .fade = 1 }); - built.stroke(.{ .thickness = 1.1 * s, .color = .{ .color = dvui.Color.black.opacity(0.85) }, .closed = true }); + built.stroke(.{ + .thickness = 1.1 * s, + .color = .{ .color = dvui.Color.black.opacity(0.85) }, + .closed = true, + }); } } }