ncdu-zig/src/browser.zig

196 lines
6.3 KiB
Zig
Raw Normal View History

const std = @import("std");
const main = @import("main.zig");
const model = @import("model.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 {
ui.style(.hd);
ui.move(0,0);
ui.hline(' ', ui.cols);
ui.move(0,0);
ui.addstr("ncdu " ++ main.program_version ++ " ~ Use the arrow keys to navigate, press ");
ui.style(.key_hd);
ui.addch('?');
ui.style(.hd);
ui.addstr(" for help");
if (main.config.read_only) {
ui.move(0, saturateSub(ui.cols, 10));
ui.addstr("[readonly]");
}
// TODO: [imported] indicator
ui.style(.default);
ui.move(1,0);
ui.hline('-', ui.cols);
ui.move(1,3);
ui.addch(' ');
ui.addstr(try ui.shorten(try ui.toUtf8(model.root.entry.name()), saturateSub(ui.cols, 5)));
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.move(ui.rows-1, 0);
ui.hline(' ', ui.cols);
ui.move(ui.rows-1, 1);
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);
}