summaryrefslogtreecommitdiff
path: root/src/main.zig
diff options
context:
space:
mode:
Diffstat (limited to 'src/main.zig')
-rw-r--r--src/main.zig71
1 files changed, 57 insertions, 14 deletions
diff --git a/src/main.zig b/src/main.zig
index 4b1910bd..1cbdf24c 100644
--- a/src/main.zig
+++ b/src/main.zig
@@ -112,6 +112,16 @@ const nested_text =
\\
;
+/// $PARDES_PID named a live editor, so this shell IS inside one, but the Look
+/// never got there. Saying so beats quietly opening the second editor that
+/// $PARDES_PID exists to prevent.
+const unreachable_text =
+ \\pardes: this shell is inside pardes, but that session did not take the
+ \\file. Check that it is still running, or pass --nested to start a second
+ \\editor in here anyway.
+ \\
+;
+
// 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;
@@ -260,6 +270,10 @@ fn nativeMain(init: std.process.Init) !void {
}
}
opts.tty_only = explicit_tty or session_only;
+ // The real bare-tty startup wants the editor half present from the first
+ // frame, so it names that layout; a core booted for a test names none and
+ // gets the raw shell by itself.
+ if (opts.tty_only) opts.boot = .tty;
opts.mounts = mounts[0..mounts_len];
if (detach != null and attach != null)
badArgs(init.io, "--detach and --attach are opposites: one runs the session, the other joins one", .{});
@@ -272,31 +286,60 @@ fn nativeMain(init: std.process.Init) !void {
if (opts.mounts.len != 0 and attach != null)
badArgs(init.io, "--mount configures a session's own core, and --attach has none of its own", .{});
if (!new_session and !opts.nested and detach == null and attach == null) forwarding: {
- const enabled = std.c.getenv("PARDES_FORWARD_LOOK") orelse break :forwarding;
- if (!std.mem.eql(u8, std.mem.span(enabled), "1")) break :forwarding;
- const dial = std.mem.span(std.c.getenv("PARDES_9P") orelse break :forwarding);
- ninep_io.Client.validateDial(dial) catch break :forwarding;
- const pane_text = std.mem.span(std.c.getenv("PARDES_PANE") orelse break :forwarding);
- for (pane_text) |byte| if (!std.ascii.isDigit(byte)) break :forwarding;
- const serial = std.fmt.parseInt(u32, pane_text, 10) catch break :forwarding;
- if (serial == 0) break :forwarding;
- var ctl_buf: [64]u8 = undefined;
- const ctl = try std.fmt.bufPrint(&ctl_buf, "/pane/{d}/ctl", .{serial});
+ // Am I inside a pardes at all? That is $PARDES_PID and nothing else —
+ // one variable, exported by every session that means to take its
+ // children's Looks, so a session that could not open a socket is still
+ // recognisably the session this shell is inside. A pid naming no live
+ // process is a shell that outlived its editor (a daemon started from a
+ // pane, a stale environment carried into a new terminal), which is not
+ // nested at all.
+ const owner = std.mem.span(std.c.getenv("PARDES_PID") orelse break :forwarding);
+ const pid = std.fmt.parseInt(std.c.pid_t, owner, 10) catch break :forwarding;
+ if (pid <= 0 or std.c.kill(pid, @enumFromInt(0)) != 0) break :forwarding;
+
+ // Can I reach it? That is the other two, and it is a separate answer.
+ const Parent = struct { dial: []const u8, serial: u32 };
+ const found: ?Parent = reaching: {
+ const dial = std.mem.span(std.c.getenv("PARDES_9P") orelse break :reaching null);
+ ninep_io.Client.validateDial(dial) catch break :reaching null;
+ const pane_text = std.mem.span(std.c.getenv("PARDES_PANE") orelse break :reaching null);
+ for (pane_text) |byte| if (!std.ascii.isDigit(byte)) break :reaching null;
+ const serial = std.fmt.parseInt(u32, pane_text, 10) catch break :reaching null;
+ if (serial == 0) break :reaching null;
+ break :reaching .{ .dial = dial, .serial = serial };
+ };
+ const parent = found orelse {
+ try std.Io.File.stderr().writeStreamingAll(init.io, unreachable_text);
+ std.process.exit(1);
+ };
+ // The pane's `look` file: one line, and the line is the clicked text
+ // itself, which is what a right click in that pane would have been.
+ var look_buf: [64]u8 = undefined;
+ const look = try std.fmt.bufPrint(&look_buf, "/pane/{d}/look", .{parent.serial});
const word = positional orelse {
var tag_buf: [64]u8 = undefined;
- const tag = try std.fmt.bufPrint(&tag_buf, "/pane/{d}/tag", .{serial});
- const contents = ninep_io.Client.read(arena, dial, tag, tag) catch break :forwarding;
+ const tag = try std.fmt.bufPrint(&tag_buf, "/pane/{d}/tag", .{parent.serial});
+ const contents = ninep_io.Client.read(arena, parent.dial, tag, tag) catch {
+ try std.Io.File.stderr().writeStreamingAll(init.io, unreachable_text);
+ std.process.exit(1);
+ };
arena.free(contents);
try std.Io.File.stderr().writeStreamingAll(init.io, nested_text);
std.process.exit(1);
};
+ // A word this launch cannot turn into a path is an argument problem
+ // rather than a nesting one: fall through and let the session that
+ // starts report it, the way `pardes typo` outside pardes does.
if (std.mem.indexOfAny(u8, word, "\r\n") != null) break :forwarding;
const target = @import("look.zig").parsePathLine(word);
var realbuf: [4096]u8 = undefined;
const path = if (pardes.filesystem.isVirtual(target.path)) target.path else (pardes.filesystem.resolveOs(target.path, &realbuf) orelse break :forwarding).path;
var command_buf: [8192]u8 = undefined;
- const command = std.fmt.bufPrint(&command_buf, "look {s}{s}\n", .{ path, word[target.path.len..] }) catch break :forwarding;
- ninep_io.Client.write(arena, dial, ctl, command) catch break :forwarding;
+ const command = std.fmt.bufPrint(&command_buf, "{s}{s}\n", .{ path, word[target.path.len..] }) catch break :forwarding;
+ ninep_io.Client.write(arena, parent.dial, look, command) catch {
+ try std.Io.File.stderr().writeStreamingAll(init.io, unreachable_text);
+ std.process.exit(1);
+ };
return;
}
if (positional) |a| {