Neovim plugin improving access to clipboard history (mirror)

feat/refactor: added backwards paste and changed global names

+86 -66
+1 -1
Makefile
··· 4 4 5 5 lint: 6 6 echo "Linting lua/yankbank..." 7 - luacheck lua/ --globals vim YANKS REG_TYPES OPTS 7 + luacheck lua/ --globals vim YB_YANKS YB_REG_TYPES YB_OPTS 8 8 9 9 pr-ready: fmt lint
+7 -7
lua/yankbank/api.lua
··· 5 5 ---@return table 6 6 function M.get_entry(i) 7 7 return { 8 - yank_text = YANKS[i], 9 - reg_type = REG_TYPES[i], 8 + yank_text = YB_YANKS[i], 9 + reg_type = YB_REG_TYPES[i], 10 10 } 11 11 end 12 12 ··· 14 14 ---@return table 15 15 function M.get_all() 16 16 local out = {} 17 - for i, v in ipairs(YANKS) do 17 + for i, v in ipairs(YB_YANKS) do 18 18 table.insert(out, { 19 19 yank_text = v, 20 - reg_type = REG_TYPES[i], 20 + reg_type = YB_REG_TYPES[i], 21 21 }) 22 22 end 23 23 return out ··· 33 33 --- remove entry from yankbank by index 34 34 ---@param i integer index to remove 35 35 function M.remove_entry(i) 36 - local yank_text = table.remove(YANKS, i) 37 - table.remove(REG_TYPES, i) 38 - if OPTS.persist_type == "sqlite" then 36 + local yank_text = table.remove(YB_YANKS, i) 37 + table.remove(YB_REG_TYPES, i) 38 + if YB_OPTS.persist_type == "sqlite" then 39 39 require("yankbank.persistence.sql").data().remove_match(yank_text) 40 40 end 41 41 end
+9 -9
lua/yankbank/clipboard.lua
··· 13 13 end 14 14 15 15 -- check for duplicate values already inserted 16 - for i, entry in ipairs(YANKS) do 16 + for i, entry in ipairs(YB_YANKS) do 17 17 if entry == text then 18 18 -- remove matched entry so it can be inserted at 1st position 19 - table.remove(YANKS, i) 20 - table.remove(REG_TYPES, i) 19 + table.remove(YB_YANKS, i) 20 + table.remove(YB_REG_TYPES, i) 21 21 break 22 22 end 23 23 end 24 24 25 25 -- add entry to bank 26 - table.insert(YANKS, 1, text) 27 - table.insert(REG_TYPES, 1, reg_type) 26 + table.insert(YB_YANKS, 1, text) 27 + table.insert(YB_REG_TYPES, 1, reg_type) 28 28 29 29 -- trim table size if necessary 30 - if #YANKS > OPTS.max_entries then 31 - table.remove(YANKS) 32 - table.remove(REG_TYPES) 30 + if #YB_YANKS > YB_OPTS.max_entries then 31 + table.remove(YB_YANKS) 32 + table.remove(YB_REG_TYPES) 33 33 end 34 34 35 35 -- add entry to persistent store ··· 62 62 }) 63 63 64 64 -- poll registers when vim is focused (check for new clipboard activity) 65 - if OPTS.focus_gain_poll == true then 65 + if YB_OPTS.focus_gain_poll == true then 66 66 vim.api.nvim_create_autocmd("FocusGained", { 67 67 callback = function() 68 68 -- get register information
+5 -5
lua/yankbank/data.lua
··· 8 8 local yank_num = 0 9 9 10 10 -- calculate the maximum width needed for the yank numbers 11 - local max_digits = #tostring(#YANKS) 11 + local max_digits = #tostring(#YB_YANKS) 12 12 13 13 -- assumes yanks is table of strings 14 - for i, yank in ipairs(YANKS) do 14 + for i, yank in ipairs(YB_YANKS) do 15 15 yank_num = yank_num + 1 16 16 17 17 local yank_lines = yank ··· 48 48 table.insert(line_yank_map, i) 49 49 end 50 50 51 - if i < #YANKS then 51 + if i < #YB_YANKS then 52 52 -- Add a visual separator between yanks, aligned with the yank content 53 - if OPTS.sep ~= "" then 53 + if YB_OPTS.sep ~= "" then 54 54 table.insert( 55 55 display_lines, 56 - string.rep(" ", max_digits + 2) .. OPTS.sep 56 + string.rep(" ", max_digits + 2) .. YB_OPTS.sep 57 57 ) 58 58 end 59 59 table.insert(line_yank_map, false)
+4 -3
lua/yankbank/helpers.lua
··· 43 43 vim.api.nvim_win_set_cursor(0, { 1, 0 }) 44 44 end 45 45 46 - --- customized paste function that functions like 'p' 46 + --- customized paste function that functions like 'p' or 'P' 47 47 ---@param text string|table 48 48 ---@param reg_type string 49 - function M.smart_paste(text, reg_type) 49 + ---@param after boolean define if text should be pasted after 'p' or before 'P' 50 + function M.smart_paste(text, reg_type, after) 50 51 local lines = {} 51 52 if type(text) == "string" then 52 53 -- convert text string to string list ··· 61 62 lines = text 62 63 end 63 64 64 - vim.api.nvim_put(lines, reg_type, true, true) 65 + vim.api.nvim_put(lines, reg_type, after, true) 65 66 end 66 67 67 68 return M
+8 -7
lua/yankbank/init.lua
··· 1 1 local M = {} 2 2 3 + -- define global variables 4 + YB_YANKS = {} 5 + YB_REG_TYPES = {} 6 + YB_OPTS = {} 7 + 3 8 -- local imports 4 9 local menu = require("yankbank.menu") 5 10 local clipboard = require("yankbank.clipboard") 6 11 local persistence = require("yankbank.persistence") 7 12 8 - YANKS = {} 9 - REG_TYPES = {} 10 - OPTS = {} 11 - 12 13 -- default plugin options 13 14 local default_opts = { 14 15 max_entries = 10, ··· 24 25 25 26 --- wrapper function for main plugin functionality 26 27 local function show_yank_bank() 27 - YANKS = persistence.get_yanks() or YANKS 28 + YB_YANKS = persistence.get_yanks() or YB_YANKS 28 29 29 30 -- initialize buffer and populate bank 30 31 local buf_data = menu.create_and_fill_buffer() ··· 43 44 ---@param opts? table 44 45 function M.setup(opts) 45 46 -- merge opts with default options table 46 - OPTS = vim.tbl_deep_extend("keep", opts or {}, default_opts) 47 + YB_OPTS = vim.tbl_deep_extend("keep", opts or {}, default_opts) 47 48 48 49 -- enable persistence based on opts (needs to be called before autocmd setup) 49 - YANKS, REG_TYPES = persistence.setup() 50 + YB_YANKS, YB_REG_TYPES = persistence.setup() 50 51 51 52 -- create clipboard autocmds 52 53 clipboard.setup_yank_autocmd()
+44 -26
lua/yankbank/menu.lua
··· 8 8 navigation_next = "j", 9 9 navigation_prev = "k", 10 10 paste = "<CR>", 11 + paste_back = "P", 11 12 yank = "yy", 12 13 close = { "<Esc>", "<C-c>", "q" }, 13 14 } ··· 17 18 yank_register = "+", 18 19 } 19 20 21 + -- merge default and options keymap tables 22 + local k = vim.tbl_deep_extend("force", default_keymaps, YB_OPTS.keymaps or {}) 23 + 24 + -- merge default and options register tables 25 + YB_OPTS.registers = 26 + vim.tbl_deep_extend("force", default_registers, YB_OPTS.registers or {}) 27 + 28 + -- check table for number behavior option (prefix or jump, default to prefix) 29 + YB_OPTS.num_behavior = YB_OPTS.num_behavior or "prefix" 30 + 20 31 --- Container class for YankBank buffer related variables 21 32 ---@class YankBankBufData 22 33 ---@field bufnr integer ··· 28 39 ---@return YankBankBufData? 29 40 function M.create_and_fill_buffer() 30 41 -- stop if yanks or register types table is empty 31 - if #YANKS == 0 or #REG_TYPES == 0 then 42 + if #YB_YANKS == 0 or #YB_REG_TYPES == 0 then 32 43 print("No yanks to show.") 33 44 return nil 34 45 end ··· 105 116 -- key mappings for selection and closing the popup 106 117 local map_opts = { noremap = true, silent = true, buffer = b.bufnr } 107 118 108 - -- merge default and options keymap tables 109 - local k = vim.tbl_deep_extend("force", default_keymaps, OPTS.keymaps or {}) 110 - 111 - -- merge default and options keymap tables 112 - OPTS.registers = 113 - vim.tbl_deep_extend("force", default_registers, OPTS.registers or {}) 114 - 115 - -- check table for number behavior option (prefix or jump, default to prefix) 116 - OPTS.num_behavior = OPTS.num_behavior or "prefix" 117 - 118 119 -- popup buffer navigation binds 119 - if OPTS.num_behavior == "prefix" then 120 + if YB_OPTS.num_behavior == "prefix" then 120 121 vim.keymap.set("n", k.navigation_next, function() 121 122 local count = vim.v.count1 > 0 and vim.v.count1 or 1 122 123 helpers.next_numbered_item(count) ··· 126 127 local count = vim.v.count1 > 0 and vim.v.count1 or 1 127 128 helpers.prev_numbered_item(count) 128 129 return "" 129 - end, { noremap = true, silent = true, buffer = b.bufnr }) 130 + end, map_opts) 130 131 else 131 132 vim.keymap.set( 132 133 "n", 133 134 k.navigation_next, 134 135 helpers.next_numbered_item, 135 - { noremap = true, silent = true, buffer = b.bufnr } 136 + map_opts 136 137 ) 137 138 vim.keymap.set( 138 139 "n", 139 140 k.navigation_prev, 140 141 helpers.prev_numbered_item, 141 - { noremap = true, silent = true, buffer = b.bufnr } 142 + map_opts 142 143 ) 143 144 end 144 145 145 - -- Map number keys to jump to entry if num_behavior is 'jump' 146 - if OPTS.num_behavior == "jump" then 147 - for i = 1, OPTS.max_entries do 146 + -- map number keys to jump to entry if num_behavior is 'jump' 147 + if YB_OPTS.num_behavior == "jump" then 148 + for i = 1, YB_OPTS.max_entries do 148 149 vim.keymap.set("n", tostring(i), function() 149 150 local target_line = nil 150 151 for line_num, yank_num in pairs(b.line_yank_map) do ··· 166 167 -- use the mapping to find the original yank 167 168 local yankIndex = b.line_yank_map[cursor] 168 169 if yankIndex then 169 - -- retrieve the full yank, including all lines 170 - local text = YANKS[yankIndex] 171 - 170 + -- close window upon selection 171 + vim.api.nvim_win_close(b.win_id, true) 172 + helpers.smart_paste( 173 + YB_YANKS[yankIndex], 174 + YB_REG_TYPES[yankIndex], 175 + true 176 + ) 177 + else 178 + print("Error: Invalid selection") 179 + end 180 + end, map_opts) 181 + -- paste backwards 182 + vim.keymap.set("n", k.paste_back, function() 183 + local cursor = vim.api.nvim_win_get_cursor(b.win_id)[1] 184 + -- use the mapping to find the original yank 185 + local yankIndex = b.line_yank_map[cursor] 186 + if yankIndex then 172 187 -- close window upon selection 173 188 vim.api.nvim_win_close(b.win_id, true) 174 - helpers.smart_paste(text, REG_TYPES[yankIndex]) 189 + helpers.smart_paste( 190 + YB_YANKS[yankIndex], 191 + YB_REG_TYPES[yankIndex], 192 + false 193 + ) 175 194 else 176 195 print("Error: Invalid selection") 177 196 end 178 - end, { buffer = b.bufnr }) 197 + end, map_opts) 179 198 180 199 -- bind yank behavior 181 200 vim.keymap.set("n", k.yank, function() 182 201 local cursor = vim.api.nvim_win_get_cursor(b.win_id)[1] 183 202 local yankIndex = b.line_yank_map[cursor] 184 203 if yankIndex then 185 - local text = YANKS[yankIndex] 186 - vim.fn.setreg(OPTS.registers.yank_register, text) 204 + vim.fn.setreg(YB_OPTS.registers.yank_register, YB_YANKS[yankIndex]) 187 205 vim.api.nvim_win_close(b.win_id, true) 188 206 end 189 - end, { buffer = b.bufnr }) 207 + end, map_opts) 190 208 191 209 -- close popup keybinds 192 210 -- REFACTOR: check if close keybind is string, handle differently
+4 -4
lua/yankbank/persistence.lua
··· 6 6 ---@param entry string 7 7 ---@param reg_type string 8 8 function M.add_entry(entry, reg_type) 9 - if OPTS.persist_type == "sqlite" then 9 + if YB_OPTS.persist_type == "sqlite" then 10 10 persistence:insert_yank(entry, reg_type) 11 11 end 12 12 end 13 13 14 14 --- get current state of yanks in persistent storage 15 15 function M.get_yanks() 16 - if OPTS.persist_type == "sqlite" then 16 + if YB_OPTS.persist_type == "sqlite" then 17 17 return persistence:get_bank() 18 18 end 19 19 end ··· 22 22 ---@return table 23 23 ---@return table 24 24 function M.setup() 25 - if not OPTS.persist_type then 25 + if not YB_OPTS.persist_type then 26 26 return {}, {} 27 - elseif OPTS.persist_type == "sqlite" then 27 + elseif YB_OPTS.persist_type == "sqlite" then 28 28 persistence = require("yankbank.persistence.sql").setup() 29 29 return persistence:get_bank() 30 30 else
+4 -4
lua/yankbank/persistence/sql.lua
··· 102 102 --- set up database persistence 103 103 ---@return sqlite_tbl data 104 104 function M.setup() 105 - max_entries = OPTS.max_entries 105 + max_entries = YB_OPTS.max_entries 106 106 107 107 vim.api.nvim_create_user_command("YankBankClearDB", function() 108 108 data:remove() 109 - YANKS = {} 110 - REG_TYPES = {} 109 + YB_YANKS = {} 110 + YB_REG_TYPES = {} 111 111 end, {}) 112 112 113 - if OPTS.debug == true then 113 + if YB_OPTS.debug == true then 114 114 vim.api.nvim_create_user_command("YankBankViewDB", function() 115 115 print(vim.inspect(data:get())) 116 116 end, {})