···3131Packer:
3232```lua
3333use {
3434- 'ptdewey/yankbank-nvim',
3434+ "ptdewey/yankbank-nvim",
3535 config = function()
3636 require('yankbank').setup()
3737 end,
···5555| focus_gain_poll | boolean | `nil` |
5656| registers | table container for register overrides | `{ }` |
5757| registers.yank_register | default register to yank from popup to | `"+"` |
5858+| persist_type | string defining persistence type "memory" or "sqlite" | `"memory"` |
5959+| persist_path | string defining path for persistence file/db file | `"~/.local/share/nvim/lazy/yankbank-nvim"` (if installed with lazy) |
586059616062#### Example Configuration
···7072 navigation_next = "j",
7173 navigation_prev = "k",
7274 },
7575+ num_behavior = "prefix",
7676+ persist_type = "sqlite",
7377 registers = {
7478 yank_register = "+",
7579 },
···8488- `num_behavior = "jump"` jumps to entry matching the pressed number key (i.e. '3' jumps to entry 3)
8589 - Note: If 'max_entries' is a two-digit number, there will be a delay upon pressing numbers that prefix a valid entry.
86909191+#### Persistence
9292+If persistence between sessions is desired, sqlite.lua will be used to create a persistent store for recent yanks in the plugin root directory.
9393+To utilize sqlite persistence, `"kkharji/sqlite.lua"` must be added as a dependency in your config, and `persist_type` must be set to `"sqlite"`:
9494+9595+```lua
9696+-- lazy
9797+return {
9898+ "ptdewey/yankbank-nvim",
9999+ dependencies = "kkharji/sqlite.lua",
100100+ config = function()
101101+ require('yankbank').setup({
102102+ -- other options...
103103+ persist_type = "sqlite"
104104+ })
105105+ end,
106106+}
107107+```
8710888109The 'focus_gain_poll' option allows for enabling an additional autocommand that watches for focus gains (refocusing Neovim window), and checks for changes in the unnamedplus ('+') register, adding to yankbank when new contents are found. This allows for automatically adding text copied from other sources (like a browser) to the yankbank without the bank opening trigger. Off by default, but I highly recommend enabling it (`focus_gain_poll = true`)
89110···100121101122102123## Potential Improvements
103103-104104-- Persistence between sessions (through either sqlite database or just a file)
124124+- Polling on unnamedplus register to populate bank in more intuitive manner (could be enabled as option)
105125- nvim-cmp integration
106126- fzf integration
127127+- telescope integration
107128108129## Alternatives
109130
+23-23
lua/yankbank/clipboard.lua
···11--- clipboard.lua
21local M = {}
3233+-- import persistence module
44+local persistence = require("yankbank.persistence")
55+46-- Function to add yanked text to table
55-function M.add_yank(yanks, reg_types, text, reg_type, max_entries)
77+---@param yanks table
88+---@param reg_types table
99+---@param text string
1010+---@param reg_type string
1111+---@param opts table
1212+function M.add_yank(yanks, reg_types, text, reg_type, opts)
613 -- avoid adding empty strings
77- if text == "" and text == " " and text == "\n" then
1414+ -- TODO: could block adding single characters here
1515+ if text == "" or text == " " or text == "\n" then
816 return
917 end
1018···1523 end
1624 end
17252626+ -- add entry to bank
1827 table.insert(yanks, 1, text)
1928 table.insert(reg_types, 1, reg_type)
2020-2121- if #yanks > max_entries then
2929+ if #yanks > opts.max_entries then
2230 table.remove(yanks)
2331 table.remove(reg_types)
2432 end
3333+3434+ -- add entry to persistent store
3535+ persistence.add_entry(text, reg_type, opts)
2536end
26372727--- set up plugin autocommands
2828--- TODO: make augroup
3838+-- autocommand to listen for yank events
3939+---@param yanks table
4040+---@param reg_types table
4141+---@param opts table
2942function M.setup_yank_autocmd(yanks, reg_types, opts)
3030- -- autocommand to listen for yank events
3143 vim.api.nvim_create_autocmd("TextYankPost", {
3244 callback = function()
3345 -- get register information
3446 local rn = vim.v.event.regname
35473636- -- check if changes were made to default register
4848+ -- check changes wwere made to default register
3749 if rn == "" or rn == "+" then
3850 local reg_type = vim.fn.getregtype(rn)
3951 local yank_text = vim.fn.getreg(rn)
···4557 if #yank_text <= 1 then
4658 return
4759 end
4848- M.add_yank(
4949- yanks,
5050- reg_types,
5151- yank_text,
5252- reg_type,
5353- opts.max_entries
5454- )
6060+ M.add_yank(yanks, reg_types, yank_text, reg_type, opts)
5561 end
5662 end,
5763 })
···7278 return
7379 end
74807575- M.add_yank(
7676- yanks,
7777- reg_types,
7878- yank_text,
7979- reg_type,
8080- opts.max_entries
8181- )
8181+ M.add_yank(yanks, reg_types, yank_text, reg_type, opts)
8282 end,
8383 })
8484 end
+13-5
lua/yankbank/data.lua
···11--- data.lua
21local M = {}
3244--- reformat yanks table for popup
33+---reformat yanks table for popup
44+---@param yanks table
55+---@param sep string
66+---@return table, table
57function M.get_display_lines(yanks, sep)
68 local display_lines = {}
79 local line_yank_map = {}
···1012 -- calculate the maximum width needed for the yank numbers
1113 local max_digits = #tostring(#yanks)
12141515+ -- assumes yanks is table of strings
1316 for i, yank in ipairs(yanks) do
1417 yank_num = yank_num + 1
15181616- -- remove trailing newlines
1717- yank = yank:gsub("\n$", "")
1818- local yank_lines = vim.split(yank, "\n", { plain = true })
1919+ -- FIX: there were changes here, might need further changes
2020+ local yank_lines = yank
2121+ if type(yank) == "string" then
2222+ -- remove trailing newlines
2323+ yank = yank:gsub("\n$", "")
2424+ yank_lines = vim.split(yank, "\n", { plain = true })
2525+ end
2626+1927 local leading_space, leading_space_length
20282129 -- determine the number of leading whitespaces on the first line
+18-7
lua/yankbank/helpers.lua
···11--- helpers.lua
21local M = {}
3243-- navigate to the next numbered item
44+---@param steps integer
55function M.next_numbered_item(steps)
66 steps = steps or 1 -- Default to 1 if no steps are provided
77 local current_line = vim.api.nvim_win_get_cursor(0)[1]
···2424end
25252626-- navigate to the previous numbered item
2727+---@param steps integer
2728function M.prev_numbered_item(steps)
2829 steps = steps or 1 -- Default to 1 if no steps are provided
2930 local current_line = vim.api.nvim_win_get_cursor(0)[1]
···4344end
44454546-- customized paste function that functions like 'p'
4747+---@param text string|table
4848+---@param reg_type string
4649function M.smart_paste(text, reg_type)
4747- -- convert text string to string list
4850 local lines = {}
4949- for line in text:gmatch("([^\n]*)\n?") do
5050- table.insert(lines, line)
5151+ if type(text) == "string" then
5252+ -- convert text string to string list
5353+ for line in text:gmatch("([^\n]*)\n?") do
5454+ table.insert(lines, line)
5555+ end
5656+ if #lines > 1 then
5757+ table.remove(lines)
5858+ end
5959+ else
6060+ -- text is already table
6161+ lines = text
5162 end
52635364 -- remove last newline character to replicate base put behavior
5454- if #lines > 1 then
5555- table.remove(lines)
5656- end
6565+ -- if lines[#lines] == "" then
6666+ -- table.remove(lines)
6767+ -- end
5768 vim.api.nvim_put(lines, reg_type, true, true)
5869end
5970
+18-9
lua/yankbank/init.lua
···11--- init.lua
21local M = {}
3243-- local imports
54local menu = require("yankbank.menu")
65local clipboard = require("yankbank.clipboard")
66+local persistence = require("yankbank.persistence")
7788-- initialize yanks tables
99local yanks = {}
1010local reg_types = {}
1111+1212+-- default plugin options
1113local default_opts = {
1214 max_entries = 10,
1315 sep = "-----",
···1618 registers = {
1719 yank_register = "+",
1820 },
2121+ persist_type = "memory",
1922 keymaps = {},
2023}
21242222--- wrapper function for main plugin functionality
2525+-- local plugin_path = debug.getinfo(1).source:sub(2):match("(.*/).*/.*/") or "./"
2626+2727+--- wrapper function for main plugin functionality
2828+---@param opts table
2929+--- TODO: read from persistent database if sql persist is set (allow multi-session sync)
2330local function show_yank_bank(opts)
2431 -- Parse command arguments directly if args are provided as a string
2532 opts = opts or default_opts
26332734 -- initialize buffer and populate bank
2828- local bufnr, display_lines, line_yank_map = menu.create_and_fill_buffer(
2929- yanks,
3030- reg_types,
3131- opts.max_entries,
3232- opts.sep
3333- )
3535+ local bufnr, display_lines, line_yank_map =
3636+ menu.create_and_fill_buffer(yanks, reg_types, opts)
34373538 -- handle empty bank case
3636- if not bufnr then
3939+ if not bufnr or not display_lines or not line_yank_map then
3740 return
3841 end
3942···4346end
44474548-- plugin setup
4949+---@param opts table
4650function M.setup(opts)
5151+ -- merge opts with default options table
5252+ -- opts = vim.tbl_deep_extend("force", default_opts, opts or {})
4753 opts = opts or default_opts
5454+5555+ -- enable persistence based on opts (needs to be called before autocmd setup)
5656+ yanks, reg_types = persistence.setup(opts)
48574958 -- create clipboard autocmds
5059 clipboard.setup_yank_autocmd(yanks, reg_types, opts)
+28-15
lua/yankbank/menu.lua
···11--- menu.lua
21local M = {}
3243-- import clipboard functions
55-local clipboard = require("yankbank.clipboard")
44+-- local clipboard = require("yankbank.clipboard")
65local data = require("yankbank.data")
76local helpers = require("yankbank.helpers")
8799--- create new buffer and reformat yank table for ui
1010-function M.create_and_fill_buffer(yanks, reg_types, max_entries, sep)
88+---create new buffer and reformat yank table for ui
99+---@param yanks table
1010+---@param reg_types table
1111+---@param opts table
1212+---@return integer?
1313+---@return table?
1414+---@return table?
1515+function M.create_and_fill_buffer(yanks, reg_types, opts)
1116 -- check the content of the system clipboard register
1217 -- TODO: this could be replaced with some sort of polling of the + register
1313- local text = vim.fn.getreg("+")
1414- local most_recent_yank = yanks[1] or ""
1515- if text ~= most_recent_yank then
1616- local reg_type = vim.fn.getregtype("+")
1717- clipboard.add_yank(yanks, reg_types, text, reg_type, max_entries)
1818- end
1818+ -- local text = vim.fn.getreg("+")
1919+ -- local most_recent_yank = yanks[1] or ""
2020+ -- local reg_type = vim.fn.getregtype("+")
2121+ -- clipboard.add_yank(yanks, reg_types, text, reg_type, opts)
19222023 -- stop if yank table is empty
2121- if #yanks == 0 then
2424+ if #yanks == 0 and #reg_types then
2225 print("No yanks to show.")
2323- return
2626+ return nil, nil, nil
2427 end
25282629 -- create new buffer
···3033 local current_filetype = vim.bo.filetype
3134 vim.api.nvim_set_option_value("filetype", current_filetype, { buf = bufnr })
32353333- local display_lines, line_yank_map = data.get_display_lines(yanks, sep)
3636+ -- TODO: need to update yanks from bank file before get_display_lines
3737+ local display_lines, line_yank_map = data.get_display_lines(yanks, opts.sep)
34383539 -- replace current buffer contents with updated table
3640 vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, display_lines)
···3842 return bufnr, display_lines, line_yank_map
3943end
40444141--- Calculate size and create popup window from bufnr
4545+---Calculate size and create popup window from bufnr
4646+---@param bufnr integer
4747+---@param display_lines table
4848+---@return integer
4249function M.open_window(bufnr, display_lines)
4350 -- set maximum window width based on number of lines
4451 local max_width = 0
···8188 return win_id
8289end
83908484--- Set key mappings for the popup window
9191+---Set key mappings for the popup window
9292+---@param win_id integer
9393+---@param bufnr integer
9494+---@param yanks table
9595+---@param reg_types table
9696+---@param line_yank_map table
9797+---@param opts table
8598function M.set_keymaps(win_id, bufnr, yanks, reg_types, line_yank_map, opts)
8699 -- Key mappings for selection and closing the popup
87100 local map_opts = { noremap = true, silent = true, buffer = bufnr }
+32
lua/yankbank/persistence.lua
···11+local M = {}
22+33+local persistence = {}
44+55+---add entry from bank to
66+---@param entry string|table
77+---@param reg_type string
88+---@param opts table
99+function M.add_entry(entry, reg_type, opts)
1010+ if not opts.persist_type then
1111+ return
1212+ elseif opts.persist_type == "sqlite" then
1313+ persistence:insert_yank(entry, reg_type)
1414+ end
1515+end
1616+1717+---initialize bank persistence
1818+---@param opts table
1919+---@return table
2020+---@return table
2121+function M.setup(opts)
2222+ if not opts.persist_type then
2323+ return {}, {}
2424+ elseif opts.persist_type == "sqlite" then
2525+ persistence = require("yankbank.persistence.sql").setup(opts)
2626+ return persistence:get_bank()
2727+ else
2828+ return {}, {}
2929+ end
3030+end
3131+3232+return M
+80
lua/yankbank/persistence/sql.lua
···11+local M = {}
22+33+local sqlite = require("sqlite.db")
44+55+local dbdir = vim.fn.stdpath("data") .. "/databases"
66+local max_entries = 10
77+88+---@class YankBankDB:sqlite_db
99+---@field bank sqlite_tbl
1010+local db = sqlite({
1111+ uri = dbdir .. "/yankbank.db",
1212+ bank = {
1313+ -- yanked text should be unique and be primary key
1414+ yank_text = { "text", unique = true, primary = true, required = true },
1515+ reg_type = { "text", required = true },
1616+ },
1717+})
1818+1919+---@class sqlite_tbl
2020+local data = db.bank
2121+2222+--- insert yank entry into database
2323+---@param yank_text string yanked text
2424+---@param reg_type string register type
2525+function data:insert_yank(yank_text, reg_type)
2626+ -- attempt to remove entry if count > 0 (to move potential duplicate)
2727+ if self:count() > 0 then
2828+ self:remove({ yank_text = yank_text })
2929+ end
3030+3131+ -- insert entry
3232+ self:insert({
3333+ yank_text = yank_text,
3434+ reg_type = reg_type,
3535+ })
3636+3737+ -- attempt to trim database size
3838+ self:trim_size()
3939+end
4040+4141+--- trim database size if it exceeds max_entries option
4242+function data:trim_size()
4343+ if self:count() > max_entries then
4444+ -- remove the oldest entry
4545+ self:remove({ yank_text = self:get()[1].yank_text })
4646+ end
4747+end
4848+4949+--- get sqlite bank contents
5050+---@return table yanks, table reg_types
5151+function data:get_bank()
5252+ local yanks, reg_types = {}, {}
5353+5454+ local bank = self:get()
5555+ for _, entry in ipairs(bank) do
5656+ table.insert(yanks, 1, entry.yank_text)
5757+ table.insert(reg_types, 1, entry.reg_type)
5858+ end
5959+6060+ return yanks, reg_types
6161+end
6262+6363+-- FIX: correctly handle multiple sessions open at once
6464+-- - fetch database state each time YankBank command is called?
6565+6666+--- set up database persistence
6767+---@param opts table
6868+---@return sqlite_tbl data
6969+function M.setup(opts)
7070+ max_entries = opts.max_entries
7171+7272+ -- TODO: move database into plugin directory instead to allow easier uninstall
7373+ if vim.fn.isdirectory(dbdir) == 0 then
7474+ vim.fn.mkdir(dbdir, "p")
7575+ end
7676+7777+ return data
7878+end
7979+8080+return M