1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
|
const std = @import("std");
const builtin = @import("builtin");
const pardes = @import("pardes.zig");
const nested = @import("nested.zig");
const is_emscripten = builtin.os.tag == .emscripten;
extern "c" fn emscripten_console_error(utf8: [*:0]const u8) void;
// emscripten: std.debug's default threaded-io singleton doesn't run on wasm;
// fail it (init errors surface via emscripten_console_error below). Native
// values match the std defaults. Same shim as the prototype's replay.zig.
pub const std_options_debug_threaded_io: ?*std.Io.Threaded = if (is_emscripten)
null
else
std.Io.Threaded.global_single_threaded;
pub const std_options_debug_io: std.Io = if (is_emscripten)
std.Io.failing
else
std_options_debug_threaded_io.?.io();
// Every std.log call in the process — ours and every dependency's — funnels
// through this one function. ghostty-vt narrates whatever it does not
// implement in the bytes a child writes to its pty (`debug(stream)`,
// `warning(stream): ignoring unimplemented CSI p`, `debug(kitty_gfx)`; opening
// yazi is worth several lines before it has drawn anything), and in the tty
// shell stderr IS the screen — those land on top of the rendered grid, and in
// the gui/web shells on the console. So drop the libraries at every level: an
// `err` painted over the UI is no better than a debug one. Only pardes' own
// scopes get through, because their messages carry detail the error returns
// don't (gui's SDL_GetError strings, dump's zon parse diagnostic) — .default
// is NOT one of them, ghostty and uucode both log unscoped. Set PARDES_LOG to
// get the real logger back: `PARDES_LOG=1 pardes 2>/tmp/pardes.log`.
pub const std_options: std.Options = .{ .logFn = logFn };
fn logFn(
comptime level: std.log.Level,
comptime scope: @EnumLiteral(),
comptime format: []const u8,
args: anytype,
) void {
if (scope != .gui and scope != .dump and std.c.getenv("PARDES_LOG") == null) return;
std.log.defaultLog(level, scope, format, args);
}
// A panic must restore the terminal (cooked mode, main screen, mouse off)
// before the trace prints, or it lands garbled in a raw alt screen. recover()
// no-ops unless the vaxis tty is live, so gui/tty share the handler.
//
// Then the same message and trace are appended to `<config dir>/crashes`
// BEFORE stderr gets them, because stderr is the one place this program cannot
// keep them: see crash.zig. Silent on every failure, so the fallback is
// exactly the behaviour that was here before.
pub const panic = if (is_emscripten) std.debug.FullPanic(std.debug.defaultPanic) else std.debug.FullPanic(struct {
fn call(msg: []const u8, ret_addr: ?usize) noreturn {
// ONCE. This handler is re-entered whenever something panics while it
// runs, and std's own trace printer does exactly that — `defaultPanic`
// survives its own recursion through a private `panic_stage`, and
// everything up here is in front of that guard. `recover()` is not
// idempotent: it closes the vaxis tty and never clears the global that
// says there is one, so a second call double-closes, which std answers
// with `recoverableOsBugDetected` and an `unreachable` in a Debug
// build. Measured with the crash file in place: one panic left TWO
// records, the real message and then "reached unreachable code" from
// this line under it.
if (!recovering.swap(true, .seq_cst)) @import("vaxis").recover();
@import("crash.zig").record(msg);
std.debug.defaultPanic(msg, ret_addr);
}
}.call);
/// Whether this process has already restored its terminal — see `panic` above,
/// and note that `debug.handleSegfault` below shares it: a SIGSEGV raised while
/// the panic handler runs must not double-close either.
var recovering: std.atomic.Value(bool) = .init(false);
// Fatal signals (SIGSEGV/SIGILL/SIGBUS/SIGFPE) bypass the panic handler and
// no defer/errdefer ever runs — hook std.debug's segfault path the same way
// so the terminal is restored before the trace prints.
pub const debug = if (is_emscripten) struct {} else struct {
pub fn handleSegfault(addr: ?usize, name: []const u8, opt_ctx: anytype) noreturn {
if (!recovering.swap(true, .seq_cst)) @import("vaxis").recover();
return std.debug.defaultHandleSegfault(addr, name, opt_ctx);
}
};
const help_text =
\\Usage: pardes [options] [file|dir]
\\
\\With no arguments the shell pane starts in tty mode. A FILE argument
\\(optionally file:LINE) opens it alone, filling the window; a DIRECTORY
\\argument becomes the cwd shells spawn in.
\\
\\Options:
\\ --tty start as one shell pane already in tty mode
\\ -n <count> initial shell panes: 1 (default) or 3 (classic).
\\ A FILE argument boots just that file instead.
\\ --tty-toggle <key> use Ctrl-<key> to enter/leave tty mode
\\ -l <dump.zon> load a dump of another instance (see Dump)
\\ --nested run a full session even inside another pardes.
\\ Without it, a pardes started inside a pardes
\\ hands its FILE argument to the outer one. This
\\ session will not serve its own children either.
\\ --fs serve acme's control filesystem for this session
\\ under $XDG_RUNTIME_DIR/pardes/<pid>, and export
\\ PARDES_FS and PARDES_PANE into every pane shell
\\ --fs=<dir> ...at <dir> instead. Must be absolute; pardes
\\ unmounts it on exit but leaves the directory
\\ --fs9 serve that same tree over 9P2000 on a unix socket
\\ at $XDG_RUNTIME_DIR/pardes-9p-<name>.sock, where
\\ <name> is the session name or this pid. Dial it
\\ with `9p -a <socket>` or mount with `9pfuse`.
\\ Independent of --fs: either, both or neither
\\ --fs9=<name> ...named <name> rather than derived. One path
\\ component: no '/' and nothing empty. Served by
\\ --detach sessions today; the tty and GUI shells
\\ still take --fs only
\\ --detach run this session with NO terminal of its own,
\\ serving frontends over a unix socket beside the
\\ nested-instance one. The core, the panes and the
\\ undo history outlive every frontend that attaches
\\ --detach=<name> ...named <name> rather than this process's pid, so
\\ a frontend can say which session it wants. One
\\ path component: no '/' and nothing empty
\\ --attach become a frontend of the one detached session
\\ that is running: draw its screen and send it
\\ input, and nothing else — the session owns its
\\ pane shells, its files and its watches. Works in
\\ both the terminal and the SDL window build.
\\ Several frontends may be attached at once and all
\\ see the same screen
\\ --attach=<name> ...of the session called <name>, which is what to
\\ use when more than one is running
\\ -h, --help show this help and exit
\\ --version print the version and exit
\\
;
/// Built at COMPTIME, because both halves are: `version` comes out of
/// `build.zig.zon` through the options module and `commit` out of `git` at
/// configure time, so there is nothing here to format at runtime and no buffer
/// to size. The commit is in parentheses when there is one and absent
/// otherwise — a build from a tarball says `pardes 0.0.1` and is not lying
/// about a revision it never had. See `pardes.version`/`pardes.commit`.
const version_text = if (pardes.commit) |c|
"pardes " ++ pardes.version ++ " (" ++ c ++ ")\n"
else
"pardes " ++ pardes.version ++ "\n";
const nested_text =
\\pardes: this shell is already inside pardes, and a pardes inside a pardes
\\is spicy. Name a file or a directory and the outer session opens it, or
\\pass --nested if you really want a second one in here.
\\
;
// The browser runtime calls a C main (exported below); everything else keeps
// the std.process.Init entry.
pub const main = if (is_emscripten) webMain else nativeMain;
comptime {
if (is_emscripten) @export(&emscriptenMain, .{ .name = "main" });
}
fn emscriptenMain(argc: c_int, argv: [*]?[*:0]u8) callconv(.c) c_int {
_ = argc;
_ = argv;
webMain() catch |err| {
var buf: [128]u8 = undefined;
const msg = std.fmt.bufPrintSentinel(&buf, "pardes web init failed: {t}", .{err}, 0) catch "pardes web init failed";
emscripten_console_error(msg.ptr);
return 1;
};
return 0;
}
// no argv in the browser: options stay default, state comes from the dump, and
// there is no session to attach to — a browser tab has no unix socket.
fn webMain() !void {
try @import("gui/gui.zig").run(.{}, .{}, null);
}
fn nativeMain(init: std.process.Init) !void {
// FIRST, before anything can log or panic. std's start code hands the
// debug/log stderr writer the process environ exactly as the kernel laid
// it out beside argv, and `std.debug.lockStderr` scans that block once,
// lazily, for NO_COLOR and CLICOLOR_FORCE. Lazily is the problem: libc
// may have rewritten the block by then. SDL_CreateWindow does, every
// time — `Wayland_ShowWindow` unsets XDG_ACTIVATION_TOKEN, and glibc's
// unsetenv compacts `environ` in place and leaves a null in the slot the
// captured length still counts. `Environ.scan` unwraps that null, so the
// FIRST log line or panic in the gui shell panicked inside the scan, and
// then the panic handler took the same path and deadlocked on the stderr
// lock it was already holding: "attempt to use null value" and a hang,
// with no trace and no message of its own. Every SDL_GetError report in
// gui.zig was that, which is to say unreachable.
//
// Locking and immediately unlocking here does the scan while the block is
// still the one std was given, and memoizes it. Nothing is written.
var stderr_probe: [64]u8 = undefined;
_ = std.debug.lockStderr(&stderr_probe);
std.debug.unlockStderr();
var opts: pardes.Options = .{};
const arena = init.arena.allocator();
const args = try init.minimal.args.toSlice(arena);
// bare `pardes` boots straight into tty mode — and so does a `pardes`
// carrying nothing but flags that say something about the SESSION rather
// than about its layout: --nested is about this session's relationship to
// its parent, --fs is about who may script it, --detach is about who may
// WATCH it, --attach is about whose screen this one is showing, and none of
// the four says anything about what should be on screen. Anything else (a
// FILE, -n, --tty) is layout, and answers this question itself further
// down.
opts.tty_only = for (args[1..]) |a| {
if (!std.mem.eql(u8, a, "--nested") and
!std.mem.eql(u8, a, "--fs") and
!std.mem.startsWith(u8, a, "--fs=") and
!std.mem.eql(u8, a, "--fs9") and
!std.mem.startsWith(u8, a, "--fs9=") and
!std.mem.eql(u8, a, "--detach") and
!std.mem.startsWith(u8, a, "--detach=") and
!std.mem.eql(u8, a, "--attach") and
!std.mem.startsWith(u8, a, "--attach=")) break false;
} else true;
// `--detach[=<name>]`: null when it was not given, so the empty string is
// free to mean "the default name" the way opts.fs uses it for a directory.
var detach: ?[]const u8 = null;
// ...and `--attach[=<name>]`, the same shape: null when it was not given,
// and the empty string means "the one session there is"
// (detached_client.resolve) rather than a session with no name.
var attach: ?[]const u8 = null;
// Kept RAW until every flag is parsed: classifying it means chdir'ing into
// a directory and recording nothing, and the nested client below still
// needs the word itself to resolve.
var positional: ?[:0]const u8 = null;
var i: usize = 1;
while (i < args.len) : (i += 1) {
const a = args[i];
if (std.mem.eql(u8, a, "--tty")) {
opts.tty_only = true;
} else if (std.mem.startsWith(u8, a, "--tty-toggle=")) {
opts.tty_toggle = parseCtrlKey(a["--tty-toggle=".len..]) orelse return error.BadArgs;
} else if (std.mem.eql(u8, a, "--tty-toggle")) {
i += 1;
if (i >= args.len) return error.BadArgs;
opts.tty_toggle = parseCtrlKey(args[i]) orelse return error.BadArgs;
} else if (std.mem.eql(u8, a, "-n")) {
i += 1;
if (i >= args.len) return error.BadArgs;
opts.shells = std.fmt.parseInt(u8, args[i], 10) catch return error.BadArgs;
} else if (std.mem.eql(u8, a, "-l")) {
i += 1;
if (i >= args.len) return error.BadArgs;
opts.load_path = args[i];
} else if (std.mem.eql(u8, a, "--fs")) {
opts.fs = "";
} else if (std.mem.startsWith(u8, a, "--fs=")) {
// `--fs=<dir>` and NEVER `--fs <dir>`, which is the one place this
// parser cannot follow --tty-toggle: --tty-toggle's argument is
// mandatory, so consuming the next word is unambiguous. `--fs` is
// useful bare, so a two-word form would make `pardes --fs README`
// mount at ./README and open no file — the flag would silently eat
// the FILE argument. One spelling, and it carries its own value.
opts.fs = a["--fs=".len..];
} else if (std.mem.eql(u8, a, "--fs9")) {
opts.fs9 = "";
} else if (std.mem.startsWith(u8, a, "--fs9=")) {
// One spelling that carries its own value, for the reason `--fs`
// gives directly above: the flag is useful bare, so a two-word
// form would make `pardes --fs9 README` a socket called README
// that opens no file.
opts.fs9 = a["--fs9=".len..];
} else if (std.mem.eql(u8, a, "--nested")) {
opts.nested = true;
} else if (std.mem.eql(u8, a, "--detach")) {
detach = "";
} else if (std.mem.startsWith(u8, a, "--detach=")) {
// `--detach=<name>` and never `--detach <name>`, for exactly the
// reason --fs gives above: the flag is useful bare, so a two-word
// form would make `pardes --detach README` a session called README
// that opens no file.
detach = a["--detach=".len..];
} else if (std.mem.eql(u8, a, "--attach")) {
attach = "";
} else if (std.mem.startsWith(u8, a, "--attach=")) {
// `--attach=<name>` and never `--attach <name>`, for the reason
// --fs states above and --detach repeats: the flag is useful bare,
// so a two-word form would make `pardes --attach README` an attach
// to a session called README that opens no file.
attach = a["--attach=".len..];
} else if (std.mem.eql(u8, a, "-h") or std.mem.eql(u8, a, "--help")) {
try std.Io.File.stdout().writeStreamingAll(init.io, help_text);
return;
} else if (std.mem.eql(u8, a, "--version")) {
try std.Io.File.stdout().writeStreamingAll(init.io, version_text);
return;
} else if (a.len > 0 and a[0] != '-' and positional == null) {
positional = a;
} else {
return error.BadArgs;
}
}
// Started INSIDE another pardes: hand it the file and get out of the way
// rather than stacking a second full-screen UI inside one of its panes.
// The word is resolved here rather than sent raw because the outer
// instance resolves against ITS panes' directories, which are not ours.
// A word naming nothing on disk sends nothing and falls through to the
// classification below, which already refuses it — no second UI either way.
//
// `--detach` is exempt for the same reason `--nested` is, arrived at from
// the other side: it stacks no UI at all. A detached session started from a
// pane is a session, not a request that the outer instance open something,
// and handing it our positional would leave the caller with no session.
//
// `--attach` is exempt for the mirror of that: it stacks a UI, but the UI
// is a session that already exists somewhere else, and handing our word to
// the outer instance would open the file in the WRONG session and leave
// the caller with no frontend.
if (!opts.nested and detach == null and attach == null) if (nested.outer()) |outer_pid| {
const word = positional orelse {
try std.Io.File.stderr().writeStreamingAll(init.io, nested_text);
std.process.exit(1);
};
var cwdbuf: [4096]u8 = undefined;
const cwd = std.c.getcwd(&cwdbuf, cwdbuf.len) orelse return error.BadArgs;
var realbuf: [4096]u8 = undefined;
const sent = switch (@import("look.zig").resolve(word, std.mem.span(@as([*:0]u8, @ptrCast(cwd))), &realbuf)) {
.dir => |d| nested.sendLook(outer_pid, d, 0),
.file => |t| nested.sendLook(outer_pid, t.path, t.at.line),
.image => |t| nested.sendLook(outer_pid, t.path, 0),
// an unreachable outer instance (an older build, a stale socket
// path) is not worth failing a launch over: run normally instead
else => false,
};
if (sent) return;
};
if (positional) |a| {
// a directory becomes the cwd shells spawn in (chdir succeeds only on
// dirs); anything else resolves as a file
if (std.c.chdir(a.ptr) != 0) {
var cwdbuf: [4096]u8 = undefined;
const cwd = std.c.getcwd(&cwdbuf, cwdbuf.len) orelse return error.BadArgs;
var realbuf: [4096]u8 = undefined;
switch (@import("look.zig").resolve(a, std.mem.span(@as([*:0]u8, @ptrCast(cwd))), &realbuf)) {
.file => |t| {
opts.file = try arena.dupe(u8, t.path);
opts.file_line = t.at.line;
},
.image => |t| opts.file = try arena.dupe(u8, t.path),
// Nothing of that name is there. A typo is not a reason to
// refuse to start: the session boots with one `+Errors` pane
// naming what was asked for (pardes.zig `missing_path`). The
// other arms stay `BadArgs` — a `.dir` here means `chdir`
// refused a directory that IS one, which is a permission
// problem and not a typo, and `.url`/`.pane` are targets no
// launch can act on.
.none => opts.missing_path = try arena.dupe(u8, a),
else => return error.BadArgs,
}
}
}
// Native shells opt into the user config; direct core callers and web keep
// Options' null default. Read it before entering either frontend so every
// builtin has run before that frontend can render its first frame.
const found = @import("user_config.zig").load(init.io, arena, init.environ_map);
opts.startup_config = found.bytes;
opts.startup_config_path = found.path;
opts.config_dir = found.dir;
// The panic handler above writes beside that init file, and this is the
// only place it can learn where that is — it runs with no `Options` in
// reach. Set for every native entry through this file, including the
// `--detach` daemon below, whose stderr nobody is reading.
if (found.dir) |d| @import("crash.zig").setDir(d);
// `--detach` is the core with no terminal and `--attach` is a terminal
// with no core, so the two together are a contradiction with no useful
// reading. Refused rather than resolved by declaration order, which would
// silently drop whichever flag lost.
if (detach != null and attach != null) return error.BadArgs;
// `--fs` mounts the acme control filesystem, and it needs a CORE to serve.
// `--attach` has none — it is a terminal whose state lives in another
// process — so the flag there would be parsed, stored, and served by
// nobody. Refused rather than dropped, and it is the stronger case of the
// line above: a contradiction is at least visible, whereas a silently
// dropped mount is invisible until someone waits for a directory that will
// never appear.
//
// `--detach` used to be refused here too, and is not any more. The reason
// given was that `push_fs_reply` was one of the host methods the detached
// core deliberately left null; it no longer is. A daemon mounts its own
// /dev/fuse and polls it in the same `poll(2)` as its frontends and its
// pane shells, which costs it one descriptor and no thread — strictly less
// than the desktop shells pay. `--detach --fs` is now the configuration
// that most wants a control filesystem, because it is the one whose panes
// outlive every terminal that could otherwise have scripted them.
if (opts.fs != null and attach != null) return error.BadArgs;
// ...and `--fs9` for exactly that reason and no other: it is the same tree
// over a different transport, and an `--attach` has no core to serve it
// from either. Checked separately rather than folded into the line above
// so that neither flag's refusal is a side effect of the other's — they
// are independent everywhere else.
if (opts.fs9 != null and attach != null) return error.BadArgs;
// `--detach` replaces the frontend rather than choosing among them: the
// core runs here, with no terminal, and the frontends are elsewhere on a
// socket (src/detached/). It is checked before `platform` because it is not
// a shell — the tty and gui builds can both be asked for one.
if (detach) |name| {
// Bare `--detach` is named by this process's pid, which is the one name
// nobody has to be told and no two sessions can share. Unsigned: `{d}`
// prints a leading '+' for a positive SIGNED int, which is nested.zig's
// note about the same cast.
const named = if (name.len != 0)
name
else
try std.fmt.allocPrint(arena, "{d}", .{@as(u32, @intCast(std.c.getpid()))});
return @import("detached/server.zig").run(init, opts, named);
}
// A frontend is a SHELL, and the two native ones — a terminal and an SDL
// window — both know how to be one. The browser has no unix socket to
// reach a session over and the AppKit shell is entered by its own host
// rather than through this file, so neither is wired for it; the check is
// comptime-folded, so a tty or gui build carries none of it.
if (attach != null and pardes.platform != .tty and pardes.platform != .gui) {
try std.Io.File.stderr().writeStreamingAll(init.io, "pardes: --attach needs the tty or gui shell\n");
std.process.exit(1);
}
switch (pardes.platform) {
.tty => try @import("tty/tty.zig").run(init, opts, attach),
.gui => try @import("gui/gui.zig").run(init, opts, attach),
// Every other shell is entered by its host and never links this file
// at all: the browser through src/web.zig, the macOS app through
// src/macos.zig, and the ESP32-P4 firmware through src/esp32p4/app.zig,
// which is a root of its own in this repository and links the
// `pardes-esp32p4` object over the C ABI in src/esp32p4.zig.
.web, .macos, .esp32p4 => unreachable,
}
}
fn parseCtrlKey(raw: []const u8) ?u21 {
var value = std.mem.trim(u8, raw, " \t\r\n");
if (value.len >= 5 and std.ascii.eqlIgnoreCase(value[0..5], "ctrl-")) {
value = value[5..];
} else if (value.len >= 5 and std.ascii.eqlIgnoreCase(value[0..5], "ctrl+")) {
value = value[5..];
} else if (value.len == 2 and value[0] == '^') {
value = value[1..];
}
if (value.len != 1) return null;
const c = std.ascii.toLower(value[0]);
if (c < 'a' or c > 'z' or c == 'c') return null;
return c;
}
// The shells are imported inside main(), which a test build never analyses —
// so their inline tests need naming here to exist at all. Each shell only
// compiles when selected (GUI @cImports SDL; TTY imports vaxis), hence the
// comptime gates. Naming a file gets THAT file's tests and no further:
// fonts.zig is imported by gui.zig, builtins.zig and the macOS host, and still
// needs its own line here.
test {
_ = @import("user_config.zig");
// Reached only from the panic handler and from `nativeMain`, neither of
// which a test build analyses — so without this line the crash file has no
// test at all.
_ = @import("crash.zig");
_ = @import("allocators.zig");
_ = @import("fs_service.zig");
// acme's control filesystem, both halves, and NOT their own b.addTest
// modules in build.zig the way temp_file/nested/fonts are: both reach
// src/pardes.zig (acmefs takes a *Pardes, fuse.zig speaks its Req/Reply),
// so a standalone module would have to re-wire ghostty-vt, tree-sitter, the
// themes and every option the core imports. This module already has them.
//
// fuse.zig genuinely needs its name here (nothing the core analyses reaches
// it — only the shells import it). acmefs.zig does not today, because
// pardes.zig re-exports it unconditionally; it is named anyway, because the
// day that re-export grows a comptime gate is the day 23 tests disappear in
// silence. That is the fonts.zig story above, told once already.
_ = @import("acmefs.zig");
_ = @import("fuse.zig");
// The detached-session transport (src/detached/), same story as fuse.zig
// above: it speaks the core's Event/Surface, so it belongs in THIS module
// rather than a standalone b.addTest, and nothing the core analyses reaches
// it. A TTY build does — tty.zig imports client.zig for `--attach` — but
// these names are what makes the transport's tests exist in every other
// build too, and `--detach` is not a tty-only feature.
// client.zig's own tests drive a real `Session` over a real socket, so
// naming it reaches server.zig too — but server.zig is named anyway, for
// the acmefs.zig reason: the day client.zig stops importing it is the day
// those tests vanish in silence.
_ = @import("detached/wire.zig");
_ = @import("detached/server.zig");
_ = @import("detached/client.zig");
// The 9P listener, and it needs its name here for the reason the
// panel_compositor line below states rather than the fuse.zig one above:
// detached/server.zig imports it, but naming a file does not make Zig
// analyse the tests of what IT imports — measured, by three tests that
// compiled and never ran. A standalone b.addTest is not an option either,
// because this file reaches pardes.zig (`Server(acmefs)`); src/9p.zig, the
// half that does NOT, has one in build.zig.
_ = @import("fs9_service.zig");
// Its client twin, and it needs its name here for the same reason with the
// same measurement behind it: builtins.zig imports it for the `9p` word,
// and naming a file does not make Zig analyse the tests of what IT
// imports. The protocol half's tests are in src/9p.zig's own b.addTest;
// these are the host's — the argument split, the two dial spellings, and
// the immediate refusal when nothing is listening.
_ = @import("fs9_client.zig");
if (comptime pardes.platform == .tty) {
_ = @import("tty/tty.zig");
// tty.zig calls the compositor only from its runtime loop, so merely
// naming the shell does not make Zig analyse the compositor's tests.
_ = @import("tty/panel_compositor.zig");
}
if (comptime pardes.platform == .gui) _ = @import("gui/gui.zig");
if (comptime pardes.font_picker) _ = @import("fonts.zig");
}
|