When you navigate by paragraph in vim using { and } it skips lines that contain nothing but whitespace though they are otherwise 'blank'.
How can I convince vim to treat "whitespace only" lines as paragraph breaks so { and } will jump to them?
When you navigate by paragraph in vim using { and } it skips lines that contain nothing but whitespace though they are otherwise 'blank'.
How can I convince vim to treat "whitespace only" lines as paragraph breaks so { and } will jump to them?
The { and } commands move by "paragraph", and vim's documentation (see :help paragraph
) says:
Note that a blank line (only containing white space) is NOT a paragraph boundary.
So the only way you can do this would be to remap { and }. Something like:
nmap { ?^\\s*$<CR>
nmap } /^\\s*$<CR>
could work, but you may want to adjust this so it doesn't alter your search history.
I never have a legitimate need for whitespace only lines so I solved this "problem" by adding the following to my .vimrc
:
" Highlight spaces at the end of lines.
highlight link localWhitespaceError Error
au Syntax * syn match localWhitespaceError /\(\zs\%#\|\s\)\+$/ display
" Remove end of line white space.
noremap <Leader>r ma:%s/\s\+$//e<CR>`a
So then if { and } skips whitespace only lines I use my mapping to remove it and try again.
This is something that's bothered me for a long time. Probably the "right" solution would be to submit a patch to vim itself that would allow you to customize paragraph boundaries with a regex (like :set paragraphs, but actually useful).
In the meantime, I've made a function and a couple of mappings that almost do the right thing:
function! ParagraphMove(delta, visual)
normal m'
normal |
if a:visual
normal gv
endif
if a:delta > 0
" first whitespace-only line following a non-whitespace character
let pos1 = search("\\S", "W")
let pos2 = search("^\\s*$", "W")
if pos1 == 0 || pos2 == 0
let pos = search("\\%$", "W")
endif
elseif a:delta < 0
" first whitespace-only line preceding a non-whitespace character
let pos1 = search("\\S", "bW")
let pos2 = search("^\\s*$", "bW")
if pos1 == 0 || pos2 == 0
let pos = search("\\%^", "bW")
endif
endif
normal |
endfunction
nnoremap <silent> } :call ParagraphMove( 1, 0)<CR>
onoremap <silent> } :call ParagraphMove( 1, 0)<CR>
" vnoremap <silent> } :call ParagraphMove( 1, 1)<CR>
nnoremap <silent> { :call ParagraphMove(-1, 0)<CR>
onoremap <silent> { :call ParagraphMove(-1, 0)<CR>
" vnoremap <silent> { :call ParagraphMove(-1, 1)<CR>
This doesn't correctly handle counts like '4}' or visual mode correctly (uncomment the vnoremap lines at your peril), but seems ok for things like not clobbering the current search pattern and not flickering. Also, 'd}', 'y}', etc. seem to work ok. If anyone has ideas for making counts work or fixing visual mode, please let me know.
Here's a modified version that handles counts correctly:
function! ParagraphMove(delta, visual, count)
normal m'
normal |
if a:visual
normal gv
endif
if a:count == 0
let limit = 1
else
let limit = a:count
endif
let i = 0
while i < limit
if a:delta > 0
" first whitespace-only line following a non-whitespace character
let pos1 = search("\\S", "W")
let pos2 = search("^\\s*$", "W")
if pos1 == 0 || pos2 == 0
let pos = search("\\%$", "W")
endif
elseif a:delta < 0
" first whitespace-only line preceding a non-whitespace character
let pos1 = search("\\S", "bW")
let pos2 = search("^\\s*$", "bW")
if pos1 == 0 || pos2 == 0
let pos = search("\\%^", "bW")
endif
endif
let i += 1
endwhile
normal |
endfunction
nnoremap <silent> } :<C-U>call ParagraphMove( 1, 0, v:count)<CR>
onoremap <silent> } :<C-U>call ParagraphMove( 1, 0, v:count)<CR>
" vnoremap <silent> } :<C-U>call ParagraphMove( 1, 1)<CR>
nnoremap <silent> { :<C-U>call ParagraphMove(-1, 0, v:count)<CR>
onoremap <silent> { :<C-U>call ParagraphMove(-1, 0, v:count)<CR>
" vnoremap <silent> { :<C-U>call ParagraphMove(-1, 1)<CR>