Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ The picker opens as a centered floating window listing recent yanks (newest firs
| `<Up>` / `<Down>` | Same as `k` / `j` |
| `<C-j>` / `<C-k>` | Move the **selected entry** down / up in history order (reorder) |
| `<Enter>` | Paste the selected entry and close |
| `y` | Copy the selected entry to the system clipboard (`+` / `*`) and keep the picker open |
| `dd` | Delete the selected entry from history |
| `q` or `<Esc>` | Close without pasting |

Expand All @@ -84,7 +85,7 @@ While the picker is focused, `<C-w>` does not switch windows or open which-key (
1. Yank text as usual (`y`, `yy`, visual yank, etc.).
2. Open ClipRing (`:ClipRing` or your mapping).
3. Use `j` / `k` to highlight an entry, optionally `<C-j>` / `<C-k>` to reorder favorites.
4. Press `<Enter>` to paste, or `q` to cancel.
4. Press `<Enter>` to paste, `y` to copy to the system clipboard without pasting, or `q` to cancel.

With `persist = true`, history is restored after you restart Neovim (stored under `persist_path`).

Expand All @@ -101,12 +102,15 @@ require("clipring").setup({
open_mapping = "<leader>y", -- string, list of strings, or false (nil = no keymap)
reorder_down_mapping = "<C-j>", -- picker: move entry down in history (false to disable)
reorder_up_mapping = "<C-k>", -- picker: move entry up in history (false to disable)
copy_mapping = "y", -- picker: copy to system clipboard (false to disable)
})
```

**`open_mapping`** — set a string (e.g. `"<leader>y"`) or multiple (`{ "<leader>y", "<M-y>" }`) to open ClipRing from Normal, Visual, and Insert. Leave unset or `nil` to use only `:ClipRing`. Use `false` to clear a keymap after a previous `setup()`.

Omit `reorder_down_mapping` / `reorder_up_mapping` to keep the defaults above. Set either to `false` to turn off that binding.
Omit `reorder_down_mapping` / `reorder_up_mapping` / `copy_mapping` to keep the defaults above. Set any of them to `false` to turn off that binding.

Copy uses Neovim’s `+` and `*` registers (and the unnamed `"` register). You need clipboard support in Neovim (`:checkhealth clipboard`); on remote SSH, OSC52 or a clipboard provider may be required.

If `<C-j>` / `<C-k>` conflict with global maps (e.g. `:move`), use different keys: `reorder_down_mapping = "<A-j>"`.

Expand Down
2 changes: 2 additions & 0 deletions lua/clipring/config.lua
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ local M = {}
---@field open_mapping string|string[]|false|nil keymap(s) to open picker (`nil` = `:ClipRing` only)
---@field reorder_down_mapping string|false|nil move selected entry down in picker (default `<C-j>`)
---@field reorder_up_mapping string|false|nil move selected entry up in picker (default `<C-k>`)
---@field copy_mapping string|false|nil copy selected entry to system clipboard in picker (default `y`)

M.defaults = {
max_entries = 100,
Expand All @@ -21,6 +22,7 @@ M.defaults = {
open_mapping = nil,
reorder_down_mapping = "<C-j>",
reorder_up_mapping = "<C-k>",
copy_mapping = "y",
}

---@type ClipRingConfig
Expand Down
15 changes: 15 additions & 0 deletions lua/clipring/paste.lua
Original file line number Diff line number Diff line change
Expand Up @@ -307,4 +307,19 @@ function M.apply(entry, opener_mode, visual_marks, opener_win, opener_cursor)
end
end

--- Copy ring entry to unnamed and system clipboard registers without pasting.
---@param entry ClipRingEntry|nil
---@return boolean
function M.copy_to_clipboard(entry)
if not entry or not entry.lines or #entry.lines == 0 then
return false
end
local lines = entry.lines
local regtype = entry.regtype or "v"
vim.fn.setreg('"', lines, regtype)
vim.fn.setreg("+", lines, regtype)
vim.fn.setreg("*", lines, regtype)
return true
end

return M
17 changes: 17 additions & 0 deletions lua/clipring/ui.lua
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,17 @@ local function select_current()
paste.apply(entry, mode, marks, opener_win, opener_cursor)
end

local function copy_current()
local all = ring.get_all()
if #all == 0 then
return
end
local entry = all[state.index]
if entry then
paste.copy_to_clipboard(entry)
end
end

local function delete_current()
local all = ring.get_all()
if #all == 0 then
Expand Down Expand Up @@ -236,6 +247,12 @@ local function attach_keymaps()
map("<CR>", function()
select_current()
end, "ClipRing: paste entry")
local copy_key = picker_mapping("copy_mapping", "y")
if copy_key then
map(copy_key, function()
copy_current()
end, "ClipRing: copy entry to system clipboard")
end
map("dd", function()
delete_current()
end, "ClipRing: delete entry")
Expand Down
8 changes: 8 additions & 0 deletions tests/paste_spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -150,4 +150,12 @@ describe("clipring.paste", function()
assert.are_not.equal("hola mundofoo", h.buf_text(buf))
end)

it("copy_to_clipboard sets registers with regtype", function()
local entry = h.entry({ "one", "two" }, "V")
assert.is_true(paste.copy_to_clipboard(entry))
-- Unnamed register is always available (headless has no OS clipboard for +).
assert.same({ "one", "two" }, vim.fn.getreg('"', 1, 1))
assert.are.equal("V", vim.fn.getregtype('"'))
end)

end)
24 changes: 24 additions & 0 deletions tests/ui_spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,30 @@ describe("clipring.ui", function()
ui.close()
end)

it("maps y to copy the selected entry to clipboard registers without closing", function()
ui.open()
local clip_buf = feed_clipring("j")
assert.are.equal(2, h.clipring_selected_line(clip_buf))
feed_clipring("y")
assert.are.equal("older", vim.fn.getreg('"'))
assert.is_true(vim.fn.bufwinid(clip_buf) > 0)
ui.close()
end)

it("maps custom copy key from config", function()
require("clipring.config").setup({
max_entries = 20,
deduplicate = true,
min_length = 1,
persist = false,
copy_mapping = "c",
})
ui.open()
feed_clipring("c")
assert.are.equal("newer", vim.fn.getreg('"'))
ui.close()
end)

it("maps Ctrl-j and Ctrl-k to reorder yanks in the ring", function()
ui.open()
assert.are.equal("newer", ring.get(1).lines[1])
Expand Down