├── LICENSE ├── README.md ├── doc └── mini-cursorword.txt └── lua └── mini └── cursorword.lua /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Evgeni Chasnovski 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | [![GitHub license](https://badgen.net/github/license/echasnovski/mini.nvim)](https://github.com/echasnovski/mini.nvim/blob/main/LICENSE) 5 | 6 | 7 | ### Automatic highlighting of word under cursor 8 | 9 | See more details in [Features](#features) and [help file](doc/mini-cursorword.txt). 10 | 11 | --- 12 | 13 | ⦿ This is a part of [mini.nvim](https://github.com/echasnovski/mini.nvim) library. Please use [this link](https://github.com/echasnovski/mini.nvim/blob/main/README.md) if you want to mention this module. 14 | 15 | ⦿ All contributions (issues, pull requests, discussions, etc.) are done inside of 'mini.nvim'. 16 | 17 | ⦿ See the repository page to learn about common design principles and configuration recipes. 18 | 19 | --- 20 | 21 | If you want to help this project grow but don't know where to start, check out [contributing guides of 'mini.nvim'](https://github.com/echasnovski/mini.nvim/blob/main/CONTRIBUTING.md) or leave a Github star for 'mini.nvim' project and/or any its standalone Git repositories. 22 | 23 | ## Demo 24 | 25 | https://user-images.githubusercontent.com/24854248/173044454-0e4ab873-6e73-448d-838f-45f4b2be876b.mp4 26 | 27 | ## Features 28 | 29 | - Current word under cursor can be highlighted differently. 30 | - "Word under cursor" is meant as in Vim's ``: something user would get as 'iw' text object. 31 | - Highlighting stops in insert and terminal modes. 32 | 33 | ## Installation 34 | 35 | This plugin can be installed as part of 'mini.nvim' library (**recommended**) or as a standalone Git repository. 36 | 37 | There are two branches to install from: 38 | 39 | - `main` (default, **recommended**) will have latest development version of plugin. All changes since last stable release should be perceived as being in beta testing phase (meaning they already passed alpha-testing and are moderately settled). 40 | - `stable` will be updated only upon releases with code tested during public beta-testing phase in `main` branch. 41 | 42 | Here are code snippets for some common installation methods (use only one): 43 | 44 |
45 | With mini.deps 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 |
Github repoBranch Code snippet
'mini.nvim' library Main Follow recommended 'mini.deps' installation
Stable
Standalone plugin Main add('echasnovski/mini.cursorword')
Stable add({ source = 'echasnovski/mini.cursorword', checkout = 'stable' })
68 |
69 | 70 |
71 | With folke/lazy.nvim 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 |
Github repoBranch Code snippet
'mini.nvim' libraryMain { 'echasnovski/mini.nvim', version = false },
Stable { 'echasnovski/mini.nvim', version = '*' },
Standalone pluginMain { 'echasnovski/mini.cursorword', version = false },
Stable { 'echasnovski/mini.cursorword', version = '*' },
96 |
97 | 98 |
99 | With junegunn/vim-plug 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 |
Github repoBranch Code snippet
'mini.nvim' libraryMain Plug 'echasnovski/mini.nvim'
Stable Plug 'echasnovski/mini.nvim', { 'branch': 'stable' }
Standalone plugin Main Plug 'echasnovski/mini.cursorword'
Stable Plug 'echasnovski/mini.cursorword', { 'branch': 'stable' }
123 |
124 | 125 |
126 | 127 | **Important**: don't forget to call `require('mini.cursorword').setup()` to enable its functionality. 128 | 129 | **Note**: if you are on Windows, there might be problems with too long file paths (like `error: unable to create file : Filename too long`). Try doing one of the following: 130 | - Enable corresponding git global config value: `git config --system core.longpaths true`. Then try to reinstall. 131 | - Install plugin in other place with shorter path. 132 | 133 | ## Default config 134 | 135 | ```lua 136 | -- No need to copy this inside `setup()`. Will be used automatically. 137 | { 138 | -- Delay (in ms) between when cursor moved and when highlighting appeared 139 | delay = 100, 140 | } 141 | ``` 142 | 143 | ## Similar plugins 144 | 145 | - [RRethy/vim-illuminate](https://github.com/RRethy/vim-illuminate) 146 | - [itchyny/vim-cursorword](https://github.com/itchyny/vim-cursorword) 147 | -------------------------------------------------------------------------------- /doc/mini-cursorword.txt: -------------------------------------------------------------------------------- 1 | *mini.cursorword* Autohighlight word under cursor 2 | *MiniCursorword* 3 | 4 | MIT License Copyright (c) 2021 Evgeni Chasnovski 5 | 6 | ============================================================================== 7 | 8 | Features: 9 | - Autohighlight word under cursor with customizable delay. 10 | 11 | - Current word under cursor can be highlighted differently. 12 | 13 | - Highlighting is triggered only if current cursor character is a |[:keyword:]|. 14 | 15 | - Highlighting stops in insert and terminal modes. 16 | 17 | - "Word under cursor" is meant as in Vim's ||: something user would 18 | get as 'iw' text object. 19 | 20 | # Setup ~ 21 | 22 | This module needs a setup with `require('mini.cursorword').setup({})` 23 | (replace `{}` with your `config` table). It will create global Lua table 24 | `MiniCursorword` which you can use for scripting or manually (with 25 | `:lua MiniCursorword.*`). 26 | 27 | See |MiniCursorword.config| for `config` structure and default values. 28 | 29 | You can override runtime config settings locally to buffer inside 30 | `vim.b.minicursorword_config` which should have same structure as 31 | `MiniCursorword.config`. See |mini.nvim-buffer-local-config| for more details. 32 | 33 | # Highlight groups ~ 34 | 35 | * `MiniCursorword` - highlight group of a non-current cursor word. 36 | Default: plain underline. 37 | 38 | * `MiniCursorwordCurrent` - highlight group of a current word under cursor. 39 | Default: links to `MiniCursorword` (so `:hi clear MiniCursorwordCurrent` 40 | will lead to showing `MiniCursorword` highlight group). 41 | Note: To not highlight it, use the following Lua code: >lua 42 | 43 | vim.api.nvim_set_hl(0, 'MiniCursorwordCurrent', {}) 44 | < 45 | To change any highlight group, modify it directly with |:highlight|. 46 | 47 | # Disabling ~ 48 | 49 | To disable core functionality, set `vim.g.minicursorword_disable` (globally) or 50 | `vim.b.minicursorword_disable` (for a buffer) to `true`. Considering high 51 | number of different scenarios and customization intentions, writing exact 52 | rules for disabling module's functionality is left to user. See 53 | |mini.nvim-disabling-recipes| for common recipes. Note: after disabling 54 | there might be highlighting left; it will be removed after next 55 | highlighting update. 56 | 57 | Module-specific disabling: 58 | - Don't show highlighting if cursor is on the word that is in a blocklist 59 | of current filetype. In this example, blocklist for "lua" is "local" and 60 | "require" words, for "javascript" - "import": >lua 61 | 62 | _G.cursorword_blocklist = function() 63 | local curword = vim.fn.expand('') 64 | local filetype = vim.bo.filetype 65 | 66 | -- Add any disabling global or filetype-specific logic here 67 | local blocklist = {} 68 | if filetype == 'lua' then 69 | blocklist = { 'local', 'require' } 70 | elseif filetype == 'javascript' then 71 | blocklist = { 'import' } 72 | end 73 | 74 | vim.b.minicursorword_disable = vim.tbl_contains(blocklist, curword) 75 | end 76 | 77 | -- Make sure to add this autocommand *before* calling module's `setup()`. 78 | vim.cmd('au CursorMoved * lua _G.cursorword_blocklist()') 79 | < 80 | ------------------------------------------------------------------------------ 81 | *MiniCursorword.setup()* 82 | `MiniCursorword.setup`({config}) 83 | Module setup 84 | 85 | Parameters ~ 86 | {config} `(table|nil)` Module config table. See |MiniCursorword.config|. 87 | 88 | Usage ~ 89 | >lua 90 | require('mini.cursorword').setup() -- use default config 91 | -- OR 92 | require('mini.cursorword').setup({}) -- replace {} with your config table 93 | < 94 | ------------------------------------------------------------------------------ 95 | *MiniCursorword.config* 96 | `MiniCursorword.config` 97 | Module config 98 | 99 | Default values: 100 | >lua 101 | MiniCursorword.config = { 102 | -- Delay (in ms) between when cursor moved and when highlighting appeared 103 | delay = 100, 104 | } 105 | < 106 | 107 | vim:tw=78:ts=8:noet:ft=help:norl: -------------------------------------------------------------------------------- /lua/mini/cursorword.lua: -------------------------------------------------------------------------------- 1 | --- *mini.cursorword* Autohighlight word under cursor 2 | --- *MiniCursorword* 3 | --- 4 | --- MIT License Copyright (c) 2021 Evgeni Chasnovski 5 | --- 6 | --- ============================================================================== 7 | --- 8 | --- Features: 9 | --- - Autohighlight word under cursor with customizable delay. 10 | --- 11 | --- - Current word under cursor can be highlighted differently. 12 | --- 13 | --- - Highlighting is triggered only if current cursor character is a |[:keyword:]|. 14 | --- 15 | --- - Highlighting stops in insert and terminal modes. 16 | --- 17 | --- - "Word under cursor" is meant as in Vim's ||: something user would 18 | --- get as 'iw' text object. 19 | --- 20 | --- # Setup ~ 21 | --- 22 | --- This module needs a setup with `require('mini.cursorword').setup({})` 23 | --- (replace `{}` with your `config` table). It will create global Lua table 24 | --- `MiniCursorword` which you can use for scripting or manually (with 25 | --- `:lua MiniCursorword.*`). 26 | --- 27 | --- See |MiniCursorword.config| for `config` structure and default values. 28 | --- 29 | --- You can override runtime config settings locally to buffer inside 30 | --- `vim.b.minicursorword_config` which should have same structure as 31 | --- `MiniCursorword.config`. See |mini.nvim-buffer-local-config| for more details. 32 | --- 33 | --- # Highlight groups ~ 34 | --- 35 | --- * `MiniCursorword` - highlight group of a non-current cursor word. 36 | --- Default: plain underline. 37 | --- 38 | --- * `MiniCursorwordCurrent` - highlight group of a current word under cursor. 39 | --- Default: links to `MiniCursorword` (so `:hi clear MiniCursorwordCurrent` 40 | --- will lead to showing `MiniCursorword` highlight group). 41 | --- Note: To not highlight it, use the following Lua code: >lua 42 | --- 43 | --- vim.api.nvim_set_hl(0, 'MiniCursorwordCurrent', {}) 44 | --- < 45 | --- To change any highlight group, modify it directly with |:highlight|. 46 | --- 47 | --- # Disabling ~ 48 | --- 49 | --- To disable core functionality, set `vim.g.minicursorword_disable` (globally) or 50 | --- `vim.b.minicursorword_disable` (for a buffer) to `true`. Considering high 51 | --- number of different scenarios and customization intentions, writing exact 52 | --- rules for disabling module's functionality is left to user. See 53 | --- |mini.nvim-disabling-recipes| for common recipes. Note: after disabling 54 | --- there might be highlighting left; it will be removed after next 55 | --- highlighting update. 56 | --- 57 | --- Module-specific disabling: 58 | --- - Don't show highlighting if cursor is on the word that is in a blocklist 59 | --- of current filetype. In this example, blocklist for "lua" is "local" and 60 | --- "require" words, for "javascript" - "import": >lua 61 | --- 62 | --- _G.cursorword_blocklist = function() 63 | --- local curword = vim.fn.expand('') 64 | --- local filetype = vim.bo.filetype 65 | --- 66 | --- -- Add any disabling global or filetype-specific logic here 67 | --- local blocklist = {} 68 | --- if filetype == 'lua' then 69 | --- blocklist = { 'local', 'require' } 70 | --- elseif filetype == 'javascript' then 71 | --- blocklist = { 'import' } 72 | --- end 73 | --- 74 | --- vim.b.minicursorword_disable = vim.tbl_contains(blocklist, curword) 75 | --- end 76 | --- 77 | --- -- Make sure to add this autocommand *before* calling module's `setup()`. 78 | --- vim.cmd('au CursorMoved * lua _G.cursorword_blocklist()') 79 | --- < 80 | 81 | -- Module definition ========================================================== 82 | local MiniCursorword = {} 83 | local H = {} 84 | 85 | --- Module setup 86 | --- 87 | ---@param config table|nil Module config table. See |MiniCursorword.config|. 88 | --- 89 | ---@usage >lua 90 | --- require('mini.cursorword').setup() -- use default config 91 | --- -- OR 92 | --- require('mini.cursorword').setup({}) -- replace {} with your config table 93 | --- < 94 | MiniCursorword.setup = function(config) 95 | -- TODO: Remove after Neovim=0.8 support is dropped 96 | if vim.fn.has('nvim-0.9') == 0 then 97 | vim.notify( 98 | '(mini.cursorword) Neovim<0.9 is soft deprecated (module works but not supported).' 99 | .. ' It will be deprecated after next "mini.nvim" release (module might not work).' 100 | .. ' Please update your Neovim version.' 101 | ) 102 | end 103 | 104 | -- Export module 105 | _G.MiniCursorword = MiniCursorword 106 | 107 | -- Setup config 108 | config = H.setup_config(config) 109 | 110 | -- Apply config 111 | H.apply_config(config) 112 | 113 | -- Define behavior 114 | H.create_autocommands() 115 | 116 | -- Create default highlighting 117 | H.create_default_hl() 118 | end 119 | 120 | --- Module config 121 | --- 122 | --- Default values: 123 | ---@eval return MiniDoc.afterlines_to_code(MiniDoc.current.eval_section) 124 | MiniCursorword.config = { 125 | -- Delay (in ms) between when cursor moved and when highlighting appeared 126 | delay = 100, 127 | } 128 | --minidoc_afterlines_end 129 | 130 | -- Module functionality ======================================================= 131 | 132 | -- Helper data ================================================================ 133 | -- Module default config 134 | H.default_config = vim.deepcopy(MiniCursorword.config) 135 | 136 | -- Delay timer 137 | H.timer = vim.loop.new_timer() 138 | 139 | -- Information about last match highlighting (stored *per window*): 140 | -- - Key: windows' unique buffer identifiers. 141 | -- - Value: table with: 142 | -- - `id` field for match id (from `vim.fn.matchadd()`). 143 | -- - `word` field for matched word. 144 | H.window_matches = {} 145 | 146 | -- Helper functionality ======================================================= 147 | -- Settings ------------------------------------------------------------------- 148 | H.setup_config = function(config) 149 | H.check_type('config', config, 'table', true) 150 | config = vim.tbl_deep_extend('force', vim.deepcopy(H.default_config), config or {}) 151 | 152 | H.check_type('delay', config.delay, 'number') 153 | 154 | return config 155 | end 156 | 157 | H.apply_config = function(config) 158 | MiniCursorword.config = config 159 | 160 | -- Make `setup()` to proper reset module 161 | for _, m in ipairs(vim.fn.getmatches()) do 162 | if vim.startswith(m.group, 'MiniCursorword') then vim.fn.matchdelete(m.id) end 163 | end 164 | end 165 | 166 | H.create_autocommands = function() 167 | local gr = vim.api.nvim_create_augroup('MiniCursorword', {}) 168 | 169 | local au = function(event, pattern, callback, desc) 170 | vim.api.nvim_create_autocmd(event, { group = gr, pattern = pattern, callback = callback, desc = desc }) 171 | end 172 | 173 | au('CursorMoved', '*', H.auto_highlight, 'Auto highlight cursorword') 174 | au({ 'InsertEnter', 'TermEnter', 'QuitPre' }, '*', H.auto_unhighlight, 'Auto unhighlight cursorword') 175 | au('ModeChanged', '*:[^i]', H.auto_highlight, 'Auto highlight cursorword') 176 | 177 | au('ColorScheme', '*', H.create_default_hl, 'Ensure colors') 178 | au('FileType', 'TelescopePrompt', function() vim.b.minicursorword_disable = true end, 'Disable locally') 179 | end 180 | 181 | --stylua: ignore 182 | H.create_default_hl = function() 183 | vim.api.nvim_set_hl(0, 'MiniCursorword', { default = true, underline = true }) 184 | vim.api.nvim_set_hl(0, 'MiniCursorwordCurrent', { default = true, link = 'MiniCursorword' }) 185 | end 186 | 187 | H.is_disabled = function() return vim.g.minicursorword_disable == true or vim.b.minicursorword_disable == true end 188 | 189 | H.get_config = function(config) 190 | return vim.tbl_deep_extend('force', MiniCursorword.config, vim.b.minicursorword_config or {}, config or {}) 191 | end 192 | 193 | -- Autocommands --------------------------------------------------------------- 194 | H.auto_highlight = function() 195 | -- Stop any possible previous delayed highlighting 196 | H.timer:stop() 197 | 198 | -- Stop highlighting immediately if module is disabled when cursor is not on 199 | -- 'keyword' 200 | if not H.should_highlight() then return H.unhighlight() end 201 | 202 | -- Get current information 203 | local win_id = vim.api.nvim_get_current_win() 204 | local win_match = H.window_matches[win_id] or {} 205 | local curword = H.get_cursor_word() 206 | 207 | -- Only immediately update highlighting of current word under cursor if 208 | -- currently highlighted word equals one under cursor 209 | if win_match.word == curword then 210 | H.unhighlight(true) 211 | H.highlight(true) 212 | return 213 | end 214 | 215 | -- Stop highlighting previous match (if it exists) 216 | H.unhighlight() 217 | 218 | -- Delay highlighting 219 | H.timer:start( 220 | H.get_config().delay, 221 | 0, 222 | vim.schedule_wrap(function() 223 | -- Ensure that always only one word is highlighted 224 | H.unhighlight() 225 | H.highlight() 226 | end) 227 | ) 228 | end 229 | 230 | H.auto_unhighlight = function() 231 | -- Stop any possible previous delayed highlighting 232 | H.timer:stop() 233 | H.unhighlight() 234 | end 235 | 236 | -- Highlighting --------------------------------------------------------------- 237 | ---@param only_current boolean|nil Whether to forcefully highlight only current word 238 | --- under cursor. 239 | ---@private 240 | H.highlight = function(only_current) 241 | -- A modified version of https://stackoverflow.com/a/25233145 242 | -- Using `matchadd()` instead of a simpler `:match` to tweak priority of 243 | -- 'current word' highlighting: with `:match` it is higher than for 244 | -- `incsearch` which is not convenient. 245 | local win_id = vim.api.nvim_get_current_win() 246 | if not vim.api.nvim_win_is_valid(win_id) then return end 247 | 248 | if not H.should_highlight() then return end 249 | 250 | H.window_matches[win_id] = H.window_matches[win_id] or {} 251 | 252 | -- Add match highlight for current word under cursor 253 | local current_word_pattern = [[\k*\%#\k*]] 254 | local match_id_current = vim.fn.matchadd('MiniCursorwordCurrent', current_word_pattern, -1) 255 | H.window_matches[win_id].id_current = match_id_current 256 | 257 | -- Don't add main match id if not needed or if one is already present 258 | if only_current or H.window_matches[win_id].id ~= nil then return end 259 | 260 | -- Add match highlight for non-current word under cursor. NOTEs: 261 | -- - Using `\(...\)\@!` allows to not match current word. 262 | -- - Using 'very nomagic' ('\V') allows not escaping. 263 | -- - Using `\<` and `\>` matches whole word (and not as part). 264 | local curword = H.get_cursor_word() 265 | local pattern = string.format([[\(%s\)\@!\&\V\<%s\>]], current_word_pattern, curword) 266 | local match_id = vim.fn.matchadd('MiniCursorword', pattern, -1) 267 | 268 | -- Store information about highlight 269 | H.window_matches[win_id].id = match_id 270 | H.window_matches[win_id].word = curword 271 | end 272 | 273 | ---@param only_current boolean|nil Whether to remove highlighting only of current 274 | --- word under cursor. 275 | ---@private 276 | H.unhighlight = function(only_current) 277 | -- Don't do anything if there is no valid information to act upon 278 | local win_id = vim.api.nvim_get_current_win() 279 | local win_match = H.window_matches[win_id] 280 | if not vim.api.nvim_win_is_valid(win_id) or win_match == nil then return end 281 | 282 | -- Use `pcall` because there is an error if match id is not present. It can 283 | -- happen if something else called `clearmatches`. 284 | pcall(vim.fn.matchdelete, win_match.id_current) 285 | H.window_matches[win_id].id_current = nil 286 | 287 | if not only_current then 288 | pcall(vim.fn.matchdelete, win_match.id) 289 | H.window_matches[win_id] = nil 290 | end 291 | end 292 | 293 | H.should_highlight = function() return not H.is_disabled() and H.is_cursor_on_keyword() end 294 | 295 | -- Utilities ------------------------------------------------------------------ 296 | H.error = function(msg) error('(mini.cursorword) ' .. msg, 0) end 297 | 298 | H.check_type = function(name, val, ref, allow_nil) 299 | if type(val) == ref or (ref == 'callable' and vim.is_callable(val)) or (allow_nil and val == nil) then return end 300 | H.error(string.format('`%s` should be %s, not %s', name, ref, type(val))) 301 | end 302 | 303 | H.is_cursor_on_keyword = function() 304 | local col = vim.fn.col('.') 305 | local curchar = vim.api.nvim_get_current_line():sub(col, col) 306 | 307 | -- Use `pcall()` to catch `E5108` (can happen in binary files, see #112) 308 | local ok, match_res = pcall(vim.fn.match, curchar, '[[:keyword:]]') 309 | return ok and match_res >= 0 310 | end 311 | 312 | H.get_cursor_word = function() return vim.fn.escape(vim.fn.expand(''), [[\/]]) end 313 | 314 | return MiniCursorword 315 | --------------------------------------------------------------------------------