How to use abbreviations in Vim with arguments? - vim

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.

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>
]]

How can I block moving up/down with multiple j,k keystrokes?

I learned moving with hjkl by blocking arrow keys.
I'd like to do something similiar for moving up/down with jjjjjj/kkkkk.
For example whenever I press j 4 times in a row with small delays it would jump back to original position, so I'd have to think how to move smarter to the place I want.
I'm not a fan of technical solutions to this problem (I'd rather critically reflect on my own typing occasionally), but this can be done by storing subsequent keypresses in an array, and complaining if the size becomes too large:
let g:pos = []
let g:keys = []
function! RecordKey( key )
if v:count || get(g:keys, 0, '') != a:key
" Used [count], or different key; start over.
let g:keys = [a:key]
let g:pos = getpos('.')
echo
return 1
endif
call add(g:keys, a:key)
if len(g:keys) > 4
" Too many identical movements (without count).
let g:keys = [a:key]
call setpos('.', g:pos)
echohl ErrorMsg
echomsg 'Try again'
echohl None
return 0
endif
echo
return 1
endfunction
" Reset counter after a delay in movement.
autocmd CursorHold * let g:keys = []
nnoremap <silent> j :<C-u>if RecordKey('j')<Bar>execute 'normal!' (v:count ? v:count : '') . 'j'<Bar>endif<CR>
nnoremap <silent> k :<C-u>if RecordKey('k')<Bar>execute 'normal!' (v:count ? v:count : '') . 'k'<Bar>endif<CR>
" Add more movements as you wish.
(Trying this out, I'm already annoyed by this :-)
I would suggest the following mappings:
nnoremap jjjj j
nnoremap kkkk k
This will make fast movement up and down very cumbersome. Unfortunately it will also prohibit a normal 'j' from executing very fast, as Vim will wait to see whether you want to add anything else after the first keypress to complete the binding. This can be circumvented by pressing another key afterwards (e.g. switching to insert mode with i/I/a/A or the like).

Disallow subsequent h/j/k/l

I want to force myself to not press jjjjj and rather use 5j instead. I'm looking for a solution that forbids / disables that kind of subsequent motion usage.
For initially practicing h/j/k/l instead of arrows I used
nnoremap <Left> :echoe "Use h"<CR>
nnoremap <Right> :echoe "Use l"<CR>
nnoremap <Up> :echoe "Use k"<CR>
nnoremap <Down> :echoe "Use j"<CR>
I tried to do something similar like
nnoremap jj :echoe "Use xj"<CR>
nnoremap ll :echoe "Use xl"<CR>
nnoremap kk :echoe "Use xk"<CR>
nnoremap hh :echoe "Use xh"<CR>
But this results in that even jumping with 5j needs to wait for the vim timeout.
I've checked vim-hardtime, but it also prevents me from doing things like 2j9j within the timeout, which I would hardly call a bad habit, but rather a sudden change of mind while navigating.
The following might be a starting point (to be put in your .vimrc file) from which you can develop your own plugin:
nno <silent> j :<C-U>execute "call Restrictedj(" . v:count . ")"<CR>
let g:moved1 = v:false
fu! Restrictedj(count)
if a:count > 1
exe line('.') + a:count
let g:moved1 = v:false
else
if !g:moved1
exe line('.') + 1
else
echoe 'Use xj'
end
let g:moved1 = v:true
end
endf
Such a code will make j (without count) error from the second use of it on.
The main fault is that you can only reactivate it by pressing 2j, 3j, or more, and not by pressing any other key (which would be desirable).
In principle the function can be modified in such a way that pressing each one of the four hjkl reactivates the remaining three. However I think that the ideal is that each of hjkl should be reactivated by any action other than pressing that key again.
The timeout is unavoidable by definition, but you could at least reduce the timeout by setting timeoutlen. It defaults to 1000, which is quite long. You could probably get away with lowering it to 500, especially seeing as you are planning on using this only temporarily as a training aid.
The following is a more self-contained solution: one function and four mappings for h, j, k, l.
There is no timer, but the only way to "reactivate" each of the four keys is using it with an explicit count or using one of the three other keys.
fu! NoRepHJKL(count, key, selfCall)
if !exists('g:can_use')
let g:can_use = { 'h': v:true, 'j': v:true, 'k': v:true, 'l': v:true }
endif
if a:count > 0
execute "normal! " . a:key
call NoRepHJKL(a:count - 1, a:key, v:true)
else
if a:selfCall || g:can_use[a:key]
let g:can_use.h = v:true
let g:can_use.j = v:true
let g:can_use.k = v:true
let g:can_use.l = v:true
endif
if !a:selfCall && g:can_use[a:key]
execute "normal! " . a:key
let g:can_use[a:key] = v:false
endif
endif
endf
nn <silent> h :<C-U>call NoRepHJKL(v:count, 'h', v:false)<CR>
nn <silent> j :<C-U>call NoRepHJKL(v:count, 'j', v:false)<CR>
nn <silent> k :<C-U>call NoRepHJKL(v:count, 'k', v:false)<CR>
nn <silent> l :<C-U>call NoRepHJKL(v:count, 'l', v:false)<CR>
The function
defines a global boolean dictionary for the four keys (only the first time it's called) which contains whether each of the four key can be used;
if the a:count passed to it is positive (this includes 1), it uses the key (given through the argument a:key) in normal mode and calls itself recursively, with a reduced a:count argument, and with the information that the it is a:selfCalling.
if the a:count is zero
it will make all four keys available for the next use only if it reached zero by recursion or (if not) if the a:key is not been overused;
if it is not a self call, but the a:key is not been overused, then it uses it in normal mode and makes it unavailable for the next use.

vim: mapping key to comment with indentation

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.

Jump to the end of a long list of repeated pattern

I have a big file with a lot of lines that share the same pattern, something like this:
dbn.py:206 ... (some other text) <-- I am here
dbn.py:206 ... (some other text)
...
(something I don't know) <-- I want to jump here
Is there a quick way in Vim to jump to the place where the succession of dbp.py:206 ends?
/^\(dbn.py\)\#!
Matches first line which does not start with the text inside the escaped parentheses.
If you want quick access to this you could add a vmap which yanks the visually selected text and inserts it in the right spot (but first escaping it with escape(var, '/').
Try this vmap: vmap <leader>n "hy<Esc>/^\(<C-R>=escape(#h,'/')<CR>\)\#!<CR>
Press n when visually selecting the text you wish to skip and you should be placed on the next first line which does not begin with the selection.
I just write a function to select identical lines:
nnoremap vii :call SelectIdenticalLines()<CR>
fun! SelectIdenticalLines()
let s = getline('.')
let n = line('.')
let i = n
let j = n
while getline(i)==s && i>0
let i-=1
endwhile
while getline(j)==s && j<=line('$')
let j+=1
endwhile
call cursor(i+1, 0)
norm V
call cursor(j-1, 0)
endfun
type vii to select identical lines (feel free to change the key-binding)
type zf to fold them.
type za to toggle folding
It's handy when you want to squeeze several empty line.
It acts like C-x C-o in emacs.
One option is to go to the bottom of the file and search backwards for the last line you want, then go down one:
G ?^dbn\.py:206?+1

Resources