mirror of
https://code.blicky.net/yorhel/ncdu.git
synced 2026-01-12 17:08:39 -09:00
Add --delete-command option
Fixes #215. delete.zig's item replacement/refresh code is pretty awful and may be buggy in some edge cases. Existing refresh infrastructure wasn't designed to update an individual file.
This commit is contained in:
parent
67f34090fb
commit
66b875eb00
6 changed files with 184 additions and 83 deletions
26
ncdu.1
26
ncdu.1
|
|
@ -44,6 +44,7 @@
|
||||||
.Op Fl \-group\-directories\-first , \-no\-group\-directories\-first
|
.Op Fl \-group\-directories\-first , \-no\-group\-directories\-first
|
||||||
.Op Fl \-confirm\-quit , \-no\-confirm\-quit
|
.Op Fl \-confirm\-quit , \-no\-confirm\-quit
|
||||||
.Op Fl \-confirm\-delete , \-no\-confirm\-delete
|
.Op Fl \-confirm\-delete , \-no\-confirm\-delete
|
||||||
|
.Op Fl \-delete\-command Ar command
|
||||||
.Op Fl \-color Ar off | dark | dark-bg
|
.Op Fl \-color Ar off | dark | dark-bg
|
||||||
.Op Ar path
|
.Op Ar path
|
||||||
.Nm
|
.Nm
|
||||||
|
|
@ -359,6 +360,31 @@ Can be helpful when you accidentally press 'q' during or after a very long scan.
|
||||||
Require a confirmation before deleting a file or directory.
|
Require a confirmation before deleting a file or directory.
|
||||||
Enabled by default, but can be disabled if you're absolutely sure you won't
|
Enabled by default, but can be disabled if you're absolutely sure you won't
|
||||||
accidentally press 'd'.
|
accidentally press 'd'.
|
||||||
|
.It Fl \-delete\-command Ar command
|
||||||
|
When set to a non-empty string, replace the built-in file deletion feature with
|
||||||
|
a custom shell command.
|
||||||
|
.Pp
|
||||||
|
The absolute path of the item to be deleted is appended to the given command
|
||||||
|
and the result is evaluated in a shell.
|
||||||
|
The command is run from the same directory that ncdu itself was started in.
|
||||||
|
The
|
||||||
|
.Ev NCDU_DELETE_PATH
|
||||||
|
environment variable is set to the absolute path of the item to be deleted and
|
||||||
|
.Ev NCDU_LEVEL
|
||||||
|
is set in the same fashion as when spawning a shell from within ncdu.
|
||||||
|
.Pp
|
||||||
|
After command completion, the in-memory view of the selected item is refreshed
|
||||||
|
and directory sizes are adjusted as necessary.
|
||||||
|
This is not a full refresh of the complete directory tree, so if the item has
|
||||||
|
been renamed or moved to another directory, it's new location is not
|
||||||
|
automatically picked up.
|
||||||
|
.Pp
|
||||||
|
For example, to use
|
||||||
|
.Xr rm 1
|
||||||
|
interactive mode to prompt before each deletion:
|
||||||
|
.Dl ncdu --no-confirm-delete --delete-command \[aq]rm -ri --\[aq]
|
||||||
|
Or to move files to trash:
|
||||||
|
.Dl ncdu --delete-command \[aq]gio trash --\[aq]
|
||||||
.It Fl \-color Ar off | dark | dark-bg
|
.It Fl \-color Ar off | dark | dark-bg
|
||||||
Set the color scheme.
|
Set the color scheme.
|
||||||
The following schemes are recognized:
|
The following schemes are recognized:
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,9 @@ const main = @import("main.zig");
|
||||||
const model = @import("model.zig");
|
const model = @import("model.zig");
|
||||||
const ui = @import("ui.zig");
|
const ui = @import("ui.zig");
|
||||||
const browser = @import("browser.zig");
|
const browser = @import("browser.zig");
|
||||||
|
const scan = @import("scan.zig");
|
||||||
|
const sink = @import("sink.zig");
|
||||||
|
const mem_sink = @import("mem_sink.zig");
|
||||||
const util = @import("util.zig");
|
const util = @import("util.zig");
|
||||||
const c = @import("c.zig").c;
|
const c = @import("c.zig").c;
|
||||||
|
|
||||||
|
|
@ -68,6 +71,57 @@ fn deleteItem(dir: std.fs.Dir, path: [:0]const u8, ptr: *align(1) ?*model.Entry)
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Returns true if the item has been deleted successfully.
|
||||||
|
fn deleteCmd(path: [:0]const u8, ptr: *align(1) ?*model.Entry) bool {
|
||||||
|
{
|
||||||
|
var env = std.process.getEnvMap(main.allocator) catch unreachable;
|
||||||
|
defer env.deinit();
|
||||||
|
env.put("NCDU_DELETE_PATH", path) catch unreachable;
|
||||||
|
|
||||||
|
// Since we're passing the path as an environment variable and go through
|
||||||
|
// the shell anyway, we can refer to the variable and avoid error-prone
|
||||||
|
// shell escaping.
|
||||||
|
const cmd = std.fmt.allocPrint(main.allocator, "{s} \"$NCDU_DELETE_PATH\"", .{main.config.delete_command}) catch unreachable;
|
||||||
|
defer main.allocator.free(cmd);
|
||||||
|
ui.runCmd(&.{"/bin/sh", "-c", cmd}, null, &env, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
const stat = scan.statAt(std.fs.cwd(), path, false, null) catch {
|
||||||
|
// Stat failed. Would be nice to display an error if it's not
|
||||||
|
// 'FileNotFound', but w/e, let's just assume the item has been
|
||||||
|
// deleted as expected.
|
||||||
|
ptr.*.?.zeroStats(parent);
|
||||||
|
ptr.* = ptr.*.?.next.ptr;
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
// If either old or new entry is not a dir, remove & re-add entry in the in-memory tree.
|
||||||
|
if (ptr.*.?.pack.etype != .dir or stat.etype != .dir) {
|
||||||
|
ptr.*.?.zeroStats(parent);
|
||||||
|
const e = model.Entry.create(main.allocator, stat.etype, main.config.extended and !stat.ext.isEmpty(), ptr.*.?.name());
|
||||||
|
e.next.ptr = ptr.*.?.next.ptr;
|
||||||
|
mem_sink.statToEntry(&stat, e, parent);
|
||||||
|
ptr.* = e;
|
||||||
|
|
||||||
|
var it : ?*model.Dir = parent;
|
||||||
|
while (it) |p| : (it = p.parent) {
|
||||||
|
if (stat.etype != .link) {
|
||||||
|
p.entry.pack.blocks +|= e.pack.blocks;
|
||||||
|
p.entry.size +|= e.size;
|
||||||
|
}
|
||||||
|
p.items +|= 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If new entry is a dir, recursively scan.
|
||||||
|
if (ptr.*.?.dir()) |d| {
|
||||||
|
main.state = .refresh;
|
||||||
|
sink.global.sink = .mem;
|
||||||
|
mem_sink.global.root = d;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// Returns the item that should be selected in the browser.
|
// Returns the item that should be selected in the browser.
|
||||||
pub fn delete() ?*model.Entry {
|
pub fn delete() ?*model.Entry {
|
||||||
while (main.state == .delete and state == .confirm)
|
while (main.state == .delete and state == .confirm)
|
||||||
|
|
@ -89,23 +143,39 @@ pub fn delete() ?*model.Entry {
|
||||||
path.append('/') catch unreachable;
|
path.append('/') catch unreachable;
|
||||||
path.appendSlice(entry.name()) catch unreachable;
|
path.appendSlice(entry.name()) catch unreachable;
|
||||||
|
|
||||||
_ = deleteItem(std.fs.cwd(), util.arrayListBufZ(&path), it);
|
if (main.config.delete_command.len == 0) {
|
||||||
model.inodes.addAllStats();
|
_ = deleteItem(std.fs.cwd(), util.arrayListBufZ(&path), it);
|
||||||
return if (it.* == e) e else next_sel;
|
model.inodes.addAllStats();
|
||||||
|
return if (it.* == e) e else next_sel;
|
||||||
|
} else {
|
||||||
|
const isdel = deleteCmd(util.arrayListBufZ(&path), it);
|
||||||
|
model.inodes.addAllStats();
|
||||||
|
return if (isdel) next_sel else it.*;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn drawConfirm() void {
|
fn drawConfirm() void {
|
||||||
browser.draw();
|
browser.draw();
|
||||||
const box = ui.Box.create(6, 60, "Confirm delete");
|
const box = ui.Box.create(6, 60, "Confirm delete");
|
||||||
box.move(1, 2);
|
box.move(1, 2);
|
||||||
ui.addstr("Are you sure you want to delete \"");
|
if (main.config.delete_command.len == 0) {
|
||||||
ui.addstr(ui.shorten(ui.toUtf8(entry.name()), 21));
|
ui.addstr("Are you sure you want to delete \"");
|
||||||
ui.addch('"');
|
ui.addstr(ui.shorten(ui.toUtf8(entry.name()), 21));
|
||||||
if (entry.pack.etype != .dir)
|
ui.addch('"');
|
||||||
ui.addch('?')
|
if (entry.pack.etype != .dir)
|
||||||
else {
|
ui.addch('?')
|
||||||
box.move(2, 18);
|
else {
|
||||||
ui.addstr("and all of its contents?");
|
box.move(2, 18);
|
||||||
|
ui.addstr("and all of its contents?");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ui.addstr("Are you sure you want to run \"");
|
||||||
|
ui.addstr(ui.shorten(ui.toUtf8(main.config.delete_command), 25));
|
||||||
|
ui.addch('"');
|
||||||
|
box.move(2, 4);
|
||||||
|
ui.addstr("on \"");
|
||||||
|
ui.addstr(ui.shorten(ui.toUtf8(entry.name()), 50));
|
||||||
|
ui.addch('"');
|
||||||
}
|
}
|
||||||
|
|
||||||
box.move(4, 15);
|
box.move(4, 15);
|
||||||
|
|
|
||||||
66
src/main.zig
66
src/main.zig
|
|
@ -114,6 +114,7 @@ pub const config = struct {
|
||||||
pub var confirm_quit: bool = false;
|
pub var confirm_quit: bool = false;
|
||||||
pub var confirm_delete: bool = true;
|
pub var confirm_delete: bool = true;
|
||||||
pub var ignore_delete_errors: bool = false;
|
pub var ignore_delete_errors: bool = false;
|
||||||
|
pub var delete_command: [:0]const u8 = "";
|
||||||
};
|
};
|
||||||
|
|
||||||
pub var state: enum { scan, browse, refresh, shell, delete } = .scan;
|
pub var state: enum { scan, browse, refresh, shell, delete } = .scan;
|
||||||
|
|
@ -306,6 +307,7 @@ fn argConfig(args: *Args, opt: Args.Option, infile: bool) !void {
|
||||||
else if (opt.is("--no-confirm-quit")) config.confirm_quit = false
|
else if (opt.is("--no-confirm-quit")) config.confirm_quit = false
|
||||||
else if (opt.is("--confirm-delete")) config.confirm_delete = true
|
else if (opt.is("--confirm-delete")) config.confirm_delete = true
|
||||||
else if (opt.is("--no-confirm-delete")) config.confirm_delete = false
|
else if (opt.is("--no-confirm-delete")) config.confirm_delete = false
|
||||||
|
else if (opt.is("--delete-command")) config.delete_command = allocator.dupeZ(u8, try args.arg()) catch unreachable
|
||||||
else if (opt.is("--color")) {
|
else if (opt.is("--color")) {
|
||||||
const val = try args.arg();
|
const val = try args.arg();
|
||||||
if (std.mem.eql(u8, val, "off")) config.ui_color = .off
|
if (std.mem.eql(u8, val, "off")) config.ui_color = .off
|
||||||
|
|
@ -428,6 +430,7 @@ fn help() noreturn {
|
||||||
\\ --group-directories-first Sort directories before files
|
\\ --group-directories-first Sort directories before files
|
||||||
\\ --confirm-quit Ask confirmation before quitting ncdu
|
\\ --confirm-quit Ask confirmation before quitting ncdu
|
||||||
\\ --no-confirm-delete Don't ask confirmation before deletion
|
\\ --no-confirm-delete Don't ask confirmation before deletion
|
||||||
|
\\ --delete-command CMD Command to run for file deletion
|
||||||
\\ --color SCHEME off / dark / dark-bg
|
\\ --color SCHEME off / dark / dark-bg
|
||||||
\\
|
\\
|
||||||
\\Refer to `man ncdu` for more information.
|
\\Refer to `man ncdu` for more information.
|
||||||
|
|
@ -437,58 +440,6 @@ fn help() noreturn {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fn spawnShell() void {
|
|
||||||
ui.deinit();
|
|
||||||
defer ui.init();
|
|
||||||
|
|
||||||
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' => |d| &[1] u8{d+1},
|
|
||||||
'9' => "9",
|
|
||||||
else => "1"
|
|
||||||
}) catch unreachable
|
|
||||||
else
|
|
||||||
env.put("NCDU_LEVEL", "1") catch unreachable;
|
|
||||||
|
|
||||||
const shell = std.posix.getenvZ("NCDU_SHELL") orelse std.posix.getenvZ("SHELL") orelse "/bin/sh";
|
|
||||||
var child = std.process.Child.init(&.{shell}, allocator);
|
|
||||||
child.cwd = browser.dir_path;
|
|
||||||
child.env_map = &env;
|
|
||||||
|
|
||||||
const stdin = std.io.getStdIn();
|
|
||||||
const stderr = std.io.getStdErr();
|
|
||||||
const term = child.spawnAndWait() catch |e| blk: {
|
|
||||||
stderr.writer().print(
|
|
||||||
"Error spawning shell: {s}\n\nPress enter to continue.\n",
|
|
||||||
.{ ui.errorString(e) }
|
|
||||||
) catch {};
|
|
||||||
stdin.reader().skipUntilDelimiterOrEof('\n') catch unreachable;
|
|
||||||
break :blk std.process.Child.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,
|
|
||||||
};
|
|
||||||
stderr.writer().print(
|
|
||||||
"Shell returned with {s} code {}.\n\nPress enter to continue.\n", .{ n, v }
|
|
||||||
) catch {};
|
|
||||||
stdin.reader().skipUntilDelimiterOrEof('\n') catch unreachable;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
fn readExcludeFile(path: [:0]const u8) !void {
|
fn readExcludeFile(path: [:0]const u8) !void {
|
||||||
const f = try std.fs.cwd().openFileZ(path, .{});
|
const f = try std.fs.cwd().openFileZ(path, .{});
|
||||||
defer f.close();
|
defer f.close();
|
||||||
|
|
@ -671,13 +622,18 @@ pub fn main() void {
|
||||||
browser.loadDir(0);
|
browser.loadDir(0);
|
||||||
},
|
},
|
||||||
.shell => {
|
.shell => {
|
||||||
spawnShell();
|
const shell = std.posix.getenvZ("NCDU_SHELL") orelse std.posix.getenvZ("SHELL") orelse "/bin/sh";
|
||||||
|
var env = std.process.getEnvMap(allocator) catch unreachable;
|
||||||
|
defer env.deinit();
|
||||||
|
ui.runCmd(&.{shell}, browser.dir_path, &env, false);
|
||||||
state = .browse;
|
state = .browse;
|
||||||
},
|
},
|
||||||
.delete => {
|
.delete => {
|
||||||
const next = delete.delete();
|
const next = delete.delete();
|
||||||
state = .browse;
|
if (state != .refresh) {
|
||||||
browser.loadDir(if (next) |n| n.nameHash() else 0);
|
state = .browse;
|
||||||
|
browser.loadDir(if (next) |n| n.nameHash() else 0);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
else => handleEvent(true, false)
|
else => handleEvent(true, false)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,24 @@ pub const Thread = struct {
|
||||||
arena: std.heap.ArenaAllocator = std.heap.ArenaAllocator.init(std.heap.page_allocator),
|
arena: std.heap.ArenaAllocator = std.heap.ArenaAllocator.init(std.heap.page_allocator),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
pub fn statToEntry(stat: *const sink.Stat, e: *model.Entry, parent: *model.Dir) void {
|
||||||
|
e.pack.blocks = stat.blocks;
|
||||||
|
e.size = stat.size;
|
||||||
|
if (e.dir()) |d| {
|
||||||
|
d.parent = parent;
|
||||||
|
d.pack.dev = model.devices.getId(stat.dev);
|
||||||
|
}
|
||||||
|
if (e.link()) |l| {
|
||||||
|
l.parent = parent;
|
||||||
|
l.ino = stat.ino;
|
||||||
|
l.pack.nlink = stat.nlink;
|
||||||
|
model.inodes.lock.lock();
|
||||||
|
defer model.inodes.lock.unlock();
|
||||||
|
l.addLink();
|
||||||
|
}
|
||||||
|
if (e.ext()) |ext| ext.* = stat.ext;
|
||||||
|
}
|
||||||
|
|
||||||
pub const Dir = struct {
|
pub const Dir = struct {
|
||||||
dir: *model.Dir,
|
dir: *model.Dir,
|
||||||
entries: Map,
|
entries: Map,
|
||||||
|
|
@ -107,21 +125,7 @@ pub const Dir = struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
const e = self.getEntry(t, stat.etype, main.config.extended and !stat.ext.isEmpty(), name);
|
const e = self.getEntry(t, stat.etype, main.config.extended and !stat.ext.isEmpty(), name);
|
||||||
e.pack.blocks = stat.blocks;
|
statToEntry(stat, e, self.dir);
|
||||||
e.size = stat.size;
|
|
||||||
if (e.dir()) |d| {
|
|
||||||
d.parent = self.dir;
|
|
||||||
d.pack.dev = model.devices.getId(stat.dev);
|
|
||||||
}
|
|
||||||
if (e.link()) |l| {
|
|
||||||
l.parent = self.dir;
|
|
||||||
l.ino = stat.ino;
|
|
||||||
l.pack.nlink = stat.nlink;
|
|
||||||
model.inodes.lock.lock();
|
|
||||||
defer model.inodes.lock.unlock();
|
|
||||||
l.addLink();
|
|
||||||
}
|
|
||||||
if (e.ext()) |ext| ext.* = stat.ext;
|
|
||||||
return e;
|
return e;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -46,7 +46,7 @@ fn truncate(comptime T: type, comptime field: anytype, x: anytype) std.meta.fiel
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fn statAt(parent: std.fs.Dir, name: [:0]const u8, follow: bool, symlink: *bool) !sink.Stat {
|
pub fn statAt(parent: std.fs.Dir, name: [:0]const u8, follow: bool, symlink: ?*bool) !sink.Stat {
|
||||||
// std.posix.fstatatZ() in Zig 0.14 is not suitable due to https://github.com/ziglang/zig/issues/23463
|
// std.posix.fstatatZ() in Zig 0.14 is not suitable due to https://github.com/ziglang/zig/issues/23463
|
||||||
var stat: std.c.Stat = undefined;
|
var stat: std.c.Stat = undefined;
|
||||||
if (std.c.fstatat(parent.fd, name, &stat, if (follow) 0 else std.c.AT.SYMLINK_NOFOLLOW) != 0) {
|
if (std.c.fstatat(parent.fd, name, &stat, if (follow) 0 else std.c.AT.SYMLINK_NOFOLLOW) != 0) {
|
||||||
|
|
@ -58,7 +58,7 @@ fn statAt(parent: std.fs.Dir, name: [:0]const u8, follow: bool, symlink: *bool)
|
||||||
else => error.Unexpected,
|
else => error.Unexpected,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
symlink.* = std.c.S.ISLNK(stat.mode);
|
if (symlink) |s| s.* = std.c.S.ISLNK(stat.mode);
|
||||||
return sink.Stat{
|
return sink.Stat{
|
||||||
.etype =
|
.etype =
|
||||||
if (std.c.S.ISDIR(stat.mode)) .dir
|
if (std.c.S.ISDIR(stat.mode)) .dir
|
||||||
|
|
|
||||||
45
src/ui.zig
45
src/ui.zig
|
|
@ -642,3 +642,48 @@ pub fn getch(block: bool) i32 {
|
||||||
die("Error reading keyboard input, assuming TTY has been lost.\n(Potentially nonsensical error message: {s})\n",
|
die("Error reading keyboard input, assuming TTY has been lost.\n(Potentially nonsensical error message: {s})\n",
|
||||||
.{ c.strerror(@intFromEnum(std.posix.errno(-1))) });
|
.{ c.strerror(@intFromEnum(std.posix.errno(-1))) });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
pub fn runCmd(cmd: []const []const u8, cwd: ?[]const u8, env: *std.process.EnvMap, reporterr: bool) void {
|
||||||
|
deinit();
|
||||||
|
defer init();
|
||||||
|
|
||||||
|
// 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' => |d| &[1] u8{d+1},
|
||||||
|
'9' => "9",
|
||||||
|
else => "1"
|
||||||
|
}) catch unreachable
|
||||||
|
else
|
||||||
|
env.put("NCDU_LEVEL", "1") catch unreachable;
|
||||||
|
|
||||||
|
var child = std.process.Child.init(cmd, main.allocator);
|
||||||
|
child.cwd = cwd;
|
||||||
|
child.env_map = env;
|
||||||
|
|
||||||
|
const stdin = std.io.getStdIn();
|
||||||
|
const stderr = std.io.getStdErr();
|
||||||
|
const term = child.spawnAndWait() catch |e| blk: {
|
||||||
|
stderr.writer().print(
|
||||||
|
"Error running command: {s}\n\nPress enter to continue.\n",
|
||||||
|
.{ ui.errorString(e) }
|
||||||
|
) catch {};
|
||||||
|
stdin.reader().skipUntilDelimiterOrEof('\n') catch unreachable;
|
||||||
|
break :blk std.process.Child.Term{ .Exited = 0 };
|
||||||
|
};
|
||||||
|
|
||||||
|
const n = switch (term) {
|
||||||
|
.Exited => "error",
|
||||||
|
.Signal => "signal",
|
||||||
|
.Stopped => "stopped",
|
||||||
|
.Unknown => "unknown",
|
||||||
|
};
|
||||||
|
const v = switch (term) { inline else => |v| v };
|
||||||
|
if (term != .Exited or (reporterr and v != 0)) {
|
||||||
|
stderr.writer().print(
|
||||||
|
"\nCommand returned with {s} code {}.\nPress enter to continue.\n", .{ n, v }
|
||||||
|
) catch {};
|
||||||
|
stdin.reader().skipUntilDelimiterOrEof('\n') catch unreachable;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue