2021-05-02 22:01:09 -08:00
|
|
|
const std = @import("std");
|
|
|
|
|
const main = @import("main.zig");
|
2021-05-04 08:01:49 -08:00
|
|
|
const model = @import("model.zig");
|
2021-05-02 22:01:09 -08:00
|
|
|
const ui = @import("ui.zig");
|
2021-05-23 07:18:49 -08:00
|
|
|
const c = @cImport(@cInclude("time.h"));
|
2021-05-06 09:15:47 -08:00
|
|
|
usingnamespace @import("util.zig");
|
|
|
|
|
|
2021-05-07 07:16:35 -08:00
|
|
|
// Currently opened directory and its parents.
|
|
|
|
|
var dir_parents = model.Parents{};
|
|
|
|
|
|
2021-05-06 09:15:47 -08:00
|
|
|
// 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);
|
|
|
|
|
|
2021-05-23 07:18:49 -08:00
|
|
|
var dir_max_blocks: u64 = 0;
|
|
|
|
|
var dir_max_size: u64 = 0;
|
|
|
|
|
|
2021-05-07 07:16:35 -08:00
|
|
|
// Index into dir_items that is currently selected.
|
2021-05-07 02:01:00 -08:00
|
|
|
var cursor_idx: usize = 0;
|
2021-05-07 07:16:35 -08:00
|
|
|
|
|
|
|
|
const View = struct {
|
|
|
|
|
// Index into dir_items, indicates which entry is displayed at the top of the view.
|
|
|
|
|
// This is merely a suggestion, it will be adjusted upon drawing if it's
|
|
|
|
|
// out of bounds or if the cursor is not otherwise visible.
|
|
|
|
|
top: usize = 0,
|
|
|
|
|
|
|
|
|
|
// The hash(name) of the selected entry (cursor), this is used to derive
|
|
|
|
|
// cursor_idx after sorting or changing directory.
|
|
|
|
|
// (collisions may cause the wrong entry to be selected, but dealing with
|
|
|
|
|
// string allocations sucks and I expect collisions to be rare enough)
|
|
|
|
|
cursor_hash: u64 = 0,
|
|
|
|
|
|
|
|
|
|
fn hashEntry(entry: ?*model.Entry) u64 {
|
|
|
|
|
return if (entry) |e| std.hash.Wyhash.hash(0, e.name()) else 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Update cursor_hash and save the current view to the hash table.
|
|
|
|
|
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_parents.top()), self.*) catch {};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Should be called after dir_parents or dir_items has changed, will load the last saved view and find the proper cursor_idx.
|
|
|
|
|
fn load(self: *@This()) void {
|
|
|
|
|
if (opened_dir_views.get(@ptrToInt(dir_parents.top()))) |v| self.* = v
|
|
|
|
|
else self.* = @This(){};
|
2021-05-11 02:55:15 -08:00
|
|
|
cursor_idx = 0;
|
2021-05-07 07:16:35 -08:00
|
|
|
for (dir_items.items) |e, i| {
|
|
|
|
|
if (self.cursor_hash == hashEntry(e)) {
|
|
|
|
|
cursor_idx = i;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
// 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);
|
2021-05-07 02:01:00 -08:00
|
|
|
|
2021-05-06 09:15:47 -08:00
|
|
|
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 => {
|
2021-05-24 01:02:26 -08:00
|
|
|
const ai = if (a.dir()) |d| d.items else 0;
|
|
|
|
|
const bi = if (b.dir()) |d| d.items else 0;
|
2021-05-06 09:15:47 -08:00
|
|
|
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)
|
2021-05-23 07:18:49 -08:00
|
|
|
else std.mem.lessThan(u8, bn, an);
|
2021-05-06 09:15:47 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 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);
|
2021-05-07 07:16:35 -08:00
|
|
|
current_view.load();
|
2021-05-06 09:15:47 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 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
|
2021-05-07 07:16:35 -08:00
|
|
|
pub fn loadDir() !void {
|
2021-05-06 09:15:47 -08:00
|
|
|
dir_items.shrinkRetainingCapacity(0);
|
2021-05-23 07:18:49 -08:00
|
|
|
dir_max_size = 1;
|
|
|
|
|
dir_max_blocks = 1;
|
|
|
|
|
|
2021-05-06 09:15:47 -08:00
|
|
|
if (dir_parents.top() != model.root)
|
|
|
|
|
try dir_items.append(null);
|
|
|
|
|
var it = dir_parents.top().sub;
|
|
|
|
|
while (it) |e| {
|
2021-05-23 07:18:49 -08:00
|
|
|
if (e.blocks > dir_max_blocks) dir_max_blocks = e.blocks;
|
|
|
|
|
if (e.size > dir_max_size) dir_max_size = e.size;
|
2021-05-06 09:15:47 -08:00
|
|
|
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();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const Row = struct {
|
|
|
|
|
row: u32,
|
|
|
|
|
col: u32 = 0,
|
|
|
|
|
bg: ui.Bg = .default,
|
|
|
|
|
item: ?*model.Entry,
|
|
|
|
|
|
|
|
|
|
const Self = @This();
|
|
|
|
|
|
2021-05-23 07:18:49 -08:00
|
|
|
fn flag(self: *Self) void {
|
2021-05-06 09:15:47 -08:00
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
|
2021-05-23 07:18:49 -08:00
|
|
|
fn size(self: *Self) void {
|
2021-05-06 09:15:47 -08:00
|
|
|
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
|
|
|
|
|
}
|
|
|
|
|
|
2021-05-23 07:18:49 -08:00
|
|
|
fn graph(self: *Self) void {
|
|
|
|
|
if (main.config.show_graph == .off) return;
|
|
|
|
|
|
|
|
|
|
const bar_size = std.math.max(ui.cols/7, 10);
|
|
|
|
|
defer self.col += switch (main.config.show_graph) {
|
|
|
|
|
.off => unreachable,
|
|
|
|
|
.graph => bar_size + 3,
|
|
|
|
|
.percent => 9,
|
|
|
|
|
.both => bar_size + 10,
|
|
|
|
|
};
|
|
|
|
|
const item = self.item orelse return;
|
|
|
|
|
|
2021-05-06 09:15:47 -08:00
|
|
|
ui.move(self.row, self.col);
|
|
|
|
|
self.bg.fg(.default);
|
2021-05-23 07:18:49 -08:00
|
|
|
ui.addch('[');
|
|
|
|
|
if (main.config.show_graph == .both or main.config.show_graph == .percent) {
|
|
|
|
|
self.bg.fg(.num);
|
|
|
|
|
ui.addprint("{d:>5.1}", .{ 100*
|
|
|
|
|
if (main.config.show_blocks) @intToFloat(f32, item.blocks) / @intToFloat(f32, std.math.max(1, dir_parents.top().entry.blocks))
|
|
|
|
|
else @intToFloat(f32, item.size) / @intToFloat(f32, std.math.max(1, dir_parents.top().entry.size))
|
|
|
|
|
});
|
|
|
|
|
self.bg.fg(.default);
|
|
|
|
|
ui.addch('%');
|
|
|
|
|
}
|
|
|
|
|
if (main.config.show_graph == .both) ui.addch(' ');
|
|
|
|
|
if (main.config.show_graph == .both or main.config.show_graph == .graph) {
|
2021-05-24 01:02:26 -08:00
|
|
|
const perblock = std.math.divFloor(u64, if (main.config.show_blocks) dir_max_blocks else dir_max_size, bar_size) catch unreachable;
|
2021-05-23 07:18:49 -08:00
|
|
|
const num = if (main.config.show_blocks) item.blocks else item.size;
|
|
|
|
|
var i: u32 = 0;
|
2021-05-24 01:02:26 -08:00
|
|
|
var siz: u64 = 0;
|
2021-05-23 07:18:49 -08:00
|
|
|
self.bg.fg(.graph);
|
2021-05-24 01:02:26 -08:00
|
|
|
while (i < bar_size) : (i += 1) {
|
|
|
|
|
siz = saturateAdd(siz, perblock);
|
|
|
|
|
ui.addch(if (siz <= num) '#' else ' ');
|
|
|
|
|
}
|
2021-05-23 07:18:49 -08:00
|
|
|
}
|
|
|
|
|
self.bg.fg(.default);
|
|
|
|
|
ui.addch(']');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn items(self: *Self) void {
|
|
|
|
|
if (!main.config.show_items) return;
|
|
|
|
|
defer self.col += 7;
|
2021-05-24 01:02:26 -08:00
|
|
|
const n = (if (self.item) |d| d.dir() orelse return else return).items;
|
2021-05-23 07:18:49 -08:00
|
|
|
ui.move(self.row, self.col);
|
|
|
|
|
self.bg.fg(.num);
|
|
|
|
|
if (n < 1000)
|
|
|
|
|
ui.addprint(" {d:>4}", .{n})
|
2021-05-24 01:02:26 -08:00
|
|
|
else if (n < 10_000) {
|
|
|
|
|
ui.addch(' ');
|
|
|
|
|
ui.addnum(self.bg, n);
|
|
|
|
|
} else if (n < 100_000)
|
|
|
|
|
ui.addnum(self.bg, n)
|
2021-05-23 07:18:49 -08:00
|
|
|
else if (n < 1000_000) {
|
|
|
|
|
ui.addprint("{d:>5.1}", .{ @intToFloat(f32, 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 });
|
|
|
|
|
self.bg.fg(.default);
|
|
|
|
|
ui.addch('M');
|
|
|
|
|
} else {
|
|
|
|
|
self.bg.fg(.default);
|
|
|
|
|
ui.addstr(" > ");
|
|
|
|
|
self.bg.fg(.num);
|
|
|
|
|
ui.addch('1');
|
|
|
|
|
self.bg.fg(.default);
|
|
|
|
|
ui.addch('G');
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn mtime(self: *Self) void {
|
|
|
|
|
if (!main.config.show_mtime) return;
|
|
|
|
|
defer self.col += 27;
|
|
|
|
|
ui.move(self.row, self.col+1);
|
|
|
|
|
const ext = (if (self.item) |e| e.ext() else @as(?*model.Ext, null)) orelse dir_parents.top().entry.ext();
|
|
|
|
|
if (ext) |e| {
|
|
|
|
|
const t = castClamp(c.time_t, e.mtime);
|
|
|
|
|
var buf: [32:0]u8 = undefined;
|
|
|
|
|
const len = c.strftime(&buf, buf.len, "%Y-%m-%d %H:%M:%S %z", c.localtime(&t));
|
|
|
|
|
if (len > 0) {
|
|
|
|
|
self.bg.fg(.num);
|
|
|
|
|
ui.addstr(buf[0..len:0]);
|
|
|
|
|
} else
|
|
|
|
|
ui.addstr(" invalid mtime");
|
|
|
|
|
} else
|
|
|
|
|
ui.addstr(" no mtime");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn name(self: *Self) !void {
|
|
|
|
|
ui.move(self.row, self.col);
|
2021-05-06 09:15:47 -08:00
|
|
|
if (self.item) |i| {
|
2021-05-23 07:18:49 -08:00
|
|
|
self.bg.fg(if (i.etype == .dir) .dir else .default);
|
2021-05-06 09:15:47 -08:00
|
|
|
ui.addch(if (i.etype == .dir) '/' else ' ');
|
2021-05-07 07:16:35 -08:00
|
|
|
ui.addstr(try ui.shorten(try ui.toUtf8(i.name()), saturateSub(ui.cols, self.col + 1)));
|
2021-05-23 07:18:49 -08:00
|
|
|
} else {
|
|
|
|
|
self.bg.fg(.dir);
|
2021-05-06 09:15:47 -08:00
|
|
|
ui.addstr("/..");
|
2021-05-23 07:18:49 -08:00
|
|
|
}
|
2021-05-06 09:15:47 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn draw(self: *Self) !void {
|
2021-05-07 02:01:00 -08:00
|
|
|
if (self.bg == .sel) {
|
|
|
|
|
self.bg.fg(.default);
|
|
|
|
|
ui.move(self.row, 0);
|
|
|
|
|
ui.hline(' ', ui.cols);
|
|
|
|
|
}
|
2021-05-23 07:18:49 -08:00
|
|
|
self.flag();
|
|
|
|
|
self.size();
|
|
|
|
|
self.graph();
|
|
|
|
|
self.items();
|
|
|
|
|
self.mtime();
|
2021-05-06 09:15:47 -08:00
|
|
|
try self.name();
|
|
|
|
|
}
|
|
|
|
|
};
|
2021-05-02 22:01:09 -08:00
|
|
|
|
2021-05-11 02:55:15 -08:00
|
|
|
var need_confirm_quit = false;
|
|
|
|
|
|
|
|
|
|
fn drawQuit() void {
|
|
|
|
|
const box = ui.Box.create(4, 22, "Confirm quit");
|
|
|
|
|
box.move(2, 2);
|
|
|
|
|
ui.addstr("Really quit? (");
|
|
|
|
|
ui.style(.key);
|
|
|
|
|
ui.addch('y');
|
|
|
|
|
ui.style(.default);
|
|
|
|
|
ui.addch('/');
|
|
|
|
|
ui.style(.key);
|
|
|
|
|
ui.addch('N');
|
|
|
|
|
ui.style(.default);
|
|
|
|
|
ui.addch(')');
|
|
|
|
|
}
|
|
|
|
|
|
2021-05-04 08:01:49 -08:00
|
|
|
pub fn draw() !void {
|
2021-05-02 22:01:09 -08:00
|
|
|
ui.style(.hd);
|
2021-05-04 08:01:49 -08:00
|
|
|
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 ");
|
2021-05-02 22:01:09 -08:00
|
|
|
ui.style(.key_hd);
|
2021-05-04 08:01:49 -08:00
|
|
|
ui.addch('?');
|
2021-05-02 22:01:09 -08:00
|
|
|
ui.style(.hd);
|
2021-05-04 08:01:49 -08:00
|
|
|
ui.addstr(" for help");
|
2021-05-06 09:15:47 -08:00
|
|
|
if (main.config.read_only) {
|
|
|
|
|
ui.move(0, saturateSub(ui.cols, 10));
|
|
|
|
|
ui.addstr("[readonly]");
|
|
|
|
|
}
|
|
|
|
|
// TODO: [imported] indicator
|
2021-05-02 22:01:09 -08:00
|
|
|
|
|
|
|
|
ui.style(.default);
|
2021-05-04 08:01:49 -08:00
|
|
|
ui.move(1,0);
|
|
|
|
|
ui.hline('-', ui.cols);
|
|
|
|
|
ui.move(1,3);
|
|
|
|
|
ui.addch(' ');
|
2021-05-23 07:18:49 -08:00
|
|
|
ui.style(.dir);
|
|
|
|
|
|
|
|
|
|
var pathbuf = std.ArrayList(u8).init(main.allocator);
|
|
|
|
|
try dir_parents.path(pathbuf.writer());
|
|
|
|
|
ui.addstr(try ui.shorten(try ui.toUtf8(try arrayListBufZ(&pathbuf)), saturateSub(ui.cols, 5)));
|
|
|
|
|
pathbuf.deinit();
|
|
|
|
|
|
|
|
|
|
ui.style(.default);
|
2021-05-04 08:01:49 -08:00
|
|
|
ui.addch(' ');
|
2021-05-02 22:01:09 -08:00
|
|
|
|
2021-05-07 02:01:00 -08:00
|
|
|
const numrows = saturateSub(ui.rows, 3);
|
2021-05-07 07:16:35 -08:00
|
|
|
if (cursor_idx < current_view.top) current_view.top = cursor_idx;
|
|
|
|
|
if (cursor_idx >= current_view.top + numrows) current_view.top = cursor_idx - numrows + 1;
|
2021-05-07 02:01:00 -08:00
|
|
|
|
2021-05-06 09:15:47 -08:00
|
|
|
var i: u32 = 0;
|
2021-05-23 07:18:49 -08:00
|
|
|
var sel_row: u32 = 0;
|
2021-05-07 02:01:00 -08:00
|
|
|
while (i < numrows) : (i += 1) {
|
2021-05-07 07:16:35 -08:00
|
|
|
if (i+current_view.top >= dir_items.items.len) break;
|
2021-05-07 02:01:00 -08:00
|
|
|
var row = Row{
|
|
|
|
|
.row = i+2,
|
2021-05-07 07:16:35 -08:00
|
|
|
.item = dir_items.items[i+current_view.top],
|
|
|
|
|
.bg = if (i+current_view.top == cursor_idx) .sel else .default,
|
2021-05-07 02:01:00 -08:00
|
|
|
};
|
2021-05-23 07:18:49 -08:00
|
|
|
if (row.bg == .sel) sel_row = i+2;
|
2021-05-06 09:15:47 -08:00
|
|
|
try row.draw();
|
|
|
|
|
}
|
|
|
|
|
|
2021-05-02 22:01:09 -08:00
|
|
|
ui.style(.hd);
|
2021-05-04 08:01:49 -08:00
|
|
|
ui.move(ui.rows-1, 0);
|
|
|
|
|
ui.hline(' ', ui.cols);
|
|
|
|
|
ui.move(ui.rows-1, 1);
|
2021-05-24 01:02:26 -08:00
|
|
|
ui.style(if (main.config.show_blocks) .bold_hd else .hd);
|
2021-05-06 09:15:47 -08:00
|
|
|
ui.addstr("Total disk usage: ");
|
|
|
|
|
ui.addsize(.hd, blocksToSize(dir_parents.top().entry.blocks));
|
2021-05-24 01:02:26 -08:00
|
|
|
ui.style(if (main.config.show_blocks) .hd else .bold_hd);
|
2021-05-06 09:15:47 -08:00
|
|
|
ui.addstr(" Apparent size: ");
|
|
|
|
|
ui.addsize(.hd, dir_parents.top().entry.size);
|
|
|
|
|
ui.addstr(" Items: ");
|
2021-05-24 01:02:26 -08:00
|
|
|
ui.addnum(.hd, dir_parents.top().items);
|
2021-05-11 02:55:15 -08:00
|
|
|
|
|
|
|
|
if (need_confirm_quit) drawQuit();
|
2021-05-23 07:18:49 -08:00
|
|
|
if (sel_row > 0) ui.move(sel_row, 0);
|
2021-05-02 22:01:09 -08:00
|
|
|
}
|
2021-05-07 02:01:00 -08:00
|
|
|
|
2021-05-24 01:02:26 -08:00
|
|
|
fn sortToggle(col: main.config.SortCol, default_order: main.config.SortOrder) void {
|
2021-05-07 02:01:00 -08:00
|
|
|
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;
|
|
|
|
|
main.config.sort_col = col;
|
|
|
|
|
sortDir();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn key(ch: i32) !void {
|
2021-05-11 02:55:15 -08:00
|
|
|
if (need_confirm_quit) {
|
|
|
|
|
switch (ch) {
|
|
|
|
|
'y', 'Y' => if (need_confirm_quit) ui.quit(),
|
|
|
|
|
else => need_confirm_quit = false,
|
|
|
|
|
}
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2021-05-07 07:16:35 -08:00
|
|
|
defer current_view.save();
|
|
|
|
|
|
2021-05-07 02:01:00 -08:00
|
|
|
switch (ch) {
|
2021-05-11 02:55:15 -08:00
|
|
|
'q' => if (main.config.confirm_quit) { need_confirm_quit = true; } else ui.quit(),
|
2021-05-07 02:01:00 -08:00
|
|
|
|
|
|
|
|
// Selection
|
|
|
|
|
'j', ui.c.KEY_DOWN => {
|
|
|
|
|
if (cursor_idx+1 < dir_items.items.len) cursor_idx += 1;
|
|
|
|
|
},
|
|
|
|
|
'k', ui.c.KEY_UP => {
|
|
|
|
|
if (cursor_idx > 0) cursor_idx -= 1;
|
|
|
|
|
},
|
|
|
|
|
ui.c.KEY_HOME => cursor_idx = 0,
|
|
|
|
|
ui.c.KEY_END, ui.c.KEY_LL => cursor_idx = saturateSub(dir_items.items.len, 1),
|
|
|
|
|
ui.c.KEY_PPAGE => cursor_idx = saturateSub(cursor_idx, saturateSub(ui.rows, 3)),
|
|
|
|
|
ui.c.KEY_NPAGE => cursor_idx = std.math.min(saturateSub(dir_items.items.len, 1), cursor_idx + saturateSub(ui.rows, 3)),
|
|
|
|
|
|
|
|
|
|
// Sort & filter settings
|
|
|
|
|
'n' => sortToggle(.name, .asc),
|
|
|
|
|
's' => sortToggle(if (main.config.show_blocks) .blocks else .size, .desc),
|
|
|
|
|
'C' => sortToggle(.items, .desc),
|
|
|
|
|
'M' => if (main.config.extended) sortToggle(.mtime, .desc),
|
|
|
|
|
'e' => {
|
|
|
|
|
main.config.show_hidden = !main.config.show_hidden;
|
|
|
|
|
try loadDir();
|
|
|
|
|
},
|
|
|
|
|
't' => {
|
|
|
|
|
main.config.sort_dirsfirst = !main.config.sort_dirsfirst;
|
|
|
|
|
sortDir();
|
|
|
|
|
},
|
|
|
|
|
'a' => {
|
|
|
|
|
main.config.show_blocks = !main.config.show_blocks;
|
|
|
|
|
if (main.config.show_blocks and main.config.sort_col == .size) {
|
|
|
|
|
main.config.sort_col = .blocks;
|
|
|
|
|
sortDir();
|
|
|
|
|
}
|
|
|
|
|
if (!main.config.show_blocks and main.config.sort_col == .blocks) {
|
|
|
|
|
main.config.sort_col = .size;
|
|
|
|
|
sortDir();
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
|
2021-05-07 07:16:35 -08:00
|
|
|
// Navigation
|
|
|
|
|
10, 'l', ui.c.KEY_RIGHT => {
|
2021-05-11 02:55:15 -08:00
|
|
|
if (dir_items.items.len == 0) {
|
|
|
|
|
} else if (dir_items.items[cursor_idx]) |e| {
|
2021-05-07 07:16:35 -08:00
|
|
|
if (e.dir()) |d| {
|
|
|
|
|
try dir_parents.push(d);
|
|
|
|
|
try loadDir();
|
|
|
|
|
}
|
|
|
|
|
} else if (dir_parents.top() != model.root) {
|
|
|
|
|
dir_parents.pop();
|
|
|
|
|
try loadDir();
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
'h', '<', ui.c.KEY_BACKSPACE, ui.c.KEY_LEFT => {
|
|
|
|
|
if (dir_parents.top() != model.root) {
|
|
|
|
|
dir_parents.pop();
|
|
|
|
|
try loadDir();
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
|
2021-05-23 07:18:49 -08:00
|
|
|
// Display settings
|
|
|
|
|
'c' => main.config.show_items = !main.config.show_items,
|
|
|
|
|
'm' => if (main.config.extended) { main.config.show_mtime = !main.config.show_mtime; },
|
|
|
|
|
'g' => main.config.show_graph = switch (main.config.show_graph) {
|
|
|
|
|
.off => .graph,
|
|
|
|
|
.graph => .percent,
|
|
|
|
|
.percent => .both,
|
|
|
|
|
.both => .off,
|
|
|
|
|
},
|
|
|
|
|
|
2021-05-07 02:01:00 -08:00
|
|
|
else => {}
|
|
|
|
|
}
|
|
|
|
|
}
|