From 2227159e6eae25dbb677cea1c9895b8ba2c8cc1e Mon Sep 17 00:00:00 2001 From: Alejandro Espinoza Date: Fri, 5 Jun 2026 15:42:40 -0600 Subject: [PATCH 1/2] feat: auto-size picker panes and trim public config surface. Preview and list resize from entry content; layout min/toggle options move to ui constants. README shows core setup plus an advanced section. Co-authored-by: Cursor --- README.md | 42 ++++--- lua/clipring/config.lua | 10 +- lua/clipring/ui.lua | 240 ++++++++++++++++++++++++++++------------ tests/ui_spec.lua | 27 +++++ 4 files changed, 226 insertions(+), 93 deletions(-) diff --git a/README.md b/README.md index e513ff9..3302902 100644 --- a/README.md +++ b/README.md @@ -56,7 +56,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. `y`) | -The picker opens as two side-by-side floats: a wide **history list** (newest first, one line per entry with register type `c` / `l` / `b`) and a narrower **preview pane** showing the selected yank with real line breaks (up to `preview_max_lines`). +The picker opens as two side-by-side floats: a **history list** (height follows entry count) and a **preview pane** that resizes to fit the selected yank’s line count and longest line (capped by `preview_max_lines`, `preview_max_width`, and `picker_max_height`). ### Inside the picker @@ -93,21 +93,22 @@ With `persist = true`, history is restored after you restart Neovim (stored unde ```lua require("clipring").setup({ - max_entries = 100, -- max items in ring - persist = false, -- save history to disk + -- Ring + max_entries = 100, + persist = false, persist_path = vim.fn.stdpath("data") .. "/clipring/history.json", - preview_length = 80, -- chars shown per line in popup - deduplicate = true, -- move duplicates to top instead of re-adding - min_length = 1, -- ignore yanks shorter than this (chars) - open_mapping = "y", -- string, list of strings, or false (nil = no keymap) - reorder_down_mapping = "", -- picker: move entry down in history (false to disable) - reorder_up_mapping = "", -- picker: move entry up in history (false to disable) - copy_mapping = "y", -- picker: copy to system clipboard (false to disable) - picker_width = 80, -- total inner width of list + preview (0 = editor width minus margin) - list_width = 0, -- history list columns (0 = remaining width after preview) - preview_max_width = 80, -- max width of the preview pane (columns) - picker_max_height = 18, -- max height of both floats (lines) - preview_max_lines = 16, -- max lines shown per entry in the preview pane + deduplicate = true, + + -- Open & picker keys + open_mapping = "y", + reorder_down_mapping = "", + reorder_up_mapping = "", + copy_mapping = "y", + + -- Layout (list and preview auto-size within these limits) + 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 }) ``` @@ -119,6 +120,17 @@ Copy uses Neovim’s `+` and `*` registers (and the unnamed `"` register). You n If `` / `` conflict with global maps (e.g. `:move`), use different keys: `reorder_down_mapping = ""`. +### Advanced + +```lua +require("clipring").setup({ + min_length = 1, -- ignore yanks shorter than this (chars) + preview_length = 80, -- max chars in each one-line list label + preview_max_width = 120, -- cap preview width; 0 = up to screen edge (default) + list_width = 0, -- fixed list width in columns; 0 = auto (recommended) +}) +``` + ## Tests Specs run on every push to `main` and on pull requests via [GitHub Actions](https://github.com/alexesba/clipring.nvim/actions/workflows/test.yml). diff --git a/lua/clipring/config.lua b/lua/clipring/config.lua index af6f358..bdbd720 100644 --- a/lua/clipring/config.lua +++ b/lua/clipring/config.lua @@ -4,7 +4,7 @@ local M = {} ---@field max_entries number ---@field persist boolean ---@field persist_path string ----@field preview_length number +---@field preview_length number max chars in each one-line list label ---@field deduplicate boolean ---@field min_length number ---@field open_mapping string|string[]|false|nil keymap(s) to open picker (`nil` = `:ClipRing` only) @@ -12,9 +12,9 @@ local M = {} ---@field reorder_up_mapping string|false|nil move selected entry up in picker (default ``) ---@field copy_mapping string|false|nil copy selected entry to system clipboard in picker (default `y`) ---@field picker_width number total inner width of list + preview (`0` = nearly full editor width) ----@field list_width number width of the history list (columns; `0` = fill space not used by preview) ----@field preview_max_width number max width of the preview pane (columns) ----@field picker_max_height number max height of picker windows (lines) +---@field list_width number width of the history list (`0` = auto; fixed width is advanced) +---@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 M.defaults = { @@ -30,7 +30,7 @@ M.defaults = { copy_mapping = "y", picker_width = 80, list_width = 0, - preview_max_width = 80, + preview_max_width = 0, picker_max_height = 18, preview_max_lines = 16, } diff --git a/lua/clipring/ui.lua b/lua/clipring/ui.lua index 8d4efcf..0e5167c 100644 --- a/lua/clipring/ui.lua +++ b/lua/clipring/ui.lua @@ -16,10 +16,18 @@ local state = { visual_marks = nil, index = 1, list_col_width = 50, + list_col = 0, + list_row = 0, + list_height = 3, + float_gap = 1, } local ns = vim.api.nvim_create_namespace("ClipRing") +local PREVIEW_MIN_WIDTH = 20 +local PREVIEW_MIN_HEIGHT = 3 +local LIST_MIN_HEIGHT = 3 + local function entry_kind(entry) if entry.regtype == "V" then return "l" @@ -123,9 +131,158 @@ local function refresh_preview_buffer() set_buf_lines(state.preview_buf, preview_lines_for_entry(entry)) end +local function max_preview_line_width(lines) + local max_w = 0 + for _, line in ipairs(lines) do + max_w = math.max(max_w, vim.fn.strdisplaywidth(line)) + end + return max_w +end + +---@param entry ClipRingEntry|nil +---@return number width, number height +local function preview_size_for_entry(entry) + local opts = config.get() + local lines = entry and preview_lines_for_entry(entry) or { "" } + local line_count = #lines + local max_line_w = max_preview_line_width(lines) + + local width = math.max(PREVIEW_MIN_WIDTH, max_line_w + 1) + if opts.preview_max_width and opts.preview_max_width > 0 then + width = math.min(width, opts.preview_max_width) + end + + local height = math.max(PREVIEW_MIN_HEIGHT, line_count) + height = math.min(height, opts.picker_max_height, vim.o.lines - 4) + if state.list_row and state.list_row > 0 then + height = math.min(height, vim.o.lines - state.list_row - 2) + end + + return width, height +end + +local function clamp_preview_width(width) + if not state.list_col or not state.list_width then + return width + end + local preview_col = state.list_col + state.list_width + 2 + state.float_gap + local max_on_screen = vim.o.columns - preview_col - 4 + return math.max(PREVIEW_MIN_WIDTH, math.min(width, max_on_screen)) +end + +--- Height of the history list from entry count. +local function list_height_for_count(count) + local opts = config.get() + local list_lines = count > 0 and count or 3 + local height = math.max(list_lines, LIST_MIN_HEIGHT) + return math.min(height, opts.picker_max_height, vim.o.lines - 4) +end + +--- Fixed list pane layout (width/position on open; height also updated via apply_list_height). +local function list_layout(initial_preview_width) + local opts = config.get() + local count = ring.count() + local height = list_height_for_count(count) + + local margin = 8 + local total_width = opts.picker_width > 0 and opts.picker_width or (vim.o.columns - margin) + total_width = math.min(total_width, vim.o.columns - margin) + total_width = math.max(total_width, 60) + + local preview_cap = opts.preview_max_width and opts.preview_max_width > 0 and opts.preview_max_width + or (initial_preview_width or PREVIEW_MIN_WIDTH) + preview_cap = math.max(PREVIEW_MIN_WIDTH, preview_cap) + preview_cap = math.min(preview_cap, total_width - 36) + + local list_width + if opts.list_width > 0 then + list_width = math.min(opts.list_width, total_width - preview_cap) + else + list_width = total_width - preview_cap + end + list_width = math.max(list_width, 36) + + local preview_w = initial_preview_width or preview_cap + local footprint = (list_width + 2) + state.float_gap + (preview_w + 2) + local row = math.max(0, math.floor((vim.o.lines - height) / 2)) + local col = math.max(0, math.floor((vim.o.columns - footprint) / 2)) + + return { + row = row, + col = col, + height = height, + list_width = list_width, + } +end + +local function apply_list_height() + if not state.list_win or not vim.api.nvim_win_is_valid(state.list_win) then + return + end + local height = list_height_for_count(ring.count()) + if height == state.list_height then + return + end + state.list_height = height + vim.api.nvim_win_set_config(state.list_win, { + relative = "editor", + width = state.list_width, + height = height, + row = state.list_row, + col = state.list_col, + }) +end + +local function apply_preview_layout() + if not state.preview_win or not vim.api.nvim_win_is_valid(state.preview_win) then + return + end + + local entry = ring.get(state.index) + local width, height + if entry then + width, height = preview_size_for_entry(entry) + else + width, height = PREVIEW_MIN_WIDTH, PREVIEW_MIN_HEIGHT + end + width = clamp_preview_width(width) + + vim.api.nvim_win_set_config(state.preview_win, { + relative = "editor", + width = width, + height = height, + row = state.list_row, + col = state.list_col + state.list_width + 2 + state.float_gap, + }) +end + +local function set_list_layout_state(layout) + state.list_col_width = layout.list_width + state.list_width = layout.list_width + state.list_col = layout.col + state.list_row = layout.row + state.list_height = layout.height +end + +local function apply_list_layout(layout) + set_list_layout_state(layout) + if not state.list_win or not vim.api.nvim_win_is_valid(state.list_win) then + return + end + vim.api.nvim_win_set_config(state.list_win, { + relative = "editor", + width = layout.list_width, + height = layout.height, + row = layout.row, + col = layout.col, + }) +end + local function refresh_buffers() refresh_list_buffer() + apply_list_height() refresh_preview_buffer() + apply_preview_layout() end local NAV_MODES = { "n", "i", "v", "x", "s" } @@ -351,72 +508,6 @@ local function create_readonly_buf(name, filetype) return buf end -local function picker_layout() - local opts = config.get() - local count = ring.count() - local list_lines = count > 0 and count or 3 - local entry = count > 0 and ring.get(state.index) or nil - local preview_lines = entry and #preview_lines_for_entry(entry) or 1 - - local height = math.max(list_lines, preview_lines, 3) - height = math.min(height, opts.picker_max_height, vim.o.lines - 4) - - local margin = 8 - local total_width = opts.picker_width > 0 and opts.picker_width or (vim.o.columns - margin) - total_width = math.min(total_width, vim.o.columns - margin) - total_width = math.max(total_width, 60) - - local preview_cap = math.max(20, opts.preview_max_width or 80) - preview_cap = math.min(preview_cap, total_width - 36) - - local preview_width = preview_cap - local list_width - if opts.list_width > 0 then - list_width = math.min(opts.list_width, total_width - preview_width) - else - list_width = total_width - preview_width - end - list_width = math.max(list_width, 36) - - local float_gap = 1 - local footprint = (list_width + 2) + float_gap + (preview_width + 2) - local row = math.max(0, math.floor((vim.o.lines - height) / 2)) - local col = math.max(0, math.floor((vim.o.columns - footprint) / 2)) - - return { - row = row, - col = col, - height = height, - list_width = list_width, - preview_width = preview_width, - preview_col = col + list_width + 2 + float_gap, - float_gap = float_gap, - } -end - -local function apply_picker_layout(layout) - if not state.list_win or not vim.api.nvim_win_is_valid(state.list_win) then - return - end - state.list_col_width = layout.list_width - vim.api.nvim_win_set_config(state.list_win, { - relative = "editor", - width = layout.list_width, - height = layout.height, - row = layout.row, - col = layout.col, - }) - if state.preview_win and vim.api.nvim_win_is_valid(state.preview_win) then - vim.api.nvim_win_set_config(state.preview_win, { - relative = "editor", - width = layout.preview_width, - height = layout.height, - row = layout.row, - col = layout.preview_col, - }) - end -end - local float_opts = { relative = "editor", style = "minimal", @@ -440,7 +531,6 @@ function M.open(opts) opts = opts or {} if state.list_win and vim.api.nvim_win_is_valid(state.list_win) then - apply_picker_layout(picker_layout()) refresh_buffers() focus_list_normal() return @@ -462,8 +552,10 @@ function M.open(opts) state.list_buf = create_readonly_buf("clipring://history", "clipring") state.preview_buf = create_readonly_buf("clipring://preview", "clipring_preview") - local layout = picker_layout() - state.list_col_width = layout.list_width + local entry = ring.count() > 0 and ring.get(state.index) or nil + local initial_pw = preview_size_for_entry(entry) + local layout = list_layout(initial_pw) + set_list_layout_state(layout) state.list_win = vim.api.nvim_open_win(state.list_buf, true, vim.tbl_extend("force", float_opts, { width = layout.list_width, @@ -474,11 +566,13 @@ function M.open(opts) title_pos = "center", })) + local pw, ph = preview_size_for_entry(entry) + pw = clamp_preview_width(pw) state.preview_win = vim.api.nvim_open_win(state.preview_buf, false, vim.tbl_extend("force", float_opts, { - width = layout.preview_width, - height = layout.height, - row = layout.row, - col = layout.preview_col, + width = pw, + height = ph, + row = state.list_row, + col = state.list_col + state.list_width + 2 + state.float_gap, })) configure_float_win(state.list_win) diff --git a/tests/ui_spec.lua b/tests/ui_spec.lua index 41e7fd6..70e1aa0 100644 --- a/tests/ui_spec.lua +++ b/tests/ui_spec.lua @@ -121,6 +121,33 @@ describe("clipring.ui", function() ui.close() end) + it("resizes list height when entries are deleted", function() + ring.clear() + for i = 1, 8 do + ring.add({ "entry-" .. i }, "v") + end + ui.open() + local list_win = vim.fn.bufwinid(h.find_clipring_buf()) + local tall = vim.api.nvim_win_get_height(list_win) + feed_clipring("dd") + local shorter = vim.api.nvim_win_get_height(list_win) + assert.is_true(shorter < tall) + ui.close() + end) + + it("resizes preview height when selection changes", function() + ring.clear() + ring.add({ "short" }, "v") + ring.add({ "a", "b", "c", "d", "e" }, "V") + ui.open() + local preview_win = vim.fn.bufwinid(h.find_clipring_preview_buf()) + local tall_height = vim.api.nvim_win_get_height(preview_win) + feed_clipring("j") + local short_height = vim.api.nvim_win_get_height(preview_win) + assert.is_true(tall_height > short_height) + ui.close() + end) + it("maps j to move down in the picker while insert mappings exist", function() ui.open({ from_insert = true }) local clip_buf = h.find_clipring_buf() From 7604f5f60f6d02c476be4fd355b86928e2914fcc Mon Sep 17 00:00:00 2001 From: Alejandro Espinoza Date: Fri, 5 Jun 2026 15:46:38 -0600 Subject: [PATCH 2/2] fix(ui): hide preview pane when the ring has nothing to show. Show preview only for non-empty entries; list recenters at full width when preview is hidden. Co-authored-by: Cursor --- README.md | 2 +- lua/clipring/ui.lua | 154 +++++++++++++++++++++++++++++++------------- tests/helpers.lua | 13 ++++ tests/ui_spec.lua | 17 +++++ 4 files changed, 140 insertions(+), 46 deletions(-) diff --git a/README.md b/README.md index 3302902..e1150e9 100644 --- a/README.md +++ b/README.md @@ -56,7 +56,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. `y`) | -The picker opens as two side-by-side floats: a **history list** (height follows entry count) and a **preview pane** that resizes to fit the selected yank’s line count and longest line (capped by `preview_max_lines`, `preview_max_width`, and `picker_max_height`). +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. ### Inside the picker diff --git a/lua/clipring/ui.lua b/lua/clipring/ui.lua index 0e5167c..dbc0482 100644 --- a/lua/clipring/ui.lua +++ b/lua/clipring/ui.lua @@ -28,6 +28,23 @@ local PREVIEW_MIN_WIDTH = 20 local PREVIEW_MIN_HEIGHT = 3 local LIST_MIN_HEIGHT = 3 +local float_opts = { + relative = "editor", + style = "minimal", + border = "rounded", +} + +local function configure_float_win(win) + if not vim.api.nvim_win_is_valid(win) then + return + end + vim.wo[win].winhl = "Normal:NormalFloat,FloatBorder:FloatBorder,CursorLine:Visual" + vim.wo[win].number = false + vim.wo[win].relativenumber = false + vim.wo[win].signcolumn = "no" + vim.wo[win].foldcolumn = "0" +end + local function entry_kind(entry) if entry.regtype == "V" then return "l" @@ -116,6 +133,17 @@ local function preview_lines_for_entry(entry) return padded end +local function entry_has_preview_content(entry) + if not entry or not entry.lines or #entry.lines == 0 then + return false + end + return not table.concat(entry.lines, "\n"):match("^%s*$") +end + +local function preview_should_show() + return ring.count() > 0 and entry_has_preview_content(ring.get(state.index)) +end + local function refresh_preview_buffer() if not state.preview_buf or not vim.api.nvim_buf_is_valid(state.preview_buf) then return @@ -178,8 +206,9 @@ local function list_height_for_count(count) return math.min(height, opts.picker_max_height, vim.o.lines - 4) end ---- Fixed list pane layout (width/position on open; height also updated via apply_list_height). -local function list_layout(initial_preview_width) +---@param initial_preview_width number|nil +---@param with_preview boolean +local function list_layout(initial_preview_width, with_preview) local opts = config.get() local count = ring.count() local height = list_height_for_count(count) @@ -189,21 +218,30 @@ local function list_layout(initial_preview_width) total_width = math.min(total_width, vim.o.columns - margin) total_width = math.max(total_width, 60) - local preview_cap = opts.preview_max_width and opts.preview_max_width > 0 and opts.preview_max_width - or (initial_preview_width or PREVIEW_MIN_WIDTH) - preview_cap = math.max(PREVIEW_MIN_WIDTH, preview_cap) - preview_cap = math.min(preview_cap, total_width - 36) - local list_width - if opts.list_width > 0 then - list_width = math.min(opts.list_width, total_width - preview_cap) + local footprint + + if not with_preview then + list_width = total_width + list_width = math.max(list_width, 36) + footprint = list_width + 2 else - list_width = total_width - preview_cap + local preview_cap = opts.preview_max_width and opts.preview_max_width > 0 and opts.preview_max_width + or (initial_preview_width or PREVIEW_MIN_WIDTH) + preview_cap = math.max(PREVIEW_MIN_WIDTH, preview_cap) + preview_cap = math.min(preview_cap, total_width - 36) + + if opts.list_width > 0 then + list_width = math.min(opts.list_width, total_width - preview_cap) + else + list_width = total_width - preview_cap + end + list_width = math.max(list_width, 36) + + local preview_w = initial_preview_width or preview_cap + footprint = (list_width + 2) + state.float_gap + (preview_w + 2) end - list_width = math.max(list_width, 36) - local preview_w = initial_preview_width or preview_cap - local footprint = (list_width + 2) + state.float_gap + (preview_w + 2) local row = math.max(0, math.floor((vim.o.lines - height) / 2)) local col = math.max(0, math.floor((vim.o.columns - footprint) / 2)) @@ -278,11 +316,51 @@ local function apply_list_layout(layout) }) end +local function hide_preview_window() + if state.preview_win and vim.api.nvim_win_is_valid(state.preview_win) then + vim.api.nvim_win_close(state.preview_win, true) + end + state.preview_win = nil +end + +local function show_preview_window() + if not state.preview_buf or not vim.api.nvim_buf_is_valid(state.preview_buf) then + return + end + refresh_preview_buffer() + local entry = ring.get(state.index) + local pw, ph = preview_size_for_entry(entry) + pw = clamp_preview_width(pw) + + if not state.preview_win or not vim.api.nvim_win_is_valid(state.preview_win) then + state.preview_win = vim.api.nvim_open_win(state.preview_buf, false, vim.tbl_extend("force", float_opts, { + width = pw, + height = ph, + row = state.list_row, + col = state.list_col + state.list_width + 2 + state.float_gap, + })) + configure_float_win(state.preview_win) + else + apply_preview_layout() + end +end + +local function sync_preview_visibility() + local show = preview_should_show() + local entry = ring.get(state.index) + local initial_pw = show and preview_size_for_entry(entry) or nil + apply_list_layout(list_layout(initial_pw, show)) + if show then + show_preview_window() + else + hide_preview_window() + end +end + local function refresh_buffers() refresh_list_buffer() apply_list_height() - refresh_preview_buffer() - apply_preview_layout() + sync_preview_visibility() end local NAV_MODES = { "n", "i", "v", "x", "s" } @@ -508,23 +586,6 @@ local function create_readonly_buf(name, filetype) return buf end -local float_opts = { - relative = "editor", - style = "minimal", - border = "rounded", -} - -local function configure_float_win(win) - if not vim.api.nvim_win_is_valid(win) then - return - end - vim.wo[win].winhl = "Normal:NormalFloat,FloatBorder:FloatBorder,CursorLine:Visual" - vim.wo[win].number = false - vim.wo[win].relativenumber = false - vim.wo[win].signcolumn = "no" - vim.wo[win].foldcolumn = "0" -end - ---@param opts table|nil ---@field from_insert boolean|nil set when the open keymap runs in Insert mode function M.open(opts) @@ -552,9 +613,10 @@ function M.open(opts) state.list_buf = create_readonly_buf("clipring://history", "clipring") state.preview_buf = create_readonly_buf("clipring://preview", "clipring_preview") - local entry = ring.count() > 0 and ring.get(state.index) or nil - local initial_pw = preview_size_for_entry(entry) - local layout = list_layout(initial_pw) + local show_preview = preview_should_show() + local entry = show_preview and ring.get(state.index) or nil + local initial_pw = show_preview and preview_size_for_entry(entry) or nil + local layout = list_layout(initial_pw, show_preview) set_list_layout_state(layout) state.list_win = vim.api.nvim_open_win(state.list_buf, true, vim.tbl_extend("force", float_opts, { @@ -566,17 +628,19 @@ function M.open(opts) title_pos = "center", })) - local pw, ph = preview_size_for_entry(entry) - pw = clamp_preview_width(pw) - state.preview_win = vim.api.nvim_open_win(state.preview_buf, false, vim.tbl_extend("force", float_opts, { - width = pw, - height = ph, - row = state.list_row, - col = state.list_col + state.list_width + 2 + state.float_gap, - })) - configure_float_win(state.list_win) - configure_float_win(state.preview_win) + + if show_preview then + local pw, ph = preview_size_for_entry(entry) + pw = clamp_preview_width(pw) + state.preview_win = vim.api.nvim_open_win(state.preview_buf, false, vim.tbl_extend("force", float_opts, { + width = pw, + height = ph, + row = state.list_row, + col = state.list_col + state.list_width + 2 + state.float_gap, + })) + configure_float_win(state.preview_win) + end attach_keymaps() refresh_buffers() diff --git a/tests/helpers.lua b/tests/helpers.lua index 7860b68..f20391f 100644 --- a/tests/helpers.lua +++ b/tests/helpers.lua @@ -73,6 +73,19 @@ function M.find_clipring_preview_buf() end end +---@return number|nil window id when the preview float is visible +function M.clipring_preview_win() + local buf = M.find_clipring_preview_buf() + if not buf then + return nil + end + local win = vim.fn.bufwinid(buf) + if win <= 0 then + return nil + end + return win +end + ---@param buf number ---@return number|nil 1-indexed line number of the highlighted entry function M.clipring_selected_line(buf) diff --git a/tests/ui_spec.lua b/tests/ui_spec.lua index 70e1aa0..7c1d9d7 100644 --- a/tests/ui_spec.lua +++ b/tests/ui_spec.lua @@ -80,6 +80,23 @@ describe("clipring.ui", function() assert.are.equal(1, h.clipring_selected_line(clip_buf)) end) + it("hides the preview pane when the ring is empty", function() + ring.clear() + ui.open() + assert.is_nil(h.clipring_preview_win()) + ui.close() + end) + + it("hides the preview pane after deleting the last entry", function() + ring.clear() + ring.add({ "only one" }, "v") + ui.open() + assert.is_not_nil(h.clipring_preview_win()) + feed_clipring("dd") + assert.is_nil(h.clipring_preview_win()) + ui.close() + end) + it("shows multiline yank content in the preview pane", function() ring.clear() ring.add({ "alpha", "beta", "gamma" }, "V")