├── .github ├── FUNDING.yml └── workflows │ ├── docs.yaml │ └── pr_check.yaml ├── .gitignore ├── .stylua.toml ├── LICENSE ├── Makefile ├── README.md ├── assets ├── screenshot-1.png └── screenshot-2.png ├── doc └── yankbank-nvim.txt └── lua └── yankbank ├── api.lua ├── clipboard.lua ├── data.lua ├── helpers.lua ├── init.lua ├── menu.lua ├── persistence.lua ├── persistence └── sql.lua └── utils.lua /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: ptdewey 2 | -------------------------------------------------------------------------------- /.github/workflows/docs.yaml: -------------------------------------------------------------------------------- 1 | name: Generate Vimdoc 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | docs: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v3 13 | - name: panvimdoc 14 | uses: kdheepak/panvimdoc@main 15 | with: 16 | vimdoc: yankbank-nvim 17 | version: "Neovim >= 0.7.0" 18 | demojify: true 19 | treesitter: true 20 | - name: Push changes 21 | uses: stefanzweifel/git-auto-commit-action@v4 22 | with: 23 | commit_message: "doc: auto-generate vimdoc" 24 | commit_user_name: "github-actions[bot]" 25 | commit_user_email: "github-actions[bot]@users.noreply.github.com" 26 | commit_author: "github-actions[bot] " 27 | -------------------------------------------------------------------------------- /.github/workflows/pr_check.yaml: -------------------------------------------------------------------------------- 1 | name: Run formatter and linter 2 | 3 | on: 4 | pull_request: 5 | 6 | jobs: 7 | stylua: 8 | name: Stylua 9 | runs-on: ubuntu-22.04 10 | steps: 11 | - uses: actions/checkout@v3 12 | - uses: JohnnyMorganz/stylua-action@v3 13 | with: 14 | token: ${{ secrets.GITHUB_TOKEN }} 15 | version: latest 16 | args: --color always --check . 17 | 18 | luacheck: 19 | name: Luacheck 20 | runs-on: ubuntu-22.04 21 | steps: 22 | - uses: actions/checkout@v3 23 | 24 | - name: Prepare 25 | run: | 26 | sudo apt-get update 27 | sudo apt-get install -y luarocks 28 | sudo luarocks install luacheck 29 | 30 | - name: Lint 31 | run: sudo make lint 32 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | yankbank.db 2 | -------------------------------------------------------------------------------- /.stylua.toml: -------------------------------------------------------------------------------- 1 | column_width = 80 2 | line_endings = "Unix" 3 | indent_type = "Spaces" 4 | indent_width = 4 5 | quote_style = "AutoPreferDouble" 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Patrick Dewey 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | fmt: 2 | echo "Formatting lua/yankbank..." 3 | stylua lua/ --config-path=.stylua.toml 4 | 5 | lint: 6 | echo "Linting lua/yankbank..." 7 | luacheck lua/ --globals vim YB_YANKS YB_REG_TYPES YB_OPTS YB_PINS 8 | 9 | pr-ready: fmt lint 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # YankBank 2 | 3 | A Neovim plugin for keeping track of more recent yanks and deletions and exposing them in a quick access menu. 4 | 5 | ## What it Does 6 | 7 | YankBank stores the N recent yanks into the unnamed register ("), then populates a popup window with these recent yanks, allowing for quick access to recent yank history. 8 | Upon opening the popup menu, the current contents of the unnamedplus (+) register are also added to the menu (if they are different from the current contents of the unnamed register). 9 | 10 | Choosing an entry from the menu (by hitting enter) will paste it into the currently open buffer at the cursor position. 11 | 12 | YankBank also offers persistence between sessions, meaning that you won't lose your yanks after closing and reopening a session (see [persistence](#Persistence)). 13 | 14 | ### Screenshots 15 | 16 | ![YankBank popup window zoomed](assets/screenshot-2.png) 17 | 18 | The menu is specific to the current session, and will only contain the contents of the current unnamedplus register upon opening in a completely new session. 19 | It will be populated further for each yank or deletion in that session. 20 | 21 | ## Installation and Setup 22 | 23 | #### With Persistence (Recommended) 24 | 25 | Using lazy.nvim 26 | ```lua 27 | { 28 | "ptdewey/yankbank-nvim", 29 | dependencies = "kkharji/sqlite.lua", 30 | config = function() 31 | require('yankbank').setup({ 32 | persist_type = "sqlite", 33 | }) 34 | end, 35 | } 36 | ``` 37 | 38 | #### Without persistence: 39 | 40 | Using lazy.nvim 41 | ```lua 42 | { 43 | "ptdewey/yankbank-nvim", 44 | config = function() 45 | require('yankbank').setup() 46 | end, 47 | } 48 | ``` 49 | 50 | ### Setup Options 51 | 52 | The setup function also supports taking in a table of options: 53 | | Option | Type | Default | 54 | |-------------|--------------------------------------------|----------------| 55 | | max_entries | integer number of entries to show in popup | `10` | 56 | | sep | string separator to show between table entries | `"-----"` | 57 | | keymaps | table containing keymap overrides | `{}` | 58 | | keymaps.navigation_next | string | `"j"` | 59 | | keymaps.navigation_prev | string | `"k"` | 60 | | keymaps.paste | string | `""` | 61 | | keymaps.paste_back | string | `"P"` | 62 | | keymaps.yank | string | `"yy"` | 63 | | keymaps.close | table of strings | `{ "", "", "q" }` | 64 | | num_behavior | string defining jump behavior "prefix" or "jump" | `"prefix"` | 65 | | focus_gain_poll | boolean | `nil` | 66 | | registers | table container for register overrides | `{ }` | 67 | | registers.yank_register | default register to yank from popup to | `"+"` | 68 | | persist_type | string defining persistence type "sqlite" or nil | `nil` | 69 | | db_path | string defining database file path for use with sqlite persistence | plugin install directory | 70 | 71 | 72 | #### Example Configuration 73 | 74 | ```lua 75 | { 76 | "ptdewey/yankbank-nvim", 77 | config = function() 78 | require('yankbank').setup({ 79 | max_entries = 9, 80 | sep = "-----", 81 | num_behavior = "jump", 82 | focus_gain_poll = true, 83 | persist_type = "sqlite", 84 | keymaps = { 85 | paste = "", 86 | paste_back = "P", 87 | }, 88 | registers = { 89 | yank_register = "+", 90 | }, 91 | }) 92 | end, 93 | } 94 | ``` 95 | 96 | If no separator is desired, pass in an empty string for `sep` 97 | 98 | The 'num_behavior' option defines in-popup navigation behavior when hitting number keys. 99 | - `num_behavior = "prefix"` works similar to traditional vim navigation with '3j' moving down 3 entries in the bank. 100 | - `num_behavior = "jump"` jumps to entry matching the pressed number key (i.e. '3' jumps to entry 3) 101 | - Note: If 'max_entries' is a two-digit number, there will be a delay upon pressing numbers that prefix a valid entry. 102 | 103 | The '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 with `focus_gain_poll = true`. 104 | 105 | ### Persistence 106 | For the best experience with YankBank, enabling persistence is highly recommended. 107 | If persistence is enabled, sqlite.lua will be used to create a persistent store for recent yanks in the plugin root directory. 108 | To utilize sqlite persistence, `"kkharji/sqlite.lua"` must be added as a dependency in your config, and `persist_type` must be set to `"sqlite"`: 109 | 110 | ```lua 111 | -- lazy 112 | return { 113 | "ptdewey/yankbank-nvim", 114 | dependencies = "kkharji/sqlite.lua", 115 | config = function() 116 | require('yankbank').setup({ 117 | -- other options... 118 | persist_type = "sqlite" 119 | }) 120 | end, 121 | } 122 | ``` 123 | 124 | Note: The database can be cleared with the `:YankBankClearDB` command or by deleting the db file (found in the plugin install directory by default). 125 | 126 | If you run into any SQL related issues, please file an issue on GitHub. (As a temporary fix, you can also try clearing the database) 127 | 128 | 129 | If you run into permissions issues when creating the db file (i.e. when installing using Nix), use the `db_path` option to change the default file path. (`vim.fn.stdpath("data")` should work) 130 | 131 | ## Usage 132 | 133 | The popup menu can be opened with the command:`:YankBank`, an entry is pasted at the current cursor position by hitting enter, and the menu can be closed by hitting escape, ctrl-c, or q. 134 | An entry from the menu can also be yanked into the unnamedplus register by hitting yy. 135 | 136 | I would personally also recommend setting a keybind to open the menu. 137 | ```lua 138 | -- map to 'y' 139 | vim.keymap.set("n", "y", "YankBank", { noremap = true }) 140 | ``` 141 | 142 | --- 143 | 144 | ## API (WIP) 145 | 146 | Some plugin internals are also accessible via the YankBank api. 147 | 148 | Examples: 149 | ```lua 150 | -- get the ith entry in the bank 151 | ---@param i integer index to get 152 | -- output format: { yank_text = "entry", reg_type = "v" } 153 | local e = require("yankbank.api").get_entry(i) 154 | 155 | -- add an entry to the bank 156 | ---@param yank_text string yank text to add to YANKS table 157 | ---@param reg_type string register type "v", "V", or "^V" (visual, v-line, v-block respectively) 158 | require("yankbank.api").add_entry("yank_text", "reg_type") 159 | 160 | -- remove an entry from the bank by index 161 | ---@param i integer index to remove 162 | require("yankbank.api").remove_entry(i) 163 | 164 | --- pin entry to yankbank so that it won't be removed when its position exceeds the max number of entries 165 | ---@param i integer index to pin 166 | require("yankbank.api").pin_entry(i) 167 | 168 | 169 | --- unpin bank entry 170 | ---@param i integer index to unpin 171 | require("yankbank.api").unpin_entry(i) 172 | ``` 173 | 174 | For more details about the API see [lua/yankbank/api.lua](lua/yankbank/api.lua) 175 | 176 | --- 177 | 178 | ## Potential Improvements 179 | - nvim-cmp integration 180 | - fzf integration 181 | - telescope integration 182 | 183 | ## Alternatives 184 | 185 | - [nvim-neoclip](https://github.com/AckslD/nvim-neoclip.lua) 186 | - [yanky.nvim](https://github.com/gbprod/yanky.nvim) 187 | -------------------------------------------------------------------------------- /assets/screenshot-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ptdewey/yankbank-nvim/ded8f01bd6a7887310e14d463c8aa55eb79ec281/assets/screenshot-1.png -------------------------------------------------------------------------------- /assets/screenshot-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ptdewey/yankbank-nvim/ded8f01bd6a7887310e14d463c8aa55eb79ec281/assets/screenshot-2.png -------------------------------------------------------------------------------- /doc/yankbank-nvim.txt: -------------------------------------------------------------------------------- 1 | *yankbank-nvim.txt* For Neovim >= 0.7.0 Last change: 2024 October 28 2 | 3 | ============================================================================== 4 | Table of Contents *yankbank-nvim-table-of-contents* 5 | 6 | 1. YankBank |yankbank-nvim-yankbank| 7 | - What it Does |yankbank-nvim-yankbank-what-it-does| 8 | - Installation and Setup |yankbank-nvim-yankbank-installation-and-setup| 9 | - Usage |yankbank-nvim-yankbank-usage| 10 | - API (WIP) |yankbank-nvim-yankbank-api-(wip)| 11 | - Potential Improvements |yankbank-nvim-yankbank-potential-improvements| 12 | - Alternatives |yankbank-nvim-yankbank-alternatives| 13 | 2. Links |yankbank-nvim-links| 14 | 15 | ============================================================================== 16 | 1. YankBank *yankbank-nvim-yankbank* 17 | 18 | A Neovim plugin for keeping track of more recent yanks and deletions and 19 | exposing them in a quick access menu. 20 | 21 | 22 | WHAT IT DOES *yankbank-nvim-yankbank-what-it-does* 23 | 24 | YankBank stores the N recent yanks into the unnamed register (“), then 25 | populates a popup window with these recent yanks, allowing for quick access to 26 | recent yank history. Upon opening the popup menu, the current contents of the 27 | unnamedplus (+) register are also added to the menu (if they are different from 28 | the current contents of the unnamed register). 29 | 30 | Choosing an entry from the menu (by hitting enter) will paste it into the 31 | currently open buffer at the cursor position. 32 | 33 | YankBank also offers persistence between sessions, meaning that you won’t 34 | lose your yanks after closing and reopening a session (see 35 | |yankbank-nvim-persistence|). 36 | 37 | 38 | SCREENSHOTS ~ 39 | 40 | The menu is specific to the current session, and will only contain the contents 41 | of the current unnamedplus register upon opening in a completely new session. 42 | It will be populated further for each yank or deletion in that session. 43 | 44 | 45 | INSTALLATION AND SETUP *yankbank-nvim-yankbank-installation-and-setup* 46 | 47 | 48 | WITH PERSISTENCE (RECOMMENDED) 49 | 50 | Using lazy.nvim 51 | 52 | >lua 53 | { 54 | "ptdewey/yankbank-nvim", 55 | dependencies = "kkharji/sqlite.lua", 56 | config = function() 57 | require('yankbank').setup({ 58 | persist_type = "sqlite", 59 | }) 60 | end, 61 | } 62 | < 63 | 64 | 65 | WITHOUT PERSISTENCE: 66 | 67 | Using lazy.nvim 68 | 69 | >lua 70 | { 71 | "ptdewey/yankbank-nvim", 72 | config = function() 73 | require('yankbank').setup() 74 | end, 75 | } 76 | < 77 | 78 | 79 | SETUP OPTIONS ~ 80 | 81 | The setup function also supports taking in a table of options: | Option | Type 82 | | Default | 83 | |————-|——————————————–|—————-| 84 | | max_entries | integer number of entries to show in popup | `10` | | sep | 85 | string separator to show between table entries | `"-----"` | | keymaps | table 86 | containing keymap overrides | `{}` | | keymaps.navigation_next | string | `"j"` 87 | | | keymaps.navigation_prev | string | `"k"` | | keymaps.paste | string | 88 | `""` | | keymaps.paste_back | string | `"P"` | | keymaps.yank | string | 89 | `"yy"` | | keymaps.close | table of strings | `{ "", "", "q" }` | | 90 | num_behavior | string defining jump behavior "prefix" or "jump" | `"prefix"` | 91 | | focus_gain_poll | boolean | `nil` | | registers | table container for 92 | register overrides | `{ }` | | registers.yank_register | default register to 93 | yank from popup to | `"+"` | | persist_type | string defining persistence type 94 | "sqlite" or nil | `nil` | | db_path | string defining database file path for 95 | use with sqlite persistence | plugin install directory | 96 | 97 | 98 | EXAMPLE CONFIGURATION 99 | 100 | >lua 101 | { 102 | "ptdewey/yankbank-nvim", 103 | config = function() 104 | require('yankbank').setup({ 105 | max_entries = 9, 106 | sep = "-----", 107 | num_behavior = "jump", 108 | focus_gain_poll = true, 109 | persist_type = "sqlite", 110 | keymaps = { 111 | paste = "", 112 | paste_back = "P", 113 | }, 114 | registers = { 115 | yank_register = "+", 116 | }, 117 | }) 118 | end, 119 | } 120 | < 121 | 122 | If no separator is desired, pass in an empty string for `sep` 123 | 124 | The 'num_behavior' option defines in-popup navigation behavior when hitting 125 | number keys. - `num_behavior = "prefix"` works similar to traditional vim 126 | navigation with '3j' moving down 3 entries in the bank. - `num_behavior = 127 | "jump"` jumps to entry matching the pressed number key (i.e. '3' jumps to 128 | entry 3) - Note: If 'max_entries' is a two-digit number, there will be a delay 129 | upon pressing numbers that prefix a valid entry. 130 | 131 | The 'focus_gain_poll' option allows for enabling an additional autocommand that 132 | watches for focus gains (refocusing Neovim window), and checks for changes in 133 | the unnamedplus ('+') register, adding to yankbank when new contents are found. 134 | This allows for automatically adding text copied from other sources (like a 135 | browser) to the yankbank without the bank opening trigger. Off by default, but 136 | I highly recommend enabling it with `focus_gain_poll = true`. 137 | 138 | 139 | PERSISTENCE ~ 140 | 141 | For the best experience with YankBank, enabling persistence is highly 142 | recommended. If persistence is enabled, sqlite.lua will be used to create a 143 | persistent store for recent yanks in the plugin root directory. To utilize 144 | sqlite persistence, `"kkharji/sqlite.lua"` must be added as a dependency in 145 | your config, and `persist_type` must be set to `"sqlite"` 146 | 147 | >lua 148 | -- lazy 149 | return { 150 | "ptdewey/yankbank-nvim", 151 | dependencies = "kkharji/sqlite.lua", 152 | config = function() 153 | require('yankbank').setup({ 154 | -- other options... 155 | persist_type = "sqlite" 156 | }) 157 | end, 158 | } 159 | < 160 | 161 | Note:The database can be cleared with the `:YankBankClearDB` command or by 162 | deleting the db file (found in the plugin install directory by default). 163 | 164 | If you run into any SQL related issues, please file an issue on GitHub. (As a 165 | temporary fix, you can also try clearing the database) 166 | 167 | If you run into permissions issues when creating the db file (i.e. when 168 | installing using Nix), use the `db_path` option to change the default file 169 | path. (`vim.fn.stdpath("data")` should work) 170 | 171 | 172 | USAGE *yankbank-nvim-yankbank-usage* 173 | 174 | The popup menu can be opened with the command:`:YankBank`, an entry is pasted 175 | at the current cursor position by hitting enter, and the menu can be closed by 176 | hitting escape, ctrl-c, or q. An entry from the menu can also be yanked into 177 | the unnamedplus register by hitting yy. 178 | 179 | I would personally also recommend setting a keybind to open the menu. 180 | 181 | >lua 182 | -- map to 'y' 183 | vim.keymap.set("n", "y", "YankBank", { noremap = true }) 184 | < 185 | 186 | ------------------------------------------------------------------------------ 187 | 188 | API (WIP) *yankbank-nvim-yankbank-api-(wip)* 189 | 190 | Some plugin internals are also accessible via the YankBank api. 191 | 192 | Examples: 193 | 194 | >lua 195 | -- get the ith entry in the bank 196 | ---@param i integer index to get 197 | -- output format: { yank_text = "entry", reg_type = "v" } 198 | local e = require("yankbank.api").get_entry(i) 199 | 200 | -- add an entry to the bank 201 | ---@param yank_text string yank text to add to YANKS table 202 | ---@param reg_type string register type "v", "V", or "^V" (visual, v-line, v-block respectively) 203 | require("yankbank.api").add_entry("yank_text", "reg_type") 204 | 205 | -- remove an entry from the bank by index 206 | ---@param i integer index to remove 207 | require("yankbank.api").remove_entry(i) 208 | 209 | --- pin entry to yankbank so that it won't be removed when its position exceeds the max number of entries 210 | ---@param i integer index to pin 211 | require("yankbank.api").pin_entry(i) 212 | 213 | 214 | --- unpin bank entry 215 | ---@param i integer index to unpin 216 | require("yankbank.api").unpin_entry(i) 217 | < 218 | 219 | For more details about the API see lua/yankbank/api.lua 220 | 221 | ------------------------------------------------------------------------------ 222 | 223 | POTENTIAL IMPROVEMENTS *yankbank-nvim-yankbank-potential-improvements* 224 | 225 | - nvim-cmp integration 226 | - fzf integration 227 | - telescope integration 228 | 229 | 230 | ALTERNATIVES *yankbank-nvim-yankbank-alternatives* 231 | 232 | - nvim-neoclip 233 | - yanky.nvim 234 | 235 | ============================================================================== 236 | 2. Links *yankbank-nvim-links* 237 | 238 | 1. *YankBank popup window zoomed*: assets/screenshot-2.png 239 | 240 | Generated by panvimdoc 241 | 242 | vim:tw=78:ts=8:noet:ft=help:norl: 243 | -------------------------------------------------------------------------------- /lua/yankbank/api.lua: -------------------------------------------------------------------------------- 1 | local M = {} 2 | 3 | --- get a table containg a single yankbank entry by index 4 | ---@param i integer 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 | 13 | --- get a table containing all yankbank entries 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 24 | end 25 | 26 | --- add an entry to yankbank 27 | ---@param yank_text string yank text to add to YANKS table 28 | ---@param reg_type string register type "v", "V", or "^V" (visual, v-line, v-block respectively) 29 | ---@param pin integer|boolean? 30 | function M.add_entry(yank_text, reg_type, pin) 31 | require("yankbank.clipboard").add_yank(yank_text, reg_type, pin) 32 | end 33 | 34 | --- remove entry from yankbank by index 35 | ---@param i integer index to remove 36 | function M.remove_entry(i) 37 | local yank_text = table.remove(YB_YANKS, i) 38 | local reg_type = table.remove(YB_REG_TYPES, i) 39 | if YB_OPTS.persist_type == "sqlite" then 40 | require("yankbank.persistence.sql") 41 | .data() 42 | .remove_match(yank_text, reg_type) 43 | end 44 | end 45 | 46 | --- pin entry to yankbank so that it won't be removed when its position exceeds the max number of entries 47 | --- 48 | ---@param i integer index to pin 49 | function M.pin_entry(i) 50 | if i > #YB_PINS then 51 | return 52 | end 53 | 54 | -- TODO: show pins differently in popup (could use different hl_groups for pinned entries?) 55 | YB_PINS[i] = 1 56 | 57 | if YB_OPTS.persist_type == "sqlite" then 58 | return require("yankbank.persistence.sql") 59 | .data() 60 | .pin(YB_YANKS[i], YB_REG_TYPES[i]) 61 | end 62 | end 63 | 64 | --- unpin bank entry 65 | --- 66 | ---@param i integer index to unpin 67 | function M.unpin_entry(i) 68 | if i > #YB_PINS then 69 | return 70 | end 71 | 72 | -- TODO: update popup pin highlight 73 | YB_PINS[i] = 0 74 | 75 | if YB_OPTS.persist_type == "sqlite" then 76 | return require("yankbank.persistence.sql") 77 | .data() 78 | .unpin(YB_YANKS[i], YB_REG_TYPES[i]) 79 | end 80 | end 81 | 82 | -- TODO: individual popup keymap setting functions 83 | 84 | return M 85 | -------------------------------------------------------------------------------- /lua/yankbank/clipboard.lua: -------------------------------------------------------------------------------- 1 | local M = {} 2 | 3 | -- import persistence module 4 | local persistence = require("yankbank.persistence") 5 | local utils = require("yankbank.utils") 6 | 7 | --- Function to add yanked text to table 8 | ---@param text string 9 | ---@param reg_type string 10 | ---@param pin integer|boolean? 11 | function M.add_yank(text, reg_type, pin) 12 | -- avoid adding empty strings 13 | if text == "" and text == " " and text == "\n" then 14 | return 15 | end 16 | 17 | local is_pinned = 0 18 | 19 | -- check for duplicate values already inserted 20 | for i, entry in ipairs(YB_YANKS) do 21 | if entry == text then 22 | -- remove matched entry so it can be inserted at 1st position 23 | table.remove(YB_YANKS, i) 24 | table.remove(YB_REG_TYPES, i) 25 | is_pinned = table.remove(YB_PINS, i) 26 | break 27 | end 28 | end 29 | 30 | -- override is_pinned if pin is set 31 | is_pinned = (pin == 1 or pin == true) and 1 32 | or (pin == 0 or pin == false) and 0 33 | or is_pinned 34 | 35 | -- add entry to bank 36 | table.insert(YB_YANKS, 1, text) 37 | table.insert(YB_REG_TYPES, 1, reg_type) 38 | table.insert(YB_PINS, 1, is_pinned) 39 | 40 | -- trim table size if necessary 41 | if #YB_YANKS > YB_OPTS.max_entries then 42 | local i = utils.last_zero_entry(YB_PINS) 43 | 44 | if not i or i == 1 then 45 | -- WARN: undefined behavior 46 | print( 47 | "Warning: all YankBank entries are pinned, insertion behavior is undefined when all entries are pinned." 48 | ) 49 | else 50 | -- remove last non-pinned entry 51 | table.remove(YB_YANKS, i) 52 | table.remove(YB_REG_TYPES, i) 53 | table.remove(YB_PINS, i) 54 | end 55 | end 56 | 57 | -- add entry to persistent store 58 | persistence.add_entry(text, reg_type, pin) 59 | end 60 | 61 | --- autocommand to listen for yank events 62 | function M.setup_yank_autocmd() 63 | vim.api.nvim_create_autocmd("TextYankPost", { 64 | callback = function() 65 | -- get register information 66 | local rn = vim.v.event.regname 67 | 68 | -- check changes wwere made to default register 69 | if rn == "" or rn == "+" then 70 | local reg_type = vim.fn.getregtype(rn) 71 | local yank_text = vim.fn.getreg(rn) 72 | 73 | if not yank_text or type(yank_text) ~= "string" then 74 | return 75 | end 76 | 77 | if #yank_text <= 1 then 78 | return 79 | end 80 | 81 | M.add_yank(yank_text, reg_type) 82 | end 83 | end, 84 | }) 85 | 86 | -- poll registers when vim is focused (check for new clipboard activity) 87 | if YB_OPTS.focus_gain_poll == true then 88 | vim.api.nvim_create_autocmd("FocusGained", { 89 | callback = function() 90 | -- get register information 91 | local reg_type = vim.fn.getregtype("+") 92 | local yank_text = vim.fn.getreg("+") 93 | 94 | if not yank_text or type(yank_text) ~= "string" then 95 | return 96 | end 97 | 98 | if string.len(yank_text) <= 1 then 99 | return 100 | end 101 | 102 | M.add_yank(yank_text, reg_type) 103 | end, 104 | }) 105 | end 106 | end 107 | 108 | return M 109 | -------------------------------------------------------------------------------- /lua/yankbank/data.lua: -------------------------------------------------------------------------------- 1 | local M = {} 2 | 3 | --- reformat yanks table for popup 4 | ---@return table, table 5 | function M.get_display_lines() 6 | local display_lines = {} 7 | local line_yank_map = {} 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 18 | if type(yank) == "string" then 19 | -- remove trailing newlines 20 | yank = yank:gsub("\n$", "") 21 | yank_lines = vim.split(yank, "\n", { plain = true }) 22 | end 23 | 24 | local leading_space, leading_space_length 25 | 26 | -- determine the number of leading whitespaces on the first line 27 | if #yank_lines > 0 then 28 | leading_space = yank_lines[1]:match("^(%s*)") 29 | leading_space_length = #leading_space 30 | end 31 | 32 | for j, line in ipairs(yank_lines) do 33 | if j == 1 then 34 | -- Format the line number with uniform spacing 35 | local lineNumber = 36 | string.format("%" .. max_digits .. "d: ", yank_num) 37 | line = line:sub(leading_space_length + 1) 38 | table.insert(display_lines, lineNumber .. line) 39 | else 40 | -- Remove the same amount of leading whitespace as on the first line 41 | line = line:sub(leading_space_length + 1) 42 | -- Use spaces equal to the line number's reserved space to align subsequent lines 43 | table.insert( 44 | display_lines, 45 | string.rep(" ", max_digits + 2) .. line 46 | ) 47 | end 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) 60 | end 61 | end 62 | 63 | return display_lines, line_yank_map 64 | end 65 | 66 | return M 67 | -------------------------------------------------------------------------------- /lua/yankbank/helpers.lua: -------------------------------------------------------------------------------- 1 | local M = {} 2 | 3 | --- navigate to the next numbered item 4 | ---@param steps integer 5 | function M.next_numbered_item(steps) 6 | steps = steps or 1 -- Default to 1 if no steps are provided 7 | local current_line = vim.api.nvim_win_get_cursor(0)[1] 8 | local total_lines = vim.api.nvim_buf_line_count(0) 9 | local jumps_made = 0 10 | local last_entry = current_line 11 | for i = current_line + 1, total_lines do 12 | local line = vim.api.nvim_buf_get_lines(0, i - 1, i, false)[1] 13 | if line:match("^%s*%d+:") and jumps_made < steps then 14 | jumps_made = jumps_made + 1 15 | last_entry = i 16 | if jumps_made == steps then 17 | vim.api.nvim_win_set_cursor(0, { i, 0 }) 18 | return 19 | end 20 | end 21 | end 22 | -- if steps exceeds number of entries below, jump to last entry 23 | vim.api.nvim_win_set_cursor(0, { last_entry, 0 }) 24 | end 25 | 26 | --- navigate to the previous numbered item 27 | ---@param steps integer 28 | function M.prev_numbered_item(steps) 29 | steps = steps or 1 -- Default to 1 if no steps are provided 30 | local current_line = vim.api.nvim_win_get_cursor(0)[1] 31 | local jumps_made = 0 32 | for i = current_line - 1, 1, -1 do 33 | local line = vim.api.nvim_buf_get_lines(0, i - 1, i, false)[1] 34 | if line:match("^%s*%d+:") and jumps_made < steps then 35 | jumps_made = jumps_made + 1 36 | if jumps_made == steps then 37 | vim.api.nvim_win_set_cursor(0, { i, 0 }) 38 | return 39 | end 40 | end 41 | end 42 | -- if steps exceeds the number of entries above, jump to first entry. 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 54 | for line in text:gmatch("([^\n]*)\n?") do 55 | table.insert(lines, line) 56 | end 57 | if #lines > 1 then 58 | table.remove(lines) 59 | end 60 | else 61 | -- text is already table 62 | lines = text 63 | end 64 | 65 | vim.api.nvim_put(lines, reg_type, after, true) 66 | end 67 | 68 | return M 69 | -------------------------------------------------------------------------------- /lua/yankbank/init.lua: -------------------------------------------------------------------------------- 1 | local M = {} 2 | 3 | -- define global variables 4 | YB_YANKS = {} 5 | YB_REG_TYPES = {} 6 | YB_PINS = {} 7 | YB_OPTS = {} 8 | 9 | -- local imports 10 | local menu = require("yankbank.menu") 11 | local clipboard = require("yankbank.clipboard") 12 | local persistence = require("yankbank.persistence") 13 | 14 | -- default plugin options 15 | local default_opts = { 16 | max_entries = 10, 17 | sep = "-----", 18 | focus_gain_poll = false, 19 | num_behavior = "prefix", 20 | registers = { 21 | yank_register = "+", 22 | }, 23 | keymaps = {}, 24 | persist_type = nil, 25 | db_path = nil, 26 | } 27 | 28 | --- wrapper function for main plugin functionality 29 | local function show_yank_bank() 30 | YB_YANKS = persistence.get_yanks() or YB_YANKS 31 | 32 | -- initialize buffer and populate bank 33 | local buf_data = menu.create_and_fill_buffer() 34 | if not buf_data then 35 | return 36 | end 37 | 38 | -- open popup window 39 | buf_data.win_id = menu.open_window(buf_data) 40 | 41 | -- set popup keybinds 42 | menu.set_keymaps(buf_data) 43 | end 44 | 45 | -- plugin setup 46 | ---@param opts? table 47 | function M.setup(opts) 48 | -- merge opts with default options table 49 | YB_OPTS = vim.tbl_deep_extend("keep", opts or {}, default_opts) 50 | 51 | -- set up menu keybinds from defafaults and YB_OPTS.keymaps 52 | menu.setup() 53 | 54 | -- enable persistence based on opts (needs to be called before autocmd setup) 55 | YB_YANKS, YB_REG_TYPES, YB_PINS = persistence.setup() 56 | 57 | -- create clipboard autocmds 58 | clipboard.setup_yank_autocmd() 59 | 60 | -- create user command 61 | vim.api.nvim_create_user_command("YankBank", function() 62 | show_yank_bank() 63 | end, { desc = "Show Recent Yanks" }) 64 | end 65 | 66 | return M 67 | -------------------------------------------------------------------------------- /lua/yankbank/menu.lua: -------------------------------------------------------------------------------- 1 | local M = {} 2 | 3 | local data = require("yankbank.data") 4 | local helpers = require("yankbank.helpers") 5 | 6 | -- default plugin keymaps 7 | local default_keymaps = { 8 | navigation_next = "j", 9 | navigation_prev = "k", 10 | paste = "", 11 | paste_back = "P", 12 | yank = "yy", 13 | close = { "", "", "q" }, 14 | } 15 | 16 | -- define default yank register 17 | local default_registers = { 18 | yank_register = "+", 19 | } 20 | 21 | -- local YB_OPTS.keymaps = {} 22 | 23 | function M.setup() 24 | -- merge default and options keymap tables 25 | YB_OPTS.keymaps = 26 | vim.tbl_deep_extend("force", default_keymaps, YB_OPTS.keymaps or {}) 27 | -- merge default and options register tables 28 | YB_OPTS.registers = 29 | vim.tbl_deep_extend("force", default_registers, YB_OPTS.registers or {}) 30 | 31 | -- check table for number behavior option (prefix or jump, default to prefix) 32 | YB_OPTS.num_behavior = YB_OPTS.num_behavior or "prefix" 33 | end 34 | 35 | --- Container class for YankBank buffer related variables 36 | ---@class YankBankBufData 37 | ---@field bufnr integer 38 | ---@field display_lines table 39 | ---@field line_yank_map table 40 | ---@field win_id integer 41 | 42 | ---create new buffer and reformat yank table for ui 43 | ---@return YankBankBufData? 44 | function M.create_and_fill_buffer() 45 | -- stop if yanks or register types table is empty 46 | if #YB_YANKS == 0 or #YB_REG_TYPES == 0 then 47 | print("No yanks to show.") 48 | return nil 49 | end 50 | 51 | -- create new buffer 52 | local bufnr = vim.api.nvim_create_buf(false, true) 53 | 54 | -- set buffer type same as current window for syntax highlighting 55 | local current_filetype = vim.bo.filetype 56 | vim.api.nvim_set_option_value("filetype", current_filetype, { buf = bufnr }) 57 | 58 | local display_lines, line_yank_map = data.get_display_lines() 59 | 60 | -- replace current buffer contents with updated table 61 | vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, display_lines) 62 | 63 | ---@type YankBankBufData 64 | return { 65 | bufnr = bufnr, 66 | display_lines = display_lines, 67 | line_yank_map = line_yank_map, 68 | win_id = -1, 69 | } 70 | end 71 | 72 | ---Calculate size and create popup window from bufnr 73 | ---@param b YankBankBufData 74 | ---@return integer 75 | function M.open_window(b) 76 | -- set maximum window width based on number of lines 77 | local max_width = 0 78 | if b.display_lines and #b.display_lines > 0 then 79 | for _, line in ipairs(b.display_lines) do 80 | max_width = math.max(max_width, #line) 81 | end 82 | else 83 | max_width = vim.api.nvim_get_option_value("columns", {}) 84 | end 85 | 86 | -- define buffer window width and height based on number of columns 87 | -- FIX: long enough entries will cause window to go below end of screen 88 | -- FIX: wrapping long lines will cause entries below to not show in menu (requires scrolling to see) 89 | local width = 90 | math.min(max_width, vim.api.nvim_get_option_value("columns", {}) - 4) 91 | local height = math.min( 92 | b.display_lines and #b.display_lines or 1, 93 | vim.api.nvim_get_option_value("lines", {}) - 10 94 | ) 95 | 96 | -- open window 97 | local win_id = vim.api.nvim_open_win(b.bufnr, true, { 98 | relative = "editor", 99 | width = width, 100 | height = height, 101 | col = math.floor( 102 | (vim.api.nvim_get_option_value("columns", {}) - width) / 2 103 | ), 104 | row = math.floor( 105 | (vim.api.nvim_get_option_value("lines", {}) - height) / 2 106 | ), 107 | border = "rounded", 108 | style = "minimal", 109 | }) 110 | 111 | -- Highlight current line 112 | vim.api.nvim_set_option_value("cursorline", true, { win = win_id }) 113 | 114 | return win_id 115 | end 116 | 117 | --- Set key mappings for the popup window 118 | ---@param b YankBankBufData 119 | function M.set_keymaps(b) 120 | -- key mappings for selection and closing the popup 121 | local map_opts = { noremap = true, silent = true, buffer = b.bufnr } 122 | 123 | -- popup buffer navigation binds 124 | if YB_OPTS.num_behavior == "prefix" then 125 | vim.keymap.set("n", YB_OPTS.keymaps.navigation_next, function() 126 | local count = vim.v.count1 > 0 and vim.v.count1 or 1 127 | helpers.next_numbered_item(count) 128 | return "" 129 | end, { noremap = true, silent = true, buffer = b.bufnr }) 130 | vim.keymap.set("n", YB_OPTS.keymaps.navigation_prev, function() 131 | local count = vim.v.count1 > 0 and vim.v.count1 or 1 132 | helpers.prev_numbered_item(count) 133 | return "" 134 | end, map_opts) 135 | else 136 | vim.keymap.set( 137 | "n", 138 | YB_OPTS.keymaps.navigation_next, 139 | helpers.next_numbered_item, 140 | map_opts 141 | ) 142 | vim.keymap.set( 143 | "n", 144 | YB_OPTS.keymaps.navigation_prev, 145 | helpers.prev_numbered_item, 146 | map_opts 147 | ) 148 | end 149 | 150 | -- map number keys to jump to entry if num_behavior is 'jump' 151 | if YB_OPTS.num_behavior == "jump" then 152 | for i = 1, YB_OPTS.max_entries do 153 | vim.keymap.set("n", tostring(i), function() 154 | local target_line = nil 155 | for line_num, yank_num in pairs(b.line_yank_map) do 156 | if yank_num == i then 157 | target_line = line_num 158 | break 159 | end 160 | end 161 | if target_line then 162 | vim.api.nvim_win_set_cursor(b.win_id, { target_line, 0 }) 163 | end 164 | end, map_opts) 165 | end 166 | end 167 | 168 | -- bind paste behavior 169 | vim.keymap.set("n", YB_OPTS.keymaps.paste, function() 170 | local cursor = vim.api.nvim_win_get_cursor(b.win_id)[1] 171 | -- use the mapping to find the original yank 172 | local yankIndex = b.line_yank_map[cursor] 173 | if yankIndex then 174 | -- close window upon selection 175 | vim.api.nvim_win_close(b.win_id, true) 176 | helpers.smart_paste( 177 | YB_YANKS[yankIndex], 178 | YB_REG_TYPES[yankIndex], 179 | true 180 | ) 181 | else 182 | print("Error: Invalid selection") 183 | end 184 | end, map_opts) 185 | -- paste backwards 186 | vim.keymap.set("n", YB_OPTS.keymaps.paste_back, function() 187 | local cursor = vim.api.nvim_win_get_cursor(b.win_id)[1] 188 | -- use the mapping to find the original yank 189 | local yankIndex = b.line_yank_map[cursor] 190 | if yankIndex then 191 | -- close window upon selection 192 | vim.api.nvim_win_close(b.win_id, true) 193 | helpers.smart_paste( 194 | YB_YANKS[yankIndex], 195 | YB_REG_TYPES[yankIndex], 196 | false 197 | ) 198 | else 199 | print("Error: Invalid selection") 200 | end 201 | end, map_opts) 202 | 203 | -- bind yank behavior 204 | vim.keymap.set("n", YB_OPTS.keymaps.yank, function() 205 | local cursor = vim.api.nvim_win_get_cursor(b.win_id)[1] 206 | local yankIndex = b.line_yank_map[cursor] 207 | if yankIndex then 208 | vim.fn.setreg(YB_OPTS.registers.yank_register, YB_YANKS[yankIndex]) 209 | vim.api.nvim_win_close(b.win_id, true) 210 | end 211 | end, map_opts) 212 | 213 | -- close popup keybinds 214 | -- REFACTOR: check if close keybind is string, handle differently 215 | for _, map in ipairs(YB_OPTS.keymaps.close) do 216 | vim.keymap.set("n", map, function() 217 | vim.api.nvim_win_close(b.win_id, true) 218 | end, map_opts) 219 | end 220 | end 221 | 222 | return M 223 | -------------------------------------------------------------------------------- /lua/yankbank/persistence.lua: -------------------------------------------------------------------------------- 1 | local M = {} 2 | 3 | local persistence = {} 4 | 5 | ---add entry from bank to 6 | ---@param entry string 7 | ---@param reg_type string 8 | ---@param pin integer|boolean? 9 | function M.add_entry(entry, reg_type, pin) 10 | if YB_OPTS.persist_type == "sqlite" then 11 | persistence:insert_yank(entry, reg_type, pin) 12 | end 13 | end 14 | 15 | --- get current state of yanks in persistent storage 16 | function M.get_yanks() 17 | if YB_OPTS.persist_type == "sqlite" then 18 | return persistence:get_bank() 19 | end 20 | end 21 | 22 | ---initialize bank persistence 23 | ---@return table 24 | ---@return table 25 | ---@return table 26 | function M.setup() 27 | if not YB_OPTS.persist_type then 28 | return {}, {}, {} 29 | elseif YB_OPTS.persist_type == "sqlite" then 30 | persistence = require("yankbank.persistence.sql").setup() 31 | return persistence:get_bank() 32 | else 33 | return {}, {}, {} 34 | end 35 | end 36 | 37 | return M 38 | -------------------------------------------------------------------------------- /lua/yankbank/persistence/sql.lua: -------------------------------------------------------------------------------- 1 | local M = {} 2 | 3 | local sqlite = require("sqlite") 4 | 5 | local dbdir = YB_OPTS.db_path 6 | or debug.getinfo(1).source:sub(2):match("(.*/).*/.*/.*/") 7 | or "./" 8 | -- or vim.fn.stdpath("data") 9 | local max_entries = 10 10 | 11 | ---@class YankBankDB:sqlite_db 12 | ---@field bank sqlite_tbl 13 | ---@field bank sqlite_tbl 14 | local db = sqlite({ 15 | uri = dbdir .. "/yankbank.db", 16 | bank = { 17 | -- yanked text should be unique and be primary key 18 | yank_text = { "text", unique = true, primary = true, required = true }, 19 | reg_type = { "text", required = true }, 20 | pinned = { "integer", required = true, default = 0 }, 21 | }, 22 | }) 23 | 24 | ---@class sqlite_tbl 25 | local data = db.bank 26 | 27 | ---@param content string 28 | ---@return string 29 | function M.escape(content) 30 | return string.format("__ESCAPED__'%s'", content) 31 | end 32 | 33 | ---@param content string 34 | ---@return string 35 | ---@return integer? 36 | function M.unescape(content) 37 | return content:gsub("^__ESCAPED__'(.*)'$", "%1") 38 | end 39 | 40 | --- insert yank entry into database 41 | ---@param yank_text string yanked text 42 | ---@param reg_type string register type 43 | ---@param pin integer|boolean? pin status of inserted entry 44 | function data:insert_yank(yank_text, reg_type, pin) 45 | -- attempt to remove entry if count > 0 (to move potential duplicate) 46 | local is_pinned = 0 47 | if self:count() > 0 then 48 | db:with_open(function() 49 | -- check if entry exists in db 50 | local res = db:eval( 51 | "SELECT * FROM bank WHERE yank_text = :yank_text and reg_type = :reg_type", 52 | { yank_text = M.escape(yank_text), reg_type = reg_type } 53 | ) 54 | 55 | -- if result is empty (eval returns boolean), proceed to insertion 56 | if type(res) == "boolean" then 57 | return 58 | end 59 | 60 | -- entry found, get pin status 61 | is_pinned = res[1].pinned 62 | 63 | -- remove entry from db so it can be moved to first position 64 | db:eval( 65 | "DELETE FROM bank WHERE yank_text = :yank_text and reg_type = :reg_type", 66 | { yank_text = M.escape(yank_text), reg_type = reg_type } 67 | ) 68 | end) 69 | end 70 | 71 | -- override is_pinned if pin param is set, default to is_pinned otherwise 72 | is_pinned = (pin == 1 or pin == true) and 1 73 | or (pin == 0 or pin == false) and 0 74 | or is_pinned 75 | 76 | -- insert entry using the eval method with parameterized query to avoid error on 'data:insert()' 77 | db:with_open(function() 78 | db:eval( 79 | "INSERT INTO bank (yank_text, reg_type, pinned) VALUES (:yank_text, :reg_type, :pinned)", 80 | { 81 | yank_text = M.escape(yank_text), 82 | reg_type = reg_type, 83 | pinned = is_pinned, 84 | } 85 | ) 86 | end) 87 | 88 | -- attempt to trim database size 89 | self:trim_size() 90 | end 91 | 92 | --- trim database size if it exceeds max_entries option 93 | --- WARN: if all entries are pinned, behavior is undefined 94 | function data:trim_size() 95 | if self:count() > max_entries then 96 | -- remove the oldest entry 97 | local e = db:with_open(function() 98 | return db:select("bank", { 99 | where = { pinned = 0 }, 100 | order_by = { asc = "rowid" }, 101 | limit = 1, 102 | })[1] 103 | end) 104 | 105 | if e then 106 | db:with_open(function() 107 | db:eval( 108 | "DELETE FROM bank WHERE yank_text = :yank_text", 109 | { yank_text = e.yank_text } 110 | ) 111 | end) 112 | end 113 | end 114 | end 115 | 116 | --- get sqlite bank contents 117 | ---@return table yanks, table reg_types, table pins 118 | function data:get_bank() 119 | local yanks, reg_types, pins = {}, {}, {} 120 | 121 | local bank = self:get() 122 | for _, entry in ipairs(bank) do 123 | local text, _ = M.unescape(entry.yank_text) 124 | table.insert(yanks, 1, text) 125 | table.insert(reg_types, 1, entry.reg_type) 126 | table.insert(pins, 1, entry.pinned) 127 | end 128 | 129 | return yanks, reg_types, pins 130 | end 131 | 132 | --- remove an entry from the banks table matching input text 133 | ---@param text string 134 | ---@param reg_type string 135 | function data.remove_match(text, reg_type) 136 | db:with_open(function() 137 | return db:eval( 138 | "DELETE FROM bank WHERE yank_text = :yank_text and reg_type = :reg_type", 139 | { yank_text = M.escape(text), reg_type = reg_type } 140 | ) 141 | end) 142 | end 143 | 144 | --- pin entry in yankbank to prevent removal 145 | ---@param text string text to match and pin 146 | ---@param reg_type string reg_type corresponding to text 147 | ---@return boolean? 148 | function data.pin(text, reg_type) 149 | return db:with_open(function() 150 | return ( 151 | db:eval( 152 | "UPDATE bank SET pinned = 1 WHERE yank_text = :yank_text and reg_type = :reg_type", 153 | { yank_text = M.escape(text), reg_type = reg_type } 154 | ) 155 | ) 156 | end) 157 | end 158 | 159 | --- unpin entry in yankbank to prevent removal 160 | ---@param text string 161 | ---@param reg_type string reg_type corresponding to text 162 | ---@return boolean? 163 | function data.unpin(text, reg_type) 164 | return db:with_open(function() 165 | return db:eval( 166 | "UPDATE bank SET pinned = 0 WHERE yank_text = :yank_text and reg_type = :reg_type", 167 | { yank_text = M.escape(text), reg_type = reg_type } 168 | ) 169 | end) 170 | end 171 | 172 | --- get data in sqlite_tbl form (for api use only) 173 | ---@return sqlite_tbl 174 | function M.data() 175 | return data 176 | end 177 | 178 | --- set up database persistence 179 | ---@return sqlite_tbl data 180 | function M.setup() 181 | max_entries = YB_OPTS.max_entries 182 | 183 | vim.api.nvim_create_user_command("YankBankClearDB", function() 184 | data:drop() 185 | YB_YANKS = {} 186 | YB_REG_TYPES = {} 187 | end, {}) 188 | 189 | if YB_OPTS.debug == true then 190 | vim.api.nvim_create_user_command("YankBankViewDB", function() 191 | print(vim.inspect(data:get())) 192 | end, {}) 193 | end 194 | 195 | return data 196 | end 197 | 198 | return M 199 | -------------------------------------------------------------------------------- /lua/yankbank/utils.lua: -------------------------------------------------------------------------------- 1 | local M = {} 2 | 3 | --- get the last zero entry in a table 4 | --- 5 | ---@param t table 6 | ---@return integer? 7 | function M.last_zero_entry(t) 8 | for i = #t, 1, -1 do 9 | if t[i] == 0 then 10 | return i 11 | end 12 | end 13 | return nil 14 | end 15 | 16 | return M 17 | --------------------------------------------------------------------------------