地圖 (Jido) is a lightweight Unix TUI file explorer designed for speed and simplicity.
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}