tangled
alpha
login
or
join now
brookjeynes.dev
/
jido
7
fork
atom
地圖 (Jido) is a lightweight Unix TUI file explorer designed for speed and simplicity.
7
fork
atom
overview
issues
pulls
pipelines
feat: move file loading logic to preview.zig
brookjeynes.dev
1 month ago
0457dd47
ff22e3be
+248
1 changed file
expand all
collapse all
unified
split
src
preview.zig
+248
src/preview.zig
···
1
1
const std = @import("std");
2
2
3
3
+
const App = @import("./app.zig");
4
4
+
const Archive = @import("./archive.zig");
5
5
+
const Image = @import("./image.zig");
6
6
+
const config = &@import("./config.zig").config;
7
7
+
3
8
pub const PreviewType = enum {
4
9
none,
5
10
text,
···
90
95
};
91
96
}
92
97
};
98
98
+
99
99
+
pub fn loadPreviewForCurrentEntry(app: *App) !void {
100
100
+
if (!config.preview_file) return;
101
101
+
102
102
+
const entry = (try app.directories.getSelected()) orelse return;
103
103
+
104
104
+
const path = try app.directories.dir.realpathAlloc(
105
105
+
app.alloc,
106
106
+
entry.name,
107
107
+
);
108
108
+
defer app.alloc.free(path);
109
109
+
110
110
+
if (app.preview_cache.get(path)) |_| {
111
111
+
return;
112
112
+
}
113
113
+
114
114
+
const preview = switch (entry.kind) {
115
115
+
.directory => try loadDirectoryPreview(app, entry),
116
116
+
.file => try loadFilePreview(app, entry),
117
117
+
else => PreviewData{ .none = {} },
118
118
+
};
119
119
+
120
120
+
try app.preview_cache.set(path, preview);
121
121
+
}
122
122
+
123
123
+
fn loadDirectoryPreview(app: *App, entry: std.fs.Dir.Entry) !PreviewData {
124
124
+
app.directories.clearChildEntries();
125
125
+
126
126
+
app.directories.populateChildEntries(entry.name) catch |err| {
127
127
+
const message = try std.fmt.allocPrint(
128
128
+
app.alloc,
129
129
+
"Failed to read directory entries - {}.",
130
130
+
.{err},
131
131
+
);
132
132
+
defer app.alloc.free(message);
133
133
+
app.notification.write(message, .err) catch {};
134
134
+
if (app.file_logger) |file_logger| {
135
135
+
file_logger.write(message, .err) catch {};
136
136
+
}
137
137
+
return PreviewData{ .none = {} };
138
138
+
};
139
139
+
140
140
+
var list = std.ArrayList([]const u8).init(app.alloc);
141
141
+
for (app.directories.child_entries.all()) |child| {
142
142
+
const owned = try app.alloc.dupe(u8, child);
143
143
+
try list.append(owned);
144
144
+
}
145
145
+
146
146
+
return PreviewData{ .directory = list };
147
147
+
}
148
148
+
149
149
+
fn loadFilePreview(app: *App, entry: std.fs.Dir.Entry) !PreviewData {
150
150
+
const file_ext = std.fs.path.extension(entry.name);
151
151
+
152
152
+
if (config.show_images) {
153
153
+
if (isImageExtension(file_ext)) {
154
154
+
return try loadImagePreview(app, entry);
155
155
+
}
156
156
+
}
157
157
+
158
158
+
if (std.mem.eql(u8, file_ext, ".pdf")) {
159
159
+
return try loadPdfPreview(app, entry);
160
160
+
}
161
161
+
162
162
+
if (Archive.ArchiveType.fromPath(entry.name)) |archive_type| {
163
163
+
return try loadArchivePreview(app, entry, archive_type);
164
164
+
}
165
165
+
166
166
+
return try loadTextPreview(app, entry);
167
167
+
}
168
168
+
169
169
+
fn loadTextPreview(app: *App, entry: std.fs.Dir.Entry) !PreviewData {
170
170
+
var file = app.directories.dir.openFile(
171
171
+
entry.name,
172
172
+
.{ .mode = .read_only },
173
173
+
) catch |err| {
174
174
+
const message = try std.fmt.allocPrint(
175
175
+
app.alloc,
176
176
+
"Failed to open file - {}.",
177
177
+
.{err},
178
178
+
);
179
179
+
defer app.alloc.free(message);
180
180
+
app.notification.write(message, .err) catch {};
181
181
+
if (app.file_logger) |file_logger| {
182
182
+
file_logger.write(message, .err) catch {};
183
183
+
}
184
184
+
return PreviewData{ .none = {} };
185
185
+
};
186
186
+
defer file.close();
187
187
+
188
188
+
var buffer: [4096]u8 = undefined;
189
189
+
const bytes = file.readAll(&buffer) catch |err| {
190
190
+
const message = try std.fmt.allocPrint(
191
191
+
app.alloc,
192
192
+
"Failed to read file contents - {}.",
193
193
+
.{err},
194
194
+
);
195
195
+
defer app.alloc.free(message);
196
196
+
app.notification.write(message, .err) catch {};
197
197
+
if (app.file_logger) |file_logger| {
198
198
+
file_logger.write(message, .err) catch {};
199
199
+
}
200
200
+
return PreviewData{ .none = {} };
201
201
+
};
202
202
+
203
203
+
if (std.unicode.utf8ValidateSlice(buffer[0..bytes])) {
204
204
+
const text = try app.alloc.dupe(u8, buffer[0..bytes]);
205
205
+
return PreviewData{ .text = text };
206
206
+
}
207
207
+
208
208
+
return PreviewData{ .none = {} };
209
209
+
}
210
210
+
211
211
+
fn loadImagePreview(app: *App, entry: std.fs.Dir.Entry) !PreviewData {
212
212
+
const path = try app.directories.dir.realpathAlloc(
213
213
+
app.alloc,
214
214
+
entry.name,
215
215
+
);
216
216
+
defer app.alloc.free(path);
217
217
+
218
218
+
app.images.mutex.lock();
219
219
+
const exists = app.images.cache.contains(path);
220
220
+
app.images.mutex.unlock();
221
221
+
222
222
+
if (!exists) {
223
223
+
const owned_path = try app.alloc.dupe(u8, path);
224
224
+
Image.processImage(app.alloc, app, owned_path) catch {
225
225
+
app.alloc.free(owned_path);
226
226
+
return PreviewData{ .none = {} };
227
227
+
};
228
228
+
}
229
229
+
230
230
+
return PreviewData{
231
231
+
.image = .{
232
232
+
.cache_path = try app.alloc.dupe(u8, path),
233
233
+
},
234
234
+
};
235
235
+
}
236
236
+
237
237
+
fn loadPdfPreview(app: *App, entry: std.fs.Dir.Entry) !PreviewData {
238
238
+
const path = try app.directories.dir.realpathAlloc(
239
239
+
app.alloc,
240
240
+
entry.name,
241
241
+
);
242
242
+
defer app.alloc.free(path);
243
243
+
244
244
+
const result = std.process.Child.run(.{
245
245
+
.allocator = app.alloc,
246
246
+
.argv = &[_][]const u8{
247
247
+
"pdftotext",
248
248
+
"-f",
249
249
+
"0",
250
250
+
"-l",
251
251
+
"5",
252
252
+
path,
253
253
+
"-",
254
254
+
},
255
255
+
.cwd_dir = app.directories.dir,
256
256
+
}) catch {
257
257
+
app.notification.write("No preview available. Install pdftotext to get PDF previews.", .err) catch {};
258
258
+
return PreviewData{ .none = {} };
259
259
+
};
260
260
+
defer app.alloc.free(result.stdout);
261
261
+
defer app.alloc.free(result.stderr);
262
262
+
263
263
+
if (result.term.Exited != 0) {
264
264
+
app.notification.write("No preview available. Install pdftotext to get PDF previews.", .err) catch {};
265
265
+
return PreviewData{ .none = {} };
266
266
+
}
267
267
+
268
268
+
const text = try app.alloc.dupe(u8, result.stdout);
269
269
+
return PreviewData{ .pdf = text };
270
270
+
}
271
271
+
272
272
+
fn loadArchivePreview(
273
273
+
app: *App,
274
274
+
entry: std.fs.Dir.Entry,
275
275
+
archive_type: Archive.ArchiveType,
276
276
+
) !PreviewData {
277
277
+
var file = app.directories.dir.openFile(
278
278
+
entry.name,
279
279
+
.{ .mode = .read_only },
280
280
+
) catch |err| {
281
281
+
const message = try std.fmt.allocPrint(
282
282
+
app.alloc,
283
283
+
"Failed to open archive - {}.",
284
284
+
.{err},
285
285
+
);
286
286
+
defer app.alloc.free(message);
287
287
+
app.notification.write(message, .err) catch {};
288
288
+
if (app.file_logger) |file_logger| {
289
289
+
file_logger.write(message, .err) catch {};
290
290
+
}
291
291
+
return PreviewData{ .none = {} };
292
292
+
};
293
293
+
defer file.close();
294
294
+
295
295
+
const archive_contents = Archive.listArchiveContents(
296
296
+
app.alloc,
297
297
+
file,
298
298
+
archive_type,
299
299
+
config.archive_traversal_limit,
300
300
+
) catch |err| {
301
301
+
const message = try std.fmt.allocPrint(
302
302
+
app.alloc,
303
303
+
"Failed to read archive: {s}",
304
304
+
.{@errorName(err)},
305
305
+
);
306
306
+
defer app.alloc.free(message);
307
307
+
app.notification.write(message, .err) catch {};
308
308
+
if (app.file_logger) |file_logger| {
309
309
+
file_logger.write(message, .err) catch {};
310
310
+
}
311
311
+
return PreviewData{ .none = {} };
312
312
+
};
313
313
+
314
314
+
if (config.sort_dirs) {
315
315
+
const sort_mod = @import("./sort.zig");
316
316
+
std.mem.sort(
317
317
+
[]const u8,
318
318
+
archive_contents.entries.items,
319
319
+
{},
320
320
+
sort_mod.string,
321
321
+
);
322
322
+
}
323
323
+
324
324
+
return PreviewData{ .archive = archive_contents.entries };
325
325
+
}
326
326
+
327
327
+
fn isImageExtension(ext: []const u8) bool {
328
328
+
const supported = [_][]const u8{
329
329
+
".png", ".jpg", ".jpeg", ".gif",
330
330
+
".bmp", ".tga", ".qoi", ".pam",
331
331
+
".pbm", ".pgm", ".ppm",
332
332
+
};
333
333
+
334
334
+
for (supported) |supported_ext| {
335
335
+
if (std.ascii.eqlIgnoreCase(ext, supported_ext)) {
336
336
+
return true;
337
337
+
}
338
338
+
}
339
339
+
return false;
340
340
+
}