diff --git a/Makefile b/Makefile index eb854c6..d5935c6 100644 --- a/Makefile +++ b/Makefile @@ -9,7 +9,7 @@ ZIG ?= zig PREFIX ?= /usr/local BINDIR ?= ${PREFIX}/bin MANDIR ?= ${PREFIX}/share/man/man1 -ZIG_FLAGS ?= -Drelease-fast +ZIG_FLAGS ?= -Doptimize=ReleaseFast NCDU_VERSION=$(shell grep 'program_version = "' src/main.zig | sed -e 's/^.*"\(.\+\)".*$$/\1/') @@ -80,7 +80,7 @@ static-%.tar.gz: @# Alternative approach, bypassing zig-build cd static-$* && zig build-exe -target $*\ -Iinst/include -Iinst/include/ncursesw -lc inst/lib/libncursesw.a\ - --cache-dir zig-cache -static --strip -O ReleaseFast ../src/main.zig ../src/ncurses_refs.c + --cache-dir zig-cache -static -fstrip -O ReleaseFast ../src/main.zig ../src/ncurses_refs.c cd static-$* && mv main ncdu && tar -czf ../static-$*.tar.gz ncdu rm -rf static-$* diff --git a/README.md b/README.md index 6c4bb6b..050a639 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ C version (1.x). ## Requirements -- Zig 0.10.0 or 0.10.1 +- Zig 0.11.0 - Some sort of POSIX-like OS - ncurses libraries and header files diff --git a/build.zig b/build.zig index f475023..c1df229 100644 --- a/build.zig +++ b/build.zig @@ -3,27 +3,29 @@ const std = @import("std"); -pub fn build(b: *std.build.Builder) void { +pub fn build(b: *std.Build) void { const target = b.standardTargetOptions(.{}); - const mode = b.standardReleaseOptions(); + const optimize = b.standardOptimizeOption(.{}); const pie = b.option(bool, "pie", "Build with PIE support (by default false)") orelse false; - const exe = b.addExecutable("ncdu", "src/main.zig"); - exe.setTarget(target); - exe.setBuildMode(mode); + const exe = b.addExecutable(.{ + .name = "ncdu", + .root_source_file = .{ .path = "src/main.zig" }, + .target = target, + .optimize = optimize, + }); + // https://github.com/ziglang/zig/blob/b52be973dfb7d1408218b8e75800a2da3dc69108/build.zig#L551-L554 if (exe.target.isDarwin()) { // useful for package maintainers exe.headerpad_max_install_names = true; } - exe.addCSourceFile("src/ncurses_refs.c", &[_][]const u8{}); - exe.linkLibC(); - exe.linkSystemLibrary("ncursesw"); + linkNcurses(exe); exe.pie = pie; - exe.install(); + b.installArtifact(exe); - const run_cmd = exe.run(); + const run_cmd = b.addRunArtifact(exe); run_cmd.step.dependOn(b.getInstallStep()); if (b.args) |args| { run_cmd.addArgs(args); @@ -32,11 +34,22 @@ pub fn build(b: *std.build.Builder) void { const run_step = b.step("run", "Run the app"); run_step.dependOn(&run_cmd.step); - const tst = b.addTest("src/main.zig"); - tst.linkLibC(); - tst.linkSystemLibrary("ncursesw"); - tst.addCSourceFile("src/ncurses_refs.c", &[_][]const u8{}); - tst.pie = pie; - const tst_step = b.step("test", "Run tests"); - tst_step.dependOn(&tst.step); + const unit_tests = b.addTest(.{ + .root_source_file = .{ .path = "src/main.zig" }, + .target = target, + .optimize = optimize, + }); + linkNcurses(unit_tests); + unit_tests.pie = pie; + + const run_unit_tests = b.addRunArtifact(unit_tests); + + const test_step = b.step("test", "Run unit tests"); + test_step.dependOn(&run_unit_tests.step); +} + +pub fn linkNcurses(compile_step: *std.Build.CompileStep) void { + compile_step.linkSystemLibrary("ncursesw"); + compile_step.linkLibC(); + compile_step.addCSourceFile(.{ .file = .{ .path = "src/ncurses_refs.c" }, .flags = &.{} }); } diff --git a/src/browser.zig b/src/browser.zig index 65e48e8..2b8c169 100644 --- a/src/browser.zig +++ b/src/browser.zig @@ -44,15 +44,15 @@ const View = struct { fn save(self: *@This()) void { self.cursor_hash = if (dir_items.items.len == 0) 0 else hashEntry(dir_items.items[cursor_idx]); - opened_dir_views.put(@ptrToInt(dir_parent), self.*) catch {}; + opened_dir_views.put(@intFromPtr(dir_parent), self.*) catch {}; } // Should be called after dir_parent or dir_items has changed, will load the last saved view and find the proper cursor_idx. fn load(self: *@This(), sel: ?*const model.Entry) void { - if (opened_dir_views.get(@ptrToInt(dir_parent))) |v| self.* = v + if (opened_dir_views.get(@intFromPtr(dir_parent))) |v| self.* = v else self.* = @This(){}; cursor_idx = 0; - for (dir_items.items) |e, i| { + for (dir_items.items, 0..) |e, i| { if (if (sel != null) e == sel else self.cursor_hash == hashEntry(e)) { cursor_idx = i; break; @@ -64,7 +64,7 @@ const View = struct { var current_view = View{}; // Directories the user has browsed to before, and which item was last selected. -// The key is the @ptrToInt() of the opened *Dir; An int because the pointer +// The key is the @intFromPtr() of the opened *Dir; An int because the pointer // itself may have gone stale after deletion or refreshing. They're only for // lookups, not dereferencing. var opened_dir_views = std.AutoHashMap(usize, View).init(main.allocator); @@ -117,7 +117,7 @@ fn sortDir(next_sel: ?*const model.Entry) void { // No need to sort the first item if that's the parent dir reference, // excluding that allows sortLt() to ignore null values. const lst = dir_items.items[(if (dir_items.items.len > 0 and dir_items.items[0] == null) @as(usize, 1) else 0)..]; - std.sort.sort(?*model.Entry, lst, @as(void, undefined), sortLt); + std.mem.sort(?*model.Entry, lst, {}, sortLt); current_view.load(next_sel); } @@ -134,7 +134,7 @@ pub fn loadDir(next_sel: ?*const model.Entry) void { if (dir_parent != model.root) dir_items.append(null) catch unreachable; var it = dir_parent.sub; - while (it) |e| { + while (it) |e| : (it = e.next) { if (e.pack.blocks > dir_max_blocks) dir_max_blocks = e.pack.blocks; if (e.size > dir_max_size) dir_max_size = e.size; const shown = main.config.show_hidden or blk: { @@ -146,7 +146,6 @@ pub fn loadDir(next_sel: ?*const model.Entry) void { dir_items.append(e) catch unreachable; if (e.dir()) |d| if (d.shared_blocks > 0 or d.shared_size > 0) { dir_has_shared = true; }; } - it = e.next; } sortDir(next_sel); } @@ -203,7 +202,7 @@ const Row = struct { fn graph(self: *Self) void { if ((!main.config.show_graph and !main.config.show_percent) or self.col + 20 > ui.cols) return; - const bar_size = std.math.max(ui.cols/7, 10); + const bar_size = @max(ui.cols/7, 10); defer self.col += 3 + (if (main.config.show_graph) bar_size else 0) + (if (main.config.show_percent) @as(u32, 6) else 0) @@ -216,8 +215,8 @@ const Row = struct { if (main.config.show_percent) { self.bg.fg(.num); ui.addprint("{d:>5.1}", .{ 100 * - if (main.config.show_blocks) @intToFloat(f32, item.pack.blocks) / @intToFloat(f32, std.math.max(1, dir_parent.entry.pack.blocks)) - else @intToFloat(f32, item.size) / @intToFloat(f32, std.math.max(1, dir_parent.entry.size)) + if (main.config.show_blocks) @as(f32, @floatFromInt(item.pack.blocks)) / @as(f32, @floatFromInt(@max(1, dir_parent.entry.pack.blocks))) + else @as(f32, @floatFromInt(item.size)) / @as(f32, @floatFromInt(@max(1, dir_parent.entry.size))) }); self.bg.fg(.default); ui.addch('%'); @@ -230,11 +229,11 @@ const Row = struct { max *= bar_size; num *= bar_size; } + const perblock = std.math.divFloor(u64, max, bar_size) catch unreachable; - var i: u32 = 0; self.bg.fg(.graph); - while (i < bar_size) : (i += 1) { - const frac = std.math.min(@as(usize, 8), (num *| 8) / perblock); + for (0..bar_size) |_| { + const frac = @min(@as(usize, 8), (num *| 8) / perblock); ui.addstr(switch (main.config.graph_style) { .hash => ([_][:0]const u8{ " ", " ", " ", " ", " ", " ", " ", " ", "#" })[frac], .half => ([_][:0]const u8{ " ", " ", " ", " ", "▌", "▌", "▌", "▌", "█" })[frac], @@ -261,11 +260,11 @@ const Row = struct { } else if (n < 100_000) ui.addnum(self.bg, n) else if (n < 1000_000) { - ui.addprint("{d:>5.1}", .{ @intToFloat(f32, n) / 1000 }); + ui.addprint("{d:>5.1}", .{ @as(f32, @floatFromInt(n)) / 1000 }); self.bg.fg(.default); ui.addch('k'); } else if (n < 1000_000_000) { - ui.addprint("{d:>5.1}", .{ @intToFloat(f32, n) / 1000_000 }); + ui.addprint("{d:>5.1}", .{ @as(f32, @floatFromInt(n)) / 1000_000 }); self.bg.fg(.default); ui.addch('M'); } else { @@ -384,8 +383,8 @@ const info = struct { // TODO: Zig's sort() implementation is type-generic and not very // small. I suspect we can get a good save on our binary size by using // a smaller or non-generic sort. This doesn't have to be very fast. - std.sort.sort(*model.Link, list.items, @as(void, undefined), lt); - for (list.items) |n,i| if (&n.entry == e) { links_idx = i; }; + std.mem.sort(*model.Link, list.items, {}, lt); + for (list.items, 0..) |n,i| if (&n.entry == e) { links_idx = i; }; links = list; } } @@ -395,8 +394,7 @@ const info = struct { if (links_idx < links_top) links_top = links_idx; if (links_idx >= links_top + numrows) links_top = links_idx - numrows + 1; - var i: u32 = 0; - while (i < numrows) : (i += 1) { + for (0..numrows) |i| { if (i + links_top >= links.?.items.len) break; const e = links.?.items[i+links_top]; ui.style(if (i+links_top == links_idx) .sel else .default); @@ -625,7 +623,7 @@ const help = struct { var i = offset*2; while (i < (offset + keylines)*2) : (i += 2) { line += 1; - box.move(line, 13 - @intCast(u32, keys[i].len)); + box.move(line, 13 - @as(u32, @intCast(keys[i].len))); ui.style(.key); ui.addstr(keys[i]); ui.style(.default); @@ -657,15 +655,12 @@ const help = struct { } fn drawAbout(box: ui.Box) void { - for (logo) |s, n| { - box.move(@intCast(u32, n)+3, 12); + for (logo, 0..) |s, n| { + box.move(@as(u32, @intCast(n+3)), 12); var i: u5 = 28; - while (true) { + while (i != 0) : (i -= 1) { ui.style(if (s & (@as(u29,1)< 0) .sel else .default); ui.addch(' '); - if (i == 0) - break; - i -= 1; } } ui.style(.default); @@ -823,7 +818,7 @@ fn keyInputSelection(ch: i32, idx: *usize, len: usize, page: u32) bool { ui.c.KEY_HOME => idx.* = 0, ui.c.KEY_END, ui.c.KEY_LL => idx.* = len -| 1, ui.c.KEY_PPAGE => idx.* = idx.* -| page, - ui.c.KEY_NPAGE => idx.* = std.math.min(len -| 1, idx.* + page), + ui.c.KEY_NPAGE => idx.* = @min(len -| 1, idx.* + page), else => return false, } return true; diff --git a/src/main.zig b/src/main.zig index 2e253b4..60ec8eb 100644 --- a/src/main.zig +++ b/src/main.zig @@ -13,15 +13,25 @@ const util = @import("util.zig"); const exclude = @import("exclude.zig"); const c = @cImport(@cInclude("locale.h")); +test "imports" { + _ = model; + _ = scan; + _ = ui; + _ = browser; + _ = delete; + _ = util; + _ = exclude; +} + // "Custom" allocator that wraps the libc allocator and calls ui.oom() on error. // This allocator never returns an error, it either succeeds or causes ncdu to quit. // (Which means you'll find a lot of "catch unreachable" sprinkled through the code, // they look scarier than they are) -fn wrapAlloc(_: *anyopaque, len: usize, alignment: u29, len_align: u29, return_address: usize) error{OutOfMemory}![]u8 { +fn wrapAlloc(_: *anyopaque, len: usize, ptr_alignment: u8, return_address: usize) ?[*]u8 { while (true) { - if (std.heap.c_allocator.vtable.alloc(undefined, len, alignment, len_align, return_address)) |r| + if (std.heap.c_allocator.vtable.alloc(undefined, len, ptr_alignment, return_address)) |r| return r - else |_| {} + else {} ui.oom(); } } @@ -264,19 +274,26 @@ fn tryReadArgsFile(path: [:0]const u8) void { defer f.close(); var arglist = std.ArrayList([:0]const u8).init(allocator); + var rd_ = std.io.bufferedReader(f.reader()); - var rd = rd_.reader(); - var linebuf: [4096]u8 = undefined; + const rd = rd_.reader(); + + var line_buf: [4096]u8 = undefined; + var line_fbs = std.io.fixedBufferStream(&line_buf); + const line_writer = line_fbs.writer(); - while ( - rd.readUntilDelimiterOrEof(&linebuf, '\n') - catch |e| ui.die("Error reading from {s}: {s}\nRun with --ignore-config to skip reading config files.\n", .{ path, ui.errorString(e) }) - ) |line_| { - var line = std.mem.trim(u8, line_, &std.ascii.spaces); + while (true) : (line_fbs.reset()) { + rd.streamUntilDelimiter(line_writer, '\n', line_buf.len) catch |err| switch (err) { + error.EndOfStream => if (line_fbs.getPos() catch unreachable == 0) break, + else => |e| ui.die("Error reading from {s}: {s}\nRun with --ignore-config to skip reading config files.\n", .{ path, ui.errorString(e) }), + }; + var line_ = line_fbs.getWritten(); + + var line = std.mem.trim(u8, line_, &std.ascii.whitespace); if (line.len == 0 or line[0] == '#') continue; if (std.mem.indexOfAny(u8, line, " \t=")) |i| { arglist.append(allocator.dupeZ(u8, line[0..i]) catch unreachable) catch unreachable; - line = std.mem.trimLeft(u8, line[i+1..], &std.ascii.spaces); + line = std.mem.trimLeft(u8, line[i+1..], &std.ascii.whitespace); } arglist.append(allocator.dupeZ(u8, line) catch unreachable) catch unreachable; } @@ -291,12 +308,14 @@ fn tryReadArgsFile(path: [:0]const u8) void { } fn version() noreturn { - std.io.getStdOut().writer().writeAll("ncdu " ++ program_version ++ "\n") catch {}; + const stdout = std.io.getStdOut(); + stdout.writeAll("ncdu " ++ program_version ++ "\n") catch {}; std.process.exit(0); } fn help() noreturn { - std.io.getStdOut().writer().writeAll( + const stdout = std.io.getStdOut(); + stdout.writeAll( \\ncdu \\ \\Options: @@ -339,7 +358,7 @@ fn spawnShell() void { // NCDU_LEVEL can only count to 9, keeps the implementation simple. if (env.get("NCDU_LEVEL")) |l| env.put("NCDU_LEVEL", if (l.len == 0) "1" else switch (l[0]) { - '0'...'8' => @as([]const u8, &.{l[0]+1}), + '0'...'8' => |d| &[1] u8{d+1}, '9' => "9", else => "1" }) catch unreachable @@ -347,17 +366,19 @@ fn spawnShell() void { env.put("NCDU_LEVEL", "1") catch unreachable; const shell = std.os.getenvZ("NCDU_SHELL") orelse std.os.getenvZ("SHELL") orelse "/bin/sh"; - var child = std.ChildProcess.init(&.{shell}, allocator); + var child = std.process.Child.init(&.{shell}, allocator); child.cwd = path.items; child.env_map = &env; + const stdin = std.io.getStdIn(); + const stderr = std.io.getStdErr(); const term = child.spawnAndWait() catch |e| blk: { - _ = std.io.getStdErr().writer().print( + stderr.writer().print( "Error spawning shell: {s}\n\nPress enter to continue.\n", .{ ui.errorString(e) } ) catch {}; - _ = std.io.getStdIn().reader().skipUntilDelimiterOrEof('\n') catch unreachable; - break :blk std.ChildProcess.Term{ .Exited = 0 }; + stdin.reader().skipUntilDelimiterOrEof('\n') catch unreachable; + break :blk std.process.Child.Term{ .Exited = 0 }; }; if (term != .Exited) { const n = switch (term) { @@ -372,10 +393,10 @@ fn spawnShell() void { .Stopped => |v| v, .Unknown => |v| v, }; - _ = std.io.getStdErr().writer().print( + stderr.writer().print( "Shell returned with {s} code {}.\n\nPress enter to continue.\n", .{ n, v } ) catch {}; - _ = std.io.getStdIn().reader().skipUntilDelimiterOrEof('\n') catch unreachable; + stdin.reader().skipUntilDelimiterOrEof('\n') catch unreachable; } } @@ -383,15 +404,22 @@ fn spawnShell() void { fn readExcludeFile(path: [:0]const u8) !void { const f = try std.fs.cwd().openFileZ(path, .{}); defer f.close(); + var rd_ = std.io.bufferedReader(f.reader()); - var rd = rd_.reader(); - var buf = std.ArrayList(u8).init(allocator); - defer buf.deinit(); - while (true) { - rd.readUntilDelimiterArrayList(&buf, '\n', 4096) - catch |e| if (e != error.EndOfStream) return e else if (buf.items.len == 0) break; - if (buf.items.len > 0) - exclude.addPattern(util.arrayListBufZ(&buf)); + const rd = rd_.reader(); + + var line_buf: [4096]u8 = undefined; + var line_fbs = std.io.fixedBufferStream(&line_buf); + const line_writer = line_fbs.writer(); + + while (true) : (line_fbs.reset()) { + rd.streamUntilDelimiter(line_writer, '\n', line_buf.len) catch |err| switch (err) { + error.EndOfStream => if (line_fbs.getPos() catch unreachable == 0) break, + else => |e| return e, + }; + const line = line_fbs.getWritten(); + if (line.len > 0) + exclude.addPattern(line); } } @@ -459,8 +487,10 @@ pub fn main() void { if (@import("builtin").os.tag != .linux and config.exclude_kernfs) ui.die("The --exclude-kernfs flag is currently only supported on Linux.\n", .{}); - const out_tty = std.io.getStdOut().isTty(); - const in_tty = std.io.getStdIn().isTty(); + const stdin = std.io.getStdIn(); + const stdout = std.io.getStdOut(); + const out_tty = stdout.isTty(); + const in_tty = stdin.isTty(); if (config.scan_ui == null) { if (export_file) |f| { if (!out_tty or std.mem.eql(u8, f, "-")) config.scan_ui = .none @@ -475,7 +505,7 @@ pub fn main() void { defer ui.deinit(); var out_file = if (export_file) |f| ( - if (std.mem.eql(u8, f, "-")) std.io.getStdOut() + if (std.mem.eql(u8, f, "-")) stdout else std.fs.cwd().createFileZ(f, .{}) catch |e| ui.die("Error opening export file: {s}.\n", .{ui.errorString(e)}) ) else null; @@ -554,13 +584,6 @@ pub fn handleEvent(block: bool, force_draw: bool) void { } } -test "imports" { - _ = @import("model.zig"); - _ = @import("ui.zig"); - _ = @import("util.zig"); - _ = @import("exclude.zig"); -} - test "argument parser" { const lst = [_][:0]const u8{ "a", "-abcd=e", "--opt1=arg1", "--opt2", "arg2", "-x", "foo", "", "--", "--arg", "", "-", }; const T = struct { diff --git a/src/model.zig b/src/model.zig index e4f098b..bf44638 100644 --- a/src/model.zig +++ b/src/model.zig @@ -50,15 +50,15 @@ pub const Entry = extern struct { const Self = @This(); pub fn dir(self: *Self) ?*Dir { - return if (self.pack.etype == .dir) @ptrCast(*Dir, self) else null; + return if (self.pack.etype == .dir) @ptrCast(self) else null; } pub fn link(self: *Self) ?*Link { - return if (self.pack.etype == .link) @ptrCast(*Link, self) else null; + return if (self.pack.etype == .link) @ptrCast(self) else null; } pub fn file(self: *Self) ?*File { - return if (self.pack.etype == .file) @ptrCast(*File, self) else null; + return if (self.pack.etype == .file) @ptrCast(self) else null; } // Whether this entry should be displayed as a "directory". @@ -68,17 +68,18 @@ pub const Entry = extern struct { } pub fn name(self: *const Self) [:0]const u8 { - const ptr = switch (self.pack.etype) { - .dir => &@ptrCast(*const Dir, self).name, - .link => &@ptrCast(*const Link, self).name, - .file => &@ptrCast(*const File, self).name, + const self_name = switch (self.pack.etype) { + .dir => &@as(*const Dir, @ptrCast(self)).name, + .link => &@as(*const Link, @ptrCast(self)).name, + .file => &@as(*const File, @ptrCast(self)).name, }; - return std.mem.sliceTo(@ptrCast([*:0]const u8, ptr), 0); + const name_ptr: [*:0]const u8 = @ptrCast(self_name); + return std.mem.sliceTo(name_ptr, 0); } pub fn ext(self: *Self) ?*Ext { if (!self.pack.isext) return null; - return @ptrCast(*Ext, @ptrCast([*]Ext, self) - 1); + return @ptrCast(@as([*]Ext, @ptrCast(self)) - 1); } fn alloc(comptime T: type, etype: EType, isext: bool, ename: []const u8) *Entry { @@ -89,13 +90,13 @@ pub const Entry = extern struct { ui.oom(); }; if (isext) { - @ptrCast(*Ext, ptr).* = .{}; + @as(*Ext, @ptrCast(ptr)).* = .{}; ptr = ptr[@sizeOf(Ext)..]; } - const e = @ptrCast(*T, ptr); + const e: *T = @ptrCast(ptr); e.* = .{ .entry = .{ .pack = .{ .etype = etype, .isext = isext } } }; - const n = @ptrCast([*]u8, &e.name)[0..ename.len+1]; - std.mem.copy(u8, n, ename); + const n = @as([*]u8, @ptrCast(&e.name))[0..ename.len+1]; + @memcpy(n[0..ename.len], ename); n[ename.len] = 0; return &e.entry; } @@ -320,7 +321,7 @@ pub const devices = struct { pub fn getId(dev: u64) DevId { var d = lookup.getOrPut(dev) catch unreachable; if (!d.found_existing) { - d.value_ptr.* = @intCast(DevId, list.items.len); + d.value_ptr.* = @as(DevId, @intCast(list.items.len)); list.append(dev) catch unreachable; } return d.value_ptr.*; diff --git a/src/ncurses_refs.c b/src/ncurses_refs.c index c23a5bd..40af7d1 100644 --- a/src/ncurses_refs.c +++ b/src/ncurses_refs.c @@ -25,6 +25,3 @@ chtype ncdu_acs_urcorner() { return ACS_URCORNER; } chtype ncdu_acs_lrcorner() { return ACS_LRCORNER; } chtype ncdu_acs_hline() { return ACS_VLINE ; } chtype ncdu_acs_vline() { return ACS_HLINE ; } - -/* https://github.com/ziglang/zig/issues/8947 */ -void ncdu_init_pair(int a, int b, int c) { init_pair(a,b,c); } diff --git a/src/scan.zig b/src/scan.zig index 76b8fea..95ec78b 100644 --- a/src/scan.zig +++ b/src/scan.zig @@ -23,12 +23,12 @@ const Stat = struct { symlink: bool = false, ext: model.Ext = .{}, - fn clamp(comptime T: type, comptime field: anytype, x: anytype) std.meta.fieldInfo(T, field).field_type { - return util.castClamp(std.meta.fieldInfo(T, field).field_type, x); + fn clamp(comptime T: type, comptime field: anytype, x: anytype) std.meta.fieldInfo(T, field).type { + return util.castClamp(std.meta.fieldInfo(T, field).type, x); } - fn truncate(comptime T: type, comptime field: anytype, x: anytype) std.meta.fieldInfo(T, field).field_type { - return util.castTruncate(std.meta.fieldInfo(T, field).field_type, x); + fn truncate(comptime T: type, comptime field: anytype, x: anytype) std.meta.fieldInfo(T, field).type { + return util.castTruncate(std.meta.fieldInfo(T, field).type, x); } fn read(parent: std.fs.Dir, name: [:0]const u8, follow: bool) !Stat { @@ -787,7 +787,7 @@ const Import = struct { }, 'd' => { if (eq(u8, key, "dsize")) { - self.ctx.stat.blocks = @intCast(model.Blocks, self.uint(u64)>>9); + self.ctx.stat.blocks = @intCast(self.uint(u64)>>9); return; } if (eq(u8, key, "dev")) { @@ -968,7 +968,7 @@ const Import = struct { }; pub fn importRoot(path: [:0]const u8, out: ?std.fs.File) void { - var fd = if (std.mem.eql(u8, "-", path)) std.io.getStdIn() + const fd = if (std.mem.eql(u8, "-", path)) std.io.getStdIn() else std.fs.cwd().openFileZ(path, .{}) catch |e| ui.die("Error reading file: {s}.\n", .{ui.errorString(e)}); defer fd.close(); @@ -1060,9 +1060,8 @@ fn drawBox() void { animation_pos += 1; if (animation_pos >= txt.len*2) animation_pos = 0; if (animation_pos < txt.len) { - var i: u32 = 0; box.move(8, 2); - while (i <= animation_pos) : (i += 1) ui.addch(txt[i]); + for (txt[0..animation_pos + 1]) |t| ui.addch(t); } else { var i: u32 = txt.len-1; while (i > animation_pos-txt.len) : (i -= 1) { @@ -1093,7 +1092,8 @@ pub fn draw() void { .{ ui.shorten(active_context.pathZ(), 51), active_context.items_seen, r.num(), r.unit } ) catch return; } - _ = std.io.getStdErr().write(line) catch {}; + const stderr = std.io.getStdErr(); + stderr.writeAll(line) catch {}; }, .full => drawBox(), } diff --git a/src/ui.zig b/src/ui.zig index 663030a..2678994 100644 --- a/src/ui.zig +++ b/src/ui.zig @@ -24,7 +24,8 @@ pub var cols: u32 = undefined; pub fn die(comptime fmt: []const u8, args: anytype) noreturn { deinit(); - _ = std.io.getStdErr().writer().print(fmt, args) catch {}; + const stderr = std.io.getStdErr(); + stderr.writer().print(fmt, args) catch {}; std.process.exit(1); } @@ -45,7 +46,8 @@ pub fn quit() noreturn { pub fn oom() void { const haveui = inited; deinit(); - _ = std.io.getStdErr().writer().writeAll("\x1b7\x1b[JOut of memory, trying again in 1 second. Hit Ctrl-C to abort.\x1b8") catch {}; + const stderr = std.io.getStdErr(); + stderr.writeAll("\x1b7\x1b[JOut of memory, trying again in 1 second. Hit Ctrl-C to abort.\x1b8") catch {}; std.time.sleep(std.time.ns_per_s); if (haveui) init(); @@ -135,7 +137,7 @@ pub fn shorten(in: [:0]const u8, max_width: u32) [:0] const u8 { // (The "proper" way is to use mbtowc(), but I'd rather port the musl wcwidth implementation to Zig so that I *know* it'll be Unicode. // On the other hand, ncurses also use wcwidth() so that would cause duplicated code. Ugh) const cp_width_ = c.wcwidth(cp); - const cp_width = @intCast(u32, if (cp_width_ < 0) 0 else cp_width_); + const cp_width: u32 = @intCast(if (cp_width_ < 0) 0 else cp_width_); const cp_len = std.unicode.utf8CodepointSequenceLength(cp) catch unreachable; total_width += cp_width; if (!prefix_done and prefix_width + cp_width <= @divFloor(max_width-1, 2)-1) { @@ -155,7 +157,7 @@ pub fn shorten(in: [:0]const u8, max_width: u32) [:0] const u8 { it = std.unicode.Utf8View.initUnchecked(in[prefix_end..]).iterator(); while (it.nextCodepoint()) |cp| { const cp_width_ = c.wcwidth(cp); - const cp_width = @intCast(u32, if (cp_width_ < 0) 0 else cp_width_); + const cp_width: u32 = @intCast(if (cp_width_ < 0) 0 else cp_width_); const cp_len = std.unicode.utf8CodepointSequenceLength(cp) catch unreachable; start_width += cp_width; start_len += cp_len; @@ -197,7 +199,6 @@ extern fn ncdu_acs_urcorner() c.chtype; extern fn ncdu_acs_lrcorner() c.chtype; extern fn ncdu_acs_hline() c.chtype; extern fn ncdu_acs_vline() c.chtype; -extern fn ncdu_init_pair(idx: c_int, fg: c_int, bg: c_int) void; const StyleAttr = struct { fg: i16, bg: i16, attr: u32 }; const StyleDef = struct { @@ -286,20 +287,18 @@ const styles = [_]StyleDef{ }; pub const Style = lbl: { - var fields: [styles.len]std.builtin.Type.EnumField = undefined; - var decls = [_]std.builtin.Type.Declaration{}; - inline for (styles) |s, i| { - fields[i] = .{ + comptime var fields: [styles.len]std.builtin.Type.EnumField = undefined; + inline for (&fields, styles, 0..) |*field, s, i| { + field.* = .{ .name = s.name, .value = i, }; } break :lbl @Type(.{ .Enum = .{ - .layout = .Auto, .tag_type = u8, .fields = &fields, - .decls = &decls, + .decls = &[_]std.builtin.Type.Declaration{}, .is_exhaustive = true, } }); @@ -336,14 +335,15 @@ pub const Bg = enum { fn updateSize() void { // getmax[yx] macros are marked as "legacy", but Zig can't deal with the "proper" getmaxyx macro. - rows = @intCast(u32, c.getmaxy(c.stdscr)); - cols = @intCast(u32, c.getmaxx(c.stdscr)); + rows = @intCast(c.getmaxy(c.stdscr)); + cols = @intCast(c.getmaxx(c.stdscr)); } fn clearScr() void { // Send a "clear from cursor to end of screen" instruction, to clear a // potential line left behind from scanning in -1 mode. - _ = std.io.getStdErr().write("\x1b[J") catch {}; + const stderr = std.io.getStdErr(); + stderr.writeAll("\x1b[J") catch {}; } pub fn init() void { @@ -351,7 +351,7 @@ pub fn init() void { clearScr(); if (main.config.nc_tty) { var tty = c.fopen("/dev/tty", "r+"); - if (tty == null) die("Error opening /dev/tty: {s}.\n", .{ c.strerror(@enumToInt(std.c.getErrno(-1))) }); + if (tty == null) die("Error opening /dev/tty: {s}.\n", .{ c.strerror(@intFromEnum(std.c.getErrno(-1))) }); var term = c.newterm(null, tty, tty); if (term == null) die("Error initializing ncurses.\n", .{}); _ = c.set_term(term); @@ -366,9 +366,8 @@ pub fn init() void { _ = c.start_color(); _ = c.use_default_colors(); - for (styles) |s, i| _ = ncdu_init_pair(@intCast(i16, i+1), s.style().fg, s.style().bg); - _ = c.bkgd(@intCast(c.chtype, c.COLOR_PAIR(@enumToInt(Style.default)+1))); - + for (styles, 0..) |s, i| _ = c.init_pair(@as(i16, @intCast(i+1)), s.style().fg, s.style().bg); + _ = c.bkgd(@intCast(c.COLOR_PAIR(@intFromEnum(Style.default)+1))); inited = true; } @@ -384,11 +383,11 @@ pub fn deinit() void { } pub fn style(s: Style) void { - _ = c.attr_set(styles[@enumToInt(s)].style().attr, @enumToInt(s)+1, null); + _ = c.attr_set(styles[@intFromEnum(s)].style().attr, @intFromEnum(s)+1, null); } pub fn move(y: u32, x: u32) void { - _ = c.move(@intCast(i32, y), @intCast(i32, x)); + _ = c.move(@as(i32, @intCast(y)), @as(i32, @intCast(x))); } // Wraps to the next line if the text overflows, not sure how to disable that. @@ -419,7 +418,7 @@ pub const FmtSize = struct { pub fn fmt(v: u64) @This() { var r: @This() = undefined; - var f = @intToFloat(f32, v); + var f: f32 = @floatFromInt(v); if (main.config.si) { if(f < 1000.0) { r.unit = " B"; } else if(f < 1e6) { r.unit = " KB"; f /= 1e3; } @@ -464,7 +463,7 @@ pub fn addnum(bg: Bg, v: u64) void { const s = std.fmt.bufPrint(&buf, "{d}", .{v}) catch unreachable; var f: [64:0]u8 = undefined; var i: usize = 0; - for (s) |digit, n| { + for (s, 0..) |digit, n| { if (n != 0 and (s.len - n) % 3 == 0) { for (main.config.thousands_sep) |ch| { f[i] = ch; @@ -518,7 +517,7 @@ pub fn addts(bg: Bg, ts: u64) void { } pub fn hline(ch: c.chtype, len: u32) void { - _ = c.hline(ch, @intCast(i32, len)); + _ = c.hline(ch, @as(i32, @intCast(len))); } // Draws a bordered box in the center of the screen. @@ -586,20 +585,19 @@ pub fn getch(block: bool) i32 { // In non-blocking mode, we can only assume that ERR means "no input yet". // In blocking mode, give it 100 tries with a 10ms delay in between, // then just give up and die to avoid an infinite loop and unresponsive program. - var attempts: u8 = 0; - while (attempts < 100) : (attempts += 1) { - var ch = c.getch(); + for (0..100) |_| { + const ch = c.getch(); if (ch == c.KEY_RESIZE) { updateSize(); return -1; } if (ch == c.ERR) { if (!block) return 0; - std.os.nanosleep(0, 10*std.time.ns_per_ms); + std.time.sleep(10*std.time.ns_per_ms); continue; } return ch; } die("Error reading keyboard input, assuming TTY has been lost.\n(Potentially nonsensical error message: {s})\n", - .{ c.strerror(@enumToInt(std.c.getErrno(-1))) }); + .{ c.strerror(@intFromEnum(std.c.getErrno(-1))) }); } diff --git a/src/util.zig b/src/util.zig index fb42348..f0fc1d4 100644 --- a/src/util.zig +++ b/src/util.zig @@ -11,7 +11,7 @@ pub fn castClamp(comptime T: type, x: anytype) T { } else if (std.math.minInt(@TypeOf(x)) < std.math.minInt(T) and x < std.math.minInt(T)) { return std.math.minInt(T); } else { - return @intCast(T, x); + return @intCast(x); } } @@ -19,13 +19,13 @@ pub fn castClamp(comptime T: type, x: anytype) T { pub fn castTruncate(comptime T: type, x: anytype) T { const Ti = @typeInfo(T).Int; const Xi = @typeInfo(@TypeOf(x)).Int; - const nx = if (Xi.signedness != Ti.signedness) @bitCast(std.meta.Int(Ti.signedness, Xi.bits), x) else x; - return if (Xi.bits > Ti.bits) @truncate(T, nx) else nx; + const nx: std.meta.Int(Ti.signedness, Xi.bits) = @bitCast(x); + return if (Xi.bits > Ti.bits) @truncate(nx) else nx; } // Multiplies by 512, saturating. pub fn blocksToSize(b: u64) u64 { - return if (b & 0xFF80000000000000 > 0) std.math.maxInt(u64) else b << 9; + return b *| 512; } // Ensure the given arraylist buffer gets zero-terminated and returns a slice @@ -44,8 +44,8 @@ pub fn strnatcmp(a: [:0]const u8, b: [:0]const u8) std.math.Order { var bi: usize = 0; const isDigit = std.ascii.isDigit; while (true) { - while (std.ascii.isSpace(a[ai])) ai += 1; - while (std.ascii.isSpace(b[bi])) bi += 1; + while (std.ascii.isWhitespace(a[ai])) ai += 1; + while (std.ascii.isWhitespace(b[bi])) bi += 1; if (isDigit(a[ai]) and isDigit(b[bi])) { if (a[ai] == '0' or b[bi] == '0') { // compare_left @@ -133,12 +133,9 @@ test "strnatcmp" { }; // Test each string against each other string, simple and thorough. const eq = std.testing.expectEqual; - var i: usize = 0; - while (i < w.len) : (i += 1) { - var j: usize = 0; + for (0..w.len) |i| { try eq(strnatcmp(w[i], w[i]), .eq); - while (j < i) : (j += 1) try eq(strnatcmp(w[i], w[j]), .gt); - j += 1; - while (j < w.len) : (j += 1) try eq(strnatcmp(w[i], w[j]), .lt); + for (0..i) |j| try eq(strnatcmp(w[i], w[j]), .gt); + for (i+1..w.len) |j| try eq(strnatcmp(w[i], w[j]), .lt); } }