diff options
Diffstat (limited to 'src/ninep/events.zig')
| -rw-r--r-- | src/ninep/events.zig | 79 |
1 files changed, 54 insertions, 25 deletions
diff --git a/src/ninep/events.zig b/src/ninep/events.zig index bacf3ad6..c0b52d03 100644 --- a/src/ninep/events.zig +++ b/src/ninep/events.zig @@ -6,6 +6,7 @@ const exec = @import("../exec.zig"); const look = @import("../look.zig"); const cloud9 = @import("cloud9"); const tree = @import("tree.zig"); +const ctl = @import("ctl.zig"); const pane_files = @import("pane.zig"); const dump = @import("../dump.zig"); @@ -336,9 +337,10 @@ fn sanitize(record: []const u8, out: []u8) []u8 { /// `text` as one line of UTF-8 a reader can split and decode, as /log and /// /index show names: a backslash is `\\`, a newline `\n` (a name with one -/// stays readable as what it is, not run into the next), any other control -/// character, DEL or C1 control (U+0080-U+009F) a space, and a byte that -/// is not UTF-8 `\xNN`. `out` of 4 bytes a byte of `text` holds it all. +/// stays readable as what it is, not run into the next), and any other +/// control character, DEL, C1 control (U+0080-U+009F) or byte that is not +/// UTF-8 `\xNN`, so a name read there decodes to the bytes it is. `out` of +/// 4 bytes a byte of `text` holds it all. pub fn shown(text: []const u8, out: []u8) []u8 { return shownAs(text, out, true); } @@ -364,8 +366,15 @@ fn shownAs(text: []const u8, out: []u8, escape_newline: bool) []u8 { continue; } if (c < ' ' or c == 0x7f) { - out[w] = ' '; - w += 1; + // A name's (escape_newline) is `\xNN`, so it reads back as the + // byte it was; a message's a space. + if (escape_newline) { + _ = std.fmt.bufPrint(out[w..], "\\x{x:0>2}", .{c}) catch break; + w += 4; + } else { + out[w] = ' '; + w += 1; + } i += 1; continue; } @@ -377,6 +386,13 @@ fn shownAs(text: []const u8, out: []u8, escape_newline: bool) []u8 { continue; } if (n == 2 and c == 0xC2 and body[i + 1] <= 0x9F) { + if (escape_newline) { + if (w + 8 > out.len) break; + _ = std.fmt.bufPrint(out[w..], "\\xc2\\x{x:0>2}", .{body[i + 1]}) catch break; + w += 8; + i += 2; + continue; + } out[w] = ' '; w += 1; i += 2; @@ -494,18 +510,6 @@ pub fn writeLog(p: *Pardes, req: Req) Reply { return .{ .tag = req.tag, .written = @intCast(req.data.len) }; } -/// The length a stat reports: the text an open would freeze now. -pub fn logLen(q: *const Queue) u64 { - var n: u64 = 0; - var at = q.head; - while (at + 4 <= q.buf.items.len) { - const len = std.mem.readInt(u32, q.buf.items[at..][0..4], .little); - n += len; - at += 4 + len; - } - return n; -} - // ---- per-pane event records ---- pub const max_record_text = 256; @@ -697,11 +701,27 @@ fn scanNumber(data: []const u8, i: *usize) ?u32 { /// Writing a Look or Exec record back performs the action it names. /// The word a click at `at` expands to: the file name or word run under it /// on its line (look.expandedWord), else nothing. -fn expandAt(whole: []const u8, at: usize) []const u8 { +/// An empty range written back: what a click at `at` takes, as the mouse's +/// does (exec.pointerOperand). In a diff pane's body that is look.diffClick's +/// answer, the line's address or its code's words. +pub fn expandAt(p: *Pardes, pane: *Pane, whole: []const u8, at: usize, in_body: bool) []const u8 { const start = if (std.mem.lastIndexOfScalar(u8, whole[0..at], '\n')) |nl| nl + 1 else 0; const end = std.mem.indexOfScalarPos(u8, whole, at, '\n') orelse whole.len; - const b = look.expandedWord(whole[start..end], at - start) orelse return ""; - return whole[start + b.lo .. start + b.hi]; + var word_from: usize = 0; + if (in_body) diff: { + const row = std.mem.count(u8, whole[0..start], "\n"); + // Only when the served line is the one the diff reads there. + const lines = look.diffLines(p, pane) orelse break :diff; + if (row >= lines.len or !std.mem.eql(u8, std.mem.trimEnd(u8, lines[row], "\r"), std.mem.trimEnd(u8, whole[start..end], "\r"))) break :diff; + var buf: [4096 + 32]u8 = undefined; + const click = look.diffClick(p, pane, row, at - start, &buf) orelse break :diff; + if (click.address) |address| return p.scratch.allocator().dupe(u8, address) catch ""; + word_from = click.word_from; + } + const line = whole[start..end]; + if (at - start < word_from or word_from > line.len) return ""; + const b = look.expandedWord(line[word_from..], at - start - word_from) orelse return ""; + return whole[start + word_from + b.lo .. start + word_from + b.hi]; } pub fn writeEvent(p: *Pardes, asked: Req, id: usize) Reply { @@ -732,6 +752,11 @@ pub fn writeEvent(p: *Pardes, asked: Req, id: usize) Reply { } if (check.i != req.data.len) return tree.failText(req.tag, E.INVAL, tree.e_bad_event ++ ": a record is <origin><action><q0> <q1> <flag> <count> <text>, a line each"); } + // What a record written back runs fails the write as a click written + // to exec would (a refused Del: EIO and its err), not said and lost. + p.fs.capturing = true; + p.fs.failure_len = 0; + defer p.fs.capturing = false; var run: EventReader = .{ .data = req.data }; while (run.next()) |r| { const live = p.paneBySerial(serial) orelse break; @@ -742,7 +767,7 @@ pub fn writeEvent(p: *Pardes, asked: Req, id: usize) Reply { // An empty range with no text is a click at that point: expanded as // the click would be, to the word or file name under it (acme's // expand, look.c). - const said = if (r.q0 == r.q1) r.text orelse expandAt(whole, lo) else whole[lo..hi]; + const said = if (r.q0 == r.q1) r.text orelse expandAt(p, pane, whole, lo, !r.action.onTag()) else whole[lo..hi]; const text = p.scratch.allocator().dupe(u8, said) catch continue; switch (r.action) { // A body's click written back is done as the click would have @@ -811,6 +836,10 @@ pub fn writeEvent(p: *Pardes, asked: Req, id: usize) Reply { else => unreachable, } } + if (p.fs.failure_len > 0) { + const failure = p.fs.failure[0..p.fs.failure_len]; + return tree.failText(req.tag, ctl.failureErrno(failure), std.fmt.bufPrint(&p.fs.ename, "{s}", .{failure}) catch failure); + } return .{ .tag = req.tag, .written = @intCast(asked.data.len) }; } @@ -981,10 +1010,10 @@ test "a pane deleted while its event file is open leaves no suppression behind" _ = wr(p, Node.of(serial, .ctl), "Del\n"); try testing.expect(p.paneBySerial(serial) == null); try testing.expectEqual(@as(u16, 0), p.fs.listeners); - // The reader's next read hears what acme says of a window gone under it. + // The reader's next read is its end: no more records, no error. const shut = call(p, .{ .tag = 19, .op = .read, .node = event, .handle = a.reply.handle, .size = 64 }); - try testing.expectEqual(E.NOENT, shut.errno()); - try testing.expectEqualStrings(tree.e_shut_down, shut.reply.ename); + try testing.expectEqual(tree.Status.ok, shut.reply.status); + try testing.expectEqualStrings("", shut.bytes); _ = call(p, .{ .tag = 20, .op = .release, .node = event, .handle = a.reply.handle }); _ = call(p, .{ .tag = 21, .op = .release, .node = event, .handle = b.reply.handle }); @@ -1388,7 +1417,7 @@ test "a record is one line of UTF-8: DEL and C1 are spaces, bytes not UTF-8 are test "a long err record keeps its end, its middle given up to an ellipsis" { const p = try withFile(testing.allocator, "x\n"); defer p.deinit(); - _ = wr(p, @intFromEnum(tree.TopFile.ctl), "Bogus " ++ "abcdefgh " ** 40 ++ "\n"); + _ = wr(p, @intFromEnum(tree.TopFile.ctl), "Bogus" ++ "abcdefgh" ** 40 ++ "\n"); const log = @intFromEnum(tree.TopFile.log); const h = call(p, .{ .tag = 1, .op = .open, .node = log }).reply.handle; const text = call(p, .{ .tag = 2, .op = .read, .node = log, .handle = h, .size = 1 << 16 }).bytes; |
