mirror of
https://code.blicky.net/yorhel/ncdu.git
synced 2026-01-13 01:08:41 -09:00
Handle allocation failures
In a similar way to the C version of ncdu: by wrapping malloc(). It's simpler to handle allocation failures at the source to allow for easy retries, pushing the retries up the stack will complicate code somewhat more. Likewise, this is a best-effort approach to handling OOM, allocation failures in ncurses aren't handled and display glitches may occur when we get an OOM inside a drawing function. This is a somewhat un-Zig-like way of handling errors and adds scary-looking 'catch unreachable's all over the code, but that's okay.
This commit is contained in:
parent
c077c5bed5
commit
2390308883
7 changed files with 128 additions and 84 deletions
|
|
@ -32,7 +32,6 @@ Missing features:
|
|||
- Directory refresh
|
||||
- File deletion
|
||||
- Opening a shell
|
||||
- OOM handling
|
||||
|
||||
### Improvements compared to the C version
|
||||
|
||||
|
|
|
|||
|
|
@ -120,24 +120,24 @@ fn sortDir() void {
|
|||
// - dir_parents changes (i.e. we change directory)
|
||||
// - config.show_hidden changes
|
||||
// - files in this dir have been added or removed
|
||||
pub fn loadDir() !void {
|
||||
pub fn loadDir() void {
|
||||
dir_items.shrinkRetainingCapacity(0);
|
||||
dir_max_size = 1;
|
||||
dir_max_blocks = 1;
|
||||
|
||||
if (dir_parents.top() != model.root)
|
||||
try dir_items.append(null);
|
||||
dir_items.append(null) catch unreachable;
|
||||
var it = dir_parents.top().sub;
|
||||
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
|
||||
try dir_items.append(e)
|
||||
dir_items.append(e) catch unreachable
|
||||
else {
|
||||
const excl = if (e.file()) |f| f.excluded else false;
|
||||
const name = e.name();
|
||||
if (!excl and name[0] != '.' and name[name.len-1] != '~')
|
||||
try dir_items.append(e);
|
||||
dir_items.append(e) catch unreachable;
|
||||
}
|
||||
it = e.next;
|
||||
}
|
||||
|
|
@ -271,19 +271,19 @@ const Row = struct {
|
|||
ui.addstr(" no mtime");
|
||||
}
|
||||
|
||||
fn name(self: *Self) !void {
|
||||
fn name(self: *Self) void {
|
||||
ui.move(self.row, self.col);
|
||||
if (self.item) |i| {
|
||||
self.bg.fg(if (i.etype == .dir) .dir else .default);
|
||||
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(ui.shorten(ui.toUtf8(i.name()), saturateSub(ui.cols, self.col + 1)));
|
||||
} else {
|
||||
self.bg.fg(.dir);
|
||||
ui.addstr("/..");
|
||||
}
|
||||
}
|
||||
|
||||
fn draw(self: *Self) !void {
|
||||
fn draw(self: *Self) void {
|
||||
if (self.bg == .sel) {
|
||||
self.bg.fg(.default);
|
||||
ui.move(self.row, 0);
|
||||
|
|
@ -294,7 +294,7 @@ const Row = struct {
|
|||
self.graph();
|
||||
self.items();
|
||||
self.mtime();
|
||||
try self.name();
|
||||
self.name();
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -314,7 +314,7 @@ fn drawQuit() void {
|
|||
ui.addch(')');
|
||||
}
|
||||
|
||||
pub fn draw() !void {
|
||||
pub fn draw() void {
|
||||
ui.style(.hd);
|
||||
ui.move(0,0);
|
||||
ui.hline(' ', ui.cols);
|
||||
|
|
@ -340,8 +340,8 @@ pub fn draw() !void {
|
|||
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)));
|
||||
dir_parents.path(&pathbuf);
|
||||
ui.addstr(ui.shorten(ui.toUtf8(arrayListBufZ(&pathbuf)), saturateSub(ui.cols, 5)));
|
||||
pathbuf.deinit();
|
||||
|
||||
ui.style(.default);
|
||||
|
|
@ -361,7 +361,7 @@ pub fn draw() !void {
|
|||
.bg = if (i+current_view.top == cursor_idx) .sel else .default,
|
||||
};
|
||||
if (row.bg == .sel) sel_row = i+2;
|
||||
try row.draw();
|
||||
row.draw();
|
||||
}
|
||||
|
||||
ui.style(.hd);
|
||||
|
|
@ -389,7 +389,7 @@ fn sortToggle(col: main.config.SortCol, default_order: main.config.SortOrder) vo
|
|||
sortDir();
|
||||
}
|
||||
|
||||
pub fn keyInput(ch: i32) !void {
|
||||
pub fn keyInput(ch: i32) void {
|
||||
if (need_confirm_quit) {
|
||||
switch (ch) {
|
||||
'y', 'Y' => if (need_confirm_quit) ui.quit(),
|
||||
|
|
@ -422,7 +422,7 @@ pub fn keyInput(ch: i32) !void {
|
|||
'M' => if (main.config.extended) sortToggle(.mtime, .desc),
|
||||
'e' => {
|
||||
main.config.show_hidden = !main.config.show_hidden;
|
||||
try loadDir();
|
||||
loadDir();
|
||||
},
|
||||
't' => {
|
||||
main.config.sort_dirsfirst = !main.config.sort_dirsfirst;
|
||||
|
|
@ -445,18 +445,18 @@ pub fn keyInput(ch: i32) !void {
|
|||
if (dir_items.items.len == 0) {
|
||||
} else if (dir_items.items[cursor_idx]) |e| {
|
||||
if (e.dir()) |d| {
|
||||
try dir_parents.push(d);
|
||||
try loadDir();
|
||||
dir_parents.push(d);
|
||||
loadDir();
|
||||
}
|
||||
} else if (dir_parents.top() != model.root) {
|
||||
dir_parents.pop();
|
||||
try loadDir();
|
||||
loadDir();
|
||||
}
|
||||
},
|
||||
'h', '<', ui.c.KEY_BACKSPACE, ui.c.KEY_LEFT => {
|
||||
if (dir_parents.top() != model.root) {
|
||||
dir_parents.pop();
|
||||
try loadDir();
|
||||
loadDir();
|
||||
}
|
||||
},
|
||||
|
||||
|
|
|
|||
43
src/main.zig
43
src/main.zig
|
|
@ -7,7 +7,29 @@ const ui = @import("ui.zig");
|
|||
const browser = @import("browser.zig");
|
||||
const c = @cImport(@cInclude("locale.h"));
|
||||
|
||||
pub const allocator = std.heap.c_allocator;
|
||||
// "Custom" allocator that wraps the libc allocator and calls ui.oom() on error.
|
||||
// This allocator never returns an error, it either succeeds or causes ncdu to quit.
|
||||
// (Which means you'll find a lot of "catch unreachable" sprinkled through the code,
|
||||
// they look scarier than they are)
|
||||
fn wrapAlloc(alloc: *std.mem.Allocator, len: usize, alignment: u29, len_align: u29, return_address: usize) error{OutOfMemory}![]u8 {
|
||||
while (true) {
|
||||
if (std.heap.c_allocator.allocFn(alloc, len, alignment, len_align, return_address)) |r|
|
||||
return r
|
||||
else |_| {}
|
||||
ui.oom();
|
||||
}
|
||||
}
|
||||
|
||||
fn wrapResize(alloc: *std.mem.Allocator, buf: []u8, buf_align: u29, new_len: usize, len_align: u29, return_address: usize) std.mem.Allocator.Error!usize {
|
||||
// AFAIK, all uses of resizeFn to grow an allocation will fall back to allocFn on failure.
|
||||
return std.heap.c_allocator.resizeFn(alloc, buf, buf_align, new_len, len_align, return_address);
|
||||
}
|
||||
|
||||
var allocator_state = std.mem.Allocator{
|
||||
.allocFn = wrapAlloc,
|
||||
.resizeFn = wrapResize,
|
||||
};
|
||||
pub const allocator = &allocator_state;
|
||||
|
||||
pub const config = struct {
|
||||
pub const SortCol = enum { name, blocks, size, items, mtime };
|
||||
|
|
@ -158,7 +180,7 @@ fn readExcludeFile(path: []const u8) !void {
|
|||
rd.readUntilDelimiterArrayList(&buf, '\n', 4096)
|
||||
catch |e| if (e != error.EndOfStream) return e else if (buf.items.len == 0) break;
|
||||
if (buf.items.len > 0)
|
||||
try config.exclude_patterns.append(try buf.toOwnedSliceSentinel(0));
|
||||
config.exclude_patterns.append(buf.toOwnedSliceSentinel(0) catch unreachable) catch unreachable;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -203,7 +225,7 @@ pub fn main() !void {
|
|||
else if(opt.is("-f")) import_file = args.arg()
|
||||
else if(opt.is("--si")) config.si = true
|
||||
else if(opt.is("-L") or opt.is("--follow-symlinks")) config.follow_symlinks = true
|
||||
else if(opt.is("--exclude")) try config.exclude_patterns.append(args.arg())
|
||||
else if(opt.is("--exclude")) config.exclude_patterns.append(args.arg()) catch unreachable
|
||||
else if(opt.is("-X") or opt.is("--exclude-from")) {
|
||||
const arg = args.arg();
|
||||
readExcludeFile(arg) catch |e| ui.die("Error reading excludes from {s}: {}.\n", .{ arg, e });
|
||||
|
|
@ -249,22 +271,21 @@ pub fn main() !void {
|
|||
config.scan_ui = .full; // in case we're refreshing from the UI, always in full mode.
|
||||
ui.init();
|
||||
state = .browse;
|
||||
try browser.loadDir();
|
||||
browser.loadDir();
|
||||
|
||||
// TODO: Handle OOM errors
|
||||
while (true) try handleEvent(true, false);
|
||||
while (true) handleEvent(true, false);
|
||||
}
|
||||
|
||||
var event_delay_timer: std.time.Timer = undefined;
|
||||
|
||||
// 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 {
|
||||
pub fn handleEvent(block: bool, force_draw: bool) void {
|
||||
if (block or force_draw or event_delay_timer.read() > config.update_delay) {
|
||||
if (ui.inited) _ = ui.c.erase();
|
||||
switch (state) {
|
||||
.scan => try scan.draw(),
|
||||
.browse => try browser.draw(),
|
||||
.scan => scan.draw(),
|
||||
.browse => browser.draw(),
|
||||
}
|
||||
if (ui.inited) _ = ui.c.refresh();
|
||||
event_delay_timer.reset();
|
||||
|
|
@ -280,8 +301,8 @@ pub fn handleEvent(block: bool, force_draw: bool) !void {
|
|||
if (ch == 0) return;
|
||||
if (ch == -1) return handleEvent(firstblock, true);
|
||||
switch (state) {
|
||||
.scan => try scan.keyInput(ch),
|
||||
.browse => try browser.keyInput(ch),
|
||||
.scan => scan.keyInput(ch),
|
||||
.browse => browser.keyInput(ch),
|
||||
}
|
||||
firstblock = false;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
const std = @import("std");
|
||||
const main = @import("main.zig");
|
||||
const ui = @import("ui.zig");
|
||||
usingnamespace @import("util.zig");
|
||||
|
||||
// While an arena allocator is optimimal for almost all scenarios in which ncdu
|
||||
|
|
@ -67,17 +68,23 @@ pub const Entry = packed struct {
|
|||
return @intToPtr(*Ext, std.mem.alignForward(@ptrToInt(self) + nameOffset(self.etype) + n.len + 1, @alignOf(Ext)));
|
||||
}
|
||||
|
||||
pub fn create(etype: EType, isext: bool, ename: []const u8) !*Entry {
|
||||
pub fn create(etype: EType, isext: bool, ename: []const u8) *Entry {
|
||||
const base_size = nameOffset(etype) + ename.len + 1;
|
||||
const size = (if (isext) std.mem.alignForward(base_size, @alignOf(Ext))+@sizeOf(Ext) else base_size);
|
||||
var ptr = try allocator.allocator.allocWithOptions(u8, size, @alignOf(Entry), null);
|
||||
var ptr = blk: {
|
||||
while (true) {
|
||||
if (allocator.allocator.allocWithOptions(u8, size, @alignOf(Entry), null)) |p|
|
||||
break :blk p
|
||||
else |_| {}
|
||||
ui.oom();
|
||||
}
|
||||
};
|
||||
std.mem.set(u8, ptr, 0); // kind of ugly, but does the trick
|
||||
var e = @ptrCast(*Entry, ptr);
|
||||
e.etype = etype;
|
||||
e.isext = isext;
|
||||
var name_ptr = @intToPtr([*]u8, @ptrToInt(e) + nameOffset(etype));
|
||||
std.mem.copy(u8, name_ptr[0..ename.len], ename);
|
||||
//std.debug.warn("{any}\n", .{ @ptrCast([*]u8, e)[0..size] });
|
||||
return e;
|
||||
}
|
||||
|
||||
|
|
@ -95,8 +102,7 @@ pub const Entry = packed struct {
|
|||
}
|
||||
|
||||
// Insert this entry into the tree at the given directory, updating parent sizes and item counts.
|
||||
// (TODO: This function creates an unrecoverable mess on OOM, need to do something better)
|
||||
pub fn insert(self: *Entry, parents: *const Parents) !void {
|
||||
pub fn insert(self: *Entry, parents: *const Parents) void {
|
||||
self.next = parents.top().sub;
|
||||
parents.top().sub = self;
|
||||
if (self.dir()) |d| std.debug.assert(d.sub == null);
|
||||
|
|
@ -121,7 +127,7 @@ pub const Entry = packed struct {
|
|||
|
||||
} else if (self.link()) |l| {
|
||||
const n = HardlinkNode{ .ino = l.ino, .dir = p, .num_files = 1 };
|
||||
var d = try devices.items[dev].hardlinks.getOrPut(n);
|
||||
var d = devices.items[dev].hardlinks.getOrPut(n) catch unreachable;
|
||||
new_hl = !d.found_existing;
|
||||
if (d.found_existing) d.entry.key.num_files += 1;
|
||||
// First time we encounter this file in this dir, count it.
|
||||
|
|
@ -285,12 +291,12 @@ const Device = struct {
|
|||
var devices: std.ArrayList(Device) = std.ArrayList(Device).init(main.allocator);
|
||||
var dev_lookup: std.AutoHashMap(u64, DevId) = std.AutoHashMap(u64, DevId).init(main.allocator);
|
||||
|
||||
pub fn getDevId(dev: u64) !DevId {
|
||||
var d = try dev_lookup.getOrPut(dev);
|
||||
pub fn getDevId(dev: u64) DevId {
|
||||
var d = dev_lookup.getOrPut(dev) catch unreachable;
|
||||
if (!d.found_existing) {
|
||||
errdefer dev_lookup.removeAssertDiscard(dev);
|
||||
d.entry.value = @intCast(DevId, devices.items.len);
|
||||
try devices.append(.{ .dev = dev });
|
||||
devices.append(.{ .dev = dev }) catch unreachable;
|
||||
}
|
||||
return d.entry.value;
|
||||
}
|
||||
|
|
@ -308,8 +314,8 @@ pub const Parents = struct {
|
|||
|
||||
const Self = @This();
|
||||
|
||||
pub fn push(self: *Self, dir: *Dir) !void {
|
||||
return self.stack.append(dir);
|
||||
pub fn push(self: *Self, dir: *Dir) void {
|
||||
return self.stack.append(dir) catch unreachable;
|
||||
}
|
||||
|
||||
// Attempting to remove the root node is considered a bug.
|
||||
|
|
@ -338,13 +344,14 @@ pub const Parents = struct {
|
|||
return .{ .lst = self };
|
||||
}
|
||||
|
||||
pub fn path(self: *const Self, wr: anytype) !void {
|
||||
// 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 {
|
||||
const r = root.entry.name();
|
||||
try wr.writeAll(r);
|
||||
out.appendSlice(r) catch unreachable;
|
||||
var i: usize = 0;
|
||||
while (i < self.stack.items.len) {
|
||||
if (i != 0 or r[r.len-1] != '/') try wr.writeByte('/');
|
||||
try wr.writeAll(self.stack.items[i].entry.name());
|
||||
if (i != 0 or r[r.len-1] != '/') out.append('/') catch unreachable;
|
||||
out.appendSlice(self.stack.items[i].entry.name()) catch unreachable;
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
53
src/scan.zig
53
src/scan.zig
|
|
@ -132,7 +132,7 @@ const Context = struct {
|
|||
const Self = @This();
|
||||
|
||||
fn initFile(out: std.fs.File) !Self {
|
||||
var buf = try main.allocator.create(Writer);
|
||||
var buf = main.allocator.create(Writer) catch unreachable;
|
||||
errdefer main.allocator.destroy(buf);
|
||||
buf.* = std.io.bufferedWriter(out.writer());
|
||||
var wr = buf.writer();
|
||||
|
|
@ -154,13 +154,13 @@ const Context = struct {
|
|||
}
|
||||
|
||||
// Add the name of the file/dir entry we're currently inspecting
|
||||
fn pushPath(self: *Self, name: []const u8) !void {
|
||||
try self.path_indices.append(self.path.items.len);
|
||||
if (self.path.items.len > 1) try self.path.append('/');
|
||||
fn pushPath(self: *Self, name: []const u8) void {
|
||||
self.path_indices.append(self.path.items.len) catch unreachable;
|
||||
if (self.path.items.len > 1) self.path.append('/') catch unreachable;
|
||||
const start = self.path.items.len;
|
||||
try self.path.appendSlice(name);
|
||||
self.path.appendSlice(name) catch unreachable;
|
||||
|
||||
try self.path.append(0);
|
||||
self.path.append(0) catch unreachable;
|
||||
self.name = self.path.items[start..self.path.items.len-1:0];
|
||||
self.path.items.len -= 1;
|
||||
}
|
||||
|
|
@ -177,7 +177,7 @@ const Context = struct {
|
|||
}
|
||||
|
||||
fn pathZ(self: *Self) [:0]const u8 {
|
||||
return arrayListBufZ(&self.path) catch unreachable;
|
||||
return arrayListBufZ(&self.path);
|
||||
}
|
||||
|
||||
// Set a flag to indicate that there was an error listing file entries in the current directory.
|
||||
|
|
@ -195,12 +195,12 @@ const Context = struct {
|
|||
|
||||
if (t == .err) {
|
||||
if (self.last_error) |p| main.allocator.free(p);
|
||||
self.last_error = try main.allocator.dupeZ(u8, self.path.items);
|
||||
self.last_error = main.allocator.dupeZ(u8, self.path.items) catch unreachable;
|
||||
}
|
||||
|
||||
if (self.parents) |*p| {
|
||||
var e = try model.Entry.create(.file, false, self.name);
|
||||
e.insert(p) catch unreachable;
|
||||
var e = model.Entry.create(.file, false, self.name);
|
||||
e.insert(p);
|
||||
var f = e.file().?;
|
||||
switch (t) {
|
||||
.err => e.set_err(p),
|
||||
|
|
@ -233,10 +233,10 @@ const Context = struct {
|
|||
const etype = if (self.stat.dir) model.EType.dir
|
||||
else if (self.stat.hlinkc) model.EType.link
|
||||
else model.EType.file;
|
||||
var e = try model.Entry.create(etype, main.config.extended, self.name);
|
||||
var e = model.Entry.create(etype, main.config.extended, self.name);
|
||||
e.blocks = self.stat.blocks;
|
||||
e.size = self.stat.size;
|
||||
if (e.dir()) |d| d.dev = try model.getDevId(self.stat.dev);
|
||||
if (e.dir()) |d| d.dev = model.getDevId(self.stat.dev);
|
||||
if (e.file()) |f| f.notreg = !self.stat.dir and !self.stat.reg;
|
||||
// TODO: Handle the scenario where we don't know the hard link count
|
||||
// (i.e. on imports from old ncdu versions that don't have the "nlink" field)
|
||||
|
|
@ -249,8 +249,8 @@ const Context = struct {
|
|||
if (self.items_seen == 0)
|
||||
model.root = e.dir().?
|
||||
else {
|
||||
try e.insert(p);
|
||||
if (e.dir()) |d| try p.push(d); // Enter the directory
|
||||
e.insert(p);
|
||||
if (e.dir()) |d| p.push(d); // Enter the directory
|
||||
}
|
||||
|
||||
} else if (self.wr) |wr| {
|
||||
|
|
@ -286,8 +286,7 @@ const Context = struct {
|
|||
var active_context: ?*Context = null;
|
||||
|
||||
// Read and index entries of the given dir.
|
||||
// (TODO: shouldn't error on OOM but instead call a function that waits or something)
|
||||
fn scanDir(ctx: *Context, dir: std.fs.Dir, dir_dev: u64) (std.fs.File.Writer.Error || std.mem.Allocator.Error)!void {
|
||||
fn scanDir(ctx: *Context, dir: std.fs.Dir, dir_dev: u64) std.fs.File.Writer.Error!void {
|
||||
// XXX: The iterator allocates 8k+ bytes on the stack, may want to do heap allocation here?
|
||||
var it = dir.iterate();
|
||||
while(true) {
|
||||
|
|
@ -297,9 +296,9 @@ fn scanDir(ctx: *Context, dir: std.fs.Dir, dir_dev: u64) (std.fs.File.Writer.Err
|
|||
} orelse break;
|
||||
|
||||
ctx.stat.dir = false;
|
||||
try ctx.pushPath(entry.name);
|
||||
ctx.pushPath(entry.name);
|
||||
defer ctx.popPath();
|
||||
try main.handleEvent(false, false);
|
||||
main.handleEvent(false, false);
|
||||
|
||||
// XXX: This algorithm is extremely slow, can be optimized with some clever pattern parsing.
|
||||
const excluded = blk: {
|
||||
|
|
@ -378,7 +377,7 @@ pub fn scanRoot(path: []const u8, out: ?std.fs.File) !void {
|
|||
|
||||
const full_path = std.fs.realpathAlloc(main.allocator, path) catch null;
|
||||
defer if (full_path) |p| main.allocator.free(p);
|
||||
try ctx.pushPath(full_path orelse path);
|
||||
ctx.pushPath(full_path orelse path);
|
||||
|
||||
ctx.stat = try Stat.read(std.fs.cwd(), ctx.pathZ(), true);
|
||||
if (!ctx.stat.dir) return error.NotADirectory;
|
||||
|
|
@ -703,7 +702,7 @@ const Import = struct {
|
|||
else => self.die("expected ',' or '}'"),
|
||||
}
|
||||
}
|
||||
if (name) |n| self.ctx.pushPath(n) catch unreachable
|
||||
if (name) |n| self.ctx.pushPath(n)
|
||||
else self.die("missing \"name\" field");
|
||||
if (special) |s| self.ctx.addSpecial(s) catch unreachable
|
||||
else self.ctx.addStat(dir_dev) catch unreachable;
|
||||
|
|
@ -733,7 +732,7 @@ const Import = struct {
|
|||
self.ctx.popPath();
|
||||
|
||||
if ((self.ctx.items_seen & 1023) == 0)
|
||||
main.handleEvent(false, false) catch unreachable;
|
||||
main.handleEvent(false, false);
|
||||
}
|
||||
|
||||
fn root(self: *Self) void {
|
||||
|
|
@ -791,7 +790,7 @@ pub fn importRoot(path: [:0]const u8, out: ?std.fs.File) !void {
|
|||
var animation_pos: u32 = 0;
|
||||
var need_confirm_quit = false;
|
||||
|
||||
fn drawBox() !void {
|
||||
fn drawBox() void {
|
||||
ui.init();
|
||||
const ctx = active_context.?;
|
||||
const width = saturateSub(ui.cols, 5);
|
||||
|
|
@ -808,7 +807,7 @@ fn drawBox() !void {
|
|||
|
||||
box.move(3, 2);
|
||||
ui.addstr("Current item: ");
|
||||
ui.addstr(try ui.shorten(try ui.toUtf8(ctx.pathZ()), saturateSub(width, 18)));
|
||||
ui.addstr(ui.shorten(ui.toUtf8(ctx.pathZ()), saturateSub(width, 18)));
|
||||
|
||||
if (ctx.last_error) |path| {
|
||||
box.move(5, 2);
|
||||
|
|
@ -816,7 +815,7 @@ fn drawBox() !void {
|
|||
ui.addstr("Warning: ");
|
||||
ui.style(.default);
|
||||
ui.addstr("error scanning ");
|
||||
ui.addstr(try ui.shorten(try ui.toUtf8(path), saturateSub(width, 28)));
|
||||
ui.addstr(ui.shorten(ui.toUtf8(path), saturateSub(width, 28)));
|
||||
box.move(6, 3);
|
||||
ui.addstr("some directory sizes may not be correct.");
|
||||
}
|
||||
|
|
@ -855,7 +854,7 @@ fn drawBox() !void {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn draw() !void {
|
||||
pub fn draw() void {
|
||||
switch (main.config.scan_ui) {
|
||||
.none => {},
|
||||
.line => {
|
||||
|
|
@ -873,11 +872,11 @@ pub fn draw() !void {
|
|||
}
|
||||
_ = std.io.getStdErr().write(line) catch {};
|
||||
},
|
||||
.full => try drawBox(),
|
||||
.full => drawBox(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn keyInput(ch: i32) !void {
|
||||
pub fn keyInput(ch: i32) void {
|
||||
if (need_confirm_quit) {
|
||||
switch (ch) {
|
||||
'y', 'Y' => if (need_confirm_quit) ui.quit(),
|
||||
|
|
|
|||
38
src/ui.zig
38
src/ui.zig
|
|
@ -29,6 +29,24 @@ pub fn quit() noreturn {
|
|||
std.process.exit(0);
|
||||
}
|
||||
|
||||
// Should be called when malloc fails. Will show a message to the user, wait
|
||||
// for a second and return to give it another try.
|
||||
// Glitch: this function may be called while we're in the process of drawing
|
||||
// the ncurses window, in which case the deinit/reinit will cause the already
|
||||
// drawn part to be discarded. A redraw will fix that, but that tends to only
|
||||
// happen after user input.
|
||||
// Also, init() and other ncurses-related functions may have hidden allocation,
|
||||
// no clue if ncurses will consistently report OOM, but we're not handling that
|
||||
// right now.
|
||||
pub fn oom() void {
|
||||
const haveui = inited;
|
||||
deinit();
|
||||
_ = std.io.getStdErr().writer().writeAll("\x1b7\x1b[JOut of memory, trying again in 1 second. Hit Ctrl-C to abort.\x1b8") catch {};
|
||||
std.time.sleep(std.time.ns_per_s);
|
||||
if (haveui)
|
||||
init();
|
||||
}
|
||||
|
||||
var to_utf8_buf = std.ArrayList(u8).init(main.allocator);
|
||||
|
||||
fn toUtf8BadChar(ch: u8) bool {
|
||||
|
|
@ -44,7 +62,7 @@ fn toUtf8BadChar(ch: u8) bool {
|
|||
// internal buffer that will be invalidated on the next call.
|
||||
// (Doesn't check for non-printable Unicode characters)
|
||||
// (This program assumes that the console locale is UTF-8, but file names may not be)
|
||||
pub fn toUtf8(in: [:0]const u8) ![:0]const u8 {
|
||||
pub fn toUtf8(in: [:0]const u8) [:0]const u8 {
|
||||
const hasBadChar = blk: {
|
||||
for (in) |ch| if (toUtf8BadChar(ch)) break :blk true;
|
||||
break :blk false;
|
||||
|
|
@ -56,16 +74,16 @@ pub fn toUtf8(in: [:0]const u8) ![:0]const u8 {
|
|||
if (std.unicode.utf8ByteSequenceLength(in[i])) |cp_len| {
|
||||
if (!toUtf8BadChar(in[i]) and i + cp_len <= in.len) {
|
||||
if (std.unicode.utf8Decode(in[i .. i + cp_len])) |_| {
|
||||
try to_utf8_buf.appendSlice(in[i .. i + cp_len]);
|
||||
to_utf8_buf.appendSlice(in[i .. i + cp_len]) catch unreachable;
|
||||
i += cp_len;
|
||||
continue;
|
||||
} else |_| {}
|
||||
}
|
||||
} else |_| {}
|
||||
try to_utf8_buf.writer().print("\\x{X:0>2}", .{in[i]});
|
||||
to_utf8_buf.writer().print("\\x{X:0>2}", .{in[i]}) catch unreachable;
|
||||
i += 1;
|
||||
}
|
||||
return try arrayListBufZ(&to_utf8_buf);
|
||||
return arrayListBufZ(&to_utf8_buf);
|
||||
}
|
||||
|
||||
var shorten_buf = std.ArrayList(u8).init(main.allocator);
|
||||
|
|
@ -75,7 +93,7 @@ var shorten_buf = std.ArrayList(u8).init(main.allocator);
|
|||
// Input is assumed to be valid UTF-8.
|
||||
// Return value points to the input string or to an internal buffer that is
|
||||
// invalidated on a subsequent call.
|
||||
pub fn shorten(in: [:0]const u8, max_width: u32) ![:0] const u8 {
|
||||
pub fn shorten(in: [:0]const u8, max_width: u32) [:0] const u8 {
|
||||
if (max_width < 4) return "...";
|
||||
var total_width: u32 = 0;
|
||||
var prefix_width: u32 = 0;
|
||||
|
|
@ -98,8 +116,8 @@ pub fn shorten(in: [:0]const u8, max_width: u32) ![:0] const u8 {
|
|||
if (total_width <= max_width) return in;
|
||||
|
||||
shorten_buf.shrinkRetainingCapacity(0);
|
||||
try shorten_buf.appendSlice(in[0..prefix_end]);
|
||||
try shorten_buf.appendSlice("...");
|
||||
shorten_buf.appendSlice(in[0..prefix_end]) catch unreachable;
|
||||
shorten_buf.appendSlice("...") catch unreachable;
|
||||
|
||||
var start_width: u32 = prefix_width;
|
||||
var start_len: u32 = prefix_end;
|
||||
|
|
@ -111,15 +129,15 @@ pub fn shorten(in: [:0]const u8, max_width: u32) ![:0] const u8 {
|
|||
start_width += cp_width;
|
||||
start_len += cp_len;
|
||||
if (total_width - start_width <= max_width - prefix_width - 3) {
|
||||
try shorten_buf.appendSlice(in[start_len..]);
|
||||
shorten_buf.appendSlice(in[start_len..]) catch unreachable;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return try arrayListBufZ(&shorten_buf);
|
||||
return arrayListBufZ(&shorten_buf);
|
||||
}
|
||||
|
||||
fn shortenTest(in: [:0]const u8, max_width: u32, out: [:0]const u8) !void {
|
||||
try std.testing.expectEqualStrings(out, try shorten(in, max_width));
|
||||
try std.testing.expectEqualStrings(out, shorten(in, max_width));
|
||||
}
|
||||
|
||||
test "shorten" {
|
||||
|
|
|
|||
|
|
@ -38,8 +38,8 @@ pub fn blocksToSize(b: u64) u64 {
|
|||
// 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);
|
||||
pub fn arrayListBufZ(buf: *std.ArrayList(u8)) [:0]const u8 {
|
||||
buf.append(0) catch unreachable;
|
||||
defer buf.items.len -= 1;
|
||||
return buf.items[0..buf.items.len-1:0];
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue