summaryrefslogtreecommitdiff
path: root/src/main.zig
diff options
context:
space:
mode:
Diffstat (limited to 'src/main.zig')
-rw-r--r--src/main.zig110
1 files changed, 101 insertions, 9 deletions
diff --git a/src/main.zig b/src/main.zig
index c60fb888..08beae86 100644
--- a/src/main.zig
+++ b/src/main.zig
@@ -85,6 +85,19 @@ const help_text =
\\ 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
+ \\ --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, send it input,
+ \\ fork its pane shells. 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
\\
;
@@ -128,14 +141,27 @@ fn nativeMain(init: std.process.Init) !void {
// 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, and neither says anything
- // about what should be on screen. Anything else (a FILE, -n, --tty) is
- // layout, and answers this question itself further down.
+ // 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=")) break false;
+ !std.mem.startsWith(u8, a, "--fs=") 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" (tty.zig
+ // `sessionName`) 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.
@@ -171,6 +197,22 @@ fn nativeMain(init: std.process.Init) !void {
opts.fs = a["--fs=".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;
@@ -186,7 +228,17 @@ fn nativeMain(init: std.process.Init) !void {
// 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.
- if (!opts.nested) if (nested.outer()) |outer_pid| {
+ //
+ // `--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);
@@ -228,14 +280,41 @@ fn nativeMain(init: std.process.Init) !void {
opts.startup_config = found.bytes;
opts.startup_config_path = found.path;
opts.config_dir = found.dir;
+ // `--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;
+ // `--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 only the tty one knows how to be one today.
+ // Comptime-folded, so a tty build carries none of this.
+ if (attach != null and pardes.platform != .tty) {
+ try std.Io.File.stderr().writeStreamingAll(init.io, "pardes: --attach needs the tty shell\n");
+ std.process.exit(1);
+ }
switch (pardes.platform) {
- .tty => try @import("tty/tty.zig").run(init, opts),
+ .tty => try @import("tty/tty.zig").run(init, opts, attach),
.gui => try @import("gui/gui.zig").run(init, opts),
// 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 its own app root in
- // the zig-p4 package, which imports this package's `pardes_p4` module.
- .web, .macos, .p4 => unreachable,
+ // 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,
}
}
@@ -277,6 +356,19 @@ test {
// 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");
if (comptime pardes.platform == .tty) {
_ = @import("tty/tty.zig");
// tty.zig calls the compositor only from its runtime loop, so merely