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: ...@@ -30,7 +30,6 @@ Missing features:
- Help window - Help window
- File deletion - File deletion
- Opening a shell
### Improvements compared to the C version ### Improvements compared to the C version
......
...@@ -287,6 +287,12 @@ run ncdu as follows: ...@@ -287,6 +287,12 @@ run ncdu as follows:
export NCDU_SHELL=vifm export NCDU_SHELL=vifm
ncdu 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 =item q
Quit Quit
......
...@@ -7,7 +7,7 @@ const c = @cImport(@cInclude("time.h")); ...@@ -7,7 +7,7 @@ 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.
var dir_parents = model.Parents{}; pub var dir_parents = model.Parents{};
// Sorted list of all items in the currently opened directory. // Sorted list of all items in the currently opened directory.
// (first item may be null to indicate the "parent directory" item) // (first item may be null to indicate the "parent directory" item)
...@@ -305,6 +305,7 @@ const Row = struct { ...@@ -305,6 +305,7 @@ const Row = struct {
}; };
var state: enum { main, quit, info } = .main; var state: enum { main, quit, info } = .main;
var message: ?[:0]const u8 = null;
const quit = struct { const quit = struct {
fn draw() void { fn draw() void {
...@@ -625,6 +626,13 @@ pub fn draw() void { ...@@ -625,6 +626,13 @@ pub fn draw() void {
.quit => quit.draw(), .quit => quit.draw(),
.info => info.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); if (sel_row > 0) ui.move(sel_row, 0);
} }
...@@ -656,6 +664,11 @@ fn keyInputSelection(ch: i32, idx: *usize, len: usize, page: u32) bool { ...@@ -656,6 +664,11 @@ fn keyInputSelection(ch: i32, idx: *usize, len: usize, page: u32) bool {
pub fn keyInput(ch: i32) void { pub fn keyInput(ch: i32) void {
defer current_view.save(); defer current_view.save();
if (message != null) {
message = null;
return;
}
switch (state) { switch (state) {
.main => {}, // fallthrough .main => {}, // fallthrough
.quit => return quit.keyInput(ch), .quit => return quit.keyInput(ch),
...@@ -666,13 +679,21 @@ pub fn keyInput(ch: i32) void { ...@@ -666,13 +679,21 @@ pub fn keyInput(ch: i32) void {
'q' => if (main.config.confirm_quit) { state = .quit; } else ui.quit(), 'q' => if (main.config.confirm_quit) { state = .quit; } else ui.quit(),
'i' => info.set(dir_items.items[cursor_idx], .info), 'i' => info.set(dir_items.items[cursor_idx], .info),
'r' => { 'r' => {
if (main.config.imported) { if (main.config.imported)
// TODO: Display message message = "Directory imported from file, refreshing is disabled."
} else { else {
main.state = .refresh; main.state = .refresh;
scan.setupRefresh(dir_parents.copy()); 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 // Sort & filter settings
'n' => sortToggle(.name, .asc), 'n' => sortToggle(.name, .asc),
......
...@@ -67,7 +67,7 @@ pub const config = struct { ...@@ -67,7 +67,7 @@ pub const config = struct {
pub var confirm_quit: bool = false; 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. // 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.: // T can be any type that has a 'fn next(T) ?[:0]const u8' method, e.g.:
...@@ -174,6 +174,62 @@ fn help() noreturn { ...@@ -174,6 +174,62 @@ fn help() noreturn {
std.process.exit(0); 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 { fn readExcludeFile(path: []const u8) !void {
const f = try std.fs.cwd().openFile(path, .{}); const f = try std.fs.cwd().openFile(path, .{});
defer f.close(); defer f.close();
...@@ -279,12 +335,18 @@ pub fn main() void { ...@@ -279,12 +335,18 @@ pub fn main() void {
browser.loadDir(); browser.loadDir();
while (true) { while (true) {
if (state == .refresh) { switch (state) {
scan.scan(); .refresh => {
state = .browse; scan.scan();
browser.loadDir(); state = .browse;
} else browser.loadDir();
handleEvent(true, false); },
.shell => {
spawnShell();
state = .browse;
},
else => handleEvent(true, false)
}
} }
} }
...@@ -298,6 +360,7 @@ pub fn handleEvent(block: bool, force_draw: bool) void { ...@@ -298,6 +360,7 @@ pub fn handleEvent(block: bool, force_draw: bool) void {
switch (state) { switch (state) {
.scan, .refresh => scan.draw(), .scan, .refresh => scan.draw(),
.browse => browser.draw(), .browse => browser.draw(),
.shell => unreachable,
} }
if (ui.inited) _ = ui.c.refresh(); if (ui.inited) _ = ui.c.refresh();
event_delay_timer.reset(); event_delay_timer.reset();
...@@ -315,6 +378,7 @@ pub fn handleEvent(block: bool, force_draw: bool) void { ...@@ -315,6 +378,7 @@ pub fn handleEvent(block: bool, force_draw: bool) void {
switch (state) { switch (state) {
.scan, .refresh => scan.keyInput(ch), .scan, .refresh => scan.keyInput(ch),
.browse => browser.keyInput(ch), .browse => browser.keyInput(ch),
.shell => unreachable,
} }
firstblock = false; firstblock = false;
} }
......
...@@ -52,19 +52,25 @@ pub fn oom() void { ...@@ -52,19 +52,25 @@ pub fn oom() void {
// (Would be nicer if Zig just exposed errno so I could call strerror() directly) // (Would be nicer if Zig just exposed errno so I could call strerror() directly)
pub fn errorString(e: anyerror) [:0]const u8 { pub fn errorString(e: anyerror) [:0]const u8 {
return switch (e) { return switch (e) {
error.AccessDenied => "Access denied",
error.DiskQuota => "Disk quota exceeded", 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.FileTooBig => "File too big",
error.FileBusy => "File is busy",
error.InputOutput => "I/O error", 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.NoSpaceLeft => "No space left on device",
error.AccessDenied => "Access denied", error.NotDir => "Not a directory",
error.SymlinkLoop => "Symlink loop", error.OutOfMemory, error.SystemResources => "Out of memory",
error.ProcessFdQuotaExceeded => "Process file descriptor limit exceeded", error.ProcessFdQuotaExceeded => "Process file descriptor limit exceeded",
error.SymlinkLoop => "Symlink loop",
error.SystemFdQuotaExceeded => "System file descriptor limit exceeded", 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 :( 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