summaryrefslogtreecommitdiff
path: root/src/ninep/pane.zig
diff options
context:
space:
mode:
authorGabriel Schneider <[email protected]>2026-09-28 10:26:47 -0300
committerGabriel Schneider <[email protected]>2026-10-01 00:12:14 -0300
commit60e40675c82efd17aa6bf31bc66c3f8f3f918360 (patch)
tree1be23a1372c7383a2d37fe82ea6e8af9b91f488c /src/ninep/pane.zig
parentd9c6786e19567878e3c89ae212296ce941484096 (diff)
downloadpardes-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.zig46
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 => {},