Does the default Vim behaviour make sense? I guess that depends on your preferences. One downside of your proposed behaviour is that something like d}
will now delete the first line of the next paragraph too, and visually selecting paragraphs with v}}
is harder, as it will select the first line of the next paragraph. I suspect this is the reason it works like it does.
AFAIK there isn't any way to make {
and }
behave like you want, but it's not very hard to override that; here's a punt which seems to work well in some quick testing.
I have not used it extensively, so there may be caveats. It's based on the jumpy.vim code though, which is reasonably well tested so I think it should work well, but could be it turns out the search pattern needs some tweaking.
With this d}
won't delete the first line of the next paragraph, but blank lines will be removed (but d{
will leave an extra blank line), and V
will select it. I think that's probably fixable though, if you want it (i.e. go one line up if mode
is x
or o
).
fun! s:jump(mode, dir) abort
" Pattern to find:
"
" \n Newline
" \s*\n Any whitespace, followed by a newline (i.e. a blank line)
" \s*\zs Skip any whitespace, and \zs sets the start of the pattern so
" the cursor is on the right position.
let pattern = '\n\s*\n\s*\zs'
" Get motion count; done here as some commands later on will reset it.
let count = v:count1
" Set context mark so user can jump back with '' or ``.
normal! m'
" Start visual selection or re-select previously selected.
if a:mode is# 'x'
normal! gv
endif
let save = winsaveview()
for i in range(count)
let loc = search(pattern, 'Wz' .. (a:dir is# 'prev' ? 'b' : ''))
if loc > 0
" Jump to first non-whitespace if cursor is on leading whitespace.
if getline('.')[:col('.') - 1] =~# '^\s*$'
normal! ^
endif
continue
endif
" Jump to top or bottom of file if we're at the first or last match.
if i is count - 1
exe 'keepjumps normal! ' .. (a:dir is# 'next' ? 'G' : 'gg')
else
call winrestview(save)
endif
break
endfor
endfun
" Mappings for normal mode (nnoremap), operator-pending mode (onoremap),
" and visual/select mode (xnoremap).
nnoremap <silent> { :call <SID>jump('n', 'prev')<CR>
nnoremap <silent> } :call <SID>jump('n', 'next')<CR>
onoremap <silent> { :call <SID>jump('o', 'prev')<CR>
onoremap <silent> } :call <SID>jump('o', 'next')<CR>
xnoremap <silent> { :call <SID>jump('x', 'prev')<CR>
xnoremap <silent> } :call <SID>jump('x', 'next')<CR>