mirror of
https://code.blicky.net/yorhel/ncdu.git
synced 2026-01-15 02:08:40 -09:00
Implement file deletion + a bunch of bug fixes
This commit is contained in:
parent
448fa9e7a6
commit
3a21dea2cd
6 changed files with 276 additions and 33 deletions
|
|
@ -29,7 +29,6 @@ backported to the C version, depending on how viable a proper Zig release is.
|
||||||
Missing features:
|
Missing features:
|
||||||
|
|
||||||
- Help window
|
- Help window
|
||||||
- File deletion
|
|
||||||
|
|
||||||
### Improvements compared to the C version
|
### Improvements compared to the C version
|
||||||
|
|
||||||
|
|
@ -76,11 +75,9 @@ Aside from this implementation being unfinished:
|
||||||
Not sure if these count as improvements or regressions, so I'll just list these
|
Not sure if these count as improvements or regressions, so I'll just list these
|
||||||
separately:
|
separately:
|
||||||
|
|
||||||
- The browsing UI is not visible during refresh.
|
- The browsing UI is not visible during refresh or file deletion.
|
||||||
- Some columns in the file browser are hidden automatically if the terminal is
|
- Some columns in the file browser are hidden automatically if the terminal is
|
||||||
not wide enough to display them.
|
not wide enough to display them.
|
||||||
- Browsing keys other than changing the currently selected item don't work
|
|
||||||
anymore while the info window is being displayed.
|
|
||||||
- The file's path is not displayed in the item window anymore (it's redundant).
|
- The file's path is not displayed in the item window anymore (it's redundant).
|
||||||
- The item window's height is dynamic based on its contents.
|
- The item window's height is dynamic based on its contents.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ const std = @import("std");
|
||||||
const main = @import("main.zig");
|
const main = @import("main.zig");
|
||||||
const model = @import("model.zig");
|
const model = @import("model.zig");
|
||||||
const scan = @import("scan.zig");
|
const scan = @import("scan.zig");
|
||||||
|
const delete = @import("delete.zig");
|
||||||
const ui = @import("ui.zig");
|
const ui = @import("ui.zig");
|
||||||
const c = @cImport(@cInclude("time.h"));
|
const c = @cImport(@cInclude("time.h"));
|
||||||
usingnamespace @import("util.zig");
|
usingnamespace @import("util.zig");
|
||||||
|
|
@ -44,12 +45,12 @@ const View = struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Should be called after dir_parents or dir_items has changed, will load the last saved view and find the proper cursor_idx.
|
// Should be called after dir_parents or dir_items has changed, will load the last saved view and find the proper cursor_idx.
|
||||||
fn load(self: *@This()) void {
|
fn load(self: *@This(), sel: ?*const model.Entry) void {
|
||||||
if (opened_dir_views.get(@ptrToInt(dir_parents.top()))) |v| self.* = v
|
if (opened_dir_views.get(@ptrToInt(dir_parents.top()))) |v| self.* = v
|
||||||
else self.* = @This(){};
|
else self.* = @This(){};
|
||||||
cursor_idx = 0;
|
cursor_idx = 0;
|
||||||
for (dir_items.items) |e, i| {
|
for (dir_items.items) |e, i| {
|
||||||
if (self.cursor_hash == hashEntry(e)) {
|
if (if (sel != null) e == sel else self.cursor_hash == hashEntry(e)) {
|
||||||
cursor_idx = i;
|
cursor_idx = i;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
@ -110,19 +111,19 @@ fn sortLt(_: void, ap: ?*model.Entry, bp: ?*model.Entry) bool {
|
||||||
// - config.sort_* changes
|
// - config.sort_* changes
|
||||||
// - dir_items changes (i.e. from loadDir())
|
// - dir_items changes (i.e. from loadDir())
|
||||||
// - files in this dir have changed in a way that affects their ordering
|
// - files in this dir have changed in a way that affects their ordering
|
||||||
fn sortDir() void {
|
fn sortDir(next_sel: ?*const model.Entry) void {
|
||||||
// No need to sort the first item if that's the parent dir reference,
|
// No need to sort the first item if that's the parent dir reference,
|
||||||
// excluding that allows sortLt() to ignore null values.
|
// excluding that allows sortLt() to ignore null values.
|
||||||
const lst = dir_items.items[(if (dir_items.items.len > 0 and dir_items.items[0] == null) @as(usize, 1) else 0)..];
|
const lst = dir_items.items[(if (dir_items.items.len > 0 and dir_items.items[0] == null) @as(usize, 1) else 0)..];
|
||||||
std.sort.sort(?*model.Entry, lst, @as(void, undefined), sortLt);
|
std.sort.sort(?*model.Entry, lst, @as(void, undefined), sortLt);
|
||||||
current_view.load();
|
current_view.load(next_sel);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Must be called when:
|
// Must be called when:
|
||||||
// - dir_parents changes (i.e. we change directory)
|
// - dir_parents changes (i.e. we change directory)
|
||||||
// - config.show_hidden changes
|
// - config.show_hidden changes
|
||||||
// - files in this dir have been added or removed
|
// - files in this dir have been added or removed
|
||||||
pub fn loadDir() void {
|
pub fn loadDir(next_sel: ?*const model.Entry) void {
|
||||||
dir_items.shrinkRetainingCapacity(0);
|
dir_items.shrinkRetainingCapacity(0);
|
||||||
dir_max_size = 1;
|
dir_max_size = 1;
|
||||||
dir_max_blocks = 1;
|
dir_max_blocks = 1;
|
||||||
|
|
@ -145,7 +146,7 @@ pub fn loadDir() void {
|
||||||
}
|
}
|
||||||
it = e.next;
|
it = e.next;
|
||||||
}
|
}
|
||||||
sortDir();
|
sortDir(next_sel);
|
||||||
}
|
}
|
||||||
|
|
||||||
const Row = struct {
|
const Row = struct {
|
||||||
|
|
@ -531,18 +532,10 @@ const info = struct {
|
||||||
if (keyInputSelection(ch, &links_idx, links.?.paths.items.len, 5))
|
if (keyInputSelection(ch, &links_idx, links.?.paths.items.len, 5))
|
||||||
return true;
|
return true;
|
||||||
if (ch == 10) { // Enter - go to selected entry
|
if (ch == 10) { // Enter - go to selected entry
|
||||||
// XXX: This jump can be a little bit jarring as, usually,
|
|
||||||
// browsing to parent directory will cause the previously
|
|
||||||
// opened dir to be selected. This jump doesn't update the View
|
|
||||||
// state of parent dirs, so that won't be the case anymore.
|
|
||||||
const p = links.?.paths.items[links_idx];
|
const p = links.?.paths.items[links_idx];
|
||||||
dir_parents.stack.shrinkRetainingCapacity(0);
|
dir_parents.stack.shrinkRetainingCapacity(0);
|
||||||
dir_parents.stack.appendSlice(p.path.stack.items) catch unreachable;
|
dir_parents.stack.appendSlice(p.path.stack.items) catch unreachable;
|
||||||
loadDir();
|
loadDir(&p.node.entry);
|
||||||
for (dir_items.items) |e, i| {
|
|
||||||
if (e == &p.node.entry)
|
|
||||||
cursor_idx = i;
|
|
||||||
}
|
|
||||||
set(null, .info);
|
set(null, .info);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -630,7 +623,7 @@ pub fn draw() void {
|
||||||
const box = ui.Box.create(6, 60, "Message");
|
const box = ui.Box.create(6, 60, "Message");
|
||||||
box.move(2, 2);
|
box.move(2, 2);
|
||||||
ui.addstr(m);
|
ui.addstr(m);
|
||||||
box.move(4, 34);
|
box.move(4, 33);
|
||||||
ui.addstr("Press any key to continue");
|
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);
|
||||||
|
|
@ -641,7 +634,7 @@ fn sortToggle(col: main.config.SortCol, default_order: main.config.SortOrder) vo
|
||||||
else if (main.config.sort_order == .asc) main.config.sort_order = .desc
|
else if (main.config.sort_order == .asc) main.config.sort_order = .desc
|
||||||
else main.config.sort_order = .asc;
|
else main.config.sort_order = .asc;
|
||||||
main.config.sort_col = col;
|
main.config.sort_col = col;
|
||||||
sortDir();
|
sortDir(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn keyInputSelection(ch: i32, idx: *usize, len: usize, page: u32) bool {
|
fn keyInputSelection(ch: i32, idx: *usize, len: usize, page: u32) bool {
|
||||||
|
|
@ -677,7 +670,7 @@ pub fn keyInput(ch: i32) void {
|
||||||
|
|
||||||
switch (ch) {
|
switch (ch) {
|
||||||
'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' => if (dir_items.items.len > 0) info.set(dir_items.items[cursor_idx], .info),
|
||||||
'r' => {
|
'r' => {
|
||||||
if (main.config.imported)
|
if (main.config.imported)
|
||||||
message = "Directory imported from file, refreshing is disabled."
|
message = "Directory imported from file, refreshing is disabled."
|
||||||
|
|
@ -694,6 +687,21 @@ pub fn keyInput(ch: i32) void {
|
||||||
else
|
else
|
||||||
main.state = .shell;
|
main.state = .shell;
|
||||||
},
|
},
|
||||||
|
'd' => {
|
||||||
|
if (dir_items.items.len == 0) {
|
||||||
|
} else if (main.config.imported)
|
||||||
|
message = "Deletion feature not available for imported directories."
|
||||||
|
else if (main.config.read_only)
|
||||||
|
message = "Deletion feature disabled in read-only mode."
|
||||||
|
else if (dir_items.items[cursor_idx]) |e| {
|
||||||
|
main.state = .delete;
|
||||||
|
const next =
|
||||||
|
if (cursor_idx+1 < dir_items.items.len) dir_items.items[cursor_idx+1]
|
||||||
|
else if (cursor_idx == 0) null
|
||||||
|
else dir_items.items[cursor_idx-1];
|
||||||
|
delete.setup(dir_parents.copy(), e, next);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
// Sort & filter settings
|
// Sort & filter settings
|
||||||
'n' => sortToggle(.name, .asc),
|
'n' => sortToggle(.name, .asc),
|
||||||
|
|
@ -702,22 +710,22 @@ pub fn keyInput(ch: i32) void {
|
||||||
'M' => if (main.config.extended) sortToggle(.mtime, .desc),
|
'M' => if (main.config.extended) sortToggle(.mtime, .desc),
|
||||||
'e' => {
|
'e' => {
|
||||||
main.config.show_hidden = !main.config.show_hidden;
|
main.config.show_hidden = !main.config.show_hidden;
|
||||||
loadDir();
|
loadDir(null);
|
||||||
state = .main;
|
state = .main;
|
||||||
},
|
},
|
||||||
't' => {
|
't' => {
|
||||||
main.config.sort_dirsfirst = !main.config.sort_dirsfirst;
|
main.config.sort_dirsfirst = !main.config.sort_dirsfirst;
|
||||||
sortDir();
|
sortDir(null);
|
||||||
},
|
},
|
||||||
'a' => {
|
'a' => {
|
||||||
main.config.show_blocks = !main.config.show_blocks;
|
main.config.show_blocks = !main.config.show_blocks;
|
||||||
if (main.config.show_blocks and main.config.sort_col == .size) {
|
if (main.config.show_blocks and main.config.sort_col == .size) {
|
||||||
main.config.sort_col = .blocks;
|
main.config.sort_col = .blocks;
|
||||||
sortDir();
|
sortDir(null);
|
||||||
}
|
}
|
||||||
if (!main.config.show_blocks and main.config.sort_col == .blocks) {
|
if (!main.config.show_blocks and main.config.sort_col == .blocks) {
|
||||||
main.config.sort_col = .size;
|
main.config.sort_col = .size;
|
||||||
sortDir();
|
sortDir(null);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
@ -727,19 +735,20 @@ pub fn keyInput(ch: i32) void {
|
||||||
} else if (dir_items.items[cursor_idx]) |e| {
|
} else if (dir_items.items[cursor_idx]) |e| {
|
||||||
if (e.dir()) |d| {
|
if (e.dir()) |d| {
|
||||||
dir_parents.push(d);
|
dir_parents.push(d);
|
||||||
loadDir();
|
loadDir(null);
|
||||||
state = .main;
|
state = .main;
|
||||||
}
|
}
|
||||||
} else if (!dir_parents.isRoot()) {
|
} else if (!dir_parents.isRoot()) {
|
||||||
dir_parents.pop();
|
dir_parents.pop();
|
||||||
loadDir();
|
loadDir(null);
|
||||||
state = .main;
|
state = .main;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
'h', '<', ui.c.KEY_BACKSPACE, ui.c.KEY_LEFT => {
|
'h', '<', ui.c.KEY_BACKSPACE, ui.c.KEY_LEFT => {
|
||||||
if (!dir_parents.isRoot()) {
|
if (!dir_parents.isRoot()) {
|
||||||
|
const e = dir_parents.top();
|
||||||
dir_parents.pop();
|
dir_parents.pop();
|
||||||
loadDir();
|
loadDir(&e.entry);
|
||||||
state = .main;
|
state = .main;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
||||||
224
src/delete.zig
Normal file
224
src/delete.zig
Normal file
|
|
@ -0,0 +1,224 @@
|
||||||
|
const std = @import("std");
|
||||||
|
const main = @import("main.zig");
|
||||||
|
const model = @import("model.zig");
|
||||||
|
const ui = @import("ui.zig");
|
||||||
|
const browser = @import("browser.zig");
|
||||||
|
usingnamespace @import("util.zig");
|
||||||
|
|
||||||
|
var parents: model.Parents = .{};
|
||||||
|
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;
|
||||||
|
|
||||||
|
// ownership of p is passed to this function
|
||||||
|
pub fn setup(p: model.Parents, e: *model.Entry, n: ?*model.Entry) void {
|
||||||
|
parents = 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, .{ .access_sub_paths = true, .iterate = false })
|
||||||
|
catch |e| return err(e);
|
||||||
|
var it = &d.sub;
|
||||||
|
parents.push(d);
|
||||||
|
defer parents.pop();
|
||||||
|
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(&parents);
|
||||||
|
ptr.* = ptr.*.?.next;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns the item that should be selected in the browser.
|
||||||
|
pub fn delete() ?*model.Entry {
|
||||||
|
defer parents.deinit();
|
||||||
|
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 = &parents.top().sub;
|
||||||
|
while (it.*) |n| : (it = &n.next)
|
||||||
|
if (it.* == entry)
|
||||||
|
break;
|
||||||
|
|
||||||
|
var path = std.ArrayList(u8).init(main.allocator);
|
||||||
|
defer path.deinit();
|
||||||
|
parents.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(), arrayListBufZ(&path), it);
|
||||||
|
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.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();
|
||||||
|
parents.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(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();
|
||||||
|
parents.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(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 => {}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
16
src/main.zig
16
src/main.zig
|
|
@ -5,6 +5,7 @@ const model = @import("model.zig");
|
||||||
const scan = @import("scan.zig");
|
const scan = @import("scan.zig");
|
||||||
const ui = @import("ui.zig");
|
const ui = @import("ui.zig");
|
||||||
const browser = @import("browser.zig");
|
const browser = @import("browser.zig");
|
||||||
|
const delete = @import("delete.zig");
|
||||||
const c = @cImport(@cInclude("locale.h"));
|
const c = @cImport(@cInclude("locale.h"));
|
||||||
|
|
||||||
// "Custom" allocator that wraps the libc allocator and calls ui.oom() on error.
|
// "Custom" allocator that wraps the libc allocator and calls ui.oom() on error.
|
||||||
|
|
@ -65,9 +66,11 @@ pub const config = struct {
|
||||||
pub var imported: bool = false;
|
pub var imported: bool = false;
|
||||||
pub var can_shell: bool = true;
|
pub var can_shell: bool = true;
|
||||||
pub var confirm_quit: bool = false;
|
pub var confirm_quit: bool = false;
|
||||||
|
pub var confirm_delete: bool = true;
|
||||||
|
pub var ignore_delete_errors: bool = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
pub var state: enum { scan, browse, refresh, shell } = .scan;
|
pub var state: enum { scan, browse, refresh, shell, delete } = .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.:
|
||||||
|
|
@ -332,19 +335,24 @@ pub fn main() void {
|
||||||
config.scan_ui = .full; // in case we're refreshing from the UI, always in full mode.
|
config.scan_ui = .full; // in case we're refreshing from the UI, always in full mode.
|
||||||
ui.init();
|
ui.init();
|
||||||
state = .browse;
|
state = .browse;
|
||||||
browser.loadDir();
|
browser.loadDir(null);
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
switch (state) {
|
switch (state) {
|
||||||
.refresh => {
|
.refresh => {
|
||||||
scan.scan();
|
scan.scan();
|
||||||
state = .browse;
|
state = .browse;
|
||||||
browser.loadDir();
|
browser.loadDir(null);
|
||||||
},
|
},
|
||||||
.shell => {
|
.shell => {
|
||||||
spawnShell();
|
spawnShell();
|
||||||
state = .browse;
|
state = .browse;
|
||||||
},
|
},
|
||||||
|
.delete => {
|
||||||
|
const next = delete.delete();
|
||||||
|
state = .browse;
|
||||||
|
browser.loadDir(next);
|
||||||
|
},
|
||||||
else => handleEvent(true, false)
|
else => handleEvent(true, false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -360,6 +368,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(),
|
||||||
|
.delete => delete.draw(),
|
||||||
.shell => unreachable,
|
.shell => unreachable,
|
||||||
}
|
}
|
||||||
if (ui.inited) _ = ui.c.refresh();
|
if (ui.inited) _ = ui.c.refresh();
|
||||||
|
|
@ -378,6 +387,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),
|
||||||
|
.delete => delete.keyInput(ch),
|
||||||
.shell => unreachable,
|
.shell => unreachable,
|
||||||
}
|
}
|
||||||
firstblock = false;
|
firstblock = false;
|
||||||
|
|
|
||||||
|
|
@ -384,6 +384,7 @@ const Context = struct {
|
||||||
else if (self.wr) |wr|
|
else if (self.wr) |wr|
|
||||||
self.writeSpecial(wr.writer(), t) catch |e| writeErr(e);
|
self.writeSpecial(wr.writer(), t) catch |e| writeErr(e);
|
||||||
|
|
||||||
|
self.stat.dir = false; // So that popPath() doesn't consider this as leaving a dir.
|
||||||
self.items_seen += 1;
|
self.items_seen += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -53,11 +53,12 @@ pub fn oom() void {
|
||||||
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.AccessDenied => "Access denied",
|
||||||
|
error.DirNotEmpty => "Directory not empty",
|
||||||
error.DiskQuota => "Disk quota exceeded",
|
error.DiskQuota => "Disk quota exceeded",
|
||||||
|
error.FileBusy => "File is busy",
|
||||||
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.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.InvalidExe => "Invalid executable",
|
||||||
error.IsDir => "Is a directory",
|
error.IsDir => "Is a directory",
|
||||||
|
|
@ -66,6 +67,7 @@ pub fn errorString(e: anyerror) [:0]const u8 {
|
||||||
error.NotDir => "Not a directory",
|
error.NotDir => "Not a directory",
|
||||||
error.OutOfMemory, error.SystemResources => "Out of memory",
|
error.OutOfMemory, error.SystemResources => "Out of memory",
|
||||||
error.ProcessFdQuotaExceeded => "Process file descriptor limit exceeded",
|
error.ProcessFdQuotaExceeded => "Process file descriptor limit exceeded",
|
||||||
|
error.ReadOnlyFilesystem => "Read-only filesystem",
|
||||||
error.SymlinkLoop => "Symlink loop",
|
error.SymlinkLoop => "Symlink loop",
|
||||||
error.SystemFdQuotaExceeded => "System file descriptor limit exceeded",
|
error.SystemFdQuotaExceeded => "System file descriptor limit exceeded",
|
||||||
else => "Unknown error", // rather useless :(
|
else => "Unknown error", // rather useless :(
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue