const std = @import("std");
const main = @import("main.zig");
const util = @import("util.zig");
const c = @import("c.zig").c;
pub var inited: bool = false;
pub var main_thread: std.Thread.Id = undefined;
pub var oom_threads = std.atomic.Value(usize).init(0);
pub var rows: u32 = undefined;
pub var cols: u32 = undefined;
pub fn die(comptime fmt: []const u8, args: anytype) noreturn {
deinit();
std.debug.print(fmt, args);
std.process.exit(1);
}
pub fn quit() noreturn {
deinit();
std.process.exit(0);
}
const sleep = if (@hasDecl(std.time, "sleep")) std.time.sleep else std.Thread.sleep;
pub fn oom() void {
@branchHint(.cold);
if (main_thread == std.Thread.getCurrentId()) {
const haveui = inited;
deinit();
std.debug.print("\x1b7\x1b[JOut of memory, trying again in 1 second. Hit Ctrl-C to abort.\x1b8", .{});
sleep(std.time.ns_per_s);
if (haveui)
init();
} else {
_ = oom_threads.fetchAdd(1, .monotonic);
sleep(std.time.ns_per_s);
_ = oom_threads.fetchSub(1, .monotonic);
}
}
pub fn errorString(e: anyerror) [:0]const u8 {
return switch (e) {
error.AccessDenied => "Access denied",
error.DirNotEmpty => "Directory not empty",
error.DiskQuota => "Disk quota exceeded",
error.FileBusy => "File is busy",
error.FileNotFound => "No such file or directory",
error.FileSystem => "I/O error",
error.FileTooBig => "File too big",
error.InputOutput => "I/O error",
error.InvalidExe => "Invalid executable",
error.IsDir => "Is a directory",
error.NameTooLong => "Filename too long",
error.NoSpaceLeft => "No space left on device",
error.NotDir => "Not a directory",
error.OutOfMemory, error.SystemResources => "Out of memory",
error.ProcessFdQuotaExceeded => "Process file descriptor limit exceeded",
error.ReadOnlyFilesystem => "Read-only filesystem",
error.SymlinkLoop => "Symlink loop",
error.SystemFdQuotaExceeded => "System file descriptor limit exceeded",
error.EndOfStream => "Unexpected end of file",
else => @errorName(e),
};
}
var to_utf8_buf: std.ArrayListUnmanaged(u8) = .empty;
fn toUtf8BadChar(ch: u8) bool {
return switch (ch) {
0...0x1F, 0x7F => true,
else => false
};
}
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;
};
if (!hasBadChar and std.unicode.utf8ValidateSlice(in)) return in;
var i: usize = 0;
to_utf8_buf.shrinkRetainingCapacity(0);
while (i < in.len) {
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])) |_| {
to_utf8_buf.appendSlice(main.allocator, in[i .. i + cp_len]) catch unreachable;
i += cp_len;
continue;
} else |_| {}
}
} else |_| {}
to_utf8_buf.writer(main.allocator).print("\\x{X:0>2}", .{in[i]}) catch unreachable;
i += 1;
}
return util.arrayListBufZ(&to_utf8_buf, main.allocator);
}
var shorten_buf: std.ArrayListUnmanaged(u8) = .empty;
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;
var prefix_end: u32 = 0;
var prefix_done = false;
var it = std.unicode.Utf8View.initUnchecked(in).iterator();
while (it.nextCodepoint()) |cp| {
const cp_width_ = c.wcwidth(cp);
const cp_width: u32 = @intCast(if (cp_width_ < 0) 0 else cp_width_);
const cp_len = std.unicode.utf8CodepointSequenceLength(cp) catch unreachable;
total_width += cp_width;
if (!prefix_done and prefix_width + cp_width <= @divFloor(max_width-1, 2)-1) {
prefix_width += cp_width;
prefix_end += cp_len;
} else
prefix_done = true;
}
if (total_width <= max_width) return in;
shorten_buf.shrinkRetainingCapacity(0);
shorten_buf.appendSlice(main.allocator, in[0..prefix_end]) catch unreachable;
shorten_buf.appendSlice(main.allocator, "...") catch unreachable;
var start_width: u32 = prefix_width;
var start_len: u32 = prefix_end;
it = std.unicode.Utf8View.initUnchecked(in[prefix_end..]).iterator();
while (it.nextCodepoint()) |cp| {
const cp_width_ = c.wcwidth(cp);
const cp_width: u32 = @intCast(if (cp_width_ < 0) 0 else cp_width_);
const cp_len = std.unicode.utf8CodepointSequenceLength(cp) catch unreachable;
start_width += cp_width;
start_len += cp_len;
if (total_width - start_width <= max_width - prefix_width - 3) {
shorten_buf.appendSlice(main.allocator, in[start_len..]) catch unreachable;
break;
}
}
return util.arrayListBufZ(&shorten_buf, main.allocator);
}
fn shortenTest(in: [:0]const u8, max_width: u32, out: [:0]const u8) !void {
try std.testing.expectEqualStrings(out, shorten(in, max_width));
}
test "shorten" {
_ = c.setlocale(c.LC_ALL, "");
const t = shortenTest;
try t("abcde", 3, "...");
try t("abcde", 5, "abcde");
try t("abcde", 4, "...e");
try t("abcdefgh", 6, "a...gh");
try t("abcdefgh", 7, "ab...gh");
try t("ABCDEFGH", 16, "ABCDEFGH");
try t("ABCDEFGH", 7, "A...H");
try t("ABCDEFGH", 8, "A...H");
try t("ABCDEFGH", 9, "A...GH");
try t("AaBCDEFGH", 8, "A...H");
try t("ABCDEFGaH", 8, "A...aH");
try t("ABCDEFGH", 15, "ABC...FGH");
try t("❤︎a❤︎a❤︎a", 5, "❤︎...︎a");
try t("ą́ą́ą́ą́ą́ą́", 5, "ą́...̨́ą́");
}
const StyleAttr = struct { fg: i16, bg: i16, attr: u32 };
const StyleDef = struct {
name: [:0]const u8,
off: StyleAttr,
dark: StyleAttr,
darkbg: StyleAttr,
fn style(self: *const @This()) StyleAttr {
return switch (main.config.ui_color) {
.off => self.off,
.dark => self.dark,
.darkbg => self.darkbg,
};
}
};
const styles = [_]StyleDef{
.{ .name = "default",
.off = .{ .fg = -1, .bg = -1, .attr = 0 },
.dark = .{ .fg = -1, .bg = -1, .attr = 0 },
.darkbg = .{ .fg = c.COLOR_WHITE, .bg = c.COLOR_BLACK, .attr = 0 } },
.{ .name = "bold",
.off = .{ .fg = -1, .bg = -1, .attr = c.A_BOLD },
.dark = .{ .fg = -1, .bg = -1, .attr = c.A_BOLD },
.darkbg = .{ .fg = c.COLOR_WHITE, .bg = c.COLOR_BLACK, .attr = c.A_BOLD } },
.{ .name = "bold_hd",
.off = .{ .fg = -1, .bg = -1, .attr = c.A_BOLD|c.A_REVERSE },
.dark = .{ .fg = c.COLOR_BLACK, .bg = c.COLOR_CYAN, .attr = c.A_BOLD },
.darkbg = .{ .fg = c.COLOR_BLACK, .bg = c.COLOR_CYAN, .attr = c.A_BOLD } },
.{ .name = "box_title",
.off = .{ .fg = -1, .bg = -1, .attr = c.A_BOLD },
.dark = .{ .fg = c.COLOR_BLUE, .bg = -1, .attr = c.A_BOLD },
.darkbg = .{ .fg = c.COLOR_BLUE, .bg = c.COLOR_BLACK, .attr = c.A_BOLD } },
.{ .name = "hd",
.off = .{ .fg = -1, .bg = -1, .attr = c.A_REVERSE },
.dark = .{ .fg = c.COLOR_BLACK, .bg = c.COLOR_CYAN, .attr = 0 },
.darkbg = .{ .fg = c.COLOR_BLACK, .bg = c.COLOR_CYAN, .attr = 0 } },
.{ .name = "sel",
.off = .{ .fg = -1, .bg = -1, .attr = c.A_REVERSE },
.dark = .{ .fg = c.COLOR_WHITE, .bg = c.COLOR_GREEN, .attr = c.A_BOLD },
.darkbg = .{ .fg = c.COLOR_WHITE, .bg = c.COLOR_GREEN, .attr = c.A_BOLD } },
.{ .name = "num",
.off = .{ .fg = -1, .bg = -1, .attr = 0 },
.dark = .{ .fg = c.COLOR_YELLOW, .bg = -1, .attr = c.A_BOLD },
.darkbg = .{ .fg = c.COLOR_YELLOW, .bg = c.COLOR_BLACK, .attr = c.A_BOLD } },
.{ .name = "num_hd",
.off = .{ .fg = -1, .bg = -1, .attr = c.A_REVERSE },
.dark = .{ .fg = c.COLOR_YELLOW, .bg = c.COLOR_CYAN, .attr = c.A_BOLD },
.darkbg = .{ .fg = c.COLOR_YELLOW, .bg = c.COLOR_CYAN, .attr = c.A_BOLD } },
.{ .name = "num_sel",
.off = .{ .fg = -1, .bg = -1, .attr = c.A_REVERSE },
.dark = .{ .fg = c.COLOR_YELLOW, .bg = c.COLOR_GREEN, .attr = c.A_BOLD },
.darkbg = .{ .fg = c.COLOR_YELLOW, .bg = c.COLOR_GREEN, .attr = c.A_BOLD } },
.{ .name = "key",
.off = .{ .fg = -1, .bg = -1, .attr = c.A_BOLD },
.dark = .{ .fg = c.COLOR_YELLOW, .bg = -1, .attr = c.A_BOLD },
.darkbg = .{ .fg = c.COLOR_YELLOW, .bg = c.COLOR_BLACK, .attr = c.A_BOLD } },
.{ .name = "key_hd",
.off = .{ .fg = -1, .bg = -1, .attr = c.A_BOLD|c.A_REVERSE },
.dark = .{ .fg = c.COLOR_YELLOW, .bg = c.COLOR_CYAN, .attr = c.A_BOLD },
.darkbg = .{ .fg = c.COLOR_YELLOW, .bg = c.COLOR_CYAN, .attr = c.A_BOLD } },
.{ .name = "dir",
.off = .{ .fg = -1, .bg = -1, .attr = 0 },
.dark = .{ .fg = c.COLOR_BLUE, .bg = -1, .attr = c.A_BOLD },
.darkbg = .{ .fg = c.COLOR_BLUE, .bg = c.COLOR_BLACK, .attr = c.A_BOLD } },
.{ .name = "dir_sel",
.off = .{ .fg = -1, .bg = -1, .attr = c.A_REVERSE },
.dark = .{ .fg = c.COLOR_BLUE, .bg = c.COLOR_GREEN, .attr = c.A_BOLD },
.darkbg = .{ .fg = c.COLOR_BLUE, .bg = c.COLOR_GREEN, .attr = c.A_BOLD } },
.{ .name = "flag",
.off = .{ .fg = -1, .bg = -1, .attr = 0 },
.dark = .{ .fg = c.COLOR_RED, .bg = -1, .attr = 0 },
.darkbg = .{ .fg = c.COLOR_RED, .bg = c.COLOR_BLACK, .attr = 0 } },
.{ .name = "flag_sel",
.off = .{ .fg = -1, .bg = -1, .attr = c.A_REVERSE },
.dark = .{ .fg = c.COLOR_RED, .bg = c.COLOR_GREEN, .attr = 0 },
.darkbg = .{ .fg = c.COLOR_RED, .bg = c.COLOR_GREEN, .attr = 0 } },
.{ .name = "graph",
.off = .{ .fg = -1, .bg = -1, .attr = 0 },
.dark = .{ .fg = c.COLOR_MAGENTA, .bg = -1, .attr = 0 },
.darkbg = .{ .fg = c.COLOR_MAGENTA, .bg = c.COLOR_BLACK, .attr = 0 } },
.{ .name = "graph_sel",
.off = .{ .fg = -1, .bg = -1, .attr = c.A_REVERSE },
.dark = .{ .fg = c.COLOR_MAGENTA, .bg = c.COLOR_GREEN, .attr = 0 },
.darkbg = .{ .fg = c.COLOR_MAGENTA, .bg = c.COLOR_GREEN, .attr = 0 } },
};
pub const Style = lbl: {
var fields: [styles.len]std.builtin.Type.EnumField = undefined;
for (&fields, styles, 0..) |*field, s, i| {
field.* = .{
.name = s.name,
.value = i,
};
}
break :lbl @Type(.{
.@"enum" = .{
.tag_type = u8,
.fields = &fields,
.decls = &[_]std.builtin.Type.Declaration{},
.is_exhaustive = true,
}
});
};
const ui = @This();
pub const Bg = enum {
default, hd, sel,
pub fn fg(self: @This(), s: Style) void {
ui.style(switch (self) {
.default => s,
.hd =>
switch (s) {
.default => Style.hd,
.key => Style.key_hd,
.num => Style.num_hd,
else => unreachable,
},
.sel =>
switch (s) {
.default => Style.sel,
.num => Style.num_sel,
.dir => Style.dir_sel,
.flag => Style.flag_sel,
.graph => Style.graph_sel,
else => unreachable,
}
});
}
};
fn updateSize() void {
rows = @intCast(c.getmaxy(c.stdscr));
cols = @intCast(c.getmaxx(c.stdscr));
}
fn clearScr() void {
std.debug.print("\x1b[J", .{});
}
pub fn init() void {
if (inited) return;
clearScr();
if (main.config.nc_tty) {
const tty = c.fopen("/dev/tty", "r+");
if (tty == null) die("Error opening /dev/tty: {s}.\n", .{ c.strerror(@intFromEnum(std.posix.errno(-1))) });
const term = c.newterm(null, tty, tty);
if (term == null) die("Error initializing ncurses.\n", .{});
_ = c.set_term(term);
} else {
if (c.initscr() == null) die("Error initializing ncurses.\n", .{});
}
updateSize();
_ = c.cbreak();
_ = c.noecho();
_ = c.curs_set(0);
_ = c.keypad(c.stdscr, true);
_ = c.start_color();
_ = c.use_default_colors();
for (styles, 0..) |s, i| _ = c.init_pair(@as(i16, @intCast(i+1)), s.style().fg, s.style().bg);
_ = c.bkgd(@intCast(c.COLOR_PAIR(@intFromEnum(Style.default)+1)));
inited = true;
}
pub fn deinit() void {
if (!inited) {
clearScr();
return;
}
_ = c.erase();
_ = c.refresh();
_ = c.endwin();
inited = false;
}
pub fn style(s: Style) void {
_ = c.attr_set(styles[@intFromEnum(s)].style().attr, @intFromEnum(s)+1, null);
}
pub fn move(y: u32, x: u32) void {
_ = c.move(@as(i32, @intCast(y)), @as(i32, @intCast(x)));
}
pub fn addstr(s: [:0]const u8) void {
_ = c.addstr(s.ptr);
}
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 {
_ = c.addch(ch);
}
pub const FmtSize = struct {
buf: [5:0]u8,
unit: [:0]const u8,
fn init(u: [:0]const u8, n: u64, mul: u64, div: u64) FmtSize {
return .{
.unit = u,
.buf = util.fmt5dec(@intCast( ((n*mul) +| (div / 2)) / div )),
};
}
pub fn fmt(v: u64) FmtSize {
if (main.config.si) {
if (v < 1000) { return FmtSize.init(" B", v, 10, 1); }
else if (v < 999_950) { return FmtSize.init(" kB", v, 1, 100); }
else if (v < 999_950_000) { return FmtSize.init(" MB", v, 1, 100_000); }
else if (v < 999_950_000_000) { return FmtSize.init(" GB", v, 1, 100_000_000); }
else if (v < 999_950_000_000_000) { return FmtSize.init(" TB", v, 1, 100_000_000_000); }
else if (v < 999_950_000_000_000_000) { return FmtSize.init(" PB", v, 1, 100_000_000_000_000); }
else { return FmtSize.init(" EB", v, 1, 100_000_000_000_000_000); }
} else {
if (v < 1000) { return FmtSize.init(" B", v, 10, 1); }
else if (v < 1023949) { return FmtSize.init(" KiB", v, 10, 1<<10); }
else if (v < 1048523572) { return FmtSize.init(" MiB", v, 10, 1<<20); }
else if (v < 1073688136909) { return FmtSize.init(" GiB", v, 10, 1<<30); }
else if (v < 1099456652194612) { return FmtSize.init(" TiB", v, 10, 1<<40); }
else if (v < 1125843611847281869) { return FmtSize.init(" PiB", v, 10, 1<<50); }
else { return FmtSize.init(" EiB", v, 1, (1<<60)/10); }
}
}
pub fn num(self: *const FmtSize) [:0]const u8 {
return &self.buf;
}
fn testEql(self: FmtSize, exp: []const u8) !void {
var buf: [10]u8 = undefined;
try std.testing.expectEqualStrings(exp, try std.fmt.bufPrint(&buf, "{s}{s}", .{ self.num(), self.unit }));
}
};
test "fmtsize" {
main.config.si = true;
try FmtSize.fmt( 0).testEql(" 0.0 B");
try FmtSize.fmt( 999).testEql("999.0 B");
try FmtSize.fmt( 1000).testEql(" 1.0 kB");
try FmtSize.fmt( 1049).testEql(" 1.0 kB");
try FmtSize.fmt( 1050).testEql(" 1.1 kB");
try FmtSize.fmt( 999_899).testEql("999.9 kB");
try FmtSize.fmt( 999_949).testEql("999.9 kB");
try FmtSize.fmt( 999_950).testEql(" 1.0 MB");
try FmtSize.fmt( 1000_000).testEql(" 1.0 MB");
try FmtSize.fmt( 999_850_009).testEql("999.9 MB");
try FmtSize.fmt( 999_899_999).testEql("999.9 MB");
try FmtSize.fmt( 999_900_000).testEql("999.9 MB");
try FmtSize.fmt( 999_949_999).testEql("999.9 MB");
try FmtSize.fmt( 999_950_000).testEql(" 1.0 GB");
try FmtSize.fmt( 999_999_999).testEql(" 1.0 GB");
try FmtSize.fmt(std.math.maxInt(u64)).testEql(" 18.4 EB");
main.config.si = false;
try FmtSize.fmt( 0).testEql(" 0.0 B");
try FmtSize.fmt( 999).testEql("999.0 B");
try FmtSize.fmt( 1000).testEql(" 1.0 KiB");
try FmtSize.fmt( 1024).testEql(" 1.0 KiB");
try FmtSize.fmt( 102400).testEql("100.0 KiB");
try FmtSize.fmt( 1023898).testEql("999.9 KiB");
try FmtSize.fmt( 1023949).testEql(" 1.0 MiB");
try FmtSize.fmt( 1048523571).testEql("999.9 MiB");
try FmtSize.fmt( 1048523572).testEql(" 1.0 GiB");
try FmtSize.fmt( 1073688136908).testEql("999.9 GiB");
try FmtSize.fmt( 1073688136909).testEql(" 1.0 TiB");
try FmtSize.fmt( 1099456652194611).testEql("999.9 TiB");
try FmtSize.fmt( 1099456652194612).testEql(" 1.0 PiB");
try FmtSize.fmt(1125843611847281868).testEql("999.9 PiB");
try FmtSize.fmt(1125843611847281869).testEql(" 1.0 EiB");
try FmtSize.fmt(std.math.maxInt(u64)).testEql(" 16.0 EiB");
}
pub fn addsize(bg: Bg, v: u64) void {
const r = FmtSize.fmt(v);
bg.fg(.num);
addstr(r.num());
bg.fg(.default);
addstr(r.unit);
}
pub fn addnum(bg: Bg, v: u64) void {
var buf: [32]u8 = undefined;
const s = std.fmt.bufPrint(&buf, "{d}", .{v}) catch unreachable;
var f: [64:0]u8 = undefined;
var i: usize = 0;
for (s, 0..) |digit, n| {
if (n != 0 and (s.len - n) % 3 == 0) {
for (main.config.thousands_sep) |ch| {
f[i] = ch;
i += 1;
}
}
f[i] = digit;
i += 1;
}
f[i] = 0;
bg.fg(.num);
addstr(&f);
bg.fg(.default);
}
pub fn addmode(mode: u32) void {
addch(switch (mode & std.posix.S.IFMT) {
std.posix.S.IFDIR => 'd',
std.posix.S.IFREG => '-',
std.posix.S.IFLNK => 'l',
std.posix.S.IFIFO => 'p',
std.posix.S.IFSOCK => 's',
std.posix.S.IFCHR => 'c',
std.posix.S.IFBLK => 'b',
else => '?'
});
addch(if (mode & 0o400 > 0) 'r' else '-');
addch(if (mode & 0o200 > 0) 'w' else '-');
addch(if (mode & 0o4000 > 0) 's' else if (mode & 0o100 > 0) @as(u7, 'x') else '-');
addch(if (mode & 0o040 > 0) 'r' else '-');
addch(if (mode & 0o020 > 0) 'w' else '-');
addch(if (mode & 0o2000 > 0) 's' else if (mode & 0o010 > 0) @as(u7, 'x') else '-');
addch(if (mode & 0o004 > 0) 'r' else '-');
addch(if (mode & 0o002 > 0) 'w' else '-');
addch(if (mode & 0o1000 > 0) (if (std.posix.S.ISDIR(mode)) @as(u7, 't') else 'T') else if (mode & 0o001 > 0) @as(u7, 'x') else '-');
}
pub fn addts(bg: Bg, ts: u64) void {
const t = util.castClamp(c.time_t, ts);
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) {
bg.fg(.num);
ui.addstr(buf[0..len:0]);
} else {
bg.fg(.default);
ui.addstr(" invalid mtime");
}
}
pub fn hline(ch: c.chtype, len: u32) void {
_ = c.hline(ch, @as(i32, @intCast(len)));
}
pub const Box = struct {
start_row: u32,
start_col: u32,
const Self = @This();
pub fn create(height: u32, width: u32, title: [:0]const u8) Self {
const s = Self{
.start_row = (rows>>1) -| (height>>1),
.start_col = (cols>>1) -| (width>>1),
};
style(.default);
if (width < 6 or height < 3) return s;
const acs_map = @extern(*[128]c.chtype, .{ .name = "acs_map" });
const ulcorner = acs_map['l'];
const llcorner = acs_map['m'];
const urcorner = acs_map['k'];
const lrcorner = acs_map['j'];
const acs_hline = acs_map['q'];
const acs_vline = acs_map['x'];
var i: u32 = 0;
while (i < height) : (i += 1) {
s.move(i, 0);
addch(if (i == 0) ulcorner else if (i == height-1) llcorner else acs_vline);
hline(if (i == 0 or i == height-1) acs_hline else ' ', width-2);
s.move(i, width-1);
addch(if (i == 0) urcorner else if (i == height-1) lrcorner else acs_vline);
}
s.move(0, 3);
style(.box_title);
addch(' ');
addstr(title);
addch(' ');
style(.default);
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);
}
pub fn move(s: Self, row: u32, col: u32) void {
ui.move(s.start_row + row, s.start_col + col);
}
};
pub fn getch(block: bool) i32 {
_ = c.nodelay(c.stdscr, !block);
for (0..100) |_| {
const ch = c.getch();
if (ch == c.KEY_RESIZE) {
updateSize();
return -1;
}
if (ch == c.ERR) {
if (!block) return 0;
sleep(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(@intFromEnum(std.posix.errno(-1))) });
}
fn waitInput() void {
if (@hasDecl(std.io, "getStdIn")) {
std.io.getStdIn().reader().skipUntilDelimiterOrEof('\n') catch unreachable;
} else {
var buf: [512]u8 = undefined;
var rd = std.fs.File.stdin().reader(&buf);
_ = rd.interface.discardDelimiterExclusive('\n') catch unreachable;
}
}
pub fn runCmd(cmd: []const []const u8, cwd: ?[]const u8, env: *std.process.EnvMap, reporterr: bool) void {
deinit();
defer init();
if (env.get("NCDU_LEVEL")) |l|
env.put("NCDU_LEVEL", if (l.len == 0) "1" else switch (l[0]) {
'0'...'8' => |d| &[1] u8{d+1},
'9' => "9",
else => "1"
}) catch unreachable
else
env.put("NCDU_LEVEL", "1") catch unreachable;
var child = std.process.Child.init(cmd, main.allocator);
child.cwd = cwd;
child.env_map = env;
const term = child.spawnAndWait() catch |e| blk: {
std.debug.print("Error running command: {s}\n\nPress enter to continue.\n", .{ ui.errorString(e) });
waitInput();
break :blk std.process.Child.Term{ .Exited = 0 };
};
const n = switch (term) {
.Exited => "error",
.Signal => "signal",
.Stopped => "stopped",
.Unknown => "unknown",
};
const v = switch (term) { inline else => |v| v };
if (term != .Exited or (reporterr and v != 0)) {
std.debug.print("\nCommand returned with {s} code {}.\nPress enter to continue.\n", .{ n, v });
waitInput();
}
}