+1
-1
PROJECT_BOARD.md
+1
-1
PROJECT_BOARD.md
+4
README.md
+4
README.md
···
56
56
"Command mode" section for available commands. Will enter
57
57
input mode.
58
58
v :Verbose mode. Provides more information about selected entry.
59
+
y :Yank selected item.
60
+
p :Past yanked item.
59
61
60
62
Input mode:
61
63
<Esc> :Cancel input.
···
114
116
.toggle_verbose_file_information: ?Char = 'v',
115
117
.force_delete: ?Char = null -- Files deleted this way are
116
118
not recoverable
119
+
.yank: ?Char = 'y'
120
+
.paste: ?Char = 'p'
117
121
}
118
122
119
123
NotificationStyles = struct {
+1
-1
build.zig
+1
-1
build.zig
···
2
2
const builtin = @import("builtin");
3
3
4
4
///Must match the `version` in `build.zig.zon`.
5
-
const version = std.SemanticVersion{ .major = 0, .minor = 9, .patch = 6 };
5
+
const version = std.SemanticVersion{ .major = 0, .minor = 9, .patch = 7 };
6
6
7
7
const targets: []const std.Target.Query = &.{
8
8
.{ .cpu_arch = .aarch64, .os_tag = .macos },
+1
-1
build.zig.zon
+1
-1
build.zig.zon
+10
src/app.zig
+10
src/app.zig
···
38
38
" \"Command mode\" section for available commands. Will enter ",
39
39
" input mode.",
40
40
"v :Verbose mode. Provides more information about selected entry. ",
41
+
"y :Yank selected item.",
42
+
"p :Past yanked item.",
41
43
"",
42
44
"Input mode:",
43
45
"<Esc> :Cancel input.",
···
74
76
pub const Action = union(enum) {
75
77
delete: ActionPaths,
76
78
rename: ActionPaths,
79
+
paste: []const u8,
77
80
};
78
81
79
82
pub const Event = union(enum) {
···
103
106
text_input: vaxis.widgets.TextInput,
104
107
text_input_buf: [std.fs.max_path_bytes]u8 = undefined,
105
108
109
+
yanked: ?struct { dir: []const u8, entry: std.fs.Dir.Entry } = null,
106
110
image: ?vaxis.Image = null,
107
111
last_known_height: usize,
108
112
···
140
144
self.alloc.free(a.new);
141
145
self.alloc.free(a.old);
142
146
},
147
+
.paste => |a| self.alloc.free(a),
143
148
}
149
+
}
150
+
151
+
if (self.yanked) |yanked| {
152
+
self.alloc.free(yanked.dir);
153
+
self.alloc.free(yanked.entry.name);
144
154
}
145
155
146
156
self.command_history.resetSelected();
+2
src/config.zig
+2
src/config.zig
+25
src/environment.zig
+25
src/environment.zig
···
1
1
const std = @import("std");
2
+
const zuid = @import("zuid");
2
3
const builtin = @import("builtin");
3
4
4
5
pub fn getHomeDir() !?std.fs.Dir {
···
21
22
}
22
23
}
23
24
return null;
25
+
}
26
+
27
+
pub fn checkDuplicatePath(
28
+
buf: []u8,
29
+
dir: std.fs.Dir,
30
+
relative_path: []const u8,
31
+
) std.fmt.BufPrintError!struct {
32
+
path: []const u8,
33
+
had_duplicate: bool,
34
+
} {
35
+
var had_duplicate = false;
36
+
const new_path = if (fileExists(dir, relative_path)) lbl: {
37
+
had_duplicate = true;
38
+
const extension = std.fs.path.extension(relative_path);
39
+
break :lbl try std.fmt.bufPrint(
40
+
buf,
41
+
"{s}-{s}{s}",
42
+
.{ relative_path[0 .. relative_path.len - extension.len], zuid.new.v4(), extension },
43
+
);
44
+
} else lbl: {
45
+
break :lbl try std.fmt.bufPrint(buf, "{s}", .{relative_path});
46
+
};
47
+
48
+
return .{ .path = new_path, .had_duplicate = had_duplicate };
24
49
}
25
50
26
51
pub fn openFile(
+157
-20
src/event_handlers.zig
+157
-20
src/event_handlers.zig
···
192
192
};
193
193
app.directories.removeSelected();
194
194
},
195
+
.yank => {
196
+
if (app.yanked) |yanked| {
197
+
app.alloc.free(yanked.dir);
198
+
app.alloc.free(yanked.entry.name);
199
+
}
200
+
201
+
app.yanked = lbl: {
202
+
const entry = (app.directories.getSelected() catch {
203
+
app.notification.write("Failed to yank item - no item selected.", .warn) catch {};
204
+
break :lbl null;
205
+
}) orelse break :lbl null;
206
+
207
+
switch (entry.kind) {
208
+
.file => {
209
+
break :lbl .{
210
+
.dir = try app.alloc.dupe(u8, app.directories.fullPath(".") catch {
211
+
const message = try std.fmt.allocPrint(
212
+
app.alloc,
213
+
"Failed to yank '{s}' - unable to retrieve directory path.",
214
+
.{entry.name},
215
+
);
216
+
defer app.alloc.free(message);
217
+
app.notification.write(message, .err) catch {};
218
+
break :lbl null;
219
+
}),
220
+
.entry = .{
221
+
.kind = entry.kind,
222
+
.name = try app.alloc.dupe(u8, entry.name),
223
+
},
224
+
};
225
+
},
226
+
else => {
227
+
const message = try std.fmt.allocPrint(
228
+
app.alloc,
229
+
"Failed to yank '{s}' - unsupported file type '{s}'.",
230
+
.{ entry.name, @tagName(entry.kind) },
231
+
);
232
+
defer app.alloc.free(message);
233
+
app.notification.write(message, .warn) catch {};
234
+
break :lbl null;
235
+
},
236
+
}
237
+
};
238
+
if (app.yanked) |y| {
239
+
const message = try std.fmt.allocPrint(app.alloc, "Yanked '{s}'.", .{y.entry.name});
240
+
defer app.alloc.free(message);
241
+
app.notification.write(message, .info) catch {};
242
+
}
243
+
},
244
+
.paste => {
245
+
const yanked = if (app.yanked) |y| y else return;
246
+
247
+
var new_path_buf: [std.fs.max_path_bytes]u8 = undefined;
248
+
const new_path_res = environment.checkDuplicatePath(&new_path_buf, app.directories.dir, yanked.entry.name) catch {
249
+
const message = try std.fmt.allocPrint(app.alloc, "Failed to copy '{s}' - path too long.", .{yanked.entry.name});
250
+
defer app.alloc.free(message);
251
+
app.notification.write(message, .err) catch {};
252
+
return;
253
+
};
254
+
255
+
switch (yanked.entry.kind) {
256
+
.file => {
257
+
var source_dir = std.fs.openDirAbsolute(yanked.dir, .{ .iterate = true }) catch {
258
+
const message = try std.fmt.allocPrint(app.alloc, "Failed to copy '{s}' - unable to open directory '{s}'.", .{ yanked.entry.name, yanked.dir });
259
+
defer app.alloc.free(message);
260
+
app.notification.write(message, .err) catch {};
261
+
return;
262
+
};
263
+
defer source_dir.close();
264
+
std.fs.Dir.copyFile(
265
+
source_dir,
266
+
yanked.entry.name,
267
+
app.directories.dir,
268
+
new_path_res.path,
269
+
.{},
270
+
) catch |err| switch (err) {
271
+
error.FileNotFound => {
272
+
const message = try std.fmt.allocPrint(app.alloc, "Failed to copy '{s}' - the original file was deleted or moved.", .{yanked.entry.name});
273
+
defer app.alloc.free(message);
274
+
app.notification.write(message, .err) catch {};
275
+
return;
276
+
},
277
+
else => {
278
+
const message = try std.fmt.allocPrint(app.alloc, "Failed to copy '{s}' - {}.", .{ yanked.entry.name, err });
279
+
defer app.alloc.free(message);
280
+
app.notification.write(message, .err) catch {};
281
+
return;
282
+
},
283
+
};
284
+
285
+
// Append action to undo history.
286
+
{
287
+
var new_path_abs_buf: [std.fs.max_path_bytes]u8 = undefined;
288
+
const new_path_abs = app.directories.dir.realpath(new_path_res.path, &new_path_abs_buf) catch {
289
+
const message = try std.fmt.allocPrint(
290
+
app.alloc,
291
+
"Failed to push copy action for '{s}' to undo history - unable to retrieve absolute directory path for '{s}'. This action will not be able to be undone via the `undo` keybind.",
292
+
.{ new_path_res.path, yanked.entry.name },
293
+
);
294
+
defer app.alloc.free(message);
295
+
app.notification.write(message, .err) catch {};
296
+
return;
297
+
};
298
+
299
+
if (app.actions.push(.{
300
+
.paste = try app.alloc.dupe(u8, new_path_abs),
301
+
})) |prev_elem| {
302
+
app.alloc.free(prev_elem.delete.prev_path);
303
+
app.alloc.free(prev_elem.delete.new_path);
304
+
}
305
+
}
306
+
307
+
const message = try std.fmt.allocPrint(app.alloc, "Copied '{s}'.", .{yanked.entry.name});
308
+
defer app.alloc.free(message);
309
+
app.notification.write(message, .info) catch {};
310
+
},
311
+
else => {
312
+
const message = try std.fmt.allocPrint(
313
+
app.alloc,
314
+
"Failed to copy '{s}' - unsupported file type '{s}'.",
315
+
.{ yanked.entry.name, @tagName(yanked.entry.kind) },
316
+
);
317
+
defer app.alloc.free(message);
318
+
app.notification.write(message, .warn) catch {};
319
+
},
320
+
}
321
+
322
+
app.directories.clearEntries();
323
+
app.directories.populateEntries("") catch |err| {
324
+
switch (err) {
325
+
error.AccessDenied => app.notification.writeErr(.PermissionDenied) catch {},
326
+
else => app.notification.writeErr(.UnknownError) catch {},
327
+
}
328
+
};
329
+
},
195
330
}
196
331
} else {
197
332
switch (key.codepoint) {
···
287
422
288
423
switch (action) {
289
424
.delete => |a| {
290
-
defer app.alloc.free(a.new);
291
-
defer app.alloc.free(a.old);
425
+
defer app.alloc.free(a.new_path);
426
+
defer app.alloc.free(a.prev_path);
292
427
293
-
var had_duplicate = false;
294
-
295
-
// Handle if item with same name already exists.
296
428
var new_path_buf: [std.fs.max_path_bytes]u8 = undefined;
297
-
const new_path = if (environment.fileExists(app.directories.dir, a.old)) lbl: {
298
-
const extension = std.fs.path.extension(a.old);
299
-
had_duplicate = true;
300
-
break :lbl try std.fmt.bufPrint(
301
-
&new_path_buf,
302
-
"{s}-{s}{s}",
303
-
.{ a.old[0 .. a.old.len - extension.len], zuid.new.v4(), extension },
304
-
);
305
-
} else lbl: {
306
-
break :lbl a.old;
307
-
};
429
+
const new_path_res = try environment.checkDuplicatePath(&new_path_buf, app.directories.dir, a.prev_path);
308
430
309
-
if (app.directories.dir.rename(a.new, new_path)) {
431
+
if (app.directories.dir.rename(a.new_path, new_path_res.path)) {
310
432
app.directories.clearEntries();
311
-
const fuzzy = inputToSlice(app);
312
-
app.directories.populateEntries(fuzzy) catch |err| {
433
+
app.directories.populateEntries("") catch |err| {
313
434
switch (err) {
314
435
error.AccessDenied => try app.notification.writeErr(.PermissionDenied),
315
436
else => try app.notification.writeErr(.UnknownError),
316
437
}
317
438
};
318
-
if (had_duplicate) {
439
+
if (new_path_res.had_duplicate) {
319
440
try app.notification.writeWarn(.DuplicateFileOnUndo);
320
441
} else {
321
442
try app.notification.writeInfo(.RestoredDelete);
···
361
482
} else |_| {
362
483
try app.notification.writeErr(.UnableToUndo);
363
484
}
485
+
},
486
+
.paste => |path| {
487
+
defer app.alloc.free(path);
488
+
489
+
app.directories.dir.deleteTree(path) catch {
490
+
try app.notification.writeErr(.UnableToUndo);
491
+
return;
492
+
};
493
+
494
+
app.directories.clearEntries();
495
+
app.directories.populateEntries("") catch |err| {
496
+
switch (err) {
497
+
error.AccessDenied => try app.notification.writeErr(.PermissionDenied),
498
+
else => try app.notification.writeErr(.UnknownError),
499
+
}
500
+
};
364
501
},
365
502
}
366
503