+4
-1
CHANGELOG.md
+4
-1
CHANGELOG.md
···
1
1
# Changelog
2
2
3
+
## v0.9.9 (2025-04-06)
4
+
- feat: Added ability to copy folders.
5
+
3
6
## v0.9.8 (2025-04-04)
4
7
- fix: Ensure complete Git branch is displayed.
5
8
- refactor: Audit try usage to improve system resiliance.
6
9
- refactor: Removed need for enum based notifications.
7
10
8
11
## v0.9.7 (2025-04-01)
9
-
- feat: Added ability to copy folders.
12
+
- feat: Added ability to copy files.
10
13
This is done by (y)anking the file, then (p)asting in the desired directory.
11
14
This action can be (u)ndone and behind the scenes is a deletion.
12
15
- fix: Allow the cursor to be moved left and right.
+2
-2
PROJECT_BOARD.md
+2
-2
PROJECT_BOARD.md
···
8
8
## v1.0 release
9
9
10
10
### New features
11
-
- [ ] File/Folder movement.
11
+
- [x] File/Folder movement.
12
12
- [x] Copy files.
13
-
- [ ] Copy folders.
13
+
- [x] Copy folders.
14
14
- [ ] Keybind to unzip archives.
15
15
- [x] Keybind to hard delete items (bypass trash).
16
16
- [x] Ability to unbind keys.
+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 = 8 };
5
+
const version = std.SemanticVersion{ .major = 0, .minor = 9, .patch = 9 };
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
+106
-23
src/events.zig
+106
-23
src/events.zig
···
174
174
}) orelse break :lbl null;
175
175
176
176
switch (entry.kind) {
177
-
.file => {
177
+
.file, .directory, .sym_link => {
178
178
break :lbl .{
179
179
.dir = try app.alloc.dupe(u8, app.directories.fullPath(".") catch {
180
180
message = try std.fmt.allocPrint(
···
206
206
}
207
207
}
208
208
209
-
pub fn paste(app: *App) error{OutOfMemory}!void {
209
+
pub fn paste(app: *App) error{ OutOfMemory, NoSpaceLeft }!void {
210
210
var message: ?[]const u8 = null;
211
211
defer if (message) |msg| app.alloc.free(msg);
212
212
···
220
220
};
221
221
222
222
switch (yanked.entry.kind) {
223
-
.file => {
223
+
.directory => {
224
+
var source_dir = std.fs.openDirAbsolute(yanked.dir, .{ .iterate = true }) catch {
225
+
message = try std.fmt.allocPrint(app.alloc, "Failed to copy '{s}' - unable to open directory '{s}'.", .{ yanked.entry.name, yanked.dir });
226
+
app.notification.write(message.?, .err) catch {};
227
+
if (app.file_logger) |file_logger| file_logger.write(message.?, .err) catch {};
228
+
return;
229
+
};
230
+
defer source_dir.close();
231
+
232
+
var selected_dir = source_dir.openDir(yanked.entry.name, .{ .iterate = true }) catch {
233
+
message = try std.fmt.allocPrint(app.alloc, "Failed to copy '{s}' - unable to open directory '{s}'.", .{ yanked.entry.name, yanked.entry.name });
234
+
app.notification.write(message.?, .err) catch {};
235
+
if (app.file_logger) |file_logger| file_logger.write(message.?, .err) catch {};
236
+
return;
237
+
};
238
+
defer selected_dir.close();
239
+
240
+
var walker = selected_dir.walk(app.alloc) catch |err| {
241
+
message = try std.fmt.allocPrint(app.alloc, "Failed to copy '{s}' - unable to walk directory tree due to {}.", .{ yanked.entry.name, err });
242
+
app.notification.write(message.?, .err) catch {};
243
+
if (app.file_logger) |file_logger| file_logger.write(message.?, .err) catch {};
244
+
return;
245
+
};
246
+
defer walker.deinit();
247
+
248
+
// Make initial dir.
249
+
app.directories.dir.makeDir(new_path_res.path) catch |err| {
250
+
message = try std.fmt.allocPrint(app.alloc, "Failed to copy '{s}' - unable to create new directory due to {}.", .{ yanked.entry.name, err });
251
+
app.notification.write(message.?, .err) catch {};
252
+
if (app.file_logger) |file_logger| file_logger.write(message.?, .err) catch {};
253
+
return;
254
+
};
255
+
256
+
var errored = false;
257
+
var inner_path_buf: [std.fs.max_path_bytes]u8 = undefined;
258
+
while (walker.next() catch |err| {
259
+
message = try std.fmt.allocPrint(app.alloc, "Failed to copy one or more files - {}. A partial copy may have taken place.", .{err});
260
+
app.notification.write(message.?, .err) catch {};
261
+
if (app.file_logger) |file_logger| file_logger.write(message.?, .err) catch {};
262
+
return;
263
+
}) |entry| {
264
+
const path = try std.fmt.bufPrint(&inner_path_buf, "{s}{s}{s}", .{ new_path_res.path, std.fs.path.sep_str, entry.path });
265
+
switch (entry.kind) {
266
+
.directory => {
267
+
app.directories.dir.makeDir(path) catch {
268
+
message = try std.fmt.allocPrint(app.alloc, "Failed to copy '{s}' - unable to create containing directory '{s}'.", .{ entry.basename, path });
269
+
app.notification.write(message.?, .err) catch {};
270
+
if (app.file_logger) |file_logger| file_logger.write(message.?, .err) catch {};
271
+
errored = true;
272
+
};
273
+
},
274
+
.file, .sym_link => {
275
+
entry.dir.copyFile(entry.basename, app.directories.dir, path, .{}) catch |err| switch (err) {
276
+
error.FileNotFound => {
277
+
message = try std.fmt.allocPrint(app.alloc, "Failed to copy '{s}' - the original file was deleted or moved.", .{entry.path});
278
+
app.notification.write(message.?, .err) catch {};
279
+
if (app.file_logger) |file_logger| file_logger.write(message.?, .err) catch {};
280
+
errored = true;
281
+
},
282
+
else => {
283
+
message = try std.fmt.allocPrint(app.alloc, "Failed to copy '{s}' - {}.", .{ entry.path, err });
284
+
app.notification.write(message.?, .err) catch {};
285
+
if (app.file_logger) |file_logger| file_logger.write(message.?, .err) catch {};
286
+
errored = true;
287
+
},
288
+
};
289
+
},
290
+
else => {
291
+
message = try std.fmt.allocPrint(app.alloc, "Failed to copy '{s}' - unsupported file type '{}'.", .{ entry.path, entry.kind });
292
+
app.notification.write(message.?, .err) catch {};
293
+
if (app.file_logger) |file_logger| file_logger.write(message.?, .err) catch {};
294
+
errored = true;
295
+
},
296
+
}
297
+
}
298
+
299
+
if (errored) {
300
+
app.notification.write("Failed to copy some items, check the log file for more details.", .err) catch {};
301
+
} else {
302
+
message = try std.fmt.allocPrint(app.alloc, "Copied '{s}'.", .{yanked.entry.name});
303
+
app.notification.write(message.?, .info) catch {};
304
+
}
305
+
},
306
+
.file, .sym_link => {
224
307
var source_dir = std.fs.openDirAbsolute(yanked.dir, .{ .iterate = true }) catch {
225
308
message = try std.fmt.allocPrint(app.alloc, "Failed to copy '{s}' - unable to open directory '{s}'.", .{ yanked.entry.name, yanked.dir });
226
309
app.notification.write(message.?, .err) catch {};
···
250
333
},
251
334
};
252
335
253
-
// Append action to undo history.
254
-
var new_path_abs_buf: [std.fs.max_path_bytes]u8 = undefined;
255
-
const new_path_abs = app.directories.dir.realpath(new_path_res.path, &new_path_abs_buf) catch {
256
-
message = try std.fmt.allocPrint(
257
-
app.alloc,
258
-
"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.",
259
-
.{ new_path_res.path, yanked.entry.name },
260
-
);
261
-
app.notification.write(message.?, .err) catch {};
262
-
if (app.file_logger) |file_logger| file_logger.write(message.?, .err) catch {};
263
-
return;
264
-
};
265
-
266
-
if (app.actions.push(.{
267
-
.paste = try app.alloc.dupe(u8, new_path_abs),
268
-
})) |prev_elem| {
269
-
app.alloc.free(prev_elem.delete.prev_path);
270
-
app.alloc.free(prev_elem.delete.new_path);
271
-
}
272
-
273
336
message = try std.fmt.allocPrint(app.alloc, "Copied '{s}'.", .{yanked.entry.name});
274
337
app.notification.write(message.?, .info) catch {};
275
338
},
···
278
341
app.notification.write(message.?, .warn) catch {};
279
342
return;
280
343
},
344
+
}
345
+
346
+
// Append action to undo history.
347
+
var new_path_abs_buf: [std.fs.max_path_bytes]u8 = undefined;
348
+
const new_path_abs = app.directories.dir.realpath(new_path_res.path, &new_path_abs_buf) catch {
349
+
message = try std.fmt.allocPrint(
350
+
app.alloc,
351
+
"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.",
352
+
.{ new_path_res.path, yanked.entry.name },
353
+
);
354
+
app.notification.write(message.?, .err) catch {};
355
+
if (app.file_logger) |file_logger| file_logger.write(message.?, .err) catch {};
356
+
return;
357
+
};
358
+
359
+
if (app.actions.push(.{
360
+
.paste = try app.alloc.dupe(u8, new_path_abs),
361
+
})) |prev_elem| {
362
+
app.alloc.free(prev_elem.delete.prev_path);
363
+
app.alloc.free(prev_elem.delete.new_path);
281
364
}
282
365
283
366
try app.repopulateDirectory("");