diff --git a/modules/virtual-io/src/root.zig b/modules/virtual-io/src/root.zig index a47857c48..bbd4a1196 100644 --- a/modules/virtual-io/src/root.zig +++ b/modules/virtual-io/src/root.zig @@ -255,6 +255,37 @@ pub const VirtualIo = struct { vio.nodes.deinit(vio.gpa); } + /// Copies all files and directories from `src` directory into `dst` (which must belong to `io_other`). + pub fn save_dir_recursive(vio: *const VirtualIo, src: Dir.ID, io_other: Io, dst: Io.Dir) !usize { + return save_dir_recursive_helper(vio, try src.get(vio), io_other, dst); + } + + fn save_dir_recursive_helper(vio: *const VirtualIo, src: *const Dir, io_other: Io, dst: Io.Dir) !usize { + var it = src.inner.iterator(); + var files_written: usize = 0; + + while (it.next()) |entry| { + const sub_path = entry.key_ptr.*; + const node = vio.nodes.getPtr(entry.value_ptr.*) orelse return error.Invalid; + switch (node.*) { + .dir => |*dir| { + const new_tgt = try dst.createDirPathOpen(io_other, sub_path, .{}); + defer new_tgt.close(io_other); + files_written += try save_dir_recursive_helper(vio, dir, io_other, new_tgt); + }, + .file => |file| { + var writer = (try dst.createFile(io_other, sub_path, .{})) + .writer(io_other, ""); + defer writer.file.close(io_other); + try writer.interface.writeAll(file.inner.items); + try writer.interface.flush(); + files_written += 1; + }, + } + } + return files_written; + } + pub fn total_file_count(vio: *const VirtualIo) usize { var ret: usize = 0; var it = vio.nodes.iterator(); diff --git a/tools/regz/build.zig.zon b/tools/regz/build.zig.zon index c6f7dd226..c4f448e1d 100644 --- a/tools/regz/build.zig.zon +++ b/tools/regz/build.zig.zon @@ -2,6 +2,7 @@ .name = .mz_tools_regz, .fingerprint = 0x7886602f9b3c8b94, .version = "0.0.0", + .minimum_zig_version = "0.17.0-dev.1471+ff10b90bc", .paths = .{ "README.md", "build.zig", diff --git a/tools/regz/src/Database.zig b/tools/regz/src/Database.zig index 5a8c0957f..bcf86d5ae 100644 --- a/tools/regz/src/Database.zig +++ b/tools/regz/src/Database.zig @@ -2098,108 +2098,103 @@ fn cleanup_unused_enums(db: *Database) !void { , .{}); } -pub fn apply_patch(db: *Database, zon_text: [:0]const u8, diags: *std.zon.parse.Diagnostics) !void { - const patches = try std.zon.parse.fromSliceAlloc([]const Patch, db.gpa, zon_text, diags, .{}); - defer std.zon.parse.free(db.gpa, patches); - - for (patches) |patch| { - switch (patch) { - .override_arch => |override_arch| { - const device_id = try db.get_device_id_by_name(override_arch.device_name) orelse { - return error.DeviceNotFound; - }; - - try db.conn.exec( - \\UPDATE devices - \\SET arch = ? - \\WHERE id = ?; - , .{ - override_arch.arch.to_string(), - @backingInt(device_id), - }); - }, - .set_device_property => |set_prop| { - const device_id = try db.get_device_id_by_name(set_prop.device_name) orelse { - return error.DeviceNotFound; - }; - - try db.conn.exec( - \\INSERT INTO device_properties - \\ (device_id, key, value, description) - \\VALUES - \\ (?, ?, ?, ?) - \\ON CONFLICT(device_id, key) - \\DO UPDATE SET - \\ value = excluded.value, - \\ description = excluded.description; - , .{ - @backingInt(device_id), - set_prop.key, - set_prop.value, - set_prop.description, +pub fn apply_patch(db: *Database, patch: Patch) !void { + switch (patch) { + .override_arch => |override_arch| { + const device_id = try db.get_device_id_by_name(override_arch.device_name) orelse { + return error.DeviceNotFound; + }; + + try db.conn.exec( + \\UPDATE devices + \\SET arch = ? + \\WHERE id = ?; + , .{ + override_arch.arch.to_string(), + @backingInt(device_id), + }); + }, + .set_device_property => |set_prop| { + const device_id = try db.get_device_id_by_name(set_prop.device_name) orelse { + return error.DeviceNotFound; + }; + + try db.conn.exec( + \\INSERT INTO device_properties + \\ (device_id, key, value, description) + \\VALUES + \\ (?, ?, ?, ?) + \\ON CONFLICT(device_id, key) + \\DO UPDATE SET + \\ value = excluded.value, + \\ description = excluded.description; + , .{ + @backingInt(device_id), + set_prop.key, + set_prop.value, + set_prop.description, + }); + }, + .add_enum => |add_enum| { + const struct_id = try db.get_struct_ref(add_enum.parent); + + const enum_id = try db.create_enum(struct_id, .{ + .name = add_enum.@"enum".name, + .description = add_enum.@"enum".description, + .size_bits = add_enum.@"enum".bitsize, + }); + + for (add_enum.@"enum".fields) |enum_field| { + try db.add_enum_field(enum_id, .{ + .name = enum_field.name, + .description = enum_field.description, + .value = enum_field.value, }); - }, - .add_enum => |add_enum| { - const struct_id = try db.get_struct_ref(add_enum.parent); - - const enum_id = try db.create_enum(struct_id, .{ - .name = add_enum.@"enum".name, - .description = add_enum.@"enum".description, - .size_bits = add_enum.@"enum".bitsize, + } + }, + .set_enum_type => |set_enum_type| { + const enum_id = if (set_enum_type.to) |to| try db.get_enum_ref(to) else null; + const field_name, const register_ref = try get_ref_last_component(set_enum_type.of); + const register_id = try db.get_register_ref(register_ref orelse return error.InvalidRef); + try db.set_register_field_enum_id(register_id, field_name, enum_id); + try db.cleanup_unused_enums(); + }, + .add_interrupt => |add_interrupt| { + const device_id = try db.get_device_id_by_name(add_interrupt.device_name) orelse { + return error.DeviceNotFound; + }; + + _ = try db.create_interrupt(device_id, .{ + .name = add_interrupt.name, + .description = add_interrupt.description, + .idx = add_interrupt.idx, + }); + }, + .add_enum_and_apply => |add_enum_patch| { + // First, create the enum (same as add_enum) + const struct_id = try db.get_struct_ref(add_enum_patch.parent); + + const enum_id = try db.create_enum(struct_id, .{ + .name = add_enum_patch.@"enum".name, + .description = add_enum_patch.@"enum".description, + .size_bits = add_enum_patch.@"enum".bitsize, + }); + + for (add_enum_patch.@"enum".fields) |enum_field| { + try db.add_enum_field(enum_id, .{ + .name = enum_field.name, + .description = enum_field.description, + .value = enum_field.value, }); + } - for (add_enum.@"enum".fields) |enum_field| { - try db.add_enum_field(enum_id, .{ - .name = enum_field.name, - .description = enum_field.description, - .value = enum_field.value, - }); - } - }, - .set_enum_type => |set_enum_type| { - const enum_id = if (set_enum_type.to) |to| try db.get_enum_ref(to) else null; - const field_name, const register_ref = try get_ref_last_component(set_enum_type.of); + // Then, apply to all specified fields (same as set_enum_type) + for (add_enum_patch.apply_to) |field_ref| { + const field_name, const register_ref = try get_ref_last_component(field_ref); const register_id = try db.get_register_ref(register_ref orelse return error.InvalidRef); try db.set_register_field_enum_id(register_id, field_name, enum_id); - try db.cleanup_unused_enums(); - }, - .add_interrupt => |add_interrupt| { - const device_id = try db.get_device_id_by_name(add_interrupt.device_name) orelse { - return error.DeviceNotFound; - }; - - _ = try db.create_interrupt(device_id, .{ - .name = add_interrupt.name, - .description = add_interrupt.description, - .idx = add_interrupt.idx, - }); - }, - .add_enum_and_apply => |add_enum_patch| { - // First, create the enum (same as add_enum) - const struct_id = try db.get_struct_ref(add_enum_patch.parent); - - const enum_id = try db.create_enum(struct_id, .{ - .name = add_enum_patch.@"enum".name, - .description = add_enum_patch.@"enum".description, - .size_bits = add_enum_patch.@"enum".bitsize, - }); - - for (add_enum_patch.@"enum".fields) |enum_field| { - try db.add_enum_field(enum_id, .{ - .name = enum_field.name, - .description = enum_field.description, - .value = enum_field.value, - }); - } - - // Then, apply to all specified fields (same as set_enum_type) - for (add_enum_patch.apply_to) |field_ref| { - const field_name, const register_ref = try get_ref_last_component(field_ref); - const register_id = try db.get_register_ref(register_ref orelse return error.InvalidRef); - try db.set_register_field_enum_id(register_id, field_name, enum_id); - } - }, - } + } + }, } } @@ -2264,35 +2259,26 @@ test "add_enum_and_apply patch creates enum and applies to fields" { }); // Apply the add_enum_and_apply patch - const patch_zon: [:0]const u8 = - \\.{ - \\ .{ - \\ .add_enum_and_apply = .{ - \\ .parent = "types.peripherals.TEST_PERIPHERAL", - \\ .@"enum" = .{ - \\ .name = "TestMode", - \\ .bitsize = 2, - \\ .fields = .{ - \\ .{ .value = 0x0, .name = "mode_a" }, - \\ .{ .value = 0x1, .name = "mode_b" }, - \\ .{ .value = 0x2, .name = "mode_c" }, - \\ .{ .value = 0x3, .name = "mode_d" }, - \\ }, - \\ }, - \\ .apply_to = .{ - \\ "types.peripherals.TEST_PERIPHERAL.REG0.MODE", - \\ "types.peripherals.TEST_PERIPHERAL.REG1.MODE", - \\ "types.peripherals.TEST_PERIPHERAL.REG2.MODE", - \\ }, - \\ }, - \\ }, - \\} - ; - - var diags: std.zon.parse.Diagnostics = .{}; - defer diags.deinit(allocator); - - try db.apply_patch(patch_zon, &diags); + try db.apply_patch(.{ + .add_enum_and_apply = .{ + .parent = "types.peripherals.TEST_PERIPHERAL", + .@"enum" = .{ + .name = "TestMode", + .bitsize = 2, + .fields = &.{ + .{ .value = 0x0, .name = "mode_a" }, + .{ .value = 0x1, .name = "mode_b" }, + .{ .value = 0x2, .name = "mode_c" }, + .{ .value = 0x3, .name = "mode_d" }, + }, + }, + .apply_to = &.{ + "types.peripherals.TEST_PERIPHERAL.REG0.MODE", + "types.peripherals.TEST_PERIPHERAL.REG1.MODE", + "types.peripherals.TEST_PERIPHERAL.REG2.MODE", + }, + }, + }); // Verify the enum was created var arena = std.heap.ArenaAllocator.init(allocator); @@ -2330,29 +2316,20 @@ test "add_enum_and_apply patch with empty apply_to list" { const struct_id = try db.get_peripheral_struct(peripheral_id); // Apply patch with empty apply_to list (just creates the enum) - const patch_zon: [:0]const u8 = - \\.{ - \\ .{ - \\ .add_enum_and_apply = .{ - \\ .parent = "types.peripherals.TEST_PERIPHERAL", - \\ .@"enum" = .{ - \\ .name = "UnusedEnum", - \\ .bitsize = 4, - \\ .fields = .{ - \\ .{ .value = 0, .name = "value0" }, - \\ .{ .value = 1, .name = "value1" }, - \\ }, - \\ }, - \\ .apply_to = .{}, - \\ }, - \\ }, - \\} - ; - - var diags: std.zon.parse.Diagnostics = .{}; - defer diags.deinit(allocator); - - try db.apply_patch(patch_zon, &diags); + try db.apply_patch(.{ + .add_enum_and_apply = .{ + .parent = "types.peripherals.TEST_PERIPHERAL", + .@"enum" = .{ + .name = "UnusedEnum", + .bitsize = 4, + .fields = &.{ + .{ .value = 0, .name = "value0" }, + .{ .value = 1, .name = "value1" }, + }, + }, + .apply_to = &.{}, + }, + }); // Verify the enum was created var arena = std.heap.ArenaAllocator.init(allocator); @@ -2373,29 +2350,20 @@ test "add_enum_and_apply patch with invalid field reference" { }); // Apply patch with invalid field reference - const patch_zon: [:0]const u8 = - \\.{ - \\ .{ - \\ .add_enum_and_apply = .{ - \\ .parent = "types.peripherals.TEST_PERIPHERAL", - \\ .@"enum" = .{ - \\ .name = "TestEnum", - \\ .bitsize = 2, - \\ .fields = .{ - \\ .{ .value = 0, .name = "value0" }, - \\ }, - \\ }, - \\ .apply_to = .{ - \\ "types.peripherals.TEST_PERIPHERAL.NONEXISTENT.FIELD", - \\ }, - \\ }, - \\ }, - \\} - ; - - var diags: std.zon.parse.Diagnostics = .{}; - defer diags.deinit(allocator); - - const result = db.apply_patch(patch_zon, &diags); + const result = db.apply_patch(.{ + .add_enum_and_apply = .{ + .parent = "types.peripherals.TEST_PERIPHERAL", + .@"enum" = .{ + .name = "TestEnum", + .bitsize = 2, + .fields = &.{ + .{ .value = 0, .name = "value0" }, + }, + }, + .apply_to = &.{ + "types.peripherals.TEST_PERIPHERAL.NONEXISTENT.FIELD", + }, + }, + }); try std.testing.expectError(error.MissingEntity, result); } diff --git a/tools/regz/src/main.zig b/tools/regz/src/main.zig index f4440f5a8..44e6efb92 100644 --- a/tools/regz/src/main.zig +++ b/tools/regz/src/main.zig @@ -145,19 +145,23 @@ fn main_impl(init: std.process.Init) anyerror!void { defer db.destroy(); for (args.patch_paths.items) |patch_path| { - const patch = try std.Io.Dir.cwd().readFileAllocOptions(io, patch_path, gpa, .unlimited, .@"1", 0); - defer gpa.free(patch); + const zon_text = try std.Io.Dir.cwd().readFileAllocOptions(io, patch_path, gpa, .unlimited, .@"1", 0); + defer gpa.free(zon_text); var diags: std.zon.parse.Diagnostics = .{}; defer diags.deinit(db.gpa); - db.apply_patch(patch, &diags) catch |err| { + const patches = std.zon.parse.fromSliceAlloc([]const regz.Patch, db.gpa, zon_text, &diags, .{}) catch |err| { if (err == error.ParseZon) { std.log.err("Failed to parse zon patch file '{s}': {f}", .{ patch_path, diags }); } return err; }; + defer std.zon.parse.free(db.gpa, patches); + + for (patches) |patch| + try db.apply_patch(patch); } // arch dependent stuff diff --git a/tools/regz/src/module.zig b/tools/regz/src/module.zig index 21c70d4f3..5c3802772 100644 --- a/tools/regz/src/module.zig +++ b/tools/regz/src/module.zig @@ -1,9 +1,13 @@ +const patch = @import("patch.zig"); + pub const Database = @import("Database.zig"); pub const Analysis = @import("analysis.zig"); pub const arm = @import("arch/arm.zig"); -pub const Patch = @import("patch.zig").Patch; +pub const Patch = patch.Patch; +pub const Type = patch.Type; pub const Arch = @import("arch.zig").Arch; pub const embassy = @import("embassy.zig"); +pub const virtual_io = @import("virtual-io"); test { _ = Database; diff --git a/tools/regz/src/patch.zig b/tools/regz/src/patch.zig index 5c94b0bc5..6205876c8 100644 --- a/tools/regz/src/patch.zig +++ b/tools/regz/src/patch.zig @@ -18,40 +18,47 @@ pub const Type = struct { }; pub const Patch = union(enum) { - override_arch: struct { + pub const OverrideArch = struct { device_name: []const u8, arch: Arch, - }, - set_device_property: struct { + }; + pub const SetDeviceProperty = struct { device_name: []const u8, key: []const u8, value: []const u8, description: ?[]const u8 = null, - }, - add_enum: struct { + }; + pub const AddEnum = struct { parent: []const u8, @"enum": Type.Enum, - }, - /// The replaced type MUST be the same size. Bit or Byte size depends on the - /// context - set_enum_type: struct { + }; + pub const SetEnumType = struct { of: []const u8, to: ?[]const u8, - }, - add_interrupt: struct { + }; + pub const AddInterrupt = struct { device_name: []const u8, idx: i32, name: []const u8, description: ?[]const u8 = null, - }, - /// Creates a new enum type in the specified parent struct and applies it - /// to all the specified field references. This is a convenience patch that - /// combines `add_enum` with multiple `set_enum_type` operations. - add_enum_and_apply: struct { + }; + pub const AddEnumAndApply = struct { parent: []const u8, @"enum": Type.Enum, apply_to: []const []const u8, - }, + }; + + override_arch: OverrideArch, + set_device_property: SetDeviceProperty, + add_enum: AddEnum, + /// The replaced type MUST be the same size. Bit or Byte size depends on the + /// context + set_enum_type: SetEnumType, + add_interrupt: AddInterrupt, + /// Creates a new enum type in the specified parent struct and applies it + /// to all the specified field references. This is a convenience patch that + /// combines `add_enum` with multiple `set_enum_type` operations. + add_enum_and_apply: AddEnumAndApply, pub fn from_json_str(allocator: Allocator, json_str: []const u8) !std.json.Parsed(Patch) { return std.json.parseFromSlice(Patch, allocator, json_str, .{}); diff --git a/tools/sorcerer/build.zig b/tools/sorcerer/build.zig index 6f4980879..ccae06528 100644 --- a/tools/sorcerer/build.zig +++ b/tools/sorcerer/build.zig @@ -9,182 +9,161 @@ pub fn build(b: *std.Build) void { const target = b.standardTargetOptions(.{}); const optimize = b.standardOptimizeOption(.{}); - const mz_dep = b.dependency("microzig", .{}); + const no_gui = b.option(bool, "no-gui", "Disable gui-only dependencies") orelse false; - const mb = MicroBuild.init(b, mz_dep) orelse return; - const register_schemas = get_register_schemas(b, mb) catch @panic("OOM"); - const write_files = b.addWriteFiles(); + const run_all_tests = b.step("test", "Run unit tests"); - // Generate Zig file with embedded schemas (used by both CLI and GUI) - const register_schema_zig = write_files.add("register_schemas.zig", generate_zig_schema_literal(b.allocator, register_schemas) catch @panic("OOM")); + // ───────────────────────────────────────────────────────────────────────── + // Dependencies + // ───────────────────────────────────────────────────────────────────────── + const diffz_mod = b.dependency("diffz", .{ + .target = target, + .optimize = optimize, + }).module("diffz"); + + const microzig_dep = b.dependency("microzig", .{}); - const regz_dep = mz_dep.builder.dependency("tools/regz", .{ + const regz_mod = microzig_dep.builder.dependency("tools/regz", .{ .target = target, // Setting to release safe because on debug builds its what slows // things down. It _should_ be solid for the most part once you're // developing Sorcerer, if that's not the case then you can change this // manually. .optimize = .ReleaseSafe, - }); + }).module("regz"); - const regz_mod = regz_dep.module("regz"); + // ───────────────────────────────────────────────────────────────────────── + // Diff algorithm unit tests + // ───────────────────────────────────────────────────────────────────────── - // Shared module for RegisterSchemaUsage (used by both schemas_mod and cli_mod) - const register_schema_usage_mod = b.createModule(.{ - .root_source_file = b.path("src/RegisterSchemaUsage.zig"), + const diff_tests = b.addTest(.{ + .root_module = b.createModule(.{ + .root_source_file = b.path("src/test_diff.zig"), + .target = target, + .optimize = optimize, + .imports = &.{ + .{ .name = "diffz", .module = diffz_mod }, + }, + }), }); + run_all_tests.dependOn(&b.addRunArtifact(diff_tests).step); + + // ───────────────────────────────────────────────────────────────────────── + // Generate register schemas + // ───────────────────────────────────────────────────────────────────────── + const mb = MicroBuild.init(b, microzig_dep) orelse return; // Create schemas module from generated Zig file const schemas_mod = b.createModule(.{ - .root_source_file = register_schema_zig, + // Generate Zig file with embedded schemas (used by both CLI and GUI) + .root_source_file = b.addWriteFiles().add( + "register_schemas.zig", + generate_zig_schema_literal( + b.allocator, + get_register_schemas(b, mb) catch @panic("OOM"), + ) catch @panic("OOM"), + ), .imports = &.{ - .{ .name = "RegisterSchemaUsage", .module = register_schema_usage_mod }, + // Usage - needed by both the cli and gui app. + .{ + .name = "RegisterSchemaUsage", + .module = b.createModule(.{ + .root_source_file = b.path("src/RegisterSchemaUsage.zig"), + }), + }, }, }); // ───────────────────────────────────────────────────────────────────────── // CLI executable // ───────────────────────────────────────────────────────────────────────── - const cli_mod = b.createModule(.{ - .root_source_file = b.path("src/cli.zig"), - .target = target, - .optimize = optimize, - .imports = &.{ - .{ .name = "regz", .module = regz_mod }, - .{ .name = "schemas", .module = schemas_mod }, - .{ .name = "RegisterSchemaUsage", .module = register_schema_usage_mod }, - }, - }); - const cli_exe = b.addExecutable(.{ .name = "sorcerer-cli", - .root_module = cli_mod, + .root_module = b.createModule(.{ + .root_source_file = b.path("src/cli.zig"), + .target = target, + .optimize = optimize, + .imports = &.{ + .{ .name = "regz", .module = regz_mod }, + .{ .name = "schemas", .module = schemas_mod }, + }, + }), }); b.installArtifact(cli_exe); const run_cli_cmd = b.addRunArtifact(cli_exe); run_cli_cmd.addPassthruArgs(); - run_cli_cmd.step.dependOn(b.getInstallStep()); + run_cli_cmd.step.dependOn(&cli_exe.step); + + b.step("run-cli", "Run the CLI tool") + .dependOn(&run_cli_cmd.step); - const run_cli_step = b.step("run-cli", "Run the CLI tool"); - run_cli_step.dependOn(&run_cli_cmd.step); + run_all_tests.dependOn( + &b.addRunArtifact(b.addTest(.{ + .root_module = cli_exe.root_module, + })).step, + ); // ───────────────────────────────────────────────────────────────────────── // GUI executable // ───────────────────────────────────────────────────────────────────────── - const dvui_dep = b.dependency("dvui", .{ - .target = target, - .optimize = optimize, - }); + if (no_gui) return; - const serial_dep = b.dependency("serial", .{ + // GUI-only dependencies + const tree_sitter_diff_dep = b.lazyDependency("tree_sitter_diff", .{ .target = target, .optimize = optimize, }); - const dvui_mod = dvui_dep.module("dvui_sdl3"); - const serial_mod = serial_dep.module("serial"); - - const exe_mod = b.createModule(.{ - .root_source_file = b.path("src/main.zig"), + const dvui_mod = (b.lazyDependency("dvui", .{ .target = target, .optimize = optimize, - .imports = &.{ - .{ - .name = "dvui", - .module = dvui_mod, - }, - .{ - .name = "regz", - .module = regz_mod, - }, - .{ - .name = "serial", - .module = serial_mod, - }, - .{ - .name = "schemas", - .module = schemas_mod, - }, - .{ - .name = "RegisterSchemaUsage", - .module = register_schema_usage_mod, - }, - }, - }); + .backend = .sdl3, + }) orelse return).module("dvui_sdl3"); - const tree_sitter_zig_dep = b.lazyDependency("tree_sitter_zig", .{ - .target = target, - .optimize = optimize, + const gui_exe = b.addExecutable(.{ + .name = "sorcerer", + .root_module = b.createModule(.{ + .root_source_file = b.path("src/main.zig"), + .target = target, + .optimize = optimize, + .imports = &.{ + .{ .name = "diffz", .module = diffz_mod }, + .{ .name = "dvui", .module = dvui_mod }, + .{ .name = "regz", .module = regz_mod }, + .{ .name = "schemas", .module = schemas_mod }, + }, + }), }); - if (tree_sitter_zig_dep) |tsd| { - exe_mod.addIncludePath(tsd.path("src")); - exe_mod.addCSourceFiles(.{ - .root = tsd.path(""), - .files = &.{"src/parser.c"}, - .flags = &.{"-std=c11"}, - }); - } + b.installArtifact(gui_exe); - const tree_sitter_diff_dep = b.lazyDependency("tree_sitter_diff", .{ - .target = target, - .optimize = optimize, - }); if (tree_sitter_diff_dep) |tsd| { - exe_mod.addIncludePath(tsd.path("src")); - exe_mod.addCSourceFiles(.{ + gui_exe.root_module.addIncludePath(tsd.path("src")); + gui_exe.root_module.addCSourceFiles(.{ .root = tsd.path(""), .files = &.{"src/parser.c"}, .flags = &.{"-std=c11"}, }); } - const diffz_dep = b.dependency("diffz", .{ - .target = target, - .optimize = optimize, - }); - exe_mod.addImport("diffz", diffz_dep.module("diffz")); - - const exe = b.addExecutable(.{ - .name = "sorcerer", - .root_module = exe_mod, - }); - b.installArtifact(exe); - - const run_cmd = b.addRunArtifact(exe); - run_cmd.addPassthruArgs(); + const run_gui_cmd = b.addRunArtifact(gui_exe); + run_gui_cmd.step.dependOn(&gui_exe.step); + run_gui_cmd.addPassthruArgs(); // I only want the path to the register schema file, not the lazy path, // because I want to be able to refresh it with `zig build` while sorcerer // is running. Sorcerer will watch the file for changes and update itself // automatically. - run_cmd.step.dependOn(b.getInstallStep()); - - const run_step = b.step("run", "Run the GUI app"); - run_step.dependOn(&run_cmd.step); - - const exe_unit_tests = b.addTest(.{ - .root_module = exe_mod, - }); - - const run_exe_unit_tests = b.addRunArtifact(exe_unit_tests); - const test_step = b.step("test", "Run unit tests"); - test_step.dependOn(&run_exe_unit_tests.step); - - // Diff algorithm unit tests - const diff_test_mod = b.createModule(.{ - .root_source_file = b.path("src/test_diff.zig"), - .target = target, - .optimize = optimize, - }); - diff_test_mod.addImport("diffz", diffz_dep.module("diffz")); + run_gui_cmd.step.dependOn(&gui_exe.step); - const diff_tests = b.addTest(.{ - .root_module = diff_test_mod, - }); + b.step("run", "Run the GUI app") + .dependOn(&run_gui_cmd.step); - const run_diff_tests = b.addRunArtifact(diff_tests); - test_step.dependOn(&run_diff_tests.step); + run_all_tests.dependOn( + &b.addRunArtifact(b.addTest(.{ + .root_module = gui_exe.root_module, + })).step, + ); } const TargetWithPath = struct { @@ -195,8 +174,8 @@ const TargetWithPath = struct { fn get_targets(mb: *MicroBuild) []const TargetWithPath { @setEvalBranchQuota(50000); var ret: std.array_list.Managed(TargetWithPath) = .init(mb.builder.allocator); - inline for (@typeInfo(@FieldType(MicroBuild, "ports")).@"struct".fields) |field| { - recursively_collect_targets(@field(mb.ports, field.name), field.name, &ret) catch @panic("OOM"); + inline for (@typeInfo(@FieldType(MicroBuild, "ports")).@"struct".field_names) |field_name| { + recursively_collect_targets(@field(mb.ports, field_name), field_name, &ret) catch @panic("OOM"); } return ret.toOwnedSlice() catch unreachable; @@ -219,9 +198,9 @@ fn recursively_collect_targets(field: anytype, path: []const u8, targets: *std.a return; } - inline for (type_info.@"struct".fields) |child_field| { - const new_path = std.fmt.allocPrint(targets.allocator, "{s}.{s}", .{ path, child_field.name }) catch @panic("OOM"); - try recursively_collect_targets(@field(field, child_field.name), new_path, targets); + inline for (type_info.@"struct".field_names) |field_name| { + const new_path = std.fmt.allocPrint(targets.allocator, "{s}.{s}", .{ path, field_name }) catch @panic("OOM"); + try recursively_collect_targets(@field(field, field_name), new_path, targets); } } @@ -244,19 +223,19 @@ fn find_target_location(b: *std.Build, lazy_path: LazyPath) RegisterSchemaUsage. const build_root = get_build_root(b, dependency.builder); const root = @import("root"); const packages = root.dependencies.packages; - const package_hash = inline for (@typeInfo(packages).@"struct".decls) |decl| { - const package = @field(packages, decl.name); + const package_hash = inline for (@typeInfo(packages).@"struct".decl_names) |decl_name| { + const package = @field(packages, decl_name); if (!@hasDecl(package, "build_root")) continue; if (std.mem.eql(u8, package.build_root, build_root)) { - break decl.name; + break decl_name; } } else unreachable; const Pair = struct { port: []const u8, dep: []const u8 }; - const result: Pair = outer: inline for (@typeInfo(packages).@"struct".decls) |decl| { - const package = @field(packages, decl.name); + const result: Pair = outer: inline for (@typeInfo(packages).@"struct".decl_names) |decl_name| { + const package = @field(packages, decl_name); if (!@hasDecl(package, "deps")) continue; @@ -264,7 +243,7 @@ fn find_target_location(b: *std.Build, lazy_path: LazyPath) RegisterSchemaUsage. const name = dep[0]; const dep_package_hash = dep[1]; if (std.mem.eql(u8, package_hash, dep_package_hash)) { - break :outer .{ .port = decl.name, .dep = name }; + break :outer .{ .port = decl_name, .dep = name }; } } } else unreachable; @@ -284,7 +263,7 @@ fn find_target_location(b: *std.Build, lazy_path: LazyPath) RegisterSchemaUsage. } fn convert_patch_files(b: *std.Build, patch_files: []const LazyPath) ![]const RegisterSchemaUsage.PatchFile { - var result: std.ArrayList(RegisterSchemaUsage.PatchFile) = .{}; + var result: std.ArrayList(RegisterSchemaUsage.PatchFile) = .empty; for (patch_files) |patch_file| { const converted: RegisterSchemaUsage.PatchFile = switch (patch_file) { .src_path => |src_path| .{ @@ -311,18 +290,18 @@ fn find_dep_name(b: *std.Build, dependency: *std.Build.Dependency) []const u8 { const build_root = get_build_root(b, dependency.builder); const root = @import("root"); const packages = root.dependencies.packages; - const package_hash = inline for (@typeInfo(packages).@"struct".decls) |decl| { - const package = @field(packages, decl.name); + const package_hash = inline for (@typeInfo(packages).@"struct".decl_names) |decl_name| { + const package = @field(packages, decl_name); if (!@hasDecl(package, "build_root")) continue; if (std.mem.eql(u8, package.build_root, build_root)) { - break decl.name; + break decl_name; } } else unreachable; - inline for (@typeInfo(packages).@"struct".decls) |decl| { - const package = @field(packages, decl.name); + inline for (@typeInfo(packages).@"struct".decl_names) |decl_name| { + const package = @field(packages, decl_name); if (!@hasDecl(package, "deps")) continue; @@ -359,35 +338,36 @@ const LazyPathContext = struct { .src_path => |val| val.owner == b.src_path.owner and std.mem.eql(u8, val.sub_path, b.src_path.sub_path), .generated => @panic("Generated paths unsupported"), .cwd_relative => @panic("Cwd relative paths unsupported, you probably shouldn't be vendoring that in MicroZig anyways"), + .relative => @panic("Relative paths unsupported, you probably shouldn't be vendoring that in MicroZig anyways"), .dependency => |val| val.dependency == b.dependency.dependency and std.mem.eql(u8, val.sub_path, b.dependency.sub_path), }; } }; fn LazyPathHashMap(comptime Value: type) type { - return std.ArrayHashMap(std.Build.LazyPath, Value, LazyPathContext, true); + return std.ArrayHashMapUnmanaged(std.Build.LazyPath, Value, LazyPathContext, true); } fn get_register_schemas(b: *std.Build, mb: *MicroBuild) ![]const RegisterSchemaUsage { const targets = get_targets(mb); - var deduped_targets: LazyPathHashMap(RegisterSchemaUsage.Format) = .init(b.allocator); - var chips: LazyPathHashMap(std.ArrayList(RegisterSchemaUsage.Chip)) = .init(b.allocator); - var boards: LazyPathHashMap(std.ArrayList(RegisterSchemaUsage.Board)) = .init(b.allocator); - var locations: LazyPathHashMap(RegisterSchemaUsage.Location) = .init(b.allocator); + var deduped_targets: LazyPathHashMap(RegisterSchemaUsage.Format) = .empty; + var chips: LazyPathHashMap(std.ArrayList(RegisterSchemaUsage.Chip)) = .empty; + var boards: LazyPathHashMap(std.ArrayList(RegisterSchemaUsage.Board)) = .empty; + var locations: LazyPathHashMap(RegisterSchemaUsage.Location) = .empty; for (targets) |twp| { const t = twp.target; const lazy_path = switch (t.chip.register_definition) { .targetdb => |targetdb| blk: { - try deduped_targets.put(targetdb.path, .targetdb); + try deduped_targets.put(b.allocator, targetdb.path, .targetdb); break :blk targetdb.path; }, .embassy => |embassy| blk: { - try deduped_targets.put(embassy.path, .embassy); + try deduped_targets.put(b.allocator, embassy.path, .embassy); break :blk embassy.path; }, inline else => |lazy_path| blk: { - try deduped_targets.put(lazy_path, switch (t.chip.register_definition) { + try deduped_targets.put(b.allocator, lazy_path, switch (t.chip.register_definition) { .svd => .svd, .atdf => .atdf, .embassy, .targetdb => unreachable, @@ -407,13 +387,13 @@ fn get_register_schemas(b: *std.Build, mb: *MicroBuild) ![]const RegisterSchemaU .patch_files = patch_files, }); } else { - var chip_list: std.ArrayList(RegisterSchemaUsage.Chip) = .{}; + var chip_list: std.ArrayList(RegisterSchemaUsage.Chip) = .empty; try chip_list.append(b.allocator, .{ .name = t.chip.name, .target_name = twp.path, .patch_files = patch_files, }); - try chips.put(lazy_path, chip_list); + try chips.put(b.allocator, lazy_path, chip_list); } if (t.board) |board| if (boards.getEntry(lazy_path)) |entry| { @@ -428,20 +408,20 @@ fn get_register_schemas(b: *std.Build, mb: *MicroBuild) ![]const RegisterSchemaU }); } } else { - var board_list: std.ArrayList(RegisterSchemaUsage.Board) = .{}; + var board_list: std.ArrayList(RegisterSchemaUsage.Board) = .empty; try board_list.append(b.allocator, .{ .name = board.name, }); - try boards.put(lazy_path, board_list); + try boards.put(b.allocator, lazy_path, board_list); }; } for (deduped_targets.keys()) |lazy_path| { const location = find_target_location(b, lazy_path); - try locations.put(lazy_path, location); + try locations.put(b.allocator, lazy_path, location); } - var ret: std.ArrayList(RegisterSchemaUsage) = .{}; + var ret: std.ArrayList(RegisterSchemaUsage) = .empty; for (deduped_targets.keys(), deduped_targets.values()) |lazy_path, format| { var chip_list = chips.get(lazy_path).?; var board_list = boards.get(lazy_path); @@ -476,112 +456,111 @@ fn get_port_name(path: []const u8) []const u8 { } /// Generate a Zig source file containing the register schemas as compile-time constants. -fn generate_zig_schema_literal(allocator: std.mem.Allocator, schemas: []const RegisterSchemaUsage) ![]const u8 { - var buf: std.ArrayList(u8) = .{}; - const writer = buf.writer(allocator); - +fn generate_zig_schema_literal(arena: std.mem.Allocator, schemas: []const RegisterSchemaUsage) ![]const u8 { // Helper to normalize paths (convert backslashes to forward slashes for Windows compatibility) - const normalize_path = struct { - fn call(alloc: std.mem.Allocator, path: []const u8) ![]const u8 { - const result = try alloc.alloc(u8, path.len); - for (path, 0..) |c, i| { - result[i] = if (c == '\\') '/' else c; + const NormalizedPath = struct { + s: []const u8, + + pub fn format( + self: @This(), + w: *std.Io.Writer, + ) !void { + var tail = self.s; + while (std.mem.indexOfScalar(u8, tail, '\\')) |pos| { + try w.writeAll(tail[0..pos]); + try w.writeByte('/'); + tail = tail[pos + 1 ..]; } - return result; + try w.writeAll(tail); } - }.call; + }; + + var writer: std.Io.Writer.Allocating = .init(arena); + const w = &writer.writer; - try writer.writeAll( + try w.writeAll( \\// Auto-generated file - do not edit manually. \\// Generated by tools/sorcerer/build.zig \\ - \\const RegisterSchemaUsage = @import("RegisterSchemaUsage"); + \\pub const Usage = @import("RegisterSchemaUsage"); \\ - \\pub const schemas: []const RegisterSchemaUsage = &.{ + \\pub const schemas: []const Usage = &.{ \\ ); for (schemas) |schema| { - try writer.writeAll(" .{\n"); + try w.writeAll(" .{\n"); // Format - try writer.print(" .format = .{s},\n", .{@tagName(schema.format)}); + try w.print(" .format = .{s},\n", .{@tagName(schema.format)}); // Chips - try writer.writeAll(" .chips = &.{\n"); + try w.writeAll(" .chips = &.{\n"); for (schema.chips) |chip| { - try writer.writeAll(" .{\n"); - try writer.print(" .name = \"{s}\",\n", .{chip.name}); - try writer.print(" .target_name = \"{s}\",\n", .{chip.target_name}); + try w.writeAll(" .{\n"); + try w.print(" .name = \"{s}\",\n", .{chip.name}); + try w.print(" .target_name = \"{s}\",\n", .{chip.target_name}); // Patch files if (chip.patch_files.len > 0) { - try writer.writeAll(" .patch_files = &.{\n"); + try w.writeAll(" .patch_files = &.{\n"); for (chip.patch_files) |patch_file| { switch (patch_file) { .src_path => |src| { - const sub_path = try normalize_path(allocator, src.sub_path); - const build_root = try normalize_path(allocator, src.build_root); - try writer.writeAll(" .{ .src_path = .{\n"); - try writer.print(" .sub_path = \"{s}\",\n", .{sub_path}); - try writer.print(" .build_root = \"{s}\",\n", .{build_root}); - try writer.writeAll(" } },\n"); + try w.writeAll(" .{ .src_path = .{\n"); + try w.print(" .sub_path = \"{f}\",\n", .{NormalizedPath{ .s = src.sub_path }}); + try w.print(" .build_root = \"{f}\",\n", .{NormalizedPath{ .s = src.build_root }}); + try w.writeAll(" } },\n"); }, .dependency => |dep| { - const sub_path = try normalize_path(allocator, dep.sub_path); - const build_root = try normalize_path(allocator, dep.build_root); - try writer.writeAll(" .{ .dependency = .{\n"); - try writer.print(" .sub_path = \"{s}\",\n", .{sub_path}); - try writer.print(" .build_root = \"{s}\",\n", .{build_root}); - try writer.print(" .dep_name = \"{s}\",\n", .{dep.dep_name}); - try writer.writeAll(" } },\n"); + try w.writeAll(" .{ .dependency = .{\n"); + try w.print(" .sub_path = \"{f}\",\n", .{NormalizedPath{ .s = dep.sub_path }}); + try w.print(" .build_root = \"{f}\",\n", .{NormalizedPath{ .s = dep.build_root }}); + try w.print(" .dep_name = \"{s}\",\n", .{dep.dep_name}); + try w.writeAll(" } },\n"); }, } } - try writer.writeAll(" },\n"); + try w.writeAll(" },\n"); } - try writer.writeAll(" },\n"); + try w.writeAll(" },\n"); } - try writer.writeAll(" },\n"); + try w.writeAll(" },\n"); // Boards - try writer.writeAll(" .boards = &.{"); + try w.writeAll(" .boards = &.{"); for (schema.boards, 0..) |board, i| { - if (i > 0) try writer.writeAll(", "); - try writer.print(".{{ .name = \"{s}\" }}", .{board.name}); + if (i > 0) try w.writeAll(", "); + try w.print(".{{ .name = \"{s}\" }}", .{board.name}); } - try writer.writeAll("},\n"); + try w.writeAll("},\n"); // Location - try writer.writeAll(" .location = "); + try w.writeAll(" .location = "); switch (schema.location) { .src_path => |src| { - const sub_path = try normalize_path(allocator, src.sub_path); - const build_root = try normalize_path(allocator, src.build_root); - try writer.writeAll(".{ .src_path = .{\n"); - try writer.print(" .port_name = \"{s}\",\n", .{src.port_name}); - try writer.print(" .sub_path = \"{s}\",\n", .{sub_path}); - try writer.print(" .build_root = \"{s}\",\n", .{build_root}); - try writer.writeAll(" } },\n"); + try w.writeAll(".{ .src_path = .{\n"); + try w.print(" .port_name = \"{s}\",\n", .{src.port_name}); + try w.print(" .sub_path = \"{f}\",\n", .{NormalizedPath{ .s = src.sub_path }}); + try w.print(" .build_root = \"{f}\",\n", .{NormalizedPath{ .s = src.build_root }}); + try w.writeAll(" } },\n"); }, .dependency => |dep| { - const sub_path = try normalize_path(allocator, dep.sub_path); - const build_root = try normalize_path(allocator, dep.build_root); - try writer.writeAll(".{ .dependency = .{\n"); - try writer.print(" .sub_path = \"{s}\",\n", .{sub_path}); - try writer.print(" .build_root = \"{s}\",\n", .{build_root}); - try writer.print(" .dep_name = \"{s}\",\n", .{dep.dep_name}); - try writer.print(" .port_name = \"{s}\",\n", .{dep.port_name}); - try writer.writeAll(" } },\n"); + try w.writeAll(".{ .dependency = .{\n"); + try w.print(" .sub_path = \"{f}\",\n", .{NormalizedPath{ .s = dep.sub_path }}); + try w.print(" .build_root = \"{f}\",\n", .{NormalizedPath{ .s = dep.build_root }}); + try w.print(" .dep_name = \"{s}\",\n", .{dep.dep_name}); + try w.print(" .port_name = \"{s}\",\n", .{dep.port_name}); + try w.writeAll(" } },\n"); }, } - try writer.writeAll(" },\n"); + try w.writeAll(" },\n"); } - try writer.writeAll( + try w.writeAll( \\}; \\ ); - return buf.toOwnedSlice(allocator); + return w.buffered(); } diff --git a/tools/sorcerer/build.zig.zon b/tools/sorcerer/build.zig.zon index 49229fc89..fee3a8ce9 100644 --- a/tools/sorcerer/build.zig.zon +++ b/tools/sorcerer/build.zig.zon @@ -1,33 +1,27 @@ .{ .name = .sorcerer, + .fingerprint = 0x5f77e0bc82473a35, .version = "0.0.1", + .minimum_zig_version = "0.17.0-dev.1471+ff10b90bc", .dependencies = .{ .microzig = .{ .path = "../..", }, .dvui = .{ - .url = "git+https://github.com/david-vanderson/dvui#0ca4d5c7a7915ee53a7f69c7facb8b48b7807f40", - .hash = "dvui-0.4.0-dev-AQFJmc3h2gBHoPtLm2KO1NEWuqV-kPm1cusWeY6OkHKf", - }, - .serial = .{ - .url = "git+https://github.com/ZigEmbeddedGroup/serial#fbd7389ff8bbc9fa362aa74081588755b5d028a0", - .hash = "serial-0.0.1-PoeRzF60AAAN8Iu0yXTIX-t3DVzsnmN7vWHKM2HA2Zbq", - }, - .tree_sitter_zig = .{ - .url = "git+https://github.com/tree-sitter-grammars/tree-sitter-zig#6479aa13f32f701c383083d8b28360ebd682fb7d", - .hash = "tree_sitter_zig-1.1.2-AAAAAJ57XACsD_XBHC4i6G6rnCsZ1dZj7CLRTC09toEB", + .url = "git+https://github.com/david-vanderson/dvui#20d3e92db74e55678c26009818786d07ae9f30a1", + .hash = "dvui-0.6.0-dev-AQFJmUH_AAGloFzBJgw0vAMLY4pFCl_BQ8qIPiL1kcOd", + .lazy = true, }, .tree_sitter_diff = .{ - .url = "git+https://github.com/tree-sitter-grammars/tree-sitter-diff#2520c3f934b3179bb540d23e0ef45f75304b5fed", - .hash = "N-V-__8AAAULEwDDBPj9cGOTHPT1iE6oZabnwo70bstgVEf_", + .url = "git+https://github.com/tree-sitter-grammars/tree-sitter-diff#ada384ac7bfc1307f32de474620120add29998fb", + .hash = "N-V-__8AAPUcFACL4N4XTVUilDAwPIkX8066VSaipW5tYXDT", + .lazy = true, }, .diffz = .{ - .url = "git+https://github.com/ziglibs/diffz#a20dd1f11b10819a6f570f98b42e1c91e3704357", - .hash = "diffz-0.0.1-G2tlIQrOAQCfH15jdyaLyrMgV8eGPouFhkCeYFTmJaLk", + .url = "git+https://github.com/ziglibs/diffz#aac8aa99c436ab8277b0711922aad062c0167b12", + .hash = "diffz-0.0.1-G2tlISvOAQDORzPTSxDgiKwlHuADKeJMdJrw4kRfLufj", }, }, - .minimum_zig_version = "0.15.2", - .fingerprint = 0x5f77e0bc82473a35, .paths = .{ "README.md", "build.zig", diff --git a/tools/sorcerer/src/RegzWindow.zig b/tools/sorcerer/src/RegzWindow.zig index 8e9897aa2..380ae8eb4 100644 --- a/tools/sorcerer/src/RegzWindow.zig +++ b/tools/sorcerer/src/RegzWindow.zig @@ -1,3 +1,13 @@ +const std = @import("std"); +const dvui = @import("dvui"); +const regz = @import("regz"); +const schemas = @import("schemas"); + +const Allocator = std.mem.Allocator; +const VirtualIo = regz.virtual_io.VirtualIo; + +const RegzWindow = @This(); + gpa: Allocator, arena: std.heap.ArenaAllocator, db: *regz.Database, @@ -5,12 +15,12 @@ id_extra: usize, title: []const u8, show_window: bool = true, path: []const u8, -vfs: VirtualFilesystem, -selected_file: ?VirtualFilesystem.ID = null, -displayed_file: ?VirtualFilesystem.ID = null, +vfs: VirtualIo, +selected_file: ?std.Io.File = null, +displayed_file: ?std.Io.File = null, active_view: View = .code_generation, chip_info: ?ChipInfo = null, -loaded_patches: std.StringArrayHashMapUnmanaged(LoadedPatchFile) = .{}, +loaded_patches: std.StringArrayHashMapUnmanaged(LoadedPatchFile) = .empty, selected_patch: ?SelectedPatch = null, patches_loaded: bool = false, format: regz.Database.Format, @@ -38,7 +48,7 @@ show_validation_error: bool = false, validation_error_message: ?[]const u8 = null, // Reference to all register schema usages (for cross-target validation) -register_schema_usages: ?[]const RegisterSchemaUsage = null, +register_schema_usages: ?[]const schemas.Usage = null, pub const View = enum { code_generation, @@ -48,7 +58,7 @@ pub const View = enum { pub const ChipInfo = struct { name: []const u8, - patch_files: []const RegisterSchemaUsage.PatchFile, + patch_files: []const schemas.Usage.PatchFile, }; pub const LoadedPatchFile = struct { @@ -113,16 +123,6 @@ pub const PeripheralAnalysisResult = struct { result: regz.Analysis.AnalysisResult, }; -const RegzWindow = @This(); -const std = @import("std"); -const Allocator = std.mem.Allocator; - -const regz = @import("regz"); -const VirtualFilesystem = regz.VirtualFilesystem; -const RegisterSchemaUsage = @import("RegisterSchemaUsage"); - -const dvui = @import("dvui"); - // Tree-sitter Zig language parser extern fn tree_sitter_zig() callconv(.c) *dvui.c.TSLanguage; @@ -233,12 +233,14 @@ pub fn create( path: []const u8, device: ?[]const u8, chip_info: ?ChipInfo, - register_schema_usages: ?[]const RegisterSchemaUsage, + register_schema_usages: ?[]const schemas.Usage, ) !*RegzWindow { - const window = try gpa.create(RegzWindow); - errdefer gpa.destroy(window); + const wnd = try gpa.create(RegzWindow); + errdefer gpa.destroy(wnd); + + var vfs: VirtualIo = try .init(gpa); - var db = try regz.Database.create_from_path(gpa, format, path, device); + var db = try regz.Database.create_from_path(gpa, vfs.io(), format, path, device); errdefer db.destroy(); var arena: std.heap.ArenaAllocator = .init(gpa); @@ -254,47 +256,47 @@ pub fn create( }, }; - window.* = .{ + wnd.* = .{ .gpa = gpa, .db = db, .id_extra = count, .title = title, .arena = arena, .path = path, - .vfs = .init(gpa), + .vfs = vfs, .chip_info = chip_info, .format = format, .device = device, .register_schema_usages = register_schema_usages, }; - try db.to_zig(window.vfs.dir(), .{}); + try db.to_zig(wnd.vfs.io(), VirtualIo.root_dir, .{}); count += 1; - return window; + return wnd; } -pub fn destroy(w: *RegzWindow) void { +pub fn destroy(wnd: *RegzWindow) void { // Clean up pending_patches and deleted_patch_indices ArrayLists - for (w.loaded_patches.values()) |*loaded| { - loaded.pending_patches.deinit(w.gpa); - loaded.deleted_patch_indices.deinit(w.gpa); + for (wnd.loaded_patches.values()) |*loaded| { + loaded.pending_patches.deinit(wnd.gpa); + loaded.deleted_patch_indices.deinit(wnd.gpa); } - // Clean up loaded patches hashmap (strings are in w.arena, freed below) - w.loaded_patches.deinit(w.gpa); + // Clean up loaded patches hashmap (strings are in wnd.arena, freed below) + wnd.loaded_patches.deinit(wnd.gpa); - w.vfs.deinit(); - w.arena.deinit(); - w.db.destroy(); + wnd.vfs.deinit(); + wnd.arena.deinit(); + wnd.db.destroy(); } -pub fn show(w: *RegzWindow) !void { - if (!w.show_window) +pub fn show(wnd: *RegzWindow) !void { + if (!wnd.show_window) return; - var arena: std.heap.ArenaAllocator = .init(w.gpa); + var arena: std.heap.ArenaAllocator = .init(wnd.gpa); defer arena.deinit(); // Use a local flag for the window header close button @@ -304,19 +306,19 @@ pub fn show(w: *RegzWindow) !void { var float = dvui.floatingWindow(@src(), .{}, .{ .min_size_content = .{ .w = 400, .h = 400 }, .max_size_content = .width(400), - .id_extra = w.id_extra, + .id_extra = wnd.id_extra, }); defer float.deinit(); - float.dragAreaSet(dvui.windowHeader("Regz", w.title, &header_close_flag)); + float.dragAreaSet(dvui.windowHeader("Regz", wnd.title, &header_close_flag)); // Check if user clicked the X button if (!header_close_flag) { - if (w.has_unsaved_patches) { - w.show_unsaved_warning = true; + if (wnd.has_unsaved_patches) { + wnd.show_unsaved_warning = true; // Don't actually close yet } else { - w.show_window = false; + wnd.show_window = false; } } @@ -332,85 +334,90 @@ pub fn show(w: *RegzWindow) !void { defer m.deinit(); if (dvui.menuItemLabel(@src(), "File", .{ .submenu = true }, .{})) |r| { - var fw = dvui.floatingMenu(@src(), .{ .from = r }, .{}); - defer fw.deinit(); + var fwnd = dvui.floatingMenu(@src(), .{ .from = r }, .{}); + defer fwnd.deinit(); if (dvui.menuItemLabel(@src(), "Save As...", .{}, .{ .expand = .horizontal }) != null) { m.close(); if (dvui.dialogNativeFolderSelect(dvui.currentWindow().arena(), .{ .title = "Save Generated Code To...", }) catch null) |folder_path| { - w.save_to_directory(folder_path) catch |err| { - std.log.err("Failed to save: {}", .{err}); - }; + var output_dir = try std.Io.Dir.cwd() + .createDirPathOpen(dvui.io, folder_path, .{}); + defer output_dir.close(dvui.io); + _ = try wnd.vfs.save_dir_recursive(.root, dvui.io, output_dir); } } // Save Patches - only enabled when has_unsaved_patches if (dvui.menuItemLabel(@src(), "Save Patches", .{}, .{ .expand = .horizontal, - .color_text = if (!w.has_unsaved_patches) dvui.Color.fromHex("918175") else null, - }) != null and w.has_unsaved_patches) { - w.save_all_patches(arena.allocator()) catch |err| { - w.validation_error_message = std.fmt.allocPrint(w.arena.allocator(), "Failed to save patches: {s}", .{@errorName(err)}) catch "Save failed"; - w.show_validation_error = true; + .color_text = if (!wnd.has_unsaved_patches) dvui.Color.fromHex("918175") else null, + }) != null and wnd.has_unsaved_patches) { + wnd.save_all_patches(arena.allocator()) catch |err| { + wnd.validation_error_message = std.fmt.allocPrint(wnd.arena.allocator(), "Failed to save patches: {s}", .{@errorName(err)}) catch "Save failed"; + wnd.show_validation_error = true; }; m.close(); } if (dvui.menuItemLabel(@src(), "Close", .{}, .{ .expand = .horizontal }) != null) { - if (w.has_unsaved_patches) { - w.show_unsaved_warning = true; + if (wnd.has_unsaved_patches) { + wnd.show_unsaved_warning = true; } else { - w.show_window = false; + wnd.show_window = false; } m.close(); } } if (dvui.menuItemLabel(@src(), "View", .{ .submenu = true }, .{})) |r| { - var fw = dvui.floatingMenu(@src(), .{ .from = r }, .{}); - defer fw.deinit(); + var fwnd = dvui.floatingMenu(@src(), .{ .from = r }, .{}); + defer fwnd.deinit(); if (dvui.menuItemLabel(@src(), "Code Generation", .{}, .{ .expand = .horizontal }) != null) { - w.active_view = .code_generation; + wnd.active_view = .code_generation; m.close(); } if (dvui.menuItemLabel(@src(), "Patches", .{}, .{ .expand = .horizontal }) != null) { - w.active_view = .patches; + wnd.active_view = .patches; m.close(); } if (dvui.menuItemLabel(@src(), "Analysis", .{}, .{ .expand = .horizontal }) != null) { - w.active_view = .analysis; + wnd.active_view = .analysis; m.close(); } } } - switch (w.active_view) { - .code_generation => w.show_code_generation(arena.allocator()), - .patches => w.show_patches(arena.allocator()), - .analysis => w.show_analysis(arena.allocator()), + switch (wnd.active_view) { + .code_generation => wnd.show_code_generation(), + .patches => wnd.show_patches(arena.allocator()), + .analysis => wnd.show_analysis(arena.allocator()), } // Render dialogs - w.show_create_patch_dialog_ui(arena.allocator()); - w.show_unsaved_warning_dialog(); - w.show_validation_error_dialog(); + wnd.show_create_patch_dialog_ui(arena.allocator()); + wnd.show_unsaved_warning_dialog(); + wnd.show_validation_error_dialog(); } -fn show_code_generation(w: *RegzWindow, arena: Allocator) void { +fn show_code_generation(wnd: *RegzWindow) void { + if (wnd.selected_file == null) { + std.log.warn("No file selected", .{}); + return; + } + var hbox = dvui.box(@src(), .{ .dir = .horizontal }, .{ .expand = .both }); defer hbox.deinit(); { - const scroll_arena = dvui.scrollArea(@src(), .{}, .{}); + const scroll_arena = dvui.scrollArea(@src(), .{}, .{ .expand = .both }); defer scroll_arena.deinit(); - w.show_file_tree( - arena, + wnd.show_file_tree( @src(), .{}, .{ @@ -423,7 +430,7 @@ fn show_code_generation(w: *RegzWindow, arena: Allocator) void { }, .{ .border = .{ .x = 1 }, - .corner_radius = dvui.Rect.all(4), + .corners = .all(4), .box_shadow = .{ .color = .black, .offset = .{ .x = -5, .y = 5 }, @@ -435,30 +442,38 @@ fn show_code_generation(w: *RegzWindow, arena: Allocator) void { ) catch {}; } + var source_panel = dvui.box(@src(), .{ .dir = .vertical }, .{ .expand = .both }); + defer source_panel.deinit(); + + var text_init_options: dvui.TextEntryWidget.InitOptions = .{ + .multiline = true, + .cache_layout = true, + .text = .{ .internal = .{ .limit = 10_000_000 } }, + }; if (dvui.useTreeSitter) { - var te: dvui.TextEntryWidget = undefined; - te.init(@src(), .{ - .multiline = true, - .cache_layout = true, - .text = .{ .internal = .{ .limit = 10_000_000 } }, - .tree_sitter = .{ - .language = tree_sitter_zig(), - .queries = zig_queries, - .highlights = zig_highlights, - .log_captures = false, - }, - }, .{ .expand = .both }); - defer te.deinit(); + text_init_options.tree_sitter = .{ + .language = tree_sitter_zig(), + .queries = zig_queries, + .highlights = zig_highlights, + .log_captures = false, + }; + } + + var te: dvui.TextEntryWidget = undefined; + te.init(@src(), text_init_options, .{ .expand = .both }); - if (w.selected_file) |id| { - // Update text when file selection changes - if (w.displayed_file != id or dvui.firstFrame(te.data().id)) { - te.textSet(w.vfs.get_content(id), false); + if (wnd.selected_file) |file| { + // Update text when file selection changes. + if (!std.meta.eql(wnd.displayed_file, file) or dvui.firstFrame(te.data().id)) { + if (wnd.vfs.file_contents(file)) |content| { + te.textSet(content.items, false); te.textLayout.selection.moveCursor(0, false); - w.displayed_file = id; + wnd.displayed_file = file; + } else |_| { + wnd.selected_file = null; + wnd.displayed_file = null; } } - // Process only read-only events (selection, copy, navigation, scroll) process_read_only_events(&te); te.draw(); @@ -470,16 +485,18 @@ fn show_code_generation(w: *RegzWindow, arena: Allocator) void { var tl = dvui.textLayout(@src(), .{}, .{ .expand = .horizontal }); defer tl.deinit(); - if (w.selected_file) |id| - tl.addText(w.vfs.get_content(id), .{}); + if (wnd.selected_file) |file| { + if (wnd.vfs.file_contents(file) catch null) |contents| + tl.addText(contents.items, .{}); + } } } -fn show_patches(w: *RegzWindow, arena: Allocator) void { +fn show_patches(wnd: *RegzWindow, arena: Allocator) void { // Load patches on first view - if (!w.patches_loaded) { - w.load_patch_files(); - w.patches_loaded = true; + if (!wnd.patches_loaded) { + wnd.load_patch_files(); + wnd.patches_loaded = true; } var hbox = dvui.box(@src(), .{ .dir = .horizontal }, .{ .expand = .both }); @@ -498,7 +515,7 @@ fn show_patches(w: *RegzWindow, arena: Allocator) void { var scroll = dvui.scrollArea(@src(), .{}, .{ .expand = .vertical }); defer scroll.deinit(); - w.show_patch_tree(arena); + wnd.show_patch_tree(arena); } // Right panel: Patch details @@ -506,11 +523,11 @@ fn show_patches(w: *RegzWindow, arena: Allocator) void { var scroll = dvui.scrollArea(@src(), .{}, .{ .expand = .both }); defer scroll.deinit(); - w.show_patch_details(arena); + wnd.show_patch_details(arena); } } -fn show_analysis(w: *RegzWindow, arena: Allocator) void { +fn show_analysis(wnd: *RegzWindow, arena: Allocator) void { var hbox = dvui.box(@src(), .{ .dir = .horizontal }, .{ .expand = .both }); defer hbox.deinit(); @@ -527,7 +544,7 @@ fn show_analysis(w: *RegzWindow, arena: Allocator) void { var scroll = dvui.scrollArea(@src(), .{}, .{ .expand = .vertical }); defer scroll.deinit(); - w.show_analysis_tree(arena); + wnd.show_analysis_tree(arena); } // Right panel: Equivalence group details @@ -535,13 +552,13 @@ fn show_analysis(w: *RegzWindow, arena: Allocator) void { var scroll = dvui.scrollArea(@src(), .{}, .{ .expand = .both }); defer scroll.deinit(); - w.show_analysis_details(arena); + wnd.show_analysis_details(arena); } } -fn show_analysis_tree(w: *RegzWindow, arena: Allocator) void { +fn show_analysis_tree(wnd: *RegzWindow, arena: Allocator) void { // Run analysis on all peripherals if not cached - const cached = w.get_or_run_full_analysis() orelse { + const cached = wnd.get_or_run_full_analysis() orelse { _ = dvui.label(@src(), "Error running analysis", .{}, .{ .color_text = dvui.Color.fromHex("EF2F27"), }); @@ -575,7 +592,7 @@ fn show_analysis_tree(w: *RegzWindow, arena: Allocator) void { defer tree.deinit(); for (cached.peripheral_results, 0..) |periph_result, periph_idx| { - const is_selected = w.selected_analysis_peripheral != null and w.selected_analysis_peripheral.? == periph_idx; + const is_selected = wnd.selected_analysis_peripheral != null and wnd.selected_analysis_peripheral.? == periph_idx; var branch = tree.branch(@src(), .{ .expanded = is_selected }, .{ .id_extra = periph_idx }); defer branch.deinit(); @@ -598,8 +615,8 @@ fn show_analysis_tree(w: *RegzWindow, arena: Allocator) void { // Handle click on peripheral to select if (branch.button.clicked()) { - w.selected_analysis_peripheral = periph_idx; - w.selected_equivalence_group = null; + wnd.selected_analysis_peripheral = periph_idx; + wnd.selected_equivalence_group = null; } if (branch.expander(@src(), .{ .indent = 14 }, .{ .margin = .{ .x = 14 } })) { @@ -611,8 +628,8 @@ fn show_analysis_tree(w: *RegzWindow, arena: Allocator) void { defer group_branch.deinit(); const is_group_selected = is_selected and - w.selected_equivalence_group != null and - w.selected_equivalence_group.? == group_idx; + wnd.selected_equivalence_group != null and + wnd.selected_equivalence_group.? == group_idx; // Icon for group dvui.icon(@src(), "GroupIcon", dvui.entypo.flow_tree, .{}, .{ .gravity_y = 0.5 }); @@ -626,8 +643,8 @@ fn show_analysis_tree(w: *RegzWindow, arena: Allocator) void { }); if (group_branch.button.clicked()) { - w.selected_analysis_peripheral = periph_idx; - w.selected_equivalence_group = group_idx; + wnd.selected_analysis_peripheral = periph_idx; + wnd.selected_equivalence_group = group_idx; } } @@ -645,9 +662,9 @@ fn show_analysis_tree(w: *RegzWindow, arena: Allocator) void { _ = arena; } -fn show_analysis_details(w: *RegzWindow, arena: Allocator) void { +fn show_analysis_details(wnd: *RegzWindow, arena: Allocator) void { // Check if a peripheral and group are selected - const periph_idx = w.selected_analysis_peripheral orelse { + const periph_idx = wnd.selected_analysis_peripheral orelse { var vbox = dvui.box(@src(), .{ .dir = .vertical }, .{ .expand = .both, .padding = dvui.Rect.all(16), @@ -658,7 +675,7 @@ fn show_analysis_details(w: *RegzWindow, arena: Allocator) void { return; }; - const group_idx = w.selected_equivalence_group orelse { + const group_idx = wnd.selected_equivalence_group orelse { var vbox = dvui.box(@src(), .{ .dir = .vertical }, .{ .expand = .both, .padding = dvui.Rect.all(16), @@ -670,7 +687,7 @@ fn show_analysis_details(w: *RegzWindow, arena: Allocator) void { }; // Get cached analysis result - const cached = w.cached_analysis orelse { + const cached = wnd.cached_analysis orelse { _ = dvui.label(@src(), "No analysis data available", .{}, .{}); return; }; @@ -724,7 +741,7 @@ fn show_analysis_details(w: *RegzWindow, arena: Allocator) void { _ = dvui.spacer(@src(), .{ .min_size_content = .{ .h = 12 } }); // Create Patch from Group button - const has_editable_files = w.has_editable_patch_files(); + const has_editable_files = wnd.has_editable_patch_files(); if (has_editable_files) { if (dvui.button(@src(), "Create Patch from Group", .{}, .{ .color_fill = dvui.Color.fromHex("98BC37"), @@ -732,11 +749,11 @@ fn show_analysis_details(w: *RegzWindow, arena: Allocator) void { .color_text = dvui.Color.fromHex("1C1B19"), })) { // Initialize pending patch creation - w.pending_patch_creation = .{ + wnd.pending_patch_creation = .{ .peripheral_name = periph_result.peripheral_name, .group_idx = group_idx, }; - w.show_create_patch_dialog = true; + wnd.show_create_patch_dialog = true; } } else { _ = dvui.label(@src(), "(No editable patch files)", .{}, .{ @@ -754,13 +771,14 @@ fn show_analysis_details(w: *RegzWindow, arena: Allocator) void { }); _ = dvui.spacer(@src(), .{ .min_size_content = .{ .h = 4 } }); - const header_style: dvui.GridWidget.CellStyle = .{ - .cell_opts = .{ - .border = .{ .y = 0, .h = 1, .x = 0, .w = 0 }, - }, - }; + // const header_style: dvui.GridWidget.CellStyle = .{ + // .cell_opts = .{ + // .border = .{ .y = 0, .h = 1, .x = 0, .w = 0 }, + // }, + // }; - var grid = dvui.grid(@src(), .{ .col_widths = &w.analysis_col_widths }, .{}, .{ + // var grid = dvui.grid(@src(), .{ .col_widths = &wnd.analysis_col_widths }, .{}, .{ + var grid = dvui.grid(@src(), .{}, .{ .expand = .both, .background = true, .padding = dvui.Rect.all(4), @@ -768,51 +786,51 @@ fn show_analysis_details(w: *RegzWindow, arena: Allocator) void { defer grid.deinit(); // Headers with resize handles - dvui.gridHeading(@src(), grid, 0, "Name", .{ - .sizes = &w.analysis_col_widths, - .num = 0, - .min_size = 60, - .max_size = 300, - }, header_style); - dvui.gridHeading(@src(), grid, 1, "Value", .{ - .sizes = &w.analysis_col_widths, - .num = 1, - .min_size = 40, - .max_size = 150, - }, header_style); - dvui.gridHeading(@src(), grid, 2, "Description", .{ - .sizes = &w.analysis_col_widths, - .num = 2, - .min_size = 100, - .max_size = 500, - }, header_style); + // dvui.gridHeading(@src(), grid, 0, "Name", .{ + // .sizes = &wnd.analysis_col_widths, + // .num = 0, + // .min_size = 60, + // .max_size = 300, + // }, header_style); + // dvui.gridHeading(@src(), grid, 1, "Value", .{ + // .sizes = &wnd.analysis_col_widths, + // .num = 1, + // .min_size = 40, + // .max_size = 150, + // }, header_style); + // dvui.gridHeading(@src(), grid, 2, "Description", .{ + // .sizes = &wnd.analysis_col_widths, + // .num = 2, + // .min_size = 100, + // .max_size = 500, + // }, header_style); // Rows for (group.fields, 0..) |field, row_num| { - var cell_num: dvui.GridWidget.Cell = .colRow(0, row_num); + var cell_num: dvui.GridWidget.Cell = .{ .col = 0, .row = row_num }; // Name { - defer cell_num.col_num += 1; - var cell = grid.bodyCell(@src(), cell_num, .{}); - defer cell.deinit(); + defer cell_num.col += 1; + // var cell = grid.bodyCell(@src(), cell_num, .{}); + // defer cell.deinit(); dvui.labelNoFmt(@src(), field.name, .{}, .{}); } // Value { - defer cell_num.col_num += 1; - var cell = grid.bodyCell(@src(), cell_num, .{}); - defer cell.deinit(); + defer cell_num.col += 1; + // var cell = grid.bodyCell(@src(), cell_num, .{}); + // defer cell.deinit(); const value_str = std.fmt.allocPrint(arena, "{d}", .{field.value}) catch "?"; dvui.labelNoFmt(@src(), value_str, .{}, .{}); } // Description { - defer cell_num.col_num += 1; - var cell = grid.bodyCell(@src(), cell_num, .{}); - defer cell.deinit(); + defer cell_num.col += 1; + // var cell = grid.bodyCell(@src(), cell_num, .{}); + // defer cell.deinit(); dvui.labelNoFmt(@src(), field.description orelse "", .{}, .{}); } } @@ -836,27 +854,27 @@ fn show_analysis_details(w: *RegzWindow, arena: Allocator) void { } } -fn get_or_run_full_analysis(w: *RegzWindow) ?*const CachedAnalysis { +fn get_or_run_full_analysis(wnd: *RegzWindow) ?*const CachedAnalysis { // Ensure patches are loaded and applied before running analysis - if (!w.patches_loaded) { - w.load_patch_files(); - w.patches_loaded = true; + if (!wnd.patches_loaded) { + wnd.load_patch_files(); + wnd.patches_loaded = true; } // Return cached if available - if (w.cached_analysis != null) { - return &w.cached_analysis.?; + if (wnd.cached_analysis != null) { + return &wnd.cached_analysis.?; } // Run analysis on all peripherals - const alloc = w.arena.allocator(); + const alloc = wnd.arena.allocator(); - const peripherals = w.db.get_peripherals(alloc) catch { + const peripherals = wnd.db.get_peripherals(alloc) catch { return null; }; var results: std.ArrayList(PeripheralAnalysisResult) = .empty; - var analysis = regz.Analysis.init(w.db); + var analysis: regz.Analysis = .init(wnd.db); for (peripherals) |peripheral| { const result = analysis.find_equivalent_enums(alloc, peripheral.id) catch { @@ -873,18 +891,18 @@ fn get_or_run_full_analysis(w: *RegzWindow) ?*const CachedAnalysis { } // Cache result in window's arena (persists across frames) - w.cached_analysis = .{ + wnd.cached_analysis = .{ .peripheral_results = results.toOwnedSlice(alloc) catch &.{}, }; - return &w.cached_analysis.?; + return &wnd.cached_analysis.?; } -fn load_patch_files(w: *RegzWindow) void { - const chip = w.chip_info orelse return; +fn load_patch_files(wnd: *RegzWindow) void { + const chip = wnd.chip_info orelse return; // Use window's arena for persistent storage across frames - const alloc = w.arena.allocator(); + const alloc = wnd.arena.allocator(); for (chip.patch_files) |pf| { const path_result = construct_patch_path(alloc, pf); @@ -896,43 +914,44 @@ fn load_patch_files(w: *RegzWindow) void { }; // Read and parse ZON file - const file = std.fs.cwd().openFile(path, .{}) catch |err| { + var reader = (std.Io.Dir.cwd().openFile(dvui.io, path, .{}) catch |err| { const owned_path = alloc.dupe(u8, path) catch continue; const error_msg = std.fmt.allocPrint(alloc, "Failed to open file: {s}", .{@errorName(err)}) catch continue; - w.loaded_patches.put(w.gpa, owned_path, .{ + wnd.loaded_patches.put(wnd.gpa, owned_path, .{ .path = owned_path, .patches = null, - .pending_patches = .{}, - .deleted_patch_indices = .{}, + .pending_patches = .empty, + .deleted_patch_indices = .empty, .parse_error = error_msg, .is_editable = is_editable, }) catch {}; continue; - }; - defer file.close(); + }).reader(dvui.io, ""); + defer reader.file.close(dvui.io); - const content = file.readToEndAllocOptions(alloc, 10 * 1024 * 1024, null, .of(u8), 0) catch |err| { + var writer: std.Io.Writer.Allocating = .init(alloc); + _ = writer.writer.sendFile(&reader, .limited(10 * 1024 * 1024)) catch |err| { const owned_path = alloc.dupe(u8, path) catch continue; const error_msg = std.fmt.allocPrint(alloc, "Failed to read file: {s}", .{@errorName(err)}) catch continue; - w.loaded_patches.put(w.gpa, owned_path, .{ + wnd.loaded_patches.put(wnd.gpa, owned_path, .{ .path = owned_path, .patches = null, - .pending_patches = .{}, - .deleted_patch_indices = .{}, + .pending_patches = .empty, + .deleted_patch_indices = .empty, .parse_error = error_msg, .is_editable = is_editable, }) catch {}; continue; }; - const patches = std.zon.parse.fromSlice([]const regz.Patch, alloc, content, null, .{}) catch |err| { + const patches = std.zon.parse.fromSliceAlloc([]const regz.Patch, alloc, writer.toOwnedSliceSentinel(0) catch unreachable, null, .{}) catch |err| { const owned_path = alloc.dupe(u8, path) catch continue; const error_msg = std.fmt.allocPrint(alloc, "Failed to parse ZON: {s}", .{@errorName(err)}) catch continue; - w.loaded_patches.put(w.gpa, owned_path, .{ + wnd.loaded_patches.put(wnd.gpa, owned_path, .{ .path = owned_path, .patches = null, - .pending_patches = .{}, - .deleted_patch_indices = .{}, + .pending_patches = .empty, + .deleted_patch_indices = .empty, .parse_error = error_msg, .is_editable = is_editable, }) catch {}; @@ -941,32 +960,32 @@ fn load_patch_files(w: *RegzWindow) void { // Apply patches to the database so analysis reflects them for (patches) |patch| { - apply_single_patch(w.db, alloc, patch) catch continue; + wnd.db.apply_patch(patch) catch continue; } const owned_path = alloc.dupe(u8, path) catch continue; - w.loaded_patches.put(w.gpa, owned_path, .{ + wnd.loaded_patches.put(wnd.gpa, owned_path, .{ .path = owned_path, .patches = patches, - .pending_patches = .{}, - .deleted_patch_indices = .{}, + .pending_patches = .empty, + .deleted_patch_indices = .empty, .is_editable = is_editable, }) catch {}; } // Regenerate VFS and invalidate caches since patches were applied - w.on_database_changed(); + wnd.on_database_changed(); } -fn construct_patch_path(arena: Allocator, pf: RegisterSchemaUsage.PatchFile) ?[]const u8 { - return switch (pf) { - .src_path => |sp| std.fs.path.join(arena, &.{ sp.build_root, sp.sub_path }) catch null, - .dependency => |dp| std.fs.path.join(arena, &.{ dp.build_root, dp.sub_path }) catch null, - }; +fn construct_patch_path(arena: Allocator, pf: schemas.Usage.PatchFile) ?[]const u8 { + return std.Io.Dir.path.join(arena, switch (pf) { + .src_path => |sp| &.{ sp.build_root, sp.sub_path }, + .dependency => |dp| &.{ dp.build_root, dp.sub_path }, + }) catch null; } -fn show_patch_tree(w: *RegzWindow, arena: Allocator) void { - if (w.loaded_patches.count() == 0) { +fn show_patch_tree(wnd: *RegzWindow, arena: Allocator) void { + if (wnd.loaded_patches.count() == 0) { _ = dvui.label(@src(), "No patch files", .{}, .{}); return; } @@ -978,7 +997,7 @@ fn show_patch_tree(w: *RegzWindow, arena: Allocator) void { defer tree.deinit(); var file_idx: usize = 0; - for (w.loaded_patches.keys(), w.loaded_patches.values()) |path, loaded| { + for (wnd.loaded_patches.keys(), wnd.loaded_patches.values()) |path, loaded| { defer file_idx += 1; var branch = tree.branch(@src(), .{ .expanded = true }, .{ .id_extra = file_idx }); @@ -988,7 +1007,7 @@ fn show_patch_tree(w: *RegzWindow, arena: Allocator) void { const icon = if (loaded.parse_error != null) dvui.entypo.warning else dvui.entypo.documents; dvui.icon(@src(), "FileIcon", icon, .{}, .{ .gravity_y = 0.5 }); - const basename = std.fs.path.basename(path); + const basename = std.Io.Dir.path.basename(path); const editable_suffix: []const u8 = if (loaded.is_editable) "" else " (read-only)"; _ = dvui.label(@src(), "{s}{s}", .{ basename, editable_suffix }, .{}); @@ -1027,7 +1046,7 @@ fn show_patch_tree(w: *RegzWindow, arena: Allocator) void { } if (op_branch.button.clicked() and !is_deleted) { - w.selected_patch = .{ .file_index = file_idx, .patch_index = patch_idx }; + wnd.selected_patch = .{ .file_index = file_idx, .patch_index = patch_idx }; } } } @@ -1046,7 +1065,7 @@ fn show_patch_tree(w: *RegzWindow, arena: Allocator) void { }); if (op_branch.button.clicked()) { - w.selected_patch = .{ .file_index = file_idx, .patch_index = full_idx }; + wnd.selected_patch = .{ .file_index = file_idx, .patch_index = full_idx }; } } } @@ -1065,9 +1084,9 @@ fn get_patch_label(patch: regz.Patch, arena: Allocator) []const u8 { }; } -fn show_patch_details(w: *RegzWindow, arena: Allocator) void { +fn show_patch_details(wnd: *RegzWindow, arena: Allocator) void { // Empty state - no patches available - if (w.loaded_patches.count() == 0) { + if (wnd.loaded_patches.count() == 0) { var vbox = dvui.box(@src(), .{ .dir = .vertical }, .{ .expand = .both, .padding = dvui.Rect.all(16), @@ -1079,7 +1098,7 @@ fn show_patch_details(w: *RegzWindow, arena: Allocator) void { return; } - const sel = w.selected_patch orelse { + const sel = wnd.selected_patch orelse { var vbox = dvui.box(@src(), .{ .dir = .vertical }, .{ .expand = .both, .padding = dvui.Rect.all(16), @@ -1090,11 +1109,11 @@ fn show_patch_details(w: *RegzWindow, arena: Allocator) void { return; }; - const keys = w.loaded_patches.keys(); + const keys = wnd.loaded_patches.keys(); if (sel.file_index >= keys.len) return; const path = keys[sel.file_index]; - const loaded = w.loaded_patches.get(path) orelse return; + const loaded = wnd.loaded_patches.get(path) orelse return; if (loaded.parse_error != null) { _ = dvui.label(@src(), "Cannot display details: file has parse errors", .{}, .{ @@ -1118,9 +1137,9 @@ fn show_patch_details(w: *RegzWindow, arena: Allocator) void { }; // Invalidate cache if selected patch changed - if (w.cached_diff) |cached| { + if (wnd.cached_diff) |cached| { if (cached.file_index != sel.file_index or cached.patch_index != sel.patch_index) { - w.cached_diff = null; + wnd.cached_diff = null; } } @@ -1154,7 +1173,7 @@ fn show_patch_details(w: *RegzWindow, arena: Allocator) void { .color_fill = dvui.Color.fromHex("EF2F27"), .color_text = dvui.Color.fromHex("FFFFFF"), })) { - w.delete_patch(sel.file_index, sel.patch_index, is_pending); + wnd.delete_patch(sel.file_index, sel.patch_index, is_pending); } } } @@ -1169,30 +1188,30 @@ fn show_patch_details(w: *RegzWindow, arena: Allocator) void { defer tab_bar.deinit(); // Fields tab button - const fields_selected = w.patch_detail_tab == .fields; + const fields_selected = wnd.patch_detail_tab == .fields; if (dvui.button(@src(), "Fields", .{}, .{ .background = fields_selected, .border = if (fields_selected) dvui.Rect.all(1) else dvui.Rect.all(0), .padding = dvui.Rect.all(4), })) { - w.patch_detail_tab = .fields; + wnd.patch_detail_tab = .fields; } // Diff tab button - const diff_selected = w.patch_detail_tab == .diff; + const diff_selected = wnd.patch_detail_tab == .diff; if (dvui.button(@src(), "Diff", .{}, .{ .background = diff_selected, .border = if (diff_selected) dvui.Rect.all(1) else dvui.Rect.all(0), .padding = dvui.Rect.all(4), })) { - w.patch_detail_tab = .diff; + wnd.patch_detail_tab = .diff; } } _ = dvui.spacer(@src(), .{ .min_size_content = .{ .h = 8 } }); // Tab content - switch (w.patch_detail_tab) { + switch (wnd.patch_detail_tab) { .fields => { switch (patch) { .override_arch => |p| show_override_arch_widget(p), @@ -1204,14 +1223,14 @@ fn show_patch_details(w: *RegzWindow, arena: Allocator) void { } }, .diff => { - w.show_patch_diff(arena, sel, patch); + wnd.show_patch_diff(sel, patch); }, } } -fn show_patch_diff(w: *RegzWindow, arena: Allocator, sel: SelectedPatch, patch: regz.Patch) void { +fn show_patch_diff(wnd: *RegzWindow, sel: SelectedPatch, patch: regz.Patch) void { // Check if cache is valid - if (w.cached_diff) |cached| { + if (wnd.cached_diff) |cached| { if (cached.file_index == sel.file_index and cached.patch_index == sel.patch_index) { // Display cached diff if (cached.error_message) |err| { @@ -1221,16 +1240,16 @@ fn show_patch_diff(w: *RegzWindow, arena: Allocator, sel: SelectedPatch, patch: return; } - w.display_diff(cached.file_diffs, sel); + wnd.display_diff(cached.file_diffs, sel); return; } } // Compute new diff - w.compute_patch_diff(arena, sel, patch); + wnd.compute_patch_diff(sel, patch); // Display the newly computed diff - if (w.cached_diff) |cached| { + if (wnd.cached_diff) |cached| { if (cached.error_message) |err| { _ = dvui.label(@src(), "Error computing diff: {s}", .{err}, .{ .color_text = dvui.Color.fromHex("EF2F27"), @@ -1238,31 +1257,32 @@ fn show_patch_diff(w: *RegzWindow, arena: Allocator, sel: SelectedPatch, patch: return; } - w.display_diff(cached.file_diffs, sel); + wnd.display_diff(cached.file_diffs, sel); } } -fn display_diff(w: *RegzWindow, file_diffs: []const FileDiff, sel: SelectedPatch) void { +fn display_diff(wnd: *RegzWindow, file_diffs: []const FileDiff, sel: SelectedPatch) void { if (file_diffs.len == 0) { _ = dvui.label(@src(), "No changes detected", .{}, .{}); return; } // Build unified diff format text - var diff_text: std.ArrayList(u8) = .{}; - defer diff_text.deinit(w.gpa); + var writer: std.Io.Writer.Allocating = .init(wnd.gpa); + defer writer.deinit(); + const w = &writer.writer; for (file_diffs) |fd| { // Unified diff file headers - diff_text.appendSlice(w.gpa, "--- a/") catch continue; - diff_text.appendSlice(w.gpa, fd.filename) catch continue; - diff_text.append(w.gpa, '\n') catch continue; - diff_text.appendSlice(w.gpa, "+++ b/") catch continue; - diff_text.appendSlice(w.gpa, fd.filename) catch continue; - diff_text.append(w.gpa, '\n') catch continue; + w.writeAll("--- a/") catch continue; + w.writeAll(fd.filename) catch continue; + w.writeByte('\n') catch continue; + w.writeAll("+++ b/") catch continue; + w.writeAll(fd.filename) catch continue; + w.writeByte('\n') catch continue; // Hunk header (simplified - just use @@ -1 +1 @@) - diff_text.appendSlice(w.gpa, "@@ -1 +1 @@\n") catch continue; + w.writeAll("@@ -1 +1 @@\n") catch continue; // Diff lines for (fd.lines) |line| { @@ -1271,16 +1291,16 @@ fn display_diff(w: *RegzWindow, file_diffs: []const FileDiff, sel: SelectedPatch .added => '+', .removed => '-', }; - diff_text.append(w.gpa, prefix) catch continue; - diff_text.appendSlice(w.gpa, line.text) catch continue; - diff_text.append(w.gpa, '\n') catch continue; + w.writeByte(prefix) catch continue; + w.writeAll(line.text) catch continue; + w.writeByte('\n') catch continue; } - diff_text.append(w.gpa, '\n') catch continue; + w.writeByte('\n') catch continue; } // Copy button at the top if (dvui.button(@src(), "Copy Diff", .{}, .{})) { - dvui.clipboardTextSet(diff_text.items); + dvui.clipboardTextSet(w.buffered()); } _ = dvui.spacer(@src(), .{ .min_size_content = .{ .h = 8 } }); @@ -1305,7 +1325,7 @@ fn display_diff(w: *RegzWindow, file_diffs: []const FileDiff, sel: SelectedPatch // Always set text content on first frame of this widget (unique per patch) if (dvui.firstFrame(te.data().id)) { - te.textSet(diff_text.items, false); + te.textSet(w.buffered(), false); te.textLayout.selection.moveCursor(0, false); } @@ -1343,18 +1363,18 @@ fn display_diff(w: *RegzWindow, file_diffs: []const FileDiff, sel: SelectedPatch } } -fn compute_patch_diff(w: *RegzWindow, temp_arena: Allocator, sel: SelectedPatch, patch: regz.Patch) void { +fn compute_patch_diff(wnd: *RegzWindow, sel: SelectedPatch, patch: regz.Patch) void { _ = patch; // We'll get the patch from the loaded patches instead // Use window's persistent arena for cached data - const arena = w.arena.allocator(); + const arena = wnd.arena.allocator(); // Create TWO fresh databases: // 1. before_db - with all patches BEFORE the selected one applied // 2. after_db - with all patches UP TO AND INCLUDING the selected one applied // Create before database - var before_db = regz.Database.create_from_path(w.gpa, w.format, w.path, w.device) catch |err| { - w.cached_diff = .{ + var before_db = regz.Database.create_from_path(wnd.gpa, wnd.vfs.io(), wnd.format, wnd.path, wnd.device) catch |err| { + wnd.cached_diff = .{ .file_index = sel.file_index, .patch_index = sel.patch_index, .file_diffs = &.{}, @@ -1365,8 +1385,8 @@ fn compute_patch_diff(w: *RegzWindow, temp_arena: Allocator, sel: SelectedPatch, defer before_db.destroy(); // Create after database - var after_db = regz.Database.create_from_path(w.gpa, w.format, w.path, w.device) catch |err| { - w.cached_diff = .{ + var after_db = regz.Database.create_from_path(wnd.gpa, wnd.vfs.io(), wnd.format, wnd.path, wnd.device) catch |err| { + wnd.cached_diff = .{ .file_index = sel.file_index, .patch_index = sel.patch_index, .file_diffs = &.{}, @@ -1379,8 +1399,8 @@ fn compute_patch_diff(w: *RegzWindow, temp_arena: Allocator, sel: SelectedPatch, // Apply patches to both databases // For before_db: apply all patches from files [0..sel.file_index) and patches [0..sel.patch_index) from sel.file_index // For after_db: apply all patches from files [0..sel.file_index] and patches [0..sel.patch_index] from sel.file_index - const keys = w.loaded_patches.keys(); - const values = w.loaded_patches.values(); + const keys = wnd.loaded_patches.keys(); + const values = wnd.loaded_patches.values(); for (keys, values, 0..) |_, loaded, file_idx| { const orig_count = if (loaded.patches) |p| p.len else 0; @@ -1395,23 +1415,17 @@ fn compute_patch_diff(w: *RegzWindow, temp_arena: Allocator, sel: SelectedPatch, const is_selected_or_before = (file_idx < sel.file_index) or (file_idx == sel.file_index and patch_idx <= sel.patch_index); - // Serialize this patch - var zon_buf: std.Io.Writer.Allocating = .init(temp_arena); - const patch_array: []const regz.Patch = &.{p}; - std.zon.stringify.serialize(patch_array, .{}, &zon_buf.writer) catch continue; - const zon_text = temp_arena.dupeZ(u8, zon_buf.written()) catch continue; - var diags: std.zon.parse.Diagnostics = .{}; // Apply to before_db if this patch comes before the selected one if (is_before_selected) { - before_db.apply_patch(zon_text, &diags) catch continue; + before_db.apply_patch(p) catch continue; } // Apply to after_db if this patch is the selected one or comes before it if (is_selected_or_before) { diags = .{}; - after_db.apply_patch(zon_text, &diags) catch continue; + after_db.apply_patch(p) catch continue; } } } @@ -1425,33 +1439,24 @@ fn compute_patch_diff(w: *RegzWindow, temp_arena: Allocator, sel: SelectedPatch, const is_selected_or_before = (file_idx < sel.file_index) or (file_idx == sel.file_index and full_idx <= sel.patch_index); - // Serialize this patch - var zon_buf: std.Io.Writer.Allocating = .init(temp_arena); - const patch_array: []const regz.Patch = &.{p}; - std.zon.stringify.serialize(patch_array, .{}, &zon_buf.writer) catch continue; - const zon_text = temp_arena.dupeZ(u8, zon_buf.written()) catch continue; - - var diags: std.zon.parse.Diagnostics = .{}; - // Apply to before_db if this patch comes before the selected one if (is_before_selected) { - before_db.apply_patch(zon_text, &diags) catch continue; + before_db.apply_patch(p) catch continue; } // Apply to after_db if this patch is the selected one or comes before it if (is_selected_or_before) { - diags = .{}; - after_db.apply_patch(zon_text, &diags) catch continue; + after_db.apply_patch(p) catch continue; } } } // Generate code for before state - var before_vfs: VirtualFilesystem = .init(w.gpa); + var before_vfs = VirtualIo.init(wnd.gpa) catch @panic("bruh"); defer before_vfs.deinit(); - before_db.to_zig(before_vfs.dir(), .{}) catch |err| { - w.cached_diff = .{ + before_db.to_zig(before_vfs.io(), VirtualIo.root_dir, .{}) catch |err| { + wnd.cached_diff = .{ .file_index = sel.file_index, .patch_index = sel.patch_index, .file_diffs = &.{}, @@ -1461,11 +1466,11 @@ fn compute_patch_diff(w: *RegzWindow, temp_arena: Allocator, sel: SelectedPatch, }; // Generate code for after state - var after_vfs: VirtualFilesystem = .init(w.gpa); + var after_vfs = VirtualIo.init(wnd.gpa) catch @panic("bruh"); defer after_vfs.deinit(); - after_db.to_zig(after_vfs.dir(), .{}) catch |err| { - w.cached_diff = .{ + after_db.to_zig(after_vfs.io(), VirtualIo.root_dir, .{}) catch |err| { + wnd.cached_diff = .{ .file_index = sel.file_index, .patch_index = sel.patch_index, .file_diffs = &.{}, @@ -1475,20 +1480,20 @@ fn compute_patch_diff(w: *RegzWindow, temp_arena: Allocator, sel: SelectedPatch, }; // Compare files and build diffs (before vs after) - var file_diffs: std.ArrayList(FileDiff) = .{}; + var file_diffs: std.ArrayList(FileDiff) = .empty; // Recursively compare all files - compare_vfs_files(arena, &before_vfs, &after_vfs, &file_diffs, .root, "") catch { - w.cached_diff = .{ - .file_index = sel.file_index, - .patch_index = sel.patch_index, - .file_diffs = &.{}, - .error_message = "Failed to compare files", - }; - return; - }; - - w.cached_diff = .{ + // compare_vfs_files(arena, &before_vfs, &after_vfs, &file_diffs, .root, "") catch { + // wnd.cached_diff = .{ + // .file_index = sel.file_index, + // .patch_index = sel.patch_index, + // .file_diffs = &.{}, + // .error_message = "Failed to compare files", + // }; + // return; + // }; + + wnd.cached_diff = .{ .file_index = sel.file_index, .patch_index = sel.patch_index, .file_diffs = file_diffs.toOwnedSlice(arena) catch &.{}, @@ -1497,10 +1502,10 @@ fn compute_patch_diff(w: *RegzWindow, temp_arena: Allocator, sel: SelectedPatch, fn compare_vfs_files( arena: Allocator, - original_vfs: *VirtualFilesystem, - patched_vfs: *VirtualFilesystem, + original_vfs: *VirtualIo, + patched_vfs: *VirtualIo, file_diffs: *std.ArrayList(FileDiff), - dir_id: VirtualFilesystem.ID, + dir_id: VirtualIo.ID, path_prefix: []const u8, ) !void { const children = try original_vfs.get_children(arena, dir_id); @@ -1553,11 +1558,11 @@ fn compare_vfs_files( } fn compute_line_diff(arena: Allocator, old_content: []const u8, new_content: []const u8) ![]const DiffLine { - var result: std.ArrayList(DiffLine) = .{}; + var result: std.ArrayList(DiffLine) = .empty; // Split content into lines - var old_lines: std.ArrayList([]const u8) = .{}; - var new_lines: std.ArrayList([]const u8) = .{}; + var old_lines: std.ArrayList([]const u8) = .empty; + var new_lines: std.ArrayList([]const u8) = .empty; var old_iter = std.mem.splitScalar(u8, old_content, '\n'); while (old_iter.next()) |line| { @@ -1669,7 +1674,7 @@ fn compute_lcs(arena: Allocator, old_lines: []const []const u8, new_lines: []con } // Backtrack - var lcs_result: std.ArrayList(LCS_Entry) = .{}; + var lcs_result: std.ArrayList(LCS_Entry) = .empty; var i = m; var j = n; while (i > 0 and j > 0) { @@ -1723,15 +1728,16 @@ fn show_enum_details(e: anytype, arena: Allocator) void { _ = dvui.spacer(@src(), .{ .min_size_content = .{ .h = 4 } }); // Table for enum fields - const header_style: dvui.GridWidget.CellStyle = .{ - .cell_opts = .{ - .border = .{ .y = 0, .h = 1, .x = 0, .w = 0 }, - }, - }; + // const header_style: dvui.GridWidget.CellStyle = .{ + // .cell_opts = .{ + // .border = .{ .y = 0, .h = 1, .x = 0, .w = 0 }, + // }, + // }; // Column widths: Name (120 fixed), Value (60 fixed), Description (proportional -1) - var col_widths: [3]f32 = .{ 0, 0, 0 }; - var grid = dvui.grid(@src(), .{ .col_widths = &col_widths }, .{}, .{ + // var col_widths: [3]f32 = .{ 0, 0, 0 }; + // var grid = dvui.grid(@src(), .{ .col_widths = &col_widths }, .{}, .{ + var grid = dvui.grid(@src(), .{}, .{ .expand = .both, .background = true, .padding = dvui.Rect.all(4), @@ -1739,39 +1745,39 @@ fn show_enum_details(e: anytype, arena: Allocator) void { defer grid.deinit(); // Layout: fixed 120 for Name, fixed 60 for Value, rest for Description - dvui.columnLayoutProportional(&.{ 120, 60, -1 }, &col_widths, grid.data().contentRect().w); + // dvui.columnLayoutProportional(&.{ 120, 60, -1 }, &col_widths, grid.data().contentRect().w); // Table headers - dvui.gridHeading(@src(), grid, 0, "Name", .fixed, header_style); - dvui.gridHeading(@src(), grid, 1, "Value", .fixed, header_style); - dvui.gridHeading(@src(), grid, 2, "Description", .fixed, header_style); + // dvui.gridHeading(@src(), grid, 0, "Name", .fixed, header_style); + // dvui.gridHeading(@src(), grid, 1, "Value", .fixed, header_style); + // dvui.gridHeading(@src(), grid, 2, "Description", .fixed, header_style); // Table rows for (e.fields, 0..) |field, row_num| { - var cell_num: dvui.GridWidget.Cell = .colRow(0, row_num); + var cell_num: dvui.GridWidget.Cell = .{ .col = 0, .row = row_num }; // Name column { - defer cell_num.col_num += 1; - var cell = grid.bodyCell(@src(), cell_num, .{}); - defer cell.deinit(); + defer cell_num.col += 1; + // var cell = grid.bodyCell(@src(), cell_num, .{}); + // defer cell.deinit(); dvui.labelNoFmt(@src(), field.name, .{}, .{}); } // Value column { - defer cell_num.col_num += 1; - var cell = grid.bodyCell(@src(), cell_num, .{}); - defer cell.deinit(); + defer cell_num.col += 1; + // var cell = grid.bodyCell(@src(), cell_num, .{}); + // defer cell.deinit(); const value_str = std.fmt.allocPrint(arena, "{d}", .{field.value}) catch "?"; dvui.labelNoFmt(@src(), value_str, .{}, .{}); } // Description column { - defer cell_num.col_num += 1; - var cell = grid.bodyCell(@src(), cell_num, .{}); - defer cell.deinit(); + defer cell_num.col += 1; + // var cell = grid.bodyCell(@src(), cell_num, .{}); + // defer cell.deinit(); dvui.labelNoFmt(@src(), field.description orelse "", .{}, .{}); } } @@ -1826,119 +1832,73 @@ fn labeled_field(label_text: []const u8, value: []const u8) void { _ = dvui.label(@src(), " {s}", .{value}, .{}); } -fn save_to_directory(w: *RegzWindow, folder_path: []const u8) !void { - var output_dir = try std.fs.cwd().makeOpenPath(folder_path, .{}); - defer output_dir.close(); - - try w.save_vfs_recursive(output_dir, .root); -} - -fn save_vfs_recursive(w: *RegzWindow, output_dir: std.fs.Dir, parent_id: VirtualFilesystem.ID) !void { - const children = try w.vfs.get_children(w.gpa, parent_id); - defer w.gpa.free(children); - - for (children) |entry| { - const name = w.vfs.get_name(entry.id); - switch (entry.kind) { - .directory => { - var subdir = try output_dir.makeOpenPath(name, .{}); - defer subdir.close(); - try w.save_vfs_recursive(subdir, entry.id); - }, - .file => { - const content = w.vfs.get_content(entry.id); - const file = try output_dir.createFile(name, .{}); - defer file.close(); - try file.writeAll(content); - }, - } - } -} - fn show_file_tree( w: *RegzWindow, - arena: Allocator, src: std.builtin.SourceLocation, tree_init_options: dvui.TreeWidget.InitOptions, tree_options: dvui.Options, branch_options: dvui.Options, expander_options: dvui.Options, ) !void { - const unique_id = dvui.parentGet().extendId(@src(), 0); - var tree = dvui.TreeWidget.tree(src, tree_init_options, tree_options); defer tree.deinit(); - const children = try w.vfs.get_children(arena, .root); - try w.show_file_tree_recursive(arena, .root, children, tree, unique_id, branch_options, expander_options); + try w.show_file_tree_recursive(.root, tree, branch_options, expander_options); } fn show_file_tree_recursive( w: *RegzWindow, - arena: Allocator, - directory: VirtualFilesystem.ID, - children: []const VirtualFilesystem.Entry, + directory_id: regz.virtual_io.Dir.ID, tree: *dvui.TreeWidget, - unique_id: dvui.Id, branch_options: dvui.Options, expander_options: dvui.Options, ) !void { + const directory = try directory_id.get(&w.vfs); + var children = directory.inner.iterator(); var id_extra: usize = 0; - for (children, 0..) |child_entry, i| { - if (directory == .root and w.selected_file == null and i == 0) { - w.selected_file = child_entry.id; - } + while (children.next()) |child| { + const name = child.key_ptr.*; + const child_id = child.value_ptr.*; + const node = w.vfs.nodes.get(child_id) orelse continue; + id_extra += 1; var branch_opts_override = dvui.Options{ .id_extra = id_extra, .expand = .horizontal, }; const expanded = true; - //oconst branch_id = tree.data().id.update(w.vfs.get_name(directory)); const branch = tree.branch(@src(), .{ .expanded = expanded }, branch_opts_override.override(branch_options)); defer branch.deinit(); - switch (child_entry.kind) { - .directory => { + switch (node) { + .dir => { dvui.icon( @src(), "FolderIcon", dvui.entypo.folder, .{}, - .{ - .gravity_y = 0.5, - }, + .{ .gravity_y = 0.5 }, ); - const name = w.vfs.get_name(child_entry.id); _ = dvui.label(@src(), "{s}", .{name}, .{}); dvui.icon( @src(), "DropIcon", if (branch.expanded) dvui.entypo.triangle_down else dvui.entypo.triangle_right, - .{ - //.fill_color = color - }, - .{ - .gravity_y = 0.5, - .gravity_x = 1.0, - }, + .{}, + .{ .gravity_y = 0.5, .gravity_x = 1.0 }, ); - var expander_opts_override = dvui.Options{ .margin = .{ .x = 14 }, - //.color_border = color, .background = if (expander_options.border != null) true else false, .expand = .horizontal, }; - if (branch.expander(@src(), .{ .indent = 14 }, expander_opts_override.override(expander_options))) { // The expander is open, so we need to add the branch to our tracking map //reorder_tree_open_branches.put(branch_id, {}) catch { // dvui.log.debug("Failed to track branch state!", .{}); //}; - var box = dvui.box(@src(), .{ .dir = .vertical }, .{ .expand = .horizontal, .background = false, @@ -1947,19 +1907,17 @@ fn show_file_tree_recursive( defer box.deinit(); } - const grandchildren = try w.vfs.get_children(arena, child_entry.id); - try w.show_file_tree_recursive(arena, child_entry.id, grandchildren, tree, unique_id, branch_options, expander_options); + try w.show_file_tree_recursive(@fromBackingInt(child_id), tree, branch_options, expander_options); }, .file => { dvui.icon(@src(), "FileIcon", dvui.entypo.text_document, .{}, .{ .gravity_y = 0.5, }); - const name = w.vfs.get_name(child_entry.id); _ = dvui.label(@src(), "{s}", .{name}, .{}); if (branch.button.clicked()) { - w.selected_file = child_entry.id; + w.selected_file = @as(regz.virtual_io.File.ID, @fromBackingInt(child_id)).to_std(); std.log.info("Clicked: {s}", .{name}); } }, @@ -2027,14 +1985,14 @@ fn process_read_only_events(te: *dvui.TextEntryWidget) void { } /// Check if there are any editable patch files loaded -fn has_editable_patch_files(w: *RegzWindow) bool { +fn has_editable_patch_files(wnd: *RegzWindow) bool { // Ensure patches are loaded first - if (!w.patches_loaded) { - w.load_patch_files(); - w.patches_loaded = true; + if (!wnd.patches_loaded) { + wnd.load_patch_files(); + wnd.patches_loaded = true; } - for (w.loaded_patches.values()) |loaded| { + for (wnd.loaded_patches.values()) |loaded| { if (loaded.is_editable and loaded.parse_error == null) { return true; } @@ -2043,18 +2001,18 @@ fn has_editable_patch_files(w: *RegzWindow) bool { } /// Show the create patch dialog UI -fn show_create_patch_dialog_ui(w: *RegzWindow, arena: Allocator) void { - if (!w.show_create_patch_dialog) return; +fn show_create_patch_dialog_ui(wnd: *RegzWindow, arena: Allocator) void { + if (!wnd.show_create_patch_dialog) return; - const pending = &(w.pending_patch_creation orelse return); + const pending = &(wnd.pending_patch_creation orelse return); - var float = dvui.floatingWindow(@src(), .{ .open_flag = &w.show_create_patch_dialog }, .{ + var float = dvui.floatingWindow(@src(), .{ .open_flag = &wnd.show_create_patch_dialog }, .{ .min_size_content = .{ .w = 350, .h = 250 }, .tag = "create_patch_dialog", }); defer float.deinit(); - float.dragAreaSet(dvui.windowHeader("Create Patch", "", &w.show_create_patch_dialog)); + float.dragAreaSet(dvui.windowHeader("Create Patch", "", &wnd.show_create_patch_dialog)); var vbox = dvui.box(@src(), .{ .dir = .vertical }, .{ .expand = .both, @@ -2084,10 +2042,10 @@ fn show_create_patch_dialog_ui(w: *RegzWindow, arena: Allocator) void { // List of patch files var file_idx: usize = 0; - for (w.loaded_patches.keys(), w.loaded_patches.values()) |path, loaded| { + for (wnd.loaded_patches.keys(), wnd.loaded_patches.values()) |path, loaded| { defer file_idx += 1; - const basename = std.fs.path.basename(path); + const basename = std.Io.Dir.path.basename(path); const is_selected = pending.selected_file_index != null and pending.selected_file_index.? == file_idx; if (loaded.is_editable and loaded.parse_error == null) { @@ -2118,8 +2076,8 @@ fn show_create_patch_dialog_ui(w: *RegzWindow, arena: Allocator) void { // Cancel button if (dvui.button(@src(), "Cancel", .{}, .{})) { - w.show_create_patch_dialog = false; - w.pending_patch_creation = null; + wnd.show_create_patch_dialog = false; + wnd.pending_patch_creation = null; } _ = dvui.spacer(@src(), .{ .min_size_content = .{ .w = 8 } }); @@ -2133,20 +2091,20 @@ fn show_create_patch_dialog_ui(w: *RegzWindow, arena: Allocator) void { .color_text = if (can_create) dvui.Color.fromHex("1C1B19") else dvui.Color.fromHex("918175"), }) and can_create) { // Create the patch - w.create_patch_from_group(arena, pending.*) catch |err| { - w.validation_error_message = std.fmt.allocPrint(w.arena.allocator(), "Failed to create patch: {s}", .{@errorName(err)}) catch "Failed to create patch"; - w.show_validation_error = true; + wnd.create_patch_from_group(pending.*) catch |err| { + wnd.validation_error_message = std.fmt.allocPrint(wnd.arena.allocator(), "Failed to create patch: {s}", .{@errorName(err)}) catch "Failed to create patch"; + wnd.show_validation_error = true; }; - w.show_create_patch_dialog = false; - w.pending_patch_creation = null; + wnd.show_create_patch_dialog = false; + wnd.pending_patch_creation = null; } } } /// Create a patch from an equivalence group -fn create_patch_from_group(w: *RegzWindow, arena: Allocator, pending: PendingPatchCreation) !void { +fn create_patch_from_group(wnd: *RegzWindow, pending: PendingPatchCreation) !void { // Get the cached analysis result - const cached = w.cached_analysis orelse return error.NoAnalysis; + const cached = wnd.cached_analysis orelse return error.NoAnalysis; // Find the peripheral result const periph_idx = blk: { @@ -2171,7 +2129,7 @@ fn create_patch_from_group(w: *RegzWindow, arena: Allocator, pending: PendingPat // Create the add_enum_and_apply patch const patch = try create_add_enum_and_apply_patch( - w.arena.allocator(), + wnd.arena.allocator(), pending.peripheral_name, enum_name, group, @@ -2179,30 +2137,30 @@ fn create_patch_from_group(w: *RegzWindow, arena: Allocator, pending: PendingPat // Add to the selected patch file const file_index = pending.selected_file_index orelse return error.NoFileSelected; - const keys = w.loaded_patches.keys(); + const keys = wnd.loaded_patches.keys(); if (file_index >= keys.len) return error.InvalidFileIndex; const path = keys[file_index]; - const loaded = w.loaded_patches.getPtr(path) orelse return error.FileNotFound; + const loaded = wnd.loaded_patches.getPtr(path) orelse return error.FileNotFound; - try loaded.pending_patches.append(w.gpa, patch); + try loaded.pending_patches.append(wnd.gpa, patch); loaded.is_dirty = true; - w.has_unsaved_patches = true; + wnd.has_unsaved_patches = true; // Apply the patch to the database so analysis reflects the change - try apply_single_patch(w.db, arena, patch); + try wnd.db.apply_patch(patch); // Refresh all views that depend on the database - w.on_database_changed(); + wnd.on_database_changed(); } /// Delete a patch from a patch file -fn delete_patch(w: *RegzWindow, file_idx: usize, patch_idx: usize, is_pending: bool) void { - const keys = w.loaded_patches.keys(); +fn delete_patch(wnd: *RegzWindow, file_idx: usize, patch_idx: usize, is_pending: bool) void { + const keys = wnd.loaded_patches.keys(); if (file_idx >= keys.len) return; const path = keys[file_idx]; - const loaded = w.loaded_patches.getPtr(path) orelse return; + const loaded = wnd.loaded_patches.getPtr(path) orelse return; if (!loaded.is_editable) return; @@ -2217,76 +2175,77 @@ fn delete_patch(w: *RegzWindow, file_idx: usize, patch_idx: usize, is_pending: b } else { // Mark original patch as deleted if (patch_idx < orig_count) { - loaded.deleted_patch_indices.append(w.gpa, patch_idx) catch return; + loaded.deleted_patch_indices.append(wnd.gpa, patch_idx) catch return; } } loaded.is_dirty = true; - w.has_unsaved_patches = true; + wnd.has_unsaved_patches = true; // Clear selected patch if it was the deleted one - if (w.selected_patch) |sel| { + if (wnd.selected_patch) |sel| { if (sel.file_index == file_idx and sel.patch_index == patch_idx) { - w.selected_patch = null; + wnd.selected_patch = null; } } // Rebuild database and reapply remaining patches - w.rebuild_database_with_patches(); + wnd.rebuild_database_with_patches(); } /// Rebuild the database from scratch and reapply all non-deleted patches -fn rebuild_database_with_patches(w: *RegzWindow) void { +fn rebuild_database_with_patches(wnd: *RegzWindow) void { // Destroy current database - w.db.destroy(); + wnd.db.destroy(); // Recreate database from source - w.db = regz.Database.create_from_path(w.gpa, w.format, w.path, w.device) catch |err| { + wnd.db = regz.Database.create_from_path(wnd.gpa, wnd.vfs.io(), wnd.format, wnd.path, wnd.device) catch |err| { std.log.err("Failed to recreate database: {}", .{err}); return; }; - const alloc = w.arena.allocator(); - // Reapply all non-deleted patches from all files - for (w.loaded_patches.values()) |loaded| { + for (wnd.loaded_patches.values()) |loaded| { if (loaded.patches) |patches| { for (patches, 0..) |patch, idx| { if (!loaded.is_patch_deleted(idx)) { - apply_single_patch(w.db, alloc, patch) catch continue; + wnd.db.apply_patch(patch) catch continue; } } } // Reapply pending patches for (loaded.pending_patches.items) |patch| { - apply_single_patch(w.db, alloc, patch) catch continue; + wnd.db.apply_patch(patch) catch continue; } } // Refresh all views that depend on the database - w.on_database_changed(); + wnd.on_database_changed(); } /// Called when the database changes (patches added/deleted) /// Regenerates VFS and invalidates all cached views -fn on_database_changed(w: *RegzWindow) void { +fn on_database_changed(wnd: *RegzWindow) void { // Regenerate the virtual file system with new code // Deinit old VFS and create new one - w.vfs.deinit(); - w.vfs = .init(w.gpa); - w.db.to_zig(w.vfs.dir(), .{}) catch |err| { + wnd.vfs.deinit(); + wnd.vfs = VirtualIo.init(wnd.gpa) catch |err| { + std.log.err("Failed to create vfs: {}", .{err}); + return; + }; + wnd.db.to_zig(wnd.vfs.io(), VirtualIo.root_dir, .{}) catch |err| { std.log.err("Failed to regenerate code: {}", .{err}); }; // Reset displayed file to force refresh in code view - w.displayed_file = null; - w.selected_file = null; + wnd.displayed_file = null; + wnd.selected_file = null; // Invalidate cached analysis - w.cached_analysis = null; + wnd.cached_analysis = null; // Invalidate cached diff - w.cached_diff = null; + wnd.cached_diff = null; } /// Create an add_enum_and_apply patch from an equivalence group @@ -2299,13 +2258,8 @@ fn create_add_enum_and_apply_patch( // Build parent path: "types.peripherals.{peripheral_name}" const parent = try std.fmt.allocPrint(alloc, "types.peripherals.{s}", .{peripheral_name}); - // Get the EnumField type from the Patch type using type introspection - const AddEnumAndApply = std.meta.TagPayload(regz.Patch, .add_enum_and_apply); - const EnumType = @TypeOf(@as(AddEnumAndApply, undefined).@"enum"); - const EnumFieldType = std.meta.Child(@TypeOf(@as(EnumType, undefined).fields)); - // Convert fields (note: Database.EnumField.value is u64, Patch.EnumField.value is u32) - var fields = try alloc.alloc(EnumFieldType, group.fields.len); + var fields = try alloc.alloc(regz.Type.EnumField, group.fields.len); for (group.fields, 0..) |field, i| { fields[i] = .{ .name = try alloc.dupe(u8, field.name), @@ -2339,16 +2293,16 @@ fn create_add_enum_and_apply_patch( } /// Show the unsaved warning dialog -fn show_unsaved_warning_dialog(w: *RegzWindow) void { - if (!w.show_unsaved_warning) return; +fn show_unsaved_warning_dialog(wnd: *RegzWindow) void { + if (!wnd.show_unsaved_warning) return; - var float = dvui.floatingWindow(@src(), .{ .open_flag = &w.show_unsaved_warning }, .{ + var float = dvui.floatingWindow(@src(), .{ .open_flag = &wnd.show_unsaved_warning }, .{ .min_size_content = .{ .w = 300, .h = 120 }, .tag = "unsaved_warning_dialog", }); defer float.deinit(); - float.dragAreaSet(dvui.windowHeader("Unsaved Changes", "", &w.show_unsaved_warning)); + float.dragAreaSet(dvui.windowHeader("Unsaved Changes", "", &wnd.show_unsaved_warning)); var vbox = dvui.box(@src(), .{ .dir = .vertical }, .{ .expand = .both, @@ -2376,17 +2330,17 @@ fn show_unsaved_warning_dialog(w: *RegzWindow) void { .color_text = dvui.Color.fromHex("1C1B19"), })) { // Try to save - var temp_arena: std.heap.ArenaAllocator = .init(w.gpa); + var temp_arena: std.heap.ArenaAllocator = .init(wnd.gpa); defer temp_arena.deinit(); - w.save_all_patches(temp_arena.allocator()) catch |err| { - w.validation_error_message = std.fmt.allocPrint(w.arena.allocator(), "Failed to save patches: {s}", .{@errorName(err)}) catch "Save failed"; - w.show_validation_error = true; - w.show_unsaved_warning = false; + wnd.save_all_patches(temp_arena.allocator()) catch |err| { + wnd.validation_error_message = std.fmt.allocPrint(wnd.arena.allocator(), "Failed to save patches: {s}", .{@errorName(err)}) catch "Save failed"; + wnd.show_validation_error = true; + wnd.show_unsaved_warning = false; return; }; - w.show_unsaved_warning = false; - w.show_window = false; + wnd.show_unsaved_warning = false; + wnd.show_window = false; } _ = dvui.spacer(@src(), .{ .min_size_content = .{ .w = 8 } }); @@ -2396,30 +2350,30 @@ fn show_unsaved_warning_dialog(w: *RegzWindow) void { .color_fill = dvui.Color.fromHex("EF2F27"), .color_text = dvui.Color.fromHex("FCE8C3"), })) { - w.show_unsaved_warning = false; - w.show_window = false; + wnd.show_unsaved_warning = false; + wnd.show_window = false; } _ = dvui.spacer(@src(), .{ .min_size_content = .{ .w = 8 } }); // Cancel button if (dvui.button(@src(), "Cancel", .{}, .{})) { - w.show_unsaved_warning = false; + wnd.show_unsaved_warning = false; } } } /// Show the validation error dialog -fn show_validation_error_dialog(w: *RegzWindow) void { - if (!w.show_validation_error) return; +fn show_validation_error_dialog(wnd: *RegzWindow) void { + if (!wnd.show_validation_error) return; - var float = dvui.floatingWindow(@src(), .{ .open_flag = &w.show_validation_error }, .{ + var float = dvui.floatingWindow(@src(), .{ .open_flag = &wnd.show_validation_error }, .{ .min_size_content = .{ .w = 350, .h = 100 }, .tag = "validation_error_dialog", }); defer float.deinit(); - float.dragAreaSet(dvui.windowHeader("Error", "", &w.show_validation_error)); + float.dragAreaSet(dvui.windowHeader("Error", "", &wnd.show_validation_error)); var vbox = dvui.box(@src(), .{ .dir = .vertical }, .{ .expand = .both, @@ -2427,7 +2381,7 @@ fn show_validation_error_dialog(w: *RegzWindow) void { }); defer vbox.deinit(); - if (w.validation_error_message) |msg| { + if (wnd.validation_error_message) |msg| { _ = dvui.label(@src(), "{s}", .{msg}, .{ .color_text = dvui.Color.fromHex("EF2F27"), }); @@ -2445,44 +2399,49 @@ fn show_validation_error_dialog(w: *RegzWindow) void { _ = dvui.spacer(@src(), .{ .expand = .horizontal }); if (dvui.button(@src(), "OK", .{}, .{})) { - w.show_validation_error = false; - w.validation_error_message = null; + wnd.show_validation_error = false; + wnd.validation_error_message = null; } } } /// Save all dirty patch files -fn save_all_patches(w: *RegzWindow, arena: Allocator) !void { +fn save_all_patches(wnd: *RegzWindow, arena: Allocator) !void { // First, validate all patches against targets - for (w.loaded_patches.keys(), w.loaded_patches.values()) |path, loaded| { + for (wnd.loaded_patches.keys(), wnd.loaded_patches.values()) |path, loaded| { if (!loaded.is_dirty) continue; // Get targets using this patch file - const targets = w.get_targets_using_patch_file(arena, path); + const targets = wnd.get_targets_using_patch_file(arena, path); // Validate against each target for (targets) |target| { - w.validate_patch_file(arena, path, target) catch |err| { - w.validation_error_message = std.fmt.allocPrint(w.arena.allocator(), "Validation failed for target '{s}': {s}", .{ target.name, @errorName(err) }) catch "Validation failed"; - w.show_validation_error = true; + wnd.validate_patch_file(arena, path, target) catch |err| { + wnd.validation_error_message = std.fmt.allocPrint(wnd.arena.allocator(), "Validation failed for target '{s}': {s}", .{ target.name, @errorName(err) }) catch "Validation failed"; + wnd.show_validation_error = true; return err; }; } } // All validations passed, write files and reload - const alloc = w.arena.allocator(); - for (w.loaded_patches.keys()) |path| { - const loaded = w.loaded_patches.getPtr(path) orelse continue; + const alloc = wnd.arena.allocator(); + for (wnd.loaded_patches.keys()) |path| { + const loaded = wnd.loaded_patches.getPtr(path) orelse continue; if (!loaded.is_dirty) continue; - try w.write_patch_file(path, loaded.*); + try wnd.write_patch_file(path, loaded.*); // Reload patches from the saved file to update loaded.patches - const file = try std.fs.cwd().openFile(path, .{}); - defer file.close(); - const content = try file.readToEndAllocOptions(alloc, 10 * 1024 * 1024, null, .of(u8), 0); - const new_patches = std.zon.parse.fromSlice([]const regz.Patch, alloc, content, null, .{}) catch null; + var reader = (try std.Io.Dir.cwd() + .openFile(dvui.io, path, .{})) + .reader(dvui.io, ""); + defer reader.file.close(dvui.io); + + var writer: std.Io.Writer.Allocating = .init(alloc); + _ = try writer.writer.sendFile(&reader, .limited(10 * 1024 * 1024)); + + const new_patches = std.zon.parse.fromSliceAlloc([]const regz.Patch, alloc, try writer.toOwnedSliceSentinel(0), null, .{}) catch null; // Update the loaded state loaded.patches = new_patches; @@ -2491,7 +2450,7 @@ fn save_all_patches(w: *RegzWindow, arena: Allocator) !void { loaded.deleted_patch_indices.clearRetainingCapacity(); } - w.has_unsaved_patches = false; + wnd.has_unsaved_patches = false; } const TargetInfo = struct { @@ -2501,8 +2460,8 @@ const TargetInfo = struct { }; /// Get all targets that use a specific patch file -fn get_targets_using_patch_file(w: *RegzWindow, arena: Allocator, patch_path: []const u8) []const TargetInfo { - const rsus = w.register_schema_usages orelse return &.{}; +fn get_targets_using_patch_file(wnd: *RegzWindow, arena: Allocator, patch_path: []const u8) []const TargetInfo { + const rsus = wnd.register_schema_usages orelse return &.{}; var targets: std.ArrayList(TargetInfo) = .empty; @@ -2526,8 +2485,8 @@ fn get_targets_using_patch_file(w: *RegzWindow, arena: Allocator, patch_path: [] } /// Validate a patch file against a target by attempting to apply all patches -fn validate_patch_file(w: *RegzWindow, arena: Allocator, patch_path: []const u8, target: TargetInfo) !void { - const rsus = w.register_schema_usages orelse return error.NoSchemaUsages; +fn validate_patch_file(wnd: *RegzWindow, arena: Allocator, patch_path: []const u8, target: TargetInfo) !void { + const rsus = wnd.register_schema_usages orelse return error.NoSchemaUsages; if (target.rsu_idx >= rsus.len) return error.InvalidTarget; const rsu = rsus[target.rsu_idx]; @@ -2539,7 +2498,7 @@ fn validate_patch_file(w: *RegzWindow, arena: Allocator, patch_path: []const u8, const sub_path = switch (rsu.location) { inline else => |location| location.sub_path, }; - const schema_path = try std.fs.path.join(arena, &.{ build_root, sub_path }); + const schema_path = try std.Io.Dir.path.join(arena, &.{ build_root, sub_path }); const format: regz.Database.Format = switch (rsu.format) { .svd => .svd, @@ -2550,40 +2509,29 @@ fn validate_patch_file(w: *RegzWindow, arena: Allocator, patch_path: []const u8, const chip_name = if (target.chip_idx < rsu.chips.len) rsu.chips[target.chip_idx].name else null; - const db = try regz.Database.create_from_path(w.gpa, format, schema_path, chip_name); + const db = try regz.Database.create_from_path(wnd.gpa, wnd.vfs.io(), format, schema_path, chip_name); defer db.destroy(); // Get the loaded patch data - const loaded = w.loaded_patches.get(patch_path) orelse return error.PatchFileNotFound; + const loaded = wnd.loaded_patches.get(patch_path) orelse return error.PatchFileNotFound; // Apply original patches (excluding deleted ones) if (loaded.patches) |patches| { for (patches, 0..) |patch, idx| { if (!loaded.is_patch_deleted(idx)) { - try apply_single_patch(db, arena, patch); + try db.apply_patch(patch); } } } // Apply pending patches for (loaded.pending_patches.items) |patch| { - try apply_single_patch(db, arena, patch); + try db.apply_patch(patch); } } -/// Apply a single patch to a database -fn apply_single_patch(db: *regz.Database, arena: Allocator, patch: regz.Patch) !void { - var zon_buf: std.Io.Writer.Allocating = .init(arena); - const patch_array: []const regz.Patch = &.{patch}; - try std.zon.stringify.serialize(patch_array, .{}, &zon_buf.writer); - const zon_text = try arena.dupeZ(u8, zon_buf.written()); - - var diags: std.zon.parse.Diagnostics = .{}; - try db.apply_patch(zon_text, &diags); -} - /// Write a patch file combining original (non-deleted) and pending patches -fn write_patch_file(w: *RegzWindow, path: []const u8, loaded: LoadedPatchFile) !void { +fn write_patch_file(wnd: *RegzWindow, path: []const u8, loaded: LoadedPatchFile) !void { // Count non-deleted original patches var non_deleted_count: usize = 0; if (loaded.patches) |patches| { @@ -2596,8 +2544,8 @@ fn write_patch_file(w: *RegzWindow, path: []const u8, loaded: LoadedPatchFile) ! const total_len = non_deleted_count + loaded.pending_patches.items.len; - var all_patches = try w.gpa.alloc(regz.Patch, total_len); - defer w.gpa.free(all_patches); + var all_patches = try wnd.gpa.alloc(regz.Patch, total_len); + defer wnd.gpa.free(all_patches); var idx: usize = 0; if (loaded.patches) |patches| { @@ -2614,16 +2562,18 @@ fn write_patch_file(w: *RegzWindow, path: []const u8, loaded: LoadedPatchFile) ! } // Serialize to ZON - var zon_buf: std.Io.Writer.Allocating = .init(w.arena.allocator()); + var zon_buf: std.Io.Writer.Allocating = .init(wnd.arena.allocator()); try std.zon.stringify.serialize(all_patches, .{ .emit_default_optional_fields = false, }, &zon_buf.writer); + try zon_buf.writer.writeByte('\n'); // Write to file - const file = try std.fs.cwd().createFile(path, .{}); - defer file.close(); - try file.writeAll(zon_buf.written()); - try file.writeAll("\n"); + var writer = (try std.Io.Dir.cwd() + .createFile(dvui.io, path, .{})) + .writer(dvui.io, ""); + defer writer.file.close(dvui.io); + try writer.interface.writeAll(zon_buf.written()); } // Unit tests for line diff computation diff --git a/tools/sorcerer/src/cli.zig b/tools/sorcerer/src/cli.zig index 88fae6225..d72836ff9 100644 --- a/tools/sorcerer/src/cli.zig +++ b/tools/sorcerer/src/cli.zig @@ -10,40 +10,41 @@ const std = @import("std"); const regz = @import("regz"); const schemas = @import("schemas"); -const RegisterSchemaUsage = @import("RegisterSchemaUsage"); const Allocator = std.mem.Allocator; - -const StdoutWriter = struct { - buf: [4096]u8 = undefined, - file_writer: ?std.fs.File.Writer = null, - - fn writer(self: *StdoutWriter) *std.Io.Writer { - if (self.file_writer == null) { - self.file_writer = std.fs.File.stdout().writer(&self.buf); - } - return &self.file_writer.?.interface; - } -}; - -const StderrWriter = struct { - buf: [4096]u8 = undefined, - file_writer: ?std.fs.File.Writer = null, - - fn writer(self: *StderrWriter) *std.Io.Writer { - if (self.file_writer == null) { - self.file_writer = std.fs.File.stderr().writer(&self.buf); - } - return &self.file_writer.?.interface; - } -}; - -pub fn main() !void { - var gpa = std.heap.GeneralPurposeAllocator(.{}){}; - defer _ = gpa.deinit(); - const allocator = gpa.allocator(); - - run(allocator) catch |err| { +const VirtualIo = regz.virtual_io.VirtualIo; +const Writer = std.Io.Writer; + +const usage = + \\sorcerer-cli - MicroZig Register Definition Tool + \\ + \\Usage: + \\ sorcerer-cli [options] + \\ + \\Commands: + \\ list List all available targets + \\ generate Generate register definitions for a chip + \\ + \\Options for 'list': + \\ --port Filter by port name (e.g., rp2xxx, ch32v) + \\ --json Output in JSON format + \\ + \\Options for 'generate': + \\ -o, --output Output directory (default: ./zig-out) + \\ + \\General options: + \\ -h, --help Show this help + \\ + \\Examples: + \\ sorcerer-cli list + \\ sorcerer-cli list --port rp2xxx + \\ sorcerer-cli list --json + \\ sorcerer-cli generate RP2040 -o ./my-regs/ + \\ +; + +pub fn main(init: std.process.Init) !void { + run(init) catch |err| { switch (err) { error.Explained => std.process.exit(1), else => return err, @@ -51,71 +52,49 @@ pub fn main() !void { }; } -fn run(allocator: Allocator) !void { - const args = try std.process.argsAlloc(allocator); - defer std.process.argsFree(allocator, args); +fn run(init: std.process.Init) !void { + const gpa = init.gpa; + const arena = init.arena.allocator(); + const io = init.io; + const args = try init.minimal.args.toSlice(arena); + + var stdout_writer = std.Io.File.stderr() + .writer(io, try arena.alloc(u8, 4 * 1024)); + const stdout = &stdout_writer.interface; + + var stderr_writer = std.Io.File.stderr() + .writer(io, try arena.alloc(u8, 4 * 1024)); + const stderr = &stderr_writer.interface; if (args.len < 2) { - try print_usage(); + try stdout.writeAll(usage); + try stdout.flush(); return error.Explained; } const command = args[1]; if (std.mem.eql(u8, command, "list")) { - try run_list(allocator, args[2..]); + try run_list(gpa, args[2..], stdout, stderr); } else if (std.mem.eql(u8, command, "generate")) { - try run_generate(allocator, args[2..]); + try run_generate(gpa, io, args[2..], stdout, stderr); } else if (std.mem.eql(u8, command, "-h") or std.mem.eql(u8, command, "--help")) { - try print_usage(); + try stdout.writeAll(usage); + try stdout.flush(); } else { - var stderr_writer: StderrWriter = .{}; - const stderr = stderr_writer.writer(); try stderr.print("Unknown command: {s}\n\n", .{command}); try stderr.flush(); - try print_usage(); + try stdout.writeAll(usage); + try stdout.flush(); return error.Explained; } } -fn print_usage() !void { - var stdout_writer: StdoutWriter = .{}; - const stdout = stdout_writer.writer(); - try stdout.writeAll( - \\sorcerer-cli - MicroZig Register Definition Tool - \\ - \\Usage: - \\ sorcerer-cli [options] - \\ - \\Commands: - \\ list List all available targets - \\ generate Generate register definitions for a chip - \\ - \\Options for 'list': - \\ --port Filter by port name (e.g., rp2xxx, ch32v) - \\ --json Output in JSON format - \\ - \\Options for 'generate': - \\ -o, --output Output directory (default: ./zig-out) - \\ - \\General options: - \\ -h, --help Show this help - \\ - \\Examples: - \\ sorcerer-cli list - \\ sorcerer-cli list --port rp2xxx - \\ sorcerer-cli list --json - \\ sorcerer-cli generate RP2040 -o ./my-regs/ - \\ - ); - try stdout.flush(); -} - // ───────────────────────────────────────────────────────────────────────────── // List command // ───────────────────────────────────────────────────────────────────────────── -fn run_list(allocator: Allocator, args: []const []const u8) !void { +fn run_list(allocator: Allocator, args: []const []const u8, stdout: *Writer, stderr: *Writer) !void { var port_filter: ?[]const u8 = null; var json_output = false; @@ -125,8 +104,6 @@ fn run_list(allocator: Allocator, args: []const []const u8) !void { if (std.mem.eql(u8, arg, "--port")) { i += 1; if (i >= args.len) { - var stderr_writer: StderrWriter = .{}; - const stderr = stderr_writer.writer(); try stderr.writeAll("Error: --port requires a value\n"); try stderr.flush(); return error.Explained; @@ -135,11 +112,10 @@ fn run_list(allocator: Allocator, args: []const []const u8) !void { } else if (std.mem.eql(u8, arg, "--json")) { json_output = true; } else if (std.mem.eql(u8, arg, "-h") or std.mem.eql(u8, arg, "--help")) { - try print_usage(); + try stdout.writeAll(usage); + try stdout.flush(); return; } else { - var stderr_writer: StderrWriter = .{}; - const stderr = stderr_writer.writer(); try stderr.print("Unknown option: {s}\n", .{arg}); try stderr.flush(); return error.Explained; @@ -147,23 +123,20 @@ fn run_list(allocator: Allocator, args: []const []const u8) !void { } if (json_output) { - try print_list_json(allocator, port_filter); + try print_list_json(allocator, port_filter, stdout); } else { - try print_list_table(allocator, port_filter); + try print_list_table(allocator, port_filter, stdout); } } -fn print_list_table(allocator: Allocator, port_filter: ?[]const u8) !void { - var stdout_writer: StdoutWriter = .{}; - const stdout = stdout_writer.writer(); - +fn print_list_table(allocator: Allocator, port_filter: ?[]const u8, w: *Writer) !void { // Track seen chip names to deduplicate display - var seen_chips = std.StringHashMap(void).init(allocator); - defer seen_chips.deinit(); + var seen_chips: std.StringHashMapUnmanaged(void) = .empty; + defer seen_chips.deinit(allocator); // Print header - stdout.print("{s:<24} {s}\n", .{ "CHIP", "PORT" }) catch |err| return handle_write_error(err); - stdout.print("{s:-<24} {s:-<24}\n", .{ "", "" }) catch |err| return handle_write_error(err); + w.print("{s:<24} {s}\n", .{ "CHIP", "PORT" }) catch |err| return handle_write_error(err); + w.print("{s:-<24} {s:-<24}\n", .{ "", "" }) catch |err| return handle_write_error(err); // Print entries (one line per unique chip) for (schemas.schemas) |schema| { @@ -181,12 +154,12 @@ fn print_list_table(allocator: Allocator, port_filter: ?[]const u8) !void { if (seen_chips.contains(chip.name)) { continue; } - seen_chips.put(chip.name, {}) catch {}; + seen_chips.put(allocator, chip.name, {}) catch {}; - stdout.print("{s:<24} {s}\n", .{ chip.name, port_name }) catch |err| return handle_write_error(err); + w.print("{s:<24} {s}\n", .{ chip.name, port_name }) catch |err| return handle_write_error(err); } } - stdout.flush() catch |err| return handle_write_error(err); + w.flush() catch |err| return handle_write_error(err); } /// Handle write errors - exit silently on BrokenPipe so that we can e.g. pipe to `more`. @@ -200,13 +173,13 @@ fn handle_write_error(err: anyerror) error{Explained} { }; } -fn print_list_json(allocator: Allocator, port_filter: ?[]const u8) !void { - var entries: std.ArrayList(JsonEntry) = .{}; +fn print_list_json(allocator: Allocator, port_filter: ?[]const u8, w: *Writer) !void { + var entries: std.ArrayList(JsonEntry) = .empty; defer entries.deinit(allocator); // Track seen chip names to deduplicate - var seen_chips = std.StringHashMap(void).init(allocator); - defer seen_chips.deinit(); + var seen_chips: std.StringHashMapUnmanaged(void) = .empty; + defer seen_chips.deinit(allocator); for (schemas.schemas) |schema| { const port_name = get_port_name(schema.location); @@ -223,7 +196,7 @@ fn print_list_json(allocator: Allocator, port_filter: ?[]const u8) !void { if (seen_chips.contains(chip.name)) { continue; } - seen_chips.put(chip.name, {}) catch {}; + seen_chips.put(allocator, chip.name, {}) catch {}; try entries.append(allocator, .{ .chip = chip.name, @@ -237,11 +210,9 @@ fn print_list_json(allocator: Allocator, port_filter: ?[]const u8) !void { const json_str = try std.json.Stringify.valueAlloc(allocator, entries.items, .{ .whitespace = .indent_2 }); defer allocator.free(json_str); - var stdout_writer: StdoutWriter = .{}; - const stdout = stdout_writer.writer(); - stdout.writeAll(json_str) catch |err| return handle_write_error(err); - stdout.writeByte('\n') catch |err| return handle_write_error(err); - stdout.flush() catch |err| return handle_write_error(err); + w.writeAll(json_str) catch |err| return handle_write_error(err); + w.writeByte('\n') catch |err| return handle_write_error(err); + w.flush() catch |err| return handle_write_error(err); } const JsonEntry = struct { @@ -250,7 +221,7 @@ const JsonEntry = struct { format: []const u8, }; -fn get_port_name(location: RegisterSchemaUsage.Location) []const u8 { +fn get_port_name(location: schemas.Usage.Location) []const u8 { return switch (location) { .src_path => |src| src.port_name, .dependency => |dep| dep.port_name, @@ -261,7 +232,13 @@ fn get_port_name(location: RegisterSchemaUsage.Location) []const u8 { // Generate command // ───────────────────────────────────────────────────────────────────────────── -fn run_generate(allocator: Allocator, args: []const []const u8) !void { +fn run_generate( + allocator: Allocator, + io: std.Io, + args: []const []const u8, + stdout: *Writer, + stderr: *Writer, +) !void { var chip_name: ?[]const u8 = null; var output_path: []const u8 = "./zig-out"; @@ -271,21 +248,18 @@ fn run_generate(allocator: Allocator, args: []const []const u8) !void { if (std.mem.eql(u8, arg, "-o") or std.mem.eql(u8, arg, "--output")) { i += 1; if (i >= args.len) { - var stderr_writer: StderrWriter = .{}; - const stderr = stderr_writer.writer(); try stderr.writeAll("Error: --output requires a value\n"); try stderr.flush(); return error.Explained; } output_path = args[i]; } else if (std.mem.eql(u8, arg, "-h") or std.mem.eql(u8, arg, "--help")) { - try print_usage(); + try stdout.writeAll(usage); + try stdout.flush(); return; } else if (!std.mem.startsWith(u8, arg, "-")) { chip_name = arg; } else { - var stderr_writer: StderrWriter = .{}; - const stderr = stderr_writer.writer(); try stderr.print("Unknown option: {s}\n", .{arg}); try stderr.flush(); return error.Explained; @@ -293,8 +267,6 @@ fn run_generate(allocator: Allocator, args: []const []const u8) !void { } const chip = chip_name orelse { - var stderr_writer: StderrWriter = .{}; - const stderr = stderr_writer.writer(); try stderr.writeAll("Error: chip name is required\n"); try stderr.writeAll("Usage: sorcerer-cli generate [-o ]\n"); try stderr.flush(); @@ -303,18 +275,24 @@ fn run_generate(allocator: Allocator, args: []const []const u8) !void { // Find matching schema const schema = find_schema(chip) orelse { - var stderr_writer: StderrWriter = .{}; - const stderr = stderr_writer.writer(); try stderr.print("Error: chip '{s}' not found\n", .{chip}); try stderr.writeAll("Use 'sorcerer-cli list' to see available chips\n"); try stderr.flush(); return error.Explained; }; - try generate_code(allocator, schema, chip, output_path); + try generate_code( + allocator, + io, + schema, + chip, + output_path, + stdout, + stderr, + ); } -fn find_schema(chip_name: []const u8) ?RegisterSchemaUsage { +fn find_schema(chip_name: []const u8) ?schemas.Usage { for (schemas.schemas) |schema| { for (schema.chips) |chip| { if (std.mem.eql(u8, chip.name, chip_name)) { @@ -327,15 +305,13 @@ fn find_schema(chip_name: []const u8) ?RegisterSchemaUsage { fn generate_code( allocator: Allocator, - schema: RegisterSchemaUsage, + io: std.Io, + schema: schemas.Usage, chip_name: []const u8, output_path: []const u8, + stdout: *Writer, + stderr: *Writer, ) !void { - var stderr_writer: StderrWriter = .{}; - const stderr = stderr_writer.writer(); - var stdout_writer: StdoutWriter = .{}; - const stdout = stdout_writer.writer(); - // Get full path to register definition file const input_path = try get_full_path(allocator, schema.location); defer allocator.free(input_path); @@ -354,7 +330,7 @@ fn generate_code( }; // Create database from register definition file - var db = regz.Database.create_from_path(allocator, format, input_path, chip_name) catch |err| { + var db = regz.Database.create_from_path(allocator, io, format, input_path, chip_name) catch |err| { try stderr.print("Error loading register definition: {}\n", .{err}); try stderr.flush(); return error.Explained; @@ -362,76 +338,32 @@ fn generate_code( defer db.destroy(); // Generate to virtual filesystem first - var vfs = regz.VirtualFilesystem.init(allocator); + var vfs = try VirtualIo.init(allocator); defer vfs.deinit(); - db.to_zig(vfs.dir(), .{}) catch |err| { + db.to_zig(vfs.io(), VirtualIo.root_dir, .{}) catch |err| { try stderr.print("Error generating Zig code: {}\n", .{err}); try stderr.flush(); return error.Explained; }; // Write virtual filesystem contents to actual directory - var output_dir = std.fs.cwd().makeOpenPath(output_path, .{}) catch |err| { + var output_dir = std.Io.Dir.cwd().createDirPathOpen(io, output_path, .{}) catch |err| { try stderr.print("Error creating output directory: {}\n", .{err}); try stderr.flush(); return error.Explained; }; - defer output_dir.close(); + defer output_dir.close(io); - const files_written = try write_vfs_to_dir(allocator, &vfs, output_dir, .root, ""); + const files_written = try vfs.save_dir_recursive(.root, io, output_dir); - try stdout.print("Generated {d} file(s)\n", .{files_written}); + try stdout.print("Generated {} file(s)\n", .{files_written}); try stdout.flush(); } -fn get_full_path(allocator: Allocator, location: RegisterSchemaUsage.Location) ![]const u8 { +fn get_full_path(allocator: Allocator, location: schemas.Usage.Location) ![]const u8 { return switch (location) { .src_path => |src| try std.fmt.allocPrint(allocator, "{s}/{s}", .{ src.build_root, src.sub_path }), .dependency => |dep| try std.fmt.allocPrint(allocator, "{s}/{s}", .{ dep.build_root, dep.sub_path }), }; } - -fn write_vfs_to_dir( - allocator: Allocator, - vfs: *regz.VirtualFilesystem, - output_dir: std.fs.Dir, - parent_id: regz.VirtualFilesystem.ID, - parent_path: []const u8, -) !usize { - var files_written: usize = 0; - - const children = try vfs.get_children(allocator, parent_id); - defer allocator.free(children); - - for (children) |child| { - const name = vfs.get_name(child.id); - const full_path = if (parent_path.len > 0) - try std.fmt.allocPrint(allocator, "{s}/{s}", .{ parent_path, name }) - else - try allocator.dupe(u8, name); - defer allocator.free(full_path); - - switch (child.kind) { - .file => { - const content = vfs.get_content(child.id); - - // Create subdirectory if needed - if (std.fs.path.dirname(full_path)) |dirname| { - try output_dir.makePath(dirname); - } - - const file = try output_dir.createFile(full_path, .{}); - defer file.close(); - try file.writeAll(content); - - files_written += 1; - }, - .directory => { - files_written += try write_vfs_to_dir(allocator, vfs, output_dir, child.id, full_path); - }, - } - } - - return files_written; -} diff --git a/tools/sorcerer/src/main.zig b/tools/sorcerer/src/main.zig index fa55a591d..8a77022cb 100644 --- a/tools/sorcerer/src/main.zig +++ b/tools/sorcerer/src/main.zig @@ -2,7 +2,6 @@ const std = @import("std"); const dvui = @import("dvui"); const regz = @import("regz"); const schemas = @import("schemas"); -const RegisterSchemaUsage = @import("RegisterSchemaUsage"); const RegzWindow = @import("RegzWindow.zig"); const SrceryTheme = @import("SrceryTheme.zig"); @@ -36,7 +35,7 @@ pub const std_options: std.Options = .{ .log_level = .info, }; -var gpa_instance = std.heap.GeneralPurposeAllocator(.{}){}; +var gpa_instance: std.heap.DebugAllocator(.{}) = .init; const gpa = gpa_instance.allocator(); var arena: std.heap.ArenaAllocator = .init(gpa); @@ -44,7 +43,7 @@ var state: State = .{}; const State = struct { orig_content_scale: f32 = 1.0, - regz_windows: std.StringArrayHashMapUnmanaged(*RegzWindow) = .{}, + regz_windows: std.StringArrayHashMapUnmanaged(*RegzWindow) = .empty, show_from_microzig_window: bool = false, show_stats_window: bool = true, show_search_chips_window: bool = false, @@ -72,7 +71,9 @@ pub fn AppInit(win: *dvui.Window) !void { } // Run as app is shutting down before dvui.Window.deinit() -pub fn AppDeinit() void {} +pub fn AppDeinit(win: *dvui.Window) void { + _ = win; +} // Run each frame to do normal UI pub fn AppFrame() !dvui.App.Result { @@ -127,7 +128,7 @@ pub fn frame() !dvui.App.Result { // Stats floating window show_stats_window(); - dvui.Examples.demo(); + dvui.Examples.demo(.lite); from_microzig_menu(); search_chips_window(); @@ -187,7 +188,7 @@ const Stats = struct { targetdb_count: usize, }; -fn compute_stats(rsus: []const RegisterSchemaUsage) Stats { +fn compute_stats(rsus: []const schemas.Usage) Stats { var stats: Stats = .{ .total_chips = 0, .total_boards = 0, @@ -199,8 +200,8 @@ fn compute_stats(rsus: []const RegisterSchemaUsage) Stats { .targetdb_count = 0, }; - var ports_seen = std.StringHashMap(void).init(gpa); - defer ports_seen.deinit(); + var ports_seen: std.StringHashMapUnmanaged(void) = .empty; + defer ports_seen.deinit(gpa); for (rsus) |rsu| { stats.total_chips += rsu.chips.len; @@ -216,7 +217,7 @@ fn compute_stats(rsus: []const RegisterSchemaUsage) Stats { .src_path => |loc| loc.port_name, .dependency => |loc| loc.port_name, }; - ports_seen.put(port_name, {}) catch {}; + ports_seen.put(gpa, port_name, {}) catch {}; switch (rsu.format) { .svd => stats.svd_count += 1, @@ -292,30 +293,21 @@ fn open_register_schema_submenu(m: *dvui.MenuWidget) !void { } } -var highlight_style: dvui.GridWidget.CellStyle.HoveredRow = .{ - .cell_opts = .{ - .color_fill_hover = .gray, - .background = true, - .border = .{ - .y = 0, - .h = 0, - .x = 0, - .w = 1, - }, - }, +const grid_cell_options: dvui.Options = .{ + .background = true, + .border = .{ .y = 0, .h = 0, .x = 0, .w = 1 }, }; -const header_style: dvui.GridWidget.CellStyle = .{ - .cell_opts = .{ - .border = .{ - .y = 0, - .h = 1, - .x = 0, - .w = 0, - }, - }, +const grid_header_options: dvui.Options = .{ + .border = .{ .y = 0, .h = 1, .x = 0, .w = 0 }, }; +fn gridCellOptions(hovered: ?dvui.GridWidget.Cell, row: usize) dvui.Options { + var options = grid_cell_options; + if (hovered != null and hovered.?.row == row) options.color_fill = .gray; + return options; +} + fn from_microzig_menu() void { if (!state.show_from_microzig_window) return; @@ -328,23 +320,29 @@ fn from_microzig_menu() void { float.dragAreaSet(dvui.windowHeader("Open From MicroZig", "", &state.show_from_microzig_window)); - var grid = dvui.grid(@src(), .numCols(3), .{ .scroll_opts = .{ .horizontal_bar = .auto } }, .{ .expand = .both, .background = true }); + var grid = dvui.grid(@src(), .{ .scroll_opts = .{ .horizontal = .auto } }, .{ .expand = .both, .background = true }); defer grid.deinit(); - const row_clicked: ?usize = blk: { - for (dvui.events()) |*e| { - if (!dvui.eventMatchSimple(e, grid.data())) continue; - if (e.evt != .mouse) continue; - const me = e.evt.mouse; - if (me.action != .press) continue; - if (grid.pointToCell(me.p)) |cell| { - if (cell.col_num > 0) break :blk cell.row_num; - } - } - break :blk null; - }; + { + const cell = grid.colHeader(0, grid_header_options); + defer cell.deinit(); + dvui.labelNoFmt(@src(), "Port", .{}, .{ .gravity_x = 0.5 }); + } + { + const cell = grid.colHeader(1, grid_header_options); + defer cell.deinit(); + dvui.labelNoFmt(@src(), "Package", .{}, .{ .gravity_x = 0.5 }); + } + { + const cell = grid.colHeader(2, grid_header_options); + defer cell.deinit(); + dvui.labelNoFmt(@src(), "Location", .{}, .{ .gravity_x = 0.5 }); + } + + if (grid.cellActivated()) |activation| { + const row_num = activation.cell.row; + if (row_num >= schemas.schemas.len) return; - if (row_clicked) |row_num| { std.log.info("clicked row: {}", .{row_num}); const register_schema = schemas.schemas[row_num]; @@ -392,33 +390,38 @@ fn from_microzig_menu() void { } } - dvui.gridHeading(@src(), grid, 0, "Port", .fixed, header_style); - dvui.gridHeading(@src(), grid, 1, "Package", .fixed, header_style); - dvui.gridHeading(@src(), grid, 2, "Location", .fixed, header_style); - - highlight_style.processEvents(grid); + const hovered_cell = grid.cellHovered(); for (schemas.schemas, 0..) |rsu, row_num| { - var cell_num: dvui.GridWidget.Cell = .colRow(0, row_num); + var cell_num: dvui.GridWidget.Cell = .{ .col = 0, .row = row_num }; switch (rsu.location) { .src_path => |src| { { - defer cell_num.col_num += 1; - var cell = grid.bodyCell(@src(), cell_num, highlight_style.cellOptions(cell_num)); + defer cell_num.col += 1; + var cell = grid.cell( + .{ .col = cell_num.col, .row = cell_num.row }, + gridCellOptions(hovered_cell, cell_num.row), + ); defer cell.deinit(); dvui.labelNoFmt(@src(), src.port_name, .{}, .{}); } { - defer cell_num.col_num += 1; - var cell = grid.bodyCell(@src(), cell_num, highlight_style.cellOptions(cell_num)); + defer cell_num.col += 1; + var cell = grid.cell( + .{ .col = cell_num.col, .row = cell_num.row }, + gridCellOptions(hovered_cell, cell_num.row), + ); defer cell.deinit(); dvui.labelNoFmt(@src(), "", .{}, .{}); } { - defer cell_num.col_num += 1; - var cell = grid.bodyCell(@src(), cell_num, highlight_style.cellOptions(cell_num)); + defer cell_num.col += 1; + var cell = grid.cell( + .{ .col = cell_num.col, .row = cell_num.row }, + gridCellOptions(hovered_cell, cell_num.row), + ); defer cell.deinit(); dvui.labelNoFmt(@src(), src.sub_path, .{}, .{}); @@ -426,22 +429,31 @@ fn from_microzig_menu() void { }, .dependency => |dep| { { - defer cell_num.col_num += 1; - var cell = grid.bodyCell(@src(), cell_num, highlight_style.cellOptions(cell_num)); + defer cell_num.col += 1; + var cell = grid.cell( + .{ .col = cell_num.col, .row = cell_num.row }, + gridCellOptions(hovered_cell, cell_num.row), + ); defer cell.deinit(); dvui.labelNoFmt(@src(), dep.port_name, .{}, .{}); } { - defer cell_num.col_num += 1; - var cell = grid.bodyCell(@src(), cell_num, highlight_style.cellOptions(cell_num)); + defer cell_num.col += 1; + var cell = grid.cell( + .{ .col = cell_num.col, .row = cell_num.row }, + gridCellOptions(hovered_cell, cell_num.row), + ); defer cell.deinit(); dvui.labelNoFmt(@src(), dep.dep_name, .{}, .{}); } { - defer cell_num.col_num += 1; - var cell = grid.bodyCell(@src(), cell_num, highlight_style.cellOptions(cell_num)); + defer cell_num.col += 1; + var cell = grid.cell( + .{ .col = cell_num.col, .row = cell_num.row }, + gridCellOptions(hovered_cell, cell_num.row), + ); defer cell.deinit(); dvui.labelNoFmt(@src(), dep.sub_path, .{}, .{}); @@ -483,28 +495,28 @@ fn search_chips_window() void { te.deinit(); } - // Results grid - use explicit column widths for proper alignment - var col_widths = [_]f32{ 200, 150, 80 }; - var grid = dvui.grid(@src(), .colWidths(&col_widths), .{ .scroll_opts = .{ .horizontal_bar = .auto } }, .{ .expand = .both, .background = true }); + var grid = dvui.grid(@src(), .{ .scroll_opts = .{ .horizontal = .auto } }, .{ .expand = .both, .background = true }); defer grid.deinit(); - const row_clicked: ?ChipLocation = blk: { - for (dvui.events()) |*e| { - if (!dvui.eventMatchSimple(e, grid.data())) continue; - if (e.evt != .mouse) continue; - const me = e.evt.mouse; - if (me.action != .press) continue; - if (grid.pointToCell(me.p)) |cell| { - // Decode row_num to find which chip was clicked - if (find_chip_by_row(query, cell.row_num)) |result| { - break :blk result; - } - } - } - break :blk null; - }; + { + const cell = grid.colHeader(0, grid_header_options); + defer cell.deinit(); + dvui.labelNoFmt(@src(), "Chip Name", .{}, .{ .gravity_x = 0.5 }); + } + { + const cell = grid.colHeader(1, grid_header_options); + defer cell.deinit(); + dvui.labelNoFmt(@src(), "Port", .{}, .{ .gravity_x = 0.5 }); + } + { + const cell = grid.colHeader(2, grid_header_options); + defer cell.deinit(); + dvui.labelNoFmt(@src(), "Format", .{}, .{ .gravity_x = 0.5 }); + } + + if (grid.cellActivated()) |activation| blk: { + const clicked = find_chip_by_row(query, activation.cell.row) orelse break :blk; - if (row_clicked) |clicked| { const rsus = schemas.schemas; if (clicked.rsu_idx >= rsus.len) return; const rsu = rsus[clicked.rsu_idx]; @@ -526,17 +538,13 @@ fn search_chips_window() void { } } - dvui.gridHeading(@src(), grid, 0, "Chip Name", .fixed, header_style); - dvui.gridHeading(@src(), grid, 1, "Port", .fixed, header_style); - dvui.gridHeading(@src(), grid, 2, "Format", .fixed, header_style); - - highlight_style.processEvents(grid); + const hovered_cell = grid.cellHovered(); // Only show results if user has typed something if (query.len == 0) return; // Track seen chip names to avoid duplicates - var seen_chips = std.StringHashMap(void).init(dvui.currentWindow().arena()); + var seen_chips: std.StringHashMapUnmanaged(void) = .empty; const max_results: usize = 50; @@ -561,30 +569,30 @@ fn search_chips_window() void { if (seen_chips.contains(chip.name)) { continue; } - seen_chips.put(chip.name, {}) catch {}; + seen_chips.put(dvui.currentWindow().arena(), chip.name, {}) catch {}; - var cell_num: dvui.GridWidget.Cell = .colRow(0, row_num); + var cell_num: dvui.GridWidget.Cell = .{ .col = 0, .row = row_num }; // Chip name column { - defer cell_num.col_num += 1; - var cell = grid.bodyCell(@src(), cell_num, highlight_style.cellOptions(cell_num)); + defer cell_num.col += 1; + var cell = grid.cell(.{ .col = cell_num.col, .row = cell_num.row }, gridCellOptions(hovered_cell, cell_num.row)); defer cell.deinit(); dvui.labelNoFmt(@src(), chip.name, .{}, .{}); } // Port column { - defer cell_num.col_num += 1; - var cell = grid.bodyCell(@src(), cell_num, highlight_style.cellOptions(cell_num)); + defer cell_num.col += 1; + var cell = grid.cell(.{ .col = cell_num.col, .row = cell_num.row }, gridCellOptions(hovered_cell, cell_num.row)); defer cell.deinit(); dvui.labelNoFmt(@src(), port_name, .{}, .{}); } // Format column { - defer cell_num.col_num += 1; - var cell = grid.bodyCell(@src(), cell_num, highlight_style.cellOptions(cell_num)); + defer cell_num.col += 1; + var cell = grid.cell(.{ .col = cell_num.col, .row = cell_num.row }, gridCellOptions(hovered_cell, cell_num.row)); defer cell.deinit(); const format_str: []const u8 = switch (rsu.format) { .svd => "SVD", @@ -622,7 +630,7 @@ fn find_chip_by_row(query: []const u8, target_row: usize) ?ChipLocation { if (query.len == 0) return null; // Track seen chip names to match deduplication in display - var seen_chips = std.StringHashMap(void).init(dvui.currentWindow().arena()); + var seen_chips: std.StringHashMapUnmanaged(void) = .empty; const max_results: usize = 50; @@ -642,7 +650,7 @@ fn find_chip_by_row(query: []const u8, target_row: usize) ?ChipLocation { if (seen_chips.contains(chip.name)) { continue; } - seen_chips.put(chip.name, {}) catch {}; + seen_chips.put(dvui.currentWindow().arena(), chip.name, {}) catch {}; if (row_num == target_row) { return .{ .rsu_idx = rsu_idx, .chip_idx = chip_idx }; @@ -718,33 +726,26 @@ fn target_selection_window() void { float.dragAreaSet(dvui.windowHeader(title, "", &state.show_target_selection_window)); // Results grid - var col_widths = [_]f32{350}; - var grid = dvui.grid(@src(), .colWidths(&col_widths), .{ .scroll_opts = .{ .horizontal_bar = .auto } }, .{ .expand = .both, .background = true }); + var grid = dvui.grid(@src(), .{ .scroll_opts = .{ .horizontal = .auto } }, .{ .expand = .both, .background = true }); defer grid.deinit(); - const row_clicked: ?TargetLocation = blk: { - for (dvui.events()) |*e| { - if (!dvui.eventMatchSimple(e, grid.data())) continue; - if (e.evt != .mouse) continue; - const me = e.evt.mouse; - if (me.action != .press) continue; - if (grid.pointToCell(me.p)) |cell| { - if (find_target_by_row(state.selected_chip_name, cell.row_num)) |result| { - break :blk result; - } - } - } - break :blk null; - }; + { + const cell = grid.colHeader(0, grid_header_options); + defer cell.deinit(); + dvui.labelNoFmt(@src(), "Target", .{}, .{ .gravity_x = 0.5 }); + } + + const row_clicked: ?TargetLocation = if (grid.cellActivated()) |activation| + find_target_by_row(state.selected_chip_name, activation.cell.row) + else + null; if (row_clicked) |clicked| { open_chip_target(clicked.rsu_idx, clicked.chip_idx); state.show_target_selection_window = false; } - dvui.gridHeading(@src(), grid, 0, "Target", .fixed, header_style); - - highlight_style.processEvents(grid); + const hovered_cell = grid.cellHovered(); const rsus = schemas.schemas; var row_num: usize = 0; @@ -755,11 +756,14 @@ fn target_selection_window() void { continue; } - var cell_num: dvui.GridWidget.Cell = .colRow(0, row_num); + var cell_num: dvui.GridWidget.Cell = .{ .col = 0, .row = row_num }; { - defer cell_num.col_num += 1; - var cell = grid.bodyCell(@src(), cell_num, highlight_style.cellOptions(cell_num)); + defer cell_num.col += 1; + var cell = grid.cell( + .{ .col = cell_num.col, .row = cell_num.row }, + gridCellOptions(hovered_cell, cell_num.row), + ); defer cell.deinit(); dvui.labelNoFmt(@src(), chip.target_name, .{}, .{}); } @@ -806,24 +810,17 @@ fn rsu_target_selection_window() void { float.dragAreaSet(dvui.windowHeader("Select Target", "", &state.show_rsu_target_selection_window)); // Results grid - var col_widths = [_]f32{350}; - var grid = dvui.grid(@src(), .colWidths(&col_widths), .{ .scroll_opts = .{ .horizontal_bar = .auto } }, .{ .expand = .both, .background = true }); + var grid = dvui.grid(@src(), .{ .scroll_opts = .{ .horizontal = .auto } }, .{ .expand = .both, .background = true }); defer grid.deinit(); - const row_clicked: ?usize = blk: { - for (dvui.events()) |*e| { - if (!dvui.eventMatchSimple(e, grid.data())) continue; - if (e.evt != .mouse) continue; - const me = e.evt.mouse; - if (me.action != .press) continue; - if (grid.pointToCell(me.p)) |cell| { - break :blk cell.row_num; - } - } - break :blk null; - }; + { + const cell = grid.colHeader(0, grid_header_options); + defer cell.deinit(); + dvui.labelNoFmt(@src(), "Target", .{}, .{ .gravity_x = 0.5 }); + } - if (row_clicked) |chip_idx| { + if (grid.cellActivated()) |activation| { + const chip_idx = activation.cell.row; if (chip_idx < rsu.chips.len) { open_chip_target(rsu_idx, chip_idx); state.show_rsu_target_selection_window = false; @@ -831,16 +828,15 @@ fn rsu_target_selection_window() void { } } - dvui.gridHeading(@src(), grid, 0, "Target", .fixed, header_style); - - highlight_style.processEvents(grid); - + const hovered_cell = grid.cellHovered(); for (rsu.chips, 0..) |chip, row_num| { - var cell_num: dvui.GridWidget.Cell = .colRow(0, row_num); - + var cell_num: dvui.GridWidget.Cell = .{ .col = 0, .row = row_num }; { - defer cell_num.col_num += 1; - var cell = grid.bodyCell(@src(), cell_num, highlight_style.cellOptions(cell_num)); + defer cell_num.col += 1; + var cell = grid.cell( + .{ .col = cell_num.col, .row = cell_num.row }, + gridCellOptions(hovered_cell, cell_num.row), + ); defer cell.deinit(); dvui.labelNoFmt(@src(), chip.target_name, .{}, .{}); } @@ -881,41 +877,38 @@ fn search_boards_window() void { } // Results grid - use explicit column widths for proper alignment - var col_widths = [_]f32{ 200, 150, 80 }; - var grid = dvui.grid(@src(), .colWidths(&col_widths), .{ .scroll_opts = .{ .horizontal_bar = .auto } }, .{ .expand = .both, .background = true }); + var grid = dvui.grid(@src(), .{ .scroll_opts = .{ .horizontal = .auto } }, .{ .expand = .both, .background = true }); defer grid.deinit(); - const row_clicked: ?BoardLocation = blk: { - for (dvui.events()) |*e| { - if (!dvui.eventMatchSimple(e, grid.data())) continue; - if (e.evt != .mouse) continue; - const me = e.evt.mouse; - if (me.action != .press) continue; - if (grid.pointToCell(me.p)) |cell| { - if (find_board_by_row(query, cell.row_num)) |result| { - break :blk result; - } - } - } - break :blk null; - }; + { + const cell = grid.colHeader(0, grid_header_options); + defer cell.deinit(); + dvui.labelNoFmt(@src(), "Board Name", .{}, .{ .gravity_x = 0.5 }); + } + { + const cell = grid.colHeader(1, grid_header_options); + defer cell.deinit(); + dvui.labelNoFmt(@src(), "Port", .{}, .{ .gravity_x = 0.5 }); + } + { + const cell = grid.colHeader(2, grid_header_options); + defer cell.deinit(); + dvui.labelNoFmt(@src(), "Format", .{}, .{ .gravity_x = 0.5 }); + } - if (row_clicked) |clicked| { + if (grid.cellActivated()) |activation| blk: { + const clicked = find_board_by_row(query, activation.cell.row) orelse break :blk; open_board(clicked.rsu_idx, clicked.board_idx); state.show_search_boards_window = false; } - dvui.gridHeading(@src(), grid, 0, "Board Name", .fixed, header_style); - dvui.gridHeading(@src(), grid, 1, "Port", .fixed, header_style); - dvui.gridHeading(@src(), grid, 2, "Format", .fixed, header_style); - - highlight_style.processEvents(grid); + const hovered_cell = grid.cellHovered(); // Only show results if user has typed something if (query.len == 0) return; // Track seen board names to avoid duplicates - var seen_boards = std.StringHashMap(void).init(dvui.currentWindow().arena()); + var seen_boards: std.StringHashMapUnmanaged(void) = .empty; const max_results: usize = 50; @@ -940,30 +933,39 @@ fn search_boards_window() void { if (seen_boards.contains(board.name)) { continue; } - seen_boards.put(board.name, {}) catch {}; + seen_boards.put(dvui.currentWindow().arena(), board.name, {}) catch {}; - var cell_num: dvui.GridWidget.Cell = .colRow(0, row_num); + var cell_num: dvui.GridWidget.Cell = .{ .col = 0, .row = row_num }; // Board name column { - defer cell_num.col_num += 1; - var cell = grid.bodyCell(@src(), cell_num, highlight_style.cellOptions(cell_num)); + defer cell_num.col += 1; + var cell = grid.cell( + .{ .col = cell_num.col, .row = cell_num.row }, + gridCellOptions(hovered_cell, cell_num.row), + ); defer cell.deinit(); dvui.labelNoFmt(@src(), board.name, .{}, .{}); } // Port column { - defer cell_num.col_num += 1; - var cell = grid.bodyCell(@src(), cell_num, highlight_style.cellOptions(cell_num)); + defer cell_num.col += 1; + var cell = grid.cell( + .{ .col = cell_num.col, .row = cell_num.row }, + gridCellOptions(hovered_cell, cell_num.row), + ); defer cell.deinit(); dvui.labelNoFmt(@src(), port_name, .{}, .{}); } // Format column { - defer cell_num.col_num += 1; - var cell = grid.bodyCell(@src(), cell_num, highlight_style.cellOptions(cell_num)); + defer cell_num.col += 1; + var cell = grid.cell( + .{ .col = cell_num.col, .row = cell_num.row }, + gridCellOptions(hovered_cell, cell_num.row), + ); defer cell.deinit(); const format_str: []const u8 = switch (rsu.format) { .svd => "SVD", @@ -985,7 +987,7 @@ fn find_board_by_row(query: []const u8, target_row: usize) ?BoardLocation { if (query.len == 0) return null; // Track seen board names to match deduplication in display - var seen_boards = std.StringHashMap(void).init(dvui.currentWindow().arena()); + var seen_boards: std.StringHashMapUnmanaged(void) = .empty; const max_results: usize = 50; @@ -1005,7 +1007,7 @@ fn find_board_by_row(query: []const u8, target_row: usize) ?BoardLocation { if (seen_boards.contains(board.name)) { continue; } - seen_boards.put(board.name, {}) catch {}; + seen_boards.put(dvui.currentWindow().arena(), board.name, {}) catch {}; if (row_num == target_row) { return .{ .rsu_idx = rsu_idx, .board_idx = board_idx }; @@ -1086,36 +1088,36 @@ fn search_targets_window() void { te.deinit(); } - // Results grid - use explicit column widths for proper alignment - var col_widths = [_]f32{ 280, 100, 80 }; - var grid = dvui.grid(@src(), .colWidths(&col_widths), .{ .scroll_opts = .{ .horizontal_bar = .auto } }, .{ .expand = .both, .background = true }); + var grid = dvui.grid(@src(), .{ .scroll_opts = .{ .horizontal = .auto } }, .{ .expand = .both, .background = true }); defer grid.deinit(); - const row_clicked: ?TargetLocation = blk: { - for (dvui.events()) |*e| { - if (!dvui.eventMatchSimple(e, grid.data())) continue; - if (e.evt != .mouse) continue; - const me = e.evt.mouse; - if (me.action != .press) continue; - if (grid.pointToCell(me.p)) |cell| { - if (find_target_by_query_row(query, cell.row_num)) |result| { - break :blk result; - } - } - } - break :blk null; - }; + { + const cell = grid.colHeader(0, grid_header_options); + defer cell.deinit(); + dvui.labelNoFmt(@src(), "Target", .{}, .{ .gravity_x = 0.5 }); + } + { + const cell = grid.colHeader(1, grid_header_options); + defer cell.deinit(); + dvui.labelNoFmt(@src(), "Chip", .{}, .{ .gravity_x = 0.5 }); + } + { + const cell = grid.colHeader(2, grid_header_options); + defer cell.deinit(); + dvui.labelNoFmt(@src(), "Format", .{}, .{ .gravity_x = 0.5 }); + } + + const row_clicked: ?TargetLocation = if (grid.cellActivated()) |activation| + find_target_by_query_row(query, activation.cell.row) + else + null; if (row_clicked) |clicked| { open_chip_target(clicked.rsu_idx, clicked.chip_idx); state.show_search_targets_window = false; } - dvui.gridHeading(@src(), grid, 0, "Target", .fixed, header_style); - dvui.gridHeading(@src(), grid, 1, "Chip", .fixed, header_style); - dvui.gridHeading(@src(), grid, 2, "Format", .fixed, header_style); - - highlight_style.processEvents(grid); + const hovered_cell = grid.cellHovered(); // Only show results if user has typed something if (query.len == 0) return; @@ -1134,28 +1136,37 @@ fn search_targets_window() void { continue; } - var cell_num: dvui.GridWidget.Cell = .colRow(0, row_num); + var cell_num: dvui.GridWidget.Cell = .{ .col = 0, .row = row_num }; // Target name column { - defer cell_num.col_num += 1; - var cell = grid.bodyCell(@src(), cell_num, highlight_style.cellOptions(cell_num)); + defer cell_num.col += 1; + var cell = grid.cell( + .{ .col = cell_num.col, .row = cell_num.row }, + gridCellOptions(hovered_cell, cell_num.row), + ); defer cell.deinit(); dvui.labelNoFmt(@src(), chip.target_name, .{}, .{}); } // Chip name column { - defer cell_num.col_num += 1; - var cell = grid.bodyCell(@src(), cell_num, highlight_style.cellOptions(cell_num)); + defer cell_num.col += 1; + var cell = grid.cell( + .{ .col = cell_num.col, .row = cell_num.row }, + gridCellOptions(hovered_cell, cell_num.row), + ); defer cell.deinit(); dvui.labelNoFmt(@src(), chip.name, .{}, .{}); } // Format column { - defer cell_num.col_num += 1; - var cell = grid.bodyCell(@src(), cell_num, highlight_style.cellOptions(cell_num)); + defer cell_num.col += 1; + var cell = grid.cell( + .{ .col = cell_num.col, .row = cell_num.row }, + gridCellOptions(hovered_cell, cell_num.row), + ); defer cell.deinit(); const format_str: []const u8 = switch (rsu.format) { .svd => "SVD", diff --git a/tools/sorcerer/src/test_diff.zig b/tools/sorcerer/src/test_diff.zig index 1a1ecc5b2..4568e3699 100644 --- a/tools/sorcerer/src/test_diff.zig +++ b/tools/sorcerer/src/test_diff.zig @@ -8,13 +8,13 @@ const DiffLine = struct { const Kind = enum { context, added, removed }; }; -fn compute_line_diff(arena: std.mem.Allocator, old_content: []const u8, new_content: []const u8) ![]const DiffLine { +fn compute_line_diff(arena: std.mem.Allocator, io: std.Io, old_content: []const u8, new_content: []const u8) ![]const DiffLine { const Kind = DiffLine.Kind; - var result: std.ArrayList(DiffLine) = .{}; + var result: std.ArrayList(DiffLine) = .empty; // Split content into lines - var old_lines: std.ArrayList([]const u8) = .{}; - var new_lines: std.ArrayList([]const u8) = .{}; + var old_lines: std.ArrayList([]const u8) = .empty; + var new_lines: std.ArrayList([]const u8) = .empty; var old_iter = std.mem.splitScalar(u8, old_content, '\n'); while (old_iter.next()) |line| { @@ -27,19 +27,19 @@ fn compute_line_diff(arena: std.mem.Allocator, old_content: []const u8, new_cont } // Encode lines as single characters for diffz (line-mode diffing) - var line_to_char: std.StringHashMap(u8) = .init(arena); - var char_to_line: std.ArrayList([]const u8) = .{}; + var line_to_char: std.StringHashMapUnmanaged(u8) = .empty; + var char_to_line: std.ArrayList([]const u8) = .empty; var next_char: u8 = 1; - var old_chars: std.ArrayList(u8) = .{}; - var new_chars: std.ArrayList(u8) = .{}; + var old_chars: std.ArrayList(u8) = .empty; + var new_chars: std.ArrayList(u8) = .empty; // Encode old lines for (old_lines.items) |line| { if (line_to_char.get(line)) |c| { try old_chars.append(arena, c); } else { - try line_to_char.put(line, next_char); + try line_to_char.put(arena, line, next_char); try char_to_line.append(arena, line); try old_chars.append(arena, next_char); next_char +%= 1; @@ -52,7 +52,7 @@ fn compute_line_diff(arena: std.mem.Allocator, old_content: []const u8, new_cont if (line_to_char.get(line)) |c| { try new_chars.append(arena, c); } else { - try line_to_char.put(line, next_char); + try line_to_char.put(arena, line, next_char); try char_to_line.append(arena, line); try new_chars.append(arena, next_char); next_char +%= 1; @@ -61,8 +61,8 @@ fn compute_line_diff(arena: std.mem.Allocator, old_content: []const u8, new_cont } // Run diffz on the encoded character sequences - const dmp: diffz = .{ .diff_timeout = 0 }; - const diffs = try dmp.diff(arena, old_chars.items, new_chars.items, false); + const dmp: diffz = .initDefault(io, arena); + const diffs = try dmp.diff(old_chars.items, new_chars.items, false, .none); // Decode diffs back to lines for (diffs.items) |d| { @@ -93,7 +93,7 @@ fn expect_diff_lines(result: []const DiffLine, expected: []const DiffLine) !void } test "remove non-exhaustive marker from enum" { - var arena = std.heap.ArenaAllocator.init(std.testing.allocator); + var arena: std.heap.ArenaAllocator = .init(std.testing.allocator); defer arena.deinit(); const allocator = arena.allocator(); @@ -111,7 +111,7 @@ test "remove non-exhaustive marker from enum" { \\}; ; - const result = try compute_line_diff(allocator, old, new); + const result = try compute_line_diff(allocator, std.testing.io, old, new); try expect_diff_lines(result, &.{ .{ .kind = .context, .text = "const Enum = enum {" }, @@ -123,7 +123,7 @@ test "remove non-exhaustive marker from enum" { } test "add line to content" { - var arena = std.heap.ArenaAllocator.init(std.testing.allocator); + var arena: std.heap.ArenaAllocator = .init(std.testing.allocator); defer arena.deinit(); const allocator = arena.allocator(); @@ -137,7 +137,7 @@ test "add line to content" { \\line3 ; - const result = try compute_line_diff(allocator, old, new); + const result = try compute_line_diff(allocator, std.testing.io, old, new); try expect_diff_lines(result, &.{ .{ .kind = .context, .text = "line1" }, @@ -147,7 +147,7 @@ test "add line to content" { } test "modify line in content" { - var arena = std.heap.ArenaAllocator.init(std.testing.allocator); + var arena: std.heap.ArenaAllocator = .init(std.testing.allocator); defer arena.deinit(); const allocator = arena.allocator(); @@ -162,7 +162,7 @@ test "modify line in content" { \\line3 ; - const result = try compute_line_diff(allocator, old, new); + const result = try compute_line_diff(allocator, std.testing.io, old, new); try expect_diff_lines(result, &.{ .{ .kind = .context, .text = "line1" }, @@ -173,7 +173,7 @@ test "modify line in content" { } test "identical content produces all context lines" { - var arena = std.heap.ArenaAllocator.init(std.testing.allocator); + var arena: std.heap.ArenaAllocator = .init(std.testing.allocator); defer arena.deinit(); const allocator = arena.allocator(); @@ -183,7 +183,7 @@ test "identical content produces all context lines" { \\line3 ; - const result = try compute_line_diff(allocator, content, content); + const result = try compute_line_diff(allocator, std.testing.io, content, content); try expect_diff_lines(result, &.{ .{ .kind = .context, .text = "line1" }, @@ -193,7 +193,7 @@ test "identical content produces all context lines" { } test "empty old content shows all lines as added" { - var arena = std.heap.ArenaAllocator.init(std.testing.allocator); + var arena: std.heap.ArenaAllocator = .init(std.testing.allocator); defer arena.deinit(); const allocator = arena.allocator(); @@ -203,7 +203,7 @@ test "empty old content shows all lines as added" { \\line2 ; - const result = try compute_line_diff(allocator, old, new); + const result = try compute_line_diff(allocator, std.testing.io, old, new); try expect_diff_lines(result, &.{ .{ .kind = .removed, .text = "" }, @@ -213,7 +213,7 @@ test "empty old content shows all lines as added" { } test "empty new content shows all lines as removed" { - var arena = std.heap.ArenaAllocator.init(std.testing.allocator); + var arena: std.heap.ArenaAllocator = .init(std.testing.allocator); defer arena.deinit(); const allocator = arena.allocator(); @@ -223,7 +223,7 @@ test "empty new content shows all lines as removed" { ; const new = ""; - const result = try compute_line_diff(allocator, old, new); + const result = try compute_line_diff(allocator, std.testing.io, old, new); try expect_diff_lines(result, &.{ .{ .kind = .removed, .text = "line1" }, @@ -233,7 +233,7 @@ test "empty new content shows all lines as removed" { } test "duplicate lines are handled correctly" { - var arena = std.heap.ArenaAllocator.init(std.testing.allocator); + var arena: std.heap.ArenaAllocator = .init(std.testing.allocator); defer arena.deinit(); const allocator = arena.allocator(); @@ -250,7 +250,7 @@ test "duplicate lines are handled correctly" { \\b ; - const result = try compute_line_diff(allocator, old, new); + const result = try compute_line_diff(allocator, std.testing.io, old, new); // The exact diff output depends on the diffz algorithm // Just verify we get a valid result with the right total lines @@ -298,7 +298,7 @@ const TestPatch = union(enum) { }; test "zon serialize single patch - raw output" { - var arena = std.heap.ArenaAllocator.init(std.testing.allocator); + var arena: std.heap.ArenaAllocator = .init(std.testing.allocator); defer arena.deinit(); const allocator = arena.allocator(); @@ -333,7 +333,7 @@ test "zon serialize single patch - raw output" { } test "zon serialize multiple patches - raw output" { - var arena = std.heap.ArenaAllocator.init(std.testing.allocator); + var arena: std.heap.ArenaAllocator = .init(std.testing.allocator); defer arena.deinit(); const allocator = arena.allocator();