Highlight f key searches in vim - vim

Often when I'm using f to search for characters in the current line, I'll run into more occurrences of the character than I expected so highlighting each search match would be nice.
In the example below, let's say I'm starting at the beginning of the line and am trying to get to e in vowels. It would be helpful to highlight each of those occurrences so that I could get some context on the number of times to press ; after the initial search
# Here is a comment with a lot of vowels and I have passed it now
How does Vim's current implementation of f know how to wait for only a single character instead of a newline?
I would prefer to overwrite the builtin f functionality so I'm using a remap similar to this, but one of the problems is that it expects me to press enter at the end.
nnoremap f :call HighlightFSearches(input(''))<CR>
Currently have some issues with implementing my function HighlightFSearches as well, but one problem at a time.
Also, not really looking for a plugin and yes I know I can just do a search instead of using f but my brain seems to prefer going with f first in a lot of cases.
Update
Here's my final solution with much thanks to #filbranden below!
function! HighlightFSearches(cmd)
" Get extra character for the command.
let char = nr2char(getchar())
if char ==# ''
" Skip special keys: arrows, backspace...
return ''
endif
" Highlight 'char' on the current line.
let match_str = 'match Visual "\%' . line('.') . 'l' . char . '"'
execute match_str
" Finally, execute the original command with char appended to it
return a:cmd.char
endfunction
" highlight searches using 'f'
nnoremap <expr> f highlighting#HighlightFSearches('f')
nnoremap f<bs> <nop>
vnoremap <expr> f highlighting#HighlightFSearches('f')
vnoremap f<bs> <nop>
" highlight searches using 'F'
nnoremap <expr> F highlighting#HighlightFSearches('F')
nnoremap F<bs> <nop>
vnoremap <expr> F highlighting#HighlightFSearches('F')
vnoremap F<bs> <nop>
Note that I chose the Highlight Group used for visual selects. You could choose a different one or make your own too

The short answer is that you should use getchar() to get a single character from the user.
The long answer is that this gets somewhat complicated pretty quickly, since you need to deal with special keys and corner cases while handling getchar().
Note that getchar() may return a number (for a normal keypress, which you can convert to a character with nr2char()), or a string, starting with a special 0x80 byte for special keys (backspace, arrows, etc.)
A simplistic approach (but somewhat effective) is that running nr2char() on the strings returned for the special keys will return an empty string, so we can use that to skip those.
The next advice is that you can use <expr> in your mappings to return the new command as a string. That, together with non-recursive mappings, allow you to return the actual f command itself at the end of the function, so that part of emulating it is taken care of!
Finally, one more trick you might want to use is to create a "dummy" mapping for f followed by an invalid character. The fact that such a 2-character mapping exists makes it so that your f mapping won't trigger until a second character has been entered, and this will prevent Vim from moving the cursor to the last line while waiting for a character, making the f emulation more seamless.
Putting it all together:
function! HighlightFSearches(cmd)
" Get extra character for the command.
let char = nr2char(getchar())
if char ==# ''
" Skip special keys: arrows, backspace...
return ''
endif
" Here you'll want to highlight "char"
" on the current line.
" Finally, execute the original command.
return a:cmd.char
endfunction
nnoremap <expr> f HighlightFSearches('f')
nnoremap f<bs> <nop>
The function is written in a way that you can easily reuse it for F, t and T.
For highlighting the matches, you can either use :match (or :2match, :3match) or maybe you could set #/ and let 'hlsearch' do the highlighting...
You'll probably want to anchor the regexp on the current line, so only those matches are highlighted, see :help /\%l for what you can use for that.
Finally, you'll probably want to clear the highlighting if you move to a different line. Take a look at the CursorMoved event of autocmd for that purpose.
There are quite a few details to iron out, but hopefully this will clarify how to emulate the command part of getting the character to search for.

The short and sweet answer is to substitute input() for getchar()

Related

Vim keymapping for moving as many characters as the value contained in `textwidth` internal variable

Vim features an internal variable textwidth, which determines how many characters will be printed on screen before adding an <EOL> character to wrap the text to a next line.
I would like to create a mapping, let's say <c-j>, for which I would like the cursor to move a number of characters to the right equal to the value stored in textwidth. This would simulate "going down a line" when the text is wrapped.
I assume that a simple approach would be along the lines of:
nnoremap <c-j> {textwidth}l
However, I have not found a way of evaluating the value of textwidth so that it cant be used as a count for the command l.
Any help is welcome!
There are two ways: One is the interpolation of &textwidth (the & sigil turns the option name into a variable that contains its value; cp. :help :let-option), as in #RuslanOsmanov's answer:
nnoremap <silent> <C-j> :execute "normal!" &textwidth . 'l'<CR>
Another is :help :map-expression, which automatically evaluates the mapping's right-hand side as a Vimscript expression. I would prefer this one, as it's shorter:
nnoremap <expr> <C-j> &textwidth . 'l'
Further improvements
You probably should consider what to do if 'textwidth' is unset, i.e. zero. Unhandled, this would result in a 0l motion, i.e. going to the second character in the line. You can use a conditional to turn this into a no-op, for example. (Or make it beep by returning '<Esc>' instead of '').
nnoremap <expr> <C-j> (&textwidth == 0 ? '<Esc>' : &textwidth . 'l')
Really needed?
Vim has a :help gj command (and variants for the other directions) built-in, that does something similar to what you're trying to implement. Unless you're attempting to solve a special case (e.g. disregarding options like 'showbreak' that further reduce the amount of characters actually shown), it would be advisable to just use (and maybe remap) the built-ins.
You can refer to an option value by prefixing its name with an ampersand, e.g. &textwidth.
Moving &textwidth characters to the right can be run as follows:
:execute "normal!" &textwidth 'l'
where the arguments ("normal!", &textwidth, and 'l') are concatenated with a space and executed as an Ex command.
So your mapping might look something like this:
:nnoremap <silent> <c-j> :execute "normal!" &textwidth 'l'<cr>

Remove specific char in the beginning of the line if it present in vim

Just another vim source code comment question here. I have this mapping for my python source code files:
map <C-C> <Home>i#<Esc>
imap <C-C> <Home>#<Esc> i
On Ctrl-C it puts # in the beginning if the line to comment it out. This improves productivity a lot. But when I want to uncomment lines, I have to do this manually, meaning going to the first character of each commented line and remove it. This is very annoying. At the first glance, I can just bind Home-x to some key, but I can occasionally remove an innocent space or something else in case I misshit and do this on line that has no # character at the beginning of it. I first try to do some replacement with :%s// for a single line, but that has an unwanted affect - it triggers a search and highlights 'pattern' in other lines. In case of a single # character it is a fail.
Can anybody suggest how to remove a specified character in the beginning of current line in case it present and do nothing, if not, without using pattern replacement?
I have created a simple function to Toggle comment in the line:
function! ToggleComment()
let l:pos = col('.')
if getline('.') =~ '\v(\s+|\t+)?#'
exec 'normal! _"_x'
let l:pos -= 1
else
exec 'normal! I#'
let l:pos += 1
endif
call cursor(line("."), l:pos)
endfunction
nnoremap <Leader>t :call ToggleComment()<CR>
inoremap <Leader>t <C-o>:call ToggleComment()<CR>
I recommend Tim Pope's plugin vim-commentary because is way more complete. But of course our idea gives you guys a glimpse how far we can get with vimscript.
Another approach, which does not need to save windowview and toggles comments in other languages can be seen here
Can anybody suggest how to remove a specified character in the beginning of current line in case it present and do nothing, if not, without using pattern replacement?
A solution would be (assuming your cursor is anywhere to the right of # when using the map):
map <c-c> mmF#x`m
A more general solution would be to use a substitution and histdel() to delete the last search pattern:
function! DelComment()
s/^\( *\)#/\1/
call histdel("search", -1)
let #/ = histget("search", -1)
endfunction
After executing the function (by selecting it and typing :#") you can map it to <c-c>:
map <silent> <c-c> mm:silent! call DelComment()<cr>`m
I like using marks around functions to retain the cursor position after executing the map. Feel free to remove mm and `m in the above map.

How to halve the number of whitespace at the beginning of each line in Vim?

Can someone tell me how to do the opposite of this mapping in Vim:
nnoremap <leader>iw :let _s=#/<Bar>:let _s2=line(".")<Bar>:%s/^\s*/&&/ge<Bar>:let #/=_s<Bar>:nohl<Bar>exe ':'._s2<CR>
As a clarification: This mapping doubles (the && part) the number of whitespace characters at the beginning of each line.
Only the whitespace before the first regular character is affected.
Current search string is kept in tact (via the _s variable).
The cursor position is restored after this transformation (via the _s2 variable).
So, basically, I’m searching for a mapping that will undo this one if they are executed one after another.
I’m having trouble in figuring out how to limit this new operation to work only on whitespace before the first regular character.
The following substitute command inverses the effect of its
counterpart that doubles the leading whitespace.
:%s/^\(\s*\)\1/\1/
A mapping to be constructed for this command needs to follow the same
pattern as the one used in the question (except for the substitution
to execute, of course). To reduce repetition in the definitions, one
can separate the state-preserving code into a small function:
nnoremap <silent> <leader>> :call PinnedCursorDo('%s/^\s*/&&/')<cr>
nnoremap <silent> <leader><lt> :call PinnedCursorDo('%s/^\(\s*\)\1/\1/')<cr>
function! PinnedCursorDo(cmd)
let [s, c] = [#/, getpos('.')]
exe a:cmd
let #/ = s
call setpos('.', c)
endfunction
Your original substitution is this (I have replaced the / delimiters with # for readability):
%s#^\s*#&&#
And here is my proposed inverse substitution (take a deep breath...):
%s#^\s*#\=matchstr(submatch(0),'^.\{'.string(float2nr(len(submatch(0))/2)).'\}')#
Let's say the matched string (submatch(0)) contains n whitespace characters. What I am doing is calculating half this number (n/2 = string(float2nr(len(submatch(0))/2))) and then extracting that many characters from the match (essentially matchstr(n/2)). This ensures we get precisely half the whitespace we started with (which may be a mixture of spaces and tabs).
If you know the whitespace will contain ONLY spaces or ONLY tabs, this could be simplified somewhat, for example:
%s#^\s*#\=repeat(" ",indent(".")/2)#
On another note, I would recommend reformulating your maps to make them more readable and therefore easier to modify and maintain. My approach would be to define two functions:
function! DoubleWS()
let pos = getpos('.')
let reg = getreg('#')
exe '%s/^\s*/&&/e'
call setreg('#',reg)
call setpos('.',pos)
endfunction
function! HalfWS()
let pos = getpos('.')
let reg = getreg('#')
exe '%s#^\s*#\=matchstr(submatch(0),"^.\\{".string(float2nr(len(submatch(0))/2))."\}")#e'
call setreg('#',reg)
call setpos('.',pos)
endfunction
Note that the get/set pos/reg functions are a much more robust way of maintaining cursor position and register. You can then map these functions as you wish:
nnoremap <silent> <leader>iw :call DoubleWS()<CR>
nnoremap <silent> <leader>rw :call HalfWS()<CR>
Hope that helps!

Cursor position in VIM abbreviation

is there some way to tell VIM place the cursor at some position after abbreviation expansion?
Say you have something like this in .gvimrc
iabberv <? <?=|?>
and you want to place cursor where pipe character is automatically.
A quick solution that I'd use in this case is to insert some key presses to the abbreviation:
iabbrev <? <?=?><Left><Left>
Would place the cursor two places left, after the =.
In this manner, you can use various movement keys such as <End>, <Home>, or even return to normal mode with <Esc> and command usual normal-mode commands. The example would then be
iabbrev <? <?=?><Esc>hha
and it does the same as the first example. If you expand an abbreviation with space, it will have one extra space. which you can get rid of by using a <BS> (backspace) in the abbreviation. Or expand with <C-]> which will leave no space.
Correction: since the abbreviation is first expanded, and after that the space inserted, you need a small function found in the help (map.txt):
func Eatchar(pat)
let c = nr2char(getchar(0))
return (c =~ a:pat) ? '' : c
endfunc
This is best put in .vimrc. Now the following abbreviation will work fully as intented:
:iabbrev <silent> <? <?=?><Left><Left><C-R>=Eatchar('\s')<CR>
It is a bit messy, but has an external function call that removes the white space and it should work well.
What you want are snippets. I use snipmate for that.
It can be done with lh-map-tools:
"
" in {rtp}/ftpluvin/vim/vim_snippets.vim
inoreab <buffer> <silent> if
\ <C-R>=InsertSeq('if', 'if!cursorhere!\nendif!mark!')<CR>
Other plugins offer a similar feature.

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

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 (-#).
For those tasks I use most of the time block selection.
Put your cursor on the first # character, press CtrlV (or CtrlQ for gVim), and go down until the last commented line and press x, that will delete all the # characters vertically.
For commenting a block of text is almost the same:
First, go to the first line you want to comment, press CtrlV. This will put the editor in the VISUAL BLOCK mode.
Then using the arrow key and select until the last line
Now press ShiftI, which will put the editor in INSERT mode and then press #. This will add a hash to the first line.
Then press Esc (give it a second), and it will insert a # character on all other selected lines.
For the stripped-down version of vim shipped with debian/ubuntu by default, type : s/^/# in the third step instead (any remaining highlighting of the first character of each line can be removed with :nohl).
Here are two small screen recordings for visual reference.
Comment:
Uncomment:
To comment out blocks in vim:
press Esc (to leave editing or other mode)
hit ctrl+v (visual block mode)
use the ↑/↓ arrow keys to select lines you want (it won't highlight everything - it's OK!)
Shift+i (capital I)
insert the text you want, e.g. %
press EscEsc
To uncomment blocks in vim:
press Esc (to leave editing or other mode)
hit ctrl+v (visual block mode)
use the ↑/↓ arrow keys to select the lines to uncomment.
If you want to select multiple characters, use one or combine these methods:
use the left/right arrow keys to select more text
to select chunks of text use shift + ←/→ arrow key
you can repeatedly push the delete keys below, like a regular delete button
press d or x to delete characters, repeatedly if necessary
Sometimes I'm shelled into a remote box where my plugins and .vimrc cannot help me, or sometimes NerdCommenter gets it wrong (eg JavaScript embedded inside HTML).
In these cases a low-tech alternative is the built-in norm command, which just runs any arbitrary vim commands at each line in your specified range. For example:
Commenting with #:
1. visually select the text rows (using V as usual)
2. :norm i#
This inserts "#" at the start of each line. Note that when you type : the range will be filled in, so it will really look like :'<,'>norm i#
Uncommenting #:
1. visually select the text as before (or type gv to re-select the previous selection)
2. :norm x
This deletes the first character of each line. If I had used a 2-char comment such as // then I'd simply do :norm xx to delete both chars.
If the comments are indented as in the OP's question, then you can anchor your deletion like this:
:norm ^x
which means "go to the first non-space character, then delete one character". Note that unlike block selection, this technique works even if the comments have uneven indentation!
Note: Since norm is literally just executing regular vim commands, you're not limited to comments, you could also do some complex editing to each line. If you need the escape character as part of your command sequence, type ctrl-v then hit the escape key (or even easier, just record a quick macro and then use norm to execute that macro on each line).
Note 2: You could of course also add a mapping if you find yourself using norm a lot. Eg putting the following line in ~/.vimrc lets you type ctrl-n instead of :norm after making your visual selection
vnoremap <C-n> :norm
Note 3: Bare-bones vim sometimes doesn't have the norm command compiled into it, so be sure to use the beefed up version, ie typically /usr/bin/vim, not /bin/vi
(Thanks to #Manbroski and #rakslice for improvements incorporated into this answer)
I use the NERD Commenter script. It lets you easily comment, uncomment or toggle comments in your code.
As mentioned in the comments:
for anyone who is confused by the usage, default leader is "\" so 10\cc will comment ten lines and 10\cu will uncomment those ten lines
I have the following in my .vimrc:
" Commenting blocks of code.
augroup commenting_blocks_of_code
autocmd!
autocmd FileType c,cpp,java,scala let b:comment_leader = '// '
autocmd FileType sh,ruby,python let b:comment_leader = '# '
autocmd FileType conf,fstab 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>s/^/<C-R>=escape(b:comment_leader,'\/')<CR>/<CR>:nohlsearch<CR>
noremap <silent> ,cu :<C-B>silent <C-E>s/^\V<C-R>=escape(b:comment_leader,'\/')<CR>//e<CR>:nohlsearch<CR>
Now you can type ,cc to comment a line and ,cu to uncomment a line (works both in normal and visual mode).
(I stole it from some website many years ago so I can't completely explain how it works anymore :). There is a comment where it is explained.)
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
Here is how I do it:
Go to first character on the first line you want to comment out.
Hit Ctrl+q in GVIM or Ctrl+v in VIM, then go down to select first character on the lines to comment out.
Then press c, and add the comment character.
Uncommenting works the same way, just type a space instead of the comment character.
Toggle comments
If all you need is toggle comments I'd rather go with commentary.vim by tpope.
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.
I've come up with a simple addition to my .vimrc file which works pretty well and can be extended easily. You simply add a new filetype to the comment_map and its comment leader.
I added a mapping to normal and visual modes, but you can remap to anything you like. I prefer only to have a 'toggle' style function. One bears having multiple mappings etc.
let s:comment_map = {
\ "c": '\/\/',
\ "cpp": '\/\/',
\ "go": '\/\/',
\ "java": '\/\/',
\ "javascript": '\/\/',
\ "lua": '--',
\ "scala": '\/\/',
\ "php": '\/\/',
\ "python": '#',
\ "ruby": '#',
\ "rust": '\/\/',
\ "sh": '#',
\ "desktop": '#',
\ "fstab": '#',
\ "conf": '#',
\ "profile": '#',
\ "bashrc": '#',
\ "bash_profile": '#',
\ "mail": '>',
\ "eml": '>',
\ "bat": 'REM',
\ "ahk": ';',
\ "vim": '"',
\ "tex": '%',
\ }
function! ToggleComment()
if has_key(s:comment_map, &filetype)
let comment_leader = s:comment_map[&filetype]
if getline('.') =~ "^\\s*" . comment_leader . " "
" Uncomment the line
execute "silent s/^\\(\\s*\\)" . comment_leader . " /\\1/"
else
if getline('.') =~ "^\\s*" . comment_leader
" Uncomment the line
execute "silent s/^\\(\\s*\\)" . comment_leader . "/\\1/"
else
" Comment the line
execute "silent s/^\\(\\s*\\)/\\1" . comment_leader . " /"
end
end
else
echo "No comment leader found for filetype"
end
endfunction
nnoremap <leader><Space> :call ToggleComment()<cr>
vnoremap <leader><Space> :call ToggleComment()<cr>
Note:
I don't use any callbacks or hooks into the file types/loading, because I find they slow down Vim's startup more than the .vimrc static function/map does but that's just my preference. I've also tried to keep it simple and performant. If you do use autocommands you need to be sure to put them in an autocommand group or else the callbacks get added to the filetype multiple times per-file loaded and cause a lot of performance degradation.
Use Control-V to select rectangles of text: go to the first # character, type Ctrl+V, move right once, and then down, up to the end of the comments. Now type x: you're deleting all the # characters followed by one space.
Here is a section of my .vimrc:
"insert and remove comments in visual and normal mode
vmap ,ic :s/^/#/g<CR>:let #/ = ""<CR>
map ,ic :s/^/#/g<CR>:let #/ = ""<CR>
vmap ,rc :s/^#//g<CR>:let #/ = ""<CR>
map ,rc :s/^#//g<CR>:let #/ = ""<CR>
In normal and in visual mode, this lets me press ,ic to insert comments and,rc to remove comments.
I use vim 7.4 and this works for me.
Assuming we are commenting/uncommenting 3 lines.
To comment:
if the line has no tab/space at the beginning:
ctrl + V then jjj then shift + I (cappital i) then //then esc esc
if the line has tab/space at the beginning you still can do the above or swap for c:
ctrl + V then jjj then c then //then esc esc
To uncomment:
if the lines have no tab/space at the beginning:
ctrl + V then jjj then ll (lower cap L) then c
if the lines have tab/space at the beginning, then you space one over and esc
ctrl + V then jjj then ll (lower cap L) then c then space then esc
I combined Phil and jqno's answer and made untoggle comments with spaces:
autocmd FileType c,cpp,java,scala let b:comment_leader = '//'
autocmd FileType sh,ruby,python let b:comment_leader = '#'
autocmd FileType conf,fstab 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 = '"'
function! CommentToggle()
execute ':silent! s/\([^ ]\)/' . escape(b:comment_leader,'\/') . ' \1/'
execute ':silent! s/^\( *\)' . escape(b:comment_leader,'\/') . ' \?' . escape(b:comment_leader,'\/') . ' \?/\1/'
endfunction
map <F7> :call CommentToggle()<CR>
how it works:
Lets assume we work with #-comments.
The first command s/\([^ ]\)/# \1/ searches for the first non-space character [^ ] and replaces that with # +itself. The itself-replacement is done by the \(..\) in the search-pattern and \1 in the replacement-pattern.
The second command s/^\( *\)# \?# \?/\1/ searches for lines starting with a double comment ^\( *\)# \?# \? (accepting 0 or 1 spaces in between comments) and replaces those simply with the non-comment part \( *\) (meaning the same number of preceeding spaces).
For more details about vim patterns check this out.
Visual and Shift-I did not worked for me.
Simplest that worked without any plugins is
Select block - V then j or k or any relevant motion (Don't use arrow keys) :)
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>
If you already know the line numbers, then n,ms/# // would work.
With 30 answers ahead of me, I'll try to give an even easier solution: Insert a # at the beginning of the line. Then go down a line and press dot (.). To repeat, do j,.,j,., etc...To uncomment, remove a # (you can hit x over the #), and do the reverse using k,.,etc...
How to uncomment the following three lines in vi:
#code code
#code
#code code code
Place the cursor over the upper left # symbol and press CtrlV. This puts you in visual block mode. Press the down arrow or J three times to select all three lines. Then press D. All the comments disappear. To undo, press U.
How to comment the following three lines in vi:
code code
code
code code code
Place the cursor over the upper left character, press CtrlV. This puts you in visual block mode. Press ↓ or J three times to select all three lines. Then press:
I//Esc
That's a capital I, //, and Escape.
When you press ESC, all the selected lines will get the comment symbol you specified.
I like to use the tcomment plugin:
http://www.vim.org/scripts/script.php?script_id=1173
I have mapped gc and gcc to comment a line or a highlighted block of code. It detects the file type and works really well.
Yes, there are 33 (mostly repetitive) answers already to this question.
Here is another approach to how to comment lines out in Vim: motions. The basic idea is to comment or uncomment lines out using the same method as yanking a paragraph by typing yip or deleting 2 lines by typing dj.
This approach will let you do things like:
ccj to comment the next 2 lines out, and cuk to uncomment them;
cci{ to comment a block out, and cui{ to uncomment it;
ccip to comment a whole paragraph out, and cuip to uncomment it.
ccG to comment everything out down to the last line, and cugg to uncomment everything up to the first line.
All you need are 2 functions that operate over motions, and 2 mappings for each function. First, the mappings:
nnoremap <silent> cc :set opfunc=CommentOut<cr>g#
vnoremap <silent> cc :<c-u>call CommentOut(visualmode(), 1)<cr>
nnoremap <silent> cu :set opfunc=Uncomment<cr>g#
vnoremap <silent> cu :<c-u>call Uncomment(visualmode(), 1)<cr>
(See the manual about the g# operator and the operatorfunc variable.)
And now the functions:
function! CommentOut(type, ...)
if a:0
silent exe "normal! :'<,'>s/^/#/\<cr>`<"
else
silent exe "normal! :'[,']s/^/#/\<cr>'["
endif
endfunction
function! Uncomment(type, ...)
if a:0
silent exe "normal! :'<,'>s/^\\(\\s*\\)#/\\1/\<cr>`<"
else
silent exe "normal! :'[,']s/^\\(\\s*\\)#/\\1/\<cr>`["
endif
endfunction
Modify the regular expressions above to suit your taste as to where the # should be:
There is this life changing plugin by tpope called vim-commentary
https://github.com/tpope/vim-commentary
This plugin provides:
Sanity
Properly indented comments
Does not comment out empty/unnecessary lines
Usage:
Install via Vundle (or Pathogen I guess).
Highlight your text and press : which will show as :<,'>
Type Commentary here :<,'>Commentary and press Enter.
Boom. Your done bud.
I mark the first and last lines (ma and mb), and then do :'a,'bs/^# //
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
I use EnhancedCommentify. It comments everything I needed (programming languages, scripts, config files). I use it with visual-mode bindings. Simply select text you want to comment and press co/cc/cd.
vmap co :call EnhancedCommentify('','guess')<CR>
vmap cc :call EnhancedCommentify('','comment')<CR>
vmap cd :call EnhancedCommentify('','decomment')<CR>
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.
"comment (cc) and uncomment (cu) code
noremap <silent> cc :s,^\(\s*\)[^# \t]\#=,\1# ,e<CR>:nohls<CR>zvj
noremap <silent> cu :s,^\(\s*\)# \s\#!,\1,e<CR>:nohls<CR>zvj
You can comment/uncomment single or multiple lines with #. To do multiple lines, select the lines then type cc/cu shortcut, or type a number then cc/cu, e.g. 7cc will comment 7 lines from the cursor.
I got the orignal code from the person on What's the most elegant way of commenting / uncommenting blocks of ruby code in Vim? and made some small changes (changed shortcut keys, and added a space after the #).
I use Tim Pope's vim-commentary plugin.
You can use vim-commentary by tpope (https://github.com/tpope/vim-commentary) you can use it as following:
Enter visual mode by pressing
'v'
Then press
'j' repeatedly or e.g 4j to select 4 row
Now all you have to do with the selection is enter keys:
'gc'
This will comment out all the selection, to uncomment repead keys:
'gc'
Starting with the ideas in answers here, I started my own comment function.
It toggles comments on and off. It can handle things like //print('blue'); //this thing is blue and just toggles the first comment. Furthermore it adds comments and a single space just where the first non whitespace is and not at the very start of the line.
Aditionally it doesn't unnecessarily copy the whitespaces, but uses zooms (:h \zs for help) to avoid this extra work, when commenting and indented line.
Hope it helps some minimalists out there. Suggestions are welcome.
" these lines are needed for ToggleComment()
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 = '"'
" l:pos --> cursor position
" l:space --> how many spaces we will use 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>
This simple snippet is from my .vimrc:
function! CommentToggle()
execute ':silent! s/\([^ ]\)/\/\/ \1/'
execute ':silent! s/^\( *\)\/\/ \/\/ /\1/'
endfunction
map <F7> :call CommentToggle()<CR>
It's for //-Comments, but you can adapt it easily for other characters. You could use autocmd to set a leader as jqno suggested.
This is a very simple and efficient way working with ranges and visual mode naturally.
The quickest and the most intuitive method of them all is to remap ) for walk-down-commenting of lines, and then ( for walk-up-uncommenting. Try it and you won't go back.
In Ruby or Bash, with 2-space indents:
map ) I# <Esc>j
map ( k^2x
In C/C++ or PHP, with 4-space indents:
map ) I// <Esc>j
map ( k^4x
Downsides are that you lose ( and ) for sentence-movement (but das can fill in there), and you'll occasionally fall back on select-and-replace or CtrlV for handling long sections. But that's pretty rare.
And for C-style, the long comments are best handled with:
set cindent
set formatoptions=tcqr
... Which combines well with using V[move]gq to redo the word-wrapping.

Resources