地圖 (Jido) is a lightweight Unix TUI file explorer designed for speed and simplicity.
1const std = @import("std");
2
3const vaxis = @import("vaxis");
4const zuid = @import("zuid");
5
6const App = @import("./app.zig");
7const Archive = @import("./archive.zig");
8const environment = @import("./environment.zig");
9const path_utils = @import("./path_utils.zig");
10const Preview = @import("./preview.zig");
11
12const config = &@import("./config.zig").config;
13
14pub fn delete(app: *App) error{OutOfMemory}!void {
15 var message: ?[]const u8 = null;
16 defer if (message) |msg| app.alloc.free(msg);
17
18 const entry = (app.directories.getSelected() catch {
19 app.notification.write("Can not to delete item - no item selected.", .warn) catch {};
20 return;
21 }) orelse return;
22 const clean_name = path_utils.getCleanName(entry);
23
24 var prev_path_buf: [std.fs.max_path_bytes]u8 = undefined;
25 const prev_path = app.directories.dir.realpath(clean_name, &prev_path_buf) catch {
26 message = try std.fmt.allocPrint(app.alloc, "Failed to delete '{s}' - unable to retrieve absolute path.", .{entry.name});
27 app.notification.write(message.?, .err) catch {};
28 if (app.file_logger) |file_logger| file_logger.write(message.?, .err) catch {};
29 return;
30 };
31 const prev_path_alloc = try app.alloc.dupe(u8, prev_path);
32
33 var trash_dir = dir: {
34 notfound: {
35 break :dir (config.trashDir() catch break :notfound) orelse break :notfound;
36 }
37 app.alloc.free(prev_path_alloc);
38 message = try std.fmt.allocPrint(app.alloc, "Failed to delete '{s}' - unable to retrieve trash directory.", .{entry.name});
39 app.notification.write(message.?, .err) catch {};
40 if (app.file_logger) |file_logger| file_logger.write(message.?, .err) catch {};
41 return;
42 };
43 defer trash_dir.close();
44
45 var trash_dir_path_buf: [std.fs.max_path_bytes]u8 = undefined;
46 const trash_dir_path = trash_dir.realpath(".", &trash_dir_path_buf) catch {
47 message = try std.fmt.allocPrint(app.alloc, "Failed to delete '{s}' - unable to retrieve absolute path for trash directory.", .{entry.name});
48 app.notification.write(message.?, .err) catch {};
49 if (app.file_logger) |file_logger| file_logger.write(message.?, .err) catch {};
50 return;
51 };
52
53 if (std.mem.eql(u8, prev_path_alloc, trash_dir_path)) {
54 app.notification.write("Can not delete trash directory.", .warn) catch {};
55 app.alloc.free(prev_path_alloc);
56 return;
57 }
58
59 const tmp_path = try std.fmt.allocPrint(app.alloc, "{s}/{s}-{f}", .{ trash_dir_path, clean_name, zuid.new.v4() });
60 if (app.directories.dir.rename(clean_name, tmp_path)) {
61 if (app.actions.push(.{
62 .delete = .{ .prev_path = prev_path_alloc, .new_path = tmp_path },
63 })) |prev_elem| {
64 app.alloc.free(prev_elem.delete.prev_path);
65 app.alloc.free(prev_elem.delete.new_path);
66 }
67 message = try std.fmt.allocPrint(app.alloc, "Deleted '{s}'.", .{entry.name});
68 app.notification.write(message.?, .info) catch {};
69
70 app.directories.removeSelected();
71 Preview.loadPreviewForCurrentEntry(app) catch {};
72 } else |err| {
73 app.alloc.free(prev_path_alloc);
74 app.alloc.free(tmp_path);
75
76 message = try std.fmt.allocPrint(app.alloc, "Failed to delete '{s}' - {}.", .{ entry.name, err });
77 app.notification.write(message.?, .err) catch {};
78 }
79}
80
81pub fn rename(app: *App) error{OutOfMemory}!void {
82 var message: ?[]const u8 = null;
83 defer if (message) |msg| app.alloc.free(msg);
84
85 const entry = (app.directories.getSelected() catch {
86 app.notification.write("Can not to rename item - no item selected.", .warn) catch {};
87 return;
88 }) orelse return;
89
90 var dir_prefix_buf: [std.fs.max_path_bytes]u8 = undefined;
91 const dir_prefix = app.directories.dir.realpath(".", &dir_prefix_buf) catch {
92 message = try std.fmt.allocPrint(app.alloc, "Failed to rename '{s}' - unable to retrieve absolute path.", .{entry.name});
93 app.notification.write(message.?, .err) catch {};
94 if (app.file_logger) |file_logger| file_logger.write(message.?, .err) catch {};
95 return;
96 };
97
98 const new_path = try app.text_input.toOwnedSlice();
99 defer app.alloc.free(new_path);
100
101 if (environment.fileExists(app.directories.dir, new_path)) {
102 message = try std.fmt.allocPrint(app.alloc, "Can not rename file - '{s}' already exists.", .{new_path});
103 app.notification.write(message.?, .warn) catch {};
104 } else {
105 app.directories.dir.rename(entry.name, new_path) catch |err| {
106 message = try std.fmt.allocPrint(app.alloc, "Failed to rename '{s}' - {}.", .{ new_path, err });
107 app.notification.write(message.?, .err) catch {};
108 if (app.file_logger) |file_logger| file_logger.write(message.?, .err) catch {};
109 return;
110 };
111
112 if (app.actions.push(.{
113 .rename = .{
114 .prev_path = try std.fs.path.join(app.alloc, &.{ dir_prefix, entry.name }),
115 .new_path = try std.fs.path.join(app.alloc, &.{ dir_prefix, new_path }),
116 },
117 })) |prev_elem| {
118 app.alloc.free(prev_elem.rename.prev_path);
119 app.alloc.free(prev_elem.rename.new_path);
120 }
121
122 app.directories.clearEntries();
123 app.directories.populateEntries("") catch |err| {
124 const m = try std.fmt.allocPrint(app.alloc, "Failed to read directory entries - {}.", .{err});
125 defer app.alloc.free(m);
126 app.notification.write(m, .err) catch {};
127 if (app.file_logger) |file_logger| file_logger.write(m, .err) catch {};
128 };
129
130 const target_name = if (entry.kind == .directory)
131 try std.fmt.allocPrint(app.alloc, "{s}/", .{new_path})
132 else
133 new_path;
134 defer if (entry.kind == .directory) app.alloc.free(target_name);
135
136 for (app.directories.entries.all(), 0..) |e, i| {
137 if (std.mem.eql(u8, e.name, target_name)) {
138 app.directories.entries.selected = i;
139 break;
140 }
141 }
142
143 // No need to revalidate cache as we're viewing the same file
144 Preview.loadPreviewForCurrentEntry(app) catch |err| {
145 if (app.file_logger) |file_logger| {
146 const msg = std.fmt.allocPrint(app.alloc, "Failed to load preview after repopulate: {}", .{err}) catch return;
147 defer app.alloc.free(msg);
148 file_logger.write(msg, .err) catch {};
149 }
150 };
151
152 message = try std.fmt.allocPrint(app.alloc, "Renamed '{s}' to '{s}'.", .{ entry.name, new_path });
153 app.notification.write(message.?, .info) catch {};
154 }
155}
156
157pub fn forceDelete(app: *App) error{OutOfMemory}!void {
158 const entry = (app.directories.getSelected() catch {
159 app.notification.write("Can not force delete item - no item selected.", .warn) catch {};
160 return;
161 }) orelse return;
162
163 app.directories.dir.deleteTree(entry.name) catch |err| {
164 const error_message = try std.fmt.allocPrint(app.alloc, "Failed to force delete '{s}' - {}.", .{ entry.name, err });
165 app.notification.write(error_message, .err) catch {};
166 return;
167 };
168
169 app.directories.removeSelected();
170}
171
172pub fn toggleHiddenFiles(app: *App) error{OutOfMemory}!void {
173 config.show_hidden = !config.show_hidden;
174
175 try app.repopulateDirectory("");
176 app.text_input.clearAndFree();
177}
178
179pub fn yank(app: *App) error{OutOfMemory}!void {
180 var message: ?[]const u8 = null;
181 defer if (message) |msg| app.alloc.free(msg);
182
183 if (app.yanked) |yanked| {
184 app.alloc.free(yanked.dir);
185 app.alloc.free(yanked.entry.name);
186 }
187
188 app.yanked = lbl: {
189 const entry = (app.directories.getSelected() catch {
190 app.notification.write("Can not yank item - no item selected.", .warn) catch {};
191 break :lbl null;
192 }) orelse break :lbl null;
193
194 switch (entry.kind) {
195 .file, .directory, .sym_link => {
196 break :lbl .{
197 .dir = try app.alloc.dupe(u8, app.directories.fullPath(".") catch {
198 message = try std.fmt.allocPrint(
199 app.alloc,
200 "Failed to yank '{s}' - unable to retrieve directory path.",
201 .{entry.name},
202 );
203 app.notification.write(message.?, .err) catch {};
204 if (app.file_logger) |file_logger| file_logger.write(message.?, .err) catch {};
205 break :lbl null;
206 }),
207 .entry = .{
208 .kind = entry.kind,
209 .name = try app.alloc.dupe(u8, entry.name),
210 },
211 };
212 },
213 else => {
214 message = try std.fmt.allocPrint(app.alloc, "Can not yank '{s}' - unsupported file type '{s}'.", .{ entry.name, @tagName(entry.kind) });
215 app.notification.write(message.?, .warn) catch {};
216 break :lbl null;
217 },
218 }
219 };
220
221 if (app.yanked) |y| {
222 message = try std.fmt.allocPrint(app.alloc, "Yanked '{s}'.", .{y.entry.name});
223 app.notification.write(message.?, .info) catch {};
224 }
225}
226
227pub fn paste(app: *App) error{ OutOfMemory, NoSpaceLeft }!void {
228 var message: ?[]const u8 = null;
229 defer if (message) |msg| app.alloc.free(msg);
230
231 const yanked = if (app.yanked) |y| y else return;
232
233 var new_path_buf: [std.fs.max_path_bytes]u8 = undefined;
234 const new_path_res = environment.checkDuplicatePath(&new_path_buf, app.directories.dir, yanked.entry.name) catch {
235 message = try std.fmt.allocPrint(app.alloc, "Failed to copy '{s}' - path too long.", .{yanked.entry.name});
236 app.notification.write(message.?, .err) catch {};
237 return;
238 };
239
240 switch (yanked.entry.kind) {
241 .directory => {
242 var source_dir = std.fs.openDirAbsolute(yanked.dir, .{ .iterate = true }) catch {
243 message = try std.fmt.allocPrint(app.alloc, "Failed to copy '{s}' - unable to open directory '{s}'.", .{ yanked.entry.name, yanked.dir });
244 app.notification.write(message.?, .err) catch {};
245 if (app.file_logger) |file_logger| file_logger.write(message.?, .err) catch {};
246 return;
247 };
248 defer source_dir.close();
249
250 var selected_dir = source_dir.openDir(yanked.entry.name, .{ .iterate = true }) catch {
251 message = try std.fmt.allocPrint(app.alloc, "Failed to copy '{s}' - unable to open directory '{s}'.", .{ yanked.entry.name, yanked.entry.name });
252 app.notification.write(message.?, .err) catch {};
253 if (app.file_logger) |file_logger| file_logger.write(message.?, .err) catch {};
254 return;
255 };
256 defer selected_dir.close();
257
258 var walker = selected_dir.walk(app.alloc) catch |err| {
259 message = try std.fmt.allocPrint(app.alloc, "Failed to copy '{s}' - unable to walk directory tree due to {}.", .{ yanked.entry.name, err });
260 app.notification.write(message.?, .err) catch {};
261 if (app.file_logger) |file_logger| file_logger.write(message.?, .err) catch {};
262 return;
263 };
264 defer walker.deinit();
265
266 // Make initial dir.
267 app.directories.dir.makeDir(new_path_res.path) catch |err| {
268 message = try std.fmt.allocPrint(app.alloc, "Failed to copy '{s}' - unable to create new directory due to {}.", .{ yanked.entry.name, err });
269 app.notification.write(message.?, .err) catch {};
270 if (app.file_logger) |file_logger| file_logger.write(message.?, .err) catch {};
271 return;
272 };
273
274 var errored = false;
275 var inner_path_buf: [std.fs.max_path_bytes]u8 = undefined;
276 while (walker.next() catch |err| {
277 message = try std.fmt.allocPrint(app.alloc, "Failed to copy one or more files - {}. A partial copy may have taken place.", .{err});
278 app.notification.write(message.?, .err) catch {};
279 if (app.file_logger) |file_logger| file_logger.write(message.?, .err) catch {};
280 return;
281 }) |entry| {
282 const path = try std.fmt.bufPrint(&inner_path_buf, "{s}{s}{s}", .{ new_path_res.path, std.fs.path.sep_str, entry.path });
283 switch (entry.kind) {
284 .directory => {
285 app.directories.dir.makeDir(path) catch {
286 message = try std.fmt.allocPrint(app.alloc, "Failed to copy '{s}' - unable to create containing directory '{s}'.", .{ entry.basename, path });
287 app.notification.write(message.?, .err) catch {};
288 if (app.file_logger) |file_logger| file_logger.write(message.?, .err) catch {};
289 errored = true;
290 };
291 },
292 .file, .sym_link => {
293 entry.dir.copyFile(entry.basename, app.directories.dir, path, .{}) catch |err| switch (err) {
294 error.FileNotFound => {
295 message = try std.fmt.allocPrint(app.alloc, "Failed to copy '{s}' - the original file was deleted or moved.", .{entry.path});
296 app.notification.write(message.?, .err) catch {};
297 if (app.file_logger) |file_logger| file_logger.write(message.?, .err) catch {};
298 errored = true;
299 },
300 else => {
301 message = try std.fmt.allocPrint(app.alloc, "Failed to copy '{s}' - {}.", .{ entry.path, err });
302 app.notification.write(message.?, .err) catch {};
303 if (app.file_logger) |file_logger| file_logger.write(message.?, .err) catch {};
304 errored = true;
305 },
306 };
307 },
308 else => {
309 message = try std.fmt.allocPrint(app.alloc, "Failed to copy '{s}' - unsupported file type '{s}'.", .{ entry.path, @tagName(entry.kind) });
310 app.notification.write(message.?, .err) catch {};
311 if (app.file_logger) |file_logger| file_logger.write(message.?, .err) catch {};
312 errored = true;
313 },
314 }
315 }
316
317 if (errored) {
318 app.notification.write("Failed to copy some items, check the log file for more details.", .err) catch {};
319 } else {
320 message = try std.fmt.allocPrint(app.alloc, "Copied '{s}'.", .{yanked.entry.name});
321 app.notification.write(message.?, .info) catch {};
322 }
323 },
324 .file, .sym_link => {
325 var source_dir = std.fs.openDirAbsolute(yanked.dir, .{ .iterate = true }) catch {
326 message = try std.fmt.allocPrint(app.alloc, "Failed to copy '{s}' - unable to open directory '{s}'.", .{ yanked.entry.name, yanked.dir });
327 app.notification.write(message.?, .err) catch {};
328 if (app.file_logger) |file_logger| file_logger.write(message.?, .err) catch {};
329 return;
330 };
331 defer source_dir.close();
332
333 std.fs.Dir.copyFile(
334 source_dir,
335 yanked.entry.name,
336 app.directories.dir,
337 new_path_res.path,
338 .{},
339 ) catch |err| switch (err) {
340 error.FileNotFound => {
341 message = try std.fmt.allocPrint(app.alloc, "Failed to copy '{s}' - the original file was deleted or moved.", .{yanked.entry.name});
342 app.notification.write(message.?, .err) catch {};
343 if (app.file_logger) |file_logger| file_logger.write(message.?, .err) catch {};
344 return;
345 },
346 else => {
347 message = try std.fmt.allocPrint(app.alloc, "Failed to copy '{s}' - {}.", .{ yanked.entry.name, err });
348 app.notification.write(message.?, .err) catch {};
349 if (app.file_logger) |file_logger| file_logger.write(message.?, .err) catch {};
350 return;
351 },
352 };
353
354 message = try std.fmt.allocPrint(app.alloc, "Copied '{s}'.", .{yanked.entry.name});
355 app.notification.write(message.?, .info) catch {};
356 },
357 else => {
358 message = try std.fmt.allocPrint(app.alloc, "Can not copy '{s}' - unsupported file type '{s}'.", .{ yanked.entry.name, @tagName(yanked.entry.kind) });
359 app.notification.write(message.?, .warn) catch {};
360 return;
361 },
362 }
363
364 // Append action to undo history.
365 var new_path_abs_buf: [std.fs.max_path_bytes]u8 = undefined;
366 const new_path_abs = app.directories.dir.realpath(new_path_res.path, &new_path_abs_buf) catch {
367 message = try std.fmt.allocPrint(
368 app.alloc,
369 "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.",
370 .{ new_path_res.path, yanked.entry.name },
371 );
372 app.notification.write(message.?, .err) catch {};
373 if (app.file_logger) |file_logger| file_logger.write(message.?, .err) catch {};
374 return;
375 };
376
377 if (app.actions.push(.{
378 .paste = try app.alloc.dupe(u8, new_path_abs),
379 })) |prev_elem| {
380 app.alloc.free(prev_elem.delete.prev_path);
381 app.alloc.free(prev_elem.delete.new_path);
382 }
383
384 try app.repopulateDirectory("");
385 app.text_input.clearAndFree();
386}
387
388pub fn traverseLeft(app: *App) error{OutOfMemory}!void {
389 app.text_input.clearAndFree();
390
391 const dir = app.directories.dir.openDir("../", .{ .iterate = true }) catch |err| {
392 const message = try std.fmt.allocPrint(app.alloc, "Failed to read directory entries - {}.", .{err});
393 defer app.alloc.free(message);
394 app.notification.write(message, .err) catch {};
395 if (app.file_logger) |file_logger| file_logger.write(message, .err) catch {};
396 return;
397 };
398
399 app.directories.dir.close();
400 app.directories.dir = dir;
401
402 try app.repopulateDirectory("");
403 app.text_input.clearAndFree();
404
405 if (app.directories.history.pop()) |history| {
406 if (history < app.directories.entries.len()) {
407 app.directories.entries.selected = history;
408 }
409 }
410}
411
412pub fn traverseRight(app: *App) !void {
413 var message: ?[]const u8 = null;
414 defer if (message) |msg| app.alloc.free(msg);
415
416 const entry = (app.directories.getSelected() catch {
417 app.notification.write("Can not rename item - no item selected.", .warn) catch {};
418 return;
419 }) orelse return;
420
421 switch (entry.kind) {
422 .directory => {
423 app.text_input.clearAndFree();
424
425 const dir = app.directories.dir.openDir(entry.name, .{ .iterate = true }) catch |err| {
426 message = try std.fmt.allocPrint(app.alloc, "Failed to read directory entries - {}.", .{err});
427 app.notification.write(message.?, .err) catch {};
428 if (app.file_logger) |file_logger| file_logger.write(message.?, .err) catch {};
429 return;
430 };
431
432 app.directories.dir.close();
433 app.directories.dir = dir;
434 _ = app.directories.history.push(app.directories.entries.selected);
435 try app.repopulateDirectory("");
436 app.text_input.clearAndFree();
437 },
438 .file => {
439 if (environment.getEditor()) |editor| {
440 try app.vx.exitAltScreen(app.tty.writer());
441 try app.vx.resetState(app.tty.writer());
442 app.loop.stop();
443
444 environment.openFile(app.alloc, app.directories.dir, entry.name, editor) catch |err| {
445 message = try std.fmt.allocPrint(app.alloc, "Failed to open file '{s}' - {}.", .{ entry.name, err });
446 app.notification.write(message.?, .err) catch {};
447 if (app.file_logger) |file_logger| file_logger.write(message.?, .err) catch {};
448 };
449
450 try app.loop.start();
451 try app.vx.enterAltScreen(app.tty.writer());
452 try app.vx.enableDetectedFeatures(app.tty.writer());
453 app.vx.queueRefresh();
454 } else {
455 app.notification.write("Can not open file - $EDITOR not set.", .warn) catch {};
456 }
457 },
458 else => {},
459 }
460}
461
462pub fn createNewDir(app: *App) error{OutOfMemory}!void {
463 var message: ?[]const u8 = null;
464 defer if (message) |msg| app.alloc.free(msg);
465
466 const dir = try app.text_input.toOwnedSlice();
467 defer app.alloc.free(dir);
468
469 app.directories.dir.makeDir(dir) catch |err| {
470 message = try std.fmt.allocPrint(app.alloc, "Failed to create directory '{s}' - {}", .{ dir, err });
471 app.notification.write(message.?, .err) catch {};
472 if (app.file_logger) |file_logger| file_logger.write(message.?, .err) catch {};
473 return;
474 };
475
476 try app.repopulateDirectory("");
477
478 message = try std.fmt.allocPrint(app.alloc, "Created new directory '{s}'.", .{dir});
479 app.notification.write(message.?, .info) catch {};
480}
481
482pub fn createNewFile(app: *App) error{OutOfMemory}!void {
483 var message: ?[]const u8 = null;
484 defer if (message) |msg| app.alloc.free(msg);
485
486 const file = try app.text_input.toOwnedSlice();
487 defer app.alloc.free(file);
488
489 if (environment.fileExists(app.directories.dir, file)) {
490 message = try std.fmt.allocPrint(app.alloc, "Can not create file - '{s}' already exists.", .{file});
491 app.notification.write(message.?, .warn) catch {};
492 } else {
493 _ = app.directories.dir.createFile(file, .{}) catch |err| {
494 message = try std.fmt.allocPrint(app.alloc, "Failed to create file '{s}' - {}", .{ file, err });
495 app.notification.write(message.?, .err) catch {};
496 if (app.file_logger) |file_logger| file_logger.write(message.?, .err) catch {};
497 return;
498 };
499
500 try app.repopulateDirectory("");
501
502 message = try std.fmt.allocPrint(app.alloc, "Created new file '{s}'.", .{file});
503 app.notification.write(message.?, .info) catch {};
504 }
505}
506
507pub fn undo(app: *App) error{OutOfMemory}!void {
508 var message: ?[]const u8 = null;
509 defer if (message) |msg| app.alloc.free(msg);
510
511 const action = app.actions.pop() orelse {
512 app.notification.write("There is nothing to undo.", .info) catch {};
513 return;
514 };
515
516 switch (action) {
517 .delete => |a| {
518 defer app.alloc.free(a.new_path);
519 defer app.alloc.free(a.prev_path);
520
521 var new_path_buf: [std.fs.max_path_bytes]u8 = undefined;
522 const new_path_res = environment.checkDuplicatePath(&new_path_buf, app.directories.dir, a.prev_path) catch {
523 message = try std.fmt.allocPrint(app.alloc, "Failed to undo delete '{s}' - path too long.", .{a.prev_path});
524 app.notification.write(message.?, .err) catch {};
525 if (app.file_logger) |file_logger| file_logger.write(message.?, .err) catch {};
526 return;
527 };
528
529 app.directories.dir.rename(a.new_path, new_path_res.path) catch |err| {
530 message = try std.fmt.allocPrint(app.alloc, "Failed to undo delete for '{s}' - {}.", .{ a.prev_path, err });
531 app.notification.write(message.?, .err) catch {};
532 if (app.file_logger) |file_logger| file_logger.write(message.?, .err) catch {};
533 return;
534 };
535
536 message = try std.fmt.allocPrint(app.alloc, "Restored '{s}' as '{s}'.", .{ a.prev_path, new_path_res.path });
537 app.notification.write(message.?, .info) catch {};
538 },
539 .rename => |a| {
540 defer app.alloc.free(a.new_path);
541 defer app.alloc.free(a.prev_path);
542
543 var new_path_buf: [std.fs.max_path_bytes]u8 = undefined;
544 const new_path_res = environment.checkDuplicatePath(&new_path_buf, app.directories.dir, a.prev_path) catch {
545 message = try std.fmt.allocPrint(app.alloc, "Failed to undo rename '{s}' - path too long.", .{a.prev_path});
546 app.notification.write(message.?, .err) catch {};
547 if (app.file_logger) |file_logger| file_logger.write(message.?, .err) catch {};
548 return;
549 };
550
551 app.directories.dir.rename(a.new_path, new_path_res.path) catch |err| {
552 message = try std.fmt.allocPrint(app.alloc, "Failed to undo rename for '{s}' - {}.", .{ a.new_path, err });
553 app.notification.write(message.?, .err) catch {};
554 if (app.file_logger) |file_logger| file_logger.write(message.?, .err) catch {};
555 return;
556 };
557
558 message = try std.fmt.allocPrint(app.alloc, "Reverted renaming of '{s}', now '{s}'.", .{ a.new_path, new_path_res.path });
559 app.notification.write(message.?, .info) catch {};
560 },
561 .paste => |path| {
562 defer app.alloc.free(path);
563
564 app.directories.dir.deleteTree(path) catch |err| {
565 message = try std.fmt.allocPrint(app.alloc, "Failed to delete '{s}' - {}.", .{ path, err });
566 app.notification.write(message.?, .err) catch {};
567 if (app.file_logger) |file_logger| file_logger.write(message.?, .err) catch {};
568 return;
569 };
570 },
571 }
572
573 try app.repopulateDirectory("");
574 app.text_input.clearAndFree();
575}
576
577pub fn extractArchive(app: *App) error{OutOfMemory}!void {
578 var message: ?[]const u8 = null;
579 defer if (message) |msg| app.alloc.free(msg);
580
581 const entry = (app.directories.getSelected() catch {
582 app.notification.write("Can not extract - no item selected.", .warn) catch {};
583 return;
584 }) orelse return;
585
586 const archive_type = Archive.ArchiveType.fromPath(entry.name) orelse {
587 app.notification.write("Not an archive file.", .warn) catch {};
588 return;
589 };
590
591 const extract_dir_name = Archive.getExtractDirName(entry.name);
592
593 if (environment.fileExists(app.directories.dir, extract_dir_name)) {
594 message = try std.fmt.allocPrint(app.alloc, "Can not extract file(s) - '{s}' already exists.", .{extract_dir_name});
595 app.notification.write(message.?, .warn) catch {};
596 return;
597 }
598
599 var dest_dir = app.directories.dir.makeOpenPath(extract_dir_name, .{}) catch |err| {
600 message = try std.fmt.allocPrint(app.alloc, "Failed to extract archive '{s}' - {}.", .{ extract_dir_name, err });
601 app.notification.write(message.?, .err) catch {};
602 if (app.file_logger) |file_logger| file_logger.write(message.?, .err) catch {};
603 return;
604 };
605 defer dest_dir.close();
606
607 const archive_file = app.directories.dir.openFile(entry.name, .{}) catch |err| {
608 message = try std.fmt.allocPrint(
609 app.alloc,
610 "Failed to open archive '{s}' - {}.",
611 .{ entry.name, err },
612 );
613 app.notification.write(message.?, .err) catch {};
614 if (app.file_logger) |file_logger| file_logger.write(message.?, .err) catch {};
615
616 if (!config.keep_partial_extraction) {
617 app.directories.dir.deleteTree(extract_dir_name) catch {};
618 }
619 return;
620 };
621 defer archive_file.close();
622
623 const result = Archive.extractArchive(
624 app.alloc,
625 archive_file,
626 archive_type,
627 dest_dir,
628 app.file_logger,
629 ) catch |err| {
630 message = try std.fmt.allocPrint(
631 app.alloc,
632 "Failed to extract '{s}' - {s}.",
633 .{ entry.name, @errorName(err) },
634 );
635 app.notification.write(message.?, .err) catch {};
636 if (app.file_logger) |file_logger| file_logger.write(message.?, .err) catch {};
637
638 if (!config.keep_partial_extraction) {
639 app.directories.dir.deleteTree(extract_dir_name) catch {};
640 }
641 return;
642 };
643
644 if (result.files_skipped > 0) {
645 message = try std.fmt.allocPrint(
646 app.alloc,
647 "Extracted {d} files, {d} directories to './{s}{s}'. Failed to extract {d} files, check the log file for more details.",
648 .{ result.files_extracted, result.dirs_created, std.fs.path.sep_str, extract_dir_name, result.files_skipped },
649 );
650 app.notification.write(message.?, .err) catch {};
651 if (app.file_logger) |file_logger| file_logger.write(message.?, .err) catch {};
652 } else {
653 message = try std.fmt.allocPrint(
654 app.alloc,
655 "Extracted {d} files, {d} directories to './{s}{s}'.",
656 .{ result.files_extracted, result.dirs_created, std.fs.path.sep_str, extract_dir_name },
657 );
658 app.notification.write(message.?, .info) catch {};
659 if (app.file_logger) |file_logger| file_logger.write(message.?, .info) catch {};
660 }
661
662 try app.repopulateDirectory("");
663}