I am trying to replicate the behavior of beg abbreviation shown on the page https://castel.dev/post/lecture-notes-1/ with vimscript. That means I want to make it so that when I type "beg" in insert mode at the beginning of the line, it calls a function that i choose.
I tried to make it with iabbrev, but it doesn't expand until i hit space, and space is typed after my abbreviation. It also doesn't recognize if it is the beginning of a line.
Another approach I tried is with an auto command. I added to my init.vim the following
function SayHello ()
if (getline('.') =~ "\s*beg$")
s/^\(\s*\)beg/\1\\begin{}\r\r\\end{}/
endif
endfunction
autocmd TextChangedI *.tex call SayHello ()
This almost works, but for one problem. It doesn't work if the autocomplete popup is active, which is always because of a plugin I use. So the effect of this code is that as i first type beg, there is no effect, but if i backspace one letter and add it again, it works as intended. I tried to remedy it by adding the command
autocmd CompleteChanged *.tex call SayHello ()
so that it works anyway if there is autocomplete. Unfortunately it doesn't work, because it tries to edit the buffer of the popup. I tried making the function exit the popup to no effect.
How can I make it work like on the site?
Especially useful would be knowing how to use the TextChangedI command regardless of autocomplete because that would make other useful things possible.
Create a harmless insert mode mapping:
inoremap beg beg
It inserts beg when you type beg. Cool.
Turn it into an equally harmless "expression mapping":
inoremap <expr> beg 'beg'
In an expression mapping, the right-hand side is an expression that is evaluated when you press the left-hand side in order to produce the macro that is going to be executed for you. Here, the expression is the string beg so the mapping works just like the first one.
Experiment with logic in the RHS:
inoremap <expr> beg &filetype == 'foo' ? 'beg' : 'geb'
Here, you either get a beg or a geb, depending on the current filetype. Cool, it's getting interesting!
Make it insert a crude snippet only when at the beginning of a line:
inoremap <expr> beg col('.') == 1 ? 'begin{}<CR>end{}<C-o>O' : 'beg'
Make it possible to name the environment "dynamically":
inoremap <expr> beg col('.') == 1 ? substitute('\begin{+++}<CR>\end{+++}','+++',input('environment: '),'g').'<C-o>O' : 'beg'
OK. It works, but it feels clunky.
That's where one usually starts to wonder whether it is actually worth it to roll one's own when existing plugins already handle all that much more elegantly.
Related
There might be a simple solution for this. I couldn't find any solution to it (I might be searching with wrong context)
Here is my requirement.
I wrote the below key mapping in vimrc. It should print the line "Hello user_name." n times, where n and user_name are the user input once the key is pressed.
autocmd FileType ruby nnoremap <expr> <C-h> :call FuncPrnt(<-syntax to pass input from user->)
function! FuncPrnt(count, uname)
let c=a:count
let i=0
while i<c
call append(line("."), "Hello ".a:uname.".")
let i+=1
endwhile
endfunction
On Pressing the key user enters 3 and 'Ironman'. The output would be like
Hello Ironman.
Hello Ironman.
Hello Ironman.
Thanks in advance
A simpler approach is to just use a non-<expr> mapping to prepare the Ex command without executing it:
nnoremap <C-h> :call FuncPrnt(,"")<left><left><left><left>
If you associate that with a filetype (such as "ruby" in your case), make sure you create a local mapping using <buffer>. Otherwise the mapping will be global and will work on every buffer and not only those with Ruby source files.
If you use an autocmd in your vimrc, make sure you wrap it in an augroup to prevent getting duplicated commands if you reload your vimrc.
Alternatively, you can add filetype mappings for Ruby in a file ~/.vim/ftplugin/ruby.vim which gets automatically loaded by Vim whenever a file of type Ruby is loaded. (That way you don't need to use explicit autocmds, Vim will take care of those details on your behalf.)
Got the answer. Just in case someone else is searching for similar solution
autocmd FileType ruby nnoremap <expr> <C-h> input("", ":call FuncPrnt(,\"\")<left><left><left><left>")
will print the command and waits for the user to edit. Once the user edits and enters it is executed
Thinking outside of the box a little bit (questioning the premise), perhaps the best in this case is to define a user-defined command instead of a mapping.
You can define one here with:
command! -buffer -bar -count=1 -nargs=1 FuncPrnt
\ call FuncPrnt(<count>, <q-args>)
That way you can use it with:
:3FuncPrnt Ironman
If you omit the count, 1 will be used. (You can pick a different default as the argument to -count=N.)
You can use Tab completion for FuncPrnt, so perhaps :3Fu<Tab> or even :3F<Tab> might be enough to complete the command.
This might end up being quicker or more convenient to type than <C-H>3<right><right>, since it doesn't involve moving your hand to the arrow keys.
I have two remaps in my .vimrc for the insert mode:
inoremap jk <ESC>
inoremap {<CR> {<CR>}
The first remap should have a short timeoutlen, whereas the latter should have no timeout at all. Reason: I need a timeout for the first remap, as jk might be used as regular characters. The latter on the other hand is code completion, where a timeout makes no sense.
Is it possible to assign different timeoutlen's for different remaps? Thanks & BR
One way to extend the timeout of your second mapping is to actually only map <CR> and then use an expression to check that it's being typed after a { character.
The behavior is somewhat different from a two-character mapping in that the mapping will also work if you're typing a <CR> after a { that was already there, which might be acceptable to you (or might even be exactly what you wanted.)
Since you're using an expression to do checks, you can do additional checks such as only applying the mapping if you're typing the <CR> at the end of a line (so you avoid it if you're using it to split an existing long line.)
A possible implementation of that would be:
inoremap <expr> <CR>
\ col('.') ==# col('$')
\ && getline('.')[col('.')-2] ==# '{'
\ ? "\<CR>}\<C-O>O"
\ : "\<CR>"
We're using a ternary operator here to produce a different output based on the condition holding or not. If it doesn't, we simply map back to the keystroke itself, so it keeps working as it usually does in all other contexts.
For the part that inserts a closing curly brace, I also added a CTRL-O O to the end, so after inserting the brace, it will add a new line before it and leave the cursor in an indented column inside the block. I hope you'll find this helpful, but feel free to disregard that part if it's not really what you had in mind.
There is a important part of vim mapping:
:map <C-j>r f{^
I noticed, that when the { character is not found in the line, then the rest of the mapping is not executed.
Is there a way, how to force the mapping to continue even though the search character is not found? (In this case execute the return to the beginning of the file)
Yes, Vim aborts on an error in a mapping. You can use :silent! normal! ... to continue regardless of errors. With a sequence of :normal! commands, you can even check (e.g. whether the cursor moved) and react to it. Sketch:
:map <C-j>r :exe 'normal! maf{'<Bar>if getpos("'a") == getpos(".")<Bar>echo "no move"<Bar>endif<CR>
Note that this doesn't scale well. You're better of moving the commands into a :function.
Also, you should probably use :noremap; it makes the mapping immune to remapping and recursion.
The literal answer to this question would be:
:map <C-j>r :silent! exe "normal f{^"<CR>
I am trying to do a comment remap in Vim with an inline if to check if it's already commented or not. This is what I have already and of course it's not working ha ha:
imap <c-c> <Esc>^:if getline(".")[col(".")-1] == '/' i<Delete><Delete> else i// endif
What I want to do is check the first character if it's a / or not. If it's a / then delete the first two characters on that line, if it's not a / then add two // in front of the line.
What I had originally was this:
imap <c-c> <Esc>^i//
And that worked perfectly, but what I want is to be able to comment/uncomment at a whim.
I completely agree with #Peter Rincker's answer warning against doing this in insert mode, and pointing you to fully-featured plugins.
However, I couldn't resist writing this function to do precisely what you ask for. I find it easier to deal with this kind of mapping with functions. As an added bonus, it returns you to insert mode in the same position on the line as you started (which has been shifted by inserting or deleting the characters).
function! ToggleComment()
let pos=getpos(".")
let win=winsaveview()
if getline(".") =~ '\s*\/\/'
normal! ^2x
let pos[2]-=1
else
normal! ^i//
let pos[2]+=3
endif
call winrestview(win)
call setpos(".",pos)
startinsert
endfunction
inoremap <c-c> <Esc>:call ToggleComment()<CR>
Notice the modifications to pos to ensure the cursor is returned to the correct column. The command startinsert is useful in this type of function to return to insert mode. It is always safer to use noremap for mappings, unless there is a very good reason not to.
This seems to work well, but it is not very Vim-like, and you might find other plugins more flexible in the long run.
There are many commenting plugins for vim:
commentary.vim
tComment
EnhCommentify
NERD Commenter
and many more at www.vim.org
I would highly suggest you take a look at some these plugins first before you decide to roll your own. It will save you great effort.
As a side note you typically would want to comment/uncomment in normal mode not insert mode. This is not only the vim way, but will also provide a nicer undo history.
If you are dead set on creating your own mappings I suggest you create a function to do all the hard work and have your mapping call that function via :call. If you think you can get by with simple logic that doesn't need a function then you can use an expression mapping (see :h map-<expr>). You may want organize into a plugin as it could be large. If that is the case look at :h write-plugin to give you a feel
for writing plugins the proper way.
Example of a simple expression mapping for toggling comments:
nnoremap <expr> <leader>c getline(".") =~ '\m^\s*\/\/' ? '^"_2x' : 'I//<esc>`['
there's also this vimtip! http://vim.wikia.com/wiki/Comment/UnComment_visually_selected_text
i use the bottom one with the
...
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>
,c comments out a region
,u uncomments a region
I wanted to add a way to run :noh that made the most sense to me, so I added the following
nnoremap / :noh<CR>/
nnoremap ? :noh<CR>?
So far it's working exactly how I expected (and want): hilights are cleared as another search is started and typing /<backspace> makes more sense for me than <leader><space> or similar.
My concern is that this will somehow break other useful commands or a plugin.
Anyone know if I'm safe doing this? Thanks.
What you can break in third party plugins
Every mapping that you define can break plugins, if they are badly written, or if they rely on mappings that you have redefined.
Consider a plugin that uses map instead of noremap or normal instead of normal!: if the right-hand side of that mapping or the normal mode commands include /, then your mapping will be triggered.
Still, it wo'nt break much, it's just a matter of display.
What you can break in a normal use of vim
But there is still a case where you break something: Try this
iI am typing in insert modeCTRL-O/helloEnter
Normally, CTRL-O in insert mode temporarily switches to normal mode for one command. You've just broken that, because the call to :noh consumes that command.
You still can do it this way:
function ResetPattern(forward)
noh
redraw
return a:forward ? '/' : '?'
endfunction
nnoremap <expr> / ResetPattern(1)
nnoremap <expr> ? ResetPattern(0)
I think it's safe. But, it will lose some functionality.
For example:
Type /helloEnter will highlight all hello
Type /world to inc-search, then type ESC to return where you are. But the original highlight is lost.
I just have this mapping in my .vimrc:
nmap <BS> :noh<CR>
Normal mode mapping <C-l> usually refreshes the screen. I have modified it to clear highlightings. Easily available and comes from a reflex nowadays. Doesn't destroy any functionality and clearing the highlights can be thought as a part of the screen refreshing.
nnoremap <C-l> :nohl<CR><C-l>