summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/tty/tty.zig79
1 files changed, 79 insertions, 0 deletions
diff --git a/src/tty/tty.zig b/src/tty/tty.zig
index a6f6bdfc..8992b51b 100644
--- a/src/tty/tty.zig
+++ b/src/tty/tty.zig
@@ -243,6 +243,85 @@ test "terminal input preserves fragmented keys queries paste and text after EOF"
try std.testing.expect(resized and up and pasted and clipboard and escape);
}
+test "terminal replies and malformed sequences never become typed text" {
+ if (comptime builtin.os.tag == .windows) return error.SkipZigTest;
+ const gpa = std.testing.allocator;
+ const io = std.testing.io;
+ var env = try std.testing.environ.createMap(gpa);
+ defer env.deinit();
+ var vx = try vaxis.init(io, gpa, &env, .{ .system_clipboard_allocator = gpa });
+ var output: std.Io.Writer.Allocating = .init(gpa);
+ defer output.deinit();
+ defer vx.deinit(gpa, &output.writer);
+ vx.queries_done.store(false, .unordered);
+ var tty: vaxis.Tty = undefined;
+ var loop: Loop = .init(io, &tty, &vx);
+ var cache: vaxis.GraphemeCache = .{};
+ const Reader = struct {
+ parts: []const []const u8,
+ next: usize = 0,
+ fn getWinsize(_: *@This()) !vaxis.Winsize {
+ return .{ .rows = 24, .cols = 80, .x_pixel = 0, .y_pixel = 0 };
+ }
+ fn read(self: *@This(), buf: []u8) !usize {
+ if (self.next == self.parts.len) return 0;
+ const part = self.parts[self.next];
+ self.next += 1;
+ @memcpy(buf[0..part.len], part);
+ return part.len;
+ }
+ };
+ var reader: Reader = .{
+ .parts = &.{
+ // The startup width queries' answers where the terminal has no
+ // explicit width: both used to arrive as a bare F3, typed as text.
+ "a\x1b[1;1R\x1b[1;1R",
+ // A late report somewhere else on the screen, and DECXCPR.
+ "\x1b[12;40R\x1b[?3;7;1R",
+ // rxvt's Alt+Up: ESC before the sequence, not Alt+Esc then `[A`.
+ "b\x1b\x1b[A",
+ // A kitty graphics reply cut inside its ST sliced past its input.
+ "\x1b_Gi=1;OK\x1b",
+ "\\",
+ // An X10 mouse report cut after `CSI M` left three bytes as text.
+ "\x1b[M",
+ " !!",
+ // Malformed color and clipboard replies failed the parse, which
+ // ended input altogether; and a reply ended by BEL ran on to the
+ // next sequence's ESC, leaving what lay between as text.
+ "\x1b]11;rgb:zz/zz/zz\x1b\\\x1b]52;c;!!!!\x07\x1b]52\x07",
+ // Device reports whose fields end early.
+ "\x1b[?1;y\x1b[?997;n\x1b[c",
+ "\x1b[?62;c",
+ "c",
+ },
+ };
+ try readInput(&loop, &reader, &cache);
+ try std.testing.expect(vx.queries_done.load(.unordered));
+ try std.testing.expect(vx.caps.kitty_graphics);
+ var text: std.ArrayList(u8) = .empty;
+ defer text.deinit(gpa);
+ var alt_up = false;
+ var clicked = false;
+ while (try loop.tryEvent()) |event| switch (event) {
+ .winsize => {},
+ .key_press => |key| {
+ if (key.codepoint == vaxis.Key.up and key.mods.alt) {
+ alt_up = true;
+ } else if (key.text) |bytes| {
+ try text.appendSlice(gpa, bytes);
+ } else return error.UnexpectedInputKey;
+ },
+ .mouse => |mouse| {
+ try std.testing.expectEqual(@as(i16, 0), mouse.col);
+ clicked = true;
+ },
+ else => return error.UnexpectedInputEvent,
+ };
+ try std.testing.expectEqualStrings("abc", text.items);
+ try std.testing.expect(alt_up and clicked);
+}
+
test "terminal input cancellation joins blocked reads and queued EOF" {
if (comptime builtin.os.tag == .windows) return error.SkipZigTest;
const gpa = std.testing.allocator;