summaryrefslogtreecommitdiff
path: root/src/gui
diff options
context:
space:
mode:
Diffstat (limited to 'src/gui')
-rw-r--r--src/gui/gui.zig90
1 files changed, 84 insertions, 6 deletions
diff --git a/src/gui/gui.zig b/src/gui/gui.zig
index f7c4c51c..bfd02ff9 100644
--- a/src/gui/gui.zig
+++ b/src/gui/gui.zig
@@ -3599,12 +3599,24 @@ const Shell = struct {
/// `wait_input` here, and this process never sleeps in grid mode. It starts
/// neither the watcher nor the nested listener, so `drainQueue`'s
/// `files_changed` and `command` arms cannot fire behind it.
+ ///
+ /// And it has NO SDL: `SDL_Init` is never called on this path, so the two
+ /// desktop-clipboard methods are nulled rather than left pointing at
+ /// functions that cannot answer. A null `pull_read_clipboard` is not a
+ /// missing feature, it is the core's OWN clipboard (host.zig: "Null
+ /// answers immediately from the in-process clipboard instead, so a request
+ /// never goes unanswered") — the same one `pardes-isolate` runs on. With
+ /// the methods present and returning on `s.gui == null`, `SPC y` went
+ /// nowhere and `SPC p` was answered by nobody, so paste was dead in the
+ /// one mode of this shell a test can drive.
const grid_vtable: pardes.Host.VTable = vt: {
var v = vtable;
v.pull_wait_input = null;
v.push_poll_frame = gridPollFrame;
v.push_present = gridPresent;
v.push_post_present = gridPostPresent;
+ v.push_set_clipboard = null;
+ v.pull_read_clipboard = null;
break :vt v;
};
@@ -4019,20 +4031,86 @@ fn takeClipboard() ?[:0]u8 {
return text;
}
+/// Both of these are only ever reached through `Shell.vtable`, which
+/// `Shell.host` installs only when there IS a window: the headless grid
+/// harness nulls them and keeps the core's own clipboard. So neither needs a
+/// `s.gui == null` guard, and neither may have one — a method that returns
+/// without answering is exactly what left `SPC p` unanswered in grid mode.
fn setClipboard(ctx: ?*anyopaque, text: []const u8) void {
- const s = shellOf(ctx);
- if (s.gui == null) return;
- putClipboard(s.gpa, text);
+ putClipboard(shellOf(ctx).gpa, text);
}
fn readClipboard(ctx: ?*anyopaque) void {
- const s = shellOf(ctx);
- if (s.gui == null) return;
// SDL answers synchronously, so the paste the core is waiting on lands
// inside this same drain — nothing to remember, no reply path to plumb.
const text = takeClipboard() orelse return;
defer c.SDL_free(text.ptr);
- s.core.update(.{ .paste = text });
+ shellOf(ctx).core.update(.{ .paste = text });
+}
+
+test "the headless grid host round-trips a yank back as a paste" {
+ // The GUI shell's testable mode, driven through the SAME host the grid
+ // harness installs — `Shell.host()` picks `grid_vtable` off `gui == null`,
+ // so this is the real seam and not a hand-built one. Before the two
+ // clipboard methods were nulled, `SPC y` reached a function that returned
+ // on `gui == null` and `SPC p` was answered by nobody: the content below
+ // never changed, in the one mode of this file a test can run.
+ const gpa = std.testing.allocator;
+ const core = try pardes.Pardes.init(gpa, .{ .cols = 80, .rows = 24, .file = "mise.toml" });
+ defer core.deinit();
+ core.update(.{ .resize = .{ .cols = 80, .rows = 24 } });
+
+ // Everything a Shell needs that this path never touches, at its zero
+ // value; `io` alone is undefined, because a clipboard is not IO the
+ // std.Io interface knows about and no arm reached here reads it.
+ var ptys: [pardes.MAX_PANES]?Pty = @splat(null);
+ var gens: [pardes.MAX_PANES]u32 = @splat(0);
+ var lsp_workers: LspWorkers = .{};
+ var queue: Queue = .{
+ .gpa = gpa,
+ .lsp_allocator = gpa,
+ .lsp_workers = &lsp_workers,
+ .sdl_wake = false,
+ };
+ var pipe_tasks: PipeTasks = .{};
+ var watches: file_watch.Table = @splat(null);
+ var prompt_rcs = shell_bin.PromptRcs.init();
+ defer prompt_rcs.deinit();
+ var shell: Shell = .{
+ .core = core,
+ .gui = null,
+ .io = undefined,
+ .gpa = gpa,
+ .lsp_allocator = gpa,
+ .prompt_rcs = &prompt_rcs,
+ .ptys = &ptys,
+ .gens = &gens,
+ .queue = &queue,
+ .pipe_tasks = &pipe_tasks,
+ .inotify_fd = -1,
+ .watches = &watches,
+ };
+ core.host = shell.host();
+ try std.testing.expect(core.host.vtable == &Shell.grid_vtable);
+
+ // `SPC y`: the selection to the system clipboard. Headless, "the system"
+ // is the core's in-process one.
+ const pane = core.panes[0].?;
+ core.update(.{ .key = .{ .cp = ' ' } });
+ core.update(.{ .key = .{ .cp = 'y' } });
+ while (core.nextEffect()) |e| core.perform(e);
+ const yanked = core.yank orelse return error.MissingYank;
+ try std.testing.expect(yanked.len > 0);
+
+ // ...and `SPC p` gets it back, as an ordinary paste event, inside the
+ // drain. A host that cannot answer leaves the file exactly as it was.
+ const before = pane.file.?.content.len;
+ core.update(.{ .key = .{ .cp = ' ' } });
+ core.update(.{ .key = .{ .cp = 'p' } });
+ try std.testing.expect(core.clip_pending != null);
+ while (core.nextEffect()) |e| core.perform(e);
+ try std.testing.expect(core.clip_pending == null);
+ try std.testing.expectEqual(before + yanked.len, pane.file.?.content.len);
}
fn openLink(ctx: ?*anyopaque, url: []const u8) void {