gvlasov
gvlasov

Reputation: 20015

Need some help writing a custom movement

I want a movement to jump to the end of a block of code. I wrote a function and I'm trying to onoremap it, but it doesn't work. Here is what I do:

onoremap <silent> } :set opfunc=MovementToEdgeOfBlock(1)<cr>g@

If I do simply:

nnoremap <silent> } :call MovementToEdgeOfBlock(1)<cr>

then the function works as intended. But I need it more as a movement for other commands. So what am I doing wrong?

Here is the function itself (I don't think that the problem is in the function, but anyway):

function! MovementToEdgeOfBlock(direction)
    let startLine=line(".")
    function! HowManyTabs(line)
        let i=0
        while a:line[i]==#"\t"
            let i+=1
        endwhile
        return i
    endfunction
    let startLineTabs = HowManyTabs(getline("."))
    echom startLineTabs " tabs"
    if a:direction==1
        let tabs=HowManyTabs(getline(line('.')+1))
    else
        let tabs=HowManyTabs(getline(line('.')-1))
    endif
    while tabs>startLineTabs
        if a:direction==1
            execute "normal! j"
        else
            execute "normal! k"
        endif
        let tabs=HowManyTabs(getline(line('.')))
    endwhile
endfunction

Upvotes: 1

Views: 76

Answers (1)

ZyX
ZyX

Reputation: 53604

Have you read :h 'opfunc' carefully, including :h g@ which is referenced there? It has absolutely nothing to do with what you want to achieve. More, g@ was never intended to work in operator-pending mode. And more, 'opfunc' option takes a function name, not an expression like you try to pass it, and passes this function one string argument.

What you should have done is first trying to create exactly the same mapping for operator-pending mode as you use in normal mode. If this does not work try using <expr> mappings: I would have wrote your function as following:

" Do not redefine function each time ToEdgeOfBlock is called,
" put the definition in global scope: There is no way to have 
" a local function in any case.
" The following does exactly the same thing your one used to do (except 
" that I moved getline() here), but faster
function! s:HowManyTabs(lnr)
    return len(matchstr(getline(a:lnr), "^\t*"))
endfunction
function! s:ToEdgeOfBlock(direction)
    let startlnr=line('.')
    let startlinetabs=s:HowManyTabs(startlnr)
    let shift=(a:direction ? 1 : -1)
    let nextlnr=startlnr+shift
    while s:HowManyTabs(nextlnr)>startlinetabs && 1<=nextlnr && nextlnr<=line('$')
        let nextlnr+=shift
    endwhile
    return nextlnr.'gg'
endfunction
noremap <expr> } <SID>ToEdgeOfBlock(1)

My version also allows you to undo the jump using <C-o>.

Upvotes: 2

Related Questions