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
23 changes: 18 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,21 @@
[![Tests](https://github.com/alexesba/clipring.nvim/actions/workflows/test.yml/badge.svg?branch=main)](https://github.com/alexesba/clipring.nvim/actions/workflows/test.yml)
[![MIT License](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)

Minimal yank history for Neovim — a lightweight Lua plugin inspired by YankRing and Windows Clipboard History. No required dependencies.
Minimal yank history for Neovim — a lightweight Lua plugin inspired by YankRing and Windows Clipboard History. No required dependencies — works with any Neovim setup (LazyVim, packer, plain Lua config). Treesitter and which-key are optional extras, not requirements.

**Repository:** [github.com/alexesba/clipring.nvim](https://github.com/alexesba/clipring.nvim)

## Screenshots

![ClipRing picker with history list and syntax-highlighted preview](doc/screenshots/picker-with-preview.png)

![ClipRing with an empty yank history](doc/screenshots/picker-empty.png)

## Features

- Automatic capture of every yank
- Floating popup history (`:ClipRing`) with a multiline preview pane
- Floating popup history (`:ClipRing`) with an auto-sizing multiline preview pane
- Preview pane shown only when there is content to display; optional syntax highlighting for code
- Navigate with `j` / `k`, reorder with `<C-j>` / `<C-k>`, paste with `<Enter>`, copy to the system clipboard with `y`, delete with `dd`
- Works from Normal, Insert, and Visual modes
- Optional JSON persistence between sessions
Expand Down Expand Up @@ -56,7 +63,7 @@ With a minimal `lazy.nvim` / `packer.nvim` setup, Neovim loads the plugin from `
| `:ClipRing` | Always available (no keymap required) |
| Your `open_mapping` | After you set one in `setup()` (e.g. `<leader>y`) |

The picker opens as two side-by-side floats when there are yanks to show: a **history list** (height follows entry count) and a **preview pane** that resizes to fit the selected entry. With an empty ring, only the list is shown.
The picker opens as two side-by-side floats when there are yanks to show: a **history list** (height follows entry count) and a **preview pane** that resizes to fit the selected entry. Code yanks are syntax-highlighted when ClipRing can detect a language (markdown ` ```lang ` fences, shebangs, or simple heuristics). With an empty ring, only the list is shown.

### Inside the picker

Expand All @@ -70,7 +77,7 @@ The picker opens as two side-by-side floats when there are yanks to show: a **hi
| `dd` | Delete the selected entry from history |
| `q` or `<Esc>` | Close without pasting |

While the picker is focused, `<C-w>` does not switch windows or open which-key (close the picker first, like Telescope). Keys apply to the history list; the preview pane is read-only. If you use [which-key.nvim](https://github.com/folke/which-key.nvim), `setup()` disables which-key on the `clipring` and `clipring_preview` filetypes.
While the picker is focused, `<C-w>` does not switch windows or open which-key (close the picker first, like Telescope). Keys apply to the history list; the preview pane is read-only. If you use [which-key.nvim](https://github.com/folke/which-key.nvim), `setup()` disables which-key on the history list buffer (`clipring` filetype).

### Paste behavior by mode

Expand Down Expand Up @@ -109,6 +116,7 @@ require("clipring").setup({
picker_width = 80, -- total inner width; 0 = nearly full editor width
picker_max_height = 18, -- max height for list and preview
preview_max_lines = 16, -- max lines per entry in the preview pane
preview_syntax = true, -- highlight code in the preview when a language is detected
})
```

Expand All @@ -120,6 +128,8 @@ Copy uses Neovim’s `+` and `*` registers (and the unnamed `"` register). You n

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

**Preview syntax** — when `preview_syntax` is true (default), ClipRing detects a language from markdown ` ```lang ` fences, shebangs, Neovim’s filetype match, or simple heuristics, then highlights with built-in Vim syntax. If Treesitter parsers are installed, highlighting may look better; set `preview_syntax = false` for plain text only. Fence markers are stripped from the preview — only the code body is shown.

### Advanced

```lua
Expand Down Expand Up @@ -149,11 +159,14 @@ Set `PLENARY_DIR` if plenary is already on disk:
PLENARY_DIR=~/.local/share/nvim/lazy/plenary.nvim ./scripts/run_tests.sh
```

To regenerate README screenshots, see [doc/screenshots/README.md](doc/screenshots/README.md).

Coverage today:

- **ring** — add, dedupe, max size, remove, reorder
- **preview_syntax** — fence stripping, language detection, heuristics
- **paste** — visual capture (`v` / `'<`), charwise replace vs append, insert-mode paste at saved cursor
- **ui** — picker from insert, navigation, reorder keys, multiline preview, clipboard copy, which-key / `<C-w>` behavior
- **ui** — picker from insert, navigation, reorder keys, auto-size layout, conditional preview, multiline preview, syntax highlighting, clipboard copy, which-key / `<C-w>` behavior
- **yank** — `TextYankPost` capture
- **setup** — `open_mapping` registration

Expand Down
32 changes: 32 additions & 0 deletions doc/screenshots/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# README screenshots

Maintainer tooling for the images linked from the root [README](../../README.md). Not used by the plugin at runtime.

| File | Description |
|------|-------------|
| `picker-with-preview.png` | History list + syntax-highlighted preview |
| `picker-empty.png` | Empty ring (list only) |

## Regenerate (macOS)

Requires [WezTerm](https://wezfurlong.org/wezterm/) and Screen Recording permission for your terminal.

```bash
./doc/screenshots/capture.sh # both images
./doc/screenshots/capture.sh full # preview shot only
./doc/screenshots/capture.sh empty # empty ring only
```

Preview a demo without capturing:

```bash
./doc/screenshots/open_demo.sh full
```

Demos use `nvim --clean` so your personal config does not override the sample yanks.

On other platforms, open a demo and use your OS screenshot tool:

```bash
nvim --clean --cmd "cd $(pwd)" --cmd "luafile doc/screenshots/demo.lua"
```
55 changes: 55 additions & 0 deletions doc/screenshots/capture.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
#!/usr/bin/env bash
# Maintainer helper: capture README screenshots (macOS + WezTerm).
# Run from your own terminal — not from Cursor's agent shell.
set -euo pipefail

DIR="$(cd "$(dirname "$0")" && pwd)"
ROOT="$(cd "$DIR/../.." && pwd)"

NVIM_OPTS=(
--clean
--cmd "set columns=100 lines=32"
--cmd "cd $ROOT"
)

capture_interactive() {
local demo_lua="$1"
local output="$2"
local label="$3"
local wait="${4:-3}"

echo ""
echo "==> $label"
echo " Opening demo..."
wezterm start --always-new-process --position 160,90 -- \
nvim "${NVIM_OPTS[@]}" --cmd "luafile $demo_lua" &
sleep "$wait"
echo " Click the WezTerm window to save: $output"
screencapture -iW "$output"
echo " Saved $output"
sleep 0.5
}

case "${1:-all}" in
full|with-preview|preview)
capture_interactive "$DIR/demo.lua" "$DIR/picker-with-preview.png" \
"Picker with history list + syntax-highlighted preview" 3.5
;;
empty)
capture_interactive "$DIR/demo_empty.lua" "$DIR/picker-empty.png" \
"Empty ring (list only, no preview pane)" 3
;;
all)
capture_interactive "$DIR/demo.lua" "$DIR/picker-with-preview.png" \
"Picker with history list + syntax-highlighted preview" 3.5
capture_interactive "$DIR/demo_empty.lua" "$DIR/picker-empty.png" \
"Empty ring (list only, no preview pane)" 3
;;
*)
echo "Usage: $0 [all|full|empty]" >&2
exit 1
;;
esac

echo ""
echo "Done. Screenshots in $DIR"
57 changes: 57 additions & 0 deletions doc/screenshots/demo.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
--- Demo buffer + yank history for README screenshots.
--- Usage: nvim --clean --cmd "cd <repo>" --cmd "luafile doc/screenshots/demo.lua"

local root = vim.fn.fnamemodify(vim.fn.getcwd(), ":p"):gsub("/$", "")

vim.opt.rtp:prepend(root)
vim.opt.termguicolors = true
vim.opt.number = true
vim.opt.signcolumn = "yes"
vim.opt.swapfile = false
vim.cmd("colorscheme default")

vim.api.nvim_buf_set_lines(0, 0, -1, true, {
'require("clipring").setup({',
' open_mapping = "<leader>y",',
" persist = true,",
" preview_syntax = true,",
"})",
"",
"-- Yank history opens with :ClipRing",
})
vim.bo.filetype = "lua"

require("clipring").setup({
open_mapping = nil,
persist = false,
preview_syntax = true,
picker_width = 90,
picker_max_height = 16,
preview_max_lines = 12,
preview_length = 72,
})

local ring = require("clipring.ring")
ring.clear()
ring.add({ "Hello from ClipRing!" }, "v")
ring.add({
"class Widget",
" def name",
" 'clipring'",
" end",
"end",
}, "V")
ring.add({
"```lua",
'require("clipring").setup({',
' open_mapping = "<leader>y",',
" preview_syntax = true,",
"})",
"```",
}, "V")

assert(ring.count() == 3, "demo ring should have 3 entries, got " .. ring.count())

vim.defer_fn(function()
require("clipring.ui").open()
end, 500)
21 changes: 21 additions & 0 deletions doc/screenshots/demo_empty.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
--- Empty ring demo for README screenshots (list only, no preview pane).

local root = vim.fn.fnamemodify(vim.fn.getcwd(), ":p"):gsub("/$", "")

vim.opt.rtp:prepend(root)
vim.opt.termguicolors = true
vim.opt.number = true
vim.opt.swapfile = false
vim.cmd("colorscheme default")

vim.api.nvim_buf_set_lines(0, 0, -1, true, {
"-- Copy something with y, then open ClipRing",
"",
})
vim.bo.filetype = "lua"

require("clipring").setup({ open_mapping = nil, persist = false })

vim.defer_fn(function()
require("clipring.ui").open()
end, 500)
24 changes: 24 additions & 0 deletions doc/screenshots/open_demo.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
#!/usr/bin/env bash
# Open a ClipRing demo in WezTerm for manual screenshots.
# Usage: ./doc/screenshots/open_demo.sh [full|empty]
set -euo pipefail

DIR="$(cd "$(dirname "$0")" && pwd)"
ROOT="$(cd "$DIR/../.." && pwd)"
MODE="${1:-full}"

case "$MODE" in
full)
DEMO="$DIR/demo.lua"
;;
empty)
DEMO="$DIR/demo_empty.lua"
;;
*)
echo "Usage: $0 [full|empty]" >&2
exit 1
;;
esac

exec wezterm start --always-new-process --position 160,90 -- \
nvim --clean --cmd "set columns=100 lines=32" --cmd "cd $ROOT" --cmd "luafile $DEMO"
Binary file added doc/screenshots/picker-empty.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added doc/screenshots/picker-with-preview.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 2 additions & 0 deletions lua/clipring/config.lua
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ local M = {}
---@field preview_max_width number max preview width (`0` = content width up to screen edge)
---@field picker_max_height number max height of list and preview panes (lines)
---@field preview_max_lines number max lines shown in the preview pane for one entry
---@field preview_syntax boolean highlight preview when a code filetype is detected

M.defaults = {
max_entries = 100,
Expand All @@ -33,6 +34,7 @@ M.defaults = {
preview_max_width = 0,
picker_max_height = 18,
preview_max_lines = 16,
preview_syntax = true,
}

---@type ClipRingConfig
Expand Down
Loading