2024-04-20 05:49:42 -08:00
|
|
|
// SPDX-FileCopyrightText: Yorhel <projects@yorhel.nl>
|
2021-07-18 01:36:05 -08:00
|
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
|
|
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");
|
2024-07-14 06:21:59 -08:00
|
|
|
const sink = @import("sink.zig");
|
2024-07-27 01:58:06 -08:00
|
|
|
const mem_sink = @import("mem_sink.zig");
|
2024-08-05 23:46:17 -08:00
|
|
|
const bin_reader = @import("bin_reader.zig");
|
2021-07-16 06:13:25 -08:00
|
|
|
const delete = @import("delete.zig");
|
2021-05-02 22:01:09 -08:00
|
|
|
const ui = @import("ui.zig");
|
2024-10-26 04:33:40 -08:00
|
|
|
const c = @import("c.zig").c;
|
2021-07-19 05:28:11 -08:00
|
|
|
const util = @import("util.zig");
|
2021-05-06 09:15:47 -08:00
|
|
|
|
2021-07-26 04:03:08 -08:00
|
|
|
// Currently opened directory.
|
|
|
|
|
pub var dir_parent: *model.Dir = undefined;
|
2024-08-05 23:46:17 -08:00
|
|
|
pub var dir_path: [:0]u8 = undefined;
|
|
|
|
|
var dir_parents = std.ArrayList(model.Ref).init(main.allocator);
|
|
|
|
|
var dir_alloc = std.heap.ArenaAllocator.init(main.allocator);
|
|
|
|
|
|
|
|
|
|
// Used to keep track of which dir is which ref, so we can enter it.
|
|
|
|
|
// Only used for binreader browsing.
|
|
|
|
|
var dir_refs = std.ArrayList(struct { ptr: *model.Dir, ref: u64 }).init(main.allocator);
|
2021-05-07 07:16:35 -08:00
|
|
|
|
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-30 07:02:57 -08:00
|
|
|
var dir_has_shared: bool = false;
|
2024-08-07 00:28:03 -08:00
|
|
|
var dir_loading: u64 = 0;
|
2021-05-23 07:18:49 -08:00
|
|
|
|
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.
|
|
|
|
|
cursor_hash: u64 = 0,
|
|
|
|
|
|
2024-08-05 23:46:17 -08:00
|
|
|
fn dirHash() u64 {
|
|
|
|
|
return std.hash.Wyhash.hash(0, dir_path);
|
2021-05-07 07:16:35 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 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
|
2024-08-05 23:46:17 -08:00
|
|
|
else if (dir_items.items[cursor_idx]) |e| e.nameHash()
|
|
|
|
|
else 0;
|
|
|
|
|
opened_dir_views.put(dirHash(), self.*) catch {};
|
2021-05-07 07:16:35 -08:00
|
|
|
}
|
|
|
|
|
|
2021-07-26 04:03:08 -08:00
|
|
|
// Should be called after dir_parent or dir_items has changed, will load the last saved view and find the proper cursor_idx.
|
2024-08-05 23:46:17 -08:00
|
|
|
fn load(self: *@This(), sel: u64) void {
|
|
|
|
|
if (opened_dir_views.get(dirHash())) |v| self.* = v
|
2021-05-07 07:16:35 -08:00
|
|
|
else self.* = @This(){};
|
2021-05-11 02:55:15 -08:00
|
|
|
cursor_idx = 0;
|
2023-04-09 07:45:11 -08:00
|
|
|
for (dir_items.items, 0..) |e, i| {
|
2024-08-05 23:46:17 -08:00
|
|
|
const h = if (e) |x| x.nameHash() else 0;
|
|
|
|
|
if (if (sel != 0) h == sel else self.cursor_hash == h) {
|
2021-05-07 07:16:35 -08:00
|
|
|
cursor_idx = i;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
var current_view = View{};
|
|
|
|
|
|
|
|
|
|
// Directories the user has browsed to before, and which item was last selected.
|
2024-08-05 23:46:17 -08:00
|
|
|
// The key is the hash of dir_path;
|
|
|
|
|
var opened_dir_views = std.AutoHashMap(u64, 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.?;
|
|
|
|
|
|
2024-08-01 04:20:34 -08:00
|
|
|
if (main.config.sort_dirsfirst and a.pack.etype.isDirectory() != b.pack.etype.isDirectory())
|
|
|
|
|
return a.pack.etype.isDirectory();
|
2021-05-06 09:15:47 -08:00
|
|
|
|
|
|
|
|
switch (main.config.sort_col) {
|
|
|
|
|
.name => {}, // name sorting is the fallback
|
|
|
|
|
.blocks => {
|
2022-11-02 02:28:43 -08:00
|
|
|
if (sortIntLt(a.pack.blocks, b.pack.blocks)) |r| return r;
|
2021-05-06 09:15:47 -08:00
|
|
|
if (sortIntLt(a.size, b.size)) |r| return r;
|
|
|
|
|
},
|
|
|
|
|
.size => {
|
|
|
|
|
if (sortIntLt(a.size, b.size)) |r| return r;
|
2022-11-02 02:28:43 -08:00
|
|
|
if (sortIntLt(a.pack.blocks, b.pack.blocks)) |r| return r;
|
2021-05-06 09:15:47 -08:00
|
|
|
},
|
|
|
|
|
.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;
|
2022-11-02 02:28:43 -08:00
|
|
|
if (sortIntLt(a.pack.blocks, b.pack.blocks)) |r| return r;
|
2021-05-06 09:15:47 -08:00
|
|
|
if (sortIntLt(a.size, b.size)) |r| return r;
|
|
|
|
|
},
|
|
|
|
|
.mtime => {
|
2022-11-02 02:28:43 -08:00
|
|
|
if (!a.pack.isext or !b.pack.isext) return a.pack.isext;
|
2021-05-06 09:15:47 -08:00
|
|
|
if (sortIntLt(a.ext().?.mtime, b.ext().?.mtime)) |r| return r;
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
|
2023-03-04 22:31:31 -09:00
|
|
|
const an = (if (main.config.sort_order == .asc) a else b).name();
|
|
|
|
|
const bn = (if (main.config.sort_order == .asc) b else a).name();
|
|
|
|
|
return if (main.config.sort_natural) util.strnatcmp(an, bn) == .lt
|
|
|
|
|
else std.mem.lessThan(u8, an, bn);
|
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
|
2024-08-05 23:46:17 -08:00
|
|
|
fn sortDir(next_sel: u64) void {
|
2021-05-06 09:15:47 -08:00
|
|
|
// 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)..];
|
2023-04-09 07:45:11 -08:00
|
|
|
std.mem.sort(?*model.Entry, lst, {}, sortLt);
|
2021-07-16 06:13:25 -08:00
|
|
|
current_view.load(next_sel);
|
2021-05-06 09:15:47 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Must be called when:
|
2021-07-26 04:03:08 -08:00
|
|
|
// - dir_parent changes (i.e. we change directory)
|
2021-05-06 09:15:47 -08:00
|
|
|
// - config.show_hidden changes
|
|
|
|
|
// - files in this dir have been added or removed
|
2024-08-05 23:46:17 -08:00
|
|
|
pub fn loadDir(next_sel: u64) void {
|
2024-08-07 00:28:03 -08:00
|
|
|
// XXX: The current dir listing is wiped before loading the new one, which
|
|
|
|
|
// causes the screen to flicker a bit when the loading indicator is drawn.
|
|
|
|
|
// Should we keep the old listing around?
|
|
|
|
|
main.event_delay_timer.reset();
|
2024-08-05 23:46:17 -08:00
|
|
|
_ = dir_alloc.reset(.free_all);
|
2021-05-06 09:15:47 -08:00
|
|
|
dir_items.shrinkRetainingCapacity(0);
|
2024-08-05 23:46:17 -08:00
|
|
|
dir_refs.shrinkRetainingCapacity(0);
|
2021-05-23 07:18:49 -08:00
|
|
|
dir_max_size = 1;
|
|
|
|
|
dir_max_blocks = 1;
|
2021-05-30 07:02:57 -08:00
|
|
|
dir_has_shared = false;
|
2021-05-23 07:18:49 -08:00
|
|
|
|
2024-08-05 23:46:17 -08:00
|
|
|
if (dir_parents.items.len > 1)
|
2021-05-29 03:18:21 -08:00
|
|
|
dir_items.append(null) catch unreachable;
|
2024-08-05 23:46:17 -08:00
|
|
|
var ref = dir_parent.sub;
|
|
|
|
|
while (!ref.isNull()) {
|
|
|
|
|
const e =
|
|
|
|
|
if (main.config.binreader) bin_reader.get(ref.ref, dir_alloc.allocator())
|
|
|
|
|
else ref.ptr.?;
|
|
|
|
|
|
2022-11-02 02:28:43 -08:00
|
|
|
if (e.pack.blocks > dir_max_blocks) dir_max_blocks = e.pack.blocks;
|
2021-05-23 07:18:49 -08:00
|
|
|
if (e.size > dir_max_size) dir_max_size = e.size;
|
2021-05-30 07:02:57 -08:00
|
|
|
const shown = main.config.show_hidden or blk: {
|
2024-08-01 04:20:34 -08:00
|
|
|
const excl = switch (e.pack.etype) {
|
|
|
|
|
.pattern, .otherfs, .kernfs => true,
|
|
|
|
|
else => false,
|
|
|
|
|
};
|
2021-05-06 09:15:47 -08:00
|
|
|
const name = e.name();
|
2021-05-30 07:02:57 -08:00
|
|
|
break :blk !excl and name[0] != '.' and name[name.len-1] != '~';
|
|
|
|
|
};
|
|
|
|
|
if (shown) {
|
|
|
|
|
dir_items.append(e) catch unreachable;
|
2024-08-05 23:46:17 -08:00
|
|
|
if (e.dir()) |d| {
|
|
|
|
|
if (d.shared_blocks > 0 or d.shared_size > 0) dir_has_shared = true;
|
|
|
|
|
if (main.config.binreader) dir_refs.append(.{ .ptr = d, .ref = ref.ref }) catch unreachable;
|
|
|
|
|
}
|
2021-05-06 09:15:47 -08:00
|
|
|
}
|
2024-08-05 23:46:17 -08:00
|
|
|
|
|
|
|
|
ref = e.next;
|
2024-08-07 00:28:03 -08:00
|
|
|
dir_loading += 1;
|
|
|
|
|
if ((dir_loading & 65) == 0)
|
|
|
|
|
main.handleEvent(false, false);
|
2021-05-06 09:15:47 -08:00
|
|
|
}
|
2021-07-16 06:13:25 -08:00
|
|
|
sortDir(next_sel);
|
2024-08-07 00:28:03 -08:00
|
|
|
dir_loading = 0;
|
2021-05-06 09:15:47 -08:00
|
|
|
}
|
|
|
|
|
|
2024-08-05 23:46:17 -08:00
|
|
|
|
|
|
|
|
pub fn initRoot() void {
|
|
|
|
|
if (main.config.binreader) {
|
|
|
|
|
const ref = bin_reader.getRoot();
|
|
|
|
|
dir_parent = bin_reader.get(ref, main.allocator).dir() orelse ui.die("Invalid import\n", .{});
|
|
|
|
|
dir_parents.append(.{ .ref = ref }) catch unreachable;
|
|
|
|
|
} else {
|
|
|
|
|
dir_parent = model.root;
|
|
|
|
|
dir_parents.append(.{ .ptr = &dir_parent.entry }) catch unreachable;
|
|
|
|
|
}
|
|
|
|
|
dir_path = main.allocator.dupeZ(u8, dir_parent.entry.name()) catch unreachable;
|
|
|
|
|
loadDir(0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn enterSub(e: *model.Dir) void {
|
|
|
|
|
if (main.config.binreader) {
|
|
|
|
|
const ref = blk: {
|
|
|
|
|
for (dir_refs.items) |r| if (r.ptr == e) break :blk r.ref;
|
|
|
|
|
return;
|
|
|
|
|
};
|
|
|
|
|
dir_parent.entry.destroy(main.allocator);
|
|
|
|
|
dir_parent = bin_reader.get(ref, main.allocator).dir() orelse unreachable;
|
|
|
|
|
dir_parents.append(.{ .ref = ref }) catch unreachable;
|
|
|
|
|
} else {
|
|
|
|
|
dir_parent = e;
|
|
|
|
|
dir_parents.append(.{ .ptr = &e.entry }) catch unreachable;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const newpath = std.fs.path.joinZ(main.allocator, &[_][]const u8{ dir_path, e.entry.name() }) catch unreachable;
|
|
|
|
|
main.allocator.free(dir_path);
|
|
|
|
|
dir_path = newpath;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn enterParent() void {
|
|
|
|
|
std.debug.assert(dir_parents.items.len > 1);
|
|
|
|
|
|
|
|
|
|
_ = dir_parents.pop();
|
|
|
|
|
const p = dir_parents.items[dir_parents.items.len-1];
|
|
|
|
|
if (main.config.binreader) {
|
|
|
|
|
dir_parent.entry.destroy(main.allocator);
|
|
|
|
|
dir_parent = bin_reader.get(p.ref, main.allocator).dir() orelse unreachable;
|
|
|
|
|
} else
|
|
|
|
|
dir_parent = p.ptr.?.dir() orelse unreachable;
|
|
|
|
|
|
|
|
|
|
const newpath = main.allocator.dupeZ(u8, std.fs.path.dirname(dir_path) orelse unreachable) catch unreachable;
|
|
|
|
|
main.allocator.free(dir_path);
|
|
|
|
|
dir_path = newpath;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2021-05-06 09:15:47 -08:00
|
|
|
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;
|
2024-08-01 04:20:34 -08:00
|
|
|
const ch: u7 = switch (item.pack.etype) {
|
|
|
|
|
.dir => if (item.dir().?.pack.err) '!'
|
|
|
|
|
else if (item.dir().?.pack.suberr) '.'
|
2024-08-05 23:46:17 -08:00
|
|
|
else if (item.dir().?.sub.isNull()) 'e'
|
2024-08-01 04:20:34 -08:00
|
|
|
else return,
|
|
|
|
|
.link => 'H',
|
|
|
|
|
.pattern => '<',
|
|
|
|
|
.otherfs => '>',
|
|
|
|
|
.kernfs => '^',
|
|
|
|
|
.nonreg => '@',
|
|
|
|
|
else => return,
|
2021-05-06 09:15:47 -08:00
|
|
|
};
|
|
|
|
|
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-30 07:02:57 -08:00
|
|
|
var width = if (main.config.si) @as(u32, 9) else 10;
|
|
|
|
|
if (dir_has_shared and main.config.show_shared != .off)
|
2021-06-01 06:13:59 -08:00
|
|
|
width += 2 + width;
|
2021-05-30 07:02:57 -08:00
|
|
|
defer self.col += width;
|
2021-05-06 09:15:47 -08:00
|
|
|
const item = self.item orelse return;
|
2022-11-02 02:28:43 -08:00
|
|
|
const siz = if (main.config.show_blocks) util.blocksToSize(item.pack.blocks) else item.size;
|
2021-07-19 05:28:11 -08:00
|
|
|
var shr = if (item.dir()) |d| (if (main.config.show_blocks) util.blocksToSize(d.shared_blocks) else d.shared_size) else 0;
|
|
|
|
|
if (main.config.show_shared == .unique) shr = siz -| shr;
|
2021-05-30 07:02:57 -08:00
|
|
|
|
2021-05-06 09:15:47 -08:00
|
|
|
ui.move(self.row, self.col);
|
2021-05-30 07:02:57 -08:00
|
|
|
ui.addsize(self.bg, siz);
|
2022-02-02 00:30:36 -09:00
|
|
|
if (dir_has_shared and shr > 0 and main.config.show_shared != .off) {
|
2021-05-30 07:02:57 -08:00
|
|
|
self.bg.fg(.flag);
|
2021-06-01 06:13:59 -08:00
|
|
|
ui.addstr(if (main.config.show_shared == .unique) " U " else " S ");
|
2021-05-30 07:02:57 -08:00
|
|
|
ui.addsize(self.bg, shr);
|
|
|
|
|
}
|
2021-05-06 09:15:47 -08:00
|
|
|
}
|
|
|
|
|
|
2021-05-23 07:18:49 -08:00
|
|
|
fn graph(self: *Self) void {
|
2021-10-05 07:16:59 -08:00
|
|
|
if ((!main.config.show_graph and !main.config.show_percent) or self.col + 20 > ui.cols) return;
|
2021-05-23 07:18:49 -08:00
|
|
|
|
2023-04-09 07:45:11 -08:00
|
|
|
const bar_size = @max(ui.cols/7, 10);
|
2021-10-05 07:16:59 -08:00
|
|
|
defer self.col += 3
|
|
|
|
|
+ (if (main.config.show_graph) bar_size else 0)
|
|
|
|
|
+ (if (main.config.show_percent) @as(u32, 6) else 0)
|
|
|
|
|
+ (if (main.config.show_graph and main.config.show_percent) @as(u32, 1) else 0);
|
2021-05-23 07:18:49 -08:00
|
|
|
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('[');
|
2021-10-05 07:16:59 -08:00
|
|
|
if (main.config.show_percent) {
|
2021-05-23 07:18:49 -08:00
|
|
|
self.bg.fg(.num);
|
2024-08-03 05:37:52 -08:00
|
|
|
var num : u64 = if (main.config.show_blocks) item.pack.blocks else item.size;
|
|
|
|
|
var denom : u64 = if (main.config.show_blocks) dir_parent.entry.pack.blocks else dir_parent.entry.size;
|
|
|
|
|
if (num > (1<<54)) { // avoid overflow
|
|
|
|
|
num >>= 10;
|
|
|
|
|
denom >>= 10;
|
|
|
|
|
}
|
2024-08-04 21:07:39 -08:00
|
|
|
ui.addstr(&util.fmt5dec(@intCast( @min(1000, (num * 1000 + (denom / 2)) / @max(1, denom) ))));
|
2021-05-23 07:18:49 -08:00
|
|
|
self.bg.fg(.default);
|
|
|
|
|
ui.addch('%');
|
|
|
|
|
}
|
2021-10-05 07:16:59 -08:00
|
|
|
if (main.config.show_graph and main.config.show_percent) ui.addch(' ');
|
|
|
|
|
if (main.config.show_graph) {
|
2022-02-03 06:07:37 -09:00
|
|
|
var max = if (main.config.show_blocks) dir_max_blocks else dir_max_size;
|
2022-11-02 02:28:43 -08:00
|
|
|
var num = if (main.config.show_blocks) item.pack.blocks else item.size;
|
2022-02-03 06:07:37 -09:00
|
|
|
if (max < bar_size) {
|
|
|
|
|
max *= bar_size;
|
|
|
|
|
num *= bar_size;
|
|
|
|
|
}
|
2023-04-09 07:45:11 -08:00
|
|
|
|
2022-02-03 06:07:37 -09:00
|
|
|
const perblock = std.math.divFloor(u64, max, bar_size) catch unreachable;
|
2021-05-23 07:18:49 -08:00
|
|
|
self.bg.fg(.graph);
|
2023-04-09 07:45:11 -08:00
|
|
|
for (0..bar_size) |_| {
|
|
|
|
|
const frac = @min(@as(usize, 8), (num *| 8) / perblock);
|
2022-02-03 06:07:37 -09:00
|
|
|
ui.addstr(switch (main.config.graph_style) {
|
|
|
|
|
.hash => ([_][:0]const u8{ " ", " ", " ", " ", " ", " ", " ", " ", "#" })[frac],
|
|
|
|
|
.half => ([_][:0]const u8{ " ", " ", " ", " ", "▌", "▌", "▌", "▌", "█" })[frac],
|
2022-03-14 05:27:03 -08:00
|
|
|
.eighth => ([_][:0]const u8{ " ", "▏", "▎", "▍", "▌", "▋", "▊", "▉", "█" })[frac],
|
2022-02-03 06:07:37 -09:00
|
|
|
});
|
|
|
|
|
num -|= perblock;
|
2021-05-24 01:02:26 -08:00
|
|
|
}
|
2021-05-23 07:18:49 -08:00
|
|
|
}
|
|
|
|
|
self.bg.fg(.default);
|
|
|
|
|
ui.addch(']');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn items(self: *Self) void {
|
2021-05-30 07:02:57 -08:00
|
|
|
if (!main.config.show_items or self.col + 10 > ui.cols) return;
|
2021-05-23 07:18:49 -08:00
|
|
|
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)
|
2024-08-03 05:37:52 -08:00
|
|
|
else if (n < 999_950) {
|
|
|
|
|
ui.addstr(&util.fmt5dec(@intCast( (n + 50) / 100 )));
|
2021-05-23 07:18:49 -08:00
|
|
|
self.bg.fg(.default);
|
|
|
|
|
ui.addch('k');
|
2024-08-03 05:37:52 -08:00
|
|
|
} else if (n < 999_950_000) {
|
|
|
|
|
ui.addstr(&util.fmt5dec(@intCast( (n + 50_000) / 100_000 )));
|
2021-05-23 07:18:49 -08:00
|
|
|
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 {
|
2021-05-30 07:02:57 -08:00
|
|
|
if (!main.config.show_mtime or self.col + 37 > ui.cols) return;
|
2021-05-23 07:18:49 -08:00
|
|
|
defer self.col += 27;
|
|
|
|
|
ui.move(self.row, self.col+1);
|
2024-07-16 03:09:54 -08:00
|
|
|
const ext = if (self.item) |e| e.ext() else dir_parent.entry.ext();
|
2024-08-09 08:24:59 -08:00
|
|
|
if (ext) |e| {
|
|
|
|
|
if (e.pack.hasmtime) {
|
|
|
|
|
ui.addts(self.bg, e.mtime);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
ui.addstr(" no mtime");
|
2021-05-23 07:18:49 -08:00
|
|
|
}
|
|
|
|
|
|
2021-05-29 03:18:21 -08:00
|
|
|
fn name(self: *Self) void {
|
2021-05-23 07:18:49 -08:00
|
|
|
ui.move(self.row, self.col);
|
2021-05-06 09:15:47 -08:00
|
|
|
if (self.item) |i| {
|
2022-11-02 02:28:43 -08:00
|
|
|
self.bg.fg(if (i.pack.etype == .dir) .dir else .default);
|
2024-08-01 04:20:34 -08:00
|
|
|
ui.addch(if (i.pack.etype.isDirectory()) '/' else ' ');
|
2022-02-02 00:30:36 -09:00
|
|
|
ui.addstr(ui.shorten(ui.toUtf8(i.name()), 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
|
|
|
}
|
|
|
|
|
|
2021-05-29 03:18:21 -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-29 03:18:21 -08:00
|
|
|
self.name();
|
2021-05-06 09:15:47 -08:00
|
|
|
}
|
|
|
|
|
};
|
2021-05-02 22:01:09 -08:00
|
|
|
|
2021-07-18 04:08:07 -08:00
|
|
|
var state: enum { main, quit, help, info } = .main;
|
2024-08-06 23:44:19 -08:00
|
|
|
var message: ?[]const [:0]const u8 = null;
|
2021-06-11 03:05:39 -08:00
|
|
|
|
|
|
|
|
const quit = struct {
|
|
|
|
|
fn draw() 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-11 02:55:15 -08:00
|
|
|
|
2021-06-11 03:05:39 -08:00
|
|
|
fn keyInput(ch: i32) void {
|
|
|
|
|
switch (ch) {
|
|
|
|
|
'y', 'Y' => ui.quit(),
|
|
|
|
|
else => state = .main,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const info = struct {
|
2021-07-06 08:28:35 -08:00
|
|
|
const Tab = enum { info, links };
|
|
|
|
|
|
|
|
|
|
var tab: Tab = .info;
|
|
|
|
|
var entry: ?*model.Entry = null;
|
2021-07-28 00:29:15 -08:00
|
|
|
var links: ?std.ArrayList(*model.Link) = null;
|
2021-07-06 08:28:35 -08:00
|
|
|
var links_top: usize = 0;
|
|
|
|
|
var links_idx: usize = 0;
|
|
|
|
|
|
2021-07-28 00:29:15 -08:00
|
|
|
fn lt(_: void, a: *model.Link, b: *model.Link) bool {
|
2023-11-19 23:40:20 -09:00
|
|
|
const pa = a.path(false);
|
|
|
|
|
const pb = b.path(false);
|
2021-07-28 00:29:15 -08:00
|
|
|
defer main.allocator.free(pa);
|
|
|
|
|
defer main.allocator.free(pb);
|
|
|
|
|
return std.mem.lessThan(u8, pa, pb);
|
|
|
|
|
}
|
|
|
|
|
|
2021-07-06 08:28:35 -08:00
|
|
|
// Set the displayed entry to the currently selected item and open the tab.
|
|
|
|
|
fn set(e: ?*model.Entry, t: Tab) void {
|
|
|
|
|
if (e != entry) {
|
|
|
|
|
if (links) |*l| l.deinit();
|
|
|
|
|
links = null;
|
|
|
|
|
links_top = 0;
|
|
|
|
|
links_idx = 0;
|
|
|
|
|
}
|
|
|
|
|
entry = e;
|
|
|
|
|
if (e == null) {
|
|
|
|
|
state = .main;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
state = .info;
|
|
|
|
|
tab = t;
|
2024-08-06 23:44:19 -08:00
|
|
|
if (tab == .links and links == null and !main.config.binreader) {
|
2021-07-28 00:29:15 -08:00
|
|
|
var list = std.ArrayList(*model.Link).init(main.allocator);
|
|
|
|
|
var l = e.?.link().?;
|
|
|
|
|
while (true) {
|
|
|
|
|
list.append(l) catch unreachable;
|
|
|
|
|
l = l.next;
|
|
|
|
|
if (&l.entry == e)
|
|
|
|
|
break;
|
2021-07-06 08:28:35 -08:00
|
|
|
}
|
2024-07-18 11:43:18 -08:00
|
|
|
std.sort.heap(*model.Link, list.items, {}, lt);
|
2023-04-09 07:45:11 -08:00
|
|
|
for (list.items, 0..) |n,i| if (&n.entry == e) { links_idx = i; };
|
2021-07-28 00:29:15 -08:00
|
|
|
links = list;
|
2021-07-06 08:28:35 -08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn drawLinks(box: ui.Box, row: *u32, rows: u32, cols: u32) void {
|
2024-08-06 23:44:19 -08:00
|
|
|
if (main.config.binreader) {
|
|
|
|
|
box.move(2, 2);
|
|
|
|
|
ui.addstr("This feature is not available when reading from file.");
|
|
|
|
|
return;
|
|
|
|
|
}
|
2021-07-19 05:28:11 -08:00
|
|
|
const numrows = rows -| 4;
|
2021-07-06 08:28:35 -08:00
|
|
|
if (links_idx < links_top) links_top = links_idx;
|
|
|
|
|
if (links_idx >= links_top + numrows) links_top = links_idx - numrows + 1;
|
|
|
|
|
|
2023-04-09 07:45:11 -08:00
|
|
|
for (0..numrows) |i| {
|
2021-07-28 00:29:15 -08:00
|
|
|
if (i + links_top >= links.?.items.len) break;
|
|
|
|
|
const e = links.?.items[i+links_top];
|
2021-07-06 08:28:35 -08:00
|
|
|
ui.style(if (i+links_top == links_idx) .sel else .default);
|
|
|
|
|
box.move(row.*, 2);
|
2021-07-28 00:29:15 -08:00
|
|
|
ui.addch(if (&e.entry == entry) '*' else ' ');
|
|
|
|
|
const path = e.path(false);
|
|
|
|
|
defer main.allocator.free(path);
|
2021-07-19 05:28:11 -08:00
|
|
|
ui.addstr(ui.shorten(ui.toUtf8(path), cols -| 5));
|
2021-07-06 08:28:35 -08:00
|
|
|
row.* += 1;
|
|
|
|
|
}
|
|
|
|
|
ui.style(.default);
|
|
|
|
|
box.move(rows-2, 4);
|
2021-07-28 00:29:15 -08:00
|
|
|
ui.addprint("{:>3}/{}", .{ links_idx+1, links.?.items.len });
|
2021-07-06 08:28:35 -08:00
|
|
|
}
|
2021-06-11 03:05:39 -08:00
|
|
|
|
2021-07-06 08:28:35 -08:00
|
|
|
fn drawSizeRow(box: ui.Box, row: *u32, label: [:0]const u8, size: u64) void {
|
2021-06-11 03:05:39 -08:00
|
|
|
box.move(row.*, 3);
|
|
|
|
|
ui.addstr(label);
|
|
|
|
|
ui.addsize(.default, size);
|
|
|
|
|
ui.addstr(" (");
|
|
|
|
|
ui.addnum(.default, size);
|
|
|
|
|
ui.addch(')');
|
|
|
|
|
row.* += 1;
|
|
|
|
|
}
|
|
|
|
|
|
2021-07-06 08:28:35 -08:00
|
|
|
fn drawSize(box: ui.Box, row: *u32, label: [:0]const u8, size: u64, shared: u64) void {
|
2021-06-11 03:05:39 -08:00
|
|
|
ui.style(.bold);
|
|
|
|
|
drawSizeRow(box, row, label, size);
|
|
|
|
|
if (shared > 0) {
|
|
|
|
|
ui.style(.default);
|
|
|
|
|
drawSizeRow(box, row, " > shared: ", shared);
|
2021-07-19 05:28:11 -08:00
|
|
|
drawSizeRow(box, row, " > unique: ", size -| shared);
|
2021-06-11 03:05:39 -08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-07-06 08:28:35 -08:00
|
|
|
fn drawInfo(box: ui.Box, row: *u32, cols: u32, e: *model.Entry) void {
|
2021-06-11 03:05:39 -08:00
|
|
|
// Name
|
2021-07-06 08:28:35 -08:00
|
|
|
box.move(row.*, 3);
|
2021-06-11 03:05:39 -08:00
|
|
|
ui.style(.bold);
|
|
|
|
|
ui.addstr("Name: ");
|
|
|
|
|
ui.style(.default);
|
|
|
|
|
ui.addstr(ui.shorten(ui.toUtf8(e.name()), cols-11));
|
2021-07-06 08:28:35 -08:00
|
|
|
row.* += 1;
|
2021-06-11 03:05:39 -08:00
|
|
|
|
|
|
|
|
// Type / Mode+UID+GID
|
2021-07-06 08:28:35 -08:00
|
|
|
box.move(row.*, 3);
|
2021-06-11 03:05:39 -08:00
|
|
|
ui.style(.bold);
|
|
|
|
|
if (e.ext()) |ext| {
|
|
|
|
|
var buf: [32]u8 = undefined;
|
2024-08-09 08:24:59 -08:00
|
|
|
if (ext.pack.hasmode) {
|
|
|
|
|
ui.addstr("Mode: ");
|
|
|
|
|
ui.style(.default);
|
|
|
|
|
ui.addmode(ext.mode);
|
|
|
|
|
ui.style(.bold);
|
|
|
|
|
}
|
|
|
|
|
if (ext.pack.hasuid) {
|
|
|
|
|
ui.addstr(" UID: ");
|
|
|
|
|
ui.style(.default);
|
|
|
|
|
ui.addstr(std.fmt.bufPrintZ(&buf, "{d:<6}", .{ ext.uid }) catch unreachable);
|
|
|
|
|
ui.style(.bold);
|
|
|
|
|
}
|
|
|
|
|
if (ext.pack.hasgid) {
|
|
|
|
|
ui.addstr(" GID: ");
|
|
|
|
|
ui.style(.default);
|
|
|
|
|
ui.addstr(std.fmt.bufPrintZ(&buf, "{d:<6}", .{ ext.gid }) catch unreachable);
|
|
|
|
|
}
|
2021-06-11 03:05:39 -08:00
|
|
|
} else {
|
|
|
|
|
ui.addstr("Type: ");
|
|
|
|
|
ui.style(.default);
|
2024-08-01 04:20:34 -08:00
|
|
|
ui.addstr(switch (e.pack.etype) {
|
|
|
|
|
.dir => "Directory",
|
|
|
|
|
.nonreg => "Other",
|
|
|
|
|
.reg, .link => "File",
|
|
|
|
|
else => "Excluded",
|
|
|
|
|
});
|
2021-06-11 03:05:39 -08:00
|
|
|
}
|
2021-07-06 08:28:35 -08:00
|
|
|
row.* += 1;
|
2021-06-11 03:05:39 -08:00
|
|
|
|
|
|
|
|
// Last modified
|
|
|
|
|
if (e.ext()) |ext| {
|
2024-08-09 08:24:59 -08:00
|
|
|
if (ext.pack.hasmtime) {
|
|
|
|
|
box.move(row.*, 3);
|
|
|
|
|
ui.style(.bold);
|
|
|
|
|
ui.addstr("Last modified: ");
|
|
|
|
|
ui.addts(.default, ext.mtime);
|
|
|
|
|
row.* += 1;
|
|
|
|
|
}
|
2021-06-11 03:05:39 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Disk usage & Apparent size
|
2022-11-02 02:28:43 -08:00
|
|
|
drawSize(box, row, " Disk usage: ", util.blocksToSize(e.pack.blocks), if (e.dir()) |d| util.blocksToSize(d.shared_blocks) else 0);
|
2021-07-19 05:28:11 -08:00
|
|
|
drawSize(box, row, "Apparent size: ", e.size, if (e.dir()) |d| d.shared_size else 0);
|
2021-06-11 03:05:39 -08:00
|
|
|
|
|
|
|
|
// Number of items
|
|
|
|
|
if (e.dir()) |d| {
|
2021-07-06 08:28:35 -08:00
|
|
|
box.move(row.*, 3);
|
2021-06-11 03:05:39 -08:00
|
|
|
ui.style(.bold);
|
|
|
|
|
ui.addstr(" Sub items: ");
|
|
|
|
|
ui.addnum(.default, d.items);
|
2021-07-06 08:28:35 -08:00
|
|
|
row.* += 1;
|
2021-06-11 03:05:39 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Number of links + inode (dev?)
|
|
|
|
|
if (e.link()) |l| {
|
2021-07-06 08:28:35 -08:00
|
|
|
box.move(row.*, 3);
|
2021-06-11 03:05:39 -08:00
|
|
|
ui.style(.bold);
|
|
|
|
|
ui.addstr(" Link count: ");
|
2024-07-17 04:04:17 -08:00
|
|
|
ui.addnum(.default, l.pack.nlink);
|
2021-07-06 08:28:35 -08:00
|
|
|
box.move(row.*, 23);
|
2021-06-11 03:05:39 -08:00
|
|
|
ui.style(.bold);
|
|
|
|
|
ui.addstr(" Inode: ");
|
|
|
|
|
ui.style(.default);
|
|
|
|
|
var buf: [32]u8 = undefined;
|
|
|
|
|
ui.addstr(std.fmt.bufPrintZ(&buf, "{}", .{ l.ino }) catch unreachable);
|
2021-07-06 08:28:35 -08:00
|
|
|
row.* += 1;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn draw() void {
|
|
|
|
|
const e = dir_items.items[cursor_idx].?;
|
|
|
|
|
// XXX: The dynamic height is a bit jarring, especially when that
|
|
|
|
|
// causes the same lines of information to be placed on different rows
|
|
|
|
|
// for each item. Think it's better to have a dynamic height based on
|
|
|
|
|
// terminal size and scroll if the content doesn't fit.
|
|
|
|
|
const rows = 5 // border + padding + close message
|
2024-08-06 23:44:19 -08:00
|
|
|
+ if (tab == .links and !main.config.binreader) 8 else
|
2021-07-06 08:28:35 -08:00
|
|
|
4 // name + type + disk usage + apparent size
|
|
|
|
|
+ (if (e.ext() != null) @as(u32, 1) else 0) // last modified
|
|
|
|
|
+ (if (e.link() != null) @as(u32, 1) else 0) // link count
|
|
|
|
|
+ (if (e.dir()) |d| 1 // sub items
|
|
|
|
|
+ (if (d.shared_size > 0) @as(u32, 2) else 0)
|
|
|
|
|
+ (if (d.shared_blocks > 0) @as(u32, 2) else 0)
|
|
|
|
|
else 0);
|
|
|
|
|
const cols = 60; // TODO: dynamic width?
|
|
|
|
|
const box = ui.Box.create(rows, cols, "Item info");
|
|
|
|
|
var row: u32 = 2;
|
|
|
|
|
|
|
|
|
|
// Tabs
|
2022-11-02 02:28:43 -08:00
|
|
|
if (e.pack.etype == .link) {
|
2021-07-06 08:28:35 -08:00
|
|
|
box.tab(cols-19, tab == .info, 1, "Info");
|
|
|
|
|
box.tab(cols-10, tab == .links, 2, "Links");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
switch (tab) {
|
|
|
|
|
.info => drawInfo(box, &row, cols, e),
|
|
|
|
|
.links => drawLinks(box, &row, rows, cols),
|
2021-06-11 03:05:39 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// "Press i to close this window"
|
2021-07-06 08:28:35 -08:00
|
|
|
box.move(rows-2, cols-30);
|
2021-06-11 03:05:39 -08:00
|
|
|
ui.style(.default);
|
|
|
|
|
ui.addstr("Press ");
|
|
|
|
|
ui.style(.key);
|
|
|
|
|
ui.addch('i');
|
|
|
|
|
ui.style(.default);
|
|
|
|
|
ui.addstr(" to close this window");
|
|
|
|
|
}
|
|
|
|
|
|
2021-07-06 08:28:35 -08:00
|
|
|
fn keyInput(ch: i32) bool {
|
2022-11-02 02:28:43 -08:00
|
|
|
if (entry.?.pack.etype == .link) {
|
2021-07-06 08:28:35 -08:00
|
|
|
switch (ch) {
|
2024-10-26 04:33:40 -08:00
|
|
|
'1', 'h', c.KEY_LEFT => { set(entry, .info); return true; },
|
|
|
|
|
'2', 'l', c.KEY_RIGHT => { set(entry, .links); return true; },
|
2021-07-06 08:28:35 -08:00
|
|
|
else => {},
|
|
|
|
|
}
|
|
|
|
|
}
|
2024-08-06 23:44:19 -08:00
|
|
|
if (tab == .links and !main.config.binreader) {
|
2021-07-28 00:29:15 -08:00
|
|
|
if (keyInputSelection(ch, &links_idx, links.?.items.len, 5))
|
2021-07-06 08:28:35 -08:00
|
|
|
return true;
|
|
|
|
|
if (ch == 10) { // Enter - go to selected entry
|
2021-07-28 00:29:15 -08:00
|
|
|
const l = links.?.items[links_idx];
|
|
|
|
|
dir_parent = l.parent;
|
2024-08-05 23:46:17 -08:00
|
|
|
loadDir(l.entry.nameHash());
|
2021-07-06 08:28:35 -08:00
|
|
|
set(null, .info);
|
|
|
|
|
}
|
|
|
|
|
}
|
2021-07-19 05:28:11 -08:00
|
|
|
if (keyInputSelection(ch, &cursor_idx, dir_items.items.len, ui.rows -| 3)) {
|
2021-07-06 08:28:35 -08:00
|
|
|
set(dir_items.items[cursor_idx], .info);
|
|
|
|
|
return true;
|
2021-06-11 03:05:39 -08:00
|
|
|
}
|
|
|
|
|
switch (ch) {
|
2021-07-06 08:28:35 -08:00
|
|
|
'i', 'q' => set(null, .info),
|
|
|
|
|
else => return false,
|
2021-06-11 03:05:39 -08:00
|
|
|
}
|
2021-07-06 08:28:35 -08:00
|
|
|
return true;
|
2021-06-11 03:05:39 -08:00
|
|
|
}
|
|
|
|
|
};
|
2021-05-11 02:55:15 -08:00
|
|
|
|
2021-07-18 04:08:07 -08:00
|
|
|
const help = struct {
|
|
|
|
|
const keys = [_][:0]const u8{
|
|
|
|
|
"up, k", "Move cursor up",
|
|
|
|
|
"down, j", "Move cursor down",
|
|
|
|
|
"right/enter", "Open selected directory",
|
|
|
|
|
"left, <, h", "Open parent directory",
|
|
|
|
|
"n", "Sort by name (ascending/descending)",
|
|
|
|
|
"s", "Sort by size (ascending/descending)",
|
|
|
|
|
"C", "Sort by items (ascending/descending)",
|
|
|
|
|
"M", "Sort by mtime (-e flag)",
|
|
|
|
|
"d", "Delete selected file or directory",
|
|
|
|
|
"t", "Toggle dirs before files when sorting",
|
|
|
|
|
"g", "Show percentage and/or graph",
|
2021-10-05 06:32:18 -08:00
|
|
|
"u", "Show/hide hard link shared sizes",
|
2021-07-18 04:08:07 -08:00
|
|
|
"a", "Toggle between apparent size and disk usage",
|
|
|
|
|
"c", "Toggle display of child item counts",
|
|
|
|
|
"m", "Toggle display of latest mtime (-e flag)",
|
|
|
|
|
"e", "Show/hide hidden or excluded files",
|
|
|
|
|
"i", "Show information about selected item",
|
|
|
|
|
"r", "Recalculate the current directory",
|
|
|
|
|
"b", "Spawn shell in current directory",
|
|
|
|
|
"q", "Quit ncdu"
|
|
|
|
|
};
|
|
|
|
|
const keylines = 10;
|
|
|
|
|
|
|
|
|
|
const flags = [_][:0]const u8{
|
|
|
|
|
"!", "An error occurred while reading this directory",
|
|
|
|
|
".", "An error occurred while reading a subdirectory",
|
|
|
|
|
"<", "File or directory is excluded from the statistics",
|
|
|
|
|
"e", "Empty directory",
|
|
|
|
|
">", "Directory was on another filesystem",
|
|
|
|
|
"@", "This is not a file nor a dir (symlink, socket, ...)",
|
|
|
|
|
"^", "Excluded Linux pseudo-filesystem",
|
|
|
|
|
"H", "Same file was already counted (hard link)",
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// It's kinda ugly, but for nostalgia's sake...
|
|
|
|
|
const logo = [_]u29{
|
|
|
|
|
0b11111100111110000001100110011,
|
|
|
|
|
0b11001100110000000001100110011,
|
|
|
|
|
0b11001100110000011111100110011,
|
|
|
|
|
0b11001100110000011001100110011,
|
|
|
|
|
0b11001100111110011111100111111,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
var tab: enum { keys, flags, about } = .keys;
|
|
|
|
|
var offset: u32 = 0;
|
|
|
|
|
|
|
|
|
|
fn drawKeys(box: ui.Box) void {
|
|
|
|
|
var line: u32 = 1;
|
|
|
|
|
var i = offset*2;
|
|
|
|
|
while (i < (offset + keylines)*2) : (i += 2) {
|
|
|
|
|
line += 1;
|
2023-04-09 07:45:11 -08:00
|
|
|
box.move(line, 13 - @as(u32, @intCast(keys[i].len)));
|
2021-07-18 04:08:07 -08:00
|
|
|
ui.style(.key);
|
|
|
|
|
ui.addstr(keys[i]);
|
|
|
|
|
ui.style(.default);
|
|
|
|
|
ui.addch(' ');
|
|
|
|
|
ui.addstr(keys[i+1]);
|
|
|
|
|
}
|
|
|
|
|
if (offset < keys.len/2-keylines) {
|
|
|
|
|
box.move(12, 25);
|
|
|
|
|
ui.addstr("-- more --");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn drawFlags(box: ui.Box) void {
|
|
|
|
|
box.move(2, 3);
|
|
|
|
|
ui.style(.bold);
|
|
|
|
|
ui.addstr("X [size] [graph] [file or directory]");
|
|
|
|
|
box.move(3, 4);
|
|
|
|
|
ui.style(.default);
|
|
|
|
|
ui.addstr("The X is only present in the following cases:");
|
|
|
|
|
var i: u32 = 0;
|
|
|
|
|
while (i < flags.len) : (i += 2) {
|
|
|
|
|
box.move(i/2+5, 4);
|
|
|
|
|
ui.style(.flag);
|
|
|
|
|
ui.addstr(flags[i]);
|
|
|
|
|
ui.style(.default);
|
|
|
|
|
ui.addch(' ');
|
|
|
|
|
ui.addstr(flags[i+1]);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn drawAbout(box: ui.Box) void {
|
2023-04-09 07:45:11 -08:00
|
|
|
for (logo, 0..) |s, n| {
|
|
|
|
|
box.move(@as(u32, @intCast(n+3)), 12);
|
2021-07-18 04:08:07 -08:00
|
|
|
var i: u5 = 28;
|
2023-04-09 07:45:11 -08:00
|
|
|
while (i != 0) : (i -= 1) {
|
2021-07-18 04:08:07 -08:00
|
|
|
ui.style(if (s & (@as(u29,1)<<i) > 0) .sel else .default);
|
|
|
|
|
ui.addch(' ');
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
ui.style(.default);
|
|
|
|
|
box.move(3, 43); ui.addstr("NCurses");
|
|
|
|
|
box.move(4, 43); ui.addstr("Disk");
|
|
|
|
|
box.move(5, 43); ui.addstr("Usage");
|
|
|
|
|
ui.style(.num);
|
|
|
|
|
box.move(7, 43); ui.addstr(main.program_version);
|
|
|
|
|
ui.style(.default);
|
2024-04-20 05:49:42 -08:00
|
|
|
box.move(9, 11); ui.addstr("Written by Yorhel <projects@yorhel.nl>");
|
2021-07-18 04:08:07 -08:00
|
|
|
box.move(10,16); ui.addstr("https://dev.yorhel.nl/ncdu");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn draw() void {
|
|
|
|
|
const box = ui.Box.create(15, 60, "ncdu help");
|
|
|
|
|
box.tab(30, tab == .keys, 1, "Keys");
|
|
|
|
|
box.tab(39, tab == .flags, 2, "Format");
|
|
|
|
|
box.tab(50, tab == .about, 3, "About");
|
|
|
|
|
|
|
|
|
|
box.move(13, 42);
|
|
|
|
|
ui.addstr("Press ");
|
|
|
|
|
ui.style(.key);
|
|
|
|
|
ui.addch('q');
|
|
|
|
|
ui.style(.default);
|
|
|
|
|
ui.addstr(" to close");
|
|
|
|
|
|
|
|
|
|
switch (tab) {
|
|
|
|
|
.keys => drawKeys(box),
|
|
|
|
|
.flags => drawFlags(box),
|
|
|
|
|
.about => drawAbout(box),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn keyInput(ch: i32) void {
|
|
|
|
|
const ctab = tab;
|
|
|
|
|
defer if (ctab != tab or state != .help) { offset = 0; };
|
|
|
|
|
switch (ch) {
|
|
|
|
|
'1' => tab = .keys,
|
|
|
|
|
'2' => tab = .flags,
|
|
|
|
|
'3' => tab = .about,
|
2024-10-26 04:33:40 -08:00
|
|
|
'h', c.KEY_LEFT => tab = if (tab == .about) .flags else .keys,
|
|
|
|
|
'l', c.KEY_RIGHT => tab = if (tab == .keys) .flags else .about,
|
|
|
|
|
'j', ' ', c.KEY_DOWN, c.KEY_NPAGE => {
|
2021-07-18 04:08:07 -08:00
|
|
|
const max = switch (tab) {
|
|
|
|
|
.keys => keys.len/2 - keylines,
|
|
|
|
|
else => @as(u32, 0),
|
|
|
|
|
};
|
|
|
|
|
if (offset < max)
|
|
|
|
|
offset += 1;
|
|
|
|
|
},
|
2024-10-26 04:33:40 -08:00
|
|
|
'k', c.KEY_UP, c.KEY_PPAGE => { if (offset > 0) offset -= 1; },
|
2021-07-18 04:08:07 -08:00
|
|
|
else => state = .main,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2021-05-29 03:18:21 -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");
|
2024-08-06 23:44:19 -08:00
|
|
|
if (main.config.binreader) {
|
|
|
|
|
ui.move(0, ui.cols -| 11);
|
|
|
|
|
ui.addstr("[from file]");
|
|
|
|
|
} else if (main.config.imported) {
|
2021-07-19 05:28:11 -08:00
|
|
|
ui.move(0, ui.cols -| 10);
|
2021-05-29 00:51:17 -08:00
|
|
|
ui.addstr("[imported]");
|
2021-10-06 01:05:56 -08:00
|
|
|
} else if (!main.config.can_delete.?) {
|
2021-07-19 05:28:11 -08:00
|
|
|
ui.move(0, ui.cols -| 10);
|
2021-05-06 09:15:47 -08:00
|
|
|
ui.addstr("[readonly]");
|
|
|
|
|
}
|
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);
|
2024-08-05 23:46:17 -08:00
|
|
|
ui.addstr(ui.shorten(ui.toUtf8(dir_path), ui.cols -| 5));
|
2021-05-23 07:18:49 -08:00
|
|
|
ui.style(.default);
|
2021-05-04 08:01:49 -08:00
|
|
|
ui.addch(' ');
|
2021-05-02 22:01:09 -08:00
|
|
|
|
2021-07-19 05:28:11 -08:00
|
|
|
const numrows = 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
|
|
|
|
2024-08-07 00:28:03 -08:00
|
|
|
var i: u32 = if (dir_loading > 0) numrows else 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-29 03:18:21 -08:00
|
|
|
row.draw();
|
2021-05-06 09:15:47 -08:00
|
|
|
}
|
|
|
|
|
|
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);
|
2023-03-02 22:41:55 -09:00
|
|
|
ui.move(ui.rows-1, 0);
|
2024-08-07 00:28:03 -08:00
|
|
|
if (dir_loading > 0) {
|
|
|
|
|
ui.addstr(" Loading... ");
|
|
|
|
|
ui.addnum(.hd, dir_loading);
|
|
|
|
|
} else {
|
|
|
|
|
ui.addch(if (main.config.show_blocks) '*' else ' ');
|
|
|
|
|
ui.style(if (main.config.show_blocks) .bold_hd else .hd);
|
|
|
|
|
ui.addstr("Total disk usage: ");
|
|
|
|
|
ui.addsize(.hd, util.blocksToSize(dir_parent.entry.pack.blocks));
|
|
|
|
|
ui.style(if (main.config.show_blocks) .hd else .bold_hd);
|
|
|
|
|
ui.addstr(" ");
|
|
|
|
|
ui.addch(if (main.config.show_blocks) ' ' else '*');
|
|
|
|
|
ui.addstr("Apparent size: ");
|
|
|
|
|
ui.addsize(.hd, dir_parent.entry.size);
|
|
|
|
|
ui.addstr(" Items: ");
|
|
|
|
|
ui.addnum(.hd, dir_parent.items);
|
|
|
|
|
}
|
2021-05-11 02:55:15 -08:00
|
|
|
|
2021-06-11 03:05:39 -08:00
|
|
|
switch (state) {
|
|
|
|
|
.main => {},
|
|
|
|
|
.quit => quit.draw(),
|
2021-07-18 04:08:07 -08:00
|
|
|
.help => help.draw(),
|
2021-06-11 03:05:39 -08:00
|
|
|
.info => info.draw(),
|
|
|
|
|
}
|
2021-07-14 01:20:27 -08:00
|
|
|
if (message) |m| {
|
2024-08-06 23:44:19 -08:00
|
|
|
const box = ui.Box.create(@intCast(m.len + 5), 60, "Message");
|
|
|
|
|
i = 2;
|
|
|
|
|
for (m) |ln| {
|
|
|
|
|
box.move(i, 2);
|
|
|
|
|
ui.addstr(ln);
|
|
|
|
|
i += 1;
|
|
|
|
|
}
|
|
|
|
|
box.move(i+1, 33);
|
2021-07-14 01:20:27 -08:00
|
|
|
ui.addstr("Press any key to continue");
|
|
|
|
|
}
|
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;
|
2024-08-05 23:46:17 -08:00
|
|
|
sortDir(0);
|
2021-05-07 02:01:00 -08:00
|
|
|
}
|
|
|
|
|
|
2021-07-06 08:28:35 -08:00
|
|
|
fn keyInputSelection(ch: i32, idx: *usize, len: usize, page: u32) bool {
|
2021-05-07 02:01:00 -08:00
|
|
|
switch (ch) {
|
2024-10-26 04:33:40 -08:00
|
|
|
'j', c.KEY_DOWN => {
|
2021-07-06 08:28:35 -08:00
|
|
|
if (idx.*+1 < len) idx.* += 1;
|
2021-05-07 02:01:00 -08:00
|
|
|
},
|
2024-10-26 04:33:40 -08:00
|
|
|
'k', c.KEY_UP => {
|
2021-07-06 08:28:35 -08:00
|
|
|
if (idx.* > 0) idx.* -= 1;
|
2021-05-07 02:01:00 -08:00
|
|
|
},
|
2024-10-26 04:33:40 -08:00
|
|
|
c.KEY_HOME => idx.* = 0,
|
|
|
|
|
c.KEY_END, c.KEY_LL => idx.* = len -| 1,
|
|
|
|
|
c.KEY_PPAGE => idx.* = idx.* -| page,
|
|
|
|
|
c.KEY_NPAGE => idx.* = @min(len -| 1, idx.* + page),
|
2021-06-11 03:05:39 -08:00
|
|
|
else => return false,
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn keyInput(ch: i32) void {
|
2024-08-07 00:28:03 -08:00
|
|
|
if (dir_loading > 0) return;
|
|
|
|
|
|
2021-07-06 08:28:35 -08:00
|
|
|
defer current_view.save();
|
|
|
|
|
|
2021-07-14 01:20:27 -08:00
|
|
|
if (message != null) {
|
|
|
|
|
message = null;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2021-06-11 03:05:39 -08:00
|
|
|
switch (state) {
|
|
|
|
|
.main => {}, // fallthrough
|
|
|
|
|
.quit => return quit.keyInput(ch),
|
2021-07-18 04:08:07 -08:00
|
|
|
.help => return help.keyInput(ch),
|
2021-07-06 08:28:35 -08:00
|
|
|
.info => if (info.keyInput(ch)) return,
|
2021-06-11 03:05:39 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
switch (ch) {
|
|
|
|
|
'q' => if (main.config.confirm_quit) { state = .quit; } else ui.quit(),
|
2021-07-18 04:08:07 -08:00
|
|
|
'?' => state = .help,
|
2021-07-16 06:13:25 -08:00
|
|
|
'i' => if (dir_items.items.len > 0) info.set(dir_items.items[cursor_idx], .info),
|
2021-07-13 03:33:38 -08:00
|
|
|
'r' => {
|
2024-08-06 23:44:19 -08:00
|
|
|
if (main.config.binreader)
|
|
|
|
|
message = &.{"Refresh feature is not available when reading from file."}
|
|
|
|
|
else if (!main.config.can_refresh.? and main.config.imported)
|
|
|
|
|
message = &.{"Refresh feature disabled.", "Re-run with --enable-refresh to enable this option."}
|
|
|
|
|
else if (!main.config.can_refresh.?)
|
|
|
|
|
message = &.{"Directory refresh feature disabled."}
|
2021-07-14 01:20:27 -08:00
|
|
|
else {
|
2021-07-13 03:33:38 -08:00
|
|
|
main.state = .refresh;
|
2024-07-27 01:58:06 -08:00
|
|
|
sink.global.sink = .mem;
|
|
|
|
|
mem_sink.global.root = dir_parent;
|
2021-07-13 03:33:38 -08:00
|
|
|
}
|
|
|
|
|
},
|
2021-07-14 01:20:27 -08:00
|
|
|
'b' => {
|
2021-10-06 01:05:56 -08:00
|
|
|
if (!main.config.can_shell.?)
|
2024-08-06 23:44:19 -08:00
|
|
|
message = &.{"Shell feature disabled.", "Re-run with --enable-shell to enable this option."}
|
2021-07-14 01:20:27 -08:00
|
|
|
else
|
|
|
|
|
main.state = .shell;
|
|
|
|
|
},
|
2021-07-16 06:13:25 -08:00
|
|
|
'd' => {
|
|
|
|
|
if (dir_items.items.len == 0) {
|
2024-08-06 23:44:19 -08:00
|
|
|
} else if (main.config.binreader)
|
|
|
|
|
message = &.{"File deletion is not available when reading from file."}
|
|
|
|
|
else if (!main.config.can_delete.? and main.config.imported)
|
|
|
|
|
message = &.{"File deletion is disabled.", "Re-run with --enable-delete to enable this option."}
|
|
|
|
|
else if (!main.config.can_delete.?)
|
|
|
|
|
message = &.{"File deletion is disabled."}
|
2021-07-16 06:13:25 -08:00
|
|
|
else if (dir_items.items[cursor_idx]) |e| {
|
|
|
|
|
main.state = .delete;
|
|
|
|
|
const next =
|
|
|
|
|
if (cursor_idx+1 < dir_items.items.len) dir_items.items[cursor_idx+1]
|
|
|
|
|
else if (cursor_idx == 0) null
|
|
|
|
|
else dir_items.items[cursor_idx-1];
|
2021-07-26 04:03:08 -08:00
|
|
|
delete.setup(dir_parent, e, next);
|
2021-07-16 06:13:25 -08:00
|
|
|
}
|
|
|
|
|
},
|
2021-05-07 02:01:00 -08:00
|
|
|
|
|
|
|
|
// 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;
|
2024-08-05 23:46:17 -08:00
|
|
|
loadDir(0);
|
2021-07-06 08:28:35 -08:00
|
|
|
state = .main;
|
2021-05-07 02:01:00 -08:00
|
|
|
},
|
|
|
|
|
't' => {
|
|
|
|
|
main.config.sort_dirsfirst = !main.config.sort_dirsfirst;
|
2024-08-05 23:46:17 -08:00
|
|
|
sortDir(0);
|
2021-05-07 02:01:00 -08:00
|
|
|
},
|
|
|
|
|
'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;
|
2024-08-05 23:46:17 -08:00
|
|
|
sortDir(0);
|
2021-05-07 02:01:00 -08:00
|
|
|
}
|
|
|
|
|
if (!main.config.show_blocks and main.config.sort_col == .blocks) {
|
|
|
|
|
main.config.sort_col = .size;
|
2024-08-05 23:46:17 -08:00
|
|
|
sortDir(0);
|
2021-05-07 02:01:00 -08:00
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
|
2021-05-07 07:16:35 -08:00
|
|
|
// Navigation
|
2024-10-26 04:33:40 -08:00
|
|
|
10, 'l', 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| {
|
2024-08-05 23:46:17 -08:00
|
|
|
enterSub(d);
|
|
|
|
|
//dir_parent = d;
|
|
|
|
|
loadDir(0);
|
2021-07-06 08:28:35 -08:00
|
|
|
state = .main;
|
2021-05-07 07:16:35 -08:00
|
|
|
}
|
2024-08-05 23:46:17 -08:00
|
|
|
} else if (dir_parents.items.len > 1) {
|
|
|
|
|
enterParent();
|
|
|
|
|
loadDir(0);
|
2021-07-06 08:28:35 -08:00
|
|
|
state = .main;
|
2021-05-07 07:16:35 -08:00
|
|
|
}
|
|
|
|
|
},
|
2024-10-26 04:33:40 -08:00
|
|
|
'h', '<', c.KEY_BACKSPACE, c.KEY_LEFT => {
|
2024-08-05 23:46:17 -08:00
|
|
|
if (dir_parents.items.len > 1) {
|
|
|
|
|
//const h = dir_parent.entry.nameHash();
|
|
|
|
|
enterParent();
|
|
|
|
|
loadDir(0);
|
2021-07-06 08:28:35 -08:00
|
|
|
state = .main;
|
2021-05-07 07:16:35 -08:00
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
|
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; },
|
2021-10-05 07:16:59 -08:00
|
|
|
'g' => {
|
|
|
|
|
if (!main.config.show_graph and !main.config.show_percent) { main.config.show_graph = true; main.config.show_percent = false; }
|
|
|
|
|
else if ( main.config.show_graph and !main.config.show_percent) { main.config.show_graph = false; main.config.show_percent = true; }
|
|
|
|
|
else if (!main.config.show_graph and main.config.show_percent) { main.config.show_graph = true; main.config.show_percent = true; }
|
|
|
|
|
else if ( main.config.show_graph and main.config.show_percent) { main.config.show_graph = false; main.config.show_percent = false; }
|
2021-05-23 07:18:49 -08:00
|
|
|
},
|
2021-05-30 07:02:57 -08:00
|
|
|
'u' => main.config.show_shared = switch (main.config.show_shared) {
|
|
|
|
|
.off => .shared,
|
|
|
|
|
.shared => .unique,
|
|
|
|
|
.unique => .off,
|
|
|
|
|
},
|
2021-05-23 07:18:49 -08:00
|
|
|
|
2021-07-19 05:28:11 -08:00
|
|
|
else => _ = keyInputSelection(ch, &cursor_idx, dir_items.items.len, ui.rows -| 3),
|
2021-05-07 02:01:00 -08:00
|
|
|
}
|
|
|
|
|
}
|