如何在Vim中防止光标在离开插入模式时向后移动一个字符?

44

是否有可能取消所述行为?

额外加分任务: 找到一种方法,使Vim在退出插入模式后立即刷新光标位置。


8
有趣的问题。我想知道这种行为背后的原因是什么。我只是接受了它。 - Stefano Borini
4
我尝试过了。有点不方便。当光标移动时,我可以“看到”自己是否已退出编辑模式。 - P Shved
1
@StefanoBorini 这个问题的一部分解释在 这篇stackoverflow.com上的问题中。据我理解:当您退出插入模式时,vi不知道您是使用 a 进入还是使用 i 进入,因此它假设您是使用 a 进入的。实际上,当您重复使用 a 和 <kbd>Esc</kbd> 时,光标并不会“滑动”。 - Alois Mahdal
我有Powerline甚至命令行,因此两行可以清楚地告诉我是否已退出插入模式,光标的移动完全是不必要的,并且当我想使用快速的<esc>D删除光标后的内容时会让我感到困扰。我认为基于:autocmd InsertLeave的一些东西可能会有效吗? - Steven Lu
如果停止光标闪烁,那么除了通常更少的麻烦外,您所处的模式也更加清晰(块表示正常模式,细插入符号表示插入模式,半光标表示按下 c 等键后的模式)。 - user146043
显示剩余3条评论
7个回答

27

虽然我不建议更改默认的光标机制,但实现所需行为的一种方式是使用以下插入模式映射。

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

在插入模式中,Esc键的功能被改写为执行`^命令,将光标移动到上次离开插入模式时的位置。由于在这种映射中,它会在使用Esc键离开插入模式后立即执行,因此与默认行为相比,光标会向右移动一个字符的位置。

与其他一些解决方法不同,这种方法不需要Vim编译时启用+ex_extra功能。


1
是的,而且我认为它更加一致。追加已经意味着将光标向右移动一个字符,无论您是否输入字符。 - ib.
1
那取决于您认为光标在哪里:如果您认为光标位于高亮字符后面,则 a 在当前位置插入,而 i 则将光标移回并插入。无论哪种方式都是合乎逻辑的,这取决于您认为光标在哪里。我想编写vi的人认为光标位于高亮字符之后。 - DrAl
10
很不幸,当按下箭头键和功能键时,这个解决方案会使Vim在终端中出现问题。我正在尝试使用以下修复方法:autocmd InsertLeave * :normal \^`。 - Nathan Neff
1
@Nathan:这是一个好建议!不过,我建议你要习惯默认的Vim行为。这样会有回报。 - ib.
1
@NathanNeff,那是我见过的最好的解决方案,远远超过了你回复的实际答案。 - robru
显示剩余2条评论

17
尽管有一些技巧可以处理这个问题(例如前两篇帖子中提到的 ESC 映射),但是没有一种一致的方法可以解决。原因是无法确定用于进入插入模式的方法。具体来说,给定字符串abcDefg,光标位于 D 上:

  • 如果按下 i,插入模式位置将在cD之间。正常的ESC 将把光标放在c 上;<C-O>:stopinsert<CR> (或反引号方法)将把光标放在D 上。

  • 如果按下 a,插入模式位置将在 De 之间。正常的 ESC 将把光标放在D 上;<C-O>:stopinsert<CR> 将把光标放在e 上。

如果你真的想做到这一点,你可以使用以下代码:

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

但是请记住,这只处理以ia作为输入方法的情况:如果您使用可视块模式、IA等其他方法,则需要想出新的命令来匹配它们(而且这些命令非常多)。因此,我强烈建议您不要这样做。

个人建议您习惯默认行为。您可以轻松地将其逻辑应用于i 逻辑应用于a。如果您将默认设置为适用于i的逻辑而损失了适用于a的逻辑,那么当您使用标准的vi/vim安装时,您会感到困惑。


11

根据Nathan Neff评论,我发现最好的方法是:

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

autocmd命令会将光标移回到插入模式结束时的位置(即比默认位置向前一个字符)。

virtualedit使其在行末表现得更加一致(因此可以在行末最后一个字符的后面向前移动一个字符)。

(编辑:normal!以避免递归映射)


8
inoremap <silent> <Esc> <C-O>:stopinsert<CR>

在你的.vimrc文件中。

这个映射会使得当你使用 i 进入插入模式时逻辑上正确,但如果你使用 a 进入插入模式则不合逻辑。 - DrAl
2
你可以将正常模式下的 i 重新映射为设置标志,然后将插入模式下的 <Esc> 重新映射为检查该标志,如果已设置,则使用 <C-O>:stopinsert<CR>,然后无论如何都清除该标志。 - rampion
1
我并不认为这对于 a 来说完全是不合逻辑的。通常,在每次更改后,当我离开该更改时,我就完成了该更改,并且我想做的下一件事可能涉及进行其他更改。因此,对我来说,在离开插入模式时(无论我如何进入它),我通常希望光标位于下一个字符上,以便我准备进行下一次更改,而不是准备更改我刚刚插入的内容。(大部分内容摘自我的评论:http://unix.stackexchange.com/a/11403/38050) - Kyle Strand
这个对我来说有效,适用于VIM 7.4和VIM 8.2,但是7.4在退出插入模式时没有更新模式行。 - Mr Fry

4
我相信正确的方法是这样的:
au InsertLeave * call cursor([getpos('.')[1], getpos('.')[2]+1])

1
这在第一列上的行为不正确。在call之前插入if (getpos('。')[2]> 1) |来修复它。(此外,它应该是augroup的一部分,以防止重新加载包含此autocommand的.vimrc时产生累积跳转向前。) - Kyle Strand
你能举个例子说明如何使用augroup吗?谢谢。 - Steven Lu
这对我来说是最好的解决方案,但我必须使用“set timeoutlen = 100”,因为InsertLeave事件需要整整一秒钟才能触发。 - Gearoid Murphy
1
你是指 ttimeoutlen 吗?我个人将 ttimeoutlen 设置为 10 毫秒:ttimeoutlen 是给终端仿真器客户端发出终端转义序列(例如右箭头键的 4 字节序列 \033[[C)的超时宽限。插入模式实际上直到此超时之后才会真正退出,这个原因应该很明显——Vim 不知道你的终端是否要跟随一个转义序列或者将 Esc 作为退出插入模式命令。请注意并认识到你的 Vim 设置已经在做这件事情了,延迟退出插入模式。 - Steven Lu
我无法编辑这个答案,但是Kyle Strand在第一条评论中提到了以下内容:autocmd!InsertLeave * if(getpos('。')[2]> 1)| call cursor([getpos('。')[1],getpos('。')[2] + 1])| endif 因此,自动命令不会在第一列中断。此外,请参见类似的解决方案:https://vim.fandom.com/wiki/Prevent_escape_from_moving_the_cursor_one_character_to_the_left - john c. j.

2

有一种方法来自Vim技巧维基对我很有效,已经使用了好多年:

" 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

0

这个怎么样:

:imap <Esc> <Esc><Right>

在某些设置下,如果编辑发生在当前行的末尾,那么光标将移动到下一行。 - P Shved
1
即使忽略行尾的问题,如果您使用 i 进入插入模式,这将是合乎逻辑的,但如果您使用 a 进入插入模式,则不合逻辑。 - DrAl

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接