├── LICENSE ├── README.md ├── doc └── mini-trailspace.txt └── lua └── mini └── trailspace.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 |

mini.trailspace

2 | 3 | ### Work with trailing whitespace 4 | 5 | See more details in [Features](#features) and [Documentation](doc/mini-trailspace.txt). 6 | 7 | --- 8 | 9 | > [!NOTE] 10 | > This was previously hosted at a personal `echasnovski` GitHub account. It was transferred to a dedicated organization to improve long term project stability. See more details [here](https://github.com/nvim-mini/mini.nvim/discussions/1970). 11 | 12 | ⦿ This is a part of [mini.nvim](https://github.com/nvim-mini/mini.nvim) library. Please use [this link](https://github.com/nvim-mini/mini.nvim/blob/main/readmes/mini-trailspace.md) if you want to mention this module. 13 | 14 | ⦿ All contributions (issues, pull requests, discussions, etc.) are done inside of 'mini.nvim'. 15 | 16 | ⦿ See the repository page to learn about common design principles and configuration recipes. 17 | 18 | --- 19 | 20 | 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/nvim-mini/mini.nvim/blob/main/CONTRIBUTING.md) or leave a Github star for 'mini.nvim' project and/or any its standalone Git repositories. 21 | 22 | ## Demo 23 | 24 | 25 | https://user-images.githubusercontent.com/24854248/173045420-7aaf21b6-1d2e-4333-8a23-dea7e49c3a01.mp4 26 | 27 | ## Features 28 | 29 | - Highlighting is done only in modifiable buffer by default, only in Normal mode, and stops in Insert mode and when leaving window. 30 | - Trim all trailing whitespace with `MiniTrailspace.trim()`. 31 | - Trim all trailing empty lines with `MiniTrailspace.trim_last_lines()`. 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 | - 'mini.nvim' library: 48 | 49 | | Branch | Code snippet | 50 | |--------|-----------------------------------------------| 51 | | Main | *Follow recommended ‘mini.deps’ installation* | 52 | | Stable | *Follow recommended ‘mini.deps’ installation* | 53 | 54 | - Standalone plugin: 55 | 56 | | Branch | Code snippet | 57 | |--------|----------------------------------------------------------------------| 58 | | Main | `add(‘nvim-mini/mini.trailspace’)` | 59 | | Stable | `add({ source = ‘nvim-mini/mini.trailspace’, checkout = ‘stable’ })` | 60 | 61 |
62 | 63 |
64 | With folke/lazy.nvim 65 | 66 | - 'mini.nvim' library: 67 | 68 | | Branch | Code snippet | 69 | |--------|-----------------------------------------------| 70 | | Main | `{ 'nvim-mini/mini.nvim', version = false },` | 71 | | Stable | `{ 'nvim-mini/mini.nvim', version = '*' },` | 72 | 73 | - Standalone plugin: 74 | 75 | | Branch | Code snippet | 76 | |--------|-----------------------------------------------------| 77 | | Main | `{ 'nvim-mini/mini.trailspace', version = false },` | 78 | | Stable | `{ 'nvim-mini/mini.trailspace', version = '*' },` | 79 | 80 |
81 | 82 |
83 | With junegunn/vim-plug 84 | 85 | - 'mini.nvim' library: 86 | 87 | | Branch | Code snippet | 88 | |--------|------------------------------------------------------| 89 | | Main | `Plug 'nvim-mini/mini.nvim'` | 90 | | Stable | `Plug 'nvim-mini/mini.nvim', { 'branch': 'stable' }` | 91 | 92 | - Standalone plugin: 93 | 94 | | Branch | Code snippet | 95 | |--------|------------------------------------------------------------| 96 | | Main | `Plug 'nvim-mini/mini.trailspace'` | 97 | | Stable | `Plug 'nvim-mini/mini.trailspace', { 'branch': 'stable' }` | 98 | 99 |
100 | 101 | **Important**: don't forget to call `require('mini.trailspace').setup()` to enable its functionality. 102 | 103 | **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: 104 | 105 | - Enable corresponding git global config value: `git config --system core.longpaths true`. Then try to reinstall. 106 | - Install plugin in other place with shorter path. 107 | 108 | ## Default config 109 | 110 | ```lua 111 | -- No need to copy this inside `setup()`. Will be used automatically. 112 | { 113 | -- Highlight only in normal buffers (ones with empty 'buftype'). This is 114 | -- useful to not show trailing whitespace where it usually doesn't matter. 115 | only_in_normal_buffers = true, 116 | } 117 | ``` 118 | 119 | ## Similar plugins 120 | 121 | - [ntpeters/vim-better-whitespace](https://github.com/ntpeters/vim-better-whitespace) 122 | -------------------------------------------------------------------------------- /doc/mini-trailspace.txt: -------------------------------------------------------------------------------- 1 | *mini.trailspace* Trailspace (highlight and remove) 2 | 3 | MIT License Copyright (c) 2021 Evgeni Chasnovski 4 | 5 | ------------------------------------------------------------------------------ 6 | *MiniTrailspace* 7 | Features: 8 | - Highlighting is done only in modifiable buffer by default, only in Normal 9 | mode, and stops in Insert mode and when leaving window. 10 | 11 | - Trim all trailing whitespace with |MiniTrailspace.trim()|. 12 | 13 | - Trim all trailing empty lines with |MiniTrailspace.trim_last_lines()|. 14 | 15 | # Setup ~ 16 | 17 | This module needs a setup with `require('mini.trailspace').setup({})` 18 | (replace `{}` with your `config` table). It will create global Lua table 19 | `MiniTrailspace` which you can use for scripting or manually (with 20 | `:lua MiniTrailspace.*`). 21 | 22 | See |MiniTrailspace.config| for `config` structure and default values. 23 | 24 | You can override runtime config settings locally to buffer inside 25 | `vim.b.minitrailspace_config` which should have same structure as 26 | `MiniTrailspace.config`. See |mini.nvim-buffer-local-config| for more details. 27 | 28 | # Highlight groups ~ 29 | 30 | - `MiniTrailspace` - highlight group for trailing space. 31 | 32 | To change any highlight group, set it directly with |nvim_set_hl()|. 33 | 34 | # Disabling ~ 35 | 36 | To disable, set `vim.g.minitrailspace_disable` (globally) or 37 | `vim.b.minitrailspace_disable` (for a buffer) to `true`. Considering high 38 | number of different scenarios and customization intentions, writing exact 39 | rules for disabling module's functionality is left to user. See 40 | |mini.nvim-disabling-recipes| for common recipes. Note: after disabling 41 | there might be highlighting left; it will be removed after next 42 | highlighting update (see |events| and `MiniTrailspace` |:augroup|). 43 | 44 | ------------------------------------------------------------------------------ 45 | *MiniTrailspace.setup()* 46 | `MiniTrailspace.setup`({config}) 47 | Module setup 48 | 49 | Parameters ~ 50 | {config} `(table|nil)` Module config table. See |MiniTrailspace.config|. 51 | 52 | Usage ~ 53 | >lua 54 | require('mini.trailspace').setup() -- use default config 55 | -- OR 56 | require('mini.trailspace').setup({}) -- replace {} with your config table 57 | < 58 | ------------------------------------------------------------------------------ 59 | *MiniTrailspace.config* 60 | `MiniTrailspace.config` 61 | Defaults ~ 62 | >lua 63 | MiniTrailspace.config = { 64 | -- Highlight only in normal buffers (ones with empty 'buftype'). This is 65 | -- useful to not show trailing whitespace where it usually doesn't matter. 66 | only_in_normal_buffers = true, 67 | } 68 | < 69 | ------------------------------------------------------------------------------ 70 | *MiniTrailspace.highlight()* 71 | `MiniTrailspace.highlight`() 72 | Highlight trailing whitespace in current window 73 | 74 | ------------------------------------------------------------------------------ 75 | *MiniTrailspace.unhighlight()* 76 | `MiniTrailspace.unhighlight`() 77 | Unhighlight trailing whitespace in current window 78 | 79 | ------------------------------------------------------------------------------ 80 | *MiniTrailspace.trim()* 81 | `MiniTrailspace.trim`() 82 | Trim trailing whitespace 83 | 84 | ------------------------------------------------------------------------------ 85 | *MiniTrailspace.trim_last_lines()* 86 | `MiniTrailspace.trim_last_lines`() 87 | Trim last blank lines 88 | 89 | 90 | vim:tw=78:ts=8:noet:ft=help:norl: -------------------------------------------------------------------------------- /lua/mini/trailspace.lua: -------------------------------------------------------------------------------- 1 | --- *mini.trailspace* Trailspace (highlight and remove) 2 | --- 3 | --- MIT License Copyright (c) 2021 Evgeni Chasnovski 4 | 5 | --- Features: 6 | --- - Highlighting is done only in modifiable buffer by default, only in Normal 7 | --- mode, and stops in Insert mode and when leaving window. 8 | --- 9 | --- - Trim all trailing whitespace with |MiniTrailspace.trim()|. 10 | --- 11 | --- - Trim all trailing empty lines with |MiniTrailspace.trim_last_lines()|. 12 | --- 13 | --- # Setup ~ 14 | --- 15 | --- This module needs a setup with `require('mini.trailspace').setup({})` 16 | --- (replace `{}` with your `config` table). It will create global Lua table 17 | --- `MiniTrailspace` which you can use for scripting or manually (with 18 | --- `:lua MiniTrailspace.*`). 19 | --- 20 | --- See |MiniTrailspace.config| for `config` structure and default values. 21 | --- 22 | --- You can override runtime config settings locally to buffer inside 23 | --- `vim.b.minitrailspace_config` which should have same structure as 24 | --- `MiniTrailspace.config`. See |mini.nvim-buffer-local-config| for more details. 25 | --- 26 | --- # Highlight groups ~ 27 | --- 28 | --- - `MiniTrailspace` - highlight group for trailing space. 29 | --- 30 | --- To change any highlight group, set it directly with |nvim_set_hl()|. 31 | --- 32 | --- # Disabling ~ 33 | --- 34 | --- To disable, set `vim.g.minitrailspace_disable` (globally) or 35 | --- `vim.b.minitrailspace_disable` (for a buffer) to `true`. Considering high 36 | --- number of different scenarios and customization intentions, writing exact 37 | --- rules for disabling module's functionality is left to user. See 38 | --- |mini.nvim-disabling-recipes| for common recipes. Note: after disabling 39 | --- there might be highlighting left; it will be removed after next 40 | --- highlighting update (see |events| and `MiniTrailspace` |:augroup|). 41 | ---@tag MiniTrailspace 42 | 43 | -- Module definition ========================================================== 44 | local MiniTrailspace = {} 45 | local H = {} 46 | 47 | --- Module setup 48 | --- 49 | ---@param config table|nil Module config table. See |MiniTrailspace.config|. 50 | --- 51 | ---@usage >lua 52 | --- require('mini.trailspace').setup() -- use default config 53 | --- -- OR 54 | --- require('mini.trailspace').setup({}) -- replace {} with your config table 55 | --- < 56 | MiniTrailspace.setup = function(config) 57 | -- Export module 58 | _G.MiniTrailspace = MiniTrailspace 59 | 60 | -- Setup config 61 | config = H.setup_config(config) 62 | 63 | -- Apply config 64 | H.apply_config(config) 65 | 66 | -- Define behavior 67 | H.create_autocommands(config) 68 | 69 | -- Create default highlighting 70 | H.create_default_hl() 71 | 72 | -- Initialize highlight (usually takes effect during startup) 73 | vim.defer_fn(MiniTrailspace.highlight, 0) 74 | end 75 | 76 | --- Defaults ~ 77 | ---@eval return MiniDoc.afterlines_to_code(MiniDoc.current.eval_section) 78 | MiniTrailspace.config = { 79 | -- Highlight only in normal buffers (ones with empty 'buftype'). This is 80 | -- useful to not show trailing whitespace where it usually doesn't matter. 81 | only_in_normal_buffers = true, 82 | } 83 | --minidoc_afterlines_end 84 | 85 | -- Module functionality ======================================================= 86 | --- Highlight trailing whitespace in current window 87 | MiniTrailspace.highlight = function() 88 | -- Highlight only in normal mode 89 | if H.is_disabled() or vim.fn.mode() ~= 'n' then 90 | MiniTrailspace.unhighlight() 91 | return 92 | end 93 | 94 | -- Possibly work only in normal buffers 95 | if H.get_config().only_in_normal_buffers and not H.is_buffer_normal() then return end 96 | 97 | -- Don't add match id on top of existing one 98 | if H.get_match_id() ~= nil then return end 99 | 100 | vim.fn.matchadd('MiniTrailspace', [[\s\+$]]) 101 | end 102 | 103 | --- Unhighlight trailing whitespace in current window 104 | MiniTrailspace.unhighlight = function() 105 | -- Use `pcall` because there is an error if match id is not present. It can 106 | -- happen if something else called `clearmatches`. 107 | pcall(vim.fn.matchdelete, H.get_match_id()) 108 | end 109 | 110 | --- Trim trailing whitespace 111 | MiniTrailspace.trim = function() 112 | -- Save cursor position to later restore 113 | local curpos = vim.api.nvim_win_get_cursor(0) 114 | -- Search and replace trailing whitespace 115 | vim.cmd([[keeppatterns %s/\s\+$//e]]) 116 | vim.api.nvim_win_set_cursor(0, curpos) 117 | end 118 | 119 | --- Trim last blank lines 120 | MiniTrailspace.trim_last_lines = function() 121 | local n_lines = vim.api.nvim_buf_line_count(0) 122 | local last_nonblank = vim.fn.prevnonblank(n_lines) 123 | if last_nonblank < n_lines then vim.api.nvim_buf_set_lines(0, last_nonblank, n_lines, true, {}) end 124 | end 125 | 126 | -- Helper data ================================================================ 127 | -- Module default config 128 | H.default_config = vim.deepcopy(MiniTrailspace.config) 129 | 130 | -- Helper functionality ======================================================= 131 | -- Settings ------------------------------------------------------------------- 132 | H.setup_config = function(config) 133 | H.check_type('config', config, 'table', true) 134 | config = vim.tbl_deep_extend('force', vim.deepcopy(H.default_config), config or {}) 135 | 136 | H.check_type('only_in_normal_buffers', config.only_in_normal_buffers, 'boolean') 137 | 138 | return config 139 | end 140 | 141 | H.apply_config = function(config) MiniTrailspace.config = config end 142 | 143 | H.create_autocommands = function(config) 144 | local gr = vim.api.nvim_create_augroup('MiniTrailspace', {}) 145 | 146 | local au = function(event, pattern, callback, desc) 147 | vim.api.nvim_create_autocmd(event, { group = gr, pattern = pattern, callback = callback, desc = desc }) 148 | end 149 | 150 | -- NOTE: Respecting both `WinEnter` and `BufEnter` seems to be useful to 151 | -- account of different order of handling buffer opening in new window. 152 | -- Notable example: 'nvim-tree' at commit a1600e5. 153 | au({ 'WinEnter', 'BufEnter', 'InsertLeave' }, '*', MiniTrailspace.highlight, 'Highlight') 154 | au({ 'WinLeave', 'BufLeave', 'InsertEnter' }, '*', MiniTrailspace.unhighlight, 'Unhighlight') 155 | 156 | if config.only_in_normal_buffers then 157 | -- Add tracking of 'buftype' changing because it can be set after events on 158 | -- which highlighting is done. If not done, highlighting appears but 159 | -- disappears if buffer is reentered. 160 | au('OptionSet', 'buftype', H.track_normal_buffer, 'Track normal buffer') 161 | end 162 | 163 | au('ColorScheme', '*', H.create_default_hl, 'Ensure colors') 164 | end 165 | 166 | H.create_default_hl = function() vim.api.nvim_set_hl(0, 'MiniTrailspace', { default = true, link = 'Error' }) end 167 | 168 | H.is_disabled = function() return vim.g.minitrailspace_disable == true or vim.b.minitrailspace_disable == true end 169 | 170 | H.get_config = function(config) 171 | return vim.tbl_deep_extend('force', MiniTrailspace.config, vim.b.minitrailspace_config or {}, config or {}) 172 | end 173 | 174 | H.track_normal_buffer = function() 175 | if not H.get_config().only_in_normal_buffers then return end 176 | 177 | -- This should be used with 'OptionSet' event for 'buftype' option 178 | -- Empty 'buftype' means "normal buffer" 179 | if vim.v.option_new == '' then 180 | MiniTrailspace.highlight() 181 | else 182 | MiniTrailspace.unhighlight() 183 | end 184 | end 185 | 186 | H.is_buffer_normal = function(buf_id) return vim.bo[buf_id or 0].buftype == '' end 187 | 188 | H.get_match_id = function() 189 | -- NOTE: this can be replaced with more efficient custom tracking of id per 190 | -- window but it will have more edge cases (like won't update on manual 191 | -- `clearmatches()`) 192 | for _, match in ipairs(vim.fn.getmatches()) do 193 | if match.group == 'MiniTrailspace' then return match.id end 194 | end 195 | end 196 | 197 | -- Utilities ------------------------------------------------------------------ 198 | H.error = function(msg) error('(mini.trailspace) ' .. msg, 0) end 199 | 200 | H.check_type = function(name, val, ref, allow_nil) 201 | if type(val) == ref or (ref == 'callable' and vim.is_callable(val)) or (allow_nil and val == nil) then return end 202 | H.error(string.format('`%s` should be %s, not %s', name, ref, type(val))) 203 | end 204 | 205 | return MiniTrailspace 206 | --------------------------------------------------------------------------------