summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorYorhel <git@yorhel.nl>2021-05-07 17:16:35 +0200
committerYorhel <git@yorhel.nl>2021-05-07 17:16:39 +0200
commite12eb4556d2f613557006433fcdcb25b912991aa (patch)
treedfd92ab6710c55f80fb1ebbbb872aabb8e3cbe33
parentd1eb7ba007135746c9ae6de5f5d10f15d54f53cd (diff)
UI: Implement dir navigation & remember view of past dirs
Now we're getting somewhere. This works surprisingly well, too. Existing ncdu behavior is to remember which entry was previously selected but not which entry was displayed at the top, so the view would be slightly different when switching directories. This new approach remembers both the entry and the offset.
-rw-r--r--src/browser.zig100
-rw-r--r--src/main.zig2
2 files changed, 78 insertions, 24 deletions
diff --git a/src/browser.zig b/src/browser.zig
index 0c8038d..571018d 100644
--- a/src/browser.zig
+++ b/src/browser.zig
@@ -4,15 +4,59 @@ const model = @import("model.zig");
const ui = @import("ui.zig");
usingnamespace @import("util.zig");
+// Currently opened directory and its parents.
+var dir_parents = model.Parents{};
+
// 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{};
-
+// Index into dir_items that is currently selected.
var cursor_idx: usize = 0;
-var window_top: usize = 0;
+
+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(){};
+ 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);
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;
@@ -64,14 +108,14 @@ fn sortDir() void {
// 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
+ current_view.load();
}
// 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 {
+pub fn loadDir() !void {
dir_items.shrinkRetainingCapacity(0);
if (dir_parents.top() != model.root)
try dir_items.append(null);
@@ -90,17 +134,6 @@ fn loadDir() !void {
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();
-
- window_top = 0;
- cursor_idx = 0;
- // TODO: Load view & cursor position if we've opened this dir before.
-}
-
const Row = struct {
row: u32,
col: u32 = 0,
@@ -144,7 +177,7 @@ const Row = struct {
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))));
+ ui.addstr(try ui.shorten(try ui.toUtf8(i.name()), saturateSub(ui.cols, self.col + 1)));
} else
ui.addstr("/..");
}
@@ -186,16 +219,16 @@ pub fn draw() !void {
ui.addch(' ');
const numrows = saturateSub(ui.rows, 3);
- if (cursor_idx < window_top) window_top = cursor_idx;
- if (cursor_idx >= window_top + numrows) window_top = cursor_idx - numrows + 1;
+ 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;
var i: u32 = 0;
while (i < numrows) : (i += 1) {
- if (i+window_top >= dir_items.items.len) break;
+ if (i+current_view.top >= dir_items.items.len) break;
var row = Row{
.row = i+2,
- .item = dir_items.items[i+window_top],
- .bg = if (i+window_top == cursor_idx) .sel else .default,
+ .item = dir_items.items[i+current_view.top],
+ .bg = if (i+current_view.top == cursor_idx) .sel else .default,
};
try row.draw();
}
@@ -221,6 +254,8 @@ fn sortToggle(col: main.SortCol, default_order: main.SortOrder) void {
}
pub fn key(ch: i32) !void {
+ defer current_view.save();
+
switch (ch) {
'q' => main.state = .quit,
@@ -261,6 +296,25 @@ pub fn key(ch: i32) !void {
}
},
+ // Navigation
+ 10, 'l', ui.c.KEY_RIGHT => {
+ if (dir_items.items[cursor_idx]) |e| {
+ 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();
+ }
+ },
+
else => {}
}
}
diff --git a/src/main.zig b/src/main.zig
index bb3a3b7..f409fc5 100644
--- a/src/main.zig
+++ b/src/main.zig
@@ -212,7 +212,7 @@ pub fn main() anyerror!void {
event_delay_timer = try std.time.Timer.start();
try scan.scanRoot(scan_dir orelse ".");
- try browser.open(model.Parents{});
+ try browser.loadDir();
ui.init();
defer ui.deinit();