Neovim plugin improving access to clipboard history (mirror)

feat/refactor: added backwards paste and changed global names

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