Neovim plugin improving access to clipboard history (mirror)
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

feat(WIP file persistence, more infrastructure added)

+184 -51
+1 -1
.github/workflows/ci.yaml .github/workflows/pr_check.yaml
··· 1 - name: YankBank CI 1 + name: Run formatter and linter 2 2 3 3 on: 4 4 pull_request:
+1 -1
README.md
··· 11 11 12 12 ### Screenshots 13 13 14 - ![YankBank popup window](assets/screenshot-1.png) 14 + <!-- ![YankBank popup window](assets/screenshot-1.png) --> 15 15 16 16 ![YankBank popup window zoomed](assets/screenshot-2.png) 17 17
+35 -23
lua/yankbank/clipboard.lua
··· 1 1 -- clipboard.lua 2 2 local M = {} 3 3 4 + -- TODO: convert string to table yanks to work better with files 5 + 4 6 -- Function to add yanked text to table 7 + ---@param yanks table 8 + ---@param reg_types table 9 + ---@param text string 10 + ---@param reg_type string 11 + ---@param max_entries integer 5 12 function M.add_yank(yanks, reg_types, text, reg_type, max_entries) 6 13 -- avoid adding empty strings 7 - if text ~= "" and text ~= " " and text ~= "\n" then 8 - -- do not update with duplicate values 9 - for _, entry in ipairs(yanks) do 10 - if entry == text then 11 - return 12 - end 13 - end 14 - table.insert(yanks, 1, text) 15 - table.insert(reg_types, 1, reg_type) 16 - if #yanks > max_entries then 17 - table.remove(yanks) 18 - table.remove(reg_types) 14 + -- TODO: could block adding single characters here 15 + if text == "" or text == " " or text == "\n" then 16 + return 17 + end 18 + print( 19 + "yanks: ", 20 + vim.inspect(yanks), 21 + "| reg_types: ", 22 + vim.inspect(reg_types) 23 + ) 24 + 25 + -- do not update with duplicate values 26 + for _, entry in ipairs(yanks) do 27 + if entry == text then 28 + return 19 29 end 20 30 end 31 + 32 + -- add entry to bank 33 + table.insert(yanks, 1, text) 34 + table.insert(reg_types, 1, reg_type) 35 + if #yanks > max_entries then 36 + table.remove(yanks) 37 + table.remove(reg_types) 38 + end 21 39 end 22 40 23 41 -- autocommand to listen for yank events 42 + ---@param yanks table 43 + ---@param reg_types table 44 + ---@param max_entries integer 24 45 function M.setup_yank_autocmd(yanks, reg_types, max_entries) 25 46 vim.api.nvim_create_autocmd("TextYankPost", { 26 47 callback = function() 27 - -- TODO: this function can be expanded to incorporate other registers 28 - -- - use vim.v.event.regname and an allowlist 29 - -- local reg_type = vim.v.event.operator 30 - -- if reg_type == "y" or reg_type == "d" then 31 - -- local yanked_text = vim.fn.getreg('"') 32 - 33 48 -- get register information 34 49 local rn = vim.v.event.regname 35 - local reg_type = vim.fn.getregtype('"') 50 + local reg_type = vim.fn.getregtype("+") 36 51 37 52 -- check changes wwere made to default register 38 53 if vim.v.event.regname == "" then 39 54 local yanked_text = vim.fn.getreg(rn) 40 - 41 - -- NOTE: this only blocks adding to list if something else is in plus register 42 - if string.len(yanked_text) <= 1 then 55 + if #yanked_text <= 1 then 43 56 return 44 57 end 45 - 46 58 M.add_yank(yanks, reg_types, yanked_text, reg_type, max_entries) 47 59 end 48 60 end,
+3
lua/yankbank/data.lua
··· 2 2 local M = {} 3 3 4 4 -- reformat yanks table for popup 5 + ---@param yanks table 6 + ---@param sep string 7 + ---@return table, table 5 8 function M.get_display_lines(yanks, sep) 6 9 local display_lines = {} 7 10 local line_yank_map = {}
+4
lua/yankbank/helpers.lua
··· 2 2 local M = {} 3 3 4 4 -- navigate to the next numbered item 5 + ---@param steps integer 5 6 function M.next_numbered_item(steps) 6 7 steps = steps or 1 -- Default to 1 if no steps are provided 7 8 local current_line = vim.api.nvim_win_get_cursor(0)[1] ··· 24 25 end 25 26 26 27 -- navigate to the previous numbered item 28 + ---@param steps integer 27 29 function M.prev_numbered_item(steps) 28 30 steps = steps or 1 -- Default to 1 if no steps are provided 29 31 local current_line = vim.api.nvim_win_get_cursor(0)[1] ··· 43 45 end 44 46 45 47 -- customized paste function that functions like 'p' 48 + ---@param text string 49 + ---@param reg_type string 46 50 function M.smart_paste(text, reg_type) 47 51 -- convert text string to string list 48 52 local lines = {}
+2
lua/yankbank/init.lua
··· 12 12 local sep = "-----" 13 13 14 14 -- wrapper function for main plugin functionality 15 + ---@param opts table|nil 15 16 local function show_yank_bank(opts) 16 17 -- Parse command arguments directly if args are provided as a string 17 18 opts = opts or {} ··· 32 33 end 33 34 34 35 -- plugin setup 36 + ---@param opts table|nil 35 37 function M.setup(opts) 36 38 opts = opts or {} 37 39
+138 -26
lua/yankbank/persistence/file.lua
··· 3 3 local M = {} 4 4 5 5 local n_entries = 0 6 + local m_entries = 10 6 7 7 8 -- function that checks if a file exists 8 9 ---@param file string: file path ··· 23 24 return {} 24 25 end 25 26 local lines = {} 26 - for line in io.lines(file) do 27 + local f, err = io.open(file) 28 + if not f then 29 + error("Error opening file: " .. err) 30 + end 31 + for line in f:lines() do 27 32 lines[#lines + 1] = line 28 33 end 34 + f:close() 29 35 return lines 30 36 end 31 37 ··· 36 42 local function check_for_header(line) 37 43 local n = string.match(line, "<YANKBANK_LIST:(%d+)>") 38 44 if n then 39 - n_entries = n 45 + n_entries = tonumber(n, 10) 40 46 return true 41 47 end 42 48 return false ··· 72 78 return entry 73 79 end 74 80 81 + -- remove entry from bankfile 82 + ---@param file string: bank file name 83 + local function remove_last_entry(file) 84 + local f, err = io.open(file, "r+") 85 + if not f then 86 + error("Could not open file for reading: " .. err) 87 + end 88 + 89 + -- read lines from file until matching entry is found 90 + local lines = {} 91 + -- TODO: does this for loop work? 92 + for line in f:lines() do 93 + print(line) 94 + if 95 + string.match( 96 + line, 97 + "<YANKBANK_ENTRY:" .. n_entries .. ",%d+,%a+,%a+>" 98 + ) 99 + then 100 + n_entries = n_entries - 1 101 + lines[1] = "<YANKBANK_LIST:" .. n_entries .. ">\n" 102 + break 103 + else 104 + lines[#lines + 1] = line 105 + end 106 + end 107 + f:close() 108 + 109 + -- write to file 110 + f, err = io.open(file, "w") 111 + if not f then 112 + error("Could not open file for writing: " .. err) 113 + end 114 + for i = 1, #lines do 115 + f:write(lines[i]) 116 + end 117 + f:close() 118 + end 119 + 120 + local function open_file(file, mode) 121 + local f, err = io.open(file, mode) 122 + if not f then 123 + error("Could not open file: " .. err) 124 + end 125 + return f 126 + end 127 + 128 + -- add entry bankfile. (this function needs to be callable from outside the module) 129 + ---@param file string 130 + ---@param entry table|string 131 + ---@param reg_type string 132 + -- TODO: trigger in add_yank in clipboard.lua 133 + -- Function scope probably needs to change to a different level (or be callable from persistence.lua) 134 + local function add_to_bankfile(file, entry, reg_type) 135 + if n_entries >= m_entries then 136 + remove_last_entry(file) 137 + end 138 + n_entries = n_entries + 1 139 + 140 + -- local f, err = io.open(file, "w") 141 + local lines = read_lines(file) 142 + print(vim.inspect(lines)) 143 + local f = open_file(file, "w+") 144 + -- BUG: issues around here surrounding newline behavior 145 + -- - definintely an issue adding, only appears after inserting first entry 146 + f:write("<YANKBANK_LIST:" .. n_entries .. ">\n") 147 + for i = 2, #lines do 148 + print(i) 149 + f:write(lines[i] .. "\n") 150 + print(lines[i]) 151 + end 152 + 153 + -- write entry header 154 + f:write( 155 + "<YANKBANK_ENTRY:" 156 + .. n_entries 157 + .. "," 158 + .. #entry 159 + .. ",nil," 160 + .. reg_type 161 + .. ">\n" 162 + ) 163 + 164 + -- convert string entry to table 165 + if type(entry) == "string" then 166 + print(entry) 167 + -- local text = entry 168 + -- entry = {} 169 + -- for line in text:gmatch("[^\r\n]+") do 170 + -- table.insert(entry, line) 171 + -- end 172 + f:write(entry .. "\n") 173 + return 174 + end 175 + 176 + for i = 1, #entry do 177 + f:write(entry[i]) 178 + end 179 + 180 + f:close() 181 + end 182 + 75 183 -- populate yankbank with entries contained in file. 76 184 ---@param yanks table: table to populate with yanks 77 185 ---@param file string: yankbank persistence file 78 186 ---@param max_entries integer: maximum number of yankbank entries 79 - local function populate_yankbank(yanks, file, max_entries) 187 + ---@return table, table 188 + local function populate_yankbank(file, max_entries, yanks, reg_types) 80 189 -- read lines from file 81 190 local lines = read_lines(file) 82 191 if not check_for_header(lines[1]) then 83 192 print("YankBank list header not found in file...") 84 - return 193 + return {}, {} 85 194 end 86 195 87 196 -- iterate through remaining lines in file, adding entries to yankbank 88 - local entries = {} 89 197 local i = 2 90 198 while i <= #lines do 91 199 local res = check_for_entry(lines[i]) 92 200 if res then 93 201 local entry = read_entry(i + 1, res.length, lines) 94 202 if res.index < max_entries then 95 - -- add to entries 96 - -- NOTE: might need to sort entries afterwards 97 - -- TODO: populate yankbank entries 98 - -- - update yanks table, reg_type, and place at correct spot in list 99 - entries[#entries + 1] = entry -- TODO: might need to convert to string 100 - -- NOTE: ^ should I use table.insert instead? (probably) 203 + yanks[#yanks + 1] = entry 204 + reg_types[#reg_types + 1] = res.reg_type 205 + -- NOTE: ^ should I use table.insert instead? (would allow inserting at specific position) 101 206 end 102 207 -- skip lines that were added to entries 103 208 i = i + res.length 104 209 end 105 210 i = i + 1 106 211 end 107 - 108 - -- TODO: add entries to bank 109 - print(yanks) 110 - 111 - print(vim.inspect(entries)) 212 + return yanks, reg_types 112 213 end 113 214 114 215 -- setup function for a persistence file. 115 216 -- should be called in plugin setup function 116 - ---@param yanks table: table to populate with yanks 117 217 ---@param file string: file path 118 218 ---@param max_entries integer: maximum number of yankbank entries 119 - function M.setup_persistence(yanks, file, max_entries) 120 - -- check if file exists, create it (with header) if it does not 219 + ---@param yanks table: table to populate with yanks 220 + ---@param reg_types table: table containing register types 221 + ---@return table, table 222 + function M.setup_persistence(file, max_entries, yanks, reg_types) 223 + -- check if file exists, otherwise create it (with header) 121 224 if not file_exists(file) then 122 225 print("Creating file...") 123 226 local f, err = io.open(file, "w") ··· 127 230 else 128 231 print(err) 129 232 end 130 - return 233 + return {}, {} 131 234 end 132 - populate_yankbank(yanks, file, max_entries) 235 + m_entries = max_entries 236 + return populate_yankbank(file, max_entries, yanks, reg_types) 133 237 end 134 238 135 - -- local lines = read_lines("test.txt") 136 - -- print(vim.inspect(lines)) 137 239 -- TEST: remove later 138 240 local yanks = {} 139 - print(M.setup_persistence(yanks, "test.txt", 10)) 241 + local reg_types = {} 242 + M.setup_persistence("test.txt", 10, yanks, reg_types) 243 + print(vim.inspect(yanks)) 244 + print(vim.inspect(reg_types)) 140 245 yanks = {} 141 - print(M.setup_persistence(yanks, "test1.txt", 10)) 142 - print(n_entries) 246 + reg_types = {} 247 + print(vim.inspect(yanks)) 248 + print(vim.inspect(reg_types)) 249 + M.setup_persistence("test1.txt", 10, yanks, reg_types) 250 + m_entries = 10 251 + add_to_bankfile("test1.txt", "text", "v") 252 + -- BUG: this isnt added to the testing file, file i/o might be too slow 253 + add_to_bankfile("test1.txt", "text1", "V") 254 + -- remove_last_entry("test1.txt") 143 255 144 256 return M