diff options
| author | Gabriel Schneider <[email protected]> | 2026-09-29 05:22:35 -0300 |
|---|---|---|
| committer | Gabriel Schneider <[email protected]> | 2026-10-01 00:12:16 -0300 |
| commit | e9eae368dbbe855ad2f409913ff0d44d5ac32bd8 (patch) | |
| tree | a72ba7ecd7051d72f7685e948205d7d60aa44b21 /src/pardes.zig | |
| parent | 5002c612298fe645791218059a69d2bc80a42d2d (diff) | |
| download | pardes-e9eae368dbbe855ad2f409913ff0d44d5ac32bd8.tar.gz pardes-e9eae368dbbe855ad2f409913ff0d44d5ac32bd8.zip | |
Tty takes a directory as long as a path may be
A terminal's directory rode inline in the spawn effect, capped at 256
bytes, so Tty in a directory of about 377 bytes said path too long. A
directory past the inline buffer now goes out of line (Pardes.spawn_dirs,
given to the host when the spawn is performed), up to the host's path
limit (4095); past that, Tty says its directory is longer than a path
may be. The effect queue stays its size.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Diffstat (limited to 'src/pardes.zig')
| -rw-r--r-- | src/pardes.zig | 36 |
1 files changed, 29 insertions, 7 deletions
diff --git a/src/pardes.zig b/src/pardes.zig index 9f192004..a55a22e0 100644 --- a/src/pardes.zig +++ b/src/pardes.zig @@ -2139,10 +2139,19 @@ test "owned cwd preserves long paths aliases and failed updates" { const original = pane.cwdSlice(); try pane.setOwnedCwd(original); try std.testing.expectEqualStrings(&path, pane.cwdSlice()); - const effects = p.effects_len; + // A terminal's directory may be as long as a path is (PATH_MAX): one + // past the effect's buffer goes out of line (spawn_dirs); only one past + // the host's path limit is too long. const free = p.freeSlot().?; - try std.testing.expectError(error.PathTooLong, p.newShell(free, path[0 .. effect_path_cap + 1])); + _ = try p.newShell(free, path[0..]); + try std.testing.expectEqualStrings(&path, p.spawn_dirs[free].?); + try p.removePane(free, null); + while (p.nextEffect()) |_| {} + var too_long: [limits.host_path_cap + 1]u8 = @splat('d'); + too_long[0] = '/'; + try std.testing.expectError(error.PathTooLong, p.newShell(free, &too_long)); try std.testing.expect(p.panes[free] == null); + const effects = p.effects_len; exec.saveTo(p, 0, "file.txt"); try std.testing.expectEqual(effects, p.effects_len); try std.testing.expect(std.mem.indexOf(u8, pane.msg[0..pane.msg_len], "path too long") != null); @@ -4304,6 +4313,9 @@ pub const Pardes = struct { /// (`sessionDir`), as acme's row and column tags do. exec_header: bool = false, session_dir: [limits.host_path_cap]u8 = undefined, + /// A spawn's directory too long for the effect's inline buffer, by pane + /// slot: the effect then carries none, and perform gives the host this. + spawn_dirs: [MAX_PANES]?[]u8 = @splat(null), session_dir_len: ?u16 = null, /// acme's activecol (dat.c:37), by column serial, 0 for none: the /// column last typed in (acme.c:487), clicked in with the select button @@ -4647,6 +4659,7 @@ pub const Pardes = struct { if (p.last_dump) |d| gpa.free(d); if (p.custom_theme) |theme_value| std.zon.parse.free(gpa, theme_value); if (p.chord_arg) |a| gpa.free(a); + for (p.spawn_dirs) |d| if (d) |dir| gpa.free(dir); if (p.pipe.wait) |*wait| wait.deinit(gpa); p.locations_cache.deinit(p.tree_sitter_gpa); p.fs.deinit(gpa); @@ -4789,20 +4802,29 @@ pub const Pardes = struct { pub fn newShell(p: *Pardes, id: usize, cwd: []const u8) !*Pane { std.debug.assert(p.panes[id] == null); - if (cwd.len > effect_path_cap) return error.PathTooLong; + if (cwd.len > limits.host_path_cap) return error.PathTooLong; const pane = try panes.Terminal.create(p.gpa, p.screen_w, p.screen_h); panes.Terminal.armShellSpawn(pane); p.installPane(id, pane); - p.emit(.{ .spawn = .{ .pane = @intCast(id), .serial = pane.serial, .cwd = .from(cwd) } }); + p.emitSpawn(id, pane.serial, cwd); return pane; } + /// A child for pane `id` in `cwd`: a directory up to PATH_MAX, the ones + /// past the effect's inline buffer kept here for perform to give. + pub fn emitSpawn(p: *Pardes, id: usize, serial: u32, cwd: []const u8) void { + if (p.spawn_dirs[id]) |old| p.gpa.free(old); + p.spawn_dirs[id] = null; + if (cwd.len > effect_path_cap) p.spawn_dirs[id] = p.gpa.dupe(u8, cwd) catch null; + p.emit(.{ .spawn = .{ .pane = @intCast(id), .serial = serial, .cwd = .from(if (cwd.len > effect_path_cap) "" else cwd) } }); + } + /// A command pane: a terminal whose child is `$SHELL -c line` rather /// than an interactive shell, left showing its output and `exit N` when /// it is done (exec.zig runCommand). pub fn newCommand(p: *Pardes, id: usize, cwd: []const u8, line: []const u8) !*Pane { std.debug.assert(p.panes[id] == null); - if (cwd.len > effect_path_cap) return error.PathTooLong; + if (cwd.len > limits.host_path_cap) return error.PathTooLong; const owned = try p.gpa.dupe(u8, line); errdefer p.gpa.free(owned); const pane = try panes.Terminal.create(p.gpa, p.screen_w, p.screen_h); @@ -4813,7 +4835,7 @@ pub const Pardes = struct { // directory's commands reuse it; the child's own cd does not move it. pane.setOwnedCwd(cwd) catch {}; p.installPane(id, pane); - p.emit(.{ .spawn = .{ .pane = @intCast(id), .serial = pane.serial, .cwd = .from(cwd) } }); + p.emitSpawn(id, pane.serial, cwd); return pane; } @@ -5225,7 +5247,7 @@ pub const Pardes = struct { switch (e) { // The host reads the pane (its command, its shell) as it forks, // so the slot must still hold the pane the spawn was for. - .spawn => |s| if (if (p.panes[s.pane]) |pane| pane.serial != s.serial else true) {} else if (v.spawn) |f| f(p.host.ctx, s.pane, s.cwd.slice()) else { + .spawn => |s| if (if (p.panes[s.pane]) |pane| pane.serial != s.serial else true) {} else if (v.spawn) |f| f(p.host.ctx, s.pane, if (s.cwd.len == 0) (p.spawn_dirs[s.pane] orelse "") else s.cwd.slice()) else { p.fallback.spawned[s.pane] = true; }, // A pane with no child is silent: nothing invents output on its |
