Vim command to comment out a selection of lines?

Viewed 1574

Say I have a bunch of lines:

@Override
public void draw(Graphics g) {
    g.setColor(Color.MAGENTA);
    g.fillRect(rectangle.x, rectangle.y, rectangle.width, rectangle.height);
    g.setColor(Color.BLACK);
    g.drawRect(rectangle.x, rectangle.y, rectangle.width, rectangle.height);
}

When I want to comment them out with // (i prefer line comments instead of block comments), what I do is:

  • Place the cursor infront of the @ symbol
  • Ctrl-V: Switch to enter block-select mode
  • Select the column down to the } closing parenthesis using multiple hits of j
  • Shift-I: to enter block-insert
  • Type //
  • ESC to excit
  • Enter to finish the command

--> The lines are now commented out.

Is there an easier way where I don't need to do the block-select? I found I can use a substitution like :'<, '>s/^/\/\///g but this has two problems:

  1. Its very clumsy and error prone to type (multiple forward and backward slashes need to be escaped)
  2. It places the comment symbols (//) at the beginning of the line (position 0), not at the position where the first character of that line was (so indentation is lost).

How can I insert // on the selected lines at the position of the first character of each line using Vi?

5 Answers

You can define a custom mapping or command for your :substitute.

However, there are several commenter plugins that do this very well, and those are generic (and often extensible) so that they work for any filetype:

I'd highly recommend to use one of those plugins instead of trying to reinvent a poor solution yourself.

I use Commentary as in the other answer, but a few thoughts:

  • <C-v>jjjjj could be <C-v>} or <C-v>/}<CR>
  • :substitute doesn’t have to use / as a separator: :'<,'>s-^-//
  • with a visual selection, you can also do :'<,'>normal! I//

How can I insert // on the selected lines at the position of the first character of each line using Vi?

Although, I'm agree with others and the dedicated plugin is a must have, but, as it is formulated in the OP, that's quite an easy task which can be implemented as one-liner:

vnoremap <silent>gc :call setline(".", printf("%*s" . &cms, indent("."), "", trim(getline("."))))<CR>

Now select some text, press "gc", and, voila, it works. To force // usage instead of the default /**/ set the following option for your buffer: setlocal cms=//\ %s. See :h 'cms'.

" I have a 'toggle comment function' that looks like " Reference: https://stackoverflow.com/a/24652257/2571881

" these lines are needed for ToggleComment()
" Reference: https://stackoverflow.com/a/24652257/2571881
autocmd FileType c,cpp,java      let b:comment_leader = '//'
autocmd FileType arduino         let b:comment_leader = '//'
autocmd FileType sh,ruby,python  let b:comment_leader = '#'
autocmd FileType zsh             let b:comment_leader = '#'
autocmd FileType conf,fstab      let b:comment_leader = '#'
autocmd FileType matlab,tex      let b:comment_leader = '%'
autocmd FileType vim             let b:comment_leader = '"'

function! ToggleComment()
    if exists('b:comment_leader')
        let l:pos = col('.')
        let l:space = ( &ft =~ '\v(c|cpp|java|arduino)' ? '3' : '2' )
        if getline('.') =~ '\v(\s*|\t*)' .b:comment_leader
            let l:space -= ( getline('.') =~ '\v.*\zs' . b:comment_leader . '(\s+|\t+)@!' ?  1 : 0 )
            execute 'silent s,\v^(\s*|\t*)\zs' .b:comment_leader.'[ ]?,,g'
            let l:pos -= l:space
        else
            exec 'normal! 0i' .b:comment_leader .' '
            let l:pos += l:space
        endif
        call cursor(line("."), l:pos)
    else
        echo 'no comment leader found for filetype'
    end
endfunction
nnoremap <Leader>t :call ToggleComment()<CR>
inoremap <Leader>t <C-o>:call ToggleComment()<CR>
xnoremap <Leader>t :'<,'>call ToggleComment()<CR>
" vnoremap <Leader>t :call ToggleComment()<CR>

So, once you have this function on your ~/.vimrc you can do:

vip ...................... visual inner paragraph
<leader>t ................ in order to call the function

Make a macro with q, lets put it into the a buffer, so hit qa on a given line. Then press I// to jump to start of line, and comment it out. hit Esc and q and now your macro is done. This macro will comment out the current line.
The full command is qaI//Escq

Now visually select a bunch of lines with V, and type :norm!@a to run your a macro over those lines. This will comment out a bunch of lines.

Record another macro to do the opposite with qb^xx. This can be invoked by visually selecting the lines you want to uncomment and typing norm!@b

You can save these macros in your .vimrc and map the specific macro to a key combination if you want to "save" these commands.

Related