mirror of
https://code.blicky.net/yorhel/ncdu.git
synced 2026-01-15 10:18:39 -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
|
- 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:
|
||||||
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"));
|
||||||
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 {
|
||||||
};
|
};
|
||||||
|
|
||||||
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 {
|
||||||
.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 {
|
||||||
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 {
|
||||||
'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),
|
||||||
|
|
|
||||||
78
src/main.zig
78
src/main.zig
|
|
@ -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 {
|
||||||
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 {
|
||||||
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 {
|
||||||
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 {
|
||||||
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;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
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)
|
// (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.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.AccessDenied => "Access denied",
|
||||||
error.SymlinkLoop => "Symlink loop",
|
error.DiskQuota => "Disk quota exceeded",
|
||||||
error.ProcessFdQuotaExceeded => "Process file descriptor limit exceeded",
|
|
||||||
error.SystemFdQuotaExceeded => "System file descriptor limit exceeded",
|
|
||||||
error.NameTooLong => "Filename too long",
|
|
||||||
error.FileNotFound => "No such file or directory",
|
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.IsDir => "Is a directory",
|
||||||
|
error.NameTooLong => "Filename too long",
|
||||||
|
error.NoSpaceLeft => "No space left on device",
|
||||||
error.NotDir => "Not a directory",
|
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 :(
|
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