tags:

views:

169

answers:

4

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?

+1  A: 

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.

Laurence Gonsalves
This is a decent start, but has a few problems... The ones which immediately come to mind: visual mode, clobbering search history, properly unsetting/resetting `hlsearch`, the slight blink of the cursor as it flicks to the command bar (minor, yes, but still annoying).
David Wolever
Anyway, I think it's a good solution... And, if nothing else turns up, it may be the only solution (in which case, a bit of Vim scripting might make the issues less painful).
David Wolever
A: 

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.

eremite
+1  A: 

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.

Mike P
The patch idea is the best solution I've heard/read.
Bryan Ross
+1  A: 

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>
keithr