mirror of
https://code.blicky.net/yorhel/ncdu.git
synced 2026-01-12 17:08:39 -09:00
Initial keyboard input handling + item&sort selection
This commit is contained in:
parent
27cb599e22
commit
d1eb7ba007
3 changed files with 131 additions and 52 deletions
|
|
@ -11,6 +11,9 @@ var dir_items = std.ArrayList(?*model.Entry).init(main.allocator);
|
|||
// Currently opened directory and its parents.
|
||||
var dir_parents = model.Parents{};
|
||||
|
||||
var cursor_idx: usize = 0;
|
||||
var window_top: usize = 0;
|
||||
|
||||
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;
|
||||
}
|
||||
|
|
@ -93,6 +96,8 @@ pub fn open(dir: model.Parents) !void {
|
|||
dir_parents = dir;
|
||||
try loadDir();
|
||||
|
||||
window_top = 0;
|
||||
cursor_idx = 0;
|
||||
// TODO: Load view & cursor position if we've opened this dir before.
|
||||
}
|
||||
|
||||
|
|
@ -145,6 +150,11 @@ const Row = struct {
|
|||
}
|
||||
|
||||
fn draw(self: *Self) !void {
|
||||
if (self.bg == .sel) {
|
||||
self.bg.fg(.default);
|
||||
ui.move(self.row, 0);
|
||||
ui.hline(' ', ui.cols);
|
||||
}
|
||||
try self.flag();
|
||||
try self.size();
|
||||
try self.name();
|
||||
|
|
@ -175,10 +185,18 @@ pub fn draw() !void {
|
|||
ui.addstr(try ui.shorten(try ui.toUtf8(model.root.entry.name()), saturateSub(ui.cols, 5)));
|
||||
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;
|
||||
|
||||
var i: u32 = 0;
|
||||
while (i < saturateSub(ui.rows, 3)) : (i += 1) {
|
||||
if (i >= dir_items.items.len) break;
|
||||
var row = Row{ .row = i+2, .item = dir_items.items[i] };
|
||||
while (i < numrows) : (i += 1) {
|
||||
if (i+window_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,
|
||||
};
|
||||
try row.draw();
|
||||
}
|
||||
|
||||
|
|
@ -193,3 +211,56 @@ pub fn draw() !void {
|
|||
ui.addstr(" Items: ");
|
||||
ui.addnum(.hd, dir_parents.top().total_items);
|
||||
}
|
||||
|
||||
fn sortToggle(col: main.SortCol, default_order: main.SortOrder) void {
|
||||
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 {
|
||||
switch (ch) {
|
||||
'q' => main.state = .quit,
|
||||
|
||||
// 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();
|
||||
}
|
||||
},
|
||||
|
||||
else => {}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
79
src/main.zig
79
src/main.zig
|
|
@ -9,6 +9,9 @@ const c = @cImport(@cInclude("locale.h"));
|
|||
|
||||
pub const allocator = std.heap.c_allocator;
|
||||
|
||||
pub const SortCol = enum { name, blocks, size, items, mtime };
|
||||
pub const SortOrder = enum { asc, desc };
|
||||
|
||||
pub const Config = struct {
|
||||
same_fs: bool = true,
|
||||
extended: bool = false,
|
||||
|
|
@ -17,7 +20,7 @@ pub const Config = struct {
|
|||
exclude_kernfs: bool = false,
|
||||
exclude_patterns: std.ArrayList([:0]const u8) = std.ArrayList([:0]const u8).init(allocator),
|
||||
|
||||
update_delay: u32 = 100,
|
||||
update_delay: u64 = 100*std.time.ns_per_ms,
|
||||
si: bool = false,
|
||||
nc_tty: bool = false,
|
||||
ui_color: enum { off, dark } = .off,
|
||||
|
|
@ -25,8 +28,8 @@ pub const Config = struct {
|
|||
|
||||
show_hidden: bool = true,
|
||||
show_blocks: bool = true,
|
||||
sort_col: enum { name, blocks, size, items, mtime } = .blocks,
|
||||
sort_order: enum { asc, desc } = .desc,
|
||||
sort_col: SortCol = .blocks,
|
||||
sort_order: SortOrder = .desc,
|
||||
sort_dirsfirst: bool = false,
|
||||
|
||||
read_only: bool = false,
|
||||
|
|
@ -36,6 +39,8 @@ pub const Config = struct {
|
|||
|
||||
pub var config = Config{};
|
||||
|
||||
pub var state: enum { browse, quit } = .browse;
|
||||
|
||||
// Simple generic argument parser, supports getopt_long() style arguments.
|
||||
// T can be any type that has a 'fn next(T) ?[:0]const u8' method, e.g.:
|
||||
// var args = Args(std.process.ArgIteratorPosix).init(std.process.ArgIteratorPosix.init());
|
||||
|
|
@ -112,46 +117,6 @@ fn Args(T: anytype) type {
|
|||
};
|
||||
}
|
||||
|
||||
|
||||
// For debugging
|
||||
fn writeTree(out: anytype, e: *model.Entry, indent: u32) @TypeOf(out).Error!void {
|
||||
var i: u32 = 0;
|
||||
while (i<indent) {
|
||||
try out.writeByte(' ');
|
||||
i += 1;
|
||||
}
|
||||
try out.print("{s} blocks={d} size={d}", .{ e.name(), e.blocks, e.size });
|
||||
|
||||
if (e.dir()) |d| {
|
||||
try out.print(" blocks={d}-{d} size={d}-{d} items={d}-{d} dev={x}", .{
|
||||
d.total_blocks, d.shared_blocks,
|
||||
d.total_size, d.shared_size,
|
||||
d.total_items, d.shared_items, d.dev
|
||||
});
|
||||
if (d.err) try out.writeAll(" err");
|
||||
if (d.suberr) try out.writeAll(" suberr");
|
||||
} else if (e.file()) |f| {
|
||||
if (f.err) try out.writeAll(" err");
|
||||
if (f.excluded) try out.writeAll(" excluded");
|
||||
if (f.other_fs) try out.writeAll(" other_fs");
|
||||
if (f.kernfs) try out.writeAll(" kernfs");
|
||||
if (f.notreg) try out.writeAll(" notreg");
|
||||
} else if (e.link()) |l| {
|
||||
try out.print(" ino={x} nlinks={d}", .{ l.ino, l.nlink });
|
||||
}
|
||||
if (e.ext()) |ext|
|
||||
try out.print(" mtime={d} uid={d} gid={d} mode={o}", .{ ext.mtime, ext.uid, ext.gid, ext.mode });
|
||||
|
||||
try out.writeByte('\n');
|
||||
if (e.dir()) |d| {
|
||||
var s = d.sub;
|
||||
while (s) |sub| {
|
||||
try writeTree(out, sub, indent+4);
|
||||
s = sub.next;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn version() noreturn {
|
||||
std.io.getStdOut().writer().writeAll("ncdu " ++ program_version ++ "\n") catch {};
|
||||
std.process.exit(0);
|
||||
|
|
@ -218,7 +183,7 @@ pub fn main() anyerror!void {
|
|||
}
|
||||
if (opt.is("-h") or opt.is("-?") or opt.is("--help")) help()
|
||||
else if(opt.is("-v") or opt.is("-V") or opt.is("--version")) version()
|
||||
else if(opt.is("-q")) config.update_delay = 2000
|
||||
else if(opt.is("-q")) config.update_delay = 2*std.time.ns_per_s
|
||||
else if(opt.is("-x")) config.same_fs = true
|
||||
else if(opt.is("-e")) config.extended = true
|
||||
else if(opt.is("-r") and config.read_only) config.can_shell = false
|
||||
|
|
@ -244,19 +209,35 @@ pub fn main() anyerror!void {
|
|||
if (std.builtin.os.tag != .linux and config.exclude_kernfs)
|
||||
ui.die("The --exclude-kernfs tag is currently only supported on Linux.\n", .{});
|
||||
|
||||
event_delay_timer = try std.time.Timer.start();
|
||||
|
||||
try scan.scanRoot(scan_dir orelse ".");
|
||||
try browser.open(model.Parents{});
|
||||
|
||||
ui.init();
|
||||
defer ui.deinit();
|
||||
|
||||
try browser.draw();
|
||||
// TODO: Handle OOM errors
|
||||
// TODO: Confirm quit
|
||||
while (state != .quit) try handleEvent(true, false);
|
||||
}
|
||||
|
||||
_ = ui.c.getch();
|
||||
var event_delay_timer: std.time.Timer = undefined;
|
||||
|
||||
//var out = std.io.bufferedWriter(std.io.getStdOut().writer());
|
||||
//try writeTree(out.writer(), &model.root.entry, 0);
|
||||
//try out.flush();
|
||||
// Draw the screen and handle the next input event.
|
||||
// In non-blocking mode, screen drawing is rate-limited to keep this function fast.
|
||||
pub fn handleEvent(block: bool, force_draw: bool) !void {
|
||||
if (block or force_draw or event_delay_timer.read() > config.update_delay) {
|
||||
_ = ui.c.erase();
|
||||
try browser.draw();
|
||||
_ = ui.c.refresh();
|
||||
event_delay_timer.reset();
|
||||
}
|
||||
|
||||
var ch = ui.getch(block);
|
||||
if (ch == 0) return;
|
||||
if (ch == -1) return handleEvent(block, true);
|
||||
try browser.key(ch);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
27
src/ui.zig
27
src/ui.zig
|
|
@ -279,6 +279,7 @@ pub fn init() void {
|
|||
updateSize();
|
||||
_ = c.cbreak();
|
||||
_ = c.noecho();
|
||||
_ = c.nonl();
|
||||
_ = c.curs_set(0);
|
||||
_ = c.keypad(c.stdscr, true);
|
||||
|
||||
|
|
@ -376,3 +377,29 @@ pub fn addnum(bg: Bg, v: u64) void {
|
|||
pub fn hline(ch: c.chtype, len: u32) void {
|
||||
_ = c.hline(ch, @intCast(i32, len));
|
||||
}
|
||||
|
||||
// Returns 0 if no key was pressed in non-blocking mode.
|
||||
// Returns -1 if it was KEY_RESIZE, requiring a redraw of the screen.
|
||||
pub fn getch(block: bool) i32 {
|
||||
_ = c.nodelay(c.stdscr, !block);
|
||||
// getch() has a bad tendency to not set a sensible errno when it returns ERR.
|
||||
// In non-blocking mode, we can only assume that ERR means "no input yet".
|
||||
// In blocking mode, give it 100 tries with a 10ms delay in between,
|
||||
// then just give up and die to avoid an infinite loop and unresponsive program.
|
||||
var attempts: u8 = 0;
|
||||
while (attempts < 100) : (attempts += 1) {
|
||||
var ch = c.getch();
|
||||
if (ch == c.KEY_RESIZE) {
|
||||
updateSize();
|
||||
return -1;
|
||||
}
|
||||
if (ch == c.ERR) {
|
||||
if (!block) return 0;
|
||||
std.os.nanosleep(0, 10*std.time.ns_per_ms);
|
||||
continue;
|
||||
}
|
||||
return ch;
|
||||
}
|
||||
die("Error reading keyboard input, assuming TTY has been lost.\n(Potentially nonsensical error message: {s})\n",
|
||||
.{ c.strerror(std.c.getErrno(-1)) });
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue