diff options
| author | Gabriel Schneider <[email protected]> | 2026-09-28 10:26:47 -0300 |
|---|---|---|
| committer | Gabriel Schneider <[email protected]> | 2026-10-01 00:12:14 -0300 |
| commit | 60e40675c82efd17aa6bf31bc66c3f8f3f918360 (patch) | |
| tree | 1be23a1372c7383a2d37fe82ea6e8af9b91f488c /src/ninep/pane.zig | |
| parent | d9c6786e19567878e3c89ae212296ce941484096 (diff) | |
| download | pardes-60e40675c82efd17aa6bf31bc66c3f8f3f918360.tar.gz pardes-60e40675c82efd17aa6bf31bc66c3f8f3f918360.zip | |
Addresses search a line at a time as sam does, say why they fail, and a failed addr leaves no address
addr's regexps ran mvzr over text[from..hi]: ^ and $ anchored only at the
slice's ends, . matched newlines, the search never wrapped, and every
failure read as bad address syntax. The regexps stay mvzr's, called the
way sam searches (editors/acme/regx.c): one line per haystack, so ^ and $
fall at line boundaries and . never crosses a newline; a pattern naming
\\n runs over the whole text with its . made [^\\n]; /re/ wraps unless
limit is set, and ?re? takes the last match before the range. The ceiling
(mvzr's first alternative, not sam's longest) is documented. A failed
address says why (no match for regexp, address out of range, bad regular
expression), and a failed write to addr leaves no address, so data and
xdata refuse until the next good one instead of writing at the old range.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Diffstat (limited to 'src/ninep/pane.zig')
| -rw-r--r-- | src/ninep/pane.zig | 46 |
1 files changed, 30 insertions, 16 deletions
diff --git a/src/ninep/pane.zig b/src/ninep/pane.zig index 822e0704..0a511118 100644 --- a/src/ninep/pane.zig +++ b/src/ninep/pane.zig @@ -35,6 +35,10 @@ pub const State = struct { /// register, cleared by truncating the file, so that `cp addr dot` and /// `cat addr` answer what was written. addr: Range = .{}, + /// The last address written to `addr` failed, so there is none: `data` + /// and `xdata` refuse until one is written or `addr` is truncated, + /// rather than act at the address before it, which acme would do. + addr_failed: bool = false, limit: ?Range = null, /// Opens of `event`, which hold the pane scripted. readers: u16 = 0, @@ -262,6 +266,7 @@ pub fn read(p: *Pardes, req: Req, id: usize, pane: *Pane, file: PaneFile) Reply }, .ctl => ctl.readPane(p, req, pane), .addr => addr: { + if (pf.addr_failed) break :addr tree.failText(req.tag, E.INVAL, e_addr_failed); clampAddr(pf, bodyOf(pane).len); break :addr readRange(p, req, pf.addr); }, @@ -319,6 +324,7 @@ fn readBody(p: *Pardes, req: Req, id: usize, pane: *Pane) Reply { } fn readData(req: Req, id: usize, pane: *Pane, pf: *State, stop_at_end: bool) Reply { + if (pf.addr_failed) return tree.failText(req.tag, E.INVAL, e_addr_failed); const text = bodyOf(pane); clampAddr(pf, text.len); const q0: usize = pf.addr.q0; @@ -406,6 +412,7 @@ fn writeTag(req: Req, 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 = &pane.fs; + if (pf.addr_failed) return tree.failText(req.tag, E.INVAL, e_addr_failed); clampAddr(pf, bodyOf(pane).len); const q0: usize = pf.addr.q0; const q1: usize = @max(q0, @as(usize, pf.addr.q1)); @@ -442,30 +449,34 @@ fn pairOf(text: []const u8) ?State.Range { return .{ .q0 = q0, .q1 = @max(q0, q1) }; } -/// A range file takes an address expression, or that pair of offsets. -fn rangeOf(pf: *State, text: []const u8, data: []const u8) ?State.Range { - const expr = std.mem.trimEnd(u8, data, "\n"); - if (pairOf(expr)) |r| { - const n = clip(text.len); - return .{ .q0 = @min(r.q0, n), .q1 = @min(r.q1, n) }; - } - var a: addressing.Addr = .{ .text = text, .lim = pf.limit, .expr = expr }; - const r = a.address(pf.addr) orelse return null; - return if (a.i < expr.len) null else r; -} +pub const e_addr_failed = "no address: the last one written to addr failed"; +/// A range file takes an address expression, or that pair of offsets. One +/// that does not evaluate says why: `bad address syntax`, `no match for +/// regexp`, `address out of range`, `bad regular expression`. 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); + const expr = std.mem.trimEnd(u8, req.data, "\n"); + var a: addressing.Addr = .{ .text = text, .lim = pf.limit, .expr = expr }; + const r = if (pairOf(expr)) |pair| + State.Range{ .q0 = @min(pair.q0, clip(text.len)), .q1 = @min(pair.q1, clip(text.len)) } + else if (a.address(pf.addr)) |found| (if (a.i < expr.len) null else found) else null; + const range = r orelse { + if (file == .addr) pf.addr_failed = true; + return tree.failText(req.tag, E.INVAL, a.err); + }; switch (file) { - .addr => pf.addr = r, - .limit => pf.limit = r, + .addr => { + pf.addr = range; + pf.addr_failed = false; + }, + .limit => pf.limit = range, // Setting dot scrolls to it, which is the whole of acme's `show`. .dot => { if (fileOf(pane) == null) return Reply.fail(req.tag, E.INVAL); - setDot(pane, r); + setDot(pane, range); }, else => unreachable, } @@ -601,7 +612,10 @@ pub fn truncate(p: *Pardes, pane: *Pane, file: PaneFile) tree.Status { pane.tag.vsel.active = false; pane.tag.nsel = 0; }, - .addr => pf.addr = .{}, + .addr => { + pf.addr = .{}; + pf.addr_failed = false; + }, .limit => pf.limit = null, .dot => if (fileOf(pane) != null) setDot(pane, .{}), else => {}, |
