A Better Mapping to Leave Insert Mode than jk
Update 2021-01-14: I ended up writing a plugin called better-escape.vim which aims at solving this issue.
A very popular insert mode mapping
for Neovim/Vim is to map
<ESC> for quicker escaping
from the insert mode. I have used the following mapping for quite some time:
inoremap jk <ESC>
However, it will cause lag whenever we press
j in insert mode. Because Vim
will wait for
milliseconds to see if you want to enter
j or if you mean the map
Of course, you can reduce
timeoutlen option to very small values, but it is
not user-friendly to type mappings that consists of several key strokes. Before
you can press the next key in your mapping, Vim may have already time out.
I saw on Reddit the other day that someone propose to check the character
before the current one and leave insert mode based on some conditions. I
thought this might be a better way. The idea is to have an insert mode mapping
k, when we press
k, we then check the character before k. If that
j, we will erase j and leave insert mode. Otherwise, we will
insert k as is.
Here is a crude implementation of that idea:
scriptencoding utf-8 inoremap <expr> k EscapeInsertOrNot() " some test text function! EscapeInsertOrNot() abort " If k is preceded by j, then remove j and go to normal mode. let line_text = getline('.') let cur_ch_idx = CursorCharIdx() let pre_char = CharAtIdx(line_text, cur_ch_idx-1) echom 'pre_char is:' pre_char if pre_char ==# 'j' return "\b\e" else return 'k' endif endfunction " split(line_text, '\zs') can split string into separate char "汉字测试这是一些汉字 some charjust " byte index of 这 is 14 (using col('.')) " let my_str = '你好吗' " strcharpart(my_str, 0, 1) is the first char in my_str (it is like my_str in Python) " strcharpart(my_str, 1, 1) is the second char in my_str " let ch = '你' " byteidx(ch, 1) is the number of byte in UTF-8 encoding for ch (suppose " that the character encoding is UTF-8) function! CharAtIdx(str, idx) abort " Get char at idx from str. Note that this is based on character index " instead of the byte index. return strcharpart(a:str, a:idx, 1) endfunction function! CursorCharIdx() abort " This function returns the character-based index for character under " cursor. " Get the character under cursor let line_text = getline('.') let cur_byte_idx = col('.') echo 'cur_byte_idx:' cur_byte_idx if cur_byte_idx == 1 echomsg 'cursor char idx:' 0 return 0 endif " character index starts from zero let [ch_idx, byte_idx] = [-1, 0] for c in split(line_text, '\zs') let ch_idx += 1 let byte_idx += byteidx(c, 1) echomsg ch_idx c byte_idx if byte_idx+1 == cur_byte_idx let pre_char = strcharpart(line_text, ch_idx, 1) echomsg 'pre char is:' pre_char 'pre char index:' ch_idx let cursor_char = strcharpart(line_text, ch_idx+1, 1) echomsg 'cursor char' cursor_char 'index:' ch_idx+1 return ch_idx + 1 endif endfor endfunction
Note that the above script is a little complex, because we need to take
non-ASCII characters into account. The function
CursorCharIdx() is used to
get the character index of the cursor char in the cursor line. I have tested
that it works for pure ASCII text and text containing non-ASCII characters.
I feel that the function above to get the cursor char index is too complex. So I asked a question on stackexchange and got a more concise solution:
function! CursorCharIdx() abort " A more concise way to get character index under cursor. let cursor_byte_idx = col('.') if cursor_byte_idx == 1 return 0 endif let pre_cursor_text = getline('.')[:col('.')-2] return strchars(pre_cursor_text) endfunction
One issue is that if you want to insert
jk literally, you can not just type
j followed by
k. It will be interpreted as escaping the insert mode. To
insert k, we can press
Ctrl-V, then press
k. This works, because Vim will
not consider character after
Ctrl-v for mappings. See also
:h i_CTRL-V for
the details. Since I rarely use
jk in my writing, I am fine with this issue.
- Vim: do something in a function based on character under a cursor?
- Vim (vimscript) get exact character under the cursor
- How can I get the character at the cursor position in a multibyte aware manner?
- Reliably get the character at a byte index in a string
License CC BY-NC-ND 4.0