created March 13, 2006, Updated April 21, 2013 · complexity intermediate · author Paul Donohue (Alternative by Joe Pea) · version 7.0
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.
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.