mirror of
https://code.blicky.net/yorhel/ncdu.git
synced 2026-01-13 01:08:41 -09:00
225 lines
6.5 KiB
Zig
225 lines
6.5 KiB
Zig
// SPDX-FileCopyrightText: 2021-2023 Yoran Heling <projects@yorhel.nl>
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
const std = @import("std");
|
|
const main = @import("main.zig");
|
|
const model = @import("model.zig");
|
|
const ui = @import("ui.zig");
|
|
const browser = @import("browser.zig");
|
|
const util = @import("util.zig");
|
|
|
|
var parent: *model.Dir = undefined;
|
|
var entry: *model.Entry = undefined;
|
|
var next_sel: ?*model.Entry = undefined; // Which item to select if deletion succeeds
|
|
var state: enum { confirm, busy, err } = .confirm;
|
|
var confirm: enum { yes, no, ignore } = .no;
|
|
var error_option: enum { abort, ignore, all } = .abort;
|
|
var error_code: anyerror = undefined;
|
|
|
|
pub fn setup(p: *model.Dir, e: *model.Entry, n: ?*model.Entry) void {
|
|
parent = p;
|
|
entry = e;
|
|
next_sel = n;
|
|
state = if (main.config.confirm_delete) .confirm else .busy;
|
|
confirm = .no;
|
|
}
|
|
|
|
|
|
// Returns true to abort scanning.
|
|
fn err(e: anyerror) bool {
|
|
if (main.config.ignore_delete_errors)
|
|
return false;
|
|
error_code = e;
|
|
state = .err;
|
|
|
|
while (main.state == .delete and state == .err)
|
|
main.handleEvent(true, false);
|
|
|
|
return main.state != .delete;
|
|
}
|
|
|
|
fn deleteItem(dir: std.fs.Dir, path: [:0]const u8, ptr: *align(1) ?*model.Entry) bool {
|
|
entry = ptr.*.?;
|
|
main.handleEvent(false, false);
|
|
if (main.state != .delete)
|
|
return true;
|
|
|
|
if (entry.dir()) |d| {
|
|
var fd = dir.openDirZ(path, .{.no_follow = true}, false) catch |e| return err(e);
|
|
var it = &d.sub;
|
|
parent = d;
|
|
defer parent = parent.parent.?;
|
|
while (it.*) |n| {
|
|
if (deleteItem(fd, n.name(), it)) {
|
|
fd.close();
|
|
return true;
|
|
}
|
|
if (it.* == n) // item deletion failed, make sure to still advance to next
|
|
it = &n.next;
|
|
}
|
|
fd.close();
|
|
dir.deleteDirZ(path) catch |e|
|
|
return if (e != error.DirNotEmpty or d.sub == null) err(e) else false;
|
|
} else
|
|
dir.deleteFileZ(path) catch |e| return err(e);
|
|
ptr.*.?.delStats(parent);
|
|
ptr.* = ptr.*.?.next;
|
|
return false;
|
|
}
|
|
|
|
// Returns the item that should be selected in the browser.
|
|
pub fn delete() ?*model.Entry {
|
|
while (main.state == .delete and state == .confirm)
|
|
main.handleEvent(true, false);
|
|
if (main.state != .delete)
|
|
return entry;
|
|
|
|
// Find the pointer to this entry
|
|
const e = entry;
|
|
var it = &parent.sub;
|
|
while (it.*) |n| : (it = &n.next)
|
|
if (it.* == entry)
|
|
break;
|
|
|
|
var path = std.ArrayList(u8).init(main.allocator);
|
|
defer path.deinit();
|
|
parent.fmtPath(true, &path);
|
|
if (path.items.len == 0 or path.items[path.items.len-1] != '/')
|
|
path.append('/') catch unreachable;
|
|
path.appendSlice(entry.name()) catch unreachable;
|
|
|
|
_ = deleteItem(std.fs.cwd(), util.arrayListBufZ(&path), it);
|
|
model.inodes.addAllStats();
|
|
return if (it.* == e) e else next_sel;
|
|
}
|
|
|
|
fn drawConfirm() void {
|
|
browser.draw();
|
|
const box = ui.Box.create(6, 60, "Confirm delete");
|
|
box.move(1, 2);
|
|
ui.addstr("Are you sure you want to delete \"");
|
|
ui.addstr(ui.shorten(ui.toUtf8(entry.name()), 21));
|
|
ui.addch('"');
|
|
if (entry.pack.etype != .dir)
|
|
ui.addch('?')
|
|
else {
|
|
box.move(2, 18);
|
|
ui.addstr("and all of its contents?");
|
|
}
|
|
|
|
box.move(4, 15);
|
|
ui.style(if (confirm == .yes) .sel else .default);
|
|
ui.addstr("yes");
|
|
|
|
box.move(4, 25);
|
|
ui.style(if (confirm == .no) .sel else .default);
|
|
ui.addstr("no");
|
|
|
|
box.move(4, 31);
|
|
ui.style(if (confirm == .ignore) .sel else .default);
|
|
ui.addstr("don't ask me again");
|
|
}
|
|
|
|
fn drawProgress() void {
|
|
var path = std.ArrayList(u8).init(main.allocator);
|
|
defer path.deinit();
|
|
parent.fmtPath(false, &path);
|
|
path.append('/') catch unreachable;
|
|
path.appendSlice(entry.name()) catch unreachable;
|
|
|
|
// TODO: Item counts and progress bar would be nice.
|
|
|
|
const box = ui.Box.create(6, 60, "Deleting...");
|
|
box.move(2, 2);
|
|
ui.addstr(ui.shorten(ui.toUtf8(util.arrayListBufZ(&path)), 56));
|
|
box.move(4, 41);
|
|
ui.addstr("Press ");
|
|
ui.style(.key);
|
|
ui.addch('q');
|
|
ui.style(.default);
|
|
ui.addstr(" to abort");
|
|
}
|
|
|
|
fn drawErr() void {
|
|
var path = std.ArrayList(u8).init(main.allocator);
|
|
defer path.deinit();
|
|
parent.fmtPath(false, &path);
|
|
path.append('/') catch unreachable;
|
|
path.appendSlice(entry.name()) catch unreachable;
|
|
|
|
const box = ui.Box.create(6, 60, "Error");
|
|
box.move(1, 2);
|
|
ui.addstr("Error deleting ");
|
|
ui.addstr(ui.shorten(ui.toUtf8(util.arrayListBufZ(&path)), 41));
|
|
box.move(2, 4);
|
|
ui.addstr(ui.errorString(error_code));
|
|
|
|
box.move(4, 14);
|
|
ui.style(if (error_option == .abort) .sel else .default);
|
|
ui.addstr("abort");
|
|
|
|
box.move(4, 23);
|
|
ui.style(if (error_option == .ignore) .sel else .default);
|
|
ui.addstr("ignore");
|
|
|
|
box.move(4, 33);
|
|
ui.style(if (error_option == .all) .sel else .default);
|
|
ui.addstr("ignore all");
|
|
}
|
|
|
|
pub fn draw() void {
|
|
switch (state) {
|
|
.confirm => drawConfirm(),
|
|
.busy => drawProgress(),
|
|
.err => drawErr(),
|
|
}
|
|
}
|
|
|
|
pub fn keyInput(ch: i32) void {
|
|
switch (state) {
|
|
.confirm => switch (ch) {
|
|
'h', ui.c.KEY_LEFT => confirm = switch (confirm) {
|
|
.ignore => .no,
|
|
else => .yes,
|
|
},
|
|
'l', ui.c.KEY_RIGHT => confirm = switch (confirm) {
|
|
.yes => .no,
|
|
else => .ignore,
|
|
},
|
|
'q' => main.state = .browse,
|
|
'\n' => switch (confirm) {
|
|
.yes => state = .busy,
|
|
.no => main.state = .browse,
|
|
.ignore => {
|
|
main.config.confirm_delete = false;
|
|
state = .busy;
|
|
},
|
|
},
|
|
else => {}
|
|
},
|
|
.busy => {
|
|
if (ch == 'q')
|
|
main.state = .browse;
|
|
},
|
|
.err => switch (ch) {
|
|
'h', ui.c.KEY_LEFT => error_option = switch (error_option) {
|
|
.all => .ignore,
|
|
else => .abort,
|
|
},
|
|
'l', ui.c.KEY_RIGHT => error_option = switch (error_option) {
|
|
.abort => .ignore,
|
|
else => .all,
|
|
},
|
|
'q' => main.state = .browse,
|
|
'\n' => switch (error_option) {
|
|
.abort => main.state = .browse,
|
|
.ignore => state = .busy,
|
|
.all => {
|
|
main.config.ignore_delete_errors = true;
|
|
state = .busy;
|
|
},
|
|
},
|
|
else => {}
|
|
},
|
|
}
|
|
}
|