mirror of
https://code.blicky.net/yorhel/ncdu.git
synced 2026-01-15 18:18:41 -09:00
Add link path listing to information window
Two differences compared to the C version: - You can now select individual paths in the listing, pressing enter will open the selected path in the browser window. - Creating this listing is much slower and requires, in the worst case, a full traversal through the in-memory tree. I've tested this without the same-dev and shared-parent optimizations (i.e. worst case) on an import with 30M files and performance was still quite acceptable - the listing completed in a second - so I didn't bother adding a loading indicator. On slower systems and even larger trees this may be a little annoying, though. (also, calling nonl() apparently breaks detection of the return key, neither \n nor KEY_ENTER are emitted for some reason)
This commit is contained in:
parent
618972b82b
commit
ff3e3bccc6
5 changed files with 252 additions and 62 deletions
|
|
@ -28,7 +28,6 @@ backported to the C version, depending on how viable a proper Zig release is.
|
||||||
|
|
||||||
Missing features:
|
Missing features:
|
||||||
|
|
||||||
- Listing paths for the same hard link
|
|
||||||
- Help window
|
- Help window
|
||||||
- Directory refresh
|
- Directory refresh
|
||||||
- File deletion
|
- File deletion
|
||||||
|
|
@ -51,6 +50,7 @@ Already implemented:
|
||||||
[#36](https://code.blicky.net/yorhel/ncdu/issues/36)).
|
[#36](https://code.blicky.net/yorhel/ncdu/issues/36)).
|
||||||
- Faster --exclude-kernfs thanks to `statfs()` caching.
|
- Faster --exclude-kernfs thanks to `statfs()` caching.
|
||||||
- Improved handling of Unicode and special characters.
|
- Improved handling of Unicode and special characters.
|
||||||
|
- Key to switch to path from a file's hard link listing.
|
||||||
- Remembers item position when switching directories.
|
- Remembers item position when switching directories.
|
||||||
|
|
||||||
Potentially to be implemented:
|
Potentially to be implemented:
|
||||||
|
|
|
||||||
214
src/browser.zig
214
src/browser.zig
|
|
@ -127,7 +127,7 @@ pub fn loadDir() void {
|
||||||
dir_max_blocks = 1;
|
dir_max_blocks = 1;
|
||||||
dir_has_shared = false;
|
dir_has_shared = false;
|
||||||
|
|
||||||
if (dir_parents.top() != model.root)
|
if (!dir_parents.isRoot())
|
||||||
dir_items.append(null) catch unreachable;
|
dir_items.append(null) catch unreachable;
|
||||||
var it = dir_parents.top().sub;
|
var it = dir_parents.top().sub;
|
||||||
while (it) |e| {
|
while (it) |e| {
|
||||||
|
|
@ -329,9 +329,65 @@ const quit = struct {
|
||||||
};
|
};
|
||||||
|
|
||||||
const info = struct {
|
const info = struct {
|
||||||
// TODO: List of paths for the same hardlink.
|
const Tab = enum { info, links };
|
||||||
|
|
||||||
fn drawSizeRow(box: *const ui.Box, row: *u32, label: [:0]const u8, size: u64) void {
|
var tab: Tab = .info;
|
||||||
|
var entry: ?*model.Entry = null;
|
||||||
|
var links: ?model.LinkPaths = null;
|
||||||
|
var links_top: usize = 0;
|
||||||
|
var links_idx: usize = 0;
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
if (tab == .links and links == null) {
|
||||||
|
links = model.LinkPaths.find(&dir_parents, e.?.link().?);
|
||||||
|
for (links.?.paths.items) |n,i| {
|
||||||
|
if (&n.node.entry == e) {
|
||||||
|
links_idx = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn drawLinks(box: ui.Box, row: *u32, rows: u32, cols: u32) void {
|
||||||
|
var pathbuf = std.ArrayList(u8).init(main.allocator);
|
||||||
|
|
||||||
|
const numrows = saturateSub(rows, 4);
|
||||||
|
if (links_idx < links_top) links_top = links_idx;
|
||||||
|
if (links_idx >= links_top + numrows) links_top = links_idx - numrows + 1;
|
||||||
|
|
||||||
|
var i: u32 = 0;
|
||||||
|
while (i < numrows) : (i += 1) {
|
||||||
|
if (i + links_top >= links.?.paths.items.len) break;
|
||||||
|
const e = links.?.paths.items[i+links_top];
|
||||||
|
ui.style(if (i+links_top == links_idx) .sel else .default);
|
||||||
|
box.move(row.*, 2);
|
||||||
|
ui.addch(if (&e.node.entry == entry) '*' else ' ');
|
||||||
|
pathbuf.shrinkRetainingCapacity(0);
|
||||||
|
e.fmtPath(false, &pathbuf);
|
||||||
|
ui.addstr(ui.shorten(ui.toUtf8(arrayListBufZ(&pathbuf)), saturateSub(cols, 5)));
|
||||||
|
row.* += 1;
|
||||||
|
}
|
||||||
|
ui.style(.default);
|
||||||
|
box.move(rows-2, 4);
|
||||||
|
ui.addprint("{:>3}/{}", .{ links_idx+1, links.?.paths.items.len });
|
||||||
|
pathbuf.deinit();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn drawSizeRow(box: ui.Box, row: *u32, label: [:0]const u8, size: u64) void {
|
||||||
box.move(row.*, 3);
|
box.move(row.*, 3);
|
||||||
ui.addstr(label);
|
ui.addstr(label);
|
||||||
ui.addsize(.default, size);
|
ui.addsize(.default, size);
|
||||||
|
|
@ -341,7 +397,7 @@ const info = struct {
|
||||||
row.* += 1;
|
row.* += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn drawSize(box: *const ui.Box, row: *u32, label: [:0]const u8, size: u64, shared: u64) void {
|
fn drawSize(box: ui.Box, row: *u32, label: [:0]const u8, size: u64, shared: u64) void {
|
||||||
ui.style(.bold);
|
ui.style(.bold);
|
||||||
drawSizeRow(box, row, label, size);
|
drawSizeRow(box, row, label, size);
|
||||||
if (shared > 0) {
|
if (shared > 0) {
|
||||||
|
|
@ -351,33 +407,17 @@ const info = struct {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn draw() void {
|
fn drawInfo(box: ui.Box, row: *u32, cols: u32, e: *model.Entry) 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. Not really sure how to handle yet.
|
|
||||||
const rows = 5 // border + padding + close message
|
|
||||||
+ 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;
|
|
||||||
|
|
||||||
// Name
|
// Name
|
||||||
box.move(row, 3);
|
box.move(row.*, 3);
|
||||||
ui.style(.bold);
|
ui.style(.bold);
|
||||||
ui.addstr("Name: ");
|
ui.addstr("Name: ");
|
||||||
ui.style(.default);
|
ui.style(.default);
|
||||||
ui.addstr(ui.shorten(ui.toUtf8(e.name()), cols-11));
|
ui.addstr(ui.shorten(ui.toUtf8(e.name()), cols-11));
|
||||||
row += 1;
|
row.* += 1;
|
||||||
|
|
||||||
// Type / Mode+UID+GID
|
// Type / Mode+UID+GID
|
||||||
box.move(row, 3);
|
box.move(row.*, 3);
|
||||||
ui.style(.bold);
|
ui.style(.bold);
|
||||||
if (e.ext()) |ext| {
|
if (e.ext()) |ext| {
|
||||||
ui.addstr("Mode: ");
|
ui.addstr("Mode: ");
|
||||||
|
|
@ -397,47 +437,78 @@ const info = struct {
|
||||||
ui.style(.default);
|
ui.style(.default);
|
||||||
ui.addstr(if (e.isDirectory()) "Directory" else if (if (e.file()) |f| f.notreg else false) "Other" else "File");
|
ui.addstr(if (e.isDirectory()) "Directory" else if (if (e.file()) |f| f.notreg else false) "Other" else "File");
|
||||||
}
|
}
|
||||||
row += 1;
|
row.* += 1;
|
||||||
|
|
||||||
// Last modified
|
// Last modified
|
||||||
if (e.ext()) |ext| {
|
if (e.ext()) |ext| {
|
||||||
box.move(row, 3);
|
box.move(row.*, 3);
|
||||||
ui.style(.bold);
|
ui.style(.bold);
|
||||||
ui.addstr("Last modified: ");
|
ui.addstr("Last modified: ");
|
||||||
ui.addts(.default, ext.mtime);
|
ui.addts(.default, ext.mtime);
|
||||||
row += 1;
|
row.* += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Disk usage & Apparent size
|
// Disk usage & Apparent size
|
||||||
drawSize(&box, &row, " Disk usage: ", blocksToSize(e.blocks), if (e.dir()) |d| blocksToSize(d.shared_blocks) else 0);
|
drawSize(box, row, " Disk usage: ", blocksToSize(e.blocks), if (e.dir()) |d| blocksToSize(d.shared_blocks) else 0);
|
||||||
drawSize(&box, &row, "Apparent size: ", e.size, if (e.dir()) |d| d.shared_size else 0);
|
drawSize(box, row, "Apparent size: ", e.size, if (e.dir()) |d| d.shared_size else 0);
|
||||||
|
|
||||||
// Number of items
|
// Number of items
|
||||||
if (e.dir()) |d| {
|
if (e.dir()) |d| {
|
||||||
box.move(row, 3);
|
box.move(row.*, 3);
|
||||||
ui.style(.bold);
|
ui.style(.bold);
|
||||||
ui.addstr(" Sub items: ");
|
ui.addstr(" Sub items: ");
|
||||||
ui.addnum(.default, d.items);
|
ui.addnum(.default, d.items);
|
||||||
row += 1;
|
row.* += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Number of links + inode (dev?)
|
// Number of links + inode (dev?)
|
||||||
if (e.link()) |l| {
|
if (e.link()) |l| {
|
||||||
box.move(row, 3);
|
box.move(row.*, 3);
|
||||||
ui.style(.bold);
|
ui.style(.bold);
|
||||||
ui.addstr(" Link count: ");
|
ui.addstr(" Link count: ");
|
||||||
ui.addnum(.default, l.nlink);
|
ui.addnum(.default, l.nlink);
|
||||||
box.move(row, 23);
|
box.move(row.*, 23);
|
||||||
ui.style(.bold);
|
ui.style(.bold);
|
||||||
ui.addstr(" Inode: ");
|
ui.addstr(" Inode: ");
|
||||||
ui.style(.default);
|
ui.style(.default);
|
||||||
var buf: [32]u8 = undefined;
|
var buf: [32]u8 = undefined;
|
||||||
ui.addstr(std.fmt.bufPrintZ(&buf, "{}", .{ l.ino }) catch unreachable);
|
ui.addstr(std.fmt.bufPrintZ(&buf, "{}", .{ l.ino }) catch unreachable);
|
||||||
row += 1;
|
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
|
||||||
|
+ if (tab == .links) 8 else
|
||||||
|
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
|
||||||
|
if (e.etype == .link) {
|
||||||
|
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),
|
||||||
}
|
}
|
||||||
|
|
||||||
// "Press i to close this window"
|
// "Press i to close this window"
|
||||||
box.move(row+1, cols-30);
|
box.move(rows-2, cols-30);
|
||||||
ui.style(.default);
|
ui.style(.default);
|
||||||
ui.addstr("Press ");
|
ui.addstr("Press ");
|
||||||
ui.style(.key);
|
ui.style(.key);
|
||||||
|
|
@ -446,15 +517,42 @@ const info = struct {
|
||||||
ui.addstr(" to close this window");
|
ui.addstr(" to close this window");
|
||||||
}
|
}
|
||||||
|
|
||||||
fn keyInput(ch: i32) void {
|
fn keyInput(ch: i32) bool {
|
||||||
if (keyInputSelection(ch)) {
|
if (entry.?.etype == .link) {
|
||||||
if (dir_items.items[cursor_idx] == null) state = .main;
|
switch (ch) {
|
||||||
return;
|
'1', 'h', ui.c.KEY_LEFT => { set(entry, .info); return true; },
|
||||||
|
'2', 'l', ui.c.KEY_RIGHT => { set(entry, .links); return true; },
|
||||||
|
else => {},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (tab == .links) {
|
||||||
|
if (keyInputSelection(ch, &links_idx, links.?.paths.items.len, 5))
|
||||||
|
return true;
|
||||||
|
if (ch == 10) { // Enter - go to selected entry
|
||||||
|
// XXX: This jump can be a little bit jarring as, usually,
|
||||||
|
// browsing to parent directory will cause the previously
|
||||||
|
// opened dir to be selected. This jump doesn't update the View
|
||||||
|
// state of parent dirs, so that won't be the case anymore.
|
||||||
|
const p = links.?.paths.items[links_idx];
|
||||||
|
dir_parents.stack.shrinkRetainingCapacity(0);
|
||||||
|
dir_parents.stack.appendSlice(p.path.stack.items) catch unreachable;
|
||||||
|
loadDir();
|
||||||
|
for (dir_items.items) |e, i| {
|
||||||
|
if (e == &p.node.entry)
|
||||||
|
cursor_idx = i;
|
||||||
|
}
|
||||||
|
set(null, .info);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (keyInputSelection(ch, &cursor_idx, dir_items.items.len, saturateSub(ui.rows, 3))) {
|
||||||
|
set(dir_items.items[cursor_idx], .info);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
switch (ch) {
|
switch (ch) {
|
||||||
'i', 'q' => state = .main,
|
'i', 'q' => set(null, .info),
|
||||||
else => {},
|
else => return false,
|
||||||
}
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -484,7 +582,7 @@ pub fn draw() void {
|
||||||
ui.style(.dir);
|
ui.style(.dir);
|
||||||
|
|
||||||
var pathbuf = std.ArrayList(u8).init(main.allocator);
|
var pathbuf = std.ArrayList(u8).init(main.allocator);
|
||||||
dir_parents.path(&pathbuf);
|
dir_parents.fmtPath(true, &pathbuf);
|
||||||
ui.addstr(ui.shorten(ui.toUtf8(arrayListBufZ(&pathbuf)), saturateSub(ui.cols, 5)));
|
ui.addstr(ui.shorten(ui.toUtf8(arrayListBufZ(&pathbuf)), saturateSub(ui.cols, 5)));
|
||||||
pathbuf.deinit();
|
pathbuf.deinit();
|
||||||
|
|
||||||
|
|
@ -537,35 +635,35 @@ fn sortToggle(col: main.config.SortCol, default_order: main.config.SortOrder) vo
|
||||||
sortDir();
|
sortDir();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn keyInputSelection(ch: i32) bool {
|
fn keyInputSelection(ch: i32, idx: *usize, len: usize, page: u32) bool {
|
||||||
switch (ch) {
|
switch (ch) {
|
||||||
'j', ui.c.KEY_DOWN => {
|
'j', ui.c.KEY_DOWN => {
|
||||||
if (cursor_idx+1 < dir_items.items.len) cursor_idx += 1;
|
if (idx.*+1 < len) idx.* += 1;
|
||||||
},
|
},
|
||||||
'k', ui.c.KEY_UP => {
|
'k', ui.c.KEY_UP => {
|
||||||
if (cursor_idx > 0) cursor_idx -= 1;
|
if (idx.* > 0) idx.* -= 1;
|
||||||
},
|
},
|
||||||
ui.c.KEY_HOME => cursor_idx = 0,
|
ui.c.KEY_HOME => idx.* = 0,
|
||||||
ui.c.KEY_END, ui.c.KEY_LL => cursor_idx = saturateSub(dir_items.items.len, 1),
|
ui.c.KEY_END, ui.c.KEY_LL => idx.* = saturateSub(len, 1),
|
||||||
ui.c.KEY_PPAGE => cursor_idx = saturateSub(cursor_idx, saturateSub(ui.rows, 3)),
|
ui.c.KEY_PPAGE => idx.* = saturateSub(idx.*, page),
|
||||||
ui.c.KEY_NPAGE => cursor_idx = std.math.min(saturateSub(dir_items.items.len, 1), cursor_idx + saturateSub(ui.rows, 3)),
|
ui.c.KEY_NPAGE => idx.* = std.math.min(saturateSub(len, 1), idx.* + page),
|
||||||
else => return false,
|
else => return false,
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn keyInput(ch: i32) void {
|
pub fn keyInput(ch: i32) void {
|
||||||
|
defer current_view.save();
|
||||||
|
|
||||||
switch (state) {
|
switch (state) {
|
||||||
.main => {}, // fallthrough
|
.main => {}, // fallthrough
|
||||||
.quit => return quit.keyInput(ch),
|
.quit => return quit.keyInput(ch),
|
||||||
.info => return info.keyInput(ch),
|
.info => if (info.keyInput(ch)) return,
|
||||||
}
|
}
|
||||||
|
|
||||||
defer current_view.save();
|
|
||||||
|
|
||||||
switch (ch) {
|
switch (ch) {
|
||||||
'q' => if (main.config.confirm_quit) { state = .quit; } else ui.quit(),
|
'q' => if (main.config.confirm_quit) { state = .quit; } else ui.quit(),
|
||||||
'i' => if (dir_items.items[cursor_idx] != null) { state = .info; },
|
'i' => info.set(dir_items.items[cursor_idx], .info),
|
||||||
|
|
||||||
// Sort & filter settings
|
// Sort & filter settings
|
||||||
'n' => sortToggle(.name, .asc),
|
'n' => sortToggle(.name, .asc),
|
||||||
|
|
@ -575,6 +673,7 @@ pub fn keyInput(ch: i32) void {
|
||||||
'e' => {
|
'e' => {
|
||||||
main.config.show_hidden = !main.config.show_hidden;
|
main.config.show_hidden = !main.config.show_hidden;
|
||||||
loadDir();
|
loadDir();
|
||||||
|
state = .main;
|
||||||
},
|
},
|
||||||
't' => {
|
't' => {
|
||||||
main.config.sort_dirsfirst = !main.config.sort_dirsfirst;
|
main.config.sort_dirsfirst = !main.config.sort_dirsfirst;
|
||||||
|
|
@ -599,16 +698,19 @@ pub fn keyInput(ch: i32) void {
|
||||||
if (e.dir()) |d| {
|
if (e.dir()) |d| {
|
||||||
dir_parents.push(d);
|
dir_parents.push(d);
|
||||||
loadDir();
|
loadDir();
|
||||||
|
state = .main;
|
||||||
}
|
}
|
||||||
} else if (dir_parents.top() != model.root) {
|
} else if (!dir_parents.isRoot()) {
|
||||||
dir_parents.pop();
|
dir_parents.pop();
|
||||||
loadDir();
|
loadDir();
|
||||||
|
state = .main;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
'h', '<', ui.c.KEY_BACKSPACE, ui.c.KEY_LEFT => {
|
'h', '<', ui.c.KEY_BACKSPACE, ui.c.KEY_LEFT => {
|
||||||
if (dir_parents.top() != model.root) {
|
if (!dir_parents.isRoot()) {
|
||||||
dir_parents.pop();
|
dir_parents.pop();
|
||||||
loadDir();
|
loadDir();
|
||||||
|
state = .main;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
@ -628,6 +730,6 @@ pub fn keyInput(ch: i32) void {
|
||||||
.unique => .off,
|
.unique => .off,
|
||||||
},
|
},
|
||||||
|
|
||||||
else => _ = keyInputSelection(ch),
|
else => _ = keyInputSelection(ch, &cursor_idx, dir_items.items.len, saturateSub(ui.rows, 3)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -392,6 +392,10 @@ pub const Parents = struct {
|
||||||
return self.stack.append(dir) catch unreachable;
|
return self.stack.append(dir) catch unreachable;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn isRoot(self: *Self) bool {
|
||||||
|
return self.stack.items.len == 0;
|
||||||
|
}
|
||||||
|
|
||||||
// Attempting to remove the root node is considered a bug.
|
// Attempting to remove the root node is considered a bug.
|
||||||
pub fn pop(self: *Self) void {
|
pub fn pop(self: *Self) void {
|
||||||
_ = self.stack.pop();
|
_ = self.stack.pop();
|
||||||
|
|
@ -419,23 +423,97 @@ pub const Parents = struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Append the path to the given arraylist. The list is assumed to use main.allocator, so it can't fail.
|
// Append the path to the given arraylist. The list is assumed to use main.allocator, so it can't fail.
|
||||||
pub fn path(self: *const Self, out: *std.ArrayList(u8)) void {
|
pub fn fmtPath(self: *const Self, withRoot: bool, out: *std.ArrayList(u8)) void {
|
||||||
const r = root.entry.name();
|
const r = root.entry.name();
|
||||||
out.appendSlice(r) catch unreachable;
|
if (withRoot) out.appendSlice(r) catch unreachable;
|
||||||
var i: usize = 0;
|
var i: usize = 0;
|
||||||
while (i < self.stack.items.len) {
|
while (i < self.stack.items.len) {
|
||||||
if (i != 0 or r[r.len-1] != '/') out.append('/') catch unreachable;
|
if (i != 0 or (withRoot and r[r.len-1] != '/')) out.append('/') catch unreachable;
|
||||||
out.appendSlice(self.stack.items[i].entry.name()) catch unreachable;
|
out.appendSlice(self.stack.items[i].entry.name()) catch unreachable;
|
||||||
i += 1;
|
i += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn copy(self: *const Self) Self {
|
||||||
|
var c = Self{};
|
||||||
|
c.stack.appendSlice(self.stack.items) catch unreachable;
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
|
||||||
pub fn deinit(self: *Self) void {
|
pub fn deinit(self: *Self) void {
|
||||||
self.stack.deinit();
|
self.stack.deinit();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
// List of paths for the same inode.
|
||||||
|
pub const LinkPaths = struct {
|
||||||
|
paths: std.ArrayList(Path) = std.ArrayList(Path).init(main.allocator),
|
||||||
|
|
||||||
|
pub const Path = struct {
|
||||||
|
path: Parents,
|
||||||
|
node: *Link,
|
||||||
|
|
||||||
|
fn lt(_: void, a: Path, b: Path) bool {
|
||||||
|
var i: usize = 0;
|
||||||
|
while (i < a.path.stack.items.len and i < b.path.stack.items.len) : (i += 1)
|
||||||
|
if (a.path.stack.items[i] != b.path.stack.items[i])
|
||||||
|
return std.mem.lessThan(u8, a.path.stack.items[i].entry.name(), b.path.stack.items[i].entry.name());
|
||||||
|
if (a.path.stack.items.len != b.path.stack.items.len)
|
||||||
|
return a.path.stack.items.len < b.path.stack.items.len;
|
||||||
|
return std.mem.lessThan(u8, a.node.entry.name(), b.node.entry.name());
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn fmtPath(self: Path, withRoot: bool, out: *std.ArrayList(u8)) void {
|
||||||
|
self.path.fmtPath(withRoot, out);
|
||||||
|
out.append('/') catch unreachable;
|
||||||
|
out.appendSlice(self.node.entry.name()) catch unreachable;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const Self = @This();
|
||||||
|
|
||||||
|
fn findRec(self: *Self, parent: *Parents, node: *const Link) void {
|
||||||
|
var entry = parent.top().sub;
|
||||||
|
while (entry) |e| : (entry = e.next) {
|
||||||
|
if (e.link()) |l| {
|
||||||
|
if (l.ino == node.ino)
|
||||||
|
self.paths.append(Path{ .path = parent.copy(), .node = l }) catch unreachable;
|
||||||
|
}
|
||||||
|
if (e.dir()) |d| {
|
||||||
|
if (d.dev == parent.top().dev) {
|
||||||
|
parent.push(d);
|
||||||
|
self.findRec(parent, node);
|
||||||
|
parent.pop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find all paths for the given link
|
||||||
|
pub fn find(parents_: *const Parents, node: *const Link) Self {
|
||||||
|
var parents = parents_.copy();
|
||||||
|
var self = Self{};
|
||||||
|
// First find the bottom-most parent that has no shared_size,
|
||||||
|
// all links are guaranteed to be inside that directory.
|
||||||
|
while (!parents.isRoot() and parents.top().shared_size > 0)
|
||||||
|
parents.pop();
|
||||||
|
self.findRec(&parents, node);
|
||||||
|
// TODO: Zig's sort() implementation is type-generic and not very
|
||||||
|
// small. I suspect we can get a good save on our binary size by using
|
||||||
|
// a smaller or non-generic sort. This doesn't have to be very fast.
|
||||||
|
std.sort.sort(Path, self.paths.items, @as(void, undefined), Path.lt);
|
||||||
|
parents.deinit();
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinit(self: *Self) void {
|
||||||
|
for (self.paths.items) |*p| p.path.deinit();
|
||||||
|
self.paths.deinit();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
test "entry" {
|
test "entry" {
|
||||||
var e = Entry.create(.file, false, "hello") catch unreachable;
|
var e = Entry.create(.file, false, "hello") catch unreachable;
|
||||||
std.debug.assert(e.etype == .file);
|
std.debug.assert(e.etype == .file);
|
||||||
|
|
|
||||||
|
|
@ -175,7 +175,7 @@ const Context = struct {
|
||||||
self.path_indices.items.len -= 1;
|
self.path_indices.items.len -= 1;
|
||||||
|
|
||||||
if (self.stat.dir) {
|
if (self.stat.dir) {
|
||||||
if (self.parents) |*p| if (p.top() != model.root) p.pop();
|
if (self.parents) |*p| if (!p.isRoot()) p.pop();
|
||||||
if (self.wr) |w| w.writer().writeByte(']') catch |e| writeErr(e);
|
if (self.wr) |w| w.writer().writeByte(']') catch |e| writeErr(e);
|
||||||
} else
|
} else
|
||||||
self.stat.dir = true; // repeated popPath()s mean we're closing parent dirs.
|
self.stat.dir = true; // repeated popPath()s mean we're closing parent dirs.
|
||||||
|
|
|
||||||
12
src/ui.zig
12
src/ui.zig
|
|
@ -330,7 +330,6 @@ pub fn init() void {
|
||||||
updateSize();
|
updateSize();
|
||||||
_ = c.cbreak();
|
_ = c.cbreak();
|
||||||
_ = c.noecho();
|
_ = c.noecho();
|
||||||
_ = c.nonl();
|
|
||||||
_ = c.curs_set(0);
|
_ = c.curs_set(0);
|
||||||
_ = c.keypad(c.stdscr, true);
|
_ = c.keypad(c.stdscr, true);
|
||||||
|
|
||||||
|
|
@ -530,6 +529,17 @@ pub const Box = struct {
|
||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn tab(s: Self, col: u32, sel: bool, num: u3, label: [:0]const u8) void {
|
||||||
|
const bg: Bg = if (sel) .hd else .default;
|
||||||
|
s.move(0, col);
|
||||||
|
bg.fg(.key);
|
||||||
|
addch('0' + @as(u8, num));
|
||||||
|
bg.fg(.default);
|
||||||
|
addch(':');
|
||||||
|
addstr(label);
|
||||||
|
style(.default);
|
||||||
|
}
|
||||||
|
|
||||||
// Move the global cursor to the given coordinates inside the box.
|
// Move the global cursor to the given coordinates inside the box.
|
||||||
pub fn move(s: Self, row: u32, col: u32) void {
|
pub fn move(s: Self, row: u32, col: u32) void {
|
||||||
ui.move(s.start_row + row, s.start_col + col);
|
ui.move(s.start_row + row, s.start_col + col);
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue