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.
Related
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.
I would like to create a mapping in vim that would switch the current line with a line that is n lines away.
So far, I have something like this:
:nnoremap gl:<C-U> dd{v:count1}jO<Esc>pjdd{v:count1}o<Esc>p
But it does not work. How can I use the v:count variable in this context?
Mappings are evaluated exactly as typed. Things like v:count are special Vimscript variables, so its value somehow needs to be interpolated into the keys of the right-hand side. With mappings, there are two possibilities:
execute
With :execute, you can break out from the normal mode (started by :nnoremap) into Ex mode and assemble the command; you need :normal! to run the normal mode command(s) that were previously just put as the right-hand side. As Vim automatically translates any [count] given to the mapping into an Ex range, <C-u> is necessary to clear that.
nnoremap <silent> gl :<C-u>execute 'normal! yyma' . v:count1 . "jVp'aVp"<CR>
map-expr
The <expr> modifier adds a layer of indirection to mappings: the right-hand side is evaluated and only the result is taken as the commands to run. There's a similar complication with [count] here; a preceding <Esc> command cancels it (so you only yank a single line).
nnoremap <expr> gl '<Esc>yyma' . v:count1 . "jVp'aVp"
your implementation
I assume you chose gl as the left-hand side, and the :<C-U> is an attempt at escaping, like I've done with my first approach. The space that separates left-hand side from right-hand side is missing there, and the :normal, too.
I've made the following changes to the implementation:
Deleting and pasting next to it complicates the counting; instead, I've used paste over a visual selection (Vp); this automatically puts the removed text into the unnamed register (so it can be reused for the second part of the swap).
Instead of using the count a second time to jump back, I've set a mark (ma) for simplicity. You might also be able to employ the jump list (<C-o>) for this.
other implementations
For simple swaps, your implementation might suffice, but it's far from perfect. There might be a desire to swap multiple (e.g. visually selected) lines, redo a swap elsewhere with ., and check that the destination line actually exists.
The unimpaired.vim plugin has a swap mapping [e. My LineJuggler plugin also has a robust and universal implementation (as well as many other related ones).
I am a happy VIM user, although I admit I'm quite far from being fluent. I found this nice post:
Vim clear last search highlighting and I thought I'd become a better person if I didn't have to hammer away a random character sequence every time I did a search. Now, I'm also using the vimrc config from here:
http://amix.dk/vim/vimrc.html
and the problem I have is that when I add the line nnoremap <esc> :noh<return><esc> to it (it doesn't seem to make a difference where I put it) I get awkward behaviour when I use arrows in command mode, namely letters from A to D appear in a newline and I get switched to insert mode.
There has to be some mapping conflict but for the life of me I can't figure out where it is.
EDIT: As it follows from the answers it turns out the Ultimate vimrc part is not relevant, the mentioned nnoremap command will cause altered arrow behaviour even if it's the only vimrc entry. Changing title to a more informative one.
PS. I know I shouldn't use arrows, hopefully I'll get there one day.
The mapping
nnoremap <esc> :noh<return><esc>
will conflict with so called "grey keys" and I believe that it should be used either in GVim only or in terminal Vim by someone who does not use special keys like arrows.
From what I know (and guess) how Vim processes keys, I would say that it's impossible to do anything with this. For Vim to recognize special key all its components should go in a row, so when you press Arrow Left Vim gets the following sequence of codes:
<esc> [ D
But after your mapping Arrow Left becomes:
: n o h l <cr> <esc>
[ D
Vim sees two separate sequences and treats <esc> as a single press of Escape key, thus next two codes of Left Arrow key lose their special meaning.
So I suggest you to map :noh to some other key sequence (e.g. to one starting with <leader>, see :help mapleader; I don't recommend you to use F-keys, using them is as bad as using of arrow keys).
The cause had been explained well, but solution was not mentioned. However there is a straight one.
If you’ll tell to Vim explicitly that there are key sequences starting from <esc>[
:nnoremap <silent><esc> :noh<CR>
:nnoremap <esc>[ <esc>[
than when single <esc> will be pressed Vim will wait for a second (or different time, see :h 'timeoutlen') or for a next key (second <esc> for example) and only then replace it with :noh<CR>.
This solution preserves the ESC mapping to :nohlsearch.
The comment on this answer explaining why this is happening tells us that the root cause is the TermResponse behavior of vim. This can be compensated for by wrapping the mapping in an autocommand for the TermResponse event.
This ensures that the binding doesn't happen until after the term response is set, which prevents Esc from also sending a string like ]>1;3201;0c to vim.
Change your line in vimrc to this:
augroup no_highlight
autocmd TermResponse * nnoremap <esc> :noh<return><esc>
augroup END
The augroup commands are not strictly necessary, but they prevent multiple mappings when you reload your vimrc without quitting vim.
EDIT: If you also use a graphical vim like Gvim or Macvim, the TermResponse event will not fire. Assuming you use a single vimrc, you'll need some additional code like
if has('gui_running')
nnoremap <silent> <esc> :nohlsearch<return><esc>
else
" code from above
augroup no_highlight
autocmd TermResponse * nnoremap <esc> :noh<return><esc>
augroup END
end
Problem is that when you press an arrow terminal emits something like <Esc>OA. Vim part that supports terminal seems to use the same mapping mechanism to do the job as you are using: while nmap <Esc>OA will tell you nothing, call feedkeys("\eOA") will move one line up and call feedkeys("\eOA", 'n') will add letter A beyond current line. With your mapping being noremappable you forbid vim to use <Esc> as a part of the key. The problem is that you need remappable mapping here, but can have remappable mapping without it being recursive as well only if it starts with {lhs}, but <Esc>:noh<CR>OA is not going to work. I thought the following code will (it uses <expr> and function with side effect to make <Esc> be the first character of the actual {rhs} and still launch :noh), but in fact it does not:
function s:NoHlSearch()
nohlsearch
return "\e"
endfunction
nmap <expr> <Esc> <SID>NoHlSearch()
. I have no other idea how to solve the problem of having non-recursive remappable mapping which includes {lhs} but not at the start.
I have had good luck with this
if $TERM =~ 'xterm'
set noek
endif
nnoremap <silent> <esc> <esc>:noh<cr>
The disadvantage is that function keys can not be used in insert mode.
:h ek
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'm trying to change the behavior of "K" in certain circumstances, but keep its original functionality in all the others. Here's my failed attempt
function! GetDocumentation()
if &filetype == 'java'
JavaDocSearch
else
normal K
endif
endfunction
nnoremap K :call GetDocumentation()<CR>
However since I use K in a function, when it's called as result of the remapping, the new mapping of K is used, and I get infinite recursion. I guess I could somehow fit the gist of the function onto the nnoremap line, but it seems awkward, and it would be nice to forcefully use the original mapping of a key.
Hope this makes sense,
Thanks.
The :normal command without the ! modifier—as it is used in the
function—executes its arguments as Normal mode commands taking mappings into
consideration. This is why the line
normal K
runs the mapping that has overridden the default handler. So, to ignore
mappings shadowing commands, change the line as follows,
normal! K
#ib. is right: you should be using :normal! here. But function is an overkill: the following expression mapping is sufficient:
nnoremap <expr> K ((&filetype is# "java")?(":JavaDocSearch\n"):("K"))
This mapping won’t trigger itself due to nore and also is eight lines shorter.