├── .gitignore ├── LICENSE ├── README.md ├── doc └── better-whitespace.txt ├── plugin └── better-whitespace.vim └── whitespace_examples.txt /.gitignore: -------------------------------------------------------------------------------- 1 | doc/tags 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Vim Better Whitespace Plugin 2 | 3 | This plugin causes all trailing whitespace characters (see [Supported Whitespace 4 | Characters](#supported-whitespace-characters) below) to be highlighted. Whitespace for the current line will 5 | not be highlighted while in insert mode. It is possible to disable current line highlighting while in other 6 | modes as well (see options below). A helper function `:StripWhitespace` is also provided to make whitespace 7 | cleaning painless. 8 | 9 | Here is a screenshot of this plugin at work: 10 | ![Example Screenshot](http://i.imgur.com/St7yHth.png) 11 | 12 | ## Installation 13 | There are a few ways you can go about installing this plugin: 14 | 15 | 1. If you have [Vundle](https://github.com/gmarik/Vundle.vim) you can simply add: 16 | ```vim 17 | Plugin 'ntpeters/vim-better-whitespace' 18 | ``` 19 | to your `.vimrc` file then run: 20 | ```vim 21 | :PluginInstall 22 | ``` 23 | 2. If you are using [Pathogen](https://github.com/tpope/vim-pathogen), you can just run the following command: 24 | ``` 25 | git clone git://github.com/ntpeters/vim-better-whitespace.git ~/.vim/bundle/vim-better-whitespace 26 | ``` 27 | 3. While this plugin can also be installed by copying its contents into your `~/.vim/` directory, I would 28 | highly recommend using one of the above methods as they make managing your Vim plugins painless. 29 | 30 | ## Usage 31 | Whitespace highlighting is enabled by default, with a highlight color of red. 32 | 33 | * To set a custom highlight color, just call: 34 | ```vim 35 | highlight ExtraWhitespace ctermbg= 36 | ``` 37 | or 38 | 39 | ```vim 40 | let g:better_whitespace_ctermcolor='' 41 | ``` 42 | Similarly, to set gui color: 43 | 44 | ```vim 45 | let g:better_whitespace_guicolor='' 46 | ``` 47 | 48 | * To enable highlighting and stripping whitespace on save by default, use respectively 49 | ```vim 50 | let g:better_whitespace_enabled=1 51 | let g:strip_whitespace_on_save=1 52 | ``` 53 | Set them to 0 to disable this default behaviour. See below for the blacklist of file types 54 | and per-buffer settings. 55 | 56 | * To enable/disable/toggle whitespace highlighting in a buffer, call one of: 57 | ```vim 58 | :EnableWhitespace 59 | :DisableWhitespace 60 | :ToggleWhitespace 61 | ``` 62 | 63 | * The highlighting for the current line in normal mode can be disabled in two ways: 64 | * ```vim 65 | let g:current_line_whitespace_disabled_hard=1 66 | ``` 67 | This will maintain whitespace highlighting as it is, but may cause a 68 | slow down in Vim since it uses the CursorMoved event to detect and 69 | exclude the current line. 70 | 71 | * ```vim 72 | let g:current_line_whitespace_disabled_soft=1 73 | ``` 74 | This will use syntax based highlighting, so there shouldn't be a 75 | performance hit like with the `hard` option. The drawback is that this 76 | highlighting will have a lower priority and may be overwritten by higher 77 | priority highlighting. 78 | 79 | * To clean extra whitespace, call: 80 | ```vim 81 | :StripWhitespace 82 | ``` 83 | By default this operates on the entire file. To restrict the portion of 84 | the file that it cleans, either give it a range or select a group of lines 85 | in visual mode and then execute it. 86 | 87 | * There is an operator (defaulting to `s`) to clean whitespace. 88 | For example, in normal mode, `sip` will remove trailing whitespace from the 89 | current paragraph. 90 | 91 | You can change the operator it, for example to set it to _s, using: 92 | ```vim 93 | let g:better_whitespace_operator='_s' 94 | ``` 95 | Now `_s` strips whitespace on \ lines, and `_s` on the 96 | lines affected by the motion given. Set to the empty string to deactivate the operator. 97 | 98 | Note: This operator will not be mapped if an existing, user-defined 99 | mapping is detected for the provided operator value. 100 | 101 | * To enable/disable stripping of extra whitespace on file save for a buffer, call one of: 102 | ```vim 103 | :EnableStripWhitespaceOnSave 104 | :DisableStripWhitespaceOnSave 105 | :ToggleStripWhitespaceOnSave 106 | ``` 107 | This will strip all trailing whitespace everytime you save the file for all file types. 108 | 109 | * If you want this behaviour by default for all filetypes, add the following to your `~/.vimrc`: 110 | 111 | ```vim 112 | let g:strip_whitespace_on_save = 1 113 | ``` 114 | 115 | For exceptions of all see ```g:better_whitespace_filetypes_blacklist```. 116 | 117 | * If you would prefer to only strip whitespace for certain filetypes, add 118 | the following to your `~/.vimrc`: 119 | 120 | ```vim 121 | autocmd FileType EnableStripWhitespaceOnSave 122 | ``` 123 | 124 | where `` is a comma separated list of the file types you want 125 | to be stripped of whitespace on file save ( ie. `javascript,c,cpp,java,html,ruby` ) 126 | 127 | * If you want to disable automatically stripping whitespace for large files, you can specify 128 | a maximum number of lines (e.g. 1000) by adding the following to your `~/.vimrc`: 129 | 130 | ```vim 131 | let g:strip_max_file_size = 1000 132 | ``` 133 | 134 | This overrides `let g:strip_whitespace_on_save` but not `:EnableStripWhitespaceOnSave`. 135 | Set to `0` to deactivate. 136 | 137 | * By default, you will be asked for confirmation before whitespace is 138 | stripped when you save the file. This can be disabled by adding the 139 | following to your `~/.vimrc`: 140 | ``` 141 | let g:strip_whitespace_confirm=0 142 | ``` 143 | 144 | * By default, all the lines in the file will have their trailing whitespace stripped 145 | when you save the file. This can be changed to only the modified lines, by adding 146 | the following to your `~/.vimrc`: 147 | ``` 148 | let g:strip_only_modified_lines=1 149 | ``` 150 | 151 | * You can override the binary used to check which lines have been modified in the file. 152 | For example to force a 'diff' installed in a different prefix and ignoring the changes 153 | due to tab expansions, you can set the following: 154 | ``` 155 | let g:diff_binary='/usr/local/bin/diff -E' 156 | ``` 157 | 158 | * To disable the highlighting for specific file types, add the following to your `~/.vimrc`: 159 | ```vim 160 | let g:better_whitespace_filetypes_blacklist=['', '', ''] 161 | ``` 162 | This replaces the filetypes from the default list of blacklisted filetypes. The 163 | default types that are blacklisted are: 164 | ```vim 165 | ['diff', 'git', 'gitcommit', 'unite', 'qf', 'help', 'markdown', 'fugitive'] 166 | ``` 167 | If you prefer to also keep these default filetypes ignored, simply include them in the 168 | blacklist: 169 | ```vim 170 | let g:better_whitespace_filetypes_blacklist=['', '', '', 171 | 'diff', 'git', 'gitcommit', 'unite', 'qf', 'help', 'markdown', 'fugitive'] 172 | ``` 173 | 174 | This blacklist can be overriden on a per-buffer basis using the buffer toggle enable and 175 | disable commands presented above. For example: 176 | ```vim 177 | " highlight whitespace in markdown files, though stripping remains disabled by the blacklist 178 | :autocmd FileType markdown EnableWhitespace 179 | " Do not modify kernel files, even though their type is not blacklisted and highlighting is enabled 180 | :autocmd BufRead /usr/src/linux* DisableStripWhitespaceOnSave 181 | ``` 182 | 183 | * To strip white lines at the end of the file when stripping whitespace, set this option in your `.vimrc`: 184 | ```vim 185 | let g:strip_whitelines_at_eof=1 186 | ``` 187 | 188 | * To highlight space characters that appear before or in-between tabs, add the following to your `.vimrc`: 189 | ```vim 190 | let g:show_spaces_that_precede_tabs=1 191 | ``` 192 | Such spaces can **not** be automatically removed by this plugin, though you can try 193 | [`=` to fix indentation](http://vimdoc.sourceforge.net/htmldoc/change.html#=). 194 | You can still navigate to the highlighted spaces with Next/PrevTrailingWhitespace (see below), and fix 195 | them manually. 196 | 197 | * To ignore lines that contain only whitespace, set the following in your `.vimrc`: 198 | ```vim 199 | let g:better_whitespace_skip_empty_lines=1 200 | ``` 201 | 202 | * To navigate to the previous or next trailing whitespace, you can use commands that you 203 | can map thusly in your `.vimrc`: 204 | ```vim 205 | nnoremap ]w :NextTrailingWhitespace 206 | nnoremap [w :PrevTrailingWhitespace 207 | ``` 208 | Note: those command take an optional range as argument, so you can for example select some 209 | text in visual mode and search only inside it: 210 | ```vim 211 | :'<,'>NextTrailingWhitespace 212 | ``` 213 | 214 | * To enable verbose output for each command, set verbosity in your `.vimrc`: 215 | ```vim 216 | let g:better_whitespace_verbosity=1 217 | ``` 218 | 219 | ## Supported Whitespace Characters 220 | Due to the fact that the built-in whitespace character class for patterns (`\s`) 221 | only matches against tabs and spaces, this plugin defines its own list of 222 | horizontal whitespace characters to match for both highlighting and stripping. 223 | 224 | This is list should match against all ASCII and Unicode horizontal whitespace 225 | characters: 226 | ``` 227 | U+0009 TAB 228 | U+0020 SPACE 229 | U+00A0 NO-BREAK SPACE 230 | U+1680 OGHAM SPACE MARK 231 | U+180E MONGOLIAN VOWEL SEPARATOR 232 | U+2000 EN QUAD 233 | U+2001 EM QUAD 234 | U+2002 EN SPACE 235 | U+2003 EM SPACE 236 | U+2004 THREE-PER-EM SPACE 237 | U+2005 FOUR-PER-EM SPACE 238 | U+2006 SIX-PER-EM SPACE 239 | U+2007 FIGURE SPACE 240 | U+2008 PUNCTUATION SPACE 241 | U+2009 THIN SPACE 242 | U+200A HAIR SPACE 243 | U+200B ZERO WIDTH SPACE 244 | U+202F NARROW NO-BREAK SPACE 245 | U+205F MEDIUM MATHEMATICAL SPACE 246 | U+3000 IDEOGRAPHIC SPACE 247 | U+FEFF ZERO WIDTH NO-BREAK SPACE 248 | ``` 249 | 250 | A file is provided with samples of each of these characters to check the plugin 251 | working with them: whitespace_examples.txt 252 | 253 | If you encounter any additional whitespace characters I have missed here, 254 | please submit a pull request. 255 | 256 | ## Screenshots 257 | Here are a couple more screenshots of the plugin at work. 258 | 259 | This screenshot shows the current line not being highlighted in insert mode: 260 | ![Insert Screenthot](http://i.imgur.com/RNHR9KX.png) 261 | 262 | This screenshot shows the current line not being highlighted in normal mode(`CurrentLineWhitespaceOff hard`): 263 | ![Normal Screenshot](http://i.imgur.com/o888Z7b.png) 264 | 265 | This screenshot shows that highlighting works fine for spaces, tabs, and a mixture of both: 266 | ![Tabs Screenshot](http://i.imgur.com/bbsVRUf.png) 267 | 268 | ## Frequently Asked Questions 269 | Hopefully some of the most common questions will be answered here. If you still have a question 270 | that I have failed to address, please open an issue and ask it! 271 | 272 | **Q: Why is trailing whitespace such a big deal?** 273 | 274 | A: In most cases it is not a syntactical issue, but rather is a common annoyance among 275 | programmers. 276 | 277 | 278 | **Q: Why not just use `listchars` with `SpecialKey` highlighting?** 279 | 280 | A: I tried using `listchars` to show trail characters with `SpecialKey` highlighting applied. 281 | Using this method the characters would still show on the current line for me even when the 282 | `SpecialKey` foreground highlight matched the `CursorLine` background highlight. 283 | 284 | 285 | **Q: Okay, so `listchars` doesn't do exactly what you want, why not just use a `match` in your `vimrc`?** 286 | 287 | A: I am using `match` in this plugin, but I've also added a way to exclude the current line in 288 | insert mode and/or normal mode. 289 | 290 | 291 | **Q: If you just want to exclude the current line, why not just use syntax-based highlight rather 292 | than using `match` and `CursorMoved` events?** 293 | 294 | A: Syntax-based highlighting is an option in this plugin. It is used to omit the current line when 295 | using `CurrentLineWhitespaceOff soft`. The only issue with this method is that `match` highlighing 296 | takes higher priorty than syntax highlighting. For example, when using a plugin such as 297 | [Indent Guides](https://github.com/nathanaelkane/vim-indent-guides), syntax-based highlighting of 298 | extra whitespace will not highlight additional white space on emtpy lines. 299 | 300 | 301 | **Q: I already have my own method of removing white space, why is the method used in this plugin better?** 302 | 303 | A: It may not be, depending on the method you are using. The method used in this plugin strips extra 304 | white space and then restores the cursor position and last search history. 305 | 306 | 307 | **Q: Most of this is pretty easy to just add to users' `vimrc` files. Why make it a plugin?** 308 | 309 | A: It is true that a large part of this is fairly simple to make a part of an individuals 310 | configuration in their `vimrc`. I wanted to provide something that is easy to setup and use 311 | for both those new to Vim and others who don't want to mess around setting up this 312 | functionality in their `vimrc`. 313 | 314 | **Q: Can you add indentation highlighting for spaces/tabs? Can you add highlighting for other 315 | types of white space?** 316 | 317 | A: No, and no. Sorry, but both are outside the scope of this plugin. The purpose of this plugin 318 | is to provide a better experience for showing and dealing with extra white space. There is already an 319 | amazing plugin for showing indentation in Vim called [Indent 320 | Guides](https://github.com/nathanaelkane/vim-indent-guides). For other types of white space highlighting, 321 | [listchars](http://vimdoc.sourceforge.net/htmldoc/options.html#'listchars') should be sufficient. 322 | 323 | **Q: I have a better way to do something in this plugin. OR You're doing something stupid/wrong/bad.** 324 | 325 | A: If you know of a better way to do something I am attempting in this plugin, or if I am doing 326 | something improperly/not reccomended then let me know! Please either open an issue informing 327 | me or make the changes yourself and open a pull request. If I am doing something that is bad 328 | or can be improved, I am more than willing to hear about it! 329 | 330 | ## Deprecated commands 331 | Toggling the current line whitespace mode is now a plugin configuration, 332 | and can not be done dynamically anymore. Thus the folowing commands are now deprecated: 333 | 334 | ```vim 335 | :CurrentLineWhitespaceOff 336 | ``` 337 | where `` is either `hard` or `soft`, and: 338 | 339 | ```vim 340 | :CurrentLineWhitespaceOn 341 | ``` 342 | 343 | If you really miss this feature, its withdrawal can easily be overriden 344 | by adding the following to the vimrc (after loading the plugin initially): 345 | 346 | ```vim 347 | fun! BetterWhitespaceCurrentLineMode(type) 348 | " set setting to whatever was passed 349 | let g:current_line_whitespace_disabled_soft=a:type == 'soft' 350 | let g:current_line_whitespace_disabled_hard=a:type == 'hard' 351 | " reload plugin 352 | unlet! g:loaded_better_whitespace_plugin 353 | runtime plugin/better-whitespace.vim 354 | " Re-override the deprecated commands 355 | command! -nargs=1 CurrentLineWhitespaceOff call BetterWhitespaceCurrentLineMode() 356 | command! CurrentLineWhitespaceOn call BetterWhitespaceCurrentLineMode('off') 357 | " Manually trigger change for current buffer. 358 | " BufWinEnter will take care of the rest. 359 | filetype detect 360 | endfun 361 | 362 | " Override deprecated commands, after (!) loading plugin 363 | command! -nargs=1 CurrentLineWhitespaceOff call BetterWhitespaceCurrentLineMode() 364 | command! CurrentLineWhitespaceOn call BetterWhitespaceCurrentLineMode('off') 365 | ``` 366 | 367 | ## Promotion 368 | If you like this plugin, please star it on Github and vote it up at Vim.org! 369 | 370 | Repository exists at: http://github.com/ntpeters/vim-better-whitespace 371 | 372 | Plugin also hosted at: http://www.vim.org/scripts/script.php?script_id=4859 373 | 374 | ## Credits 375 | Originally inspired by: https://github.com/bronson/vim-trailing-whitespace 376 | 377 | Based on: 378 | 379 | http://sartak.org/2011/03/end-of-line-whitespace-in-vim.html 380 | 381 | http://vim.wikia.com/wiki/Highlight_unwanted_spaces 382 | -------------------------------------------------------------------------------- /doc/better-whitespace.txt: -------------------------------------------------------------------------------- 1 | *better-whitespace.txt* better-whitespace 2 | 3 | 4 | This plugin causes all trailing whitespace characters (spaces and tabs) to be 5 | highlighted. Whitespace for the current line will not be highlighted while in 6 | insert mode. It is possible to disable current line highlighting while in other 7 | modes as well (see options below). 8 | 9 | To blacklist certain filetypes, set 'g:better_whitespace_filetypes_blacklist' 10 | to a list of the desired filetypes. 11 | 12 | COMMANDS *better-whitespace-commands* 13 | 14 | :ToggleWhitespace *better-whitespace-:ToggleWhitespace* 15 | :EnableWhitespace *better-whitespace-:EnableWhitespace* 16 | :DisableWhitespace *better-whitespace-:DisableWhitespace* 17 | 18 | Call these functions to toggle whitespace highlighting on/off. 19 | This will set the highlighting of extraneous whitespace for the entire file. 20 | 21 | To disable whitespace highlighting, call :DisableWhitespace. This will disable 22 | highlighting of extraneous whitespace for the entire file. 23 | 24 | :[range]NextTrailingWhitespace *better-whitespace-:NextTrailingWhitespace* 25 | :[range]PrevTrailingWhitespace *better-whitespace-:PrevTrailingWhitespace* 26 | 27 | These functions will respectively take you to the next and previous trailing whitespace 28 | in the ranged that they are passed (by default the whole file). 29 | 30 | :[range]StripWhitespace[!] *better-whitespace-:StripWhitespace* 31 | 32 | Removes extra whitespace, by default on the entire file. To restrict the 33 | portion of the file that it cleans, either give it a range or select a group 34 | of lines in visual mode and then execute it. With the !, ignores &readonly. 35 | 36 | :[range]StripWhitespaceOnChangedLines[!] *better-whitespace-:StripWhitespaceOnChangedLines* 37 | 38 | Like :StripWhitespace, but only on the lines that are modified. 39 | 40 | :ToggleStripWhitespaceOnSave *better-whitespace-:ToggleStripWhitespaceOnSave* 41 | :EnableStripWhitespaceOnSave *better-whitespace-:EnableStripWhitespaceOnSave* 42 | :DisableStripWhitespaceOnSave *better-whitespace-:DisableStripWhitespaceOnSave* 43 | 44 | This enables/disables stripping of extra whitespace on file save. 45 | If `g:strip_only_modified_lines` is set to 1, only modified lines will be stripped. 46 | 47 | 48 | DEPRECATED: :CurrentLineWhitespaceOn and :CurrentLineWhitespaceOff with the 49 | option either 'hard' or 'soft' are now deprecated and replaced by the 50 | following preferences: 51 | `g:current_line_whitespace_disabled_hard` 52 | `g:current_line_whitespace_disabled_soft` 53 | 54 | 55 | 56 | PREFERENCES *better-whitespace-preferences* 57 | 58 | `g:better_whitespace_enabled` (defaults to 1) 59 | Set this to enable whitespace highlighting by default, set to 0 to disable it. 60 | 61 | `g:better_whitespace_filetypes_blacklist` (defaults to ['diff', 'git', 62 | 'gitcommit','unite', 'qf', 'help', 63 | 'markdown', 'fugitive']) 64 | Disables better-whitespace by default on these file types. 65 | Overrides `g:better_whitespace_enabled`, and can be manually overriden with 66 | |better-whitespace-:EnableWhitespace| and related commands. 67 | 68 | `g:strip_only_modified_lines` (defaults to 0) 69 | When stripping whitespace on save, only perform the stripping on the lines 70 | that have been modified. 71 | Uses an external `diff` command set in `g:diff_binary`, which should be 72 | available on all platforms (see |E810|). 73 | 74 | `g:diff_binary` 75 | The binary to use to check which lines have been modified in a file. Defaults 76 | to 'diff' on windows and 'command diff' on unix-like platforms (linux, mac OS, 77 | etc.) to bypass any shell aliases or default options for diff. 78 | 79 | `g:strip_max_file_size` (defaults to 1000) 80 | Skip stripping whitespace on files that have more lines than the value in this 81 | variable. 0 means disabled, thus strip for any file size. This can be manually 82 | overriden with |better-whitespace-:EnableStripWhitespaceOnSave| and related commands. 83 | 84 | `g:better_whitespace_operator` *better-whitespace-operator* 85 | 86 | By default, an operator is provided mapped to: s. 87 | To modify the key mapping for this operator, set `g:better_whitespace_operator`. 88 | This operator will strip whitespace over the currently selected region in visual 89 | mode, or for the following motion in normal mode. Set an empty value to disable. 90 | 91 | Note: This operator will not be mapped if an existing, user-defined mapping is 92 | detected for the provided operator value. 93 | 94 | *better-whitespace-colors* 95 | Set the highlight color for trailing whitespaces: 96 | `g:better_whitespace_ctermcolor` (defaults to 'red') 97 | `g:better_whitespace_guicolor` (defaults to '#FF0000') 98 | 99 | *better-whitespace-current_line* 100 | `g:current_line_whitespace_disabled_hard` (defaults to 0) 101 | Set this to disable highlighting on the current line in all modes 102 | WARNING: This checks for current line on cursor move, which can significantly 103 | impact the performance of Vim (especially on large files) 104 | WARNING: Ignored if g:current_line_whitespace_disabled_soft is set. 105 | 106 | `g:current_line_whitespace_disabled_soft` (defaults to 0) 107 | Set this to disable highlighting of the current line in all modes 108 | This setting will not have the performance impact of the above, but 109 | highlighting throughout the file may be overridden by other highlight 110 | patterns with higher priority. 111 | 112 | 113 | `g:strip_whitespace_confirm` (defaults to 1) 114 | Set to 0 to deactivate asking for confirmation before whitespace is 115 | stripped when you save the file. 116 | 117 | `g:show_spaces_that_precede_tabs` (defaults to 0) 118 | Set this to match space characters that appear before or in-between tabs 119 | 120 | `g:strip_whitespace_on_save` (defaults to 0) 121 | Set this to enable stripping whitespace on file save for files where 122 | better-whitespace is enabled. 123 | 124 | `g:strip_whitelines_at_eof` (defaults to 0) 125 | Set this to enable stripping white lines at the end of the file when we 126 | strip whitespace from the end of lines. 127 | 128 | `g:better_whitespace_skip_empty_lines` (defaults to 0) 129 | Skips empty (whitespace-only) lines for highlighting. 130 | 131 | 132 | 133 | Repository exists at: http://github.com/ntpeters/vim-better-whitespace 134 | 135 | Originally inspired by: https://github.com/bronson/vim-trailing-whitespace 136 | 137 | Based on: 138 | http://sartak.org/2011/03/end-of-line-whitespace-in-vim.html 139 | http://vim.wikia.com/wiki/Highlight_unwanted_spaces 140 | -------------------------------------------------------------------------------- /plugin/better-whitespace.vim: -------------------------------------------------------------------------------- 1 | " Author: Nate Peterson 2 | " Repository: https://github.com/ntpeters/vim-better-whitespace 3 | 4 | " Prevent loading the plugin multiple times 5 | if exists('g:loaded_better_whitespace_plugin') 6 | finish 7 | endif 8 | let g:loaded_better_whitespace_plugin = 1 9 | 10 | 11 | " Section: Preferences 12 | 13 | " Initializes a given global variable to a given value, if it does not yet exist. 14 | function! s:InitVariable(var, value) 15 | let g:[a:var] = get(g:, a:var, a:value) 16 | endfunction 17 | " 18 | " Set the highlight color for trailing whitespaces 19 | call s:InitVariable('better_whitespace_ctermcolor', 'red') 20 | call s:InitVariable('better_whitespace_guicolor', '#FF0000') 21 | 22 | " Operator for StripWhitespace (empty to disable) 23 | call s:InitVariable('better_whitespace_operator', 's') 24 | 25 | " Set this to enable/disable whitespace highlighting 26 | call s:InitVariable('better_whitespace_enabled', 1) 27 | 28 | " Set this to match space characters that appear before or in-between tabs 29 | call s:InitVariable('show_spaces_that_precede_tabs', 0) 30 | 31 | " Set this to disable highlighting on the current line in all modes 32 | " WARNING: This checks for current line on cursor move, which can significantly 33 | " impact the performance of Vim (especially on large files) 34 | " WARNING: Ignored if g:current_line_whitespace_disabled_soft is set. 35 | call s:InitVariable('current_line_whitespace_disabled_hard', 0) 36 | 37 | " Set this to disable highlighting of the current line in all modes 38 | " This setting will not have the performance impact of the above, but 39 | " highlighting throughout the file may be overridden by other highlight 40 | " patterns with higher priority. 41 | call s:InitVariable('current_line_whitespace_disabled_soft', 0) 42 | 43 | " Set this to enable stripping whitespace on file save 44 | call s:InitVariable('strip_whitespace_on_save', 0) 45 | 46 | " Set this to enable stripping white lines at the end of the file when we 47 | " strip whitespace 48 | call s:InitVariable('strip_whitelines_at_eof', 0) 49 | 50 | " Set this to enable user confirmation before stripping whitespace on file save 51 | call s:InitVariable('strip_whitespace_confirm', 1) 52 | 53 | " Set this to blacklist specific filetypes 54 | call s:InitVariable('better_whitespace_filetypes_blacklist', ['diff', 'git', 'gitcommit', 'unite', 'qf', 'help', 'markdown', 'fugitive']) 55 | 56 | " Skip empty (whitespace-only) lines for highlighting 57 | call s:InitVariable('better_whitespace_skip_empty_lines', 0) 58 | 59 | " Skip stripping whitespace on lines that have not been modified 60 | call s:InitVariable('strip_only_modified_lines', 0) 61 | 62 | " Skip stripping whitespace on files that have more lines than this variable 63 | call s:InitVariable('strip_max_file_size', 1000) 64 | 65 | " Disable verbosity by default 66 | call s:InitVariable('better_whitespace_verbosity', 0) 67 | 68 | " Bypass the aliases set for diff by default 69 | if has("win32") || has("win16") 70 | call s:InitVariable('diff_binary', 'diff.exe') 71 | else 72 | call s:InitVariable('diff_binary', 'command diff') 73 | endif 74 | 75 | " Section: Whitespace matching setup 76 | 77 | " Define custom whitespace character group to include all horizontal unicode 78 | " whitespace characters except tab (\u0009). Vim's '\s' class only includes ASCII spaces and tabs. 79 | let s:whitespace_chars='\u0020\u00a0\u1680\u180e\u2000-\u200b\u202f\u205f\u3000\ufeff' 80 | let s:eol_whitespace_pattern = '[\u0009' . s:whitespace_chars . ']\+$' 81 | 82 | if g:better_whitespace_skip_empty_lines == 1 83 | let s:eol_whitespace_pattern = '[^\u0009' . s:whitespace_chars . ']\@1<=' . s:eol_whitespace_pattern 84 | endif 85 | 86 | let s:strip_whitespace_pattern = s:eol_whitespace_pattern 87 | let s:strip_whitelines_pattern = '\(\n\)\+\%$' 88 | if g:show_spaces_that_precede_tabs == 1 89 | let s:eol_whitespace_pattern .= '\|[' . s:whitespace_chars . ']\+\ze[\u0009]' 90 | endif 91 | 92 | " Only init once 93 | let s:better_whitespace_initialized = 0 94 | 95 | " Ensure the 'ExtraWhitespace' highlight group has been defined 96 | function! s:WhitespaceInit() 97 | " Check if the user has already defined highlighting for this group 98 | if hlexists('ExtraWhitespace') == 0 || empty(synIDattr(synIDtrans(hlID('ExtraWhitespace')), 'bg')) 99 | execute 'highlight ExtraWhitespace ctermbg = '.g:better_whitespace_ctermcolor. ' guibg = '.g:better_whitespace_guicolor 100 | endif 101 | let s:better_whitespace_initialized = 1 102 | endfunction 103 | 104 | " Diff command returning a space-separated list of ranges of new/modified lines (as first,last) 105 | let s:diff_cmd=g:diff_binary.' -a' 106 | 107 | " Section: Actual work functions 108 | 109 | " Function to implement trim() if it does not exist 110 | if exists('*trim') 111 | function! s:Trim(s) 112 | return trim(a:s) 113 | endfunction 114 | else 115 | function! s:Trim(s) 116 | return substitute(a:s, '^\_s*\(.\{-}\)\_s*$', '\1', '') 117 | endfunction 118 | endif 119 | 120 | " Function that clears the search entries of BetterWhiteSpace by rolling back to the given index 121 | function! s:RestoreSearchHistory(index) 122 | while histnr('search') > max([a:index, 0]) 123 | call histdel('search', -1) 124 | endwhile 125 | let @/ = histget('search', -1) 126 | endfunction 127 | 128 | " query per-buffer setting for whitespace highlighting 129 | function! s:ShouldHighlight() 130 | " Guess from the filetype if a) not locally decided, b) globally enabled, c) there is enough information 131 | if get(b:, 'better_whitespace_guess', 1) && g:better_whitespace_enabled == 1 132 | let b:better_whitespace_enabled = (empty(&buftype) || &buftype == 'acwrite') && index(g:better_whitespace_filetypes_blacklist, &ft) == -1 133 | endif 134 | return get(b:, 'better_whitespace_enabled', g:better_whitespace_enabled) 135 | endfunction 136 | 137 | " query per-buffer setting for whitespace stripping 138 | function! s:ShouldStripWhitespaceOnSave() 139 | " Guess from local whitespace enabled-ness and global whitespace setting 140 | if get(b:, 'strip_whitespace_guess', 1) && exists('b:better_whitespace_enabled') 141 | let b:strip_whitespace_on_save = b:better_whitespace_enabled && g:strip_whitespace_on_save && &modifiable && 142 | \ (g:strip_max_file_size == 0 || g:strip_max_file_size >= line('$')) 143 | endif 144 | return get(b:, 'strip_whitespace_on_save', g:strip_whitespace_on_save) 145 | endfunction 146 | 147 | " Setup matching with either syntax or match 148 | if g:current_line_whitespace_disabled_soft == 1 149 | " Match Whitespace on all lines 150 | function! s:HighlightEOLWhitespace() 151 | call ClearHighlighting() 152 | if ShouldHighlight() 153 | exe 'syn match ExtraWhitespace excludenl "' . s:eol_whitespace_pattern . '"' 154 | endif 155 | endfunction 156 | 157 | " Match Whitespace on all lines except the current one 158 | function! s:HighlightEOLWhitespaceExceptCurrentLine() 159 | call ClearHighlighting() 160 | if ShouldHighlight() 161 | exe 'syn match ExtraWhitespace excludenl "\%<' . line('.') . 'l' . s:eol_whitespace_pattern . 162 | \ '\|\%>' . line('.') . 'l' . s:eol_whitespace_pattern . '"' 163 | endif 164 | endfunction 165 | 166 | " Remove Whitespace matching 167 | function! s:ClearHighlighting() 168 | syn clear ExtraWhitespace 169 | endfunction 170 | else 171 | " Match Whitespace on all lines 172 | function! s:HighlightEOLWhitespace() 173 | call ClearHighlighting() 174 | if ShouldHighlight() 175 | let w:better_whitespace_match_id = matchadd('ExtraWhitespace', 176 | \ s:eol_whitespace_pattern, 10, get(w:, 'better_whitespace_match_id', -1)) 177 | endif 178 | endfunction 179 | 180 | " Match Whitespace on all lines except the current one 181 | function! s:HighlightEOLWhitespaceExceptCurrentLine() 182 | call ClearHighlighting() 183 | if ShouldHighlight() 184 | let w:better_whitespace_match_id = matchadd('ExtraWhitespace', 185 | \ '\%<' . line('.') . 'l' . s:eol_whitespace_pattern . 186 | \ '\|\%>' . line('.') . 'l' . s:eol_whitespace_pattern, 10, get(w:, 'better_whitespace_match_id', -1)) 187 | endif 188 | endfunction 189 | 190 | " Remove Whitespace matching 191 | function! s:ClearHighlighting() 192 | let match_id = get(w:, 'better_whitespace_match_id', -1) 193 | let valid_ids = map(getmatches(), 'v:val["id"]') 194 | if match_id >= 0 && index(valid_ids, match_id) >= 0 195 | call matchdelete(match_id) 196 | endif 197 | endfunction 198 | endif 199 | 200 | " Checks for extraneous whitespace in the file 201 | " WARNING: moves the cursor. 202 | function! s:DetectWhitespace(line1, line2) 203 | call cursor(a:line1, 1) 204 | let found = search(s:strip_whitespace_pattern, 'cn', a:line2) 205 | if g:strip_whitelines_at_eof == 1 && a:line2 >= line('$') 206 | let found += search(s:strip_whitelines_pattern, 'cn') 207 | endif 208 | return found 209 | endfunction 210 | 211 | " Removes all extraneous whitespace in the file 212 | function! s:StripWhitespace(line1, line2) 213 | " Save the current search and cursor position 214 | let _s = histnr('search') 215 | let l = line('.') 216 | let c = col('.') 217 | 218 | silent execute ':' . a:line1 . ',' . a:line2 . 's/' . s:strip_whitespace_pattern . '//e' 219 | 220 | " Strip empty lines at EOF 221 | if g:strip_whitelines_at_eof == 1 && a:line2 >= line('$') 222 | silent execute '%s/' . s:strip_whitelines_pattern . '//e' 223 | endif 224 | 225 | " Restore the saved search and cursor position 226 | call RestoreSearchHistory(_s) 227 | call cursor(l, c) 228 | endfunction 229 | 230 | " Removes all extraneous whitespace in the file 231 | function! s:StripWhitespaceCommand(line1, line2, force) 232 | if &readonly && a:force == 0 233 | echoerr "E45: 'readonly' option is set (add ! to override)" 234 | else 235 | call StripWhitespace(a:line1, a:line2) 236 | endif 237 | endfunction 238 | 239 | " Removes all extraneous whitespace in the file 240 | function! s:StripWhitespaceOnChangedLinesCommand(line1, line2, force) 241 | if &readonly && a:force == 0 242 | echoerr "E45: 'readonly' option is set (add ! to override)" 243 | else 244 | let ranges=ChangedLines() 245 | for r in ranges 246 | if r[0] > a:line2 247 | break 248 | elseif r[1] < a:line1 249 | continue 250 | endif 251 | call StripWhitespace(max([a:line1, r[0]]), min([a:line2, r[1]])) 252 | endfor 253 | endif 254 | endfunction 255 | 256 | " Strip using motion lines 257 | function! s:StripWhitespaceMotion(...) 258 | call StripWhitespace(line("'["), line("']")) 259 | endfunction 260 | 261 | " Get the ranges of changed lines 262 | function! s:ChangedLines() 263 | if !filereadable(expand('%')) 264 | return [[1,line('$')]] 265 | elseif &modified 266 | redir => l:better_whitespace_changes_list 267 | silent! echo system(s:diff_cmd.' '.shellescape(expand('%')).' -', join(getline(1, line('$')), "\n") . "\n") 268 | redir END 269 | " Get relevant lines (added/changed group headers) 270 | let lines=filter(split(l:better_whitespace_changes_list, '\n'), 'match(v:val, "^[0-9,]*[ac][0-9,]*$") > -1') 271 | " Extract first,last line info (repeat first,first if there’s only first line) 272 | return map(map(lines, 'split(split(v:val, "[ac]")[1], ",")'), 'len(v:val) == 1 ? v:val + v:val : v:val') 273 | endif 274 | return [] 275 | endfunction 276 | 277 | " Strip after checking for confirmation 278 | function! s:StripWhitespaceOnSave(force) 279 | let ranges = g:strip_only_modified_lines ? ChangedLines() : [[1,line('$')]] 280 | 281 | if g:strip_whitespace_confirm == 1 && a:force == 0 282 | let l = line(".") 283 | let c = col(".") 284 | let found = 0 285 | for r in ranges 286 | if DetectWhitespace(r[0], r[1]) 287 | " found not just any whitespace, but whitespace that we are willing to strip 288 | let found = confirm('Whitespace found, delete it?', "&No\n&Yes", 1, 'Question') == 2 289 | break 290 | endif 291 | endfor 292 | call cursor(l, c) 293 | if found == 0 294 | return 295 | endif 296 | endif 297 | 298 | for r in ranges 299 | call StripWhitespace(r[0], r[1]) 300 | endfor 301 | endfunction 302 | 303 | 304 | " Search for trailing whitespace 305 | function! s:GotoTrailingWhitespace(search_backwards, from, to) 306 | " Save the current search 307 | let _s=@/ 308 | let l = line('.') 309 | let c = col('.') 310 | 311 | " Move to start of range (if we are outside of it) 312 | if l < a:from || l > a:to 313 | if a:search_backwards != 0 314 | call cursor(a:to, 0) 315 | call cursor(0, col('$')) 316 | else 317 | call cursor(a:from, 1) 318 | endif 319 | endif 320 | 321 | " Set options (search direction, last searched line) 322 | let opts = 'wz' 323 | let until = a:to 324 | if a:search_backwards != 0 325 | let opts .= 'b' 326 | let until = a:from 327 | endif 328 | " Full file, allow wrapping 329 | if a:from == 1 && a:to == line('$') 330 | let until = 0 331 | endif 332 | 333 | " Go to pattern 334 | let found = search(s:eol_whitespace_pattern, opts, until) 335 | 336 | " Restore position if there is no match (in case we moved it) 337 | if found == 0 338 | call cursor(l, c) 339 | endif 340 | 341 | " Restore the saved search 342 | let @/=_s 343 | endfunction 344 | 345 | 346 | " Sets up (or removes) all auto commands in the buffer, after checking the 347 | " per-buffer settings. Also performs an initial highlighting (or clears it). 348 | function! SetupAutoCommands() 349 | augroup better_whitespace 350 | " Reset all auto commands in group 351 | autocmd! * 352 | 353 | if ShouldHighlight() 354 | if s:better_whitespace_initialized == 0 355 | call WhitespaceInit() 356 | endif 357 | 358 | " Highlight extraneous whitespace at the end of lines, but not the current line in insert mode. 359 | call HighlightEOLWhitespace() 360 | autocmd CursorMovedI,InsertEnter call HighlightEOLWhitespaceExceptCurrentLine() 361 | autocmd InsertLeave,BufReadPost call HighlightEOLWhitespace() 362 | 363 | if g:current_line_whitespace_disabled_soft == 0 364 | " Using syntax: clear whitespace highlighting when leaving buffer 365 | autocmd BufWinLeave if expand("") == expand("%") | call ClearHighlighting() | endif 366 | 367 | " Do not highlight whitespace on current line in insert mode 368 | autocmd CursorMovedI call HighlightEOLWhitespaceExceptCurrentLine() 369 | 370 | " Do not highlight whitespace on current line in normal mode? 371 | if g:current_line_whitespace_disabled_hard == 1 372 | autocmd CursorMoved call HighlightEOLWhitespaceExceptCurrentLine() 373 | endif 374 | endif 375 | 376 | elseif s:better_whitespace_initialized == 1 377 | " Clear highlighting if it disabled, as it might have just been toggled 378 | call ClearHighlighting() 379 | endif 380 | 381 | " Strip whitespace on save if enabled. 382 | if ShouldStripWhitespaceOnSave() 383 | autocmd BufWritePre call StripWhitespaceOnSave(v:cmdbang) 384 | endif 385 | 386 | augroup END 387 | endfunction 388 | 389 | " Check & setup auto commands upon enter & load, and again on filetype change. 390 | autocmd FileType,WinEnter,BufWinEnter * call SetupAutoCommands() 391 | autocmd ColorScheme * call WhitespaceInit() 392 | 393 | " Also check on specific buftype changes 394 | if has('nvim') 395 | autocmd TermOpen * call SetupAutoCommands() 396 | elseif exists('##TerminalWinOpen') 397 | autocmd TerminalWinOpen * call SetupAutoCommands() 398 | endif 399 | 400 | 401 | " Section: Setting of per-buffer higlighting/stripping 402 | 403 | function! s:EnableWhitespace() 404 | let b:better_whitespace_enabled = 1 405 | let b:better_whitespace_guess = 0 406 | call SetupAutoCommands() 407 | endfunction 408 | 409 | function! s:DisableWhitespace() 410 | let b:better_whitespace_enabled = 0 411 | let b:better_whitespace_guess = 0 412 | call SetupAutoCommands() 413 | endfunction 414 | 415 | function! s:ToggleWhitespace() 416 | let b:better_whitespace_enabled = 1 - ShouldHighlight() 417 | let b:better_whitespace_guess = 0 418 | call SetupAutoCommands() 419 | endfunction 420 | 421 | function! s:EnableStripWhitespaceOnSave() 422 | let b:strip_whitespace_on_save = 1 423 | let b:strip_whitespace_guess = 0 424 | call SetupAutoCommands() 425 | endfunction 426 | 427 | function! s:DisableStripWhitespaceOnSave() 428 | let b:strip_whitespace_on_save = 0 429 | let b:strip_whitespace_guess = 0 430 | call SetupAutoCommands() 431 | endfunction 432 | 433 | function! s:ToggleStripWhitespaceOnSave() 434 | let b:strip_whitespace_on_save = 1 - ShouldStripWhitespaceOnSave() 435 | let b:strip_whitespace_guess = 0 436 | call SetupAutoCommands() 437 | endfunction 438 | 439 | 440 | " Section: Public commands and mappings 441 | " Run :StripWhitespace to remove end of line whitespace *on changed lines* 442 | command! -bang -range=% StripWhitespaceOnChangedLines call StripWhitespaceOnChangedLinesCommand(, , 0) 443 | " Run :StripWhitespace to remove end of line whitespace 444 | command! -bang -range=% StripWhitespace call StripWhitespaceCommand(, , 0) 445 | " Run :EnableStripWhitespaceOnSave to enable whitespace stripping on save 446 | command! EnableStripWhitespaceOnSave call EnableStripWhitespaceOnSave() 447 | " Run :DisableStripWhitespaceOnSave to disable whitespace stripping on save 448 | command! DisableStripWhitespaceOnSave call DisableStripWhitespaceOnSave() 449 | " Run :ToggleStripWhitespaceOnSave to enable/disable whitespace stripping on save 450 | command! ToggleStripWhitespaceOnSave call ToggleStripWhitespaceOnSave() 451 | " Run :EnableWhitespace to enable whitespace highlighting 452 | command! EnableWhitespace call EnableWhitespace() 453 | " Run :DisableWhitespace to disable whitespace highlighting 454 | command! DisableWhitespace call DisableWhitespace() 455 | " Run :ToggleWhitespace to toggle whitespace highlighting on/off 456 | command! ToggleWhitespace call ToggleWhitespace() 457 | " Search for trailing white space forwards 458 | command! -range=% NextTrailingWhitespace call GotoTrailingWhitespace(0, , ) 459 | " Search for trailing white space backwards 460 | command! -range=% PrevTrailingWhitespace call GotoTrailingWhitespace(1, , ) 461 | 462 | if !empty(g:better_whitespace_operator) 463 | " Ensure we only map if no identical, user-defined mapping already exists 464 | if (empty(mapcheck(g:better_whitespace_operator, 'x'))) 465 | " Visual mode 466 | exe 'xmap '.g:better_whitespace_operator.' :StripWhitespace' 467 | endif 468 | 469 | " Ensure we only map if no identical, user-defined mapping already exists 470 | if (empty(mapcheck(g:better_whitespace_operator, 'n'))) 471 | " Normal mode (+ space, with line count) 472 | exe 'nmap '.g:better_whitespace_operator.' :exe ".,+".v:count" StripWhitespace"' 473 | " Other motions 474 | exe 'nmap '.g:better_whitespace_operator.' :set opfunc=StripWhitespaceMotiong@' 475 | endif 476 | endif 477 | 478 | 479 | " Deprecated legacy commands, set for compatiblity and to point users in the right direction. 480 | let s:errmsg='please set g:current_line_whitespace_disabled_{soft,hard} and reload better whitespace' 481 | command! -nargs=* CurrentLineWhitespaceOff echoerr 'E492: Deprecated command CurrentLineWhitespaceOff: '.s:errmsg 482 | command! CurrentLineWhitespaceOn echoerr 'E492: Deprecated command CurrentLineWhitespaceOn: '.s:errmsg 483 | -------------------------------------------------------------------------------- /whitespace_examples.txt: -------------------------------------------------------------------------------- 1 | U+0009 TAB: 2 | U+0020 SPACE: 3 | U+00A0 NO-BREAK SPACE:  4 | U+1680 OGHAM SPACE MARK:  5 | U+180E MONGOLIAN VOWEL SEPARATOR:᠎ 6 | U+2000 EN QUAD:  7 | U+2001 EM QUAD:  8 | U+2002 EN SPACE:  9 | U+2003 EM SPACE:  10 | U+2004 THREE-PER-EM SPACE:  11 | U+2005 FOUR-PER-EM SPACE:  12 | U+2006 SIX-PER-EM SPACE:  13 | U+2007 FIGURE SPACE:  14 | U+2008 PUNCTUATION SPACE:  15 | U+2009 THIN SPACE:  16 | U+200A HAIR SPACE:  17 | U+200B ZERO WIDTH SPACE:​ 18 | U+202F NARROW NO-BREAK SPACE:  19 | U+205F MEDIUM MATHEMATICAL SPACE:  20 | U+3000 IDEOGRAPHIC SPACE:  21 | U+FEFF ZERO WIDTH NO-BREAK SPACE: 22 | xxxxxx SPACES PRECEDING TABS: hello world! 23 | xxxxxx SPACES INBETWEEN TABS: hello world! 24 | xxxxxx A WHITESPACE-ONLY LINE AND AN EMPTY LINE AT EOF 25 | 26 | 27 | 28 | 29 | --------------------------------------------------------------------------------