diff options
| -rw-r--r-- | .agents/skills/pardes-9p/SKILL.md | 3 | ||||
| -rw-r--r-- | docs/fs.md | 14 | ||||
| -rw-r--r-- | src/fs-help.txt | 2 | ||||
| -rw-r--r-- | src/ninep/addr.zig | 27 |
4 files changed, 41 insertions, 5 deletions
diff --git a/.agents/skills/pardes-9p/SKILL.md b/.agents/skills/pardes-9p/SKILL.md index 302223e5..55df3091 100644 --- a/.agents/skills/pardes-9p/SKILL.md +++ b/.agents/skills/pardes-9p/SKILL.md @@ -170,7 +170,8 @@ Truncating `data` is pardes's own (acme ignores OTRUNC and always inserts). `addr`, `dot` and `limit` each read the pair of offsets they also accept, which is why copying one onto another is all that acme's `addr=dot`, `dot=addr` and `limit=addr` ever were; a write may also be an address expression (`#0,#5`, -`/pattern/`, `2+1`), whose regexps are mvzr's searched as sam searches: +`/pattern/`, `2+1`, or pardes's `12:5`: line 12, byte column 5, composing as +`12:5,14:1`), whose regexps are mvzr's searched as sam searches: `^`/`$` match at any line's start and end, `.` and `[^...]` never match a newline, the leftmost match wins (the first alternative there, not the longest). An expression is evaluated from the current address (the last one @@ -357,10 +357,20 @@ exec 3>&-`. The three range files `addr`, `dot` and `limit` each read the pair of offsets they also accept, so copying one onto another is all that acme's `addr=dot`, `dot=addr` and `limit=addr` ever were. A write is either that pair or an -address expression (`#0,#5`, `/pattern/`, `2+1`); `addr` selects what `data` +address expression (`#0,#5`, `/pattern/`, `2+1`, and pardes's own `12:5`, +below); `addr` selects what `data` and `xdata` read or replace, `dot` is the editor's own selection and moving it scrolls the pane into view, and `limit` bounds a search and reads empty until -it is set. Truncating a range file empties it; truncating `limit` lifts it. +it is set. Truncating `dot` empties it, truncating `limit` lifts it, and +truncating `addr` leaves it as it is (below). + +`line:col` is a pardes extension to sam's addresses, the spelling Look +takes in `file:12:5`: `12:5` is the point at line 12, column 5, and it +composes like any simple address (`12:5,14:1`, `12:5+#3`). The column is +in bytes from 1, as Look's is, clamped to the end of the line and snapped +back to the start of the character it falls in; a line past the end, or +column 0, is `address out of range`. sam would read `12:5` as a syntax +error. Truncating `data` or `xdata` deletes the range `addr` names and nothing else, so a shell's `echo NEW > data` replaces that range, `: > data` deletes it, and `>>` inserts at it; only truncating `body` empties the diff --git a/src/fs-help.txt b/src/fs-help.txt index 214986a1..324b992e 100644 --- a/src/fs-help.txt +++ b/src/fs-help.txt @@ -35,7 +35,7 @@ Below, $m is the mount: $NINE_MOUNT/pardes/<pid, or NAME of --detach=NAME>; PARD Pitfalls, one each: Each open of pane/new makes another pane (ls, stat, find never do); rmdir closes even a dirty one. - addr: #n,#m or /regexp/ from the current address (^ $ per line); > never resets it, 0 does; a miss refuses data. + addr: #n,#m, 12:5 or /re/ from the current address (^ $ per line); > never resets it, 0 does; a miss refuses data. data: > replaces the addr range, : > deletes it, a 2nd > inserts (addr moved); body: > empties all. tag reads the path, then its own text; > replaces that text (default words too), >> appends. A terminal's body is a history snapshot frozen per open; pty/data is the live stream. diff --git a/src/ninep/addr.zig b/src/ninep/addr.zig index ccb08924..f57c62bd 100644 --- a/src/ninep/addr.zig +++ b/src/ninep/addr.zig @@ -91,6 +91,26 @@ pub const Addr = struct { if (d < '0' or d > '9') break; n = @min(n * 10 + (d - '0'), std.math.maxInt(u32)); } + // pardes's `line:col`, Look's `file:12:5` spelling (look.zig + // parsePathLine): the point at that column of that line, the + // column in bytes from 1, clamped to the line's end and snapped + // to a character's start as Look's cursor is. Not sam's. + if (size == .line and dir == 0 and a.i + 1 < a.expr.len and a.expr[a.i] == ':' and + a.expr[a.i + 1] >= '0' and a.expr[a.i + 1] <= '9') + { + a.i += 1; + var col: u64 = 0; + while (a.i < a.expr.len and a.expr[a.i] >= '0' and a.expr[a.i] <= '9') : (a.i += 1) + col = @min(col * 10 + (a.expr[a.i] - '0'), std.math.maxInt(u32)); + const lines = modal.cursorLineCount(a.text); + if (n == 0 or n > lines or col == 0) { + a.err = e_range; + return null; + } + const q = clip(modal.offsetAt(a.text, .{ .row = @intCast(n - 1), .col = @intCast(col - 1) })); + r = .{ .q0 = q, .q1 = q }; + continue; + } r = a.number(r, @intCast(n), dir, size) orelse return null; dir = 0; size = .line; @@ -393,6 +413,11 @@ test "the address language, form by form" { .{ .expr = "/two/", .q0 = 4, .q1 = 7 }, .{ .expr = "/t.o/", .q0 = 4, .q1 = 7 }, .{ .expr = "1\n", .q0 = 0, .q1 = 4 }, + // pardes's line:col, composing like any simple address. + .{ .expr = "2:2", .q0 = 5, .q1 = 5 }, + .{ .expr = "2:99", .q0 = 7, .q1 = 7 }, + .{ .expr = "2:2,3:1", .q0 = 5, .q1 = 8 }, + .{ .expr = "2:2+#3", .q0 = 8, .q1 = 8 }, }) |c| { _ = th.wr(p, addr, "#0"); const w = th.wr(p, addr, c.expr); @@ -441,7 +466,7 @@ test "the address language, form by form" { for ([_][2][]const u8{ .{ "zzz", e_syntax }, .{ "/nomatch/", e_no_match }, .{ "99", e_range }, .{ "#999", e_range }, .{ "/a[/", e_regexp }, .{ "/(a/", e_regexp }, - .{ "/*a/", e_regexp }, + .{ "/*a/", e_regexp }, .{ "99:1", e_range }, .{ "2:0", e_range }, }) |c| { _ = th.wr(p, addr, "#0"); try testing.expectEqualStrings(c[1], th.wr(p, addr, c[0]).reply.ename); |
