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