summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorYorhel <git@yorhel.nl>2021-07-14 11:20:27 +0200
committerYorhel <git@yorhel.nl>2021-07-14 11:24:19 +0200
commit448fa9e7a66a3270650ec61b48fd2683b63049fd (patch)
treea0fa90ccb1bbb66c1f31ff0d9016fd453acac6ef /src
parent6c2ab5001c24150b74bf1acffbc571abc8817232 (diff)
Implement shell spawning
Diffstat (limited to 'src')
-rw-r--r--src/browser.zig29
-rw-r--r--src/main.zig78
-rw-r--r--src/ui.zig18
3 files changed, 108 insertions, 17 deletions
diff --git a/src/browser.zig b/src/browser.zig
index 91c4cc6..0c44bde 100644
--- a/src/browser.zig
+++ b/src/browser.zig
@@ -7,7 +7,7 @@ const c = @cImport(@cInclude("time.h"));
usingnamespace @import("util.zig");
// Currently opened directory and its parents.
-var dir_parents = model.Parents{};
+pub var dir_parents = model.Parents{};
// Sorted list of all items in the currently opened directory.
// (first item may be null to indicate the "parent directory" item)
@@ -305,6 +305,7 @@ const Row = struct {
};
var state: enum { main, quit, info } = .main;
+var message: ?[:0]const u8 = null;
const quit = struct {
fn draw() void {
@@ -625,6 +626,13 @@ pub fn draw() void {
.quit => quit.draw(),
.info => info.draw(),
}
+ if (message) |m| {
+ const box = ui.Box.create(6, 60, "Message");
+ box.move(2, 2);
+ ui.addstr(m);
+ box.move(4, 34);
+ ui.addstr("Press any key to continue");
+ }
if (sel_row > 0) ui.move(sel_row, 0);
}
@@ -656,6 +664,11 @@ fn keyInputSelection(ch: i32, idx: *usize, len: usize, page: u32) bool {
pub fn keyInput(ch: i32) void {
defer current_view.save();
+ if (message != null) {
+ message = null;
+ return;
+ }
+
switch (state) {
.main => {}, // fallthrough
.quit => return quit.keyInput(ch),
@@ -666,13 +679,21 @@ pub fn keyInput(ch: i32) void {
'q' => if (main.config.confirm_quit) { state = .quit; } else ui.quit(),
'i' => info.set(dir_items.items[cursor_idx], .info),
'r' => {
- if (main.config.imported) {
- // TODO: Display message
- } else {
+ if (main.config.imported)
+ message = "Directory imported from file, refreshing is disabled."
+ else {
main.state = .refresh;
scan.setupRefresh(dir_parents.copy());
}
},
+ 'b' => {
+ if (main.config.imported)
+ message = "Shell feature not available for imported directories."
+ else if (!main.config.can_shell)
+ message = "Shell feature disabled in read-only mode."
+ else
+ main.state = .shell;
+ },
// Sort & filter settings
'n' => sortToggle(.name, .asc),
diff --git a/src/main.zig b/src/main.zig
index 71afcd1..08f9980 100644
--- a/src/main.zig
+++ b/src/main.zig
@@ -67,7 +67,7 @@ pub const config = struct {
pub var confirm_quit: bool = false;
};
-pub var state: enum { scan, browse, refresh } = .scan;
+pub var state: enum { scan, browse, refresh, shell } = .scan;
// 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.:
@@ -174,6 +174,62 @@ fn help() noreturn {
std.process.exit(0);
}
+
+fn spawnShell() void {
+ ui.deinit();
+ defer ui.init();
+
+ var path = std.ArrayList(u8).init(allocator);
+ defer path.deinit();
+ browser.dir_parents.fmtPath(true, &path);
+
+ var env = std.process.getEnvMap(allocator) catch unreachable;
+ defer env.deinit();
+ // NCDU_LEVEL can only count to 9, keeps the implementation simple.
+ if (env.get("NCDU_LEVEL")) |l|
+ env.put("NCDU_LEVEL", if (l.len == 0) "1" else switch (l[0]) {
+ '0'...'8' => @as([]const u8, &.{l[0]+1}),
+ '9' => "9",
+ else => "1"
+ }) catch unreachable
+ else
+ env.put("NCDU_LEVEL", "1") catch unreachable;
+
+ const shell = std.os.getenvZ("NCDU_SHELL") orelse std.os.getenvZ("SHELL") orelse "/bin/sh";
+ var child = std.ChildProcess.init(&.{shell}, allocator) catch unreachable;
+ defer child.deinit();
+ child.cwd = path.items;
+ child.env_map = &env;
+
+ const term = child.spawnAndWait() catch |e| blk: {
+ _ = std.io.getStdErr().writer().print(
+ "Error spawning shell: {s}\n\nPress enter to continue.\n",
+ .{ ui.errorString(e) }
+ ) catch {};
+ _ = std.io.getStdIn().reader().skipUntilDelimiterOrEof('\n') catch unreachable;
+ break :blk std.ChildProcess.Term{ .Exited = 0 };
+ };
+ if (term != .Exited) {
+ const n = switch (term) {
+ .Exited => "status",
+ .Signal => "signal",
+ .Stopped => "stopped",
+ .Unknown => "unknown",
+ };
+ const v = switch (term) {
+ .Exited => |v| v,
+ .Signal => |v| v,
+ .Stopped => |v| v,
+ .Unknown => |v| v,
+ };
+ _ = std.io.getStdErr().writer().print(
+ "Shell returned with {s} code {}.\n\nPress enter to continue.\n", .{ n, v }
+ ) catch {};
+ _ = std.io.getStdIn().reader().skipUntilDelimiterOrEof('\n') catch unreachable;
+ }
+}
+
+
fn readExcludeFile(path: []const u8) !void {
const f = try std.fs.cwd().openFile(path, .{});
defer f.close();
@@ -279,12 +335,18 @@ pub fn main() void {
browser.loadDir();
while (true) {
- if (state == .refresh) {
- scan.scan();
- state = .browse;
- browser.loadDir();
- } else
- handleEvent(true, false);
+ switch (state) {
+ .refresh => {
+ scan.scan();
+ state = .browse;
+ browser.loadDir();
+ },
+ .shell => {
+ spawnShell();
+ state = .browse;
+ },
+ else => handleEvent(true, false)
+ }
}
}
@@ -298,6 +360,7 @@ pub fn handleEvent(block: bool, force_draw: bool) void {
switch (state) {
.scan, .refresh => scan.draw(),
.browse => browser.draw(),
+ .shell => unreachable,
}
if (ui.inited) _ = ui.c.refresh();
event_delay_timer.reset();
@@ -315,6 +378,7 @@ pub fn handleEvent(block: bool, force_draw: bool) void {
switch (state) {
.scan, .refresh => scan.keyInput(ch),
.browse => browser.keyInput(ch),
+ .shell => unreachable,
}
firstblock = false;
}
diff --git a/src/ui.zig b/src/ui.zig
index 628cefe..7910ee5 100644
--- a/src/ui.zig
+++ b/src/ui.zig
@@ -52,19 +52,25 @@ pub fn oom() void {
// (Would be nicer if Zig just exposed errno so I could call strerror() directly)
pub fn errorString(e: anyerror) [:0]const u8 {
return switch (e) {
+ error.AccessDenied => "Access denied",
error.DiskQuota => "Disk quota exceeded",
+ error.FileNotFound => "No such file or directory",
+ error.FileSystem => "I/O error", // This one is shit, Zig uses this for both EIO and ELOOP in execve().
error.FileTooBig => "File too big",
+ error.FileBusy => "File is busy",
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.AccessDenied => "Access denied",
- error.SymlinkLoop => "Symlink loop",
+ error.NotDir => "Not a directory",
+ error.OutOfMemory, error.SystemResources => "Out of memory",
error.ProcessFdQuotaExceeded => "Process file descriptor limit exceeded",
+ error.SymlinkLoop => "Symlink loop",
error.SystemFdQuotaExceeded => "System file descriptor limit exceeded",
- error.NameTooLong => "Filename too long",
- error.FileNotFound => "No such file or directory",
- error.IsDir => "Is a directory",
- error.NotDir => "Not a directory",
else => "Unknown error", // rather useless :(
+ // ^ TODO: remove that one and accept only a restricted error set for
+ // compile-time exhaustiveness checks.
};
}