2

I have a mapping that comments/uncomments lines like 5gc to comment/uncomment five lines, and I would like to make it repeatable with ..

Here is my mapping and the function it uses:

function! Comment() range
    let l:prefix = split(&commentstring, '%s', 1)[0]
    let l:suffix = split(&commentstring, '%s', 1)[1]
    let l:pattern = '\V\s\*'.escape(trim(l:prefix), '\').'\.\*'.escape(trim(l:suffix), '\').'\s\*'
    let l:iscomment = 1
    for l:line in getline(a:firstline, a:lastline)
        if len(trim(l:line)) && l:line !~# l:pattern
            let l:iscomment = 0
            break
        endif
    endfor
    if l:iscomment
        for l:n in range(a:firstline, a:lastline)
            if len(trim(getline(l:n)))
                call setline(l:n, getline(l:n)[len(l:prefix):len(getline(l:n))-len(l:suffix)-1])
            endif
        endfor
    else
        for l:n in range(a:firstline, a:lastline)
            if len(trim(getline(l:n)))
                call setline(l:n, l:prefix . getline(l:n) . l:suffix)
            endif
        endfor
    endif
endfunction

" 5gc to comment/uncomment five lines
" gc to comment/uncomment the visual selection
noremap gc :call Comment()<CR>

I tried using tpope's vim-repeat plugin like this:

nnoremap <silent> <Plug>CommentMap :call Comment()<CR>
\:call repeat#set("\<Plug>CommentMap", v:count)<CR>
nmap gc <Plug>CommentMap

But it only repeats one line as if the count is one, and I'm not sure why.

The only examples I found are just mappings with no functions. How could I do this? If it can be done without plugins that would be even better.

2
  • There are better ways to repeat than the repeat plugin, see vi.stackexchange.com/questions/31366/…
    – Mass
    Nov 10, 2022 at 16:43
  • Thanks @Mass you got me there. I had seen g@ about but I wasn't sure if it was applicable. I think the more examples of it the better. I'll accept your answer if you post one. Oh and say if you think my answer has something stupid.
    – geb
    Nov 10, 2022 at 17:54

2 Answers 2

1

Obviously, your mapping executes two commands, not one. Therefore, the second call (i.e. repeat-set) needs to have v:prevcount passed as argument.

As of "without plugins", a plugin in Vim is simply a piece of script written by another person. So you can read through it and re-implement it in a more elegant and compact way if you wish so. Yet, ultimately, that would be more or less same thing of same complexity. The exceptions are when you need only a smaller part of functionality, or if "the core of codebase" is so short that one benefits much because of dropping docs/readme/prolog/epilogue code and all such.

0

@Matt's answer was helpful and worked but thanks to @Mass' comment, I managed to get something working using the g@ thingy instead of needing any plugins. Note we're using a globalish variable to pass the count s:commentCount, and I needed to put ... in the function definition because it seems to get passed some arguments. It also no longer uses a real range, so I removed range and used my own l:firstline and l:lastline.

function! Comment(...)
    let l:firstline = line('.')
    let l:lastline = line('.') + s:commentCount - 1
    let l:prefix = split(&commentstring, '%s', 1)[0]
    let l:suffix = split(&commentstring, '%s', 1)[1]
    let l:pattern = '\V\s\*'.escape(trim(l:prefix), '\').'\.\*'.escape(trim(l:suffix), '\').'\s\*'
    let l:iscomment = 1
    for l:line in getline(l:firstline, l:lastline)
        if len(trim(l:line)) && l:line !~# l:pattern
            let l:iscomment = 0
            break
        endif
    endfor
    if l:iscomment
        for l:n in range(l:firstline, l:lastline)
            if len(trim(getline(l:n)))
                call setline(l:n, getline(l:n)[len(l:prefix):len(getline(l:n))-len(l:suffix)-1])
            endif
        endfor
    else
        for l:n in range(l:firstline, l:lastline)
            if len(trim(getline(l:n)))
                call setline(l:n, l:prefix . getline(l:n) . l:suffix)
            endif
        endfor
    endif
endfunction

function! s:setupComment()
    let s:commentCount = v:count1
    set operatorfunc=Comment
    return 'g@l'
endfunction

nnoremap <expr> gc <sid>setupComment()

Not the answer you're looking for? Browse other questions tagged or ask your own question.