I have an BufWritePre hook added to my .vimrc that trims trailing whitespace before a buffer is saved. This is very convenient for me when editing my own code or that of others who also have a policy to always remove trailing whitespace. However, this makes me sometimes mess up the whitespace of others, which doesn’t look very nice in version control.
I have two ideas how this could be solved in general, both of which I have specific problems with:
Option 1
After opening a file (maybe using a BufReadPost hook), detect whether there is trailing whitespace in the file. If yes, set a buffer-local flag to signal this. If the flag is set, disable the trimming before a save.
The problem I have with this approach is that I don’t seem to figure out how I can detect whether there is trailing whitespace in the buffer. I know about =~, but how do I get the buffer contents as a string? Or even better, I can do a search using /\s+$<cr>, but how can I check if the search was successful (if there are hits)?
Option 2 (more intelligent)
It would be even better if the whitespace trimming would only happen on the lines that were actually modified. This way I could have the benefit of not having to care about trailing whitespace in my code but still not messing up the rest of the file. This raises the question: can I somehow get the line numbers of the lines I added or modified?
I’m new to Vimscript, so I’d appreciate any hints or tips 🙂
UPDATE: I settled with option 1 now:
" configure list facility
highlight SpecialKey term=standout ctermbg=yellow guibg=yellow
set listchars=tab:>-,trail:~
" determine whether the current file has trailing whitespace
function! SetWhitespaceMode()
let b:has_trailing_whitespace=!!search('\v\s+$', 'cwn')
if b:has_trailing_whitespace
" if yes, we want to enable list for this file
set list
else
set nolist
endif
endfunction
" trim trailing whitespace in the current file
function! RTrim()
%s/\v\s+$//e
noh
endfunction
" trim trailing whitespace in the given range
function! RTrimRange() range
exec a:firstline.",".a:lastline."substitute /\\v\\s+$//e"
endfunction
" after opening and saving files, check the whitespace mode
autocmd BufReadPost * call SetWhitespaceMode()
autocmd BufWritePost * call SetWhitespaceMode()
" on save, remove trailing whitespace if there was already trailing whitespace
" in the file before
autocmd BufWritePre * if !b:has_trailing_whitespace | call RTrim() | endif
" strip whitespace manually
nmap <silent> <leader>W :call RTrim()<cr>
vmap <silent> <leader>W :call RTrimRange()<cr>
Option 1 can benefit from
search()function, like so:search()function returns a number of matched line (they start from 1) or 0 if nothing was found,!!turns it to either 1 or 0, dropping information about on which linesearch()found trailing whitespace. Withoutnflagsearch()moves the cursor which is, I guess, undesired. Withoutwit may search only in the part of buffer that is after the cursor (really depends on'wrapscan'option).Proposed option 2 implementation is a hack that uses
InsertLeaveand'[,']markers:It assumes that you only add trailing whitespaces after typing. It will break if you move your cursor across the lines in insert mode. You can also try adding
, this should remove trailing spaces at the line of last change (only one line,
'[and']can’t be used here because they point to first and last lines to often to be useful). Both autocommands should add information to undo tree.There is a second option for the option 2:
git annotateis able to annotate current state of the file thus you can usegrepto filter out lines that have both trailing spaces and uncommitted changes and use a hook to purge unwanted spaces from them before commit. Sad, buthg annotateis not able to do so and thus you will have to write something more complex, possibly in python. I can’t say anything about other VC systems.I guess it would be better if you used
set list listchars+=trail:-to see such spaces and thus be able to remove them manually if they accidentally appear (personally I can’t remember myself constantly adding trailing spaces by accident, though in comments and documentation they are used by me intentionally to indicate that paragraph continues). What do you do so that this problem appears?