+5
CHANGELOG.md
+5
CHANGELOG.md
···
1
1
# Changelog
2
2
3
+
## v0.9.8 (2025-04-04)
4
+
- fix: Ensure complete Git branch is displayed.
5
+
- refactor: Audit try usage to improve system resiliance.
6
+
- refactor: Removed need for enum based notifications.
7
+
3
8
## v0.9.7 (2025-04-01)
4
9
- feat: Added ability to copy folders.
5
10
This is done by (y)anking the file, then (p)asting in the desired directory.
+1
-1
PROJECT_BOARD.md
+1
-1
PROJECT_BOARD.md
+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 = 7 };
5
+
const version = std.SemanticVersion{ .major = 0, .minor = 9, .patch = 8 };
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
+54
-9
src/app.zig
+54
-9
src/app.zig
···
162
162
if (self.file_logger) |file_logger| file_logger.deinit();
163
163
}
164
164
165
+
pub fn inputToSlice(self: *App) []const u8 {
166
+
self.text_input.buf.cursor = self.text_input.buf.realLength();
167
+
return self.text_input.sliceToCursor(&self.text_input_buf);
168
+
}
169
+
170
+
pub fn repopulateDirectory(self: *App, fuzzy: []const u8) error{OutOfMemory}!void {
171
+
self.directories.clearEntries();
172
+
self.directories.populateEntries(fuzzy) catch |err| {
173
+
const message = try std.fmt.allocPrint(self.alloc, "Failed to read directory entries - {}.", .{err});
174
+
defer self.alloc.free(message);
175
+
self.notification.write(message, .err) catch {};
176
+
if (self.file_logger) |file_logger| file_logger.write(message, .err) catch {};
177
+
};
178
+
}
179
+
165
180
pub fn run(self: *App) !void {
166
-
try self.directories.populateEntries("");
181
+
try self.repopulateDirectory("");
167
182
168
183
var loop: vaxis.Loop(Event) = .{
169
184
.vaxis = &self.vx,
···
188
203
189
204
if ((key.codepoint == 'r' and key.mods.ctrl)) {
190
205
if (config.parse(self.alloc, self)) {
191
-
try self.notification.writeInfo(.ConfigReloaded);
206
+
self.notification.write("Reloaded configuration file.", .info) catch {};
192
207
} else |err| switch (err) {
193
208
error.SyntaxError => {
194
-
try self.notification.writeErr(.ConfigSyntaxError);
209
+
self.notification.write("Encountered a syntax error while parsing the config file.", .err) catch {
210
+
std.log.err("Encountered a syntax error while parsing the config file.", .{});
211
+
};
195
212
},
196
213
error.InvalidCharacter => {
197
-
try self.notification.writeErr(.InvalidKeybind);
214
+
self.notification.write("One or more overriden keybinds are invalid.", .err) catch {
215
+
std.log.err("One or more overriden keybinds are invalid.", .{});
216
+
};
198
217
},
199
218
error.DuplicateKeybind => {
200
219
// Error logged in function
201
220
},
202
221
else => {
203
-
try self.notification.writeErr(.ConfigUnknownError);
222
+
const message = try std.fmt.allocPrint(self.alloc, "Encountend an unknown error while parsing the config file - {}", .{err});
223
+
defer self.alloc.free(message);
224
+
225
+
self.notification.write(message, .err) catch {
226
+
std.log.err("Encountend an unknown error while parsing the config file - {}", .{err});
227
+
};
204
228
},
205
229
}
206
230
}
···
230
254
}
231
255
232
256
if (config.empty_trash_on_exit) {
233
-
if (try config.trashDir()) |dir| {
234
-
var trash_dir = dir;
235
-
defer trash_dir.close();
236
-
_ = try environment.deleteContents(trash_dir);
257
+
var trash_dir = dir: {
258
+
notfound: {
259
+
break :dir (config.trashDir() catch break :notfound) orelse break :notfound;
260
+
}
261
+
if (self.file_logger) |file_logger| file_logger.write("Failed to open trash directory.", .err) catch {
262
+
std.log.err("Failed to open trash directory.", .{});
263
+
};
264
+
return;
265
+
};
266
+
defer trash_dir.close();
267
+
268
+
const failed = environment.deleteContents(trash_dir) catch |err| {
269
+
const message = try std.fmt.allocPrint(self.alloc, "Failed to empty trash - {}.", .{err});
270
+
defer self.alloc.free(message);
271
+
if (self.file_logger) |file_logger| file_logger.write(message, .err) catch {
272
+
std.log.err("Failed to empty trash - {}.", .{err});
273
+
};
274
+
return;
275
+
};
276
+
if (failed > 0) {
277
+
const message = try std.fmt.allocPrint(self.alloc, "Failed to empty {d} items from the trash.", .{failed});
278
+
defer self.alloc.free(message);
279
+
if (self.file_logger) |file_logger| file_logger.write(message, .err) catch {
280
+
std.log.err("Failed to empty {d} items from the trash.", .{failed});
281
+
};
237
282
}
238
283
}
239
284
}
+59
-62
src/commands.zig
+59
-62
src/commands.zig
···
1
1
const std = @import("std");
2
2
const App = @import("app.zig");
3
3
const environment = @import("environment.zig");
4
-
const _config = &@import("./config.zig").config;
4
+
const user_config = &@import("./config.zig").config;
5
5
6
6
pub const CommandHistory = struct {
7
7
const history_len = 10;
···
45
45
};
46
46
47
47
///Navigate the user to the config dir.
48
-
pub fn config(app: *App) !void {
48
+
pub fn config(app: *App) error{OutOfMemory}!void {
49
49
const dir = dir: {
50
50
notfound: {
51
-
break :dir (_config.configDir() catch break :notfound) orelse break :notfound;
51
+
break :dir (user_config.configDir() catch break :notfound) orelse break :notfound;
52
52
}
53
-
try app.notification.writeErr(.ConfigPathNotFound);
53
+
const message = try std.fmt.allocPrint(app.alloc, "Failed to navigate to config directory - unable to retrieve config directory.", .{});
54
+
defer app.alloc.free(message);
55
+
app.notification.write(message, .err) catch {};
56
+
if (app.file_logger) |file_logger| file_logger.write(message, .err) catch {};
54
57
return;
55
58
};
56
-
app.directories.clearEntries();
59
+
57
60
app.directories.dir.close();
58
61
app.directories.dir = dir;
59
-
app.directories.populateEntries("") catch |err| {
60
-
switch (err) {
61
-
error.AccessDenied => try app.notification.writeErr(.PermissionDenied),
62
-
else => try app.notification.writeErr(.UnknownError),
63
-
}
64
-
};
62
+
try app.repopulateDirectory("");
65
63
}
66
64
67
65
///Navigate the user to the trash dir.
68
-
pub fn trash(app: *App) !void {
66
+
pub fn trash(app: *App) error{OutOfMemory}!void {
69
67
const dir = dir: {
70
68
notfound: {
71
-
break :dir (_config.trashDir() catch break :notfound) orelse break :notfound;
69
+
break :dir (user_config.trashDir() catch break :notfound) orelse break :notfound;
72
70
}
73
-
try app.notification.writeErr(.ConfigPathNotFound);
71
+
const message = try std.fmt.allocPrint(app.alloc, "Failed to navigate to trash directory - unable to retrieve trash directory.", .{});
72
+
defer app.alloc.free(message);
73
+
app.notification.write(message, .err) catch {};
74
+
if (app.file_logger) |file_logger| file_logger.write(message, .err) catch {};
74
75
return;
75
76
};
76
-
app.directories.clearEntries();
77
+
77
78
app.directories.dir.close();
78
79
app.directories.dir = dir;
79
-
app.directories.populateEntries("") catch |err| {
80
-
switch (err) {
81
-
error.AccessDenied => try app.notification.writeErr(.PermissionDenied),
82
-
else => try app.notification.writeErr(.UnknownError),
83
-
}
84
-
};
80
+
try app.repopulateDirectory("");
85
81
}
86
82
87
83
///Empty the trash.
88
-
pub fn emptyTrash(app: *App) !void {
89
-
const dir = dir: {
84
+
pub fn emptyTrash(app: *App) error{OutOfMemory}!void {
85
+
var message: ?[]const u8 = null;
86
+
defer if (message) |msg| app.alloc.free(msg);
87
+
88
+
var dir = dir: {
90
89
notfound: {
91
-
break :dir (_config.trashDir() catch break :notfound) orelse break :notfound;
90
+
break :dir (user_config.trashDir() catch break :notfound) orelse break :notfound;
92
91
}
93
-
try app.notification.writeErr(.ConfigPathNotFound);
92
+
message = try std.fmt.allocPrint(app.alloc, "Failed to navigate to trash directory - unable to retrieve trash directory.", .{});
93
+
app.notification.write(message.?, .err) catch {};
94
+
if (app.file_logger) |file_logger| file_logger.write(message.?, .err) catch {};
94
95
return;
95
96
};
97
+
defer dir.close();
96
98
97
-
var trash_dir = dir;
98
-
defer trash_dir.close();
99
-
const failed = try environment.deleteContents(trash_dir);
100
-
if (failed > 0) try app.notification.writeErr(.FailedToDeleteSomeItems);
99
+
const failed = environment.deleteContents(dir) catch |err| lbl: {
100
+
message = try std.fmt.allocPrint(app.alloc, "Failed to empty trash - {}.", .{err});
101
+
app.notification.write(message.?, .err) catch {};
102
+
if (app.file_logger) |file_logger| file_logger.write(message.?, .err) catch {};
103
+
break :lbl 0;
104
+
};
105
+
if (failed > 0) {
106
+
message = try std.fmt.allocPrint(app.alloc, "Failed to empty {d} items from the trash.", .{failed});
107
+
app.notification.write(message.?, .err) catch {};
108
+
if (app.file_logger) |file_logger| file_logger.write(message.?, .err) catch {};
109
+
}
101
110
102
-
app.directories.clearEntries();
103
-
app.directories.populateEntries("") catch |err| {
104
-
switch (err) {
105
-
error.AccessDenied => try app.notification.writeErr(.PermissionDenied),
106
-
else => try app.notification.writeErr(.UnknownError),
107
-
}
108
-
};
111
+
try app.repopulateDirectory("");
109
112
}
110
113
111
114
///Change directory.
112
-
pub fn cd(app: *App, path: []const u8) !void {
115
+
pub fn cd(app: *App, path: []const u8) error{OutOfMemory}!void {
116
+
var message: ?[]const u8 = null;
117
+
defer if (message) |msg| app.alloc.free(msg);
118
+
113
119
var path_buf: [std.fs.max_path_bytes]u8 = undefined;
114
120
const resolved_path = lbl: {
115
121
const resolved_path = if (std.mem.startsWith(u8, path, "~")) path: {
···
125
131
break :lbl app.directories.dir.realpath(resolved_path, &path_buf) catch path;
126
132
};
127
133
128
-
if (app.directories.dir.openDir(resolved_path, .{ .iterate = true })) |dir| {
129
-
app.directories.dir.close();
130
-
app.directories.dir = dir;
131
-
132
-
try app.notification.writeInfo(.ChangedDir);
133
-
134
-
app.directories.clearEntries();
135
-
app.directories.populateEntries("") catch |err| {
136
-
switch (err) {
137
-
error.AccessDenied => try app.notification.writeErr(.PermissionDenied),
138
-
else => try app.notification.writeErr(.UnknownError),
139
-
}
134
+
const dir = app.directories.dir.openDir(resolved_path, .{ .iterate = true }) catch |err| {
135
+
message = switch (err) {
136
+
error.FileNotFound => try std.fmt.allocPrint(app.alloc, "Failed to navigate to '{s}' - directory does not exist.", .{resolved_path}),
137
+
error.NotDir => try std.fmt.allocPrint(app.alloc, "Failed to navigate to '{s}' - item is not a directory.", .{resolved_path}),
138
+
else => try std.fmt.allocPrint(app.alloc, "Failed to read directory entries - {}.", .{err}),
140
139
};
141
-
app.directories.history.reset();
142
-
} else |err| {
143
-
switch (err) {
144
-
error.AccessDenied => try app.notification.writeErr(.PermissionDenied),
145
-
error.FileNotFound => try app.notification.writeErr(.IncorrectPath),
146
-
error.NotDir => try app.notification.writeErr(.NotADir),
147
-
else => try app.notification.writeErr(.UnknownError),
148
-
}
149
-
}
150
-
}
140
+
app.notification.write(message.?, .err) catch {};
141
+
if (app.file_logger) |file_logger| file_logger.write(message.?, .err) catch {};
142
+
return;
143
+
};
144
+
app.directories.dir.close();
145
+
app.directories.dir = dir;
146
+
147
+
message = try std.fmt.allocPrint(app.alloc, "Navigated to directory '{s}'.", .{resolved_path});
148
+
app.notification.write(message.?, .info) catch {};
151
149
152
-
///Display help menu.
153
-
pub fn displayHelpMenu(app: *App) !void {
154
-
app.state = .help_menu;
150
+
try app.repopulateDirectory("");
151
+
app.directories.history.reset();
155
152
}
-2
src/config.zig
-2
src/config.zig
+109
-75
src/drawer.zig
+109
-75
src/drawer.zig
···
1
1
const std = @import("std");
2
2
const App = @import("./app.zig");
3
+
const FileLogger = @import("./file_logger.zig");
3
4
const Notification = @import("./notification.zig");
4
5
const Directories = @import("./directories.zig");
5
6
const config = &@import("./config.zig").config;
6
7
const vaxis = @import("vaxis");
7
8
const Git = @import("./git.zig");
8
9
const List = @import("./list.zig").List;
9
-
const inputToSlice = @import("./event_handlers.zig").inputToSlice;
10
10
const zeit = @import("zeit");
11
11
12
12
const Drawer = @This();
···
24
24
git_branch: [1024]u8 = undefined,
25
25
verbose: bool = false,
26
26
27
-
pub fn draw(self: *Drawer, app: *App) !void {
27
+
pub fn draw(self: *Drawer, app: *App) error{ OutOfMemory, NoSpaceLeft }!void {
28
28
const win = app.vx.window();
29
29
win.clear();
30
30
···
48
48
return;
49
49
}
50
50
51
-
const abs_file_path_bar = try self.drawAbsFilePath(app.alloc, &app.directories, win);
51
+
const abs_file_path_bar = try self.drawAbsFilePath(app, win);
52
52
const file_info_bar = try self.drawFileInfo(app.alloc, &app.directories, win);
53
-
app.last_known_height = try drawDirList(
53
+
app.last_known_height = drawDirList(
54
54
win,
55
55
app.directories.entries,
56
56
abs_file_path_bar,
···
62
62
try self.drawFilePreview(app, win, file_name_bar);
63
63
}
64
64
65
-
const input = inputToSlice(app);
66
-
try drawUserInput(app.state, &app.text_input, input, win);
65
+
const input = app.inputToSlice();
66
+
drawUserInput(app.state, &app.text_input, input, win);
67
67
68
68
// Notification should be drawn last.
69
-
try drawNotification(&app.notification, win);
69
+
drawNotification(&app.notification, &app.file_logger, win);
70
70
}
71
71
72
72
fn drawFileName(
73
73
self: *Drawer,
74
74
directories: *Directories,
75
75
win: vaxis.Window,
76
-
) !vaxis.Window {
76
+
) error{NoSpaceLeft}!vaxis.Window {
77
77
const file_name_bar = win.child(.{
78
78
.x_off = win.width / 2,
79
79
.y_off = 0,
···
81
81
.height = top_div,
82
82
});
83
83
84
-
lbl: {
85
-
const entry = directories.getSelected() catch break :lbl;
86
-
if (entry) |e| {
87
-
const file_name = try std.fmt.bufPrint(
88
-
&self.file_name_buf,
89
-
"[{s}]",
90
-
.{e.name},
91
-
);
92
-
_ = file_name_bar.print(&.{vaxis.Segment{
93
-
.text = file_name,
94
-
.style = config.styles.file_name,
95
-
}}, .{});
96
-
}
97
-
}
84
+
const entry = lbl: {
85
+
const entry = directories.getSelected() catch return file_name_bar;
86
+
if (entry) |e| break :lbl e else return file_name_bar;
87
+
};
88
+
89
+
const file_name = try std.fmt.bufPrint(&self.file_name_buf, "[{s}]", .{entry.name});
90
+
_ = file_name_bar.printSegment(.{ .text = file_name, .style = config.styles.file_name }, .{});
98
91
99
92
return file_name_bar;
100
93
}
···
104
97
app: *App,
105
98
win: vaxis.Window,
106
99
file_name_win: vaxis.Window,
107
-
) !void {
100
+
) error{ OutOfMemory, NoSpaceLeft }!void {
108
101
const bottom_div: u16 = 1;
109
102
110
103
const preview_win = win.child(.{
···
126
119
self.current_item_path = try std.fmt.bufPrint(
127
120
&self.current_item_path_buf,
128
121
"{s}/{s}",
129
-
.{ try app.directories.fullPath("."), entry.name },
122
+
.{ app.directories.fullPath(".") catch {
123
+
const message = try std.fmt.allocPrint(app.alloc, "Can not display file - unable to retrieve directory path.", .{});
124
+
defer app.alloc.free(message);
125
+
app.notification.write(message, .err) catch {};
126
+
if (app.file_logger) |file_logger| file_logger.write(message, .err) catch {};
127
+
128
+
_ = preview_win.print(&.{
129
+
.{ .text = "Can not display file - unable to retrieve directory path. No preview available." },
130
+
}, .{});
131
+
return;
132
+
}, entry.name },
130
133
);
131
134
132
135
switch (entry.kind) {
133
136
.directory => {
134
137
app.directories.clearChildEntries();
135
-
if (app.directories.populateChildEntries(entry.name)) {
136
-
for (app.directories.child_entries.all(), 0..) |item, i| {
137
-
if (std.mem.startsWith(u8, item, ".") and config.show_hidden == false) {
138
-
continue;
139
-
}
140
-
if (i > preview_win.height) continue;
141
-
const w = preview_win.child(.{ .y_off = @intCast(i), .height = 1 });
142
-
w.fill(vaxis.Cell{ .style = config.styles.list_item });
143
-
_ = w.print(&.{.{ .text = item, .style = config.styles.list_item }}, .{});
144
-
}
145
-
} else |err| {
146
-
switch (err) {
147
-
error.AccessDenied => try app.notification.writeErr(.PermissionDenied),
148
-
else => try app.notification.writeErr(.UnknownError),
138
+
app.directories.populateChildEntries(entry.name) catch |err| {
139
+
const message = try std.fmt.allocPrint(app.alloc, "Failed to populate child directory entries - {}.", .{err});
140
+
defer app.alloc.free(message);
141
+
app.notification.write(message, .err) catch {};
142
+
if (app.file_logger) |file_logger| file_logger.write(message, .err) catch {};
143
+
144
+
_ = preview_win.print(&.{
145
+
.{ .text = "Failed to populate child directory entries. No preview available." },
146
+
}, .{});
147
+
148
+
return;
149
+
};
150
+
151
+
for (app.directories.child_entries.all(), 0..) |item, i| {
152
+
if (std.mem.startsWith(u8, item, ".") and config.show_hidden == false) {
153
+
continue;
149
154
}
155
+
if (i > preview_win.height) continue;
156
+
const w = preview_win.child(.{ .y_off = @intCast(i), .height = 1 });
157
+
w.fill(vaxis.Cell{ .style = config.styles.list_item });
158
+
_ = w.print(&.{.{ .text = item, .style = config.styles.list_item }}, .{});
150
159
}
151
160
},
152
161
.file => file: {
···
154
163
entry.name,
155
164
.{ .mode = .read_only },
156
165
) catch |err| {
157
-
switch (err) {
158
-
error.AccessDenied => try app.notification.writeErr(.PermissionDenied),
159
-
else => try app.notification.writeErr(.UnknownError),
160
-
}
166
+
const message = try std.fmt.allocPrint(app.alloc, "Failed to open file - {}.", .{err});
167
+
defer app.alloc.free(message);
168
+
app.notification.write(message, .err) catch {};
169
+
if (app.file_logger) |file_logger| file_logger.write(message, .err) catch {};
161
170
162
171
_ = preview_win.print(&.{
163
-
.{ .text = "No preview available." },
172
+
.{ .text = "Failed to open file. No preview available." },
164
173
}, .{});
165
174
166
175
break :file;
167
176
};
168
177
defer file.close();
169
-
const bytes = try file.readAll(&app.directories.file_contents);
178
+
const bytes = file.readAll(&app.directories.file_contents) catch |err| {
179
+
const message = try std.fmt.allocPrint(app.alloc, "Failed to read file contents - {}.", .{err});
180
+
defer app.alloc.free(message);
181
+
app.notification.write(message, .err) catch {};
182
+
if (app.file_logger) |file_logger| file_logger.write(message, .err) catch {};
183
+
184
+
_ = preview_win.print(&.{
185
+
.{ .text = "Failed to read file contents. No preview available." },
186
+
}, .{});
187
+
188
+
break :file;
189
+
};
170
190
171
191
// Handle image.
172
192
if (config.show_images == true) unsupported: {
173
193
var match = false;
174
194
inline for (@typeInfo(vaxis.zigimg.Image.Format).@"enum".fields) |field| {
175
-
const entry_ext = std.mem.trimLeft(
176
-
u8,
177
-
std.fs.path.extension(entry.name),
178
-
".",
179
-
);
180
-
if (std.mem.eql(u8, entry_ext, field.name)) {
181
-
match = true;
182
-
}
195
+
const entry_ext = std.mem.trimLeft(u8, std.fs.path.extension(entry.name), ".");
196
+
if (std.mem.eql(u8, entry_ext, field.name)) match = true;
183
197
}
184
198
if (!match) break :unsupported;
185
199
···
204
218
}
205
219
206
220
if (app.image) |img| {
207
-
try img.draw(preview_win, .{ .scale = .contain });
221
+
img.draw(preview_win, .{ .scale = .contain }) catch |err| {
222
+
const message = try std.fmt.allocPrint(app.alloc, "Failed to draw image to screen - {}.", .{err});
223
+
defer app.alloc.free(message);
224
+
app.notification.write(message, .err) catch {};
225
+
if (app.file_logger) |file_logger| file_logger.write(message, .err) catch {};
226
+
227
+
_ = preview_win.print(&.{
228
+
.{ .text = "Failed to draw image to screen. No preview available." },
229
+
}, .{});
230
+
231
+
break :file;
232
+
};
208
233
}
209
234
210
235
break :file;
···
273
298
alloc: std.mem.Allocator,
274
299
directories: *Directories,
275
300
win: vaxis.Window,
276
-
) !vaxis.Window {
301
+
) error{NoSpaceLeft}!vaxis.Window {
277
302
const bottom_div: u16 = if (self.verbose) 6 else 1;
278
303
279
304
const file_info_win = win.child(.{
···
306
331
if (self.verbose) lbl: {
307
332
var maybe_meta: ?std.fs.File.Metadata = null;
308
333
if (entry.kind == .directory) {
309
-
maybe_meta = try directories.dir.metadata();
334
+
maybe_meta = directories.dir.metadata() catch break :lbl;
310
335
} else if (entry.kind == .file) {
311
-
var file = try directories.dir.openFile(entry.name, .{});
312
-
maybe_meta = try file.metadata();
336
+
var file = directories.dir.openFile(entry.name, .{}) catch break :lbl;
337
+
maybe_meta = file.metadata() catch break :lbl;
313
338
}
314
339
315
340
const meta = maybe_meta orelse break :lbl;
316
-
var env = try std.process.getEnvMap(alloc);
341
+
var env = std.process.getEnvMap(alloc) catch break :lbl;
317
342
defer env.deinit();
318
-
const local = try zeit.local(alloc, &env);
343
+
const local = zeit.local(alloc, &env) catch break :lbl;
319
344
defer local.deinit();
320
345
321
-
const ctime_instant = try zeit.instant(.{
346
+
const ctime_instant = zeit.instant(.{
322
347
.source = .{ .unix_nano = meta.created().? },
323
348
.timezone = &local,
324
-
});
349
+
}) catch break :lbl;
325
350
const ctime = ctime_instant.time();
326
-
try ctime.strftime(fbs.writer().any(), "Created: %Y-%m-%d %H:%M:%S\n");
351
+
ctime.strftime(fbs.writer().any(), "Created: %Y-%m-%d %H:%M:%S\n") catch break :lbl;
327
352
328
-
const mtime_instant = try zeit.instant(.{
353
+
const mtime_instant = zeit.instant(.{
329
354
.source = .{ .unix_nano = meta.modified() },
330
355
.timezone = &local,
331
-
});
356
+
}) catch break :lbl;
332
357
const mtime = mtime_instant.time();
333
-
try mtime.strftime(fbs.writer().any(), "Last modified: %Y-%m-%d %H:%M:%S\n");
358
+
mtime.strftime(fbs.writer().any(), "Last modified: %Y-%m-%d %H:%M:%S\n") catch break :lbl;
334
359
}
335
360
336
361
// File permissions.
···
427
452
list: List(std.fs.Dir.Entry),
428
453
abs_file_path: vaxis.Window,
429
454
file_information: vaxis.Window,
430
-
) !u16 {
455
+
) u16 {
431
456
const bottom_div: u16 = 1;
432
457
433
458
const current_dir_list_win = win.child(.{
···
470
495
471
496
fn drawAbsFilePath(
472
497
self: *Drawer,
473
-
alloc: std.mem.Allocator,
474
-
directories: *Directories,
498
+
app: *App,
475
499
win: vaxis.Window,
476
-
) !vaxis.Window {
500
+
) error{ OutOfMemory, NoSpaceLeft }!vaxis.Window {
477
501
const abs_file_path_bar = win.child(.{
478
502
.x_off = 0,
479
503
.y_off = 0,
···
481
505
.height = top_div,
482
506
});
483
507
484
-
const branch_alloc = try Git.getGitBranch(alloc, directories.dir);
485
-
defer if (branch_alloc) |b| alloc.free(b);
508
+
const branch_alloc = Git.getGitBranch(app.alloc, app.directories.dir) catch null;
509
+
defer if (branch_alloc) |b| app.alloc.free(b);
486
510
const branch = if (branch_alloc) |b|
487
511
try std.fmt.bufPrint(
488
512
&self.git_branch,
···
493
517
"";
494
518
495
519
_ = abs_file_path_bar.print(&.{
496
-
vaxis.Segment{ .text = try directories.fullPath(".") },
520
+
vaxis.Segment{ .text = app.directories.fullPath(".") catch {
521
+
const message = try std.fmt.allocPrint(app.alloc, "Can not display absolute file path - unable to retrieve full path.", .{});
522
+
defer app.alloc.free(message);
523
+
app.notification.write(message, .err) catch {};
524
+
if (app.file_logger) |file_logger| file_logger.write(message, .err) catch {};
525
+
return abs_file_path_bar;
526
+
} },
497
527
vaxis.Segment{ .text = if (branch_alloc != null) " on " else "" },
498
528
vaxis.Segment{ .text = branch, .style = config.styles.git_branch },
499
529
}, .{});
···
506
536
text_input: *vaxis.widgets.TextInput,
507
537
input: []const u8,
508
538
win: vaxis.Window,
509
-
) !void {
539
+
) void {
510
540
const user_input_win = win.child(.{
511
541
.x_off = 0,
512
542
.y_off = top_div,
···
540
570
541
571
fn drawNotification(
542
572
notification: *Notification,
573
+
file_logger: *?FileLogger,
543
574
win: vaxis.Window,
544
-
) !void {
575
+
) void {
545
576
if (notification.len() == 0) return;
546
577
if (notification.clearIfEnded()) return;
547
578
···
552
583
const max_width = win.width / 4;
553
584
const width = notification.len() + width_padding;
554
585
const calculated_width = if (width > max_width) max_width else width;
555
-
const height = try std.math.divCeil(usize, notification.len(), calculated_width) + height_padding;
586
+
const height = (std.math.divCeil(usize, notification.len(), calculated_width) catch {
587
+
if (file_logger.*) |fl| fl.write("Unable to display notification - failed to calculate notification height.", .err) catch {};
588
+
return;
589
+
}) + height_padding;
556
590
557
591
const notification_win = win.child(.{
558
592
.x_off = @intCast(win.width - (calculated_width + screen_pos_padding)),
+1
-1
src/environment.zig
+1
-1
src/environment.zig
+64
-556
src/event_handlers.zig
+64
-556
src/event_handlers.zig
···
7
7
const config = &@import("./config.zig").config;
8
8
const commands = @import("./commands.zig");
9
9
const Keybinds = @import("./config.zig").Keybinds;
10
-
11
-
pub fn inputToSlice(self: *App) []const u8 {
12
-
self.text_input.buf.cursor = self.text_input.buf.realLength();
13
-
return self.text_input.sliceToCursor(&self.text_input_buf);
14
-
}
10
+
const events = @import("./events.zig");
15
11
16
12
pub fn handleNormalEvent(
17
13
app: *App,
···
37
33
38
34
if (maybe_remap) |action| {
39
35
switch (action) {
40
-
.toggle_hidden_files => {
41
-
config.show_hidden = !config.show_hidden;
42
-
43
-
const prev_selected_name: []const u8, const prev_selected_err: bool = lbl: {
44
-
const selected = app.directories.getSelected() catch break :lbl .{ "", true };
45
-
if (selected == null) break :lbl .{ "", true };
36
+
.toggle_hidden_files => try events.toggleHiddenFiles(app),
37
+
.delete => try events.delete(app),
38
+
.rename => {
39
+
const entry = (app.directories.getSelected() catch {
40
+
app.notification.write("Can not rename item - no item selected.", .warn) catch {};
41
+
return;
42
+
}) orelse return;
46
43
47
-
break :lbl .{ try app.alloc.dupe(u8, selected.?.name), false };
48
-
};
49
-
defer if (!prev_selected_err) app.alloc.free(prev_selected_name);
50
-
51
-
app.directories.clearEntries();
52
44
app.text_input.clearAndFree();
53
-
app.directories.populateEntries("") catch |err| {
54
-
switch (err) {
55
-
error.AccessDenied => try app.notification.writeErr(.PermissionDenied),
56
-
else => try app.notification.writeErr(.UnknownError),
57
-
}
58
-
};
59
45
60
-
for (app.directories.entries.all()) |entry| {
61
-
if (std.mem.eql(u8, entry.name, prev_selected_name)) return;
62
-
app.directories.entries.selected += 1;
63
-
}
64
-
65
-
// If it didn't find entry, reset selected.
66
-
app.directories.entries.selected = 0;
46
+
// Try insert entry name into text input for a nicer experience.
47
+
// This failing shouldn't stop the user from entering a new name.
48
+
app.text_input.insertSliceAtCursor(entry.name) catch {};
49
+
app.state = .rename;
67
50
},
68
-
.delete => {
69
-
const entry = lbl: {
70
-
const entry = app.directories.getSelected() catch {
71
-
try app.notification.writeErr(.UnableToDelete);
72
-
return;
73
-
};
74
-
if (entry) |e| break :lbl e else return;
75
-
};
76
51
77
-
var prev_path_buf: [std.fs.max_path_bytes]u8 = undefined;
78
-
const prev_path = try app.alloc.dupe(u8, try app.directories.dir.realpath(entry.name, &prev_path_buf));
79
-
80
-
var trash_dir = dir: {
81
-
notfound: {
82
-
break :dir (config.trashDir() catch break :notfound) orelse break :notfound;
83
-
}
84
-
app.alloc.free(prev_path);
85
-
try app.notification.writeErr(.ConfigPathNotFound);
86
-
return;
87
-
};
88
-
defer trash_dir.close();
89
-
var trash_dir_path_buf: [std.fs.max_path_bytes]u8 = undefined;
90
-
const trash_dir_path = try trash_dir.realpath(".", &trash_dir_path_buf);
91
-
92
-
if (std.mem.eql(u8, prev_path, trash_dir_path)) {
93
-
try app.notification.writeErr(.CannotDeleteTrashDir);
94
-
app.alloc.free(prev_path);
95
-
return;
96
-
}
97
-
98
-
var tmp_path_buf: [std.fs.max_path_bytes]u8 = undefined;
99
-
const tmp_path = try app.alloc.dupe(u8, try std.fmt.bufPrint(&tmp_path_buf, "{s}/{s}-{s}", .{ trash_dir_path, entry.name, zuid.new.v4() }));
100
-
101
-
if (app.directories.dir.rename(entry.name, tmp_path)) {
102
-
if (app.actions.push(.{
103
-
.delete = .{ .prev_path = prev_path, .new_path = tmp_path },
104
-
})) |prev_elem| {
105
-
app.alloc.free(prev_elem.delete.prev_path);
106
-
app.alloc.free(prev_elem.delete.new_path);
107
-
}
108
-
109
-
try app.notification.writeInfo(.Deleted);
110
-
app.directories.removeSelected();
111
-
} else |err| {
112
-
switch (err) {
113
-
error.RenameAcrossMountPoints => try app.notification.writeErr(.UnableToDeleteAcrossMountPoints),
114
-
else => try app.notification.writeErr(.UnableToDelete),
115
-
}
116
-
app.alloc.free(prev_path);
117
-
app.alloc.free(tmp_path);
118
-
}
119
-
},
120
-
.rename => {
121
-
app.text_input.clearAndFree();
122
-
app.state = .rename;
123
-
124
-
const entry = lbl: {
125
-
const entry = app.directories.getSelected() catch {
126
-
app.state = .normal;
127
-
try app.notification.writeErr(.UnableToRename);
128
-
return;
129
-
};
130
-
if (entry) |e| break :lbl e else {
131
-
app.state = .normal;
132
-
return;
133
-
}
134
-
};
135
-
136
-
app.text_input.insertSliceAtCursor(entry.name) catch {
137
-
app.state = .normal;
138
-
try app.notification.writeErr(.UnableToRename);
139
-
return;
140
-
};
141
-
},
142
52
.create_dir => {
53
+
try app.repopulateDirectory("");
143
54
app.text_input.clearAndFree();
144
-
app.directories.clearEntries();
145
-
app.directories.populateEntries("") catch |err| {
146
-
switch (err) {
147
-
error.AccessDenied => try app.notification.writeErr(.PermissionDenied),
148
-
else => try app.notification.writeErr(.UnknownError),
149
-
}
150
-
};
151
55
app.state = .new_dir;
152
56
},
153
57
.create_file => {
58
+
try app.repopulateDirectory("");
154
59
app.text_input.clearAndFree();
155
-
app.directories.clearEntries();
156
-
app.directories.populateEntries("") catch |err| {
157
-
switch (err) {
158
-
error.AccessDenied => try app.notification.writeErr(.PermissionDenied),
159
-
else => try app.notification.writeErr(.UnknownError),
160
-
}
161
-
};
162
60
app.state = .new_file;
163
61
},
164
62
.fuzzy_find => {
···
177
75
.jump_bottom => app.directories.entries.selectLast(),
178
76
.jump_top => app.directories.entries.selectFirst(),
179
77
.toggle_verbose_file_information => app.drawer.verbose = !app.drawer.verbose,
180
-
.force_delete => {
181
-
const entry = lbl: {
182
-
const entry = app.directories.getSelected() catch {
183
-
try app.notification.writeErr(.UnableToDelete);
184
-
return;
185
-
};
186
-
if (entry) |e| break :lbl e else return;
187
-
};
188
-
189
-
app.directories.dir.deleteTree(entry.name) catch {
190
-
try app.notification.writeErr(.UnableToDelete);
191
-
return;
192
-
};
193
-
app.directories.removeSelected();
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
-
if (app.file_logger) |file_logger| file_logger.write(message, .err) catch {};
219
-
break :lbl null;
220
-
}),
221
-
.entry = .{
222
-
.kind = entry.kind,
223
-
.name = try app.alloc.dupe(u8, entry.name),
224
-
},
225
-
};
226
-
},
227
-
else => {
228
-
const message = try std.fmt.allocPrint(
229
-
app.alloc,
230
-
"Failed to yank '{s}' - unsupported file type '{s}'.",
231
-
.{ entry.name, @tagName(entry.kind) },
232
-
);
233
-
defer app.alloc.free(message);
234
-
app.notification.write(message, .warn) catch {};
235
-
break :lbl null;
236
-
},
237
-
}
238
-
};
239
-
if (app.yanked) |y| {
240
-
const message = try std.fmt.allocPrint(app.alloc, "Yanked '{s}'.", .{y.entry.name});
241
-
defer app.alloc.free(message);
242
-
app.notification.write(message, .info) catch {};
243
-
}
244
-
},
245
-
.paste => {
246
-
const yanked = if (app.yanked) |y| y else return;
247
-
248
-
var new_path_buf: [std.fs.max_path_bytes]u8 = undefined;
249
-
const new_path_res = environment.checkDuplicatePath(&new_path_buf, app.directories.dir, yanked.entry.name) catch {
250
-
const message = try std.fmt.allocPrint(app.alloc, "Failed to copy '{s}' - path too long.", .{yanked.entry.name});
251
-
defer app.alloc.free(message);
252
-
app.notification.write(message, .err) catch {};
253
-
return;
254
-
};
255
-
256
-
switch (yanked.entry.kind) {
257
-
.file => {
258
-
var source_dir = std.fs.openDirAbsolute(yanked.dir, .{ .iterate = true }) catch {
259
-
const message = try std.fmt.allocPrint(app.alloc, "Failed to copy '{s}' - unable to open directory '{s}'.", .{ yanked.entry.name, yanked.dir });
260
-
defer app.alloc.free(message);
261
-
app.notification.write(message, .err) catch {};
262
-
if (app.file_logger) |file_logger| file_logger.write(message, .err) catch {};
263
-
return;
264
-
};
265
-
defer source_dir.close();
266
-
std.fs.Dir.copyFile(
267
-
source_dir,
268
-
yanked.entry.name,
269
-
app.directories.dir,
270
-
new_path_res.path,
271
-
.{},
272
-
) catch |err| switch (err) {
273
-
error.FileNotFound => {
274
-
const message = try std.fmt.allocPrint(app.alloc, "Failed to copy '{s}' - the original file was deleted or moved.", .{yanked.entry.name});
275
-
defer app.alloc.free(message);
276
-
app.notification.write(message, .err) catch {};
277
-
if (app.file_logger) |file_logger| file_logger.write(message, .err) catch {};
278
-
return;
279
-
},
280
-
else => {
281
-
const message = try std.fmt.allocPrint(app.alloc, "Failed to copy '{s}' - {}.", .{ yanked.entry.name, err });
282
-
defer app.alloc.free(message);
283
-
app.notification.write(message, .err) catch {};
284
-
if (app.file_logger) |file_logger| file_logger.write(message, .err) catch {};
285
-
return;
286
-
},
287
-
};
288
-
289
-
// Append action to undo history.
290
-
{
291
-
var new_path_abs_buf: [std.fs.max_path_bytes]u8 = undefined;
292
-
const new_path_abs = app.directories.dir.realpath(new_path_res.path, &new_path_abs_buf) catch {
293
-
const message = try std.fmt.allocPrint(
294
-
app.alloc,
295
-
"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.",
296
-
.{ new_path_res.path, yanked.entry.name },
297
-
);
298
-
defer app.alloc.free(message);
299
-
app.notification.write(message, .err) catch {};
300
-
if (app.file_logger) |file_logger| file_logger.write(message, .err) catch {};
301
-
return;
302
-
};
303
-
304
-
if (app.actions.push(.{
305
-
.paste = try app.alloc.dupe(u8, new_path_abs),
306
-
})) |prev_elem| {
307
-
app.alloc.free(prev_elem.delete.prev_path);
308
-
app.alloc.free(prev_elem.delete.new_path);
309
-
}
310
-
}
311
-
312
-
const message = try std.fmt.allocPrint(app.alloc, "Copied '{s}'.", .{yanked.entry.name});
313
-
defer app.alloc.free(message);
314
-
app.notification.write(message, .info) catch {};
315
-
},
316
-
else => {
317
-
const message = try std.fmt.allocPrint(
318
-
app.alloc,
319
-
"Failed to copy '{s}' - unsupported file type '{s}'.",
320
-
.{ yanked.entry.name, @tagName(yanked.entry.kind) },
321
-
);
322
-
defer app.alloc.free(message);
323
-
app.notification.write(message, .warn) catch {};
324
-
},
325
-
}
326
-
327
-
app.directories.clearEntries();
328
-
app.directories.populateEntries("") catch |err| {
329
-
switch (err) {
330
-
error.AccessDenied => app.notification.writeErr(.PermissionDenied) catch {},
331
-
else => app.notification.writeErr(.UnknownError) catch {},
332
-
}
333
-
};
334
-
},
78
+
.force_delete => try events.forceDelete(app),
79
+
.yank => try events.yank(app),
80
+
.paste => try events.paste(app),
335
81
}
336
82
} else {
337
83
switch (key.codepoint) {
338
-
'-', 'h', Key.left => {
339
-
app.text_input.clearAndFree();
340
-
341
-
if (app.directories.dir.openDir("../", .{ .iterate = true })) |dir| {
342
-
app.directories.dir.close();
343
-
app.directories.dir = dir;
344
-
345
-
app.directories.clearEntries();
346
-
const fuzzy = inputToSlice(app);
347
-
app.directories.populateEntries(fuzzy) catch |err| {
348
-
switch (err) {
349
-
error.AccessDenied => try app.notification.writeErr(.PermissionDenied),
350
-
else => try app.notification.writeErr(.UnknownError),
351
-
}
352
-
};
353
-
354
-
if (app.directories.history.pop()) |history| {
355
-
if (history < app.directories.entries.len()) {
356
-
app.directories.entries.selected = history;
357
-
}
358
-
}
359
-
} else |err| {
360
-
switch (err) {
361
-
error.AccessDenied => try app.notification.writeErr(.PermissionDenied),
362
-
else => try app.notification.writeErr(.UnknownError),
363
-
}
364
-
}
365
-
},
366
-
Key.enter, 'l', Key.right => {
367
-
const entry = lbl: {
368
-
const entry = app.directories.getSelected() catch return;
369
-
if (entry) |e| break :lbl e else return;
370
-
};
371
-
372
-
switch (entry.kind) {
373
-
.directory => {
374
-
app.text_input.clearAndFree();
375
-
376
-
if (app.directories.dir.openDir(entry.name, .{ .iterate = true })) |dir| {
377
-
app.directories.dir.close();
378
-
app.directories.dir = dir;
379
-
380
-
_ = app.directories.history.push(app.directories.entries.selected);
381
-
382
-
app.directories.clearEntries();
383
-
const fuzzy = inputToSlice(app);
384
-
app.directories.populateEntries(fuzzy) catch |err| {
385
-
switch (err) {
386
-
error.AccessDenied => try app.notification.writeErr(.PermissionDenied),
387
-
else => try app.notification.writeErr(.UnknownError),
388
-
}
389
-
};
390
-
} else |err| {
391
-
switch (err) {
392
-
error.AccessDenied => try app.notification.writeErr(.PermissionDenied),
393
-
else => try app.notification.writeErr(.UnknownError),
394
-
}
395
-
}
396
-
},
397
-
.file => {
398
-
if (environment.getEditor()) |editor| {
399
-
try app.vx.exitAltScreen(app.tty.anyWriter());
400
-
try app.vx.resetState(app.tty.anyWriter());
401
-
loop.stop();
402
-
403
-
environment.openFile(app.alloc, app.directories.dir, entry.name, editor) catch {
404
-
try app.notification.writeErr(.UnableToOpenFile);
405
-
};
406
-
407
-
try loop.start();
408
-
try app.vx.enterAltScreen(app.tty.anyWriter());
409
-
try app.vx.enableDetectedFeatures(app.tty.anyWriter());
410
-
app.vx.queueRefresh();
411
-
} else {
412
-
try app.notification.writeErr(.EditorNotSet);
413
-
}
414
-
},
415
-
else => {},
416
-
}
417
-
},
418
-
'j', Key.down => {
419
-
app.directories.entries.next();
420
-
},
421
-
'k', Key.up => {
422
-
app.directories.entries.previous();
423
-
},
424
-
'u' => {
425
-
if (app.actions.pop()) |action| {
426
-
const selected = app.directories.entries.selected;
427
-
428
-
switch (action) {
429
-
.delete => |a| {
430
-
defer app.alloc.free(a.new_path);
431
-
defer app.alloc.free(a.prev_path);
432
-
433
-
var new_path_buf: [std.fs.max_path_bytes]u8 = undefined;
434
-
const new_path_res = try environment.checkDuplicatePath(&new_path_buf, app.directories.dir, a.prev_path);
435
-
436
-
if (app.directories.dir.rename(a.new_path, new_path_res.path)) {
437
-
app.directories.clearEntries();
438
-
app.directories.populateEntries("") catch |err| {
439
-
switch (err) {
440
-
error.AccessDenied => try app.notification.writeErr(.PermissionDenied),
441
-
else => try app.notification.writeErr(.UnknownError),
442
-
}
443
-
};
444
-
if (new_path_res.had_duplicate) {
445
-
try app.notification.writeWarn(.DuplicateFileOnUndo);
446
-
} else {
447
-
try app.notification.writeInfo(.RestoredDelete);
448
-
}
449
-
} else |_| {
450
-
try app.notification.writeErr(.UnableToUndo);
451
-
}
452
-
},
453
-
.rename => |a| {
454
-
defer app.alloc.free(a.new_path);
455
-
defer app.alloc.free(a.prev_path);
456
-
457
-
var new_path_buf: [std.fs.max_path_bytes]u8 = undefined;
458
-
const new_path_res = try environment.checkDuplicatePath(&new_path_buf, app.directories.dir, a.prev_path);
459
-
460
-
if (app.directories.dir.rename(a.new_path, new_path_res.path)) {
461
-
app.directories.clearEntries();
462
-
app.directories.populateEntries("") catch |err| {
463
-
switch (err) {
464
-
error.AccessDenied => try app.notification.writeErr(.PermissionDenied),
465
-
else => try app.notification.writeErr(.UnknownError),
466
-
}
467
-
};
468
-
if (new_path_res.had_duplicate) {
469
-
try app.notification.writeWarn(.DuplicateFileOnUndo);
470
-
} else {
471
-
try app.notification.writeInfo(.RestoredRename);
472
-
}
473
-
} else |_| {
474
-
try app.notification.writeErr(.UnableToUndo);
475
-
}
476
-
},
477
-
.paste => |path| {
478
-
defer app.alloc.free(path);
479
-
480
-
app.directories.dir.deleteTree(path) catch {
481
-
try app.notification.writeErr(.UnableToUndo);
482
-
return;
483
-
};
484
-
485
-
app.directories.clearEntries();
486
-
app.directories.populateEntries("") catch |err| {
487
-
switch (err) {
488
-
error.AccessDenied => try app.notification.writeErr(.PermissionDenied),
489
-
else => try app.notification.writeErr(.UnknownError),
490
-
}
491
-
};
492
-
},
493
-
}
494
-
495
-
app.directories.entries.selected = selected;
496
-
} else {
497
-
try app.notification.writeInfo(.EmptyUndo);
498
-
}
499
-
},
84
+
'-', 'h', Key.left => try events.traverseLeft(app),
85
+
Key.enter, 'l', Key.right => try events.traverseRight(app, loop),
86
+
'j', Key.down => app.directories.entries.next(),
87
+
'k', Key.up => app.directories.entries.previous(),
88
+
'u' => try events.undo(app),
500
89
else => {},
501
90
}
502
91
}
···
512
101
Key.escape => {
513
102
switch (app.state) {
514
103
.fuzzy => {
515
-
app.directories.clearEntries();
516
-
app.directories.populateEntries("") catch |err| {
517
-
switch (err) {
518
-
error.AccessDenied => try app.notification.writeErr(.PermissionDenied),
519
-
else => try app.notification.writeErr(.UnknownError),
520
-
}
521
-
};
104
+
try app.repopulateDirectory("");
105
+
app.text_input.clearAndFree();
522
106
},
523
107
.command => app.command_history.resetSelected(),
524
108
else => {},
···
530
114
Key.enter => {
531
115
const selected = app.directories.entries.selected;
532
116
switch (app.state) {
533
-
.new_dir => {
534
-
const dir = inputToSlice(app);
535
-
if (app.directories.dir.makeDir(dir)) {
536
-
try app.notification.writeInfo(.CreatedFolder);
537
-
538
-
app.directories.clearEntries();
539
-
app.directories.populateEntries("") catch |err| {
540
-
switch (err) {
541
-
error.AccessDenied => try app.notification.writeErr(.PermissionDenied),
542
-
else => try app.notification.writeErr(.UnknownError),
543
-
}
544
-
};
545
-
} else |err| {
546
-
switch (err) {
547
-
error.AccessDenied => try app.notification.writeErr(.PermissionDenied),
548
-
error.PathAlreadyExists => try app.notification.writeErr(.ItemAlreadyExists),
549
-
else => try app.notification.writeErr(.UnknownError),
550
-
}
551
-
}
552
-
app.text_input.clearAndFree();
553
-
},
554
-
.new_file => {
555
-
const file = inputToSlice(app);
556
-
if (environment.fileExists(app.directories.dir, file)) {
557
-
try app.notification.writeErr(.ItemAlreadyExists);
558
-
} else {
559
-
if (app.directories.dir.createFile(file, .{})) |f| {
560
-
f.close();
561
-
562
-
try app.notification.writeInfo(.CreatedFile);
563
-
564
-
app.directories.clearEntries();
565
-
app.directories.populateEntries("") catch |err| {
566
-
switch (err) {
567
-
error.AccessDenied => try app.notification.writeErr(.PermissionDenied),
568
-
else => try app.notification.writeErr(.UnknownError),
569
-
}
570
-
};
571
-
} else |err| {
572
-
switch (err) {
573
-
error.AccessDenied => try app.notification.writeErr(.PermissionDenied),
574
-
else => try app.notification.writeErr(.UnknownError),
575
-
}
576
-
}
577
-
}
578
-
app.text_input.clearAndFree();
579
-
},
580
-
.rename => {
581
-
var dir_prefix_buf: [std.fs.max_path_bytes]u8 = undefined;
582
-
const dir_prefix = try app.directories.dir.realpath(".", &dir_prefix_buf);
583
-
584
-
const entry = lbl: {
585
-
const entry = app.directories.getSelected() catch {
586
-
try app.notification.writeErr(.UnableToRename);
587
-
return;
588
-
};
589
-
if (entry) |e| break :lbl e else return;
590
-
};
591
-
const new_path = inputToSlice(app);
592
-
593
-
if (environment.fileExists(app.directories.dir, new_path)) {
594
-
try app.notification.writeErr(.ItemAlreadyExists);
595
-
} else {
596
-
app.directories.dir.rename(entry.name, new_path) catch |err| switch (err) {
597
-
error.AccessDenied => try app.notification.writeErr(.PermissionDenied),
598
-
error.PathAlreadyExists => try app.notification.writeErr(.ItemAlreadyExists),
599
-
else => try app.notification.writeErr(.UnknownError),
600
-
};
601
-
if (app.actions.push(.{
602
-
.rename = .{
603
-
.prev_path = try std.fs.path.join(app.alloc, &.{ dir_prefix, entry.name }),
604
-
.new_path = try std.fs.path.join(app.alloc, &.{ dir_prefix, new_path }),
605
-
},
606
-
})) |prev_elem| {
607
-
app.alloc.free(prev_elem.rename.prev_path);
608
-
app.alloc.free(prev_elem.rename.new_path);
609
-
}
610
-
611
-
try app.notification.writeInfo(.Renamed);
612
-
613
-
app.directories.clearEntries();
614
-
app.directories.populateEntries("") catch |err| {
615
-
switch (err) {
616
-
error.AccessDenied => try app.notification.writeErr(.PermissionDenied),
617
-
else => try app.notification.writeErr(.UnknownError),
618
-
}
619
-
};
620
-
}
621
-
app.text_input.clearAndFree();
622
-
},
117
+
.new_dir => try events.createNewDir(app),
118
+
.new_file => try events.createNewFile(app),
119
+
.rename => try events.rename(app),
623
120
.change_dir => {
624
-
const path = inputToSlice(app);
121
+
const path = app.inputToSlice();
625
122
try commands.cd(app, path);
626
123
app.text_input.clearAndFree();
627
124
},
628
125
.command => {
629
-
const command = inputToSlice(app);
126
+
const command = app.inputToSlice();
127
+
128
+
// Push command to history if it's not empty.
630
129
if (!std.mem.eql(u8, std.mem.trim(u8, command, " "), ":")) {
631
130
if (app.command_history.push(try app.alloc.dupe(u8, command))) |deleted| {
632
131
app.alloc.free(deleted);
···
650
149
}
651
150
652
151
if (std.mem.startsWith(u8, command, ":cd ")) {
653
-
const path = std.mem.trim(u8, command, ":cd \n\r");
654
-
try commands.cd(app, path);
152
+
try commands.cd(app, command[":cd ".len..]);
655
153
break :supported;
656
154
}
657
155
···
661
159
}
662
160
663
161
if (std.mem.eql(u8, command, ":h")) {
664
-
try commands.displayHelpMenu(app);
162
+
app.state = .help_menu;
665
163
break :supported;
666
164
}
667
165
···
674
172
else => {},
675
173
}
676
174
677
-
// TODO(2025-03-29): Could there be a better way to check
678
-
// this?
679
175
if (app.state != .help_menu) app.state = .normal;
680
176
app.directories.entries.selected = selected;
681
177
},
···
685
181
if (app.state == .command) {
686
182
if (app.command_history.next()) |command| {
687
183
app.text_input.clearAndFree();
688
-
try app.text_input.insertSliceAtCursor(command);
184
+
app.text_input.insertSliceAtCursor(command) catch |err| {
185
+
const message = try std.fmt.allocPrint(app.alloc, "Failed to get previous command history - {}.", .{err});
186
+
defer app.alloc.free(message);
187
+
app.notification.write(message, .err) catch {};
188
+
if (app.file_logger) |file_logger| file_logger.write(message, .err) catch {};
189
+
};
689
190
}
690
191
}
691
192
},
···
693
194
if (app.state == .command) {
694
195
app.text_input.clearAndFree();
695
196
if (app.command_history.previous()) |command| {
696
-
try app.text_input.insertSliceAtCursor(command);
197
+
app.text_input.insertSliceAtCursor(command) catch |err| {
198
+
const message = try std.fmt.allocPrint(app.alloc, "Failed to get next command history - {}.", .{err});
199
+
defer app.alloc.free(message);
200
+
app.notification.write(message, .err) catch {};
201
+
if (app.file_logger) |file_logger| file_logger.write(message, .err) catch {};
202
+
};
697
203
} else {
698
-
try app.text_input.insertSliceAtCursor(":");
204
+
app.text_input.insertSliceAtCursor(":") catch |err| {
205
+
const message = try std.fmt.allocPrint(app.alloc, "Failed to get next command history - {}.", .{err});
206
+
defer app.alloc.free(message);
207
+
app.notification.write(message, .err) catch {};
208
+
if (app.file_logger) |file_logger| file_logger.write(message, .err) catch {};
209
+
};
699
210
}
700
211
}
701
212
},
···
704
215
705
216
switch (app.state) {
706
217
.fuzzy => {
707
-
app.directories.clearEntries();
708
-
const fuzzy = inputToSlice(app);
709
-
app.directories.populateEntries(fuzzy) catch |err| {
710
-
switch (err) {
711
-
error.AccessDenied => try app.notification.writeErr(.PermissionDenied),
712
-
else => try app.notification.writeErr(.UnknownError),
713
-
}
714
-
};
218
+
const fuzzy = app.inputToSlice();
219
+
try app.repopulateDirectory(fuzzy);
715
220
},
716
221
.command => {
717
-
const command = inputToSlice(app);
222
+
const command = app.inputToSlice();
718
223
if (!std.mem.startsWith(u8, command, ":")) {
719
224
app.text_input.clearAndFree();
720
-
try app.text_input.insertSliceAtCursor(":");
225
+
app.text_input.insertSliceAtCursor(":") catch |err| {
226
+
app.state = .normal;
227
+
228
+
const message = try std.fmt.allocPrint(app.alloc, "An input error occurred while attempting to enter a command - {}.", .{err});
229
+
defer app.alloc.free(message);
230
+
app.notification.write(message, .err) catch {};
231
+
if (app.file_logger) |file_logger| file_logger.write(message, .err) catch {};
232
+
};
721
233
}
722
234
},
723
235
else => {},
···
734
246
.key_press => |key| {
735
247
switch (key.codepoint) {
736
248
Key.escape, 'q' => app.state = .normal,
737
-
'j', Key.down => {
738
-
app.help_menu.next();
739
-
},
740
-
'k', Key.up => {
741
-
app.help_menu.previous();
742
-
},
249
+
'j', Key.down => app.help_menu.next(),
250
+
'k', Key.up => app.help_menu.previous(),
743
251
else => {},
744
252
}
745
253
},
+488
src/events.zig
+488
src/events.zig
···
1
+
const std = @import("std");
2
+
const App = @import("./app.zig");
3
+
const config = &@import("./config.zig").config;
4
+
const zuid = @import("zuid");
5
+
const environment = @import("./environment.zig");
6
+
const vaxis = @import("vaxis");
7
+
8
+
pub fn delete(app: *App) error{OutOfMemory}!void {
9
+
var message: ?[]const u8 = null;
10
+
defer if (message) |msg| app.alloc.free(msg);
11
+
12
+
const entry = (app.directories.getSelected() catch {
13
+
app.notification.write("Can not to delete item - no item selected.", .warn) catch {};
14
+
return;
15
+
}) orelse return;
16
+
17
+
var prev_path_buf: [std.fs.max_path_bytes]u8 = undefined;
18
+
const prev_path = app.directories.dir.realpath(entry.name, &prev_path_buf) catch {
19
+
message = try std.fmt.allocPrint(app.alloc, "Failed to delete '{s}' - unable to retrieve absolute path.", .{entry.name});
20
+
app.notification.write(message.?, .err) catch {};
21
+
if (app.file_logger) |file_logger| file_logger.write(message.?, .err) catch {};
22
+
return;
23
+
};
24
+
const prev_path_alloc = try app.alloc.dupe(u8, prev_path);
25
+
26
+
var trash_dir = dir: {
27
+
notfound: {
28
+
break :dir (config.trashDir() catch break :notfound) orelse break :notfound;
29
+
}
30
+
app.alloc.free(prev_path_alloc);
31
+
message = try std.fmt.allocPrint(app.alloc, "Failed to delete '{s}' - unable to retrieve trash directory.", .{entry.name});
32
+
app.notification.write(message.?, .err) catch {};
33
+
if (app.file_logger) |file_logger| file_logger.write(message.?, .err) catch {};
34
+
return;
35
+
};
36
+
defer trash_dir.close();
37
+
38
+
var trash_dir_path_buf: [std.fs.max_path_bytes]u8 = undefined;
39
+
const trash_dir_path = trash_dir.realpath(".", &trash_dir_path_buf) catch {
40
+
message = try std.fmt.allocPrint(app.alloc, "Failed to delete '{s}' - unable to retrieve absolute path for trash directory.", .{entry.name});
41
+
app.notification.write(message.?, .err) catch {};
42
+
if (app.file_logger) |file_logger| file_logger.write(message.?, .err) catch {};
43
+
return;
44
+
};
45
+
46
+
if (std.mem.eql(u8, prev_path_alloc, trash_dir_path)) {
47
+
app.notification.write("Can not delete trash directory.", .warn) catch {};
48
+
app.alloc.free(prev_path_alloc);
49
+
return;
50
+
}
51
+
52
+
const tmp_path = try std.fmt.allocPrint(app.alloc, "{s}/{s}-{s}", .{ trash_dir_path, entry.name, zuid.new.v4() });
53
+
if (app.directories.dir.rename(entry.name, tmp_path)) {
54
+
if (app.actions.push(.{
55
+
.delete = .{ .prev_path = prev_path_alloc, .new_path = tmp_path },
56
+
})) |prev_elem| {
57
+
app.alloc.free(prev_elem.delete.prev_path);
58
+
app.alloc.free(prev_elem.delete.new_path);
59
+
}
60
+
message = try std.fmt.allocPrint(app.alloc, "Deleted '{s}'.", .{entry.name});
61
+
app.notification.write(message.?, .info) catch {};
62
+
63
+
app.directories.removeSelected();
64
+
} else |err| {
65
+
app.alloc.free(prev_path_alloc);
66
+
app.alloc.free(tmp_path);
67
+
68
+
message = try std.fmt.allocPrint(app.alloc, "Failed to delete '{s}' - {}.", .{ entry.name, err });
69
+
app.notification.write(message.?, .err) catch {};
70
+
}
71
+
}
72
+
73
+
pub fn rename(app: *App) error{OutOfMemory}!void {
74
+
var message: ?[]const u8 = null;
75
+
defer if (message) |msg| app.alloc.free(msg);
76
+
77
+
const entry = (app.directories.getSelected() catch {
78
+
app.notification.write("Can not to rename item - no item selected.", .warn) catch {};
79
+
return;
80
+
}) orelse return;
81
+
82
+
var dir_prefix_buf: [std.fs.max_path_bytes]u8 = undefined;
83
+
const dir_prefix = app.directories.dir.realpath(".", &dir_prefix_buf) catch {
84
+
message = try std.fmt.allocPrint(app.alloc, "Failed to rename '{s}' - unable to retrieve absolute path.", .{entry.name});
85
+
app.notification.write(message.?, .err) catch {};
86
+
if (app.file_logger) |file_logger| file_logger.write(message.?, .err) catch {};
87
+
return;
88
+
};
89
+
90
+
const new_path = app.inputToSlice();
91
+
92
+
if (environment.fileExists(app.directories.dir, new_path)) {
93
+
message = try std.fmt.allocPrint(app.alloc, "Can not rename file - '{s}' already exists.", .{new_path});
94
+
app.notification.write(message.?, .warn) catch {};
95
+
} else {
96
+
app.directories.dir.rename(entry.name, new_path) catch |err| {
97
+
message = try std.fmt.allocPrint(app.alloc, "Failed to rename '{s}' - {}.", .{ new_path, err });
98
+
app.notification.write(message.?, .err) catch {};
99
+
if (app.file_logger) |file_logger| file_logger.write(message.?, .err) catch {};
100
+
return;
101
+
};
102
+
103
+
if (app.actions.push(.{
104
+
.rename = .{
105
+
.prev_path = try std.fs.path.join(app.alloc, &.{ dir_prefix, entry.name }),
106
+
.new_path = try std.fs.path.join(app.alloc, &.{ dir_prefix, new_path }),
107
+
},
108
+
})) |prev_elem| {
109
+
app.alloc.free(prev_elem.rename.prev_path);
110
+
app.alloc.free(prev_elem.rename.new_path);
111
+
}
112
+
113
+
try app.repopulateDirectory("");
114
+
app.text_input.clearAndFree();
115
+
116
+
message = try std.fmt.allocPrint(app.alloc, "Renamed '{s}' to '{s}'.", .{ entry.name, new_path });
117
+
app.notification.write(message.?, .info) catch {};
118
+
}
119
+
120
+
app.text_input.clearAndFree();
121
+
}
122
+
123
+
pub fn forceDelete(app: *App) error{OutOfMemory}!void {
124
+
const entry = (app.directories.getSelected() catch {
125
+
app.notification.write("Can not force delete item - no item selected.", .warn) catch {};
126
+
return;
127
+
}) orelse return;
128
+
129
+
app.directories.dir.deleteTree(entry.name) catch |err| {
130
+
const error_message = try std.fmt.allocPrint(app.alloc, "Failed to force delete '{s}' - {}.", .{ entry.name, err });
131
+
app.notification.write(error_message, .err) catch {};
132
+
return;
133
+
};
134
+
135
+
app.directories.removeSelected();
136
+
}
137
+
138
+
pub fn toggleHiddenFiles(app: *App) error{OutOfMemory}!void {
139
+
config.show_hidden = !config.show_hidden;
140
+
141
+
const prev_selected_name: []const u8, const prev_selected_err: bool = lbl: {
142
+
const selected = app.directories.getSelected() catch break :lbl .{ "", true };
143
+
if (selected == null) break :lbl .{ "", true };
144
+
145
+
break :lbl .{ try app.alloc.dupe(u8, selected.?.name), false };
146
+
};
147
+
defer if (!prev_selected_err) app.alloc.free(prev_selected_name);
148
+
149
+
try app.repopulateDirectory("");
150
+
app.text_input.clearAndFree();
151
+
152
+
for (app.directories.entries.all()) |entry| {
153
+
if (std.mem.eql(u8, entry.name, prev_selected_name)) return;
154
+
app.directories.entries.selected += 1;
155
+
}
156
+
157
+
// If it didn't find entry, reset selected.
158
+
app.directories.entries.selected = 0;
159
+
}
160
+
161
+
pub fn yank(app: *App) error{OutOfMemory}!void {
162
+
var message: ?[]const u8 = null;
163
+
defer if (message) |msg| app.alloc.free(msg);
164
+
165
+
if (app.yanked) |yanked| {
166
+
app.alloc.free(yanked.dir);
167
+
app.alloc.free(yanked.entry.name);
168
+
}
169
+
170
+
app.yanked = lbl: {
171
+
const entry = (app.directories.getSelected() catch {
172
+
app.notification.write("Can not yank item - no item selected.", .warn) catch {};
173
+
break :lbl null;
174
+
}) orelse break :lbl null;
175
+
176
+
switch (entry.kind) {
177
+
.file => {
178
+
break :lbl .{
179
+
.dir = try app.alloc.dupe(u8, app.directories.fullPath(".") catch {
180
+
message = try std.fmt.allocPrint(
181
+
app.alloc,
182
+
"Failed to yank '{s}' - unable to retrieve directory path.",
183
+
.{entry.name},
184
+
);
185
+
app.notification.write(message.?, .err) catch {};
186
+
if (app.file_logger) |file_logger| file_logger.write(message.?, .err) catch {};
187
+
break :lbl null;
188
+
}),
189
+
.entry = .{
190
+
.kind = entry.kind,
191
+
.name = try app.alloc.dupe(u8, entry.name),
192
+
},
193
+
};
194
+
},
195
+
else => {
196
+
message = try std.fmt.allocPrint(app.alloc, "Can not yank '{s}' - unsupported file type '{s}'.", .{ entry.name, @tagName(entry.kind) });
197
+
app.notification.write(message.?, .warn) catch {};
198
+
break :lbl null;
199
+
},
200
+
}
201
+
};
202
+
203
+
if (app.yanked) |y| {
204
+
message = try std.fmt.allocPrint(app.alloc, "Yanked '{s}'.", .{y.entry.name});
205
+
app.notification.write(message.?, .info) catch {};
206
+
}
207
+
}
208
+
209
+
pub fn paste(app: *App) error{OutOfMemory}!void {
210
+
var message: ?[]const u8 = null;
211
+
defer if (message) |msg| app.alloc.free(msg);
212
+
213
+
const yanked = if (app.yanked) |y| y else return;
214
+
215
+
var new_path_buf: [std.fs.max_path_bytes]u8 = undefined;
216
+
const new_path_res = environment.checkDuplicatePath(&new_path_buf, app.directories.dir, yanked.entry.name) catch {
217
+
message = try std.fmt.allocPrint(app.alloc, "Failed to copy '{s}' - path too long.", .{yanked.entry.name});
218
+
app.notification.write(message.?, .err) catch {};
219
+
return;
220
+
};
221
+
222
+
switch (yanked.entry.kind) {
223
+
.file => {
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
+
std.fs.Dir.copyFile(
233
+
source_dir,
234
+
yanked.entry.name,
235
+
app.directories.dir,
236
+
new_path_res.path,
237
+
.{},
238
+
) catch |err| switch (err) {
239
+
error.FileNotFound => {
240
+
message = try std.fmt.allocPrint(app.alloc, "Failed to copy '{s}' - the original file was deleted or moved.", .{yanked.entry.name});
241
+
app.notification.write(message.?, .err) catch {};
242
+
if (app.file_logger) |file_logger| file_logger.write(message.?, .err) catch {};
243
+
return;
244
+
},
245
+
else => {
246
+
message = try std.fmt.allocPrint(app.alloc, "Failed to copy '{s}' - {}.", .{ yanked.entry.name, err });
247
+
app.notification.write(message.?, .err) catch {};
248
+
if (app.file_logger) |file_logger| file_logger.write(message.?, .err) catch {};
249
+
return;
250
+
},
251
+
};
252
+
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
+
message = try std.fmt.allocPrint(app.alloc, "Copied '{s}'.", .{yanked.entry.name});
274
+
app.notification.write(message.?, .info) catch {};
275
+
},
276
+
else => {
277
+
message = try std.fmt.allocPrint(app.alloc, "Can not copy '{s}' - unsupported file type '{s}'.", .{ yanked.entry.name, @tagName(yanked.entry.kind) });
278
+
app.notification.write(message.?, .warn) catch {};
279
+
return;
280
+
},
281
+
}
282
+
283
+
try app.repopulateDirectory("");
284
+
app.text_input.clearAndFree();
285
+
}
286
+
287
+
pub fn traverseLeft(app: *App) error{OutOfMemory}!void {
288
+
app.text_input.clearAndFree();
289
+
290
+
const dir = app.directories.dir.openDir("../", .{ .iterate = true }) catch |err| {
291
+
const message = try std.fmt.allocPrint(app.alloc, "Failed to read directory entries - {}.", .{err});
292
+
defer app.alloc.free(message);
293
+
app.notification.write(message, .err) catch {};
294
+
if (app.file_logger) |file_logger| file_logger.write(message, .err) catch {};
295
+
return;
296
+
};
297
+
298
+
app.directories.dir.close();
299
+
app.directories.dir = dir;
300
+
301
+
try app.repopulateDirectory("");
302
+
app.text_input.clearAndFree();
303
+
304
+
if (app.directories.history.pop()) |history| {
305
+
if (history < app.directories.entries.len()) {
306
+
app.directories.entries.selected = history;
307
+
}
308
+
}
309
+
}
310
+
311
+
pub fn traverseRight(app: *App, loop: *vaxis.Loop(App.Event)) !void {
312
+
var message: ?[]const u8 = null;
313
+
defer if (message) |msg| app.alloc.free(msg);
314
+
315
+
const entry = (app.directories.getSelected() catch {
316
+
app.notification.write("Can not rename item - no item selected.", .warn) catch {};
317
+
return;
318
+
}) orelse return;
319
+
320
+
switch (entry.kind) {
321
+
.directory => {
322
+
app.text_input.clearAndFree();
323
+
324
+
const dir = app.directories.dir.openDir(entry.name, .{ .iterate = true }) catch |err| {
325
+
message = try std.fmt.allocPrint(app.alloc, "Failed to read directory entries - {}.", .{err});
326
+
app.notification.write(message.?, .err) catch {};
327
+
if (app.file_logger) |file_logger| file_logger.write(message.?, .err) catch {};
328
+
return;
329
+
};
330
+
331
+
app.directories.dir.close();
332
+
app.directories.dir = dir;
333
+
_ = app.directories.history.push(app.directories.entries.selected);
334
+
try app.repopulateDirectory("");
335
+
app.text_input.clearAndFree();
336
+
},
337
+
.file => {
338
+
if (environment.getEditor()) |editor| {
339
+
try app.vx.exitAltScreen(app.tty.anyWriter());
340
+
try app.vx.resetState(app.tty.anyWriter());
341
+
loop.stop();
342
+
343
+
environment.openFile(app.alloc, app.directories.dir, entry.name, editor) catch |err| {
344
+
message = try std.fmt.allocPrint(app.alloc, "Failed to open file '{s}' - {}.", .{ entry.name, err });
345
+
app.notification.write(message.?, .err) catch {};
346
+
if (app.file_logger) |file_logger| file_logger.write(message.?, .err) catch {};
347
+
};
348
+
349
+
try loop.start();
350
+
try app.vx.enterAltScreen(app.tty.anyWriter());
351
+
try app.vx.enableDetectedFeatures(app.tty.anyWriter());
352
+
app.vx.queueRefresh();
353
+
} else {
354
+
app.notification.write("Can not open file - $EDITOR not set.", .warn) catch {};
355
+
}
356
+
},
357
+
else => {},
358
+
}
359
+
}
360
+
361
+
pub fn createNewDir(app: *App) error{OutOfMemory}!void {
362
+
var message: ?[]const u8 = null;
363
+
defer if (message) |msg| app.alloc.free(msg);
364
+
365
+
const dir = app.inputToSlice();
366
+
367
+
app.directories.dir.makeDir(dir) catch |err| {
368
+
message = try std.fmt.allocPrint(app.alloc, "Failed to create directory '{s}' - {}", .{ dir, err });
369
+
app.notification.write(message.?, .err) catch {};
370
+
if (app.file_logger) |file_logger| file_logger.write(message.?, .err) catch {};
371
+
app.text_input.clearAndFree();
372
+
return;
373
+
};
374
+
375
+
try app.repopulateDirectory("");
376
+
app.text_input.clearAndFree();
377
+
378
+
message = try std.fmt.allocPrint(app.alloc, "Created new directory '{s}'.", .{dir});
379
+
app.notification.write(message.?, .info) catch {};
380
+
}
381
+
382
+
pub fn createNewFile(app: *App) error{OutOfMemory}!void {
383
+
var message: ?[]const u8 = null;
384
+
defer if (message) |msg| app.alloc.free(msg);
385
+
386
+
const file = app.inputToSlice();
387
+
388
+
if (environment.fileExists(app.directories.dir, file)) {
389
+
message = try std.fmt.allocPrint(app.alloc, "Can not create file - '{s}' already exists.", .{file});
390
+
app.notification.write(message.?, .warn) catch {};
391
+
} else {
392
+
_ = app.directories.dir.createFile(file, .{}) catch |err| {
393
+
message = try std.fmt.allocPrint(app.alloc, "Failed to create file '{s}' - {}", .{ file, err });
394
+
app.notification.write(message.?, .err) catch {};
395
+
if (app.file_logger) |file_logger| file_logger.write(message.?, .err) catch {};
396
+
app.text_input.clearAndFree();
397
+
return;
398
+
};
399
+
400
+
try app.repopulateDirectory("");
401
+
app.text_input.clearAndFree();
402
+
403
+
message = try std.fmt.allocPrint(app.alloc, "Created new file '{s}'.", .{file});
404
+
app.notification.write(message.?, .info) catch {};
405
+
}
406
+
407
+
app.text_input.clearAndFree();
408
+
}
409
+
410
+
pub fn undo(app: *App) error{OutOfMemory}!void {
411
+
var message: ?[]const u8 = null;
412
+
defer if (message) |msg| app.alloc.free(msg);
413
+
414
+
const action = app.actions.pop() orelse {
415
+
app.notification.write("There is nothing to undo.", .info) catch {};
416
+
return;
417
+
};
418
+
419
+
const selected = app.directories.entries.selected;
420
+
421
+
switch (action) {
422
+
.delete => |a| {
423
+
defer app.alloc.free(a.new_path);
424
+
defer app.alloc.free(a.prev_path);
425
+
426
+
var new_path_buf: [std.fs.max_path_bytes]u8 = undefined;
427
+
const new_path_res = environment.checkDuplicatePath(&new_path_buf, app.directories.dir, a.prev_path) catch {
428
+
message = try std.fmt.allocPrint(app.alloc, "Failed to undo delete '{s}' - path too long.", .{a.prev_path});
429
+
app.notification.write(message.?, .err) catch {};
430
+
if (app.file_logger) |file_logger| file_logger.write(message.?, .err) catch {};
431
+
return;
432
+
};
433
+
434
+
app.directories.dir.rename(a.new_path, new_path_res.path) catch |err| {
435
+
message = try std.fmt.allocPrint(app.alloc, "Failed to undo delete for '{s}' - {}.", .{ a.prev_path, err });
436
+
app.notification.write(message.?, .err) catch {};
437
+
if (app.file_logger) |file_logger| file_logger.write(message.?, .err) catch {};
438
+
return;
439
+
};
440
+
441
+
try app.repopulateDirectory("");
442
+
app.text_input.clearAndFree();
443
+
444
+
message = try std.fmt.allocPrint(app.alloc, "Restored '{s}' as '{s}'.", .{ a.prev_path, new_path_res.path });
445
+
app.notification.write(message.?, .info) catch {};
446
+
},
447
+
.rename => |a| {
448
+
defer app.alloc.free(a.new_path);
449
+
defer app.alloc.free(a.prev_path);
450
+
451
+
var new_path_buf: [std.fs.max_path_bytes]u8 = undefined;
452
+
const new_path_res = environment.checkDuplicatePath(&new_path_buf, app.directories.dir, a.prev_path) catch {
453
+
message = try std.fmt.allocPrint(app.alloc, "Failed to undo rename '{s}' - path too long.", .{a.prev_path});
454
+
app.notification.write(message.?, .err) catch {};
455
+
if (app.file_logger) |file_logger| file_logger.write(message.?, .err) catch {};
456
+
return;
457
+
};
458
+
459
+
app.directories.dir.rename(a.new_path, new_path_res.path) catch |err| {
460
+
message = try std.fmt.allocPrint(app.alloc, "Failed to undo rename for '{s}' - {}.", .{ a.new_path, err });
461
+
app.notification.write(message.?, .err) catch {};
462
+
if (app.file_logger) |file_logger| file_logger.write(message.?, .err) catch {};
463
+
return;
464
+
};
465
+
466
+
try app.repopulateDirectory("");
467
+
app.text_input.clearAndFree();
468
+
469
+
message = try std.fmt.allocPrint(app.alloc, "Reverted renaming of '{s}', now '{s}'.", .{ a.new_path, new_path_res.path });
470
+
app.notification.write(message.?, .info) catch {};
471
+
},
472
+
.paste => |path| {
473
+
defer app.alloc.free(path);
474
+
475
+
app.directories.dir.deleteTree(path) catch |err| {
476
+
message = try std.fmt.allocPrint(app.alloc, "Failed to delete '{s}' - {}.", .{ path, err });
477
+
app.notification.write(message.?, .err) catch {};
478
+
if (app.file_logger) |file_logger| file_logger.write(message.?, .err) catch {};
479
+
return;
480
+
};
481
+
482
+
try app.repopulateDirectory("");
483
+
app.text_input.clearAndFree();
484
+
},
485
+
}
486
+
487
+
app.directories.entries.selected = selected;
488
+
}
+5
-6
src/git.zig
+5
-6
src/git.zig
···
2
2
3
3
/// Callers owns memory returned.
4
4
pub fn getGitBranch(alloc: std.mem.Allocator, dir: std.fs.Dir) !?[]const u8 {
5
-
var file = dir.openFile(".git/HEAD", .{}) catch return null;
5
+
var file = try dir.openFile(".git/HEAD", .{});
6
6
defer file.close();
7
7
8
8
var buf: [1024]u8 = undefined;
9
-
const bytes = file.readAll(&buf) catch return null;
9
+
const bytes = try file.readAll(&buf);
10
+
if (bytes == 0) return null;
10
11
11
-
var it = std.mem.splitBackwardsSequence(u8, buf[0..bytes], "/");
12
-
const branch = it.next() orelse return null;
13
-
if (std.mem.eql(u8, branch, "")) return null;
12
+
const preamble = "ref: refs/heads/";
14
13
15
-
return try alloc.dupe(u8, branch);
14
+
return try alloc.dupe(u8, buf[preamble.len..]);
16
15
}
+12
-3
src/main.zig
+12
-3
src/main.zig
···
53
53
54
54
config.parse(alloc, &app) catch |err| switch (err) {
55
55
error.SyntaxError => {
56
-
try app.notification.writeErr(.ConfigSyntaxError);
56
+
app.notification.write("Encountered a syntax error while parsing the config file.", .err) catch {
57
+
std.log.err("Encountered a syntax error while parsing the config file.", .{});
58
+
};
57
59
},
58
60
error.InvalidCharacter => {
59
-
try app.notification.writeErr(.InvalidKeybind);
61
+
app.notification.write("One or more overriden keybinds are invalid.", .err) catch {
62
+
std.log.err("One or more overriden keybinds are invalid.", .{});
63
+
};
60
64
},
61
65
error.DuplicateKeybind => {
62
66
// Error logged in function
63
67
},
64
68
else => {
65
-
try app.notification.writeErr(.ConfigUnknownError);
69
+
const message = try std.fmt.allocPrint(alloc, "Encountend an unknown error while parsing the config file - {}", .{err});
70
+
defer alloc.free(message);
71
+
72
+
app.notification.write(message, .err) catch {
73
+
std.log.err("Encountend an unknown error while parsing the config file - {}", .{err});
74
+
};
66
75
},
67
76
};
68
77
-79
src/notification.zig
-79
src/notification.zig
···
12
12
warn,
13
13
};
14
14
15
-
const Error = enum {
16
-
PermissionDenied,
17
-
UnknownError,
18
-
UnableToUndo,
19
-
UnableToOpenFile,
20
-
UnableToDelete,
21
-
FailedToDeleteSomeItems,
22
-
UnableToDeleteAcrossMountPoints,
23
-
UnsupportedImageFormat,
24
-
EditorNotSet,
25
-
ItemAlreadyExists,
26
-
UnableToRename,
27
-
IncorrectPath,
28
-
ConfigSyntaxError,
29
-
ConfigUnknownError,
30
-
ConfigPathNotFound,
31
-
CannotDeleteTrashDir,
32
-
InvalidKeybind,
33
-
NotADir,
34
-
};
35
-
36
-
const Info = enum {
37
-
CreatedFile,
38
-
CreatedFolder,
39
-
Deleted,
40
-
Renamed,
41
-
RestoredDelete,
42
-
RestoredRename,
43
-
EmptyUndo,
44
-
ChangedDir,
45
-
ConfigReloaded,
46
-
};
47
-
48
-
const Warn = enum { DeprecatedConfigPath, DuplicateFileOnUndo };
49
-
50
15
var buf: [1024]u8 = undefined;
51
16
52
17
style: Style = Style.info,
···
59
24
_ = try self.fbs.write(text);
60
25
self.timer = std.time.timestamp();
61
26
self.style = style;
62
-
}
63
-
64
-
pub fn writeErr(self: *Self, err: Error) !void {
65
-
try switch (err) {
66
-
.PermissionDenied => self.write("Permission denied.", .err),
67
-
.UnknownError => self.write("An unknown error occurred.", .err),
68
-
.UnableToOpenFile => self.write("Unable to open file.", .err),
69
-
.UnableToDelete => self.write("Unable to delete item.", .err),
70
-
.FailedToDeleteSomeItems => self.write("Failed to delete some items..", .err),
71
-
.UnableToDeleteAcrossMountPoints => self.write("Unable to move item to /tmp. Failed to delete.", .err),
72
-
.UnableToUndo => self.write("Unable to undo previous action.", .err),
73
-
.ItemAlreadyExists => self.write("Item already exists.", .err),
74
-
.UnableToRename => self.write("Unable to rename item.", .err),
75
-
.IncorrectPath => self.write("Unable to find path.", .err),
76
-
.NotADir => self.write("Path is not a directory.", .err),
77
-
.EditorNotSet => self.write("$EDITOR is not set.", .err),
78
-
.UnsupportedImageFormat => self.write("Unsupported image format.", .err),
79
-
.ConfigSyntaxError => self.write("Could not read config due to a syntax error.", .err),
80
-
.ConfigUnknownError => self.write("Could not read config due to an unknown error.", .err),
81
-
.ConfigPathNotFound => self.write("Could not read config due to unset env variables. Please set either $HOME or $XDG_CONFIG_HOME.", .err),
82
-
.CannotDeleteTrashDir => self.write("Cannot delete trash directory.", .err),
83
-
.InvalidKeybind => self.write("Config has keybind(s) with invalid key(s).", .err),
84
-
};
85
-
}
86
-
87
-
pub fn writeInfo(self: *Self, info: Info) !void {
88
-
try switch (info) {
89
-
.CreatedFile => self.write("Successfully created file.", .info),
90
-
.CreatedFolder => self.write("Successfully created folder.", .info),
91
-
.Deleted => self.write("Successfully deleted item.", .info),
92
-
.Renamed => self.write("Successfully renamed item.", .info),
93
-
.RestoredDelete => self.write("Successfully restored deleted item.", .info),
94
-
.RestoredRename => self.write("Successfully restored renamed item.", .info),
95
-
.EmptyUndo => self.write("Nothing to undo.", .info),
96
-
.ChangedDir => self.write("Successfully changed directory.", .info),
97
-
.ConfigReloaded => self.write("Successfully reloaded config.", .info),
98
-
};
99
-
}
100
-
101
-
pub fn writeWarn(self: *Self, warning: Warn) !void {
102
-
try switch (warning) {
103
-
.DeprecatedConfigPath => self.write("You are using a deprecated config path. Please move your config to either `$XDG_CONFIG_HOME/jido` or `$HOME/.jido`", .warn),
104
-
.DuplicateFileOnUndo => self.write("A file with the same name already exists. A unique identifier has been appending to the duplicated item.", .warn),
105
-
};
106
27
}
107
28
108
29
pub fn reset(self: *Self) void {