How to prevent the cursor from moving back one character on leaving Insert mode in Vim?
Asked Answered
A

7

45

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.

Averir answered 19/2, 2010 at 10:6 Comment(8)
Interesting question. I wonder what is the reason behind this behavior. I just accepted it.Margarite
I tried it. It's kinda inconvenient. With cursor moving I could see if I left editing mode.Chaudfroid
@StefanoBorini There's part of explanation in this question here 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 <kbd>Esc</kbd> repeatedly.Fireeater
However, IMHO this assumption is not very correct, at least from POV of what seems intuitive (we call it insert mode, don't we)?Fireeater
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 <esc>D and such. I would think that something based on :autocmd InsertLeave could work??Tarkany
@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).Egbert
A similar question with an excellent answer that discourages modifying default behavior: vi.stackexchange.com/questions/3138/…Harleigh
@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.Randazzo
Y
30

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.

Ynes answered 19/2, 2010 at 12:31 Comment(7)
Yes, and it's more consistent, I think. Appending already means moving the cursor one character right regardless have you entered characters or not.Ynes
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.Shadshadberry
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 `^Dnepropetrovsk
@Nathan: It is a good suggestion! However, I recommend to get used to the default Vim behavior. It pays off.Ynes
@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.Fireeater
@NathanNeff that is the best solution I've ever seen and far surpasses the actual answer you replied to.Zworykin
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. 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 <silent> <C-]> <Esc>`^Hollo
S
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.

Shadshadberry answered 19/2, 2010 at 12:32 Comment(0)
R
12

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)

Randazzo answered 26/11, 2018 at 16:14 Comment(2)
If you use plugin mg979/vim-visual-multi: autocmd InsertLeave * if !exists('b:visual_multi') | exe 'normal! `^' | endifInez
I'd suggested inoremap <Esc> <C-o>m[single-backtick-here]<Esc>[double-backticks-here] for VsCodeVim, but it messes up the . to repeat last operation. So, better stick with inoremap <Esc> <Esc>l and accept that cursor goes to column 2 when it's on column 1.Inez
A
7
inoremap <silent> <Esc> <C-O>:stopinsert<CR>

in your .vimrc.

Amygdalin answered 19/2, 2010 at 10:9 Comment(4)
This mapping will make it logical if you enter insert mode with i but illogical if you enter insert mode with a.Shadshadberry
well, you could remap normal mode i to set a flag, then remap insert mode <Esc> to check that flag, and if set, use <C-O>:stopinsert<CR>, and then clear the flag either way.Frodina
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: unix.stackexchange.com/a/11403/38050)Nicki
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.Brunella
T
4

I do believe the proper way to do this is

au InsertLeave * call cursor([getpos('.')[1], getpos('.')[2]+1])
Tarkany answered 11/6, 2013 at 21:57 Comment(5)
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.)Nicki
Can you give an example of how to do the augroup thing? ThanksTarkany
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 triggerSimplicidentate
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.Tarkany
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: vim.fandom.com/wiki/…Maquette
N
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
Nicki answered 20/10, 2021 at 19:6 Comment(0)
V
0

What about:

:imap <Esc> <Esc><Right>
Vanguard answered 19/2, 2010 at 10:10 Comment(2)
With certain settings, that will move cursor into the next line if editing happened at the end of the current one.Chaudfroid
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.Shadshadberry

© 2022 - 2024 — McMap. All rights reserved.