地圖 (Jido) is a lightweight Unix TUI file explorer designed for speed and simplicity.
at v0.2.0 175 lines 4.6 kB view raw
1const std = @import("std"); 2const List = @import("./list.zig").List; 3const config = &@import("./config.zig").config; 4const vaxis = @import("vaxis"); 5 6const fuzzig = @import("fuzzig"); 7 8const Self = @This(); 9 10alloc: std.mem.Allocator, 11dir: std.fs.Dir, 12path_buf: [std.fs.max_path_bytes]u8 = undefined, 13file_contents: [4096]u8 = undefined, 14entries: List(std.fs.Dir.Entry), 15sub_entries: List([]const u8), 16searcher: fuzzig.Ascii, 17 18pub fn init(alloc: std.mem.Allocator) !Self { 19 return Self{ 20 .alloc = alloc, 21 .dir = try std.fs.cwd().openDir(".", .{ .iterate = true }), 22 .file_contents = undefined, 23 .entries = List(std.fs.Dir.Entry).init(alloc), 24 .sub_entries = List([]const u8).init(alloc), 25 .searcher = try fuzzig.Ascii.init( 26 alloc, 27 std.fs.max_path_bytes, 28 std.fs.max_path_bytes, 29 .{ .case_sensitive = false }, 30 ), 31 }; 32} 33 34pub fn deinit(self: *Self) void { 35 self.cleanup(); 36 self.cleanup_sub(); 37 38 self.entries.deinit(); 39 self.sub_entries.deinit(); 40 41 self.dir.close(); 42 self.searcher.deinit(); 43} 44 45pub fn full_path(self: *Self, relative_path: []const u8) ![]const u8 { 46 return try self.dir.realpath(relative_path, &self.path_buf); 47} 48 49pub fn populate_sub_entries( 50 self: *Self, 51 relative_path: []const u8, 52) !void { 53 var dir = try self.dir.openDir(relative_path, .{ .iterate = true }); 54 defer dir.close(); 55 56 var it = dir.iterate(); 57 while (try it.next()) |entry| { 58 try self.sub_entries.append(try self.alloc.dupe(u8, entry.name)); 59 } 60 61 if (config.sort_dirs == true) { 62 std.mem.sort([]const u8, self.sub_entries.items.items, {}, sort_sub_entry); 63 } 64} 65 66pub fn write_sub_entries( 67 self: *Self, 68 window: vaxis.Window, 69 style: vaxis.Style, 70) !void { 71 for (self.sub_entries.items.items, 0..) |item, i| { 72 if (std.mem.startsWith(u8, item, ".") and config.show_hidden == false) { 73 continue; 74 } 75 76 if (i > window.height) { 77 continue; 78 } 79 80 const w = window.child(.{ 81 .y_off = i, 82 .height = .{ .limit = 1 }, 83 }); 84 w.fill(vaxis.Cell{ 85 .style = style, 86 }); 87 88 _ = try w.print(&.{ 89 .{ 90 .text = item, 91 .style = style, 92 }, 93 }, .{}); 94 } 95} 96 97pub fn populate_entries(self: *Self, fuzzy_search: []const u8) !void { 98 var it = self.dir.iterate(); 99 while (try it.next()) |entry| { 100 const score = self.searcher.score(entry.name, fuzzy_search) orelse 0; 101 if (fuzzy_search.len > 0 and score < 1) { 102 continue; 103 } 104 105 try self.entries.append(.{ 106 .kind = entry.kind, 107 .name = try self.alloc.dupe(u8, entry.name), 108 }); 109 } 110 111 if (config.sort_dirs == true) { 112 std.mem.sort(std.fs.Dir.Entry, self.entries.items.items, {}, sort_entry); 113 } 114} 115 116pub fn write_entries( 117 self: *Self, 118 window: vaxis.Window, 119 selected_list_item_style: vaxis.Style, 120 list_item_style: vaxis.Style, 121 callback: ?*const fn (item_win: vaxis.Window) void, 122) !void { 123 for (self.entries.items.items[self.entries.offset..], 0..) |item, i| { 124 const is_selected = self.entries.selected - self.entries.offset == i; 125 126 if (std.mem.startsWith(u8, item.name, ".") and config.show_hidden == false) { 127 continue; 128 } 129 130 if (i > window.height) { 131 continue; 132 } 133 134 const w = window.child(.{ 135 .y_off = i, 136 .height = .{ .limit = 1 }, 137 }); 138 w.fill(vaxis.Cell{ 139 .style = if (is_selected) selected_list_item_style else list_item_style, 140 }); 141 142 if (callback) |cb| { 143 cb(w); 144 } 145 146 _ = try w.print(&.{ 147 .{ 148 .text = item.name, 149 .style = if (is_selected) selected_list_item_style else list_item_style, 150 }, 151 }, .{}); 152 } 153} 154 155fn sort_entry(_: void, lhs: std.fs.Dir.Entry, rhs: std.fs.Dir.Entry) bool { 156 return std.mem.lessThan(u8, lhs.name, rhs.name); 157} 158 159fn sort_sub_entry(_: void, lhs: []const u8, rhs: []const u8) bool { 160 return std.mem.lessThan(u8, lhs, rhs); 161} 162 163pub fn cleanup(self: *Self) void { 164 for (self.entries.all()) |entry| { 165 self.entries.alloc.free(entry.name); 166 } 167 self.entries.clear(); 168} 169 170pub fn cleanup_sub(self: *Self) void { 171 for (self.sub_entries.all()) |entry| { 172 self.sub_entries.alloc.free(entry); 173 } 174 self.sub_entries.clear(); 175}