vim-config/autoload/submode.vim
2022-07-28 00:13:09 +08:00

552 lines
14 KiB
VimL

" submode - Create your own submodes
" Version: 0.3.1
" Copyright (C) 2008-2014 kana <http://whileimautomaton.net/>
" License: MIT license {{{
" Permission is hereby granted, free of charge, to any person obtaining
" a copy of this software and associated documentation files (the
" "Software"), to deal in the Software without restriction, including
" without limitation the rights to use, copy, modify, merge, publish,
" distribute, sublicense, and/or sell copies of the Software, and to
" permit persons to whom the Software is furnished to do so, subject to
" the following conditions:
"
" The above copyright notice and this permission notice shall be included
" in all copies or substantial portions of the Software.
"
" THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
" OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
" MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
" IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
" CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
" TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
" SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
" }}}
" Concept "{{{1
"
" In the following pseudo code, :MAP means :map or :noremap, and it depends on
" user's specification.
"
" map {key-to-enter}
" \ <Plug>(submode-before-entering:{submode}:with:{key-to-enter})
" \<Plug>(submode-before-entering:{submode})
" \<Plug>(submode-enter:{submode})
"
" MAP <Plug>(submode-before-entering:{submode}:with:{key-to-enter})
" \ {anything}
" noremap <Plug>(submode-before-entering:{submode})
" \ {tweaking 'timeout' and others}
" map <Plug>(submode-enter:{submode})
" \ <Plug>(submode-before-action:{submode})
" \<Plug>(submode-prefix:{submode})
"
" map <Plug>(submode-prefix:{submode})
" \ <Plug>(submode-leave:{submode})
" map <Plug>(submode-prefix:{submode}){the first N keys in {lhs}}
" \ <Plug>(submode-leave:{submode})
" map <Plug>(submode-prefix:{submode}){lhs}
" \ <Plug>(submode-rhs:{submode}:for:{lhs})
" \<Plug>(submode-enter:{submode})
" MAP <Plug>(submode-rhs:{submode}:for:{lhs})
" \ {rhs}
" Variables "{{{1
if !exists('g:submode_always_show_submode')
let g:submode_always_show_submode = 0
endif
if !exists('g:submode_keep_leaving_key')
let g:submode_keep_leaving_key = 0
endif
if !exists('g:submode_keyseqs_to_leave')
let g:submode_keyseqs_to_leave = ['<Esc>']
endif
if !exists('g:submode_timeout')
let g:submode_timeout = &timeout
endif
if !exists('g:submode_timeoutlen')
let g:submode_timeoutlen = &timeoutlen
endif
"" See s:set_up_options() and s:restore_options().
"
" let s:original_showcmd = &showcmd
" let s:original_showmode = &showmode
" let s:original_timeout = &timeout
" let s:original_timeoutlen = &timeoutlen
" let s:original_ttimeout = &ttimeout
" let s:original_ttimeoutlen = &ttimeoutlen
if !exists('s:options_overridden_p')
let s:options_overridden_p = 0
endif
" A padding string to wipe out internal key mappings in 'showcmd' area. (gh-3)
"
" We use no-break spaces (U+00A0) or dots, depending of the current 'encoding'.
" Because
"
" * A normal space (U+0020) is rendered as "<20>" since Vim 7.4.116.
" * U+00A0 is rendered as an invisible glyph if 'encoding' is set to one of
" Unicode encodings. Otherwise "| " is rendered instead.
let s:STEALTH_TYPEAHEAD =
\ &g:encoding =~# '^u'
\ ? repeat("\<Char-0xa0>", 5)
\ : repeat('.', 10)
let s:current_submode = ''
" Interface "{{{1
" :SubmodeRestoreOptions "{{{2
command! -bar -nargs=0 SubmodeRestoreOptions call submode#restore_options()
function! submode#current() "{{{2
return s:current_submode
endfunction
function! submode#enter_with(submode, modes, options, lhs, ...) "{{{2
let rhs = 0 < a:0 ? a:1 : '<Nop>'
for mode in s:each_char(a:modes)
call s:define_entering_mapping(a:submode, mode, a:options, a:lhs, rhs)
endfor
return
endfunction
function! submode#leave_with(submode, modes, options, lhs) "{{{2
let options = substitute(a:modes, 'e', '', 'g') " <Nop> is not expression.
return submode#map(a:submode, a:modes, options . 'x', a:lhs, '<Nop>')
endfunction
function! submode#map(submode, modes, options, lhs, rhs) "{{{2
for mode in s:each_char(a:modes)
call s:define_submode_mapping(a:submode, mode, a:options, a:lhs, a:rhs)
endfor
return
endfunction
function! submode#restore_options() "{{{2
call s:restore_options()
return
endfunction
function! submode#unmap(submode, modes, options, lhs) "{{{2
for mode in s:each_char(a:modes)
call s:undefine_submode_mapping(a:submode, mode, a:options, a:lhs)
endfor
return
endfunction
" Core "{{{1
function! s:define_entering_mapping(submode, mode, options, lhs, rhs) "{{{2
execute s:map_command(a:mode, 'r')
\ s:map_options(s:filter_flags(a:options, 'bu'))
\ (a:lhs)
\ (s:named_key_before_entering_with(a:submode, a:lhs)
\ . s:named_key_before_entering(a:submode)
\ . s:named_key_enter(a:submode))
if !s:mapping_exists_p(s:named_key_enter(a:submode), a:mode)
" When the given submode is not defined yet - define the default key
" mappings to leave the submode.
for keyseq in g:submode_keyseqs_to_leave
call submode#leave_with(a:submode, a:mode, a:options, keyseq)
endfor
endif
execute s:map_command(a:mode, s:filter_flags(a:options, 'r'))
\ s:map_options(s:filter_flags(a:options, 'besu'))
\ s:named_key_before_entering_with(a:submode, a:lhs)
\ a:rhs
execute s:map_command(a:mode, '')
\ s:map_options('e')
\ s:named_key_before_entering(a:submode)
\ printf('<SID>on_entering_submode(%s)', string(a:submode))
execute s:map_command(a:mode, 'r')
\ s:map_options('')
\ s:named_key_enter(a:submode)
\ (s:named_key_before_action(a:submode)
\ . s:named_key_prefix(a:submode))
execute s:map_command(a:mode, '')
\ s:map_options('e')
\ s:named_key_before_action(a:submode)
\ printf('<SID>on_executing_action(%s)', string(a:submode))
execute s:map_command(a:mode, 'r')
\ s:map_options('')
\ s:named_key_prefix(a:submode)
\ s:named_key_leave(a:submode)
" NB: :map-<expr> cannot be used for s:on_leaving_submode(),
" because it uses some commands not allowed in :map-<expr>.
execute s:map_command(a:mode, '')
\ s:map_options('s')
\ s:named_key_leave(a:submode)
\ printf('%s<SID>on_leaving_submode(%s)<Return>',
\ a:mode =~# '[ic]' ? '<C-r>=' : '@=',
\ string(a:submode))
return
endfunction
function! s:define_submode_mapping(submode, mode, options, lhs, rhs) "{{{2
execute s:map_command(a:mode, 'r')
\ s:map_options(s:filter_flags(a:options, 'bu'))
\ (s:named_key_prefix(a:submode) . a:lhs)
\ (s:named_key_rhs(a:submode, a:lhs)
\ . (s:has_flag_p(a:options, 'x')
\ ? s:named_key_leave(a:submode)
\ : s:named_key_enter(a:submode)))
execute s:map_command(a:mode, s:filter_flags(a:options, 'r'))
\ s:map_options(s:filter_flags(a:options, 'besu'))
\ s:named_key_rhs(a:submode, a:lhs)
\ a:rhs
let keys = s:split_keys(a:lhs)
for n in range(1, len(keys) - 1)
let first_n_keys = join(keys[:-(n+1)], '')
silent! execute s:map_command(a:mode, 'r')
\ s:map_options(s:filter_flags(a:options, 'bu'))
\ (s:named_key_prefix(a:submode) . first_n_keys)
\ s:named_key_leave(a:submode)
endfor
return
endfunction
function! s:undefine_submode_mapping(submode, mode, options, lhs) "{{{2
execute s:map_command(a:mode, 'u')
\ s:map_options(s:filter_flags(a:options, 'b'))
\ s:named_key_rhs(a:submode, a:lhs)
let keys = s:split_keys(a:lhs)
for n in range(len(keys), 1, -1)
let first_n_keys = join(keys[:n-1], '')
execute s:map_command(a:mode, 'u')
\ s:map_options(s:filter_flags(a:options, 'b'))
\ s:named_key_prefix(a:submode) . first_n_keys
if s:longer_mapping_exists_p(s:named_key_prefix(a:submode), first_n_keys)
execute s:map_command(a:mode, 'r')
\ s:map_options(s:filter_flags(a:options, 'b'))
\ s:named_key_prefix(a:submode) . first_n_keys
\ s:named_key_leave(a:submode)
break
endif
endfor
return
endfunction
" Misc. "{{{1
function! s:each_char(s) "{{{2
return split(a:s, '.\zs')
endfunction
function! s:filter_flags(s, cs) "{{{2
return join(map(s:each_char(a:cs), 's:has_flag_p(a:s, v:val) ? v:val : ""'),
\ '')
endfunction
function! s:has_flag_p(s, c) "{{{2
return 0 <= stridx(a:s, a:c)
endfunction
function! s:insert_mode_p(mode) "{{{2
return a:mode =~# '^[iR]'
endfunction
function! s:longer_mapping_exists_p(submode, lhs) "{{{2
" FIXME: Implement the proper calculation.
" Note that mapcheck() can't be used for this purpose because it may
" act as s:shorter_mapping_exists_p() if there is such a mapping.
return !0
endfunction
function! s:map_command(mode, flags) "{{{2
if s:has_flag_p(a:flags, 'u')
return a:mode . 'unmap'
else
return a:mode . (s:has_flag_p(a:flags, 'r') ? 'map' : 'noremap')
endif
endfunction
function! s:map_options(options) "{{{2
let _ = {
\ 'b': '<buffer>',
\ 'e': '<expr>',
\ 's': '<silent>',
\ 'u': '<unique>',
\ }
return join(map(s:each_char(a:options), 'get(_, v:val, "")'))
endfunction
function! s:mapping_exists_p(keyseq, mode) "{{{2
return maparg(a:keyseq, a:mode) != ''
endfunction
function! s:may_override_showmode_p(mode) "{{{2
" Normal mode / Visual mode (& its variants) / Insert mode (& its variants)
return a:mode =~# "^[nvV\<C-v>sS\<C-s>]" || s:insert_mode_p(a:mode)
endfunction
function! s:named_key_before_action(submode) "{{{2
return printf('<Plug>(submode-before-action:%s)', a:submode)
endfunction
function! s:named_key_before_entering(submode) "{{{2
return printf('<Plug>(submode-before-entering:%s)', a:submode)
endfunction
function! s:named_key_before_entering_with(submode, lhs) "{{{2
return printf('<Plug>(submode-before-entering:%s:with:%s)', a:submode, a:lhs)
endfunction
function! s:named_key_enter(submode) "{{{2
return printf('<Plug>(submode-enter:%s)', a:submode)
endfunction
function! s:named_key_leave(submode) "{{{2
return printf('<Plug>(submode-leave:%s)', a:submode)
endfunction
function! s:named_key_prefix(submode) "{{{2
return printf('<Plug>(submode-prefix:%s)%s', a:submode, s:STEALTH_TYPEAHEAD)
endfunction
function! s:named_key_rhs(submode, lhs) "{{{2
return printf('<Plug>(submode-rhs:%s:for:%s)', a:submode, a:lhs)
endfunction
function! s:on_entering_submode(submode) "{{{2
call s:set_up_options(a:submode)
return ''
endfunction
function! s:on_executing_action(submode) "{{{2
if (s:original_showmode || g:submode_always_show_submode)
\ && s:may_override_showmode_p(mode())
echohl ModeMsg
echo '-- Submode:' a:submode '--'
echohl None
endif
return ''
endfunction
function! s:on_leaving_submode(submode) "{{{2
if (s:original_showmode || g:submode_always_show_submode)
\ && s:may_override_showmode_p(mode())
if s:insert_mode_p(mode())
let cursor_position = getpos('.')
endif
" BUGS: :redraw! doesn't redraw 'showmode'.
execute "normal! \<C-l>"
if s:insert_mode_p(mode())
call setpos('.', cursor_position)
endif
endif
if !g:submode_keep_leaving_key && getchar(1) isnot 0
" To completely ignore unbound key sequences in a submode,
" here we have to fetch and drop the last key in the key sequence.
call getchar()
endif
call s:restore_options()
return ''
endfunction
function! s:remove_flag(s, c) "{{{2
" Assumption: a:c is not a meta character.
return substitute(a:s, a:c, '', 'g')
endfunction
function! s:restore_options() "{{{2
if !s:options_overridden_p
return
endif
let s:options_overridden_p = 0
let &showcmd = s:original_showcmd
let &showmode = s:original_showmode
let &timeout = s:original_timeout
let &timeoutlen = s:original_timeoutlen
let &ttimeout = s:original_ttimeout
let &ttimeoutlen = s:original_ttimeoutlen
let s:current_submode = ''
return
endfunction
function! s:set_up_options(submode) "{{{2
if s:options_overridden_p
return
endif
let s:options_overridden_p = !0
let s:original_showcmd = &showcmd
let s:original_showmode = &showmode
let s:original_timeout = &timeout
let s:original_timeoutlen = &timeoutlen
let s:original_ttimeout = &ttimeout
let s:original_ttimeoutlen = &ttimeoutlen
" NB: 'showcmd' must be enabled to render the cursor properly.
" If 'showcmd' is disabled and the current submode message is rendered, the
" cursor is rendered at the end of the message, not the actual position in
" the current window. (gh-9)
set showcmd
set noshowmode
let &timeout = g:submode_timeout
let &ttimeout = s:original_timeout ? !0 : s:original_ttimeout
let &timeoutlen = g:submode_timeoutlen
let &ttimeoutlen = s:original_ttimeoutlen < 0
\ ? s:original_timeoutlen
\ : s:original_ttimeoutlen
let s:current_submode = a:submode
return
endfunction
function! s:split_keys(keyseq) "{{{2
" Assumption: Special keys such as <C-u> are escaped with < and >, i.e.,
" a:keyseq doesn't directly contain any escape sequences.
return split(a:keyseq, '\(<[^<>]\+>\|.\)\zs')
endfunction
" __END__ "{{{1
" vim: foldmethod=marker