mirror of
https://code.blicky.net/yorhel/ncdu.git
synced 2026-01-12 17:08:39 -09:00
Implement all existing browsing display options + some fixes
I plan to add more display options, but ran out of keys to bind. Probably going for a quick-select menu thingy so that we can keep the old key bindings for people accustomed to it. The graph width algorithm is slightly different, but I think this one's a minor improvement.
This commit is contained in:
parent
231ab1037d
commit
7b3ebf9241
7 changed files with 199 additions and 48 deletions
|
|
@ -29,7 +29,6 @@ backported to the C version, depending on how viable a proper Zig release is.
|
||||||
Missing features:
|
Missing features:
|
||||||
|
|
||||||
- File import
|
- File import
|
||||||
- Most directory listing settings
|
|
||||||
- Lots of informational UI windows
|
- Lots of informational UI windows
|
||||||
- Directory refresh
|
- Directory refresh
|
||||||
- File deletion
|
- File deletion
|
||||||
|
|
|
||||||
133
src/browser.zig
133
src/browser.zig
|
|
@ -2,6 +2,7 @@ const std = @import("std");
|
||||||
const main = @import("main.zig");
|
const main = @import("main.zig");
|
||||||
const model = @import("model.zig");
|
const model = @import("model.zig");
|
||||||
const ui = @import("ui.zig");
|
const ui = @import("ui.zig");
|
||||||
|
const c = @cImport(@cInclude("time.h"));
|
||||||
usingnamespace @import("util.zig");
|
usingnamespace @import("util.zig");
|
||||||
|
|
||||||
// Currently opened directory and its parents.
|
// Currently opened directory and its parents.
|
||||||
|
|
@ -11,6 +12,9 @@ var dir_parents = model.Parents{};
|
||||||
// (first item may be null to indicate the "parent directory" item)
|
// (first item may be null to indicate the "parent directory" item)
|
||||||
var dir_items = std.ArrayList(?*model.Entry).init(main.allocator);
|
var dir_items = std.ArrayList(?*model.Entry).init(main.allocator);
|
||||||
|
|
||||||
|
var dir_max_blocks: u64 = 0;
|
||||||
|
var dir_max_size: u64 = 0;
|
||||||
|
|
||||||
// Index into dir_items that is currently selected.
|
// Index into dir_items that is currently selected.
|
||||||
var cursor_idx: usize = 0;
|
var cursor_idx: usize = 0;
|
||||||
|
|
||||||
|
|
@ -97,7 +101,7 @@ fn sortLt(_: void, ap: ?*model.Entry, bp: ?*model.Entry) bool {
|
||||||
const an = a.name();
|
const an = a.name();
|
||||||
const bn = b.name();
|
const bn = b.name();
|
||||||
return if (main.config.sort_order == .asc) std.mem.lessThan(u8, an, bn)
|
return if (main.config.sort_order == .asc) std.mem.lessThan(u8, an, bn)
|
||||||
else std.mem.lessThan(u8, bn, an) or std.mem.eql(u8, an, bn);
|
else std.mem.lessThan(u8, bn, an);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Should be called when:
|
// Should be called when:
|
||||||
|
|
@ -118,10 +122,15 @@ fn sortDir() void {
|
||||||
// - files in this dir have been added or removed
|
// - files in this dir have been added or removed
|
||||||
pub fn loadDir() !void {
|
pub fn loadDir() !void {
|
||||||
dir_items.shrinkRetainingCapacity(0);
|
dir_items.shrinkRetainingCapacity(0);
|
||||||
|
dir_max_size = 1;
|
||||||
|
dir_max_blocks = 1;
|
||||||
|
|
||||||
if (dir_parents.top() != model.root)
|
if (dir_parents.top() != model.root)
|
||||||
try dir_items.append(null);
|
try dir_items.append(null);
|
||||||
var it = dir_parents.top().sub;
|
var it = dir_parents.top().sub;
|
||||||
while (it) |e| {
|
while (it) |e| {
|
||||||
|
if (e.blocks > dir_max_blocks) dir_max_blocks = e.blocks;
|
||||||
|
if (e.size > dir_max_size) dir_max_size = e.size;
|
||||||
if (main.config.show_hidden) // fast path
|
if (main.config.show_hidden) // fast path
|
||||||
try dir_items.append(e)
|
try dir_items.append(e)
|
||||||
else {
|
else {
|
||||||
|
|
@ -143,7 +152,7 @@ const Row = struct {
|
||||||
|
|
||||||
const Self = @This();
|
const Self = @This();
|
||||||
|
|
||||||
fn flag(self: *Self) !void {
|
fn flag(self: *Self) void {
|
||||||
defer self.col += 2;
|
defer self.col += 2;
|
||||||
const item = self.item orelse return;
|
const item = self.item orelse return;
|
||||||
const ch: u7 = ch: {
|
const ch: u7 = ch: {
|
||||||
|
|
@ -165,7 +174,7 @@ const Row = struct {
|
||||||
ui.addch(ch);
|
ui.addch(ch);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn size(self: *Self) !void {
|
fn size(self: *Self) void {
|
||||||
defer self.col += if (main.config.si) @as(u32, 9) else 10;
|
defer self.col += if (main.config.si) @as(u32, 9) else 10;
|
||||||
const item = self.item orelse return;
|
const item = self.item orelse return;
|
||||||
ui.move(self.row, self.col);
|
ui.move(self.row, self.col);
|
||||||
|
|
@ -173,14 +182,99 @@ const Row = struct {
|
||||||
// TODO: shared sizes
|
// TODO: shared sizes
|
||||||
}
|
}
|
||||||
|
|
||||||
fn name(self: *Self) !void {
|
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;
|
||||||
|
|
||||||
ui.move(self.row, self.col);
|
ui.move(self.row, self.col);
|
||||||
self.bg.fg(.default);
|
self.bg.fg(.default);
|
||||||
|
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) {
|
||||||
|
const perblock = std.math.divCeil(u64, if (main.config.show_blocks) dir_max_blocks else dir_max_size, bar_size) catch unreachable;
|
||||||
|
const num = if (main.config.show_blocks) item.blocks else item.size;
|
||||||
|
var i: u32 = 0;
|
||||||
|
self.bg.fg(.graph);
|
||||||
|
while (i < bar_size) : (i += 1) ui.addch(if (i*perblock <= num) '#' else ' ');
|
||||||
|
}
|
||||||
|
self.bg.fg(.default);
|
||||||
|
ui.addch(']');
|
||||||
|
}
|
||||||
|
|
||||||
|
fn items(self: *Self) void {
|
||||||
|
if (!main.config.show_items) return;
|
||||||
|
defer self.col += 7;
|
||||||
|
const d = if (self.item) |d| d.dir() orelse return else return;
|
||||||
|
const n = d.total_items;
|
||||||
|
ui.move(self.row, self.col);
|
||||||
|
self.bg.fg(.num);
|
||||||
|
if (n < 1000)
|
||||||
|
ui.addprint(" {d:>4}", .{n})
|
||||||
|
else if (n < 100_000)
|
||||||
|
ui.addprint("{d:>6.3}", .{ @intToFloat(f32, n) / 1000 })
|
||||||
|
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);
|
||||||
if (self.item) |i| {
|
if (self.item) |i| {
|
||||||
|
self.bg.fg(if (i.etype == .dir) .dir else .default);
|
||||||
ui.addch(if (i.etype == .dir) '/' else ' ');
|
ui.addch(if (i.etype == .dir) '/' else ' ');
|
||||||
ui.addstr(try ui.shorten(try ui.toUtf8(i.name()), saturateSub(ui.cols, self.col + 1)));
|
ui.addstr(try ui.shorten(try ui.toUtf8(i.name()), saturateSub(ui.cols, self.col + 1)));
|
||||||
} else
|
} else {
|
||||||
|
self.bg.fg(.dir);
|
||||||
ui.addstr("/..");
|
ui.addstr("/..");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn draw(self: *Self) !void {
|
fn draw(self: *Self) !void {
|
||||||
|
|
@ -189,8 +283,11 @@ const Row = struct {
|
||||||
ui.move(self.row, 0);
|
ui.move(self.row, 0);
|
||||||
ui.hline(' ', ui.cols);
|
ui.hline(' ', ui.cols);
|
||||||
}
|
}
|
||||||
try self.flag();
|
self.flag();
|
||||||
try self.size();
|
self.size();
|
||||||
|
self.graph();
|
||||||
|
self.items();
|
||||||
|
self.mtime();
|
||||||
try self.name();
|
try self.name();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
@ -232,7 +329,14 @@ pub fn draw() !void {
|
||||||
ui.hline('-', ui.cols);
|
ui.hline('-', ui.cols);
|
||||||
ui.move(1,3);
|
ui.move(1,3);
|
||||||
ui.addch(' ');
|
ui.addch(' ');
|
||||||
ui.addstr(try ui.shorten(try ui.toUtf8(model.root.entry.name()), saturateSub(ui.cols, 5)));
|
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);
|
||||||
ui.addch(' ');
|
ui.addch(' ');
|
||||||
|
|
||||||
const numrows = saturateSub(ui.rows, 3);
|
const numrows = saturateSub(ui.rows, 3);
|
||||||
|
|
@ -240,6 +344,7 @@ pub fn draw() !void {
|
||||||
if (cursor_idx >= current_view.top + numrows) current_view.top = cursor_idx - numrows + 1;
|
if (cursor_idx >= current_view.top + numrows) current_view.top = cursor_idx - numrows + 1;
|
||||||
|
|
||||||
var i: u32 = 0;
|
var i: u32 = 0;
|
||||||
|
var sel_row: u32 = 0;
|
||||||
while (i < numrows) : (i += 1) {
|
while (i < numrows) : (i += 1) {
|
||||||
if (i+current_view.top >= dir_items.items.len) break;
|
if (i+current_view.top >= dir_items.items.len) break;
|
||||||
var row = Row{
|
var row = Row{
|
||||||
|
|
@ -247,6 +352,7 @@ pub fn draw() !void {
|
||||||
.item = dir_items.items[i+current_view.top],
|
.item = dir_items.items[i+current_view.top],
|
||||||
.bg = if (i+current_view.top == cursor_idx) .sel else .default,
|
.bg = if (i+current_view.top == cursor_idx) .sel else .default,
|
||||||
};
|
};
|
||||||
|
if (row.bg == .sel) sel_row = i+2;
|
||||||
try row.draw();
|
try row.draw();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -262,6 +368,7 @@ pub fn draw() !void {
|
||||||
ui.addnum(.hd, dir_parents.top().total_items);
|
ui.addnum(.hd, dir_parents.top().total_items);
|
||||||
|
|
||||||
if (need_confirm_quit) drawQuit();
|
if (need_confirm_quit) drawQuit();
|
||||||
|
if (sel_row > 0) ui.move(sel_row, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn sortToggle(col: main.SortCol, default_order: main.SortOrder) void {
|
fn sortToggle(col: main.SortCol, default_order: main.SortOrder) void {
|
||||||
|
|
@ -343,6 +450,16 @@ pub fn key(ch: i32) !void {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// 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,
|
||||||
|
},
|
||||||
|
|
||||||
else => {}
|
else => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
21
src/main.zig
21
src/main.zig
|
|
@ -1,4 +1,4 @@
|
||||||
pub const program_version = "2.0";
|
pub const program_version = "2.0-dev";
|
||||||
|
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const model = @import("model.zig");
|
const model = @import("model.zig");
|
||||||
|
|
@ -29,6 +29,9 @@ pub const Config = struct {
|
||||||
|
|
||||||
show_hidden: bool = true,
|
show_hidden: bool = true,
|
||||||
show_blocks: bool = true,
|
show_blocks: bool = true,
|
||||||
|
show_items: bool = false,
|
||||||
|
show_mtime: bool = false,
|
||||||
|
show_graph: enum { off, graph, percent, both } = .graph,
|
||||||
sort_col: SortCol = .blocks,
|
sort_col: SortCol = .blocks,
|
||||||
sort_order: SortOrder = .desc,
|
sort_order: SortOrder = .desc,
|
||||||
sort_dirsfirst: bool = false,
|
sort_dirsfirst: bool = false,
|
||||||
|
|
@ -267,12 +270,16 @@ pub fn handleEvent(block: bool, force_draw: bool) !void {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var ch = ui.getch(block);
|
var firstblock = block;
|
||||||
if (ch == 0) return;
|
while (true) {
|
||||||
if (ch == -1) return handleEvent(block, true);
|
var ch = ui.getch(firstblock);
|
||||||
switch (state) {
|
if (ch == 0) return;
|
||||||
.scan => try scan.key(ch),
|
if (ch == -1) return handleEvent(firstblock, true);
|
||||||
.browse => try browser.key(ch),
|
switch (state) {
|
||||||
|
.scan => try scan.key(ch),
|
||||||
|
.browse => try browser.key(ch),
|
||||||
|
}
|
||||||
|
firstblock = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -106,11 +106,14 @@ pub const Entry = packed struct {
|
||||||
// Means we should count it for other-dev parent dirs, too.
|
// Means we should count it for other-dev parent dirs, too.
|
||||||
var new_hl = false;
|
var new_hl = false;
|
||||||
|
|
||||||
// TODO: Saturating add/substract
|
|
||||||
var it = parents.iter();
|
var it = parents.iter();
|
||||||
while(it.next()) |p| {
|
while(it.next()) |p| {
|
||||||
var add_total = false;
|
var add_total = false;
|
||||||
|
|
||||||
|
if (self.ext()) |e|
|
||||||
|
if (p.entry.ext()) |pe|
|
||||||
|
if (e.mtime > pe.mtime) { pe.mtime = e.mtime; };
|
||||||
|
|
||||||
// Hardlink in a subdirectory with a different device, only count it the first time.
|
// Hardlink in a subdirectory with a different device, only count it the first time.
|
||||||
if (self.link() != null and dev != p.dev) {
|
if (self.link() != null and dev != p.dev) {
|
||||||
add_total = new_hl;
|
add_total = new_hl;
|
||||||
|
|
@ -204,6 +207,12 @@ pub const Ext = packed struct {
|
||||||
mode: u16,
|
mode: u16,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
comptime {
|
||||||
|
std.debug.assert(@bitOffsetOf(Dir, "name") % 8 == 0);
|
||||||
|
std.debug.assert(@bitOffsetOf(Link, "name") % 8 == 0);
|
||||||
|
std.debug.assert(@bitOffsetOf(File, "name") % 8 == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// Hardlink handling:
|
// Hardlink handling:
|
||||||
//
|
//
|
||||||
|
|
@ -350,11 +359,6 @@ pub const Parents = struct {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
test "name offsets" {
|
|
||||||
std.testing.expectEqual(@bitOffsetOf(Dir, "name") % 8, 0);
|
|
||||||
std.testing.expectEqual(@bitOffsetOf(Link, "name") % 8, 0);
|
|
||||||
std.testing.expectEqual(@bitOffsetOf(File, "name") % 8, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
test "entry" {
|
test "entry" {
|
||||||
var e = Entry.create(.file, false, "hello") catch unreachable;
|
var e = Entry.create(.file, false, "hello") catch unreachable;
|
||||||
|
|
|
||||||
36
src/scan.zig
36
src/scan.zig
|
|
@ -19,26 +19,6 @@ const Stat = struct {
|
||||||
symlink: bool,
|
symlink: bool,
|
||||||
ext: model.Ext,
|
ext: model.Ext,
|
||||||
|
|
||||||
// Cast any integer type to the target type, clamping the value to the supported maximum if necessary.
|
|
||||||
fn castClamp(comptime T: type, x: anytype) T {
|
|
||||||
// (adapted from std.math.cast)
|
|
||||||
if (std.math.maxInt(@TypeOf(x)) > std.math.maxInt(T) and x > std.math.maxInt(T)) {
|
|
||||||
return std.math.maxInt(T);
|
|
||||||
} else if (std.math.minInt(@TypeOf(x)) < std.math.minInt(T) and x < std.math.minInt(T)) {
|
|
||||||
return std.math.minInt(T);
|
|
||||||
} else {
|
|
||||||
return @intCast(T, x);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Cast any integer type to the target type, truncating if necessary.
|
|
||||||
fn castTruncate(comptime T: type, x: anytype) T {
|
|
||||||
const Ti = @typeInfo(T).Int;
|
|
||||||
const Xi = @typeInfo(@TypeOf(x)).Int;
|
|
||||||
const nx = if (Xi.signedness != Ti.signedness) @bitCast(std.meta.Int(Ti.signedness, Xi.bits), x) else x;
|
|
||||||
return if (Xi.bits > Ti.bits) @truncate(T, nx) else nx;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn clamp(comptime T: type, comptime field: anytype, x: anytype) std.meta.fieldInfo(T, field).field_type {
|
fn clamp(comptime T: type, comptime field: anytype, x: anytype) std.meta.fieldInfo(T, field).field_type {
|
||||||
return castClamp(std.meta.fieldInfo(T, field).field_type, x);
|
return castClamp(std.meta.fieldInfo(T, field).field_type, x);
|
||||||
}
|
}
|
||||||
|
|
@ -176,9 +156,7 @@ const Context = struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn pathZ(self: *Self) [:0]const u8 {
|
fn pathZ(self: *Self) [:0]const u8 {
|
||||||
self.path.append(0) catch unreachable;
|
return arrayListBufZ(&self.path) catch unreachable;
|
||||||
defer self.path.items.len -= 1;
|
|
||||||
return self.path.items[0..self.path.items.len-1:0];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set a flag to indicate that there was an error listing file entries in the current directory.
|
// Set a flag to indicate that there was an error listing file entries in the current directory.
|
||||||
|
|
@ -266,6 +244,13 @@ const Context = struct {
|
||||||
if (self.parents) |p| p.pop();
|
if (self.parents) |p| p.pop();
|
||||||
if (self.wr) |w| try w.writeByte(']');
|
if (self.wr) |w| try w.writeByte(']');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn deinit(self: *Self) void {
|
||||||
|
if (self.last_error) |p| main.allocator.free(p);
|
||||||
|
if (self.parents) |p| p.deinit();
|
||||||
|
self.path.deinit();
|
||||||
|
self.path_indices.deinit();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Context that is currently being used for scanning.
|
// Context that is currently being used for scanning.
|
||||||
|
|
@ -360,8 +345,10 @@ fn scanDir(ctx: *Context, dir: std.fs.Dir, dir_dev: u64) (std.fs.File.Writer.Err
|
||||||
|
|
||||||
pub fn scanRoot(path: []const u8, out: ?std.fs.File) !void {
|
pub fn scanRoot(path: []const u8, out: ?std.fs.File) !void {
|
||||||
const full_path = std.fs.realpathAlloc(main.allocator, path) catch path;
|
const full_path = std.fs.realpathAlloc(main.allocator, path) catch path;
|
||||||
|
defer main.allocator.free(full_path);
|
||||||
|
|
||||||
var ctx = Context{};
|
var ctx = Context{};
|
||||||
|
defer ctx.deinit();
|
||||||
try ctx.pushPath(full_path);
|
try ctx.pushPath(full_path);
|
||||||
active_context = &ctx;
|
active_context = &ctx;
|
||||||
defer active_context = null;
|
defer active_context = null;
|
||||||
|
|
@ -388,7 +375,8 @@ pub fn scanRoot(path: []const u8, out: ?std.fs.File) !void {
|
||||||
if (model.root.entry.ext()) |ext| ext.* = ctx.stat.ext;
|
if (model.root.entry.ext()) |ext| ext.* = ctx.stat.ext;
|
||||||
}
|
}
|
||||||
|
|
||||||
const dir = try std.fs.cwd().openDirZ(ctx.pathZ(), .{ .access_sub_paths = true, .iterate = true });
|
var dir = try std.fs.cwd().openDirZ(ctx.pathZ(), .{ .access_sub_paths = true, .iterate = true });
|
||||||
|
defer dir.close();
|
||||||
try scanDir(&ctx, dir, ctx.stat.dev);
|
try scanDir(&ctx, dir, ctx.stat.dev);
|
||||||
if (out != null) {
|
if (out != null) {
|
||||||
try ctx.leaveDir();
|
try ctx.leaveDir();
|
||||||
|
|
|
||||||
11
src/ui.zig
11
src/ui.zig
|
|
@ -65,7 +65,7 @@ pub fn toUtf8(in: [:0]const u8) ![:0]const u8 {
|
||||||
try to_utf8_buf.writer().print("\\x{X:0>2}", .{in[i]});
|
try to_utf8_buf.writer().print("\\x{X:0>2}", .{in[i]});
|
||||||
i += 1;
|
i += 1;
|
||||||
}
|
}
|
||||||
return try to_utf8_buf.toOwnedSliceSentinel(0);
|
return try arrayListBufZ(&to_utf8_buf);
|
||||||
}
|
}
|
||||||
|
|
||||||
var shorten_buf = std.ArrayList(u8).init(main.allocator);
|
var shorten_buf = std.ArrayList(u8).init(main.allocator);
|
||||||
|
|
@ -115,7 +115,7 @@ pub fn shorten(in: [:0]const u8, max_width: u32) ![:0] const u8 {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return try shorten_buf.toOwnedSliceSentinel(0);
|
return try arrayListBufZ(&shorten_buf);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn shortenTest(in: [:0]const u8, max_width: u32, out: [:0]const u8) void {
|
fn shortenTest(in: [:0]const u8, max_width: u32, out: [:0]const u8) void {
|
||||||
|
|
@ -325,6 +325,13 @@ pub fn addstr(s: [:0]const u8) void {
|
||||||
_ = c.addstr(s);
|
_ = c.addstr(s);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Not to be used for strings that may end up >256 bytes.
|
||||||
|
pub fn addprint(comptime fmt: []const u8, args: anytype) void {
|
||||||
|
var buf: [256:0]u8 = undefined;
|
||||||
|
const s = std.fmt.bufPrintZ(&buf, fmt, args) catch unreachable;
|
||||||
|
addstr(s);
|
||||||
|
}
|
||||||
|
|
||||||
pub fn addch(ch: c.chtype) void {
|
pub fn addch(ch: c.chtype) void {
|
||||||
_ = c.addch(ch);
|
_ = c.addch(ch);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
29
src/util.zig
29
src/util.zig
|
|
@ -10,7 +10,36 @@ pub fn saturateSub(a: anytype, b: @TypeOf(a)) @TypeOf(a) {
|
||||||
return std.math.sub(@TypeOf(a), a, b) catch std.math.minInt(@TypeOf(a));
|
return std.math.sub(@TypeOf(a), a, b) catch std.math.minInt(@TypeOf(a));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Cast any integer type to the target type, clamping the value to the supported maximum if necessary.
|
||||||
|
pub fn castClamp(comptime T: type, x: anytype) T {
|
||||||
|
// (adapted from std.math.cast)
|
||||||
|
if (std.math.maxInt(@TypeOf(x)) > std.math.maxInt(T) and x > std.math.maxInt(T)) {
|
||||||
|
return std.math.maxInt(T);
|
||||||
|
} else if (std.math.minInt(@TypeOf(x)) < std.math.minInt(T) and x < std.math.minInt(T)) {
|
||||||
|
return std.math.minInt(T);
|
||||||
|
} else {
|
||||||
|
return @intCast(T, x);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cast any integer type to the target type, truncating if necessary.
|
||||||
|
pub fn castTruncate(comptime T: type, x: anytype) T {
|
||||||
|
const Ti = @typeInfo(T).Int;
|
||||||
|
const Xi = @typeInfo(@TypeOf(x)).Int;
|
||||||
|
const nx = if (Xi.signedness != Ti.signedness) @bitCast(std.meta.Int(Ti.signedness, Xi.bits), x) else x;
|
||||||
|
return if (Xi.bits > Ti.bits) @truncate(T, nx) else nx;
|
||||||
|
}
|
||||||
|
|
||||||
// Multiplies by 512, saturating.
|
// Multiplies by 512, saturating.
|
||||||
pub fn blocksToSize(b: u64) u64 {
|
pub fn blocksToSize(b: u64) u64 {
|
||||||
return if (b & 0xFF80000000000000 > 0) std.math.maxInt(u64) else b << 9;
|
return if (b & 0xFF80000000000000 > 0) std.math.maxInt(u64) else b << 9;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Ensure the given arraylist buffer gets zero-terminated and returns a slice
|
||||||
|
// into the buffer. The returned buffer is invalidated whenever the arraylist
|
||||||
|
// is freed or written to.
|
||||||
|
pub fn arrayListBufZ(buf: *std.ArrayList(u8)) ![:0]const u8 {
|
||||||
|
try buf.append(0);
|
||||||
|
defer buf.items.len -= 1;
|
||||||
|
return buf.items[0..buf.items.len-1:0];
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue