地圖 (Jido) is a lightweight Unix TUI file explorer designed for speed and simplicity.
1const std = @import("std");
2const builtin = @import("builtin");
3const options = @import("options");
4const App = @import("app.zig");
5const FileLogger = @import("file_logger.zig");
6const vaxis = @import("vaxis");
7const config = &@import("./config.zig").config;
8const resolvePath = @import("./commands.zig").resolvePath;
9
10pub const panic = vaxis.panic_handler;
11const help_menu =
12 \\Usage: jido
13 \\
14 \\a lightweight Unix TUI file explorer
15 \\
16 \\Flags:
17 \\ -h, --help Show help information and exit.
18 \\ -v, --version Print version information and exit.
19 \\ --entry-dir=PATH Open jido at chosen dir.
20 \\ --choose-dir Makes jido act like a directory chooser. When jido
21 \\ quits, it will write the name of the last visited
22 \\ directory to STDOUT.
23 \\
24;
25
26pub const std_options: std.Options = .{
27 .log_scope_levels = &.{
28 .{ .scope = .vaxis, .level = .warn },
29 .{ .scope = .vaxis_parser, .level = .warn },
30 },
31};
32
33const Options = struct {
34 help: bool = false,
35 version: bool = false,
36 @"choose-dir": bool = false,
37 @"entry-path": []const u8 = ".",
38
39 fn optKind(a: []const u8) enum { short, long, positional } {
40 if (std.mem.startsWith(u8, a, "--")) return .long;
41 if (std.mem.startsWith(u8, a, "-")) return .short;
42 return .positional;
43 }
44};
45
46pub fn main() !void {
47 var gpa = std.heap.GeneralPurposeAllocator(.{}){};
48 defer {
49 const deinit_status = gpa.deinit();
50 if (deinit_status == .leak) {
51 std.log.err("memory leak", .{});
52 }
53 }
54 const alloc = gpa.allocator();
55
56 var last_dir: ?[]const u8 = null;
57 var entry_path_buf: [std.fs.max_path_bytes]u8 = undefined;
58
59 var opts = Options{};
60 var args = std.process.args();
61 _ = args.skip();
62 while (args.next()) |arg| {
63 switch (Options.optKind(arg)) {
64 .short => {
65 const str = arg[1..];
66 for (str) |b| {
67 switch (b) {
68 'v' => opts.version = true,
69 'h' => opts.help = true,
70 else => {
71 std.log.err("Invalid opt: '{c}'", .{b});
72 std.process.exit(1);
73 },
74 }
75 }
76 },
77 .long => {
78 var split = std.mem.splitScalar(u8, arg[2..], '=');
79 const opt = split.first();
80 const val = split.rest();
81 if (std.mem.eql(u8, opt, "version")) {
82 opts.version = true;
83 } else if (std.mem.eql(u8, opt, "help")) {
84 opts.help = true;
85 } else if (std.mem.eql(u8, opt, "choose-dir")) {
86 opts.@"choose-dir" = true;
87 } else if (std.mem.eql(u8, opt, "entry-dir")) {
88 const path = if (std.mem.eql(u8, val, "")) "." else val;
89 var dir = try std.fs.cwd().openDir(".", .{ .iterate = true });
90 defer dir.close();
91 opts.@"entry-path" = resolvePath(&entry_path_buf, path, dir);
92 }
93 },
94 .positional => {
95 std.log.err("Invalid opt: '{s}'. Jido does not take positional arguments.", .{arg});
96 std.process.exit(1);
97 },
98 }
99 }
100
101 if (opts.help) {
102 std.debug.print(help_menu, .{});
103 return;
104 }
105
106 if (opts.version) {
107 std.debug.print("jido v{f}\n", .{options.version});
108 return;
109 }
110
111 {
112 var app = App.init(alloc, opts.@"entry-path") catch {
113 vaxis.recover();
114 std.process.exit(1);
115 };
116 defer app.deinit();
117
118 config.parse(alloc, &app) catch |err| switch (err) {
119 error.SyntaxError => {
120 app.notification.write("Encountered a syntax error while parsing the config file.", .err) catch {
121 std.log.err("Encountered a syntax error while parsing the config file.", .{});
122 };
123 },
124 error.InvalidCharacter => {
125 app.notification.write("One or more overriden keybinds are invalid.", .err) catch {
126 std.log.err("One or more overriden keybinds are invalid.", .{});
127 };
128 },
129 error.DuplicateKeybind => {
130 // Error logged in function
131 },
132 else => {
133 const message = try std.fmt.allocPrint(alloc, "Encountend an unknown error while parsing the config file - {}", .{err});
134 defer alloc.free(message);
135
136 app.notification.write(message, .err) catch {
137 std.log.err("Encountend an unknown error while parsing the config file - {}", .{err});
138 };
139 },
140 };
141
142 app.file_logger = if (config.config_dir) |dir| FileLogger.init(dir) else logger: {
143 std.log.err("Failed to initialise file logger - no config directory found", .{});
144 break :logger null;
145 };
146 app.notification.loop = &app.loop;
147
148 try app.run();
149
150 if (opts.@"choose-dir") {
151 last_dir = alloc.dupe(u8, try app.directories.fullPath(".")) catch null;
152 }
153 }
154
155 // Must be printed after app has deinit as part of that process clears
156 // the screen.
157 if (last_dir) |path| {
158 var stdout_buffer: [std.fs.max_path_bytes]u8 = undefined;
159 var stdout_writer = std.fs.File.stdout().writer(&stdout_buffer);
160 const stdout = &stdout_writer.interface;
161 stdout.print("{s}\n", .{path}) catch {};
162 stdout.flush() catch {};
163
164 alloc.free(path);
165 }
166}