diff options
| author | Gabriel Schneider <[email protected]> | 2026-09-27 20:14:04 -0300 |
|---|---|---|
| committer | Gabriel Schneider <[email protected]> | 2026-10-01 00:12:14 -0300 |
| commit | acd9e032a147f4aa7ad49110dc2f4fb945e7a9bb (patch) | |
| tree | 6b81d7ac0b759472bd5c1afb1fc15cd6c930ac79 /src/ninep | |
| parent | 28c814aa5cfea23e8950ef007916ffc5d089288d (diff) | |
| download | pardes-acd9e032a147f4aa7ad49110dc2f4fb945e7a9bb.tar.gz pardes-acd9e032a147f4aa7ad49110dc2f4fb945e7a9bb.zip | |
Keep a pane's filesystem state on the Pane instead of a slot-indexed table
addr, limit, the event queue and its readers, pty/data's queue, a waiting
run and the /log announcement flag lived in p.fs.panes[slot], reset when
the pane went; a reused slot is how the bug class came in. acme keeps the
same state on its Window (editors/acme/dat.h:226-266), so now Pane.fs
holds it: announced, retired (noteRetire, which still answers a waiting
run and drops the listener count first) and freed with the pane itself.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Diffstat (limited to 'src/ninep')
| -rw-r--r-- | src/ninep/addr.zig | 12 | ||||
| -rw-r--r-- | src/ninep/events.zig | 49 | ||||
| -rw-r--r-- | src/ninep/pane.zig | 60 | ||||
| -rw-r--r-- | src/ninep/pty.zig | 33 | ||||
| -rw-r--r-- | src/ninep/tree.zig | 6 |
5 files changed, 78 insertions, 82 deletions
diff --git a/src/ninep/addr.zig b/src/ninep/addr.zig index 566e2364..e41d306e 100644 --- a/src/ninep/addr.zig +++ b/src/ninep/addr.zig @@ -247,13 +247,13 @@ test "the address language, form by form" { _ = th.wr(p, addr, "1"); _ = th.wr(p, addr, "."); - try testing.expectEqual(@as(u32, 0), p.fs.panes[0].addr.q0); - try testing.expectEqual(@as(u32, 4), p.fs.panes[0].addr.q1); + try testing.expectEqual(@as(u32, 0), p.panes[0].?.fs.addr.q0); + try testing.expectEqual(@as(u32, 4), p.panes[0].?.fs.addr.q1); _ = th.wr(p, addr, "$"); _ = th.wr(p, addr, "?o?"); - try testing.expectEqual(@as(u32, 6), p.fs.panes[0].addr.q0); // the `o` in "two" - try testing.expectEqual(@as(u32, 7), p.fs.panes[0].addr.q1); + try testing.expectEqual(@as(u32, 6), p.panes[0].?.fs.addr.q0); // the `o` in "two" + try testing.expectEqual(@as(u32, 7), p.panes[0].?.fs.addr.q1); // limit is its own file: copying addr onto it bounds the search, and // truncating it lifts the bound again. @@ -264,12 +264,12 @@ test "the address language, form by form" { const pair = th.rd(p, addr, 0, 64).bytes; @memcpy(copied[0..pair.len], pair); _ = th.wr(p, limit, copied[0..pair.len]); - try testing.expectEqual(@as(u32, 4), p.fs.panes[0].limit.?.q1); + try testing.expectEqual(@as(u32, 4), p.panes[0].?.fs.limit.?.q1); try testing.expectEqualStrings(" 0 4 ", th.rd(p, limit, 0, 64).bytes); _ = th.wr(p, addr, "#0"); try testing.expectEqual(E.INVAL, th.wr(p, addr, "/three/").errno()); _ = th.call(p, .{ .tag = 6, .op = .setattr, .node = limit, .truncate = true }); - try testing.expect(p.fs.panes[0].limit == null); + try testing.expect(p.panes[0].?.fs.limit == null); _ = th.wr(p, addr, "#0"); try testing.expectEqual(Status.ok, th.wr(p, addr, "/three/").reply.status); diff --git a/src/ninep/events.zig b/src/ninep/events.zig index 6b8c744e..78945c5c 100644 --- a/src/ninep/events.zig +++ b/src/ninep/events.zig @@ -115,19 +115,14 @@ pub fn readQueue(p: *Pardes, req: Req, q: *Queue) Reply { pub const LogKind = enum { new, del, rename, save }; -/// A pane installed now is announced by `announce` once the update that made -/// it has finished, when its name is known. -pub fn noteInstall(p: *Pardes, id: usize) void { - if (id < MAX_PANES) p.fs.panes[id].unannounced = true; -} - -/// A pane leaving; one never announced leaves silently. -pub fn noteRetire(p: *Pardes, id: usize, pane: *Pane) void { - if (id >= MAX_PANES) return; - tree.pty.shellGone(p, id); +/// A pane leaving, before its state goes with it; one never announced +/// leaves silently. +pub fn noteRetire(p: *Pardes, pane: *Pane) void { + tree.pty.shellGone(p, pane); p.fs.news = true; // a read held on its event or pty/data hears it went - if (p.fs.panes[id].unannounced) { - p.fs.panes[id].unannounced = false; + p.fs.listeners -|= pane.fs.readers; + if (pane.fs.unannounced) { + pane.fs.unannounced = false; return; } noteLog(p, .del, pane); @@ -135,11 +130,11 @@ pub fn noteRetire(p: *Pardes, id: usize, pane: *Pane) void { /// Called at the end of every update: reports the panes installed by it. pub fn announce(p: *Pardes) void { - for (p.panes, 0..) |slot, id| { - const pf = &p.fs.panes[id]; - if (!pf.unannounced) continue; - pf.unannounced = false; - if (slot) |pane| noteLog(p, .new, pane); + for (p.panes) |slot| { + const pane = slot orelse continue; + if (!pane.fs.unannounced) continue; + pane.fs.unannounced = false; + noteLog(p, .new, pane); } } @@ -346,7 +341,8 @@ pub fn diffSpan(old: []const u8, new: []const u8) Span { } pub fn noteReplace(p: *Pardes, id: usize, on_tag: bool, old: []const u8, new: []const u8) void { - if (!p.fs.scripted(id)) return; + const pane = p.panes[id] orelse return; + if (pane.fs.readers == 0) return; const span = diffSpan(old, new); if (span.removed == 0 and span.inserted == 0) return; if (span.removed > 0) _ = noteAction( @@ -378,17 +374,18 @@ pub fn noteAction( flag: u32, text: []const u8, ) bool { - if (!p.fs.scripted(id)) return false; + const pane = p.panes[id] orelse return false; + if (pane.fs.readers == 0) return false; var buf: [max_record_text + 64]u8 = undefined; const record = formatRecord(&buf, p.fs.origin, action, q0, q1, flag, text); - p.fs.panes[id].events.push(p.gpa, record); + pane.fs.events.push(p.gpa, record); p.fs.news = true; return true; } pub fn notePtyOutput(p: *Pardes, id: usize, bytes: []const u8) void { if (id >= MAX_PANES or bytes.len == 0) return; - const pf = &p.fs.panes[id]; + const pf = &(p.panes[id] orelse return).fs; if (pf.pty_readers == 0) return; var off: usize = 0; while (off < bytes.len) { @@ -486,7 +483,7 @@ test "event records are acme's bytes, one per read, and .again when empty" { const event = Node.of(serial, .event); _ = noteAction(p, 0, .body_exec, 1, 4, flag_builtin, "sg "); - try testing.expect(p.fs.panes[0].events.empty()); + try testing.expect(p.panes[0].?.fs.events.empty()); const h = call(p, .{ .tag = 10, .op = .open, .node = event }); try testing.expect(h.reply.handle != 0); @@ -570,19 +567,19 @@ test "one event reader at a time, and a writer still holds the pane" { try testing.expectEqualStrings(tree.e_in_use, second.reply.ename); const writer = call(p, .{ .tag = 13, .op = .open, .node = event, .omode = 1 }); try testing.expectEqual(Status.ok, writer.reply.status); - try testing.expectEqual(@as(u16, 2), p.fs.panes[0].readers); + try testing.expectEqual(@as(u16, 2), p.panes[0].?.fs.readers); try testing.expectEqual(@as(u16, 2), p.fs.listeners); _ = call(p, .{ .tag = 14, .op = .release, .node = event, .handle = writer.reply.handle }); - try testing.expectEqual(@as(u16, 1), p.fs.panes[0].readers); - try testing.expect(p.fs.scripted(0)); + try testing.expectEqual(@as(u16, 1), p.panes[0].?.fs.readers); + try testing.expect(p.panes[0].?.fs.readers != 0); _ = call(p, .{ .tag = 15, .op = .release, .node = Node.of(serialOf(p), .body) }); try testing.expectEqual(@as(u16, 1), p.fs.listeners); _ = call(p, .{ .tag = 16, .op = .release, .node = event, .handle = reader.reply.handle }); try testing.expectEqual(@as(u16, 0), p.fs.listeners); - try testing.expect(!p.fs.scripted(0)); + try testing.expect(p.panes[0].?.fs.readers == 0); _ = call(p, .{ .tag = 17, .op = .release, .node = event }); try testing.expectEqual(@as(u16, 0), p.fs.listeners); // Once it closed, the next reader gets in. diff --git a/src/ninep/pane.zig b/src/ninep/pane.zig index 61073a55..b07ee038 100644 --- a/src/ninep/pane.zig +++ b/src/ninep/pane.zig @@ -186,7 +186,7 @@ pub fn wholeUtf8(data: []const u8) usize { return data.len; } -pub fn spliceBody(p: *Pardes, id: usize, pane: *Pane, q0: usize, q1: usize, bytes: []const u8) ?usize { +pub fn spliceBody(p: *Pardes, pane: *Pane, q0: usize, q1: usize, bytes: []const u8) ?usize { const f = fileOf(pane) orelse return null; const take = if (bytes.len == 0) 0 else wholeUtf8(bytes); const lo = @min(q0, f.content.len); @@ -195,7 +195,7 @@ pub fn spliceBody(p: *Pardes, id: usize, pane: *Pane, q0: usize, q1: usize, byte @memcpy(new[0..lo], f.content[0..lo]); @memcpy(new[lo..][0..take], bytes[0..take]); @memcpy(new[lo + take ..], f.content[hi..]); - if (!p.fs.panes[id].nomark) panes.File.pushUndo(p, pane); + if (!pane.fs.nomark) panes.File.pushUndo(p, pane); panes.File.setContent(p, f, new); return take; } @@ -208,7 +208,7 @@ pub const range_len: u64 = 24; pub fn fileSize(p: *Pardes, id: usize, f: PaneFile) u64 { const pane = p.panes[id] orelse return 0; - const pf = &p.fs.panes[id]; + const pf = &pane.fs; return switch (f) { .body, .data, .xdata => bodyOf(pane).len, .tag => tagOf(p, pane).len, @@ -265,7 +265,7 @@ pub fn indexLen(p: *Pardes) u64 { // ---- reads ---- pub fn read(p: *Pardes, req: Req, id: usize, pane: *Pane, file: PaneFile) Reply { - const pf = &p.fs.panes[id]; + const pf = &pane.fs; return switch (file) { .name => name: { const out = p.fs.stage(p.gpa); @@ -379,10 +379,10 @@ pub fn write(p: *Pardes, req: Req, id: usize, pane: *Pane, file: PaneFile) Reply .body => writeBody(p, req, id, pane), .tag => writeTag(p, req, pane), .ctl => ctl.writePane(p, req, pane), - .addr, .dot, .limit => writeRange(p, req, id, pane, file), - .data, .xdata => writeData(p, req, id, pane), - .sel => writeSel(p, req, id, pane), - .dirty, .mark, .scroll => writeFlag(p, req, id, pane, file), + .addr, .dot, .limit => writeRange(req, pane, file), + .data, .xdata => writeData(p, req, pane), + .sel => writeSel(p, req, pane), + .dirty, .mark, .scroll => writeFlag(p, req, pane, file), .look, .exec => ctl.command(p, req, pane.serial, file == .exec), .event => events.writeEvent(p, req, id), .errors => if (appendErrors(p, id, req.data)) |took| @@ -404,9 +404,9 @@ fn writeBody(p: *Pardes, req: Req, id: usize, pane: *Pane) Reply { return .{ .tag = req.tag, .written = @intCast(take) }; } const at = bodyOf(pane).len; - const take = spliceBody(p, id, pane, at, at, req.data) orelse + const take = spliceBody(p, pane, at, at, req.data) orelse return Reply.fail(req.tag, E.NOMEM); - if (!p.fs.panes[id].noscroll) showOffset(pane, at + take); + if (!pane.fs.noscroll) showOffset(pane, at + take); return .{ .tag = req.tag, .written = @intCast(take) }; } @@ -422,14 +422,14 @@ fn writeTag(p: *Pardes, req: Req, pane: *Pane) Reply { return .{ .tag = req.tag, .written = @intCast(take) }; } -fn writeData(p: *Pardes, req: Req, id: usize, pane: *Pane) Reply { +fn writeData(p: *Pardes, req: Req, pane: *Pane) Reply { if (fileOf(pane) == null) return Reply.fail(req.tag, E.INVAL); - const pf = &p.fs.panes[id]; + const pf = &pane.fs; clampAddr(pf, bodyOf(pane).len); const q0: usize = pf.addr.q0; const q1: usize = @max(q0, @as(usize, pf.addr.q1)); const before = dotOf(pane); - const take = spliceBody(p, id, pane, q0, q1, req.data) orelse + const take = spliceBody(p, pane, q0, q1, req.data) orelse return Reply.fail(req.tag, E.NOMEM); setDot(pane, shiftBy(before, clip(q0), clip(q1 - q0), clip(take))); pf.addr = .{ .q0 = clip(q0 + take), .q1 = clip(q0 + take) }; @@ -437,12 +437,12 @@ fn writeData(p: *Pardes, req: Req, id: usize, pane: *Pane) Reply { return .{ .tag = req.tag, .written = @intCast(take) }; } -fn writeSel(p: *Pardes, req: Req, id: usize, pane: *Pane) Reply { +fn writeSel(p: *Pardes, req: Req, pane: *Pane) Reply { if (fileOf(pane) == null) return Reply.fail(req.tag, E.INVAL); const d = dotOf(pane); const q0: usize = d.q0; const q1: usize = @max(q0, @as(usize, d.q1)); - const take = spliceBody(p, id, pane, q0, q1, req.data) orelse + const take = spliceBody(p, pane, q0, q1, req.data) orelse return Reply.fail(req.tag, E.NOMEM); setDot(pane, .{ .q0 = clip(q0 + take), .q1 = clip(q0 + take) }); return .{ .tag = req.tag, .written = @intCast(take) }; @@ -470,8 +470,8 @@ fn rangeOf(pf: *State, text: []const u8, data: []const u8) ?State.Range { return if (a.i < expr.len) null else r; } -fn writeRange(p: *Pardes, req: Req, id: usize, pane: *Pane, file: PaneFile) Reply { - const pf = &p.fs.panes[id]; +fn writeRange(req: Req, pane: *Pane, file: PaneFile) Reply { + const pf = &pane.fs; const text = bodyOf(pane); clampAddr(pf, text.len); const r = rangeOf(pf, text, req.data) orelse return tree.failText(req.tag, E.INVAL, tree.e_bad_addr); @@ -495,9 +495,9 @@ fn flagOf(data: []const u8) ?bool { return null; } -fn writeFlag(p: *Pardes, req: Req, id: usize, pane: *Pane, file: PaneFile) Reply { +fn writeFlag(p: *Pardes, req: Req, pane: *Pane, file: PaneFile) Reply { const on = flagOf(req.data) orelse return Reply.fail(req.tag, E.INVAL); - const pf = &p.fs.panes[id]; + const pf = &pane.fs; switch (file) { .dirty => { const f = fileOf(pane) orelse return Reply.fail(req.tag, E.INVAL); @@ -569,13 +569,13 @@ pub fn appendErrors(p: *Pardes, id: usize, text: []const u8) ?usize { if (text.len == 0) return 0; const pane = p.panes[id] orelse return null; const dir = dirOf(pane); - for (p.panes, 0..) |slot, i| { + for (p.panes) |slot| { const q = slot orelse continue; const qf = fileOf(q) orelse continue; const o = qf.output orelse continue; if (std.meta.activeTag(o.from) != .errors) continue; if (!std.mem.eql(u8, std.fs.path.dirname(qf.path) orelse "", dir)) continue; - return spliceBody(p, i, q, qf.content.len, qf.content.len, text); + return spliceBody(p, q, qf.content.len, qf.content.len, text); } const free = p.freeSlot() orelse return null; const content = p.gpa.dupe(u8, text) catch return null; @@ -590,11 +590,11 @@ pub fn appendErrors(p: *Pardes, id: usize, text: []const u8) ?usize { /// Truncation empties whatever the file holds: the body, the tag's editable /// tail (acme's `cleartag`), a range. Every other setattr just answers fresh /// attributes. -pub fn truncate(p: *Pardes, id: usize, pane: *Pane, file: PaneFile) tree.Status { - const pf = &p.fs.panes[id]; +pub fn truncate(p: *Pardes, pane: *Pane, file: PaneFile) tree.Status { + const pf = &pane.fs; switch (file) { .body, .data, .xdata => if (fileOf(pane) != null) { - _ = spliceBody(p, id, pane, 0, bodyOf(pane).len, "") orelse return .err; + _ = spliceBody(p, pane, 0, bodyOf(pane).len, "") orelse return .err; pf.addr = .{}; setDot(pane, .{}); }, @@ -753,8 +753,8 @@ test "data and xdata read from addr, move it, and write through it" { _ = wr(p, addr, "#0"); try testing.expectEqualStrings("one", rd(p, data, 0, 3).bytes); - try testing.expectEqual(@as(u32, 3), p.fs.panes[0].addr.q0); - try testing.expectEqual(@as(u32, 3), p.fs.panes[0].addr.q1); + try testing.expectEqual(@as(u32, 3), p.panes[0].?.fs.addr.q0); + try testing.expectEqual(@as(u32, 3), p.panes[0].?.fs.addr.q1); _ = wr(p, addr, "1"); try testing.expectEqualStrings("one\n", rd(p, xdata, 0, 100).bytes); @@ -765,7 +765,7 @@ test "data and xdata read from addr, move it, and write through it" { const w = wr(p, data, "ONE\n"); try testing.expectEqual(@as(u32, 4), w.reply.written); try testing.expectEqualStrings("ONE\ntwo\n", p.panes[0].?.file.?.content); - try testing.expectEqual(@as(u32, 4), p.fs.panes[0].addr.q0); + try testing.expectEqual(@as(u32, 4), p.panes[0].?.fs.addr.q0); } test "data never splits a grapheme, in either direction" { @@ -802,8 +802,8 @@ test "sel reads the selection and writing sel replaces it" { _ = wr(p, addr_node, "#0"); _ = wr(p, addr_node, " 0 3 "); - try testing.expectEqual(@as(u32, 0), p.fs.panes[0].addr.q0); - try testing.expectEqual(@as(u32, 3), p.fs.panes[0].addr.q1); + try testing.expectEqual(@as(u32, 0), p.panes[0].?.fs.addr.q0); + try testing.expectEqual(@as(u32, 3), p.panes[0].?.fs.addr.q1); // A truncation empties dot; an address expression addresses it. _ = call(p, .{ .tag = 9, .op = .setattr, .node = dot, .truncate = true }); @@ -823,7 +823,7 @@ test "the flag files read and write the state acme kept behind ctl verbs" { defer p.deinit(); const serial = serialOf(p); const pane = p.panes[0].?; - const pf = &p.fs.panes[0]; + const pf = &p.panes[0].?.fs; const dirty = Node.of(serial, .dirty); const mark = Node.of(serial, .mark); const scroll = Node.of(serial, .scroll); diff --git a/src/ninep/pty.zig b/src/ninep/pty.zig index 2a38f1f0..ddb7bcd1 100644 --- a/src/ninep/pty.zig +++ b/src/ninep/pty.zig @@ -176,7 +176,7 @@ pub fn writeRun(p: *Pardes, req: Req, id: usize, pane: *Pane) Reply { if (std.mem.trim(u8, line, " \t").len == 0) return tree.failText(req.tag, E.INVAL, e_bad_line); if (line.len == 0) return tree.failText(req.tag, E.INVAL, e_bad_line); for (line) |c| if (c < ' ' and c != '\t') return tree.failText(req.tag, E.INVAL, e_bad_line); - const pf = &p.fs.panes[id]; + const pf = &pane.fs; const state = pane.terminal orelse return tree.failText(req.tag, E.INVAL, e_bad_line); const marks = &state.stream.handler; if (pf.unmarked) { @@ -230,8 +230,8 @@ pub fn releaseRun(p: *Pardes, req: Req) Reply { const slot = runOf(p, req) orelse return .{ .tag = req.tag }; const idx: u8 = @intCast(slot - &p.fs.runs[0]); // The command runs on in the shell; nobody is waiting for it any more. - for (&p.fs.panes) |*pf| if (pf.run == idx) { - pf.run = null; + for (p.panes) |live| if (live) |pane| if (pane.fs.run == idx) { + pane.fs.run = null; }; slot.reset(p.gpa, 0); return .{ .tag = req.tag }; @@ -244,7 +244,7 @@ pub fn releaseRun(p: *Pardes, req: Req) Reply { pub fn noteMarks(p: *Pardes, id: usize, pane: *Pane) void { if (comptime !pardes.panes.Terminal.enabled) return; const state = pane.terminal orelse return; - const pf = &p.fs.panes[id]; + const pf = &pane.fs; const slot = &p.fs.runs[pf.run orelse return]; const marks = &state.stream.handler; if (marks.phase != .input) return; @@ -323,11 +323,10 @@ pub fn noteMarks(p: *Pardes, id: usize, pane: *Pane) void { /// The pane closed or its shell was replaced: the command's end will never /// be reported, so the run says so instead of waiting forever. -pub fn shellGone(p: *Pardes, id: usize) void { - const pf = &p.fs.panes[id]; - const idx = pf.run orelse return; +pub fn shellGone(p: *Pardes, pane: *Pane) void { + const idx = pane.fs.run orelse return; answer(p, &p.fs.runs[idx], "error shell gone", .{}); - pf.run = null; + pane.fs.run = null; } const e_bad_line = "bad command line"; @@ -388,7 +387,7 @@ test "a pane that is not a terminal has no pty/ at all" { .op = .open, .node = Node.of(serial, .pty_data), }).errno()); - try testing.expectEqual(@as(u16, 0), p.fs.panes[0].pty_readers); + try testing.expectEqual(@as(u16, 0), p.panes[0].?.fs.pty_readers); var buf: [32]th.Dirent = undefined; const files = th.dirents(rdir(p, dir, 0).bytes, &buf); @@ -574,18 +573,18 @@ test "pty/data writes at the shell and reads the raw stream" { p.update(.{ .output = .{ .pane = 0, .bytes = "unwatched" } }); while (p.nextEffect()) |_| {} - try testing.expectEqual(@as(usize, 0), p.fs.panes[0].pty_out.buf.items.len); + try testing.expectEqual(@as(usize, 0), p.panes[0].?.fs.pty_out.buf.items.len); try testing.expectEqual(Status.again, rd(p, data, 0, 64).reply.status); // An open that only writes is not a reader: nothing queues for it. const writer = call(p, .{ .tag = 4, .op = .open, .node = data, .omode = 1 }); - try testing.expectEqual(@as(u16, 0), p.fs.panes[0].pty_readers); + try testing.expectEqual(@as(u16, 0), p.panes[0].?.fs.pty_readers); _ = call(p, .{ .tag = 4, .op = .release, .node = data, .handle = writer.reply.handle }); const reader = call(p, .{ .tag = 5, .op = .open, .node = data }); - try testing.expectEqual(@as(u16, 1), p.fs.panes[0].pty_readers); + try testing.expectEqual(@as(u16, 1), p.panes[0].?.fs.pty_readers); try testing.expectEqual(@as(u16, 0), p.fs.listeners); - try testing.expect(!p.fs.scripted(0)); + try testing.expect(p.panes[0].?.fs.readers == 0); p.update(.{ .output = .{ .pane = 0, .bytes = "hello" } }); while (p.nextEffect()) |_| {} @@ -603,8 +602,8 @@ test "pty/data writes at the shell and reads the raw stream" { p.update(.{ .output = .{ .pane = 0, .bytes = "orphan" } }); while (p.nextEffect()) |_| {} _ = call(p, .{ .tag = 6, .op = .release, .node = data, .handle = reader.reply.handle }); - try testing.expectEqual(@as(u16, 0), p.fs.panes[0].pty_readers); - try testing.expectEqual(@as(usize, 0), p.fs.panes[0].pty_out.buf.capacity); + try testing.expectEqual(@as(u16, 0), p.panes[0].?.fs.pty_readers); + try testing.expectEqual(@as(usize, 0), p.panes[0].?.fs.pty_out.buf.capacity); try testing.expectEqual(Status.again, rd(p, data, 0, 64).reply.status); // A second reader would take half the stream from the first: refused. @@ -612,7 +611,7 @@ test "pty/data writes at the shell and reads the raw stream" { const second = call(p, .{ .tag = 8, .op = .open, .node = data, .omode = 2 }); try testing.expectEqual(Status.err, second.reply.status); try testing.expectEqualStrings(tree.e_in_use, second.reply.ename); - try testing.expectEqual(@as(u16, 1), p.fs.panes[0].pty_readers); + try testing.expectEqual(@as(u16, 1), p.panes[0].?.fs.pty_readers); p.update(.{ .output = .{ .pane = 0, .bytes = "still" } }); while (p.nextEffect()) |_| {} try testing.expectEqualStrings("still", rd(p, data, 0, 64).bytes); @@ -732,7 +731,7 @@ test "the pty queue drops the oldest at its cap" { const rest: [4096]u8 = @splat('B'); events.notePtyOutput(p, 0, &oldest); for (0..events.queue_cap / rest.len + 4) |_| events.notePtyOutput(p, 0, &rest); - const q = &p.fs.panes[0].pty_out; + const q = &p.panes[0].?.fs.pty_out; try testing.expect(q.buf.items.len - q.head <= events.queue_cap); var seen: usize = 0; diff --git a/src/ninep/tree.zig b/src/ninep/tree.zig index 3ebea669..f08d20c8 100644 --- a/src/ninep/tree.zig +++ b/src/ninep/tree.zig @@ -543,7 +543,7 @@ fn open(p: *Pardes, req: Req, target: Target) Reply { .pane => |t| { const id = p.paneBySerial(t.serial) orelse return Reply.fail(req.tag, E.NOENT); const pn = p.panes[id].?; - const pf = &p.fs.panes[id]; + const pf = &pn.fs; if (t.file.inPty() and !pn.isTerminal()) return Reply.fail(req.tag, E.NOENT); switch (t.file) { .body => if (pn.isTerminal()) return screen.openSnapshot(p, req, false), @@ -605,7 +605,7 @@ fn releaseHandle(p: *Pardes, req: Req) Reply { if (t.file == .pty_run) return pty.releaseRun(p, req); if (t.file != .event and t.file != .pty_data) return .{ .tag = req.tag }; const id = p.paneBySerial(t.serial) orelse return .{ .tag = req.tag }; - const pf = &p.fs.panes[id]; + const pf = &p.panes[id].?.fs; if (t.file == .pty_data) { if (req.handle != reader_handle or pf.pty_readers == 0) return .{ .tag = req.tag }; pf.pty_readers -= 1; @@ -626,7 +626,7 @@ fn setattr(p: *Pardes, req: Req, target: Target) Reply { if (req.truncate) switch (target) { .pane => |t| { const id = p.paneBySerial(t.serial) orelse return Reply.fail(req.tag, E.NOENT); - if (pane.truncate(p, id, p.panes[id].?, t.file) != .ok) return Reply.fail(req.tag, E.NOMEM); + if (pane.truncate(p, p.panes[id].?, t.file) != .ok) return Reply.fail(req.tag, E.NOMEM); }, .top => {}, }; |
