43

Is it possible to cancel the said behavior?

A task for extra credit: Figure out a way to force Vim to refresh the cursor position immediately after exiting Insert mode.

ib.
  • 27,830
  • 11
  • 80
  • 100
Juliusz
  • 1,300
  • 1
  • 13
  • 22
  • 8
    Interesting question. I wonder what is the reason behind this behavior. I just accepted it. – Stefano Borini Feb 19 '10 at 10:13
  • 4
    I tried it. It's kinda inconvenient. With cursor moving I could *see* if I left editing mode. – P Shved Feb 19 '10 at 11:47
  • 1
    @StefanoBorini There's part of explanation in [this question here](http://stackoverflow.com/questions/3676388/cursor-positioning) on SO. As I understand it: When you're exiting Insert mode, vi does not know whether you came in using `a` or `i`, so it assumes `a`. And really: cursor does not "slide away" when using `a` and Esc repeatedly. – Alois Mahdal Mar 12 '12 at 14:18
  • However, IMHO this assumption is not very correct, at least from POV of what seems intuitive (we call it *insert* mode, don't we)? – Alois Mahdal Mar 12 '12 at 14:20
  • I have Powerline and even the command line, so *two* lines can make it clear to me whether I have just exited insert mode or not, the movement of the cursor is entirely unnecessary and trips me up when I want to delete stuff after my cursor with a quick `D` and such. I would think that something based on `:autocmd InsertLeave` could work?? – Steven Lu Jun 11 '13 at 21:46
  • @PavelShved If you stop the cursor blinking then as well as being generally less annoying, the mode you're in is much clearer (block for normal, thin caret for insert, half cursor for when you press c etc). –  Jan 23 '14 at 11:42
  • A similar question with an excellent answer that discourages modifying default behavior: https://vi.stackexchange.com/questions/3138/cursor-moves-one-character-backwards-on-exiting-insert-mode#3140 – chb Aug 21 '18 at 06:06
  • @StefanoBorini My guess is because vi didn't let you put the cursor past the end of the line. Luckily vim has the `virtualedit` option to fix that oddity as well. – Mikel Nov 26 '18 at 16:17

7 Answers7

27

Although I would not recommend changing the default cursor mechanics, one way of achieving the behavior in question is to use the following Insert-mode mapping.

:inoremap <silent> <Esc> <Esc>`^

Here the Esc key is overloaded in Insert mode to additionally run the `^ command which moves the cursor to the position where it had been the last time Insert mode was left. Since in this mapping it is executed immediately after leaving Insert mode with Esc, the cursor is left one character to the right as compared to its position with default behavior.

Unlike some other workarounds, this one does not require Vim to be compiled with the +ex_extra feature.

ib.
  • 27,830
  • 11
  • 80
  • 100
  • 1
    Yes, and it's more consistent, I think. Appending already means moving the cursor one character right regardless have you entered characters or not. – ib. Feb 19 '10 at 12:40
  • 1
    That depends on where you think the cursor is: if you consider the cursor to be just after the highlighted character then `a` inserts at the current position and `i` moves the cursor back and inserts. Either way is logical depending on where you consider the cursor to be. I guess the folks who wrote vi considered it to be after the highlighted character. – DrAl Feb 22 '10 at 18:33
  • 10
    Unfortunately, this solution messes up Vim in a terminal when pressing the arrow keys and the function keys. I'm in the process of trying out this fix right now: `autocmd InsertLeave * :normal \`^` – Nathan Neff Jan 26 '12 at 07:51
  • 1
    @Nathan: It is a good suggestion! However, I recommend to get used to the default Vim behavior. It pays off. – ib. Jan 26 '12 at 14:25
  • @ib +1, I switch, revert and prepare new machines *a lot* and this approach paid off to me like million times. (By far I'm not talking only about vim.) But still: for less "nomadic" people, building super-comfy environment certainly can be a better approach. – Alois Mahdal Mar 12 '12 at 14:24
  • 1
    @NathanNeff that is the best solution I've ever seen and far surpasses the actual answer you replied to. – robru Sep 28 '14 at 04:18
  • 1
    If you're a fan of the home row and want to avoid the escape key, you should use `Ctrl-[` instead of escape, see [this](https://vim.fandom.com/wiki/Avoid_the_escape_key). So instead of remapping my escape key I decided to take this excellent answer and change the key from `[` to `]` for when I want to move forward: ```inoremap `^``` – oschoudhury Apr 11 '20 at 17:39
17

Although there are tricks to deal with this (such as the ESC mappings mentioned in the previous two posts), there's no consistent way to do this. The reason is that there is no way to determine the method that was used to enter insert mode. Specifically, given the string abcDefg with the cursor on the D:

  • If you press i, the insert mode location will be between the c and D. A normal ESC will put the cursor on c; <C-O>:stopinsert<CR> (or the backtick method) will put the cursor on D.

  • If you press a, the insert mode location will be between the D and e. A normal ESC will put the cursor on D; <C-O>:stopinsert<CR> will put the cursor on e.

If you REALLY want to do this, you could fudge it with something like this:

let insert_command = "inoremap <ESC> <C-O>:stopinsert<CR>"
let append_command = "iunmap <ESC>"
nnoremap i :exe insert_command<CR>i
nnoremap a :exe append_command<CR>a

BUT: remember that this will only deal with i and a as methods of entry: if you use visual block mode, I, or A or whatever, you'll need to come up with new commands to match (and there are a lot of them). Therefore, I'd strongly recommend you don't do this.

Personally, I'd recommend getting used to the default behaviour. You can easily make it logical for i OR logical for a. If you change the default to logical for i at the expense of logical for a, you'll just get confused when you use a standard vi/vim install.

mtk
  • 13,221
  • 16
  • 72
  • 112
DrAl
  • 70,428
  • 10
  • 106
  • 108
11

Based on Nathan Neff's comment, the best approach I've found is

autocmd InsertLeave * :normal! `^
set virtualedit=onemore

autocmd moves the cursor back to where it was when insert mode ended (i.e. one forward compared to the default).

virtualedit makes it act consistently at end of line (so it can be one forward of the last character on the line).

(Edited: normal! to avoid recursive mappings)

André Willik Valenti
  • 1,702
  • 14
  • 21
Mikel
  • 24,855
  • 8
  • 65
  • 66
8
inoremap <silent> <Esc> <C-O>:stopinsert<CR>

in your .vimrc.

ib.
  • 27,830
  • 11
  • 80
  • 100
tur1ng
  • 3,139
  • 5
  • 24
  • 31
  • This mapping will make it logical if you enter insert mode with `i` but illogical if you enter insert mode with `a`. – DrAl Feb 19 '10 at 12:41
  • 2
    well, you could remap normal mode `i` to set a flag, then remap insert mode `` to check that flag, and if set, use `:stopinsert`, and then clear the flag either way. – rampion Feb 19 '10 at 15:12
  • 1
    I'm not convinced this is entirely illogical for `a`. In general, after each change, when I leave that change, I'm done with that change, and the next thing I would like to do will probably involve making a different change. Thus, to me, upon leaving insert mode (no matter how I entered it), I'd generally prefer the cursor to be on the next character so I'm ready for my next change, rather than being ready to change what I just inserted. (mostly copied from my comment here: http://unix.stackexchange.com/a/11403/38050) – Kyle Strand Feb 07 '14 at 21:14
  • This one worked for me both VIM 7.4 and VIM 8.2 but 7.4 is not updating the mode line on exit of insert mode. – Mr Fry Jul 14 '20 at 00:10
4

I do believe the proper way to do this is

au InsertLeave * call cursor([getpos('.')[1], getpos('.')[2]+1])
Steven Lu
  • 41,389
  • 58
  • 210
  • 364
  • 1
    This doesn't behave correctly on column one, though. Insert `if (getpos('.')[2] > 1) |` before the `call` to fix it. (Also, it should be part of an `augroup` to prevent cumulative jumps forward when reloading a `.vimrc` that contains this autocommand.) – Kyle Strand Feb 10 '14 at 21:24
  • Can you give an example of how to do the `augroup` thing? Thanks – Steven Lu Feb 10 '14 at 23:41
  • This worked the best for me but I had to use 'set timeoutlen=100' as the InsertLeave event took a full second before it would trigger – Gearoid Murphy Feb 11 '14 at 22:06
  • 1
    Do you mean `ttimeoutlen`? I personally have `ttimeoutlen` set to `10` milliseconds: `ttimeoutlen` is the timeout leeway given to your terminal emulator client to emit terminal escape sequences, such as the 4-byte sequence `\033[[C` for the right-arrow-key. The reason that insert mode is not actually exited for real until after this timeout should be evident -- Vim won't know if your terminal is about to follow up with an escape sequence, or to commit to Esc as an exit-insert-mode command. Notice carefully and realize that your Vim setup was already doing this, delaying exiting insert mode. – Steven Lu Feb 12 '14 at 01:48
  • I cannot edit this answer, but here is what Kyle Strand in the first comment talked about: `autocmd! InsertLeave * if (getpos('.')[2] > 1) | call cursor([getpos('.')[1], getpos('.')[2]+1]) | endif` So the autocommand won't break in the first column. Also, see a similar solution here: https://vim.fandom.com/wiki/Prevent_escape_from_moving_the_cursor_one_character_to_the_left – john c. j. Sep 07 '21 at 09:54
2

There is an approach from the Vim Tips wiki that has worked well for me for...I don't know how many years:

" Leave insert mode to the *right* of the final location of the insertion
" pointer
" From http://vim.wikia.com/wiki/Prevent_escape_from_moving_the_cursor_one_character_to_the_left
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
Kyle Strand
  • 15,941
  • 8
  • 72
  • 167
0

What about:

:imap <Esc> <Esc><Right>
Paolo Tedesco
  • 55,237
  • 33
  • 144
  • 193
  • With certain settings, that will move cursor into the next line if editing happened at the end of the current one. – P Shved Feb 19 '10 at 10:43
  • 1
    Even ignoring the end-of-line thing, this will make it logical if you enter insert mode with `i` but illogical if you enter insert mode with `a`. – DrAl Feb 19 '10 at 12:40