Reputation: 95308
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:
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)?
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>
Upvotes: 2
Views: 2026
Reputation: 3654
" Show trailing whitepace and spaces before a tab:
:highlight ExtraWhitespace ctermbg=red guibg=red
:match ExtraWhitespace /\s\+$\| \+\ze\t/
:autocmd ColorScheme * highlight ExtraWhitespace ctermbg=red guibg=red
This way any bad whitespace will glow in red. It's quite hard to miss.
Upvotes: 2
Reputation: 22512
I tend not to let vim automatically trim anything. As you say, this can be a nightmare if dealing with other peoples code, and can lead to unnecessary conflicts. The approach I take, to keep my own code tidy is to make whitespace visible. With vim this can be achieved by adding the following to your ~/.vimrc
file:
highlight SpecialKey ctermfg=DarkGray
set listchars=tab:>-,trail:~
set list
The result is to show whitespace like this:
This allows me to keep files clean whilst I write them. Most other (GUI) editors have the ability to show whitespace too.
Upvotes: 3
Reputation: 53634
Option 1 can benefit from search()
function, like so:
let b:has_trailing_spaces=!!search('\v\s+$', 'cwn')
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 line search()
found trailing whitespace. Without n
flag search()
moves the cursor which is, I guess, undesired. Without w
it 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 InsertLeave
and '[
, ']
markers:
augroup CleanInsertedTrailingSpaces
autocmd!
autocmd InsertLeave * let wv=winsaveview() | keepjumps lockmarks '[,']s/\s\+$//e | call winrestview(wv)
augroup END
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
autocmd CursorHold * if getpos("'.")[1]!=0 | let wv=winsaveview() | keepjumps lockmarks '.s/\s\+$//e | call winrestview(wv) | endif
, 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 annotate
is able to annotate current state of the file thus you can use grep
to filter out lines that have both trailing spaces and uncommitted changes and use a hook to purge unwanted spaces from them before commit. Sad, but hg annotate
is 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?
Upvotes: 4