How do I comment out multiple in visual mode selected lines? How do I make it language specific?

For example, if the first 4 lines are selected:

def foo(a,b):
    for each in (a,b):
        print each
    return a+b
print "2"

The operation of a command/macro should result in this (in python):

#def foo(a,b):
#    for each in (a,b):
#        print each
#    return a+b
print "2"

12 Answers 12


If you want language specific commenting you will need a plugin like nerdcommenter.

Alternatively, although it doesn't answer your actual question, you can use builtin vim actions and your knowledge of each language's comment characters...

Option #1: V-blocks

  1. :1 Enter (Go to line 1)
  2. Ctrl-V (V-Block mode)
  3. jjj (Down 3 more lines)
  4. Shift-I (Enter insert mode before the block)
  5. # (Insert a '#')
  6. Esc (Back to normal mode)

Option #2: Substitution



  1. : Ex command follows
  2. 1,4 on lines from 1 through 4
  3. s substitute
  4. / separator for pieces of the substitution command.
    (You can also use a different character, e.g. :)
  5. ^ beginning of the line
  6. / separator
  7. # the comment character for python
  8. / final separator

Option #3: Repeat application of a macro (source)

  1. :1 Enter (Go to line 1)
  2. qa (Start recording on register a)
  3. Shift-I (Enter insert mode at the beginning of the line
  4. # (Add a '#' at the beginning of the line)
  5. Esc (Back to normal mode)
  6. q (Stop recording)

  7. :2,4 normal @a (re-run the macro recorded to register a on lines between 2 and 4)


    you can select the lines in visual mode and hit : to automatically populate the Ex line with :'<,'> (a range from the beginning to the end of the visual selection) then type normal @a and hit Enter (source).

Now, whenever you want to comment some lines just re-run the macro recorded to register a on those lines:

:9,22 normal @a (comment out lines 9-22)
  • 1
    Option 4: Plugin
    – Cody Poll
    Commented Feb 4, 2015 at 18:25
  • 1
    I don't understand why you use a macro for single command, when you can do :9,22 normal I# as per my answer.
    – Ben
    Commented Feb 19, 2015 at 9:53
  • Why would you use :1<enter> when you can use gg?
    – Tanath
    Commented Feb 19, 2015 at 17:12
  • @Tanath Commenting from the first line was specific to this example. If the author wanted to comment from lines 9 to 22 they would not be able to use gg.
    – bsmith89
    Commented Feb 19, 2015 at 22:53
  • 1
    @bsmith89 Then you'd use 9gg. Still one less character and easier to type.
    – Tanath
    Commented Feb 20, 2015 at 3:27

Using Visual Block mode (CtrlV), select the beginning of the lines. Then hit I# (that's a capital letter I) to insert the hash character on each of those lines. Then press Esc to return from Insert Mode to Normal Mode.

  • It does not work for me. It inserts a comment only in the first line.
    – gon1332
    Commented Feb 4, 2015 at 17:38
  • Are you pushing ctrl? Because ctrl+v is a different thing from just v.
    – Cody Poll
    Commented Feb 4, 2015 at 17:40
  • @CodyPoll I know. Everything is ok up to I. When I press I, then the # will be placed only in front of the first line.
    – gon1332
    Commented Feb 5, 2015 at 20:43
  • @CodyPoll Ok.. I was just inpatient. I did not press Esc after the procedure described.
    – gon1332
    Commented Feb 5, 2015 at 22:07
  • @gon1332 as far as I know you need to press Esc at the end. Commented Feb 5, 2015 at 22:07

If you just need a quick solution for whatever language you're currently in, and you already have the text selected in visual mode, then

:norm 0i#

does the job. (For each line, in normal mode, go to the first column and insert #. Using :norm I# will insert it before the first non-whitespace character, which may not be what you want.) Using :norm i# will also work, because :norm starts at the beginning of the line, but it's less explicit and less clear if you don't know that.

Of course, if you intend to do this frequently, you'll want to set up a mapping or look for a plugin.

  • 1
    0 is not needed since by default, normal command is executed with cursor at start of the line.
    – nitishch
    Commented Feb 19, 2015 at 10:28
  • 1
    Of Course, line numbers, %, marks can be prefixed with this command. Example : :1,5norm i# (or) : 'a,'bnorm i# (or) :10%norm i#
    – SibiCoder
    Commented May 28, 2016 at 8:40

Doing it automatically would require you to add something like the following to your vimrc file (source):

au FileType haskell,vhdl,ada let b:comment_leader = '-- '
au FileType vim let b:comment_leader = '" '
au FileType c,cpp,java let b:comment_leader = '// '
au FileType sh,make let b:comment_leader = '# '
au FileType tex let b:comment_leader = '% '
noremap <silent> ,c :<C-B>sil <C-E>s/^/<C-R>=escape(b:comment_leader,'\/')<CR>/<CR>:noh<CR>
noremap <silent> ,u :<C-B>sil <C-E>s/^\V<C-R>=escape(b:comment_leader,'\/')<CR>//e<CR>:noh<CR>

Using ,c to comment a region and ,u to uncomment a region. This manually sets the comment symbols for different languages.

The second option is to use a plugin like tcomment, vim-commentary or comments.vim. I use tcomment myself. Please, read the instructions on usage and installation at their pages, as I beleive that's beyond the topic of the question.

I'd suggest you use a plugin (one of the linked above or another) as that is much easier than maintaining a piece of code in your vimrc file.

Edit: I removed the manual way as the question was changed and also the correct way was answered by 200_success.

  • An additional plugin suggestion: NERD Commenter - vim.org/scripts/script.php?script_id=1218
    – gaveen
    Commented Feb 3, 2015 at 20:33
  • Note: this only supports linewise comments. For example, ANSI C does not recognize // (only /* */).
    – wchargin
    Commented Feb 3, 2015 at 21:35
  • While I like this approach, Is there a way to make it toggle comments?
    – ideasman42
    Commented Sep 23, 2015 at 5:18
  • 1
    @ideasman42 You'd have to make a function instead and check if the current line starts with a comment and then depending on that call either of the :s commands shown in the excerpt in the answer. The check itself would be something like getline('.') =~ "^" . escape(b:comment_leader, '\/'). If it's true uncomment, otherwise comment. This is not tested and should serve only as an example.
    – tokoyami
    Commented Sep 23, 2015 at 18:26

After you have selected the lines, simply type

:norm I#

: will automatically put '<,'> on your command line, which is a range from the start of your selection to the end; norm executes a normal mode command, and will act on that range; I# is the normal mode command which inserts a '#' at the start of the line.


I use scrooloose/nerdcommenter for this.

With this plugin you can visual select your lines and hit leader+c to toggle comments. Depending on the file type it will use different symbols for commenting.


I find the vim-commentary plugin is by far and away the easiest way to do this. Select a range of lines, then just hit gc. It will use an appropriate commenting character for the filetype you have open. It's even possible without any visual selection to uncomment adjacent commented lines with gcu or gcgc.


I'm a big fan of TComment for this; not only can I do filetype specific comment styles, but even specify block vs per-line for languages that support block comments.

    gc{motion}   :: Toggle comments (for small comments within one line 
                    the &filetype_inline style will be used, if 
    gcc          :: Toggle comment for the current line

Explicit commenting/uncommenting:

    g<{motion}   :: Uncomment region
    g<c          :: Uncomment the current line
    g<b          :: Uncomment the current region as block

    g>{motion}   :: Comment region
    g>c          :: Comment the current line
    g>b          :: Comment the current region as block

In visual mode:

    gc           :: Toggle comments
    gC           :: Comment selected text
  • 1
    Thanks for your answer! Could you perhaps expand it? Providing plugin-answers is fine, but right now it's just s a link to a plugin. At the very least, a basic description of what it does and how to use it is expected in the answer. also see this meta post. Commented Feb 19, 2015 at 10:09
  • Seems silly to copy/paste keybinds but there you go; I already described what it does. Commented Feb 19, 2015 at 17:07

Assuming you'd like to add prefix to 5 lines at the beginning of the line, then you can use Search and replace:


or at the end of the lines:


Or use visual mode (Ctrl+v) to select vertical block of text, then enter insert mode (I) and type something and press Esc to confirm and apply the changes to other lines.



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'
function! Uncomment ()
  "uncomment on all our lines
  execute '''<,''>s/\v(^\s*)'.escape(b:commentChar, '\/').'\v\s*/\1/e'
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()
    call Docomment()
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.


This is an extension on work in: https://vi.stackexchange.com/a/8130/26305

Uses replace to handle existing comments not in specific indentation, instead of vim key commands and allows a dictionary of filetypes which can be easily toggled with 'set filetype=X'

Paste the following, declare your missing filetype in dictionary (default comment char is '#') and use Ctrl-m to toggle comment on one or multiple lines.

let commentTextMap = {
        \'c': '\/\/',
        \'h': '\/\/',
        \'cpp': '\/\/',
        \'java': '\/\/',
        \'php': '\/\/',
        \'javascript': '\/\/',
        \'groovy': '\/\/',
        \'python': '#',
        \'sh': '#',
        \'vim': '"',
        \'make': '#',
        \'conf': '#',
noremap <silent> <expr> <C-m> ((synIDattr(synID(line("."), col("."), 0), "name") =~ 'comment\c') ?
        \ ':<S-Right>:s/^\([ \t]*\)' . get(commentTextMap, &filetype, '#') . '/\1/<CR>' :
        \ ':<S-Right>:s/^/' . get(commentTextMap, &filetype, '#') . '/<CR>:nohl<CR>'
        \ ) . ':nohl<CR>:call histdel("/", -1)<CR>'

I'll do a shameless plug of my similar answer: https://vi.stackexchange.com/a/755/492

Assuming Esc+Shift+V is already done, and some lines are selected then following keys needs to be pressed:


Followed by Enter.

The difference between this and accepted answer is lack of ^ and last / - which are optional in this case

While Ctrl+V+{select}+I#{Esc} is less keystrokes I find this one as the fastest way to comment out part of the code (python or shell script) - mostly because of the muscle memory and guarantee that it's always going to replace at the start of the line.

  • 1
    :s// uses the last pattern. That almost certainly is not ^ unless you happened to recently search that
    – D. Ben Knoble
    Commented May 12, 2023 at 13:57

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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