From 0a15af8d98771180e32402e68ea844f9f377cefb Mon Sep 17 00:00:00 2001 From: Gabriel Schneider Date: Sun, 9 Aug 2026 02:34:49 -0300 Subject: a pardes launched inside pardes hands its file to the outer one --- src/main.zig | 85 ++++++++++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 66 insertions(+), 19 deletions(-) (limited to 'src/main.zig') diff --git a/src/main.zig b/src/main.zig index 343742e3..472dda6e 100644 --- a/src/main.zig +++ b/src/main.zig @@ -1,6 +1,7 @@ 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; @@ -75,10 +76,21 @@ const help_text = \\ A FILE argument boots just that file instead. \\ --tty-toggle use Ctrl- to enter/leave tty mode \\ -l 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. \\ -h, --help show this help and exit \\ ; +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; @@ -108,8 +120,15 @@ fn nativeMain(init: std.process.Init) !void { var opts: pardes.Options = .{}; const arena = init.arena.allocator(); const args = try init.minimal.args.toSlice(arena); - if (args.len == 1) opts.tty_only = true; // bare `pardes` boots straight into tty mode - var positional = false; + // bare `pardes` boots straight into tty mode — and `pardes --nested` still + // counts as bare, because --nested says something about the session's + // relationship to its parent and nothing about its layout + if (args.len == 1 or (args.len == 2 and std.mem.eql(u8, args[1], "--nested"))) + opts.tty_only = true; + // 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]; @@ -129,30 +148,58 @@ fn nativeMain(init: std.process.Init) !void { i += 1; if (i >= args.len) return error.BadArgs; opts.load_path = args[i]; + } else if (std.mem.eql(u8, a, "--nested")) { + opts.nested = true; } 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 (a.len > 0 and a[0] != '-' and !positional) { - // one positional: a directory becomes the cwd shells spawn in - // (chdir succeeds only on dirs); anything else resolves as a file - positional = true; - 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), - else => return error.BadArgs, - } - } + } 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. + if (!opts.nested) 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), + else => return error.BadArgs, + } + } + } // Native shells opt into the user config; the sans-IO core 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. -- cgit v1.3