From b934723dfd312972810807127eda0fc17406a6a0 Mon Sep 17 00:00:00 2001 From: Levi Neuwirth Date: Sat, 27 Jun 2026 22:03:58 -0400 Subject: [PATCH] pmacs context menu: clipboard commands/keys + LSP context accessor (Q#CM5/Q#CM6) The Lua glue that gives the menu real items to surface. - `edit.copy/cut/paste/select-all` commands (Q#CM6) over the core clipboard, with the Emacs kill/yank bindings `M-w`/`C-w`/`C-y` and `C-x h` (the CUA trio's keys are already bound: `C-a` line-start, `C-v` page-down). - `pmacs.lsp.active_attachment()` (Q#CM5): a pure, side-effect-free attachment lookup for the menu's `symbol`/`diagnostic` visibility checks. Unlike `attached_for_active`, it never triggers an attach just because the menu opened. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_014TXbAwk27agwhrNNrhLi2U --- builtin/commands/default.lua | 25 +++++++++++++++++++++++++ builtin/keymaps/default.lua | 9 +++++++++ builtin/runtime/lsp.lua | 12 ++++++++++++ 3 files changed, 46 insertions(+) diff --git a/builtin/commands/default.lua b/builtin/commands/default.lua index af31814..b220a74 100644 --- a/builtin/commands/default.lua +++ b/builtin/commands/default.lua @@ -160,6 +160,31 @@ cmd { name = "region.cancel", description = "Drop any active selection without changing the cursor.", fn = function() ed.clear_selection() end } +-- Clipboard (Q#CM6) ---------------------------------------------------------- +-- Copy/cut publish the selection to the OS clipboard (OSC 52 in the TUI, +-- arboard in the GPU); paste inserts the in-app slot, which Ctrl-V / +-- bracketed paste also refreshes. The default bindings are the Emacs +-- kill/yank set (M-w / C-w / C-y, C-x h), which were all free. + +cmd { name = "edit.copy", + description = "Copy the active region to the clipboard.", + fn = function() + if not ed.clipboard_copy() then ed.set_status("no region") end + end } +cmd { name = "edit.cut", + description = "Cut the active region to the clipboard.", + fn = function() + if not ed.clipboard_cut() then ed.set_status("no region") end + end } +cmd { name = "edit.paste", + description = "Paste the clipboard at the cursor, replacing any region.", + fn = function() + if not ed.clipboard_paste() then ed.set_status("clipboard empty") end + end } +cmd { name = "edit.select-all", + description = "Select the whole buffer.", + fn = function() ed.select_all() end } + -- File I/O ------------------------------------------------------------------- cmd { name = "buffer.save", description = "Save the current buffer to its backing file.", diff --git a/builtin/keymaps/default.lua b/builtin/keymaps/default.lua index 18b9386..0c9f612 100644 --- a/builtin/keymaps/default.lua +++ b/builtin/keymaps/default.lua @@ -103,6 +103,15 @@ bind("C-S-", "cursor.select-word-right") bind("C-S-", "cursor.select-paragraph-up") bind("C-S-", "cursor.select-paragraph-down") +-- Clipboard (Q#CM6). The Emacs kill/yank set --- all of these were free +-- in the default map (C-a / C-v are taken for line-start / page-down, so +-- the CUA trio would have clobbered motion). C-w cuts, M-w copies, C-y +-- pastes; C-x h selects the whole buffer (Emacs mark-whole-buffer). +bind("M-w", "edit.copy") +bind("C-w", "edit.cut") +bind("C-y", "edit.paste") +bind("C-x h", "edit.select-all") + -- Undo / redo ---------------------------------------------------------------- -- -- Multiple undo bindings exist because terminals translate Ctrl+/ diff --git a/builtin/runtime/lsp.lua b/builtin/runtime/lsp.lua index 9699367..032f3db 100644 --- a/builtin/runtime/lsp.lua +++ b/builtin/runtime/lsp.lua @@ -532,6 +532,18 @@ local function attached_for_active() return attach_buffer(buf) end +-- Pure, side-effect-free attachment lookup for the active buffer: +-- returns the live record (with `.uri`) when a server is already +-- attached, else nil. Unlike `attached_for_active`, it never *triggers* +-- an attach --- the context menu (Q#CM3) calls it to decide whether to +-- show symbol/diagnostic items, and must not perturb LSP state just by +-- opening. +function pmacs.lsp.active_attachment() + local buf = pmacs.window.buffer() + if not buf then return nil end + return attachments[tostring(buf)] +end + -- Hooks -------------------------------------------------------------------- pmacs.hook.add("buffer.after-load", function()