What's a quick way to comment/uncomment lines in Vim?

Viewed 1193677

I have a Ruby code file open in vi, there are lines commented out with #:

class Search < ActiveRecord::Migration
  def self.up
    # create_table :searches do |t|
    #   t.integer :user_id
    #   t.string :name
    #   t.string :all_of
    #   t.string :any_of
    #   t.string :none_of
    #   t.string :exact_phrase
    # 
    #   t.timestamps
    # end
  end

  def self.down
    # drop_table :searches
  end
end

Say I want to uncomment all the lines in the first def ... end section. What's an efficient way to do that in Vim?

In general, I'm looking for an easy and fluid way to comment and uncomment lines. Here I'm dealing with Ruby code, but it could be JavaScript (//) or Haml (-#).

51 Answers

Specify which lines to comment in vim:

Reveal the line numbers:

:set number

then

:5,17s/^/#/     this will comment out line 5-17

or this:

:%s/^/#/        will comment out all lines in file

Toggle comments

If all you need is toggle comments I'd rather go with commentary.vim by tpope.

enter image description here

Installation

Pathogen:

cd ~/.vim/bundle
git clone git://github.com/tpope/vim-commentary.git

vim-plug:

Plug 'tpope/vim-commentary'

Vundle:

Plugin 'tpope/vim-commentary'

Further customization

Add this to your .vimrc file: noremap <leader>/ :Commentary<cr>

You can now toggle comments by pressing Leader+/, just like Sublime and Atom.

Visual and Shift-I did not worked for me.

Simplest that worked without any plugins is


  1. Select block - V then j or k or any relevant motion (Don't use arrow keys) :)

  2. Then hit : it prompts command to :'<,'>

    To Comment

    Using #  - `s/^/#/` 
    
    Using `//` - `s/^/\/\//`
    

    To Uncomment

    Using #  - `s/^#//` 
    
    Using `//` - `s/^\/\//`
    

Exaplanation -

'<,'> - Apply to visual block

s - substitute

^ - starts with

after / add character # in this case of \/\/ escaped for //


Update

I wrote a function to comment and uncomment current line with <Space><Space>

Works for next 10 lines for example 10<Space><Space>

Paste it to .vimrc

function CommentUncomment()
  let line = getline('.')
  if line[:1] == "//"
      norm ^2x
  else 
      norm I//
  endif
endfunction

nnoremap <Space><Space> :call CommentUncomment()<CR>

A few regular Vim commands do not work with my setup on Windows. Ctrl + v and Ctrl + q are some of them. I later discovered the following methods worked to uncomment lines.

Given

Some indented comments

   # Practice in Vim
   # Practice in Vim
   # Practice in Vim
   # Practice in Vim
   # Practice in Vim
   # Practice in Vim
   # Practice in Vim

The following approaches remove the # symbol and preserve indents.

Approaches

Move the cursor to the first comment (arrows or h, j, k, l). Then apply one of the following techniques:

Visual Block Mode (faster)

  • Ctrl + Shift + v to enter visual block mode
  • js to choose the vertical lines.
  • l to include horizontal characters (optional)
  • x to delete the block

Search/Replace + Regex

  • Choose text with regular visual mode, i.e. Shift + v
  • Type :. You'll get this prompt '<,'>.
  • Type regex, e.g. s/#// substitutes the hash with nothing.
    (Optional: type s/# // to include the space).
  • Enter

:norm command

  • Choose text with regular visual mode, i.e. Shift + v
  • Type :. You'll get this prompt '<,'>.
  • Give a command. Type :norm ^x to remove the first non-whitespace character and the next character. (Optional: try :norm x if not indented or :norm ^xx to include the space).
  • Enter

g mode

  • Choose text with regular visual mode, i.e. Shift + v
  • Type :. You'll get this prompt '<,'>.
  • Give a command. Type g/#/norm! ^x.
    (Optional: type g/#/norm! ^xx to include the space).
  • Enter

Results

    Practice in Vim
    Practice in Vim
    Practice in Vim
    Practice in Vim
    Practice in Vim
    Practice in Vim
    Practice in Vim

See Also

  • Post on removing indented comments
  • Post on how to quickly comment w/Vim
  • ThePrimeagen's tutorial on g commands.
  • VimTrick's tutorial on Commenting code

This answer is most useful if you are unable to install plugins but you still want your comment characters to follow existing indentation levels.

This answer is here to 1) show the correct code to paste into a .vimrc to get vim 7.4+ to do block commenting/uncommenting while keeping indentation level with 1 shortcut in visual mode and 2) to explain it. Here is the code:

let b:commentChar='//'
autocmd BufNewFile,BufReadPost *.[ch]    let b:commentChar='//'
autocmd BufNewFile,BufReadPost *.cpp    let b:commentChar='//'
autocmd BufNewFile,BufReadPost *.py    let b:commentChar='#'
autocmd BufNewFile,BufReadPost *.*sh    let b:commentChar='#'
function! Docomment ()
  "make comments on all the lines we've grabbed
  execute '''<,''>s/^\s*/&'.escape(b:commentChar, '\/').' /e'
endfunction
function! Uncomment ()
  "uncomment on all our lines
  execute '''<,''>s/\v(^\s*)'.escape(b:commentChar, '\/').'\v\s*/\1/e'
endfunction
function! Comment ()
  "does the first line begin with a comment?
  let l:line=getpos("'<")[1]
  "if there's a match
  if match(getline(l:line), '^\s*'.b:commentChar)>-1
    call Uncomment()
  else
    call Docomment()
  endif
endfunction
vnoremap <silent> <C-r> :<C-u>call Comment()<cr><cr>

How it works:

  • let b:commentChar='//' : This creates a variable in vim. the b here refers to the scope, which in this case is contained to the buffer, meaning the currently opened file. Your comment characters are strings and need to be wrapped in quotes, the quotes are not part of what will be substituted in when toggling comments.

  • autocmd BufNewFile,BufReadPost *... : Autocommands trigger on different things, in this case, these are triggering when a new file or the read file ends with a certain extension. Once triggered, the execute the following command, which allows us to change the commentChar depending on filetype. There are other ways to do this, but they are more confusing to novices (like me).

  • function! Docomment() : Functions are declared by starting with function and ending with endfunction. Functions must start with a capital. the ! ensures that this function overwrites any previous functions defined as Docomment() with this version of Docomment(). Without the !, I had errors, but that might be because I was defining new functions through the vim command line.

  • execute '''<,''>s/^\s*/&'.escape(b:commentChar, '\/').' /e' : Execute calls a command. In this case, we are executing substitute, which can take a range (by default this is the current line) such as % for the whole buffer or '<,'> for the highlighted section. ^\s* is regex to match the start of a line followed by any amount of whitespace, which is then appended to (due to &). The . here is used for string concatenation, since escape() can't be wrapped in quotes. escape() allows you to escape character in commentChar that matches the arguments (in this case, \ and /) by prepending them with a \. After this, we concatenate again with the end of our substitute string, which has the e flag. This flag lets us fail silently, meaning that if we do not find a match on a given line, we won't yell about it. As a whole, this line lets us put a comment character followed by a space just before the first text, meaning we keep our indentation level.

  • execute '''<,''>s/\v(^\s*)'.escape(b:commentChar, '\/').'\v\s*/\1/e' : This is similar to our last huge long command. Unique to this one, we have \v, which makes sure that we don't have to escape our (), and 1, which refers to the group we made with our (). Basically, we're matching a line that starts with any amount of whitespace and then our comment character followed by any amount of whitespace, and we are only keeping the first set of whitespace. Again, e lets us fail silently if we don't have a comment character on that line.

  • let l:line=getpos("'<")[1] : this sets a variable much like we did with our comment character, but l refers to the local scope (local to this function). getpos() gets the position of, in this case, the start of our highlighting, and the [1] means we only care about the line number, not other things like the column number.

  • if match(getline(l:line), '^\s*'.b:commentChar)>-1 : you know how if works. match() checks if the first thing contains the second thing, so we grab the line that we started our highlighting on, and check if it starts with whitespace followed by our comment character. match() returns the index where this is true, and -1 if no matches were found. Since if evaluates all nonzero numbers to be true, we have to compare our output to see if it's greater than -1. Comparison in vim returns 0 if false and 1 if true, which is what if wants to see to evaluate correctly.

  • vnoremap <silent> <C-r> :<C-u>call Comment()<cr><cr> : vnoremap means map the following command in visual mode, but don't map it recursively (meaning don't change any other commands that might use in other ways). Basically, if you're a vim novice, always use noremap to make sure you don't break things. <silent> means "I don't want your words, just your actions" and tells it not to print anything to the command line. <C-r> is the thing we're mapping, which is ctrl+r in this case (note that you can still use C-r normally for "redo" in normal mode with this mapping). C-u is kinda confusing, but basically it makes sure you don't lose track of your visual highlighting (according to this answer it makes your command start with '<,'> which is what we want). call here just tells vim to execute the function we named, and <cr> refers to hitting the enter button. We have to hit it once to actually call the function (otherwise we've just typed call function() on the command line, and we have to hit it again to get our substitutes to go through all the way (not really sure why, but whatever).

Anyway, hopefully this helps. This will take anything highlighted with v, V, or C-v, check if the first line is commented, if yes, try to uncomment all highlighted lines, and if not, add an extra layer of comment characters to each line. This is my desired behavior; I did not just want it to toggle whether each line in the block was commented or not, so it works perfectly for me after asking multiple questions on the subject.

Here's a basic one-liner based on the C-v followed by I method outlined above.

This command (:Comment) adds a chosen string to the beginning of any selected lines.

command! -range -nargs=1 Comment :execute "'<,'>normal! <C-v>0I" . <f-args> . "<Esc><Esc>"

Add this line to your .vimrc to create a command that accepts a single argument and places the argument at the beginning of every line in the current selection.

E.g. if the following text is selected:

1
2

and you run this: :Comment //, the result will be:

//1
//2

To uncomment the whole file:

  1. Esc exits insert mode
  2. gg goes to first char on first line
  3. ctrl+V or ctrl+shift+v selects current char
  4. G or shift+g goes to last line
  5. x deletes selection

In VIM:

1- Enter visual mode by presssing v.

2- Use arrows to select the block you want to comment.

3- Press :

4- Type 's/^/#'

To remove comments just replace step 4 with:

4- Type 's/^#//'

I like short, integrated and memorable methods to not be dependent on externals scripts an fancy fuss...

TLDR:
press gI (capital i) to place the cursor in insert mode on the beginning of the line (regardless if it is space or non-space character and preventing automatic indention)

Use this to fastly comment (e.g.) non-consecutive lines with the comment-sigil (e.g. # or //) as first character and without indention, by pressing the . (dot) --> but if there is still an auto-indention mechanism, while in insert mode press and to correct indention and after action escape to normal mode. Now . is usable to comment out lines...

long:
I realized now (after years), that pressing gI (capital i) will place the cursor at column 1 in insert mode (meaning: at the beginning of the line no matter if it's a word or non-word character).

Inserting the commenting sigil (like #) and pressing escape - now it's possible to comment single, non-consecutive lines (with the comment-sigil as first character and no indention) while just pressing the . (dot) on the keyboard.

In contrast to pressing 0i or just I where it's placing the commenting sigil at the first word-character, partly also with unwanted indention

" comments
augroup comment_like_a_boss
    autocmd!
    autocmd FileType c,cpp,go                let b:comment_leader = '// '
    autocmd FileType ruby,python             let b:comment_leader = '# '
    autocmd FileType conf,fstab,sh,bash,tmux let b:comment_leader = '# '
    autocmd FileType tex                     let b:comment_leader = '% '
    autocmd FileType mail                    let b:comment_leader = '> '
    autocmd FileType vim                     let b:comment_leader = '" '
augroup END
noremap <silent> ,cc :<C-b>silent <C-e>norm ^i<C-r>=b:comment_leader<CR><CR>
noremap <silent> ,uc :<C-b>silent <C-e>norm ^xx<CR>

Even though this question already has a ton of answers I still thought I would give a shoutout to a small plugin I wrote: commentify.

Commentify uses the commentstring setting to decide how to comment out a block of code, so you don't have to keep a mapping of different comment types in your configuration, and supports both line based comments (eg, //) and block comments (eg, /* */).

It also maps the same shortcut (defaults to ctrl+c) for both commenting and uncommenting the block, so you don't have to remember two mappings or a complex set of commands.

First, I'd like to thank @mike for his answer, as I'm using a modified version of it. I wanted to post my version, in case anyone is interested. The main functional difference for mine is that it will always perform the same action on every line of the range. If the selected range contains any uncommented lines, then every line has a comment leader added to it. This way, if you have human-readable comments in your otherwise uncommented code block, they don't become uncommented. Then, when you uncomment the block, the human-readable text will remain commented, since it has 2 comment leaders. It also restores the cursor position when done.

The ToggleComment function:

function! ToggleComment() range
    "Ensure we know the comment leader.
    if !exists('b:comment_leader')
        echo "Unknown comment leader."
        return
    endif
    "Save the initial cursor position, to restore later.
    let l:inipos = getpos('.')
    "Make a list of all of the line numbers in the range which are already commented.
    let l:commented_lines = []
    for i in range(a:firstline, a:lastline)
        if getline(i) =~ '^\s*' . b:comment_leader
            let l:commented_lines = add(l:commented_lines, i)
        endif
    endfor
    "If every line in the range is commented, set the action to uncomment.
    "  Otherwise, set it to comment.
    let l:i1 = index(l:commented_lines, a:firstline)
    let l:i2 = index(l:commented_lines, a:lastline)
    if l:i1 >= 0 && l:i2 >= 0 && (l:i2 - l:i1) == (a:lastline - a:firstline)
        let l:action = "uncomment"
    else
        let l:action = "comment"
    endif
    "Loop through the range, commenting or uncommenting based on l:action.
    for i in range(a:firstline, a:lastline)
        "Move to line i.
        exec "exe " . i
        "Perform the action.
        if l:action == "comment"
            exec 'normal! 0i' . b:comment_leader
        else
            execute 'silent s,' . b:comment_leader . ',,'
        endif
    endfor
    "Restore the initial position.
    call setpos('.', l:inipos)
endfunction

The key mapping (note that I changed the comment key to 'k'):

noremap <Leader>k :call ToggleComment()<CR>

Lastly, I put the autocmds inside the "if" statement and augroup below, but they are unchanged. They are mostly just there for reference.

if has("autocmd")
    augroup autocmds
        autocmd!
        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 = '"'
    augroup END
endif

EDIT: I updated the ToggleComment function from my original answer. Most importantly, I modified the regular expressions. In many cases, it would not work correctly if there was another instance of a comment leader in the string. I believe it may have had to be preceded or followed by a space, but I can't remember. Either way, an example error case for Python is below.

print("Hello, World!") # Says hello to the world.

I've fixed this, and simplified the regular expressions a bit. One side effect is that it no longer adds a space after the comment leader, but this does not bother me. I've added a local declaration to a few variables that I forgot in my original answer.

To Comment A Line (For All Languages):

  • noremap <silent> ,// :call CommentLine() <CR>

We can call it with number of lines and in visual mode too, it works. Like : To comment four lines use 4,// and to uncomment use 4,/.

To Uncomment A Line (For All Languages):

  • noremap <silent> ,/ :call UnCommentLine() <CR>

If You want to add new symbol[comment] then add a list and add some lines in function. If you want to add a language that has the comment symbol that already defined in one of the lists just add your language name in the corresponding list (To Get correct name: Open your file in vim and use :set ft to get the correct name for your language).

Definition of CommentLine()

function! CommentLine() let slash_ft_list = ['c' , 'cpp', 'java', 'scala' , 'systemverilog' , 'verilog' , 'verilog_systemverilog'] let hash_ft_list = ['sh' , 'ruby' , 'python' , 'csh' , 'conf' , 'fstab' , 'perl'] let perct_ft_list = ['tex'] let mail_ft_list = ['mail'] let quote_ft_list = ['vim'] if (index(slash_ft_list, &ft) != -1) :norm I// elseif (index(hash_ft_list, &ft) != -1) :norm I# elseif (index(perct_ft_list, &ft) != -1) :norm I% elseif (index(mail_ft_list, &ft) != -1) :norm I> elseif (index(quote_ft_list, &ft) != -1) :norm I" endif endfunction

Definition of UnCommentLine()

function! UnCommentLine() let slash_ft_list = ['c' , 'cpp', 'java', 'scala' , 'systemverilog' , 'verilog' , 'verilog_systemverilog'] let hash_ft_list = ['sh' , 'ruby' , 'python' , 'csh' , 'conf' , 'fstab' , 'perl'] let perct_ft_list = ['tex'] let mail_ft_list = ['mail'] let quote_ft_list = ['vim'] if (index(slash_ft_list, &ft) != -1) :norm ^2x elseif (index(hash_ft_list, &ft) != -1) :norm ^x elseif (index(perct_ft_list, &ft) != -1) :norm ^x elseif (index(mail_ft_list, &ft) != -1) :norm ^x elseif (index(quote_ft_list, &ft) != -1) :norm ^x endif endfunction

:g/.spare[1-9].*/,+2s/^/\/\//

The above code will comment out all the lines that contain "spare" and a number after that plus it will comment two lines more from the line in which that was found. For more such uses visit : http://vim.wikia.com/wiki/Search_and_replace#Details

Related