const std = @import("std"); const pardes = @import("pardes"); const panes = pardes.panes; const Pardes = pardes.Pardes; fn mode(p: *Pardes, id: usize, expected: panes.Text.Mode) !void { try std.testing.expect(p.executeBuiltinLine(id, "Mode")); try std.testing.expectEqual(expected, p.panes[id].?.body.mode); try std.testing.expect(p.panes[id].?.focus == .body); } fn clickModeTag(p: *Pardes) !void { const pane = p.panes[0].?; const text = try pardes.tagline.tagText(p, 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.TAG_TEXT_INSET + @as(u16, @intCast(col)), .row = row } }); try std.testing.expect(pane.focus == .tag); } fn customTag(pane: *pardes.Pane, text: []const u8) !void { if (pane.tag.own) |own| pane.gpa.free(own); pane.tag.own = try pane.gpa.dupe(u8, text); } 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(panes.Text.Mode.tty, pane.body.mode); try std.testing.expect(std.mem.indexOf(u8, try pardes.tagline.tagText(p, p.scratch.allocator(), pane), " Mode") != null); try mode(p, 0, .normal); try mode(p, 0, .insert); // A clicked tag is typed into; its execute key is a normal-mode key, as // a body's is, and the body keeps its own mode meanwhile. try clickModeTag(p); try std.testing.expectEqual(panes.Text.Mode.insert, pane.tag.mode); try std.testing.expectEqual(panes.Text.Mode.insert, pane.body.mode); p.update(.{ .key = .{ .cp = pardes.Key.escape } }); p.update(.{ .key = .{ .cp = pardes.Key.tab } }); try std.testing.expectEqual(panes.Text.Mode.tty, pane.body.mode); try std.testing.expect(pane.focus == .body); while (p.nextEffect()) |effect| try std.testing.expect(effect != .write); try 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(panes.Text.Mode.tty, pane.body.mode); p.update(.{ .key = .{ .cp = 'b', .ctrl = true } }); try std.testing.expectEqual(panes.Text.Mode.normal, pane.body.mode); p.update(.{ .key = .{ .cp = 'b', .ctrl = true } }); try std.testing.expectEqual(panes.Text.Mode.tty, pane.body.mode); try std.testing.expectEqualStrings(" Togglettymode Keep", pane.tag.own.?); } 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 pardes.tagline.tagText(p, p.scratch.allocator(), pane), " Mode") == null); try 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(panes.Text.Mode.insert, pane.body.mode); if (normal_tag) pane.tag.mode = .normal else p.update(.{ .key = .{ .cp = pardes.Key.escape } }); p.update(.{ .key = .{ .cp = pardes.Key.tab } }); try std.testing.expectEqual(panes.Text.Mode.normal, pane.body.mode); try std.testing.expect(pane.focus == .body); try std.testing.expect(pane.body.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.body.append_at != null); try mode(p, 0, .normal); try std.testing.expect(pane.body.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); } }