vim: mapping key to comment with indentation - vim

Before I was using this in .vimrc to use '+' and '-' to comment the code has been highlighted:
noremap <silent>+ :s/^/\/\/ /<CR>:noh<CR>
noremap <silent>- :s/^\/\/ //<CR>:noh<CR>
so when I comment the code it becomes:
int main() {
// int x = 0;
// int y = 0;
return 0;
}
I want to comment codes with indents, like the following:
int main() {
// int x = 0;
// int y = 0;
return x;
}
However I try to use:
noremap <silent>+ :le<CR>:s/^/\/\/ /<CR>==:noh<CR>
noremap <silent>- :le<CR>:s/^\/\/ //<CR>==:noh<CR>
The commenting result looks like:
int main() {
// int x = 0;
int y = 0;
return x;
}
I was wondering what is wrong with my mapping and how I can fix it...
In addition, is there a "smarter" way to do this?
(I am willing to learn to write a mapping instead of installing some plugins such as NERDcommenter)
Thanks,
Update:
Maybe I didn't put my questions clear so there are some answers below didn't get what I meant...
Many thanks to everyone who attempted to answer my question, I found Ben's solution is easiest to understand for beginners and rc0r's has less lines as well as it works with multiple level of indentation (even though I don't know what it really does, I will do some self-studying later).
So now the code I use looks like:
if has("autocmd")
autocmd FileType c,cpp,java,verilog noremap <silent><Leader>. :s:^\(\s*\):\1// :<CR>:noh<CR>
autocmd FileType c,cpp,java,verilog noremap <silent><Leader>, :s:^\(\s*\)// :\1:<CR>:noh<CR>
autocmd FileType sh,zsh,python,perl,ruby noremap <silent><Leader>. :s:^\(\s*\):\1# :<CR>:noh<CR>
autocmd FileType sh,zsh,python,perl,ruby noremap <silent><Leader>, :s:^\(\s*\)# :\1:<CR>:noh<CR>
autocmd FileType vim noremap <silent><Leader>. :s:^\(\s*\):\1" :<CR>:noh<CR>
autocmd FileType vim noremap <silent><Leader>, :s:^\(\s*\)" :\1:<CR>:noh<CR>
autocmd FileType asm noremap <silent><Leader>. :s:^\(\s*\):\1; :<CR>:noh<CR>
autocmd FileType asm noremap <silent><Leader>, :s:^\(\s*\); :\1:<CR>:noh<CR>
autocmd FileType vhdl,sql noremap <silent><Leader>. :s:^\(\s*\):\1-- :<CR>:noh<CR>
autocmd FileType vhdl,sql noremap <silent><Leader>, :s:^\(\s*\)-- :\1:<CR>:noh<CR>
endif
and it works well enough to me.

I'm not an expert for setting up vim maps, but with a slightly modified search and replace pattern your initial map should do the trick:
noremap <silent>+ :s:^\(\s*\):\1// :<CR>:noh<CR>
noremap <silent>- :s:^\(\s*\)// :\1:<CR>:noh<CR>
The above regex used for commenting searches for a variable number of space characters at the beginning of a line (^\(\s*\)) and replaces the found pattern with itself (using a back reference \1) followed by the characters for the comment (\1//). The second regex shown above removes the comment chars in a similar way: search for variable number of spaces followed by comment characters (^\(\s*\)//) and replace all that with just the spaces (back reference \1).

You say you're using the map on a visual selection.
The problem, then, is that your initial :left command in your mapping ends visual mode, so that the next :s command only acts on the current line instead of the entire visual selection.
You have a couple options:
Use gv to return to visual mode on the last visual selection
Use :'<,'>s instead of plain :s to explicitly set the range to the last visual selection
Either way, you should probably change your noremap to xnoremap instead, so that it only applies in visual mode. You can make another separate nnoremap mapping for normal-mode on a single line without the gv or '<,'>

There's no doubt a better way to do it :) but the following
noremap <silent>+ :le<CR>gv:'<,'>s/^\/\/ ../\/\/ /<CR>gv==:noh<CR>
changes
int main() {
// int x = 0;
// int y = 0;
return 0;
}
to
int main() {
// int x = 0;
// int y = 0;
return 0;
}
Didn't have time to work out the uncomment one yet nor work out how best not to repeat the bits in the substitue

Your command adds // to the begining of the line because you forgot to take whitespace into consideration:
:s/^\s*/\/\/ /
But you should use a plugin instead.

Related

Vim script function on lua

I'm rewriting my vim config in lua. I have a useful function for me - it allows me to delete/select/edit numbers, here is its code and key bindings
" custom text-object for numerical values
function! Numbers()
call search('\d\([^0-9\.]\|$\)', 'cW')
normal v
call search('\(^\|[^0-9\.]\d\)', 'becW')
endfunction
xnoremap in :<C-u>call Numbers()<CR>
onoremap in :normal vin<CR>
If I press cin, the cursor in the line goes to a number, for example
fotn-size: 16px
If I press cin, I will edit the number 16
I tried rewriting the same thing in lua and this is what I got (vimscript inside lua)
function Numbers()
vim.cmd [[
call search('\d\([^0-9\.]\|$\)', 'cW')
normal v
call search('\(^\|[^0-9\.]\d\)', 'becW')
]]
end
vim.keymap.set({ 'x' }, 'in', ':<C-u>lua Numbers()<CR>', { noremap = true })
vim.keymap.set({ 'o' }, 'in', ':normal vin <CR>', { noremap = true })
At first I thought it worked fine, but after a little bit of work, I realized it didn't work correctly.
Now, if I press cin, the line
fotn-size: 16px
I will edit the number 6 and the letter p . Like this 16px
But in this example.
const my_var = 125;
when I press cin I will not edit the entire number 125, but only the last digit 5
Please help me, how can I get this function to work correctly in lua?
I guess there's some issue when switching between Lua and Vimscript internally. Since you are already using vim.cmd you can put all the vimscript in it:
vim.cmd [[
function! Numbers()
call search('\d\([^0-9\.]\|$\)', 'cW')
normal v
call search('\(^\|[^0-9\.]\d\)', 'becW')
endfunction
xnoremap in :<C-u>call Numbers()<CR>
onoremap in :normal vin<CR>
]]

using wildcards in vimrc abbreviations

I'm setting up my .vimrc for writing some HTML and currently have these lines:
abb <buffer> <h1> <h1></h1><ESC>?<<CR>i<C-R>=Eatchar('\m\s\<bar>/')<CR>
abb <buffer> <h2> <h2></h2><ESC>?<<CR>i<C-R>=Eatchar('\m\s\<bar>/')<CR>
abb <buffer> <h3> <h3></h3><ESC>?<<CR>i<C-R>=Eatchar('\m\s\<bar>/')<CR>
abb <buffer> <h4> <h4></h4><ESC>?<<CR>i<C-R>=Eatchar('\m\s\<bar>/')<CR>
abb <buffer> <h5> <h5></h5><ESC>?<<CR>i<C-R>=Eatchar('\m\s\<bar>/')<CR>
abb <buffer> <h6> <h6></h6><ESC>?<<CR>i<C-R>=Eatchar('\m\s\<bar>/')<CR>
Which automatically adds a closing tag to any opening h-tag I write, moves the cursor to the middle of them and gets rid of the whitespace with the function from :helpgrep Eatchar
My qestion is, if there is a more elgant way of doing this, by telling vim to take take <h[1-6]> and adding a closing tag with the same number, instead of having to define each case specifically?
Or for that matter, is there a way to define this whole process of adding a closing tag and moving the cursor back as a function and then just specifying all the tags I want it to be applied to? Because below this I have a quite a few lines for a bunch of other tags that look almost identical.
I've written a function to satisfy my similar requirement recently. This feature can be found in other tools like VSCode.
When I type >, it'll try to look back to check if > is in a tag like <div>. If so, it will complete it and set cursor at <div>|</div>.
Compared with emmet or snippets, this way feels more straightforward.
function! CompleteTag()
let line = getline('.')
let end = col('.') - 1
let begin = end - 1
let tagname_regexp = '[a-zA-Z0-9-]'
while begin > 0
if line[begin] !~ tagname_regexp
break
endif
let begin -= 1
endwhile
if line[begin] == '<'
let tagname = line[begin+1:end-1]
let move = MoveBack(len(tagname)+3)
return '></'.tagname.'>'.move
endif
return '>'
endfunction
function! MoveBack(n)
let move = ''
for i in range(0, a:n-1)
let move .= "\<Left>"
endfor
return move
endfunction
autocmd FileType jsx,html,vue,svelte inoremap<buffer> > <c-r>=CompleteTag()<cr>
autocmd FileType jsx,html,vue,svelte inoremap<buffer> <c-cr> <cr><esc>O
Neither :help abbreviations nor :help mapping can use a wildcard on the left hand side. One solution to that kind of problem is to loop over heading levels and create your abbreviations programatically:
for level in range(1, 6)
execute "abbreviate <buffer> <h" .. level .. "> <h" .. level .. "></h" .. level .. "><ESC>?<<CR>i<C-R>=Eatchar('\\m\\s\\<bar>/')<CR>"
endfor
It certainly is a tad more clever but I wonder if the loss of legibility is really worth it.
I'm not sure how to parameterize a single abbreviation; I suspect you'll need to delve into functions for that. You can, however, define a map consisting of the trailing part:
inoremap nowsp <ESC>?<<CR>i<C-R>=Eatchar('\m\s\<bar>/')<CR>
then use that map in your abbreviations
abb <buffer> <h1> <h1></h1>nowsp
abb <buffer> <h2> <h2></h2>nowsp
" etc

Vim function doesn't seem to be recognised

I copied this in to .vimrc:
function! ClearAllButMatches()
let old = #c
let #c=""
%s//\=setreg('C', submatch(0), 'l')/g
%d _
put c
0d _
let #c = old
endfunction
vnoremap <leader>c :<c-u>call g:ClearAllButMatches()<cr>
It doesn't seem to work. When I use <leader>c, I am getting this:
E117: Unknown function: g:ClearAllButMatches
Any ideas why its happening?
note:
I have sourced my .vimrc several times.
Remove g: from the mapping or add it to the function name

vim - set auto indent to fill the leading space with space or tabstop

It seems if we enable 'ai', vim will fill the the leading space with tabstop.
I can make it fill with just space with 'et'. I don't like a C file mixed with space and tabstop.
My vimrc:
set ts=4 et
set ai
set hlsearch
syntax on
filetype plugin indent on
autocmd FileType make setlocal noexpandtab
However, in some condition I do need to input tabstop when I hit the 'TAB' on keyboard, for example, in makefile and some others.
The 'autocmd FileType' command is not good: I can't add every file type in vimrc.
What I want is simple:
autoindent to fill leading area with
space;
when hit 'TAB' on keyboard, tabstop
input, not space (so no 'et')
How to do it?
inoremap <expr> <tab> ((getline('.')[:col('.')-2]=~'\S')?("\<C-v>\t"):(repeat(' ', &ts-((virtcol('.')-1)%&ts))))
It does the same as #Lynch answer if I read it correctly.
You can also use <C-v><Tab>: this will insert <Tab> without invoking any mappings and ignores expandtab unless you remapped <C-v> or <C-v><Tab> for some reason.
If you want to just insert tab do
inoremap <Tab> <C-v><Tab>
It will ignore expandtab setting.
I did it using a function. I tested it, but maybe in some particular case you will have to fix some bugs. Try adding this to your vimrc:
set et
function! Inserttab()
let insert = ""
let line = getline('.')
let pos = getpos('.')[2]
let before = ""
let after = line
if pos != 1
let before = line[ 0: pos - 1]
let after = line[pos : strlen(line) ]
endif
if pos != 1 && substitute(before, "[ \t]", "", "g") != ""
let insert = "\t"
else
let insert = " "
endif
let line = before . insert . after
call setline('.', line)
call cursor(line('.'), strlen(before . insert))
endfunction
inoremap <tab> <esc>:call Inserttab()<CR>a
Basicaly it does remap your key in visual mode to the function Inserttab(). Also note that if you change ts for something other than 4 it will still output 4 spaces instead of two because the value is hard coded.
Also im not very familiar with vim scripts, but I think all the variables used will be global which is a bad thing.
I forgot to mention that to "see" white spaces you can use set list. You disable this with set nolist. Also in normal mode you can use ga to see information about the character your cursor is on.
Edit
I realise that you may want to insert tab at the beginin of the line. My script insert space at the begining and tab anywhere else.
If you really want a tab every time you hit tab key you could simply use this:
set et
function! Inserttab()
let insert = ""
let line = getline('.')
let pos = getpos('.')[2]
let before = ""
let after = line
if pos != 1
let before = line[ 0: pos - 1]
let after = line[pos : strlen(line) ]
endif
let insert = "\t"
let line = before . insert . after
call setline('.', line)
call cursor(line('.'), strlen(before . insert))
endfunction
inoremap <tab> <esc>:call Inserttab()<CR>a
But I dont see the point, with this version you will never be able to indent manually from insert mode.
One way to do it is
:set sw=4 (or whatever you want)
:set ts=46 (or some large number)
Then autoindent will not insert tabs unless you reach 46 spaces, in which case you can put in a higher number.
Only drag about this is if someone else is using tabs, then you have to reset ts to agree with the file you are editing. On the other hand, it will make the tabs immediately obvious, which can be desirable as well.

How to use abbreviations in Vim with arguments?

In Vim, you can make it so each time you write "FF" on insert mode changes to some code by using:
:iab FF for ( int i = 0 ; i < n ; i++ )
But is there any way to use this with arguments? Something like C's #defines, so if I write
FF(e, 10)
It becomes:
for ( int e = 0 ; e < 10 ; e++ )
Take a look at SnipMate (a vim plugin). You won't get arguments, but upon expansion of an abbreviation, it allows you to tab through modifiable areas. In the for example, you'll be brought to the i first, can edit it to be e, and it will change it to e in all areas of the for declaration. Then simply tab to the next area you'd like to change.
From the docs:
snipMate.vim aims to be an unobtrusive, concise vim script that implements some of TextMate's snippets features in Vim. A snippet is a piece of often-typed text that you can insert into your document using a trigger word followed by a .
For instance, in a C file using the default installation of snipMate.vim, if you type "for" in insert mode, it will expand a typical for loop in C:
for (i = 0; i < count; i++) {
}
To go to the next item in the loop, simply over to it; if there is repeated code, such as the "i" variable in this example, you can simply start typing once it's highlighted and all the matches specified in the snippet will be updated.
The following is a nice helpful change to remap tab and s-tab to c-d and c-a, in case you don't want to lose the functionality of tab (in ~/.vim/after/plugin/snipMate.vim):
"""ino <silent> <tab> <c-r>=TriggerSnippet()<cr>
"""snor <silent> <tab> <esc>i<right><c-r>=TriggerSnippet()<cr>
"""ino <silent> <s-tab> <c-r>=BackwardsSnippet()<cr>
"""snor <silent> <s-tab> <esc>i<right><c-r>=BackwardsSnippet()<cr>
"""ino <silent> <c-r><tab> <c-r>=ShowAvailableSnips()<cr>
ino <silent> <c-d> <c-r>=TriggerSnippet()<cr>
snor <silent> <c-d> <esc>i<right><c-r>=TriggerSnippet()<cr>
ino <silent> <c-a> <c-r>=BackwardsSnippet()<cr>
snor <silent> <c-a> <esc>i<right><c-r>=BackwardsSnippet()<cr>
ino <silent> <c-r><tab> <c-r>=ShowAvailableSnips()<cr>
You can include function definitions in abbreviations, but they cannot take arguments. This is an example from the vimdocs:
func Eatchar(pat)
let c = nr2char(getchar(0))
return (c =~ a:pat) ? '' : c
endfunc
iabbr <silent> if if ()<Left><C-R>=Eatchar('\s')<CR>
I guess you could maybe parse the abbreviation expression in the function, but I'm not sure if you can also include characters like parenthesis in the abbreviation. Maybe something here will give you an idea.
Edit: You can always do something like this:
:iab for() for(int i = 0; i < ; i++)<C-o>T<
Which lacks the argument autocompletion of course but lets you start typing it immediately.
It worked for me:
iab FF <c-o>:FF
com -nargs=* FF call s:FF(<f-args>)
fu s:FF(i, n)
let t = "for (int a = 0; a < b; ++a) {\e"
let t1 = substitute(t, 'a', a:i, 'g')
exe 'normal! A'.substitute(t1, 'b', a:x, 'g')
exe "normal o\<space>\<BS>\e"
endf
at insert mode FF e 10<cr> will be for (int e = 0; e < 10; ++e) {<cr>.
mu-template support interactive templates. With it, you can either ask something to the user, or reuse any variable, apply computation on it if you which (detecting that i is already use in the current scope is doable), and use the result in the text you will expand.

Resources