Skip to content
Snippets Groups Projects
Commit 448fa9e7 authored by Yorhel's avatar Yorhel
Browse files

Implement shell spawning

parent 6c2ab500
No related branches found
No related tags found
No related merge requests found
......@@ -30,7 +30,6 @@ Missing features:
- Help window
- File deletion
- Opening a shell
### Improvements compared to the C version
......
......@@ -287,6 +287,12 @@ run ncdu as follows:
export NCDU_SHELL=vifm
ncdu
Ncdu will set the C<NCDU_LEVEL> environment variable or increment it before
spawning the shell. This variable allows you to detect when your shell is
running from within ncdu, which can be useful to avoid nesting multiple
instances of ncdu. Ncdu itself does not (currently) warn when attempting to run
nested instances.
=item q
Quit
......
......@@ -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),
......
......@@ -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) {
switch (state) {
.refresh => {
scan.scan();
state = .browse;
browser.loadDir();
} else
handleEvent(true, false);
},
.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;
}
......
......@@ -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.
};
}
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment