From 6560da36cf8111575cd3fec443a42de6eff82f5f Mon Sep 17 00:00:00 2001 From: Gabriel Schneider Date: Tue, 15 Sep 2026 17:06:27 -0300 Subject: Cycle pane input modes with Mode and retain terminal tag access --- test/appearance_gallery.py | 4 +- test/mode.zig | 114 +++++++++++++++++++++++++++++++++++++++++++++ test/panes.zig | 1 + test/tag_cleanup.py | 23 +++++---- 4 files changed, 132 insertions(+), 10 deletions(-) create mode 100644 test/mode.zig (limited to 'test') diff --git a/test/appearance_gallery.py b/test/appearance_gallery.py index c80d1a33..7779efa9 100644 --- a/test/appearance_gallery.py +++ b/test/appearance_gallery.py @@ -17,7 +17,7 @@ NOTES = b'''Pardes / workspace review New belongs to the column. Tty opens a terminal. -Togglettymode switches its input mode. +Mode switches its input mode. Font MartianMono-NrRg:18 TaglineSize 80 @@ -106,7 +106,7 @@ def run(args): for serial, tag in panes(client).items(): require(' New ' not in tag, 'pane still contains New') require('Newtty' not in tag, 'old terminal name remains') - require('Togglettymode' in panes(client)[terminal], 'terminal mode action missing') + require('Mode' in panes(client)[terminal], 'terminal mode action missing') def owned_window(): result = subprocess.run(['xdotool', 'search', '--onlyvisible', '--pid', str(handles['pid'])], diff --git a/test/mode.zig b/test/mode.zig new file mode 100644 index 00000000..6d694b42 --- /dev/null +++ b/test/mode.zig @@ -0,0 +1,114 @@ +const std = @import("std"); +const pardes = @import("pardes"); +const panes = pardes.panes; +const Pardes = pardes.Pardes; + +fn mode(p: *Pardes, id: usize, expected: pardes.Pane.Mode) !void { + try std.testing.expect(p.executeBuiltinLine(id, "Mode")); + try std.testing.expectEqual(expected, p.panes[id].?.mode); + try std.testing.expect(!p.panes[id].?.tag_edit); +} + +fn clickModeTag(p: *Pardes) !void { + const pane = p.panes[0].?; + const text = try p.tagText(p.scratch.allocator(), pane); + const col = std.mem.indexOf(u8, text, "Mode") orelse return error.MissingModeTag; + const rect = p.rects[0]; + const row = if (p.settings.tag_bottom) rect.y + rect.h - pardes.BOX_H else rect.y; + for ([_]pardes.Mouse.Kind{ .press, .release }) |kind| + p.update(.{ .mouse = .{ .button = .left, .kind = kind, .col = rect.x + pardes.config.GUTTER + @as(u16, @intCast(col)), .row = row } }); + try std.testing.expect(pane.tag_edit); +} + +fn customTag(pane: *pardes.Pane, text: []const u8) void { + @memcpy(pane.tag_tail[0..text.len], text); + pane.tag_tail_len = text.len; + pane.tag_init = true; +} + +test "Mode cycles terminal modes and keeps legacy toggle and custom tags" { + if (comptime !panes.Terminal.enabled) return error.SkipZigTest; + const p = try Pardes.init(std.testing.allocator, .{ .tty_only = true, .cols = 100 }); + defer p.deinit(); + p.presentation.enabled = false; + while (p.nextEffect()) |_| {} + const pane = p.panes[0].?; + try std.testing.expectEqual(pardes.Pane.Mode.tty, pane.mode); + try std.testing.expect(std.mem.indexOf(u8, try p.tagText(p.scratch.allocator(), pane), " Mode") != null); + try mode(p, 0, .normal); + try mode(p, 0, .insert); + try clickModeTag(p); + try std.testing.expectEqual(pardes.Pane.Mode.insert, pane.tag_mode); + p.update(.{ .key = .{ .cp = pardes.Key.tab } }); + try std.testing.expectEqual(pardes.Pane.Mode.tty, pane.mode); + try std.testing.expect(!pane.tag_edit); + while (p.nextEffect()) |effect| try std.testing.expect(effect != .write); + + customTag(pane, " Togglettymode Keep"); + try mode(p, 0, .normal); + try mode(p, 0, .insert); + try std.testing.expect(p.executeBuiltinLine(0, "Togglettymode")); + try std.testing.expectEqual(pardes.Pane.Mode.tty, pane.mode); + p.update(.{ .key = .{ .cp = 'b', .ctrl = true } }); + try std.testing.expectEqual(pardes.Pane.Mode.normal, pane.mode); + p.update(.{ .key = .{ .cp = 'b', .ctrl = true } }); + try std.testing.expectEqual(pardes.Pane.Mode.tty, pane.mode); + try std.testing.expectEqualStrings(" Togglettymode Keep", pane.tag_tail[0..pane.tag_tail_len]); +} + +test "Mode from actual tag Exec preserves parked file and output insert transactions" { + for ([_]bool{ false, true }) |output| { + for ([_]bool{ false, true }) |normal_tag| { + const p = try Pardes.init(std.testing.allocator, .{ .tty_only = true, .cols = 100 }); + defer p.deinit(); + p.presentation.enabled = false; + const pane = try p.setTestFile("ab\n"); + if (output) pane.file.?.output = .{ .from = .search }; + try std.testing.expect(std.mem.indexOf(u8, try p.tagText(p.scratch.allocator(), pane), " Mode") == null); + customTag(pane, " Mode"); + try mode(p, 0, .insert); + p.update(.{ .key = .{ .cp = '界', .text = "界" } }); + p.update(.{ .key = .{ .cp = 'x', .text = "x" } }); + try std.testing.expectEqualStrings("界xab\n", pane.file.?.content); + try clickModeTag(p); + try std.testing.expectEqual(pardes.Pane.Mode.insert, pane.tag_mode); + if (normal_tag) pane.mode = .normal; + p.update(.{ .key = .{ .cp = pardes.Key.tab } }); + try std.testing.expectEqual(pardes.Pane.Mode.normal, pane.mode); + try std.testing.expect(!pane.tag_edit); + try std.testing.expect(pane.append_at == null); + p.update(.{ .key = .{ .cp = 'u' } }); + try std.testing.expectEqualStrings("ab\n", pane.file.?.content); + p.update(.{ .key = .{ .cp = 'a' } }); + p.update(.{ .key = .{ .cp = 'z', .text = "z" } }); + try std.testing.expect(pane.append_at != null); + try mode(p, 0, .normal); + try std.testing.expect(pane.append_at == null); + p.update(.{ .key = .{ .cp = 'u' } }); + try std.testing.expectEqualStrings("ab\n", pane.file.?.content); + } + } +} + +test "Mode leaves image and PDF document modes unchanged" { + const p = try Pardes.init(std.testing.allocator, .{ .tty_only = true }); + defer p.deinit(); + const picture = try panes.Image.create(p, 1, "/mode-test.png", &.{}); + try mode(p, 1, .normal); + try std.testing.expect(picture.image != null); + if (comptime panes.Pdf.enabled) { + var tmp = std.testing.tmpDir(.{}); + defer tmp.cleanup(); + const bytes = try panes.Pdf.pdf.makeLinksTestPdf(std.testing.allocator); + defer std.testing.allocator.free(bytes); + try tmp.dir.writeFile(std.testing.io, .{ .sub_path = "mode.pdf", .data = bytes }); + var buffer: [4096]u8 = undefined; + const len = try tmp.dir.realPath(std.testing.io, &buffer); + const path = try std.fmt.allocPrint(std.testing.allocator, "{s}/mode.pdf", .{buffer[0..len]}); + defer std.testing.allocator.free(path); + const document = try panes.Pdf.openPane(p, 2, path, 1); + const page = document.pdf.?.page; + try mode(p, 2, .normal); + try std.testing.expectEqual(page, document.pdf.?.page); + } +} diff --git a/test/panes.zig b/test/panes.zig index ed48db82..f3e7034f 100644 --- a/test/panes.zig +++ b/test/panes.zig @@ -3929,6 +3929,7 @@ test { _ = JumpSelectionTests; _ = @import("output.zig"); _ = @import("column_drag.zig"); + _ = @import("mode.zig"); _ = @import("pdf.zig"); _ = @import("fs_namespace.zig"); _ = @import("hxdiff.zig"); diff --git a/test/tag_cleanup.py b/test/tag_cleanup.py index 81b15ad8..52ee8e9d 100644 --- a/test/tag_cleanup.py +++ b/test/tag_cleanup.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -"""Check compact PDF tags and Togglettymode/Ctrl-B parity in an isolated 9P session.""" +"""Check compact PDF tags and Mode cycling and Ctrl-B in an isolated 9P session.""" import argparse from pathlib import Path @@ -42,27 +42,34 @@ def run(args): frame = client.screen() for row in range(frame['rows']): text = ''.join(c[0] for c in frame['cells'][row * frame['cols']:(row + 1) * frame['cols']]) - if ' Togglettymode Filter' in text: - return text.index(' Togglettymode Filter') + 1, row, text[0] + if ' Mode Filter' in text: + return text.index(' Mode Filter') + 1, row, text[0] return None require(wait_for(lambda: (p := tag_position()) and p[2] == '$'), 'new terminal not in raw mode') x, y, _ = tag_position() keys.click(x, y, button=1) - require(wait_for(lambda: (p := tag_position()) and p[2] == ' '), 'Togglettymode click did not enter normal mode') + require(wait_for(lambda: (p := tag_position()) and p[2] == ' '), 'Mode click did not enter normal mode') + for badge in ('^', '$'): + x, y, _ = tag_position() + keys.click(x, y, button=1) + require(wait_for(lambda: (p := tag_position()) and p[2] == badge), 'Mode cycle did not reach ' + badge) + x, y, _ = tag_position() + keys.click(x, y, button=1) + require(wait_for(lambda: (p := tag_position()) and p[2] == ' '), 'Mode did not return to normal') keys.send(b'\x02') require(wait_for(lambda: (p := tag_position()) and p[2] == '$'), 'Ctrl-B did not restore raw mode') x, y, _ = tag_position() keys.click(x, y) keys.send(b'\t') - require(wait_for(lambda: (p := tag_position()) and p[2] == ' '), 'Togglettymode from edited tag ignored parked raw mode') + require(wait_for(lambda: (p := tag_position()) and p[2] == ' '), 'Mode from edited tag ignored parked raw mode') keys.send(b'\x02') - client.write(f'/self/pane/{tty}/pty/data', b"printf 'Togglettymode toggle ready\\n'\r") - require(wait_for(lambda: b'Togglettymode toggle ready' in client.read(f'/self/pane/{tty}/body')), 'terminal input failed') + client.write(f'/self/pane/{tty}/pty/data', b"printf 'Mode toggle ready\\n'\r") + require(wait_for(lambda: b'Mode toggle ready' in client.read(f'/self/pane/{tty}/body')), 'terminal input failed') capture(client, root, 'pdf-and-tty-tags', not args.tty) require(source.read_bytes() == original, 'PDF file changed') client.write(f'/self/pane/{tty}/pty/data', b'exit\r') - print('PDF tag, Togglettymode click, Ctrl-B, edited-tag execution and source preservation passed') + print('PDF tag, Mode click, Ctrl-B, edited-tag execution and source preservation passed') if __name__ == '__main__': -- cgit v1.3