diff options
Diffstat (limited to 'src/ninep/events.zig')
| -rw-r--r-- | src/ninep/events.zig | 48 |
1 files changed, 36 insertions, 12 deletions
diff --git a/src/ninep/events.zig b/src/ninep/events.zig index f58d970e..45569b22 100644 --- a/src/ninep/events.zig +++ b/src/ninep/events.zig @@ -145,9 +145,11 @@ pub fn announce(p: *Pardes) void { /// Records `<kind> <serial> <name>`. pub fn noteLog(p: *Pardes, kind: LogKind, pane: *Pane) void { - var buf: [4096 + 64]u8 = undefined; - const name = pane_files.nameOf(pane); - pushLog(p, std.fmt.bufPrint(&buf, "{s} {d} {s}\n", .{ @tagName(kind), pane.serial, name[0..@min(name.len, 4096)] }) catch return); + var buf: [4 * 4096 + 64]u8 = undefined; + var name_buf: [4 * 4096]u8 = undefined; + // As /index shows it: a newline in the name is `\n`. + const name = shown(pane_files.nameOf(pane), &name_buf); + pushLog(p, std.fmt.bufPrint(&buf, "{s} {d} {s}\n", .{ @tagName(kind), pane.serial, name }) catch return); } /// Records `msg <serial> <text>` for what the editor said, `-` for no pane. @@ -342,16 +344,37 @@ fn followerRead(p: *Pardes, seq: u64) bool { /// The log is one ring that records whether or not anyone reads it. A record /// is one line: a newline in a message or a name would read as two records. -/// A record as one line of text: a control character, DEL or a C1 control -/// (U+0080-U+009F) is a space each, and a byte that is not UTF-8 is -/// written `\xNN`, so a reader splitting on newlines and decoding UTF-8 -/// never trips. The record's own newline, last, is kept. +/// A record as one line of text (shown): the record's own newline, last, +/// is kept. fn sanitize(record: []const u8, out: []u8) []u8 { + // A newline in a message is a space (its words go on); a name's own + // newline is escaped before it gets here (noteLog). + const w = shownAs(record[0 .. record.len - 1], out[0 .. out.len - 1], false).len; + out[w] = '\n'; + return out[0 .. w + 1]; +} + +/// `text` as one line of UTF-8 a reader can split and decode, as /log and +/// /index show names: a newline is `\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. +pub fn shown(text: []const u8, out: []u8) []u8 { + return shownAs(text, out, true); +} + +fn shownAs(text: []const u8, out: []u8, escape_newline: bool) []u8 { var w: usize = 0; var i: usize = 0; - const body = record[0 .. record.len - 1]; - while (i < body.len and w + 4 < out.len) { + const body = text; + while (i < body.len and w + 4 <= out.len) { const c = body[i]; + if (c == '\n' and escape_newline) { + @memcpy(out[w..][0..2], "\\n"); + w += 2; + i += 1; + continue; + } if (c < ' ' or c == 0x7f) { out[w] = ' '; w += 1; @@ -371,13 +394,12 @@ fn sanitize(record: []const u8, out: []u8) []u8 { i += 2; continue; } - if (w + n + 1 > out.len) break; + if (w + n > out.len) break; @memcpy(out[w..][0..n], body[i..][0..n]); w += n; i += n; } - out[w] = '\n'; - return out[0 .. w + 1]; + return out[0..w]; } fn pushLog(p: *Pardes, raw: []u8) void { @@ -1285,6 +1307,8 @@ test "a long msg record is cut between words at its cap, with an ellipsis" { test "a record is one line of UTF-8: DEL and C1 are spaces, bytes not UTF-8 are escaped" { var out: [64]u8 = undefined; + var name: [64]u8 = undefined; + try testing.expectEqualStrings("/tmp/two\\nlines", shown("/tmp/two\nlines", &name)); try testing.expectEqualStrings("msg - a b c \\xff d\n", sanitize("msg - a\x7fb\xc2\x85c \xff d\n", &out)); try testing.expectEqualStrings("msg - caf\xc3\xa9\n", sanitize("msg - caf\xc3\xa9\n", &out)); } |
