diff --git a/src/browser.zig b/src/browser.zig index 4f450f8cb4a25113920db6dc08613309e4b5bf27..469bc8c7e2f3ab4641f9d81ccd604cd09daadd94 100644 --- a/src/browser.zig +++ b/src/browser.zig @@ -85,8 +85,8 @@ fn sortLt(_: void, ap: ?*model.Entry, bp: ?*model.Entry) bool { if (sortIntLt(a.blocks, b.blocks)) |r| return r; }, .items => { - const ai = if (a.dir()) |d| d.total_items else 0; - const bi = if (b.dir()) |d| d.total_items else 0; + const ai = if (a.dir()) |d| d.items else 0; + const bi = if (b.dir()) |d| d.items else 0; if (sortIntLt(ai, bi)) |r| return r; if (sortIntLt(a.blocks, b.blocks)) |r| return r; if (sortIntLt(a.size, b.size)) |r| return r; @@ -208,11 +208,15 @@ const Row = struct { } if (main.config.show_graph == .both) ui.addch(' '); if (main.config.show_graph == .both or main.config.show_graph == .graph) { - const perblock = std.math.divCeil(u64, if (main.config.show_blocks) dir_max_blocks else dir_max_size, bar_size) catch unreachable; + const perblock = std.math.divFloor(u64, if (main.config.show_blocks) dir_max_blocks else dir_max_size, bar_size) catch unreachable; const num = if (main.config.show_blocks) item.blocks else item.size; var i: u32 = 0; + var siz: u64 = 0; self.bg.fg(.graph); - while (i < bar_size) : (i += 1) ui.addch(if (i*perblock <= num) '#' else ' '); + while (i < bar_size) : (i += 1) { + siz = saturateAdd(siz, perblock); + ui.addch(if (siz <= num) '#' else ' '); + } } self.bg.fg(.default); ui.addch(']'); @@ -221,14 +225,16 @@ const Row = struct { fn items(self: *Self) void { if (!main.config.show_items) return; defer self.col += 7; - const d = if (self.item) |d| d.dir() orelse return else return; - const n = d.total_items; + const n = (if (self.item) |d| d.dir() orelse return else return).items; ui.move(self.row, self.col); self.bg.fg(.num); if (n < 1000) ui.addprint(" {d:>4}", .{n}) - else if (n < 100_000) - ui.addprint("{d:>6.3}", .{ @intToFloat(f32, n) / 1000 }) + else if (n < 10_000) { + ui.addch(' '); + ui.addnum(self.bg, n); + } else if (n < 100_000) + ui.addnum(self.bg, n) else if (n < 1000_000) { ui.addprint("{d:>5.1}", .{ @intToFloat(f32, n) / 1000 }); self.bg.fg(.default); @@ -360,18 +366,20 @@ pub fn draw() !void { ui.move(ui.rows-1, 0); ui.hline(' ', ui.cols); ui.move(ui.rows-1, 1); + ui.style(if (main.config.show_blocks) .bold_hd else .hd); ui.addstr("Total disk usage: "); ui.addsize(.hd, blocksToSize(dir_parents.top().entry.blocks)); + ui.style(if (main.config.show_blocks) .hd else .bold_hd); ui.addstr(" Apparent size: "); ui.addsize(.hd, dir_parents.top().entry.size); ui.addstr(" Items: "); - ui.addnum(.hd, dir_parents.top().total_items); + ui.addnum(.hd, dir_parents.top().items); if (need_confirm_quit) drawQuit(); if (sel_row > 0) ui.move(sel_row, 0); } -fn sortToggle(col: main.SortCol, default_order: main.SortOrder) void { +fn sortToggle(col: main.config.SortCol, default_order: main.config.SortOrder) void { if (main.config.sort_col != col) main.config.sort_order = default_order else if (main.config.sort_order == .asc) main.config.sort_order = .desc else main.config.sort_order = .asc; diff --git a/src/main.zig b/src/main.zig index b34a59236c6e4c4f46884bb7145ddcde552f687f..573b9318329ae08125849dafb8c77caaaf82b24a 100644 --- a/src/main.zig +++ b/src/main.zig @@ -9,40 +9,38 @@ const c = @cImport(@cInclude("locale.h")); pub const allocator = std.heap.c_allocator; -pub const SortCol = enum { name, blocks, size, items, mtime }; -pub const SortOrder = enum { asc, desc }; - -pub const Config = struct { - same_fs: bool = true, - extended: bool = false, - follow_symlinks: bool = false, - exclude_caches: bool = false, - exclude_kernfs: bool = false, - exclude_patterns: std.ArrayList([:0]const u8) = std.ArrayList([:0]const u8).init(allocator), - - update_delay: u64 = 100*std.time.ns_per_ms, - scan_ui: enum { none, line, full } = .full, - si: bool = false, - nc_tty: bool = false, - ui_color: enum { off, dark } = .off, - thousands_sep: []const u8 = ".", - - show_hidden: bool = true, - show_blocks: bool = true, - show_items: bool = false, - show_mtime: bool = false, - show_graph: enum { off, graph, percent, both } = .graph, - sort_col: SortCol = .blocks, - sort_order: SortOrder = .desc, - sort_dirsfirst: bool = false, - - read_only: bool = false, - can_shell: bool = true, - confirm_quit: bool = false, +pub const config = struct { + pub const SortCol = enum { name, blocks, size, items, mtime }; + pub const SortOrder = enum { asc, desc }; + + pub var same_fs: bool = true; + pub var extended: bool = false; + pub var follow_symlinks: bool = false; + pub var exclude_caches: bool = false; + pub var exclude_kernfs: bool = false; + pub var exclude_patterns: std.ArrayList([:0]const u8) = std.ArrayList([:0]const u8).init(allocator); + + pub var update_delay: u64 = 100*std.time.ns_per_ms; + pub var scan_ui: enum { none, line, full } = .full; + pub var si: bool = false; + pub var nc_tty: bool = false; + pub var ui_color: enum { off, dark } = .off; + pub var thousands_sep: []const u8 = ","; + + pub var show_hidden: bool = true; + pub var show_blocks: bool = true; + pub var show_items: bool = false; + pub var show_mtime: bool = false; + pub var show_graph: enum { off, graph, percent, both } = .graph; + pub var sort_col: SortCol = .blocks; + pub var sort_order: SortOrder = .desc; + pub var sort_dirsfirst: bool = false; + + pub var read_only: bool = false; + pub var can_shell: bool = true; + pub var confirm_quit: bool = false; }; -pub var config = Config{}; - pub var state: enum { scan, browse } = .browse; // Simple generic argument parser, supports getopt_long() style arguments. @@ -298,30 +296,30 @@ test "argument parser" { const l = L{ .lst = &lst }; const T = struct { a: Args(L), - fn opt(self: *@This(), isopt: bool, val: []const u8) void { + fn opt(self: *@This(), isopt: bool, val: []const u8) !void { const o = self.a.next().?; - std.testing.expectEqual(isopt, o.opt); - std.testing.expectEqualStrings(val, o.val); - std.testing.expectEqual(o.is(val), isopt); + try std.testing.expectEqual(isopt, o.opt); + try std.testing.expectEqualStrings(val, o.val); + try std.testing.expectEqual(o.is(val), isopt); } - fn arg(self: *@This(), val: []const u8) void { - std.testing.expectEqualStrings(val, self.a.arg()); + fn arg(self: *@This(), val: []const u8) !void { + try std.testing.expectEqualStrings(val, self.a.arg()); } }; var t = T{ .a = Args(L).init(l) }; - t.opt(false, "a"); - t.opt(true, "-a"); - t.opt(true, "-b"); - t.arg("cd=e"); - t.opt(true, "--opt1"); - t.arg("arg1"); - t.opt(true, "--opt2"); - t.arg("arg2"); - t.opt(true, "-x"); - t.arg("foo"); - t.opt(false, ""); - t.opt(false, "--arg"); - t.opt(false, ""); - t.opt(false, "-"); - std.testing.expectEqual(t.a.next(), null); + try t.opt(false, "a"); + try t.opt(true, "-a"); + try t.opt(true, "-b"); + try t.arg("cd=e"); + try t.opt(true, "--opt1"); + try t.arg("arg1"); + try t.opt(true, "--opt2"); + try t.arg("arg2"); + try t.opt(true, "-x"); + try t.arg("foo"); + try t.opt(false, ""); + try t.opt(false, "--arg"); + try t.opt(false, ""); + try t.opt(false, "-"); + try std.testing.expectEqual(t.a.next(), null); } diff --git a/src/model.zig b/src/model.zig index c5ea1ceab0d4ad122ca48daa04cf3ce418295728..31474cfc343c4292756e8ce1e7eb3bf25e812338 100644 --- a/src/model.zig +++ b/src/model.zig @@ -113,6 +113,7 @@ pub const Entry = packed struct { if (self.ext()) |e| if (p.entry.ext()) |pe| if (e.mtime > pe.mtime) { pe.mtime = e.mtime; }; + p.items = saturateAdd(p.items, 1); // Hardlink in a subdirectory with a different device, only count it the first time. if (self.link() != null and dev != p.dev) { @@ -128,12 +129,10 @@ pub const Entry = packed struct { add_total = true; p.shared_size = saturateAdd(p.shared_size, self.size); p.shared_blocks = saturateAdd(p.shared_blocks, self.blocks); - p.shared_items = saturateAdd(p.shared_items, 1); // Encountered this file in this dir the same number of times as its link count, meaning it's not shared with other dirs. } else if(d.entry.key.num_files == l.nlink) { p.shared_size = saturateSub(p.shared_size, self.size); p.shared_blocks = saturateSub(p.shared_blocks, self.blocks); - p.shared_items = saturateSub(p.shared_items, 1); } } else { add_total = true; @@ -141,7 +140,6 @@ pub const Entry = packed struct { if(add_total) { p.entry.size = saturateAdd(p.entry.size, self.size); p.entry.blocks = saturateAdd(p.entry.blocks, self.blocks); - p.total_items = saturateAdd(p.total_items, 1); } } } @@ -161,10 +159,7 @@ pub const Dir = packed struct { // (space reclaimed by deleting a dir =~ entry. - shared_) shared_blocks: u64, shared_size: u64, - shared_items: u32, - total_items: u32, - // TODO: ncdu1 only keeps track of a total item count including duplicate hardlinks. - // That number seems useful, too. Include it somehow? + items: u32, // Indexes into the global 'devices' array dev: DevId, diff --git a/src/ui.zig b/src/ui.zig index c56e7be5f38a83561e7c85c9c4303aacdc86ed1a..73183f2b49743777c74dd1dd3101ff972da5618f 100644 --- a/src/ui.zig +++ b/src/ui.zig @@ -118,25 +118,25 @@ pub fn shorten(in: [:0]const u8, max_width: u32) ![:0] const u8 { return try arrayListBufZ(&shorten_buf); } -fn shortenTest(in: [:0]const u8, max_width: u32, out: [:0]const u8) void { - std.testing.expectEqualStrings(out, shorten(in, max_width) catch unreachable); +fn shortenTest(in: [:0]const u8, max_width: u32, out: [:0]const u8) !void { + try std.testing.expectEqualStrings(out, try shorten(in, max_width)); } test "shorten" { _ = c.setlocale(c.LC_ALL, ""); // libc wcwidth() may not recognize Unicode without this const t = shortenTest; - t("abcde", 3, "..."); - t("abcde", 5, "abcde"); - t("abcde", 4, "...e"); - t("abcdefgh", 6, "a...gh"); - t("abcdefgh", 7, "ab...gh"); - t("ABCDEFGH", 16, "ABCDEFGH"); - t("ABCDEFGH", 7, "A...H"); - t("ABCDEFGH", 8, "A...H"); - t("ABCDEFGH", 9, "A...GH"); - t("AaBCDEFGH", 8, "A...H"); // could optimize this, but w/e - t("ABCDEFGaH", 8, "A...aH"); - t("ABCDEFGH", 15, "ABC...FGH"); + try t("abcde", 3, "..."); + try t("abcde", 5, "abcde"); + try t("abcde", 4, "...e"); + try t("abcdefgh", 6, "a...gh"); + try t("abcdefgh", 7, "ab...gh"); + try t("ABCDEFGH", 16, "ABCDEFGH"); + try t("ABCDEFGH", 7, "A...H"); + try t("ABCDEFGH", 8, "A...H"); + try t("ABCDEFGH", 9, "A...GH"); + try t("AaBCDEFGH", 8, "A...H"); // could optimize this, but w/e + try t("ABCDEFGaH", 8, "A...aH"); + try t("ABCDEFGH", 15, "ABC...FGH"); } // ncurses_refs.c @@ -167,6 +167,9 @@ const styles = [_]StyleDef{ .{ .name = "bold", .off = .{ .fg = -1, .bg = -1, .attr = c.A_BOLD }, .dark = .{ .fg = -1, .bg = -1, .attr = c.A_BOLD } }, + .{ .name = "bold_hd", + .off = .{ .fg = -1, .bg = -1, .attr = c.A_BOLD|c.A_REVERSE }, + .dark = .{ .fg = c.COLOR_BLACK, .bg = c.COLOR_CYAN, .attr = c.A_BOLD } }, .{ .name = "box_title", .off = .{ .fg = -1, .bg = -1, .attr = c.A_BOLD }, .dark = .{ .fg = c.COLOR_BLUE, .bg = -1, .attr = c.A_BOLD } },