summaryrefslogtreecommitdiff
path: root/src/ninep/events.zig
diff options
context:
space:
mode:
Diffstat (limited to 'src/ninep/events.zig')
-rw-r--r--src/ninep/events.zig79
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;