summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGabriel Schneider <[email protected]>2026-09-30 17:03:07 -0300
committerGabriel Schneider <[email protected]>2026-10-01 00:12:17 -0300
commit0cde1a7537cd9f88b2f97f3c51b54669d909b597 (patch)
treeebbf8b12f2c8c5ea90be8be8d714fabcca4d535e
parent257936b5803b5660428f29fdf4bae620f83b4018 (diff)
downloadpardes-0cde1a7537cd9f88b2f97f3c51b54669d909b597.tar.gz
pardes-0cde1a7537cd9f88b2f97f3c51b54669d909b597.zip
A focus moved over 9P tells the programs that asked for focus reports, as a click does
A program that set DECSET 1004 heard `CSI O` and `CSI I` when the keyboard moved by a click, a key or the window's focus, because update() checks after each event. A /focus write went through serveFs, which never did, so the program kept believing it had focus until the next input event. serveFs now runs the same check once a request has settled. Co-Authored-By: Claude Opus 5.5 <[email protected]>
-rw-r--r--src/ninep/ctl.zig26
-rw-r--r--src/pardes.zig3
2 files changed, 29 insertions, 0 deletions
diff --git a/src/ninep/ctl.zig b/src/ninep/ctl.zig
index 66fa22ab..aebd07e2 100644
--- a/src/ninep/ctl.zig
+++ b/src/ninep/ctl.zig
@@ -2563,6 +2563,32 @@ test "a 9P look keeps its leading blanks, a blank-only look included, and exec s
try testing.expect(th.logHas(p, "blanks trimmed"));
}
+test "a focus moved over 9P tells the programs that asked for focus reports, as a click does" {
+ const p = try Pardes.init(testing.allocator, .{ .cols = 90, .rows = 30, .shells = 2 });
+ defer p.deinit();
+ while (p.nextEffect()) |_| {}
+ const first = for (p.panes) |slot| {
+ if (slot) |pane| if (pane.isTerminal()) break pane.serial;
+ } else return error.NoShell;
+ try testing.expectEqual(Status.ok, wr(p, Node.of(first, .exec), "Tty\n").reply.status);
+ var shells: [2]usize = undefined;
+ var n: usize = 0;
+ for (p.panes, 0..) |slot, id| if (slot) |pane| if (pane.isTerminal() and n < 2) {
+ shells[n] = id;
+ n += 1;
+ };
+ try testing.expectEqual(@as(usize, 2), n);
+ p.active = shells[0];
+ p.update(.{ .output = .{ .pane = @intCast(shells[0]), .bytes = "\x1b[?1004h" } });
+ p.update(.{ .output = .{ .pane = @intCast(shells[1]), .bytes = "\x1b[?1004h" } });
+ while (p.nextEffect()) |_| {}
+ var digits: [16]u8 = undefined;
+ const moved = wr(p, @intFromEnum(tree.TopFile.focus), try std.fmt.bufPrint(&digits, "{d}\n", .{p.panes[shells[1]].?.serial}));
+ try testing.expectEqual(Status.ok, moved.reply.status);
+ try testing.expectEqual(shells[1], p.active);
+ try testing.expectEqualStrings("\x1b[O\x1b[I", moved.pty_buf[0..moved.pty_len]);
+}
+
test "a line starting with # runs as nothing at an exec or a ctl, silently" {
const p = try withFile(testing.allocator, "x\n");
defer p.deinit();
diff --git a/src/pardes.zig b/src/pardes.zig
index 402b3cd8..3a6e154a 100644
--- a/src/pardes.zig
+++ b/src/pardes.zig
@@ -5733,6 +5733,9 @@ pub const Pardes = struct {
// the end of whatever the editor does next.
p.sync();
p.fsReport();
+ // A /focus write moves the keyboard as a click does: the programs
+ // that asked (DECSET 1004) hear it now, as after update().
+ p.reportFocus();
ctlfs.events.announce(p);
return reply;
}