diff --git a/build.zig b/build.zig index 4c6007b..f504c33 100644 --- a/build.zig +++ b/build.zig @@ -16,15 +16,22 @@ const Build = blk: { }; pub fn build(b: *std.Build) !void { - const target = b.standardTargetOptions(.{}); - const optimize = b.standardOptimizeOption(.{}); - const build_options = b.addOptions(); build_options.step.name = "build options"; const build_options_module = build_options.createModule(); build_options.addOption([]const u8, "minimum_zig_string", minimum_zig_version); build_options.addOption(std.SemanticVersion, "zfe_version", version); + // Building targets for release. + const build_all = b.option(bool, "build-all-targets", "Build all targets in ReleaseSafe mode.") orelse false; + if (build_all) { + try build_targets(b, build_options_module); + return; + } + + const target = b.standardTargetOptions(.{}); + const optimize = b.standardOptimizeOption(.{}); + const libvaxis = b.dependency("vaxis", .{ .target = target }).module("vaxis"); const fuzzig = b.dependency("fuzzig", .{ .target = target }).module("fuzzig"); const zuid = b.dependency("zuid", .{ .target = target }).module("zuid"); @@ -48,12 +55,6 @@ pub fn build(b: *std.Build) !void { } const run_step = b.step("run", "Run the app"); run_step.dependOn(&run_cmd.step); - - // Building targets for release. - const build_all = b.option(bool, "build-all-targets", "Build all targets in ReleaseSafe mode.") orelse false; - if (build_all) { - try build_targets(b, build_options_module); - } } fn build_targets(b: *std.Build, build_options_module: *std.Build.Module) !void { diff --git a/release_zip.sh b/release_zip.sh new file mode 100755 index 0000000..b092904 --- /dev/null +++ b/release_zip.sh @@ -0,0 +1,4 @@ +zip -r ./zig-out/x86_64-linux.zip ./zig-out/x86_64-linux/ +zip -r ./zig-out/x86_64-macos.zip ./zig-out/x86_64-macos/ +zip -r ./zig-out/aarch64-linux.zip ./zig-out/aarch64-linux/ +zip -r ./zig-out/aarch64-macos.zip ./zig-out/aarch64-macos/ diff --git a/src/app.zig b/src/app.zig index 5172f5d..a5f0232 100644 --- a/src/app.zig +++ b/src/app.zig @@ -7,6 +7,7 @@ const Notification = @import("./notification.zig"); const config = &@import("./config.zig").config; const List = @import("./list.zig").List; const Directories = @import("./directories.zig"); +const CircStack = @import("./circ_stack.zig").CircularStack; const zuid = @import("zuid"); @@ -24,24 +25,21 @@ pub const State = enum { rename, }; -const Effect = enum { +const InputReturnStatus = enum { exit, default, }; +const ActionPaths = struct { + /// Allocated. + old: []const u8, + /// Allocated. + new: []const u8, +}; + pub const Action = union(enum) { - delete: struct { - /// Allocated. - old_path: []const u8, - /// Allocated. - tmp_path: []const u8, - }, - rename: struct { - /// Allocated. - old_path: []const u8, - /// Allocated. - new_path: []const u8, - }, + delete: ActionPaths, + rename: ActionPaths, }; const Event = union(enum) { @@ -52,6 +50,7 @@ const Event = union(enum) { const top_div = 1; const info_div = 1; const bottom_div = 1; +const actions_len = 100; const App = @This(); @@ -59,8 +58,8 @@ alloc: std.mem.Allocator, vx: vaxis.Vaxis = undefined, tty: vaxis.Tty = undefined, logger: Logger, -state: State = State.normal, -actions: std.ArrayList(Action), +state: State = .normal, +actions: CircStack(Action, actions_len), // Used to detect whether to re-render an image. current_item_path_buf: [std.fs.max_path_bytes]u8 = undefined, @@ -96,28 +95,23 @@ pub fn init(alloc: std.mem.Allocator) !App { .logger = Logger{}, .text_input = TextInput.init(alloc, &vx.unicode), .notification = Notification{}, - .actions = std.ArrayList(Action).init(alloc), + .actions = CircStack(Action, actions_len).init(), .last_known_height = vx.window().height, }; } pub fn deinit(self: *App) void { - for (self.actions.items) |action| { + for (self.actions.buf[0..self.actions.count]) |action| { switch (action) { - .delete => |a| { - self.alloc.free(a.tmp_path); - self.alloc.free(a.old_path); - }, - .rename => |a| { - self.alloc.free(a.new_path); - self.alloc.free(a.old_path); + .delete, .rename => |a| { + self.alloc.free(a.new); + self.alloc.free(a.old); }, } } self.directories.deinit(); self.text_input.deinit(); - self.actions.deinit(); self.vx.deinit(self.alloc, self.tty.anyWriter()); self.tty.deinit(); } @@ -164,7 +158,7 @@ pub fn inputToSlice(self: *App) []const u8 { return self.text_input.sliceToCursor(&self.text_input_buf); } -pub fn handle_normal_event(self: *App, event: Event, loop: *vaxis.Loop(Event)) !Effect { +pub fn handle_normal_event(self: *App, event: Event, loop: *vaxis.Loop(Event)) !InputReturnStatus { switch (event) { .key_press => |key| { if ((key.codepoint == 'c' and key.mods.ctrl) or key.codepoint == 'q') { @@ -187,7 +181,7 @@ pub fn handle_normal_event(self: *App, event: Event, loop: *vaxis.Loop(Event)) ! } }; - if (self.directories.history.popOrNull()) |history| { + if (self.directories.history.pop()) |history| { self.directories.entries.selected = history.selected; self.directories.entries.offset = history.offset; } @@ -208,7 +202,7 @@ pub fn handle_normal_event(self: *App, event: Event, loop: *vaxis.Loop(Event)) ! if (self.directories.dir.openDir(entry.name, .{ .iterate = true })) |dir| { self.directories.dir = dir; - try self.directories.history.append(.{ + self.directories.history.push(.{ .selected = self.directories.entries.selected, .offset = self.directories.entries.offset, }); @@ -274,7 +268,10 @@ pub fn handle_normal_event(self: *App, event: Event, loop: *vaxis.Loop(Event)) ! try self.notification.write("Deleting item...", .info); if (self.directories.dir.rename(entry.name, tmp_path)) { - try self.actions.append(.{ .delete = .{ .old_path = old_path, .tmp_path = tmp_path } }); + // TODO: Will leak memory if pushing to a full stack. + self.actions.push(.{ + .delete = .{ .old = old_path, .new = tmp_path }, + }); try self.notification.write("Deleted item.", .info); self.directories.remove_selected(); @@ -289,16 +286,15 @@ pub fn handle_normal_event(self: *App, event: Event, loop: *vaxis.Loop(Event)) ! self.state = .new_file; }, 'u' => { - if (self.actions.items.len > 0) { + if (self.actions.pop()) |action| { const selected = self.directories.entries.selected; - const action = self.actions.pop(); switch (action) { .delete => |a| { // TODO: Will overwrite an item if it has the same name. - if (self.directories.dir.rename(a.tmp_path, a.old_path)) { - defer self.alloc.free(a.tmp_path); - defer self.alloc.free(a.old_path); + if (self.directories.dir.rename(a.new, a.old)) { + defer self.alloc.free(a.new); + defer self.alloc.free(a.old); self.directories.cleanup(); const fuzzy = self.inputToSlice(); @@ -315,9 +311,9 @@ pub fn handle_normal_event(self: *App, event: Event, loop: *vaxis.Loop(Event)) ! }, .rename => |a| { // TODO: Will overwrite an item if it has the same name. - if (self.directories.dir.rename(a.new_path, a.old_path)) { - defer self.alloc.free(a.new_path); - defer self.alloc.free(a.old_path); + if (self.directories.dir.rename(a.new, a.old)) { + defer self.alloc.free(a.new); + defer self.alloc.free(a.old); self.directories.cleanup(); const fuzzy = self.inputToSlice(); @@ -340,19 +336,19 @@ pub fn handle_normal_event(self: *App, event: Event, loop: *vaxis.Loop(Event)) ! } }, '/' => { - self.state = State.fuzzy; + self.state = .fuzzy; }, 'R' => { - self.state = State.rename; + self.state = .rename; const entry = try self.directories.get_selected(); self.text_input.insertSliceAtCursor(entry.name) catch { - self.state = State.normal; + self.state = .normal; try self.notification.write_err(.UnableToRename); }; }, 'c' => { - self.state = State.change_dir; + self.state = .change_dir; }, else => { // log.debug("codepoint: {d}\n", .{key.codepoint}); @@ -367,7 +363,7 @@ pub fn handle_normal_event(self: *App, event: Event, loop: *vaxis.Loop(Event)) ! return .default; } -pub fn handle_input_event(self: *App, event: Event) !Effect { +pub fn handle_input_event(self: *App, event: Event) !InputReturnStatus { switch (event) { .key_press => |key| { if ((key.codepoint == 'c' and key.mods.ctrl) or key.codepoint == 'q') { @@ -390,11 +386,9 @@ pub fn handle_input_event(self: *App, event: Event) !Effect { } self.text_input.clearAndFree(); - self.state = State.normal; + self.state = .normal; }, Key.enter => { - // TODO: Do these actions really have to re-populate or can we - // just append. const selected = self.directories.entries.selected; switch (self.state) { .new_dir => { @@ -454,10 +448,13 @@ pub fn handle_input_event(self: *App, event: Event) !Effect { error.PathAlreadyExists => try self.notification.write_err(.ItemAlreadyExists), else => try self.notification.write_err(.UnknownError), }; - try self.actions.append(.{ .rename = .{ - .old_path = try std.fs.path.join(self.alloc, &.{ dir_prefix, old.name }), - .new_path = try std.fs.path.join(self.alloc, &.{ dir_prefix, new }), - } }); + // TODO: Will leak memory if pushing to a full stack. + self.actions.push(.{ + .rename = .{ + .old = try std.fs.path.join(self.alloc, &.{ dir_prefix, old.name }), + .new = try std.fs.path.join(self.alloc, &.{ dir_prefix, new }), + }, + }); self.directories.cleanup(); self.directories.populate_entries("") catch |err| { @@ -481,7 +478,7 @@ pub fn handle_input_event(self: *App, event: Event) !Effect { else => try self.notification.write_err(.UnknownError), } }; - self.directories.history.clearAndFree(); + self.directories.history.reset(); } else |err| { switch (err) { error.AccessDenied => try self.notification.write_err(.PermissionDenied), @@ -495,7 +492,7 @@ pub fn handle_input_event(self: *App, event: Event) !Effect { }, else => {}, } - self.state = State.normal; + self.state = .normal; self.directories.entries.selected = selected; }, else => { @@ -573,7 +570,7 @@ fn draw_preview(self: *App, win: vaxis.Window, file_name_win: vaxis.Window) !voi }); // Populate preview bar - if (self.directories.entries.all().len > 0 and config.preview_file == true) { + if (self.directories.entries.len() > 0 and config.preview_file == true) { const entry = try self.directories.get_selected(); @memcpy(&self.last_item_path_buf, &self.current_item_path_buf); @@ -662,13 +659,13 @@ fn draw_preview(self: *App, win: vaxis.Window, file_name_win: vaxis.Window) !voi break :file; } - if (self.directories.pdf_contents.len > 0) { - self.directories.alloc.free(self.directories.pdf_contents); + if (self.directories.pdf_contents) |pdf_contents| { + self.directories.alloc.free(pdf_contents); } self.directories.pdf_contents = try stdout.toOwnedSlice(); _ = try preview_win.print(&.{ .{ - .text = self.directories.pdf_contents, + .text = self.directories.pdf_contents.?, }, }, .{}); break :file; @@ -701,7 +698,7 @@ fn draw_preview(self: *App, win: vaxis.Window, file_name_win: vaxis.Window) !voi fn draw_file_info(self: *App, win: vaxis.Window, file_info_buf: []u8) !vaxis.Window { const file_info = try std.fmt.bufPrint(file_info_buf, "{d}/{d} {s} {s}", .{ self.directories.entries.selected + 1, - self.directories.entries.items.items.len, + self.directories.entries.len(), std.fs.path.extension(if (self.directories.get_selected()) |entry| entry.name else |_| ""), std.fmt.fmtIntSizeDec((try self.directories.dir.metadata()).size()), }); @@ -725,7 +722,7 @@ fn draw_current_dir_list(self: *App, win: vaxis.Window, abs_file_path: vaxis.Win .width = if (config.preview_file) .{ .limit = win.width / 2 } else .{ .limit = win.width }, .height = .{ .limit = win.height - (abs_file_path.height + file_information.height + top_div + bottom_div) }, }); - try self.directories.write_entries(current_dir_list_win, config.styles.selected_list_item, config.styles.list_item, null); + try self.directories.write_entries(current_dir_list_win, config.styles.selected_list_item, config.styles.list_item); self.last_known_height = current_dir_list_win.height; } diff --git a/src/circ_stack.zig b/src/circ_stack.zig new file mode 100644 index 0000000..d017b65 --- /dev/null +++ b/src/circ_stack.zig @@ -0,0 +1,35 @@ +const std = @import("std"); + +pub fn CircularStack(comptime T: type, comptime capacity: usize) type { + return struct { + const Self = @This(); + + head: usize = 0, + count: usize = 0, + buf: [capacity]T = undefined, + + pub fn init() Self { + return Self{}; + } + + pub fn reset(self: *Self) void { + self.head = 0; + self.count = 0; + } + + pub fn push(self: *Self, v: T) void { + self.buf[self.head] = v; + self.head = (self.head + 1) % capacity; + if (self.count != capacity) self.count += 1; + } + + pub fn pop(self: *Self) ?T { + if (self.count == 0) return null; + + self.head = (self.head - 1) % capacity; + const value = self.buf[self.head]; + self.count -= 1; + return value; + } + }; +} diff --git a/src/directories.zig b/src/directories.zig index 836398b..83b03ca 100644 --- a/src/directories.zig +++ b/src/directories.zig @@ -1,5 +1,6 @@ const std = @import("std"); const List = @import("./list.zig").List; +const CircStack = @import("./circ_stack.zig").CircularStack; const config = &@import("./config.zig").config; const vaxis = @import("vaxis"); const fuzzig = @import("fuzzig"); @@ -9,15 +10,17 @@ const History = struct { offset: usize, }; +const history_len: usize = 100; + const Self = @This(); alloc: std.mem.Allocator, dir: std.fs.Dir, path_buf: [std.fs.max_path_bytes]u8 = undefined, file_contents: [4096]u8 = undefined, -pdf_contents: []u8 = undefined, +pdf_contents: ?[]u8 = null, entries: List(std.fs.Dir.Entry), -history: std.ArrayList(History), +history: CircStack(History, history_len), sub_entries: List([]const u8), searcher: fuzzig.Ascii, @@ -26,7 +29,7 @@ pub fn init(alloc: std.mem.Allocator) !Self { .alloc = alloc, .dir = try std.fs.cwd().openDir(".", .{ .iterate = true }), .entries = List(std.fs.Dir.Entry).init(alloc), - .history = std.ArrayList(History).init(alloc), + .history = CircStack(History, history_len).init(), .sub_entries = List([]const u8).init(alloc), .searcher = try fuzzig.Ascii.init( alloc, @@ -44,8 +47,9 @@ pub fn deinit(self: *Self) void { self.entries.deinit(); self.sub_entries.deinit(); - self.history.deinit(); - self.alloc.free(self.pdf_contents); + if (self.pdf_contents) |pdf_contents| { + self.alloc.free(pdf_contents); + } self.dir.close(); self.searcher.deinit(); @@ -79,7 +83,7 @@ pub fn populate_sub_entries( } if (config.sort_dirs == true) { - std.mem.sort([]const u8, self.sub_entries.items.items, {}, sort_sub_entry); + std.mem.sort([]const u8, self.sub_entries.all(), {}, sort_sub_entry); } } @@ -88,7 +92,7 @@ pub fn write_sub_entries( window: vaxis.Window, style: vaxis.Style, ) !void { - for (self.sub_entries.items.items, 0..) |item, i| { + for (self.sub_entries.all(), 0..) |item, i| { if (std.mem.startsWith(u8, item, ".") and config.show_hidden == false) { continue; } @@ -129,7 +133,7 @@ pub fn populate_entries(self: *Self, fuzzy_search: []const u8) !void { } if (config.sort_dirs == true) { - std.mem.sort(std.fs.Dir.Entry, self.entries.items.items, {}, sort_entry); + std.mem.sort(std.fs.Dir.Entry, self.entries.all(), {}, sort_entry); } } @@ -138,10 +142,10 @@ pub fn write_entries( window: vaxis.Window, selected_list_item_style: vaxis.Style, list_item_style: vaxis.Style, - callback: ?*const fn (item_win: vaxis.Window) void, ) !void { - for (self.entries.items.items[self.entries.offset..], 0..) |item, i| { - const is_selected = self.entries.selected - self.entries.offset == i; + for (self.entries.all()[self.entries.offset..], 0..) |item, i| { + const selected = self.entries.selected - self.entries.offset; + const is_selected = selected == i; if (std.mem.startsWith(u8, item.name, ".") and config.show_hidden == false) { continue; @@ -159,10 +163,6 @@ pub fn write_entries( .style = if (is_selected) selected_list_item_style else list_item_style, }); - if (callback) |cb| { - cb(w); - } - _ = try w.print(&.{ .{ .text = item.name, diff --git a/src/list.zig b/src/list.zig index f3b2ca7..12a677d 100644 --- a/src/list.zig +++ b/src/list.zig @@ -34,20 +34,20 @@ pub fn List(comptime T: type) type { } pub fn get(self: *Self, index: usize) !T { - if (index + 1 > self.items.items.len) { + if (index + 1 > self.len()) { return error.OutOfBounds; } - return self.items.items[index]; + return self.all()[index]; } pub fn get_selected(self: *Self) !T { - if (self.items.items.len > 0) { - if (self.selected >= self.items.items.len) { - self.selected = self.items.items.len - 1; + if (self.len() > 0) { + if (self.selected >= self.len()) { + self.selected = self.len() - 1; } - return self.items.items[self.selected]; + return try self.get(self.selected); } return error.EmptyList; @@ -57,11 +57,15 @@ pub fn List(comptime T: type) type { return self.items.items; } + pub fn len(self: *Self) usize { + return self.items.items.len; + } + pub fn next(self: *Self, win_height: usize) void { - if (self.selected + 1 < self.items.items.len) { + if (self.selected + 1 < self.len()) { self.selected += 1; - if (self.items.items[self.offset..].len != win_height and self.selected >= self.offset + (win_height / 2)) { + if (self.all()[self.offset..].len != win_height and self.selected >= self.offset + (win_height / 2)) { self.offset += 1; } } @@ -78,7 +82,7 @@ pub fn List(comptime T: type) type { } pub fn select_last(self: *Self, win_height: usize) void { - self.selected = self.items.items.len - 1; + self.selected = self.len() - 1; if (self.selected >= win_height) { self.offset = self.selected - (win_height - 1); } diff --git a/src/main.zig b/src/main.zig index f4e9574..528fc00 100644 --- a/src/main.zig +++ b/src/main.zig @@ -3,7 +3,6 @@ const builtin = @import("builtin"); const App = @import("app.zig"); -const log = &@import("./log.zig").log; const config = &@import("./config.zig").config; const vaxis = @import("vaxis"); @@ -17,15 +16,15 @@ pub fn main() !void { config.parse(alloc) catch |err| switch (err) { error.ConfigNotFound => {}, error.MissingConfigHomeEnvironmentVariable => { - log.err("Could not read config due to $HOME or $XDG_CONFIG_HOME not being set.", .{}); + std.log.err("Could not read config due to $HOME or $XDG_CONFIG_HOME not being set.", .{}); return; }, error.SyntaxError => { - log.err("Could not read config due to a syntax error.", .{}); + std.log.err("Could not read config due to a syntax error.", .{}); return; }, else => { - log.err("Could not read config due to an unknown error.", .{}); + std.log.err("Could not read config due to an unknown error.", .{}); return; }, };