diff options
| -rw-r--r-- | docs/fs.md | 6 | ||||
| -rw-r--r-- | src/mouse.zig | 17 | ||||
| -rw-r--r-- | src/ninep/events.zig | 50 | ||||
| -rw-r--r-- | src/ninep/pane.zig | 8 |
4 files changed, 73 insertions, 8 deletions
@@ -713,7 +713,11 @@ was expanded from a click (a second record gives the original range), 4 (a look) the text is a file name or address, 8 (an exec) chorded: two records follow, the argument's text and where it came from, `<file>:#q0,#q1`. Written back, an `X`/`x` record executes and an `L`/`l` record looks, as -the click would have (a chorded one with its argument); the origin letter +the click would have. A chorded one (flag 8) runs with its argument: the +record after it in the same write, else the one kept from the click; its +two follow-up records written back after it, together or in writes of +their own, are consumed as its, never run as commands (written back alone +with no argument kept, it waits for its argument record); the origin letter written back is ignored but for `F` on `X`, which runs as the command it was rather than going to a bound REPL; `I`, `D`, `i` and `d` are reports and are refused. Read `n` bytes of the text, not up to a newline -- bytes here, where acme counts runes. Every offset and diff --git a/src/mouse.zig b/src/mouse.zig index 9f054e2e..0d1bfadd 100644 --- a/src/mouse.zig +++ b/src/mouse.zig @@ -1676,5 +1676,22 @@ test "a chorded exec an event reader holds is told with its argument, and writte try std.testing.expectEqual(ctlfs.Status.ok, th.call(p, .{ .tag = 3, .op = .write, .node = event, .handle = h, .data = line }).reply.status); const pane = p.panes[0].?; try std.testing.expectEqualStrings("arg-text", pane.msg[0..pane.msg_len]); + // All three records written back as read, in one write or in three: + // the two after the first are its argument and where it came from, + // never commands of their own. + for ([_]bool{ true, false }) |together| { + pane.msg_len = 0; + var all: [256]u8 = undefined; + var w = std.Io.Writer.fixed(&all); + for (records) |r| try w.print("M{s}", .{r[1..]}); + if (together) { + _ = th.call(p, .{ .tag = 5, .op = .write, .node = event, .handle = h, .data = w.buffered() }); + } else for (records) |r| { + var one: [80]u8 = undefined; + _ = th.call(p, .{ .tag = 6, .op = .write, .node = event, .handle = h, .data = try std.fmt.bufPrint(&one, "M{s}", .{r[1..]}) }); + } + try std.testing.expectEqualStrings("arg-text", pane.msg[0..pane.msg_len]); + try std.testing.expect(!th.logHas(p, "run ")); + } _ = th.call(p, .{ .tag = 4, .op = .release, .node = event, .handle = h }); } diff --git a/src/ninep/events.zig b/src/ninep/events.zig index 1067b60e..7a06bd04 100644 --- a/src/ninep/events.zig +++ b/src/ninep/events.zig @@ -536,7 +536,7 @@ pub fn notePtyOutput(p: *Pardes, id: usize, bytes: []const u8) void { p.fs.news = true; } -const EventRecord = struct { origin: u8, action: Action, q0: u32, q1: u32, text: ?[]const u8 = null }; +const EventRecord = struct { origin: u8, action: Action, q0: u32, q1: u32, flag: u32 = 0, text: ?[]const u8 = null }; const EventReader = struct { data: []const u8, @@ -557,8 +557,9 @@ const EventReader = struct { // is taken too, so a click with no place in the text (one written // to a pane's own look or exec) can be written back and done. var text: ?[]const u8 = null; + var flag: u32 = 0; if (i < er.data.len and er.data[i] != '\n') { - _ = scanNumber(er.data, &i) orelse return null; + flag = scanNumber(er.data, &i) orelse return null; const n = scanNumber(er.data, &i) orelse return null; if (i >= er.data.len or er.data[i] != ' ' or er.data.len - (i + 1) < n) return null; text = er.data[i + 1 ..][0..n]; @@ -566,7 +567,7 @@ const EventReader = struct { } if (i >= er.data.len or er.data[i] != '\n') return null; er.i = i + 1; - return .{ .origin = origin, .action = action, .q0 = q0, .q1 = q1, .text = text }; + return .{ .origin = origin, .action = action, .q0 = q0, .q1 = q1, .flag = flag, .text = text }; } }; @@ -616,17 +617,52 @@ pub fn writeEvent(p: *Pardes, req: Req, id: usize) Reply { // never a click, and runs as the command it was. // The chorded exec a reader was told of runs with its argument // (mouse.reportGesture), when it is that range written back. - .body_exec => { + .body_exec, .tag_exec => { const pf = &pane.fs; - const arg = if (pf.chord_arg) |a| (if (pf.chord_at.q0 == r.q0 and pf.chord_at.q1 == r.q1) a else null) else null; - _ = exec.executeFrom(p, live, exec.withArg(p, text, arg), r.origin != 'F'); + // The two records that follow a chorded exec (its argument + // and where it came from), written back after it, are its: + // consumed, never run as commands of their own. + if (pf.chord_follow > 0 and r.q0 == 0 and r.q1 == 0 and r.flag == 0) { + pf.chord_follow -= 1; + // The argument a command written back alone waited for. + if (pf.chord_pending) |command| { + pf.chord_pending = null; + defer p.gpa.free(command); + const line = exec.withArg(p, command, r.text orelse ""); + if (pf.chord_pending_tag) _ = exec.execute(p, live, line) else _ = exec.executeFrom(p, live, line, true); + } + continue; + } + var arg = if (pf.chord_arg) |a| (if (pf.chord_at.q0 == r.q0 and pf.chord_at.q1 == r.q1) a else null) else null; + if (r.flag & flag_chorded != 0) { + // Its argument is the next record when it came in the + // same write; the rest that follow are consumed. + pf.chord_follow = 2; + const save = run.i; + if (run.next()) |following| if (following.q0 == 0 and following.q1 == 0 and following.flag == 0) { + if (following.text) |t| arg = p.scratch.allocator().dupe(u8, t) catch arg; + pf.chord_follow = 1; + } else { + run.i = save; + }; + // Alone, with no argument kept: it waits for its own. + if (arg == null) { + if (pf.chord_pending) |old| p.gpa.free(old); + pf.chord_pending = p.gpa.dupe(u8, text) catch null; + pf.chord_pending_tag = r.action == .tag_exec; + continue; + } + } + if (r.action == .tag_exec) + _ = exec.execute(p, live, exec.withArg(p, text, arg)) + else + _ = exec.executeFrom(p, live, exec.withArg(p, text, arg), r.origin != 'F'); if (arg != null) if (p.paneBySerial(serial)) |still| { const spf = &p.panes[still].?.fs; if (spf.chord_arg) |a| p.gpa.free(a); spf.chord_arg = null; }; }, - .tag_exec => _ = exec.execute(p, live, text), // What it found is what the look file reads back next, as a // look written there would leave (ctl.run). .body_look, .tag_look => { diff --git a/src/ninep/pane.zig b/src/ninep/pane.zig index 4a50b9a1..899d8528 100644 --- a/src/ninep/pane.zig +++ b/src/ninep/pane.zig @@ -81,11 +81,19 @@ pub const State = struct { /// for the record's write-back at `chord_at`, which runs with it. chord_arg: ?[]u8 = null, chord_at: Range = .{}, + /// A chorded record written back: how many of its two follow-up records + /// (argument, location) are still to be consumed rather than run. + chord_follow: u8 = 0, + /// A chorded record written back alone with no argument kept: the + /// command, run when its argument record comes. + chord_pending: ?[]u8 = null, + chord_pending_tag: bool = false, pub const Range = struct { q0: u32 = 0, q1: u32 = 0 }; pub fn deinit(pf: *State, gpa: std.mem.Allocator) void { if (pf.chord_arg) |a| gpa.free(a); + if (pf.chord_pending) |c| gpa.free(c); pf.events.deinit(gpa); pf.pty_out.deinit(gpa); pf.tag_snap.deinit(gpa); |
