├── LICENSE ├── README.md └── plugin └── easyescape.vim /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Yichao Zhou 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # vim-easyescape 2 | 3 | ``` 4 | _____ _____ 5 | | ____|__ _ ___ _ _ | ____|___ ___ __ _ _ __ ___ 6 | | _| / _` / __| | | | | _| / __|/ __/ _` | '_ \ / _ \ 7 | | |__| (_| \__ \ |_| | | |___\__ \ (_| (_| | |_) | __/ 8 | |_____\__,_|___/\__, | |_____|___/\___\__,_| .__/ \___| 9 | |___/ |_| 10 | ``` 11 | 12 | Pull out your Escape key! 13 | 14 | `//` is hard to press? Try `vim-easyescape`! `vim-easyescape` makes exiting insert mode easy and distraction free. 15 | 16 | ## Problems 17 | Traditionally, we need to use 18 | ``` 19 | inoremap jk 20 | inoremap kj 21 | ``` 22 | or 23 | ``` 24 | inoremap jj 25 | ``` 26 | so that we can press `jk` simultaneously (in arbitrary order since we have two maps) or press `j` twice to exit the insert mode. However, a problem with such map sequence is that Vim will pause whenever you type `j` or `k` in insert mode (it is waiting for the next key to determine whether to apply the mapping). The pause causes visual distraction which you may or may not notice. 27 | 28 | `vim-easyescape` does not have such problem and supports custom timeout. 29 | 30 | ## Installation 31 | Use your favourite plugin manager, e.g., `vim-plug`: 32 | ``` 33 | Plug "zhou13/vim-easyescape" 34 | ``` 35 | 36 | ## Usage 37 | 38 | ### Configuration 1: map of `jk` and `kj` (recommended) 39 | 40 | The unit of timeout is in ms. A very small timeout makes an input of real `jk` or `kj` possible (Python3 is required for this feature)! 41 | ``` 42 | let g:easyescape_chars = { "j": 1, "k": 1 } 43 | let g:easyescape_timeout = 100 44 | cnoremap jk 45 | cnoremap kj 46 | ``` 47 | 48 | ### Configuration 2: map of `jj` 49 | 50 | ``` 51 | let g:easyescape_chars = { "j": 2 } 52 | let g:easyescape_timeout = 100 53 | cnoremap jj 54 | ``` 55 | 56 | ### Disable easyescape based on filetype 57 | 58 | ``` 59 | autocmd FileType text,markdown call setbufvar(bufnr("%"), 'easyescape_disable', 1) 60 | ``` 61 | 62 | ## Dependency 63 | 64 | Python3 is required if timeout (`g:easyescape_timeout`) is less than 2000 ms because vim does not provide a way to fetch sub-second time. 65 | 66 | ## Known problems 67 | 68 | 1. Maps of keys in `g:easyescape_chars` need to be created. 69 | 2. Does not work in command/visual mode due to lack of `InsertCharPre` in vim/nvim. 70 | -------------------------------------------------------------------------------- /plugin/easyescape.vim: -------------------------------------------------------------------------------- 1 | " easyescape.vim Pull out your Escape key! 2 | " Author: Yichao Zhou (broken.zhou AT gmail) 3 | " Version: 0.2 4 | " --------------------------------------------------------------------- 5 | 6 | if &cp || exists("g:loaded_easyescape") 7 | finish 8 | endif 9 | let g:loaded_easyescape = 1 10 | let s:haspy3 = has("python3") 11 | 12 | if !exists("g:easyescape_chars") 13 | let g:easyescape_chars = { "j": 1, "k": 1 } 14 | endif 15 | if !exists("g:easyescape_timeout") 16 | if s:haspy3 17 | let g:easyescape_timeout = 100 18 | else 19 | let g:easyescape_timeout = 2000 20 | endif 21 | endif 22 | 23 | if !s:haspy3 && g:easyescape_timeout < 2000 24 | echomsg "Python3 is required when g:easyescape_timeout < 2000" 25 | let g:easyescape_timeout = 2000 26 | endif 27 | 28 | function! s:EasyescapeInsertCharPre() 29 | if has_key(g:easyescape_chars, v:char) == 0 30 | let s:current_chars = copy(g:easyescape_chars) 31 | endif 32 | endfunction 33 | 34 | function! s:EasyescapeSetTimer() 35 | if s:haspy3 36 | py3 easyescape_time = default_timer() 37 | endif 38 | let s:localtime = localtime() 39 | endfunction 40 | 41 | function! s:EasyescapeReadTimer() 42 | if s:haspy3 43 | py3 vim.command("let pyresult = %g" % (1000 * (default_timer() - easyescape_time))) 44 | return pyresult 45 | endif 46 | return 1000 * (localtime() - s:localtime) 47 | endfunction 48 | 49 | function! EasyescapeMap(char) 50 | if exists("b:easyescape_disable") && b:easyescape_disable == 1 51 | return a:char 52 | endif 53 | if s:current_chars[a:char] == 0 54 | let s:current_chars = copy(g:easyescape_chars) 55 | let s:current_chars[a:char] = s:current_chars[a:char] - 1 56 | call s:EasyescapeSetTimer() 57 | return a:char 58 | endif 59 | 60 | if s:EasyescapeReadTimer() > g:easyescape_timeout 61 | let s:current_chars = copy(g:easyescape_chars) 62 | let s:current_chars[a:char] = s:current_chars[a:char] - 1 63 | call s:EasyescapeSetTimer() 64 | return a:char 65 | endif 66 | 67 | let s:current_chars[a:char] = s:current_chars[a:char] - 1 68 | for value in values(s:current_chars) 69 | if value > 0 70 | call s:EasyescapeSetTimer() 71 | return a:char 72 | endif 73 | endfor 74 | 75 | let s:current_chars = copy(g:easyescape_chars) 76 | 77 | " Workaround for #3, might be less annoying but still not perfect 78 | let current_line = getline(".") 79 | let current_line_trimed = substitute(current_line, '^\s*\(.\{-}\)\s*$', '\1', '') 80 | let n_chars = eval(join(values(g:easyescape_chars), "+")) - 1 81 | 82 | let seq = s:escape_sequence 83 | if col(".") == len(current_line) + 1 && n_chars == len(current_line_trimed) 84 | let seq = seq . '0"_D' 85 | endif 86 | 87 | return seq 88 | 89 | endfunction 90 | 91 | let s:current_chars = copy(g:easyescape_chars) 92 | 93 | augroup easyescape 94 | au! 95 | au InsertCharPre * call s:EasyescapeInsertCharPre() 96 | augroup END 97 | 98 | for key in keys(g:easyescape_chars) 99 | exec "inoremap " . key . " EasyescapeMap(\"" . key . "\")" 100 | endfor 101 | 102 | if s:haspy3 103 | py3 from timeit import default_timer 104 | py3 import vim 105 | call s:EasyescapeSetTimer() 106 | else 107 | let s:localtime = localtime() 108 | endif 109 | 110 | let s:escape_sequence = repeat("\", eval(join(values(g:easyescape_chars), "+"))-1) . "\" 111 | 112 | " vim:set expandtab tabstop=4 shiftwidth=4: 113 | --------------------------------------------------------------------------------