mirror of
https://code.blicky.net/yorhel/ncdu.git
synced 2026-01-13 01:08:41 -09:00
More UI stuff + shave off 16 bytes from model.Dir
I initially wanted to keep a directory's block count and size as a separate field so that exporting an in-memory tree to a JSON dump would be easier to do, but that doesn't seem like a common operation to optimize for. We'll probably need the algorithms to subtract sub-items from directory counts anyway, so such an export can still be implemented, albeit slower.
This commit is contained in:
parent
a54c10bffb
commit
27cb599e22
6 changed files with 296 additions and 35 deletions
170
src/browser.zig
170
src/browser.zig
|
|
@ -2,6 +2,154 @@ const std = @import("std");
|
||||||
const main = @import("main.zig");
|
const main = @import("main.zig");
|
||||||
const model = @import("model.zig");
|
const model = @import("model.zig");
|
||||||
const ui = @import("ui.zig");
|
const ui = @import("ui.zig");
|
||||||
|
usingnamespace @import("util.zig");
|
||||||
|
|
||||||
|
// Sorted list of all items in the currently opened directory.
|
||||||
|
// (first item may be null to indicate the "parent directory" item)
|
||||||
|
var dir_items = std.ArrayList(?*model.Entry).init(main.allocator);
|
||||||
|
|
||||||
|
// Currently opened directory and its parents.
|
||||||
|
var dir_parents = model.Parents{};
|
||||||
|
|
||||||
|
fn sortIntLt(a: anytype, b: @TypeOf(a)) ?bool {
|
||||||
|
return if (a == b) null else if (main.config.sort_order == .asc) a < b else a > b;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sortLt(_: void, ap: ?*model.Entry, bp: ?*model.Entry) bool {
|
||||||
|
const a = ap.?;
|
||||||
|
const b = bp.?;
|
||||||
|
|
||||||
|
if (main.config.sort_dirsfirst and (a.etype == .dir) != (b.etype == .dir))
|
||||||
|
return a.etype == .dir;
|
||||||
|
|
||||||
|
switch (main.config.sort_col) {
|
||||||
|
.name => {}, // name sorting is the fallback
|
||||||
|
.blocks => {
|
||||||
|
if (sortIntLt(a.blocks, b.blocks)) |r| return r;
|
||||||
|
if (sortIntLt(a.size, b.size)) |r| return r;
|
||||||
|
},
|
||||||
|
.size => {
|
||||||
|
if (sortIntLt(a.size, b.size)) |r| return r;
|
||||||
|
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;
|
||||||
|
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;
|
||||||
|
},
|
||||||
|
.mtime => {
|
||||||
|
if (!a.isext or !b.isext) return a.isext;
|
||||||
|
if (sortIntLt(a.ext().?.mtime, b.ext().?.mtime)) |r| return r;
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Unicode-aware sorting might be nice (and slow)
|
||||||
|
const an = a.name();
|
||||||
|
const bn = b.name();
|
||||||
|
return if (main.config.sort_order == .asc) std.mem.lessThan(u8, an, bn)
|
||||||
|
else std.mem.lessThan(u8, bn, an) or std.mem.eql(u8, an, bn);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Should be called when:
|
||||||
|
// - config.sort_* changes
|
||||||
|
// - dir_items changes (i.e. from loadDir())
|
||||||
|
// - files in this dir have changed in a way that affects their ordering
|
||||||
|
fn sortDir() 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);
|
||||||
|
// TODO: Fixup selected item index
|
||||||
|
}
|
||||||
|
|
||||||
|
// Must be called when:
|
||||||
|
// - dir_parents changes (i.e. we change directory)
|
||||||
|
// - config.show_hidden changes
|
||||||
|
// - files in this dir have been added or removed
|
||||||
|
fn loadDir() !void {
|
||||||
|
dir_items.shrinkRetainingCapacity(0);
|
||||||
|
if (dir_parents.top() != model.root)
|
||||||
|
try dir_items.append(null);
|
||||||
|
var it = dir_parents.top().sub;
|
||||||
|
while (it) |e| {
|
||||||
|
if (main.config.show_hidden) // fast path
|
||||||
|
try dir_items.append(e)
|
||||||
|
else {
|
||||||
|
const excl = if (e.file()) |f| f.excluded else false;
|
||||||
|
const name = e.name();
|
||||||
|
if (!excl and name[0] != '.' and name[name.len-1] != '~')
|
||||||
|
try dir_items.append(e);
|
||||||
|
}
|
||||||
|
it = e.next;
|
||||||
|
}
|
||||||
|
sortDir();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Open the given dir for browsing; takes ownership of the Parents struct.
|
||||||
|
pub fn open(dir: model.Parents) !void {
|
||||||
|
dir_parents.deinit();
|
||||||
|
dir_parents = dir;
|
||||||
|
try loadDir();
|
||||||
|
|
||||||
|
// TODO: Load view & cursor position if we've opened this dir before.
|
||||||
|
}
|
||||||
|
|
||||||
|
const Row = struct {
|
||||||
|
row: u32,
|
||||||
|
col: u32 = 0,
|
||||||
|
bg: ui.Bg = .default,
|
||||||
|
item: ?*model.Entry,
|
||||||
|
|
||||||
|
const Self = @This();
|
||||||
|
|
||||||
|
fn flag(self: *Self) !void {
|
||||||
|
defer self.col += 2;
|
||||||
|
const item = self.item orelse return;
|
||||||
|
const ch: u7 = ch: {
|
||||||
|
if (item.file()) |f| {
|
||||||
|
if (f.err) break :ch '!';
|
||||||
|
if (f.excluded) break :ch '<';
|
||||||
|
if (f.other_fs) break :ch '>';
|
||||||
|
if (f.kernfs) break :ch '^';
|
||||||
|
if (f.notreg) break :ch '@';
|
||||||
|
} else if (item.dir()) |d| {
|
||||||
|
if (d.err) break :ch '!';
|
||||||
|
if (d.suberr) break :ch '.';
|
||||||
|
if (d.sub == null) break :ch 'e';
|
||||||
|
} else if (item.link()) |_| break :ch 'H';
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
ui.move(self.row, self.col);
|
||||||
|
self.bg.fg(.flag);
|
||||||
|
ui.addch(ch);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn size(self: *Self) !void {
|
||||||
|
defer self.col += if (main.config.si) @as(u32, 9) else 10;
|
||||||
|
const item = self.item orelse return;
|
||||||
|
ui.move(self.row, self.col);
|
||||||
|
ui.addsize(self.bg, if (main.config.show_blocks) blocksToSize(item.blocks) else item.size);
|
||||||
|
// TODO: shared sizes
|
||||||
|
}
|
||||||
|
|
||||||
|
fn name(self: *Self) !void {
|
||||||
|
ui.move(self.row, self.col);
|
||||||
|
self.bg.fg(.default);
|
||||||
|
if (self.item) |i| {
|
||||||
|
ui.addch(if (i.etype == .dir) '/' else ' ');
|
||||||
|
ui.addstr(try ui.shorten(try ui.toUtf8(i.name()), saturateSub(ui.cols, saturateSub(self.col, 1))));
|
||||||
|
} else
|
||||||
|
ui.addstr("/..");
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw(self: *Self) !void {
|
||||||
|
try self.flag();
|
||||||
|
try self.size();
|
||||||
|
try self.name();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
pub fn draw() !void {
|
pub fn draw() !void {
|
||||||
ui.style(.hd);
|
ui.style(.hd);
|
||||||
|
|
@ -13,19 +161,35 @@ pub fn draw() !void {
|
||||||
ui.addch('?');
|
ui.addch('?');
|
||||||
ui.style(.hd);
|
ui.style(.hd);
|
||||||
ui.addstr(" for help");
|
ui.addstr(" for help");
|
||||||
// TODO: [imported]/[readonly] indicators
|
if (main.config.read_only) {
|
||||||
|
ui.move(0, saturateSub(ui.cols, 10));
|
||||||
|
ui.addstr("[readonly]");
|
||||||
|
}
|
||||||
|
// TODO: [imported] indicator
|
||||||
|
|
||||||
ui.style(.default);
|
ui.style(.default);
|
||||||
ui.move(1,0);
|
ui.move(1,0);
|
||||||
ui.hline('-', ui.cols);
|
ui.hline('-', ui.cols);
|
||||||
ui.move(1,3);
|
ui.move(1,3);
|
||||||
ui.addch(' ');
|
ui.addch(' ');
|
||||||
ui.addstr(try ui.shorten(try ui.toUtf8(model.root.entry.name()), std.math.sub(u32, ui.cols, 5) catch 4));
|
ui.addstr(try ui.shorten(try ui.toUtf8(model.root.entry.name()), saturateSub(ui.cols, 5)));
|
||||||
ui.addch(' ');
|
ui.addch(' ');
|
||||||
|
|
||||||
|
var i: u32 = 0;
|
||||||
|
while (i < saturateSub(ui.rows, 3)) : (i += 1) {
|
||||||
|
if (i >= dir_items.items.len) break;
|
||||||
|
var row = Row{ .row = i+2, .item = dir_items.items[i] };
|
||||||
|
try row.draw();
|
||||||
|
}
|
||||||
|
|
||||||
ui.style(.hd);
|
ui.style(.hd);
|
||||||
ui.move(ui.rows-1, 0);
|
ui.move(ui.rows-1, 0);
|
||||||
ui.hline(' ', ui.cols);
|
ui.hline(' ', ui.cols);
|
||||||
ui.move(ui.rows-1, 1);
|
ui.move(ui.rows-1, 1);
|
||||||
ui.addstr("No items to display.");
|
ui.addstr("Total disk usage: ");
|
||||||
|
ui.addsize(.hd, blocksToSize(dir_parents.top().entry.blocks));
|
||||||
|
ui.addstr(" Apparent size: ");
|
||||||
|
ui.addsize(.hd, dir_parents.top().entry.size);
|
||||||
|
ui.addstr(" Items: ");
|
||||||
|
ui.addnum(.hd, dir_parents.top().total_items);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,12 @@ pub const Config = struct {
|
||||||
ui_color: enum { off, dark } = .off,
|
ui_color: enum { off, dark } = .off,
|
||||||
thousands_sep: []const u8 = ".",
|
thousands_sep: []const u8 = ".",
|
||||||
|
|
||||||
|
show_hidden: bool = true,
|
||||||
|
show_blocks: bool = true,
|
||||||
|
sort_col: enum { name, blocks, size, items, mtime } = .blocks,
|
||||||
|
sort_order: enum { asc, desc } = .desc,
|
||||||
|
sort_dirsfirst: bool = false,
|
||||||
|
|
||||||
read_only: bool = false,
|
read_only: bool = false,
|
||||||
can_shell: bool = true,
|
can_shell: bool = true,
|
||||||
confirm_quit: bool = false,
|
confirm_quit: bool = false,
|
||||||
|
|
@ -239,9 +245,11 @@ pub fn main() anyerror!void {
|
||||||
ui.die("The --exclude-kernfs tag is currently only supported on Linux.\n", .{});
|
ui.die("The --exclude-kernfs tag is currently only supported on Linux.\n", .{});
|
||||||
|
|
||||||
try scan.scanRoot(scan_dir orelse ".");
|
try scan.scanRoot(scan_dir orelse ".");
|
||||||
|
try browser.open(model.Parents{});
|
||||||
|
|
||||||
ui.init();
|
ui.init();
|
||||||
defer ui.deinit();
|
defer ui.deinit();
|
||||||
|
|
||||||
try browser.draw();
|
try browser.draw();
|
||||||
|
|
||||||
_ = ui.c.getch();
|
_ = ui.c.getch();
|
||||||
|
|
|
||||||
|
|
@ -1,24 +1,15 @@
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const main = @import("main.zig");
|
const main = @import("main.zig");
|
||||||
|
usingnamespace @import("util.zig");
|
||||||
|
|
||||||
// While an arena allocator is optimimal for almost all scenarios in which ncdu
|
// While an arena allocator is optimimal for almost all scenarios in which ncdu
|
||||||
// is used, it doesn't allow for re-using deleted nodes after doing a delete or
|
// is used, it doesn't allow for re-using deleted nodes after doing a delete or
|
||||||
// refresh operation, so a long-running ncdu session with regular refreshes
|
// refresh operation, so a long-running ncdu session with regular refreshes
|
||||||
// will leak memory, but I'd say that's worth the efficiency gains.
|
// will leak memory, but I'd say that's worth the efficiency gains.
|
||||||
// (TODO: Measure, though. Might as well use a general purpose allocator if the
|
// TODO: Can still implement a simple bucketed free list on top of this arena
|
||||||
// memory overhead turns out to be insignificant.)
|
// allocator to reuse nodes, if necessary.
|
||||||
var allocator = std.heap.ArenaAllocator.init(std.heap.page_allocator);
|
var allocator = std.heap.ArenaAllocator.init(std.heap.page_allocator);
|
||||||
|
|
||||||
fn saturateAdd(a: anytype, b: @TypeOf(a)) @TypeOf(a) {
|
|
||||||
std.debug.assert(@typeInfo(@TypeOf(a)).Int.signedness == .unsigned);
|
|
||||||
return std.math.add(@TypeOf(a), a, b) catch std.math.maxInt(@TypeOf(a));
|
|
||||||
}
|
|
||||||
|
|
||||||
fn saturateSub(a: anytype, b: @TypeOf(a)) @TypeOf(a) {
|
|
||||||
std.debug.assert(@typeInfo(@TypeOf(a)).Int.signedness == .unsigned);
|
|
||||||
return std.math.sub(@TypeOf(a), a, b) catch std.math.minInt(@TypeOf(a));
|
|
||||||
}
|
|
||||||
|
|
||||||
pub const EType = packed enum(u2) { dir, link, file };
|
pub const EType = packed enum(u2) { dir, link, file };
|
||||||
|
|
||||||
// Memory layout:
|
// Memory layout:
|
||||||
|
|
@ -57,7 +48,7 @@ pub const Entry = packed struct {
|
||||||
return if (self.etype == .file) @ptrCast(*File, self) else null;
|
return if (self.etype == .file) @ptrCast(*File, self) else null;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn name_offset(etype: EType) usize {
|
fn nameOffset(etype: EType) usize {
|
||||||
return switch (etype) {
|
return switch (etype) {
|
||||||
.dir => @byteOffsetOf(Dir, "name"),
|
.dir => @byteOffsetOf(Dir, "name"),
|
||||||
.link => @byteOffsetOf(Link, "name"),
|
.link => @byteOffsetOf(Link, "name"),
|
||||||
|
|
@ -66,25 +57,25 @@ pub const Entry = packed struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn name(self: *const Self) [:0]const u8 {
|
pub fn name(self: *const Self) [:0]const u8 {
|
||||||
const ptr = @intToPtr([*:0]u8, @ptrToInt(self) + name_offset(self.etype));
|
const ptr = @intToPtr([*:0]u8, @ptrToInt(self) + nameOffset(self.etype));
|
||||||
return ptr[0..std.mem.lenZ(ptr) :0];
|
return ptr[0..std.mem.lenZ(ptr) :0];
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn ext(self: *Self) ?*Ext {
|
pub fn ext(self: *Self) ?*Ext {
|
||||||
if (!self.isext) return null;
|
if (!self.isext) return null;
|
||||||
const n = self.name();
|
const n = self.name();
|
||||||
return @intToPtr(*Ext, std.mem.alignForward(@ptrToInt(self) + name_offset(self.etype) + n.len + 1, @alignOf(Ext)));
|
return @intToPtr(*Ext, std.mem.alignForward(@ptrToInt(self) + nameOffset(self.etype) + n.len + 1, @alignOf(Ext)));
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn create(etype: EType, isext: bool, ename: []const u8) !*Entry {
|
pub fn create(etype: EType, isext: bool, ename: []const u8) !*Entry {
|
||||||
const base_size = name_offset(etype) + ename.len + 1;
|
const base_size = nameOffset(etype) + ename.len + 1;
|
||||||
const size = (if (isext) std.mem.alignForward(base_size, @alignOf(Ext))+@sizeOf(Ext) else base_size);
|
const size = (if (isext) std.mem.alignForward(base_size, @alignOf(Ext))+@sizeOf(Ext) else base_size);
|
||||||
var ptr = try allocator.allocator.allocWithOptions(u8, size, @alignOf(Entry), null);
|
var ptr = try allocator.allocator.allocWithOptions(u8, size, @alignOf(Entry), null);
|
||||||
std.mem.set(u8, ptr, 0); // kind of ugly, but does the trick
|
std.mem.set(u8, ptr, 0); // kind of ugly, but does the trick
|
||||||
var e = @ptrCast(*Entry, ptr);
|
var e = @ptrCast(*Entry, ptr);
|
||||||
e.etype = etype;
|
e.etype = etype;
|
||||||
e.isext = isext;
|
e.isext = isext;
|
||||||
var name_ptr = @intToPtr([*]u8, @ptrToInt(e) + name_offset(etype));
|
var name_ptr = @intToPtr([*]u8, @ptrToInt(e) + nameOffset(etype));
|
||||||
std.mem.copy(u8, name_ptr[0..ename.len], ename);
|
std.mem.copy(u8, name_ptr[0..ename.len], ename);
|
||||||
//std.debug.warn("{any}\n", .{ @ptrCast([*]u8, e)[0..size] });
|
//std.debug.warn("{any}\n", .{ @ptrCast([*]u8, e)[0..size] });
|
||||||
return e;
|
return e;
|
||||||
|
|
@ -145,8 +136,8 @@ pub const Entry = packed struct {
|
||||||
add_total = true;
|
add_total = true;
|
||||||
}
|
}
|
||||||
if(add_total) {
|
if(add_total) {
|
||||||
p.total_size = saturateAdd(p.total_size, self.size);
|
p.entry.size = saturateAdd(p.entry.size, self.size);
|
||||||
p.total_blocks = saturateAdd(p.total_blocks, self.blocks);
|
p.entry.blocks = saturateAdd(p.entry.blocks, self.blocks);
|
||||||
p.total_items = saturateAdd(p.total_items, 1);
|
p.total_items = saturateAdd(p.total_items, 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -160,17 +151,15 @@ pub const Dir = packed struct {
|
||||||
|
|
||||||
sub: ?*Entry,
|
sub: ?*Entry,
|
||||||
|
|
||||||
// total_*: Total size of all unique files + dirs. Non-shared hardlinks are counted only once.
|
// entry.{blocks,size}: Total size of all unique files + dirs. Non-shared hardlinks are counted only once.
|
||||||
// (i.e. the space you'll need if you created a filesystem with only this dir)
|
// (i.e. the space you'll need if you created a filesystem with only this dir)
|
||||||
// shared_*: Unique hardlinks that still have references outside of this directory.
|
// shared_*: Unique hardlinks that still have references outside of this directory.
|
||||||
// (i.e. the space you won't reclaim by deleting this dir)
|
// (i.e. the space you won't reclaim by deleting this dir)
|
||||||
// (space reclaimed by deleting a dir =~ total_ - shared_)
|
// (space reclaimed by deleting a dir =~ entry. - shared_)
|
||||||
total_blocks: u64,
|
|
||||||
shared_blocks: u64,
|
shared_blocks: u64,
|
||||||
total_size: u64,
|
|
||||||
shared_size: u64,
|
shared_size: u64,
|
||||||
total_items: u32,
|
|
||||||
shared_items: u32,
|
shared_items: u32,
|
||||||
|
total_items: u32,
|
||||||
// TODO: ncdu1 only keeps track of a total item count including duplicate hardlinks.
|
// TODO: ncdu1 only keeps track of a total item count including duplicate hardlinks.
|
||||||
// That number seems useful, too. Include it somehow?
|
// That number seems useful, too. Include it somehow?
|
||||||
|
|
||||||
|
|
@ -355,6 +344,10 @@ pub const Parents = struct {
|
||||||
i += 1;
|
i += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn deinit(self: *Self) void {
|
||||||
|
self.stack.deinit();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
test "name offsets" {
|
test "name offsets" {
|
||||||
|
|
|
||||||
|
|
@ -217,13 +217,7 @@ fn scanDir(ctx: *Context, dir: std.fs.Dir) std.mem.Allocator.Error!void {
|
||||||
var e = try model.Entry.create(etype, main.config.extended, entry.name);
|
var e = try model.Entry.create(etype, main.config.extended, entry.name);
|
||||||
e.blocks = stat.blocks;
|
e.blocks = stat.blocks;
|
||||||
e.size = stat.size;
|
e.size = stat.size;
|
||||||
if (e.dir()) |d| {
|
if (e.dir()) |d| d.dev = try model.getDevId(stat.dev);
|
||||||
d.dev = try model.getDevId(stat.dev);
|
|
||||||
// The dir entry itself also counts.
|
|
||||||
d.total_blocks = stat.blocks;
|
|
||||||
d.total_size = stat.size;
|
|
||||||
d.total_items = 1;
|
|
||||||
}
|
|
||||||
if (e.file()) |f| f.notreg = !stat.dir and !stat.reg;
|
if (e.file()) |f| f.notreg = !stat.dir and !stat.reg;
|
||||||
if (e.link()) |l| {
|
if (e.link()) |l| {
|
||||||
l.ino = stat.ino;
|
l.ino = stat.ino;
|
||||||
|
|
|
||||||
88
src/ui.zig
88
src/ui.zig
|
|
@ -209,7 +209,7 @@ const styles = [_]StyleDef{
|
||||||
.dark = .{ .fg = c.COLOR_MAGENTA, .bg = c.COLOR_GREEN, .attr = 0 } },
|
.dark = .{ .fg = c.COLOR_MAGENTA, .bg = c.COLOR_GREEN, .attr = 0 } },
|
||||||
};
|
};
|
||||||
|
|
||||||
const Style = lbl: {
|
pub const Style = lbl: {
|
||||||
var fields: [styles.len]std.builtin.TypeInfo.EnumField = undefined;
|
var fields: [styles.len]std.builtin.TypeInfo.EnumField = undefined;
|
||||||
var decls = [_]std.builtin.TypeInfo.Declaration{};
|
var decls = [_]std.builtin.TypeInfo.Declaration{};
|
||||||
inline for (styles) |s, i| {
|
inline for (styles) |s, i| {
|
||||||
|
|
@ -229,6 +229,35 @@ const Style = lbl: {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const ui = @This();
|
||||||
|
|
||||||
|
pub const Bg = enum {
|
||||||
|
default, hd, sel,
|
||||||
|
|
||||||
|
// Set the style to the selected bg combined with the given fg.
|
||||||
|
pub fn fg(self: @This(), s: Style) void {
|
||||||
|
ui.style(switch (self) {
|
||||||
|
.default => s,
|
||||||
|
.hd =>
|
||||||
|
switch (s) {
|
||||||
|
.default => Style.hd,
|
||||||
|
.key => Style.key_hd,
|
||||||
|
.num => Style.num_hd,
|
||||||
|
else => unreachable,
|
||||||
|
},
|
||||||
|
.sel =>
|
||||||
|
switch (s) {
|
||||||
|
.default => Style.sel,
|
||||||
|
.num => Style.num_sel,
|
||||||
|
.dir => Style.dir_sel,
|
||||||
|
.flag => Style.flag_sel,
|
||||||
|
.graph => Style.graph_sel,
|
||||||
|
else => unreachable,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
fn updateSize() void {
|
fn updateSize() void {
|
||||||
// getmax[yx] macros are marked as "legacy", but Zig can't deal with the "proper" getmaxyx macro.
|
// getmax[yx] macros are marked as "legacy", but Zig can't deal with the "proper" getmaxyx macro.
|
||||||
rows = @intCast(u32, c.getmaxy(c.stdscr));
|
rows = @intCast(u32, c.getmaxy(c.stdscr));
|
||||||
|
|
@ -287,6 +316,63 @@ pub fn addch(ch: c.chtype) void {
|
||||||
_ = c.addch(ch);
|
_ = c.addch(ch);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Print a human-readable size string, formatted into the given bavkground.
|
||||||
|
// Takes 8 columns in SI mode, 9 otherwise.
|
||||||
|
// "###.# XB"
|
||||||
|
// "###.# XiB"
|
||||||
|
pub fn addsize(bg: Bg, v: u64) void {
|
||||||
|
var f = @intToFloat(f32, v);
|
||||||
|
var unit: [:0]const u8 = undefined;
|
||||||
|
if (main.config.si) {
|
||||||
|
if(f < 1000.0) { unit = " B"; }
|
||||||
|
else if(f < 1e6) { unit = " KB"; f /= 1e3; }
|
||||||
|
else if(f < 1e9) { unit = " MB"; f /= 1e6; }
|
||||||
|
else if(f < 1e12) { unit = " GB"; f /= 1e9; }
|
||||||
|
else if(f < 1e15) { unit = " TB"; f /= 1e12; }
|
||||||
|
else if(f < 1e18) { unit = " PB"; f /= 1e15; }
|
||||||
|
else { unit = " EB"; f /= 1e18; }
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if(f < 1000.0) { unit = " B"; }
|
||||||
|
else if(f < 1023e3) { unit = " KiB"; f /= 1024.0; }
|
||||||
|
else if(f < 1023e6) { unit = " MiB"; f /= 1048576.0; }
|
||||||
|
else if(f < 1023e9) { unit = " GiB"; f /= 1073741824.0; }
|
||||||
|
else if(f < 1023e12) { unit = " TiB"; f /= 1099511627776.0; }
|
||||||
|
else if(f < 1023e15) { unit = " PiB"; f /= 1125899906842624.0; }
|
||||||
|
else { unit = " EiB"; f /= 1152921504606846976.0; }
|
||||||
|
}
|
||||||
|
var buf: [8:0]u8 = undefined;
|
||||||
|
_ = std.fmt.bufPrintZ(&buf, "{d:>5.1}", .{f}) catch unreachable;
|
||||||
|
bg.fg(.num);
|
||||||
|
addstr(&buf);
|
||||||
|
bg.fg(.default);
|
||||||
|
addstr(unit);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Print a full decimal number with thousand separators.
|
||||||
|
// Max: 18,446,744,073,709,551,615 -> 26 columns
|
||||||
|
// (Assuming thousands_sep takes a single column)
|
||||||
|
pub fn addnum(bg: Bg, v: u64) void {
|
||||||
|
var buf: [32]u8 = undefined;
|
||||||
|
const s = std.fmt.bufPrint(&buf, "{d}", .{v}) catch unreachable;
|
||||||
|
var f: [64:0]u8 = undefined;
|
||||||
|
var i: usize = 0;
|
||||||
|
for (s) |digit, n| {
|
||||||
|
if (n != 0 and (s.len - n) % 3 == 0) {
|
||||||
|
for (main.config.thousands_sep) |ch| {
|
||||||
|
f[i] = ch;
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
f[i] = digit;
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
f[i] = 0;
|
||||||
|
bg.fg(.num);
|
||||||
|
addstr(&f);
|
||||||
|
bg.fg(.default);
|
||||||
|
}
|
||||||
|
|
||||||
pub fn hline(ch: c.chtype, len: u32) void {
|
pub fn hline(ch: c.chtype, len: u32) void {
|
||||||
_ = c.hline(ch, @intCast(i32, len));
|
_ = c.hline(ch, @intCast(i32, len));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
16
src/util.zig
Normal file
16
src/util.zig
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
pub fn saturateAdd(a: anytype, b: @TypeOf(a)) @TypeOf(a) {
|
||||||
|
std.debug.assert(@typeInfo(@TypeOf(a)).Int.signedness == .unsigned);
|
||||||
|
return std.math.add(@TypeOf(a), a, b) catch std.math.maxInt(@TypeOf(a));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn saturateSub(a: anytype, b: @TypeOf(a)) @TypeOf(a) {
|
||||||
|
std.debug.assert(@typeInfo(@TypeOf(a)).Int.signedness == .unsigned);
|
||||||
|
return std.math.sub(@TypeOf(a), a, b) catch std.math.minInt(@TypeOf(a));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Multiplies by 512, saturating.
|
||||||
|
pub fn blocksToSize(b: u64) u64 {
|
||||||
|
return if (b & 0xFF80000000000000 > 0) std.math.maxInt(u64) else b << 9;
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue