From abc39bd905afc565fc2318c2016bd25ccc818650 Mon Sep 17 00:00:00 2001 From: Ben Jordan Date: Thu, 22 Oct 2026 15:00:51 -0400 Subject: [PATCH] zig 0.17.0 --- .gitignore | 1 + blush/color.zig | 10 ++++---- build.zig | 34 ++++++++++++-------------- build.zig.zon | 7 +++++- demo/dream.zig | 2 +- dream/Options.zig | 8 ++++--- flake.lock | 6 ++--- flake.nix | 36 ++++++++++++++++++++-------- leaves/FilePicker.zig | 2 +- leaves/TextInput.zig | 8 +++---- leaves/Textarea.zig | 8 +++---- leaves/Tree.zig | 15 ++---------- matcha/input.zig | 8 +++---- matcha/platform/posix.zig | 4 ++-- zli/Cmd.zig | 50 ++++++++++++++++++++++++--------------- 15 files changed, 110 insertions(+), 89 deletions(-) diff --git a/.gitignore b/.gitignore index ac13147..71d330d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ zig-out +zig-pkg .zig-cache result diff --git a/blush/color.zig b/blush/color.zig index 332fdb8..8c21fde 100644 --- a/blush/color.zig +++ b/blush/color.zig @@ -74,16 +74,16 @@ pub const Color = union(enum) { pub fn writeSgr(color: Color, writer: *Writer, target: Target) Writer.Error!void { switch (color) { - .none => try writer.print("\x1b[{d}m", .{@intFromEnum(target) + 9}), + .none => try writer.print("\x1b[{d}m", .{@backingInt(target) + 9}), .ansi => |index| { if (index < 8) { - try writer.print("\x1b[{d}m", .{@as(u8, @intFromEnum(target)) + index}); + try writer.print("\x1b[{d}m", .{@as(u8, @backingInt(target)) + index}); } else { - try writer.print("\x1b[{d}m", .{@as(u8, @intFromEnum(target)) + 60 + (index - 8)}); + try writer.print("\x1b[{d}m", .{@as(u8, @backingInt(target)) + 60 + (index - 8)}); } }, - .ansi256 => |index| try writer.print("\x1b[{d};5;{d}m", .{ @intFromEnum(target) + 8, index }), - .rgb => |rgb| try writer.print("\x1b[{d};2;{d};{d};{d}m", .{ @intFromEnum(target) + 8, rgb.r, rgb.g, rgb.b }), + .ansi256 => |index| try writer.print("\x1b[{d};5;{d}m", .{ @backingInt(target) + 8, index }), + .rgb => |rgb| try writer.print("\x1b[{d};2;{d};{d};{d}m", .{ @backingInt(target) + 8, rgb.r, rgb.g, rgb.b }), } } diff --git a/build.zig b/build.zig index e9de799..2cf5e24 100644 --- a/build.zig +++ b/build.zig @@ -3,11 +3,24 @@ const Build = std.Build; const Module = Build.Module; const Import = Module.Import; const Dir = std.Io.Dir; +const Translator = @import("translate_c").Translator; pub fn build(b: *Build) !void { const target = b.standardTargetOptions(.{}); const optimize = b.standardOptimizeOption(.{}); + const translate_c = b.dependency("translate_c", .{}); + const translator: Translator = .init(translate_c, .{ + .c_source_file = b.path("dream/libssh.h"), + .target = target, + .optimize = optimize, + .link_system_libs = &.{ + .{ .name = "libssh", .options = .{ .needed = true } }, + }, + }); + + const libssh = translator.mod; + const blush = b.addModule("blush", .{ .root_source_file = b.path("blush/root.zig"), .target = target, @@ -41,20 +54,6 @@ pub fn build(b: *Build) !void { }); try addTest(b, "zli", zli); - const c = b.addTranslateC(.{ - .root_source_file = b.path("dream/libssh.h"), - .target = target, - .optimize = optimize, - }); - - c.linkSystemLibrary("libssh", .{ - .use_pkg_config = .yes, - .needed = true, - .preferred_link_mode = .dynamic, - }); - - const libssh = c.createModule(); - const dream = b.addModule("dream", .{ .root_source_file = b.path("dream/root.zig"), .target = target, @@ -101,9 +100,7 @@ pub fn build(b: *Build) !void { run_cmd.step.dependOn(b.getInstallStep()); - if (b.args) |args| { - run_cmd.addArgs(args); - } + run_cmd.addPassthruArgs(); } fn import(name: []const u8, module: *Module) Import { @@ -132,8 +129,7 @@ fn testFilters(b: *Build) ![]const []const u8 { ) orelse &.{}; const opt = b.user_input_options.getPtr("test-filter") orelse return &.{}; - opt.used = true; - return switch (opt.value) { + return switch (opt.*) { .list => |l| l.items, .scalar => |s| try b.allocator.dupe([]const u8, &.{s}), else => &.{}, diff --git a/build.zig.zon b/build.zig.zon index 38e102f..53d81d8 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -3,7 +3,12 @@ .version = "0.0.0", .fingerprint = 0xffd125660bde70e8, // Changing this has security and trust implications. .minimum_zig_version = "0.16.0", - .dependencies = .{}, + .dependencies = .{ + .translate_c = .{ + .url = "git+https://codeberg.org/ziglang/translate-c?ref=zig-0.17.x#cfac21f3199b2b02ce3fd7db652aa456421fe4ab", + .hash = "translate_c-2.0.2-Q_BUWvBNBwDnyehSiuAJmdFcMYHYGA-Jc3KiYdrV2Dqx", + }, + }, .paths = .{ "build.zig", "build.zig.zon", diff --git a/demo/dream.zig b/demo/dream.zig index 7eb7521..9034819 100644 --- a/demo/dream.zig +++ b/demo/dream.zig @@ -75,7 +75,7 @@ pub fn dreamer( // libssh wants a NUL-terminated key path; env values aren't sentinel-typed. const key_path_raw = init.environ_map.get("DREAM_HOST_KEY") orelse std.debug.panic("set DREAM_HOST_KEY to a host-key path\n", .{}); - const key_path = try init.gpa.dupeZ(u8, key_path_raw); + const key_path = try init.gpa.dupeSentinel(u8, key_path_raw, 0); defer init.gpa.free(key_path); var server = try dream.serve(Model, "model", init, .{ diff --git a/dream/Options.zig b/dream/Options.zig index 05c6c49..3d86b6b 100644 --- a/dream/Options.zig +++ b/dream/Options.zig @@ -23,6 +23,8 @@ banner: [:0]const u8 = "", version: ?[:0]const u8 = null, const std = @import("std"); + +const mem = std.mem; const IpAddress = std.Io.net.IpAddress; const Writer = std.Io.Writer; @@ -34,7 +36,7 @@ pub fn hostZ(self: Options, buf: []u8) ![:0]const u8 { switch (self.address) { .ip4 => |a| { const b = a.bytes; - return std.fmt.bufPrintZ(buf, "{d}.{d}.{d}.{d}", .{ b[0], b[1], b[2], b[3] }) catch + return mem.printSentinel(buf, "{d}.{d}.{d}.{d}", .{ b[0], b[1], b[2], b[3] }, 0) catch error.AddressTooLong; }, .ip6 => { @@ -45,7 +47,7 @@ pub fn hostZ(self: Options, buf: []u8) ![:0]const u8 { const s = w.buffered(); const start = (std.mem.indexOfScalar(u8, s, '[') orelse return error.AddressTooLong) + 1; const end = std.mem.indexOfScalar(u8, s, ']') orelse return error.AddressTooLong; - return std.fmt.bufPrintZ(buf, "{s}", .{s[start..end]}) catch error.AddressTooLong; + return mem.printSentinel(buf, "{s}", .{s[start..end]}, 0) catch error.AddressTooLong; }, } } @@ -53,5 +55,5 @@ pub fn hostZ(self: Options, buf: []u8) ![:0]const u8 { /// Render the bind port as a sentinel-terminated string into `buf`, for /// libssh's `SSH_BIND_OPTIONS_BINDPORT_STR`. pub fn portZ(self: Options, buf: []u8) ![:0]const u8 { - return std.fmt.bufPrintZ(buf, "{d}", .{self.address.getPort()}); + return mem.printSentinel(buf, "{d}", .{self.address.getPort()}, 0); } diff --git a/flake.lock b/flake.lock index c508296..886d2d9 100644 --- a/flake.lock +++ b/flake.lock @@ -2,11 +2,11 @@ "nodes": { "nixpkgs": { "locked": { - "lastModified": 1787736819, - "narHash": "sha256-cV5xEJJK3BvhU8rEd4mC9UsmDi5qscv/kzGPhBRC5WA=", + "lastModified": 1792502884, + "narHash": "sha256-IoNisKcGbUjdd45qCqywGMCYmZ5EnkxezvLII0FWwNA=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "9fbb54b33e91ee4ca368e35a78e0613c720600b3", + "rev": "02c087f3fe673c9e317f206a627cb6b910336dbe", "type": "github" }, "original": { diff --git a/flake.nix b/flake.nix index 252139a..d874bf3 100644 --- a/flake.nix +++ b/flake.nix @@ -5,22 +5,30 @@ nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; }; - outputs = { self, nixpkgs }: + outputs = + { self, nixpkgs }: let - systems = [ "x86_64-linux" "aarch64-linux" ]; + systems = [ + "x86_64-linux" + "aarch64-linux" + ]; forAllSystems = nixpkgs.lib.genAttrs systems; - package = pkgs: + package = + pkgs: let inherit (pkgs) zig libssh pkg-config; in pkgs.stdenv.mkDerivation { pname = "hex"; - version = "0.0.0"; + version = "0.0.1"; src = self; meta.mainProgram = "demo"; - nativeBuildInputs = [ zig pkg-config ]; + nativeBuildInputs = [ + zig + pkg-config + ]; buildInputs = [ libssh ]; @@ -41,16 +49,24 @@ default = package nixpkgs.legacyPackages.${system}; }); - devShells = forAllSystems (system: - let pkgs = nixpkgs.legacyPackages.${system}; - in { + devShells = forAllSystems ( + system: + let + pkgs = nixpkgs.legacyPackages.${system}; + in + { default = pkgs.mkShell { name = "hex"; shellHook = '' export name=hex ''; - packages = [ pkgs.zig pkgs.pkg-config pkgs.libssh ]; + packages = [ + pkgs.zig + pkgs.pkg-config + pkgs.libssh + ]; }; - }); + } + ); }; } diff --git a/leaves/FilePicker.zig b/leaves/FilePicker.zig index ead48e5..85b5914 100644 --- a/leaves/FilePicker.zig +++ b/leaves/FilePicker.zig @@ -310,7 +310,7 @@ fn readDir(self: *FilePicker, allocator: Allocator, io: Io) !void { var mode: u32 = 0; if (dir.statFile(io, e.name, .{ .follow_symlinks = false })) |s| { size = s.size; - mode = @intCast(@intFromEnum(s.permissions)); + mode = @intCast(@backingInt(s.permissions)); } else |_| {} try self.entries.append(allocator, .{ .name = name_owned, diff --git a/leaves/TextInput.zig b/leaves/TextInput.zig index 0258b5b..7a9dea2 100644 --- a/leaves/TextInput.zig +++ b/leaves/TextInput.zig @@ -34,10 +34,10 @@ pub const ValidateFn = *const fn (value: []const u21) bool; pub const KeyMap = struct { char_forward: key.Binding = .{ .keys = &.{ key.special(.right), key.ctrl('f') } }, char_backward: key.Binding = .{ .keys = &.{ key.special(.left), key.ctrl('b') } }, - word_forward: key.Binding = .{ .keys = &.{ key.alt('f') } }, - word_backward: key.Binding = .{ .keys = &.{ key.alt('b') } }, - delete_word_backward: key.Binding = .{ .keys = &.{ key.ctrl('w') } }, - delete_word_forward: key.Binding = .{ .keys = &.{ key.alt('d') } }, + word_forward: key.Binding = .{ .keys = &.{key.alt('f')} }, + word_backward: key.Binding = .{ .keys = &.{key.alt('b')} }, + delete_word_backward: key.Binding = .{ .keys = &.{key.ctrl('w')} }, + delete_word_forward: key.Binding = .{ .keys = &.{key.alt('d')} }, delete_after_cursor: key.Binding = .{ .keys = &.{key.ctrl('k')} }, delete_before_cursor: key.Binding = .{ .keys = &.{key.ctrl('u')} }, delete_char_backward: key.Binding = .{ .keys = &.{ key.special(.backspace), key.ctrl('h') } }, diff --git a/leaves/Textarea.zig b/leaves/Textarea.zig index eb550a6..145f010 100644 --- a/leaves/Textarea.zig +++ b/leaves/Textarea.zig @@ -25,8 +25,8 @@ const Textarea = @This(); pub const KeyMap = struct { char_forward: key.Binding = .{ .keys = &.{ key.special(.right), key.ctrl('f') } }, char_backward: key.Binding = .{ .keys = &.{ key.special(.left), key.ctrl('b') } }, - word_forward: key.Binding = .{ .keys = &.{ key.alt('f') } }, - word_backward: key.Binding = .{ .keys = &.{ key.alt('b') } }, + word_forward: key.Binding = .{ .keys = &.{key.alt('f')} }, + word_backward: key.Binding = .{ .keys = &.{key.alt('b')} }, line_next: key.Binding = .{ .keys = &.{ key.special(.down), key.ctrl('n') } }, line_previous: key.Binding = .{ .keys = &.{ key.special(.up), key.ctrl('p') } }, delete_word_backward: key.Binding = .{ .keys = &.{key.ctrl('w')} }, @@ -40,8 +40,8 @@ pub const KeyMap = struct { line_end: key.Binding = .{ .keys = &.{ key.special(.end), key.ctrl('e') } }, page_up: key.Binding = .{ .keys = &.{key.special(.page_up)} }, page_down: key.Binding = .{ .keys = &.{key.special(.page_down)} }, - input_begin: key.Binding = .{ .keys = &.{ key.alt('<') } }, - input_end: key.Binding = .{ .keys = &.{ key.alt('>') } }, + input_begin: key.Binding = .{ .keys = &.{key.alt('<')} }, + input_end: key.Binding = .{ .keys = &.{key.alt('>')} }, paste: key.Binding = .{ .keys = &.{key.ctrl('v')} }, transpose_char_backward: key.Binding = .{ .keys = &.{key.ctrl('t')} }, uppercase_word_forward: key.Binding = .{ .keys = &.{key.alt('u')} }, diff --git a/leaves/Tree.zig b/leaves/Tree.zig index b4bb455..bb5ea3e 100644 --- a/leaves/Tree.zig +++ b/leaves/Tree.zig @@ -498,18 +498,7 @@ fn updateViewport(self: *Tree, movement: i32) void { pub fn update(self: *Tree, msg: Msg) ?Cmd { switch (msg) { .key_press => |k| { - if (self.key_map.down.matches(k)) self.down() - else if (self.key_map.up.matches(k)) self.up() - else if (self.key_map.page_down.matches(k)) self.pageDown() - else if (self.key_map.page_up.matches(k)) self.pageUp() - else if (self.key_map.half_page_down.matches(k)) self.halfPageDown() - else if (self.key_map.half_page_up.matches(k)) self.halfPageUp() - else if (self.key_map.goto_top.matches(k)) self.gotoTop() - else if (self.key_map.goto_bottom.matches(k)) self.gotoBottom() - else if (self.key_map.toggle.matches(k)) self.toggleCurrentNode() - else if (self.key_map.open.matches(k)) self.openCurrentNode() - else if (self.key_map.close.matches(k)) self.closeCurrentNode() - else if (self.key_map.show_full_help.matches(k) or self.key_map.close_full_help.matches(k)) { + if (self.key_map.down.matches(k)) self.down() else if (self.key_map.up.matches(k)) self.up() else if (self.key_map.page_down.matches(k)) self.pageDown() else if (self.key_map.page_up.matches(k)) self.pageUp() else if (self.key_map.half_page_down.matches(k)) self.halfPageDown() else if (self.key_map.half_page_up.matches(k)) self.halfPageUp() else if (self.key_map.goto_top.matches(k)) self.gotoTop() else if (self.key_map.goto_bottom.matches(k)) self.gotoBottom() else if (self.key_map.toggle.matches(k)) self.toggleCurrentNode() else if (self.key_map.open.matches(k)) self.openCurrentNode() else if (self.key_map.close.matches(k)) self.closeCurrentNode() else if (self.key_map.show_full_help.matches(k) or self.key_map.close_full_help.matches(k)) { self.help.show_all = !self.help.show_all; self.renderFull(); } @@ -874,7 +863,7 @@ test "tree down past collapsed set keeps correct selection order" { // occupy a single selectable row. Walking down in preorder should then // land on "carlos", then descend into its subtree, then "maas". const expected = [_][]const u8{ - "~/charm", "ayman", "bash", "carlos", "emotes", + "~/charm", "ayman", "bash", "carlos", "emotes", "chefkiss.png", "kekw.png", "maas", }; tree.y_offset = 0; diff --git a/matcha/input.zig b/matcha/input.zig index 67035cb..934bc07 100644 --- a/matcha/input.zig +++ b/matcha/input.zig @@ -97,13 +97,13 @@ fn parseMouseSgr(buf: []const u8) ParseError!Parsed { const button: Msg.Mouse.Button = blk: { if (cb & bit_add != 0) { // Buttons 8-11 → backward, forward, extra_10, extra_11. - break :blk @enumFromInt(@intFromEnum(Msg.Mouse.Button.backward) + (cb & bits_mask)); + break :blk @fromBackingInt(@intCast(@backingInt(Msg.Mouse.Button.backward) + (cb & bits_mask))); } if (cb & bit_wheel != 0) { is_wheel = true; - break :blk @enumFromInt(@intFromEnum(Msg.Mouse.Button.wheel_up) + (cb & bits_mask)); + break :blk @fromBackingInt(@intCast(@backingInt(Msg.Mouse.Button.wheel_up) + (cb & bits_mask))); } - break :blk @enumFromInt(@intFromEnum(Msg.Mouse.Button.left) + (cb & bits_mask)); + break :blk @fromBackingInt(@intCast(@backingInt(Msg.Mouse.Button.left) + (cb & bits_mask))); }; const is_motion = (cb & bit_motion) != 0 and !is_wheel; @@ -248,7 +248,7 @@ fn vkToKind(vk: u16) Msg.Key.Kind { 0x28 => .down, 0x2d => .insert, 0x2e => .delete, - 0x70...0x83 => |v| @enumFromInt(@intFromEnum(Msg.Key.Kind.f1) + (v - 0x70)), + 0x70...0x83 => |v| @fromBackingInt(@intCast(@backingInt(Msg.Key.Kind.f1) + (v - 0x70))), else => .rune, }; } diff --git a/matcha/platform/posix.zig b/matcha/platform/posix.zig index 7b60057..0ec7868 100644 --- a/matcha/platform/posix.zig +++ b/matcha/platform/posix.zig @@ -210,8 +210,8 @@ pub const Tty = struct { raw.cflag.CSIZE = .CS8; raw.cflag.PARENB = false; // Read at least 1 byte, no inter-byte timeout. - raw.cc[@intFromEnum(posix.V.MIN)] = 1; - raw.cc[@intFromEnum(posix.V.TIME)] = 0; + raw.cc[@backingInt(posix.V.MIN)] = 1; + raw.cc[@backingInt(posix.V.TIME)] = 0; try posix.tcsetattr(fd, .FLUSH, raw); } }; diff --git a/zli/Cmd.zig b/zli/Cmd.zig index 049d24a..4daaab1 100644 --- a/zli/Cmd.zig +++ b/zli/Cmd.zig @@ -5,6 +5,7 @@ const ArenaAllocator = std.heap.ArenaAllocator; const Init = std.process.Init; const Cmd = @This(); const utils = @import("utils.zig"); +const Type = std.lang.Type; const Handler = *const fn (Init, []const []const u8) anyerror!void; fn displayName(comptime T: type) []const u8 { @@ -29,11 +30,15 @@ description: []const u8 = "", /// function's parameters. pub inline fn cmd(comptime Fn: anytype, comptime description: []const u8) Cmd { const FnType = @TypeOf(Fn); - const fn_params = @typeInfo(FnType).@"fn".params; + const type_info: Type = @typeInfo(FnType); + const fn_params = switch (type_info) { + .@"fn" => |f| f.param_types, + else => @panic("Fn must be a function"), + }; comptime var cli_count: usize = 0; inline for (fn_params) |fp| { - const T = fp.type orelse @compileError("command function parameters must have explicit types"); + const T = fp orelse @compileError("command function parameters must have explicit types"); if (T == Init or T == Allocator) continue; if (!isParamType(T)) @compileError("unsupported parameter type: " ++ @typeName(T) ++ " (expected cli.Param)"); @@ -43,7 +48,7 @@ pub inline fn cmd(comptime Fn: anytype, comptime description: []const u8) Cmd { comptime var param_list: [cli_count]Command.Param = undefined; comptime var idx: usize = 0; inline for (fn_params, 0..) |fp, fi| { - const T = fp.type.?; + const T = fp.?; if (T == Init or T == Allocator) continue; param_list[idx] = .{ .name = T.param_name, @@ -151,7 +156,11 @@ pub const Diag = struct { /// argument errors can be reported before the command runs. fn wrap(comptime f: anytype) type { const Fn = @TypeOf(f); - const fn_params = @typeInfo(Fn).@"fn".params; + const type_info: Type = @typeInfo(Fn); + const fn_params = switch (type_info) { + .@"fn" => |func| func.param_types, + else => @panic("f must be a function"), + }; return struct { fn parseAll(init: Init, raw_args: []const []const u8, diag: ?*Diag) anyerror!ArgsTuple(Fn) { var args: ArgsTuple(Fn) = undefined; @@ -161,7 +170,7 @@ fn wrap(comptime f: anytype) type { var cli_index: usize = 0; inline for (fn_params, 0..) |fp, i| { - const T = fp.type.?; + const T = fp.?; if (T == Init) { args[i] = init; continue; @@ -206,14 +215,15 @@ fn wrap(comptime f: anytype) type { } fn parseArg(comptime T: type, arena: Allocator, raw: []const u8) !T { - switch (@typeInfo(T)) { + const type_info: Type = @typeInfo(T); + switch (type_info) { .optional => |o| { if (raw.len == 0) return null; return try parseArg(o.child, arena, raw); }, .pointer => |p| { if (p.size != .slice) @compileError("only slice pointers supported: " ++ @typeName(T)); - if (p.child == u8 and p.is_const) return raw; + if (p.child == u8 and p.attrs.@"const") return raw; return try parseList(T, arena, raw); }, .int => return std.fmt.parseInt(T, raw, 10), @@ -227,28 +237,30 @@ fn parseArg(comptime T: type, arena: Allocator, raw: []const u8) !T { if (std.mem.startsWith(u8, s, lower)) return false; return error.InvalidBoolean; }, - .@"enum" => { + .@"enum" => |e| { var partial: ?T = null; - inline for (std.meta.fields(T)) |field| { - if (std.mem.eql(u8, field.name, raw)) - return @enumFromInt(field.value); - if (std.mem.startsWith(u8, field.name, raw)) { + inline for (e.field_names, e.field_values) |name, value| { + if (std.mem.eql(u8, name, raw)) + return @fromBackingInt(@intCast(value)); + if (std.mem.startsWith(u8, name, raw)) { if (partial != null) return error.AmbiguousEnumValue; - partial = @enumFromInt(field.value); + partial = @fromBackingInt(@intCast(value)); } } return partial orelse error.InvalidEnumTag; }, else => { if (comptime @hasDecl(T, "parse")) { - const parse_fn = @typeInfo(@TypeOf(T.parse)).@"fn"; - if (parse_fn.params.len == 1) { + const parse_info: Type = @typeInfo(T.parse); + const parse_fn = switch (parse_info) { + .@"fn" => |func| func, + else => @panic("WTF are you doing"), + }; + if (parse_fn.param_types.len == 1) return try T.parse(raw); - } else if (parse_fn.params.len == 2) { + if (parse_fn.param_types.len == 2) return try T.parse(arena, raw); - } else { - @compileError("parse function must take ([]const u8) or (Allocator, []const u8)"); - } + @compileError("parse function must take ([]const u8) or (Allocator, []const u8)"); } @compileError("unsupported argument type: " ++ @typeName(T)); }, -- knotmirror 0.0.1