From 97efe2d711b8b357157db2cb3402c3488064117a Mon Sep 17 00:00:00 2001 From: Gabriel Schneider Date: Thu, 1 Oct 2026 21:25:38 -0300 Subject: A refused Putall is answered once the other saves have landed; a bad Tab width is invalid (EINVAL), from a pane's exec too; Incl refuses a relative or missing directory and takes - only alone Co-Authored-By: Claude Opus 5.5 --- src/9p_io.zig | 17 ++++++++++++++--- src/builtins.zig | 14 ++++++++++++++ src/exec.zig | 2 +- src/ninep/ctl.zig | 30 ++++++++++++++++++++++++++++++ 4 files changed, 59 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/9p_io.zig b/src/9p_io.zig index 671a1852..46bbb2ba 100644 --- a/src/9p_io.zig +++ b/src/9p_io.zig @@ -316,9 +316,20 @@ pub const Listener = struct { // mount: 9ns answers nothing else on it while a clunk is out, and // the editor's step may be out opening a file through that mount. if (fills) return conn.reply(&reply, core.fsPayload(reply)); - // A refusal answers at once: its text may be in the core's one - // buffer for it, which a request run while this one waited would - // write over. + // A refusal that left saves going (Putall's one refused among the + // rest) is answered once they have landed, its words copied out of + // the core's one buffer for them, which a request run meanwhile + // could write over. + if (reply.status == .err and core.effects_len != 0 and !core.quit) { + var kept: [320]u8 = undefined; + const words = kept[0..@min(reply.ename.len, kept.len)]; + @memcpy(words, reply.ename[0..words.len]); + var waited = reply; + waited.ename = words; + pardes.turn.awaitSettled(epoch); + return conn.reply(&waited, ""); + } + // Any other refusal answers at once, for the same buffer's sake. if (reply.status == .err) return conn.reply(&reply, ""); // A write that quits the editor (Kill) is answered now: the editor // is on its way out and will settle nothing this could wait for, diff --git a/src/builtins.zig b/src/builtins.zig index 129dd659..70ca93a1 100644 --- a/src/builtins.zig +++ b/src/builtins.zig @@ -1191,6 +1191,20 @@ pub const Incl = struct { p.incl.clearRetainingCapacity(); return p.setMessage(c.id, "Incl: none"); } + // All checked before any is taken: `-` clears, alone; a directory + // is absolute (or ~) and there. + var check = std.mem.tokenizeAny(u8, typed, " \t"); + while (check.next()) |dir_word| { + var said: [320]u8 = undefined; + const shown = dir_word[0..@min(dir_word.len, 200)]; + if (std.mem.eql(u8, dir_word, "-")) return p.reportFailure(c.id, "Incl: invalid: - clears the list, alone"); + if (dir_word[0] != '/' and dir_word[0] != '~') + return p.reportFailure(c.id, std.fmt.bufPrint(&said, "Incl: invalid directory: {s} is relative", .{shown}) catch "Incl: invalid directory"); + var home_buf: [4096]u8 = undefined; + const expanded = @import("fs.zig").expandHome(dir_word, &home_buf); + if (comptime pardes.hosted) if (@import("fs.zig").localPath(expanded)) |local| if (!exec.isDirectory(local)) + return p.reportFailure(c.id, std.fmt.bufPrint(&said, "Incl: no such directory: {s}", .{shown}) catch "Incl: no such directory"); + } var words = std.mem.tokenizeAny(u8, typed, " \t"); while (words.next()) |dir_word| { var home_buf: [4096]u8 = undefined; diff --git a/src/exec.zig b/src/exec.zig index 9aa8a8cb..d6834797 100644 --- a/src/exec.zig +++ b/src/exec.zig @@ -1123,7 +1123,7 @@ pub fn applySettingBuiltin(p: *Pardes, setting: config.Runtime.Setting, arg: ?[] var said: [32]u8 = undefined; return p.setMessage(p.active, std.fmt.bufPrint(&said, "Tab {d}", .{p.settings.tab}) catch "Tab"); } else if (!p.settings.apply(setting, arg)) { - p.reportFailure(p.active, "Tab: takes " ++ comptime config.Runtime.takes(.tab).?); + p.reportFailure(p.active, "Tab: invalid tab width: takes " ++ comptime config.Runtime.takes(.tab).?); }, // Said with what it takes: its words, its range. else => if (!p.settings.apply(setting, arg)) { diff --git a/src/ninep/ctl.zig b/src/ninep/ctl.zig index ae68ed91..274caf88 100644 --- a/src/ninep/ctl.zig +++ b/src/ninep/ctl.zig @@ -2025,6 +2025,36 @@ test "Zerox twins dump their text once and come back on one buffer from a Restor try testing.expect(std.mem.startsWith(u8, files[0].content, "one\ntwo\nunsaved ")); } +test "a bad Tab width and a bad Incl directory are refused invalid, changing nothing; a twin's rename is each twin's" { + defer config.tab_now = config.tab_width; + const p = try withFile(testing.allocator, "x\n"); + defer p.deinit(); + const root_ctl = @intFromEnum(tree.TopFile.ctl); + for ([_][]const u8{ "Tab 0\n", "Tab 17\n", "Tab x\n" }) |line| { + try testing.expectEqual(E.INVAL, wr(p, root_ctl, line).errno()); + // Run from a pane, as a tag click runs it: said invalid too. + const r = wr(p, Node.of(serialOf(p), .exec), line); + try testing.expectEqual(E.INVAL, r.errno()); + try testing.expect(std.mem.indexOf(u8, r.reply.ename, "invalid tab width") != null); + } + try testing.expectEqual(config.tab_width, config.tab_now); + const before = p.incl.items.len; + try testing.expectEqual(E.INVAL, wr(p, root_ctl, "Incl rel/dir\n").errno()); + try testing.expectEqual(E.INVAL, wr(p, root_ctl, "Incl - /usr/include\n").errno()); + if (comptime pardes.hosted) try testing.expectEqual(E.NOENT, wr(p, root_ctl, "Incl /tmp/pardes-surely-no-such-dir\n").errno()); + try testing.expectEqual(before, p.incl.items.len); + // A twin renamed: a rename record for each. + const first = serialOf(p); + try testing.expect(p.executeBuiltinLine(0, "Zerox")); + const twin = for (p.panes) |slot| { + if (slot) |pane| if (pane.serial != first and pane.file != null) break pane.serial; + } else return error.NoTwin; + _ = wr(p, Node.of(first, .name), "/tmp/pardes-twin-renamed.txt\n"); + var rec: [64]u8 = undefined; + try testing.expect(th.logHas(p, try std.fmt.bufPrint(&rec, "rename {d} /tmp/pardes-twin-renamed.txt\n", .{first}))); + try testing.expect(th.logHas(p, try std.fmt.bufPrint(&rec, "rename {d} /tmp/pardes-twin-renamed.txt\n", .{twin}))); +} + test "a body write to a terminal is typed input, not a paste, even to a program that asked for bracketed paste" { const p = try Pardes.init(testing.allocator, .{ .cols = 90, .rows = 30 }); defer p.deinit(); -- cgit v1.3