mirror of
https://code.blicky.net/yorhel/ncdu.git
synced 2026-01-13 01:08:41 -09:00
Implement shell spawning
This commit is contained in:
parent
6c2ab5001c
commit
448fa9e7a6
5 changed files with 116 additions and 20 deletions
|
|
@ -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),
|
||||
|
|
|
|||
72
src/main.zig
72
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) {
|
||||
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;
|
||||
}
|
||||
|
|
|
|||
22
src/ui.zig
22
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.DiskQuota => "Disk quota exceeded",
|
||||
error.FileTooBig => "File too big",
|
||||
error.InputOutput => "I/O error",
|
||||
error.NoSpaceLeft => "No space left on device",
|
||||
error.AccessDenied => "Access denied",
|
||||
error.SymlinkLoop => "Symlink loop",
|
||||
error.ProcessFdQuotaExceeded => "Process file descriptor limit exceeded",
|
||||
error.SystemFdQuotaExceeded => "System file descriptor limit exceeded",
|
||||
error.NameTooLong => "Filename too long",
|
||||
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.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",
|
||||
else => "Unknown error", // rather useless :(
|
||||
// ^ TODO: remove that one and accept only a restricted error set for
|
||||
// compile-time exhaustiveness checks.
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue