created March 13, 2006, Updated April 21, 2013 · complexity intermediate · author Paul Donohue (Alternative by Joe Pea) · version 7.0
Original Solution Edit
From the Vim FAQ:
10.2. In insert mode, when I press the <Esc> key to go to command mode, the cursor moves one character to the left (except when the cursor is on the first character of the line). Is it possible to change this behavior to keep the cursor at the same column? No. It is not possible to change this behavior. The cursor is *always* positioned on a valid character (unless you have virtual-edit mode enabled). So, if you are appending text to the end of a line, when you return to command mode the cursor *must* drop back onto the last character you typed. For consistency sake, the cursor drops back everywhere, even if you are in the middle of a line. You can use the CTRL-O command in insert mode to execute a single ex command and return back to insert mode without moving the cursor column.
If you don't care about consistency and only want the cursor to drop back when necessary when exiting insert mode, try:
inoremap <silent> <Esc> <C-O>:stopinsert<CR>
If the cursor is not on a valid character (for example, at the end of a line), it will still be moved back one character (unless virtual-edit mode is enabled).
If you are in paste mode and hit <Esc>, the cursor will still be moved back one character (since all mappings are ignored in paste mode).
But, otherwise, when using this mapping, <Esc> generally won't move the cursor.
If you have any other mappings which begin with <Esc> (for example, 'map <S-Up> ...' doesn't seem to work right for me, so I use 'map ^[[1;2A ...', which starts with <Esc>), then 'timeoutlen' will apply to this mapping, and the cursor will move one character to the left until 'timeoutlen' expires, then will move back to the proper position.
Programmatic Alternative Edit
Mapping the <esc> key leads to problems most of the time. Here's how to do it programmatically:
let CursorColumnI = 0 "the cursor column position in INSERT autocmd InsertEnter * let CursorColumnI = col('.') autocmd CursorMovedI * let CursorColumnI = col('.') autocmd InsertLeave * if col('.') != CursorColumnI | call cursor(0, col('.')+1) | endif
Putting the above in your vimrc keeps track of the cursor position when in INSERT mode. When exiting INSERT mode, it will move the cursor ahead one column if the cursor position has changed after leaving insert.
The way it's suggested in the tip, it breaks the immensely useful Visual-block Insert mode (:h v_b_i). This seems to not break anything (except when used in console Vim over ssh -- then any <Esc> remapping breaks escape sequences for some reason):
inoremap <silent> <Esc> <Esc>`^
Here are some suggestions regarding making keycode maps more responsive:
hmmm... interesting... when I remove all my mappings beginning with an <Esc> character, this little trick causes all sorts of problems. It only seems to work properly when there is some mapping beginning with an <Esc> character. Very strange behavior.
Perhaps that's why the Karma on this tip is horrible?
If you are on a terminal, a quick and dirty way is to type Alt+L to switch to normal mode.
I don't think it's fair to say that if you want this behavior, then "you don't care about consistency". The default behavior is also "inconsistent" when you are at the beginning of a line; those two behaviors are totally symmetric in that sense.
Exit Insert mode without moving cursor -- this works: Edit
The solutions above (and those found on similar pages) do not work on my terminal (Yakuake). Specifically, mapping Esc to <Esc>`^ causes strange behaviour (insertion of line breaks and 'A', 'B', 'C' or 'D' characters) with arrow keys, and the del key. And while one may argue we are not to touch these keys in vim, for me, I'd rather something not go wrong if I do once in a while.
My advice is don't remap Esc to anything else within vim. I have swapped my CapsLock with Esc globally (if you have KDE desktop, it's one checkbox in 'system settings' > 'input devices' > 'advanced keyboard settings'). This works just fine in vim. But if I were to then map the Esc command to something else, it will cause side effects. This will be the case regardless of your global key mapping. Many commands in vim rely on Esc and once you remap it to anything else you will have side effects. In other words make sure <Esc> is not in the left hand side of any of your custom key mappings (just my advice to keep it simple, although I'm sure there is an overly-complex work around).
Thankfully, a small change to some of the ideas here does work nicely and provides a clean and fast way of exiting insert mode without moving the cursor. Namely, I've left the Escape command alone, so it still works as before -- as do the various ways of entering insert mode. Choose a short fast sequence you like. I chose 'fj' and its typo partner 'jf', either of which when typed quickly will exit insert mode and preserve the cursor position (unless at the end of a line and not on a valid character). If you need to type "fjord", you have to wait the timeout length, which by default is 1 second (timeoutlen=1000) after pressing 'f', before pressing 'j'. I recommend shortening this to about 300 (3/10ths of a second), so you won't notice the wait when necessary (see below).
Notes: Ctrl-c is the same as pressing Esc when in insert mode. inoremap is non-recursive key mapping for insert mode. Simply adding one 'l' after the Esc command, moves cursor one right. Pressing <A-l> in insert mode works fine but is slow to type. Mapping to it causes side effects. And <C-o> is problematic. Finally, don't add in-line comments when remapping.
Add the following to your .vimrc and restart your terminal:
" Exit INSERT mode without moving cursor (press fj or jf)
set timeoutlen=300 inoremap fj <Esc>l inoremap jf <Esc>l
This works the same:
" Exit INSERT mode without moving cursor (press fj or jf)
set timeoutlen=300 inoremap fj <C-c>l inoremap jf <C-c>l
Now you can exit Insert mode with ease without moving the cursor! Happy coding! --September 18, 2014
- The problem with <Esc>l is that it fails when you're at the beginning of the line. The autocmds above work in all cases. --September 28, 2014
To get consistent behavior at the end and the beginning of the line, one can insert a symbol (e.g., 'x') and then delete it in a 'black hole' register:
set timeoutlen=300 inoremap fj x<C-c>"_x