" submode - Create your own submodes " Version: 0.3.1 " Copyright (C) 2008-2014 kana " 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} " \ (submode-before-entering:{submode}:with:{key-to-enter}) " \(submode-before-entering:{submode}) " \(submode-enter:{submode}) " " MAP (submode-before-entering:{submode}:with:{key-to-enter}) " \ {anything} " noremap (submode-before-entering:{submode}) " \ {tweaking 'timeout' and others} " map (submode-enter:{submode}) " \ (submode-before-action:{submode}) " \(submode-prefix:{submode}) " " map (submode-prefix:{submode}) " \ (submode-leave:{submode}) " map (submode-prefix:{submode}){the first N keys in {lhs}} " \ (submode-leave:{submode}) " map (submode-prefix:{submode}){lhs} " \ (submode-rhs:{submode}:for:{lhs}) " \(submode-enter:{submode}) " MAP (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 = [''] 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("\", 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 : '' 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') " is not expression. return submode#map(a:submode, a:modes, options . 'x', a:lhs, '') 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('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('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- cannot be used for s:on_leaving_submode(), " because it uses some commands not allowed in :map-. execute s:map_command(a:mode, '') \ s:map_options('s') \ s:named_key_leave(a:submode) \ printf('%son_leaving_submode(%s)', \ a:mode =~# '[ic]' ? '=' : '@=', \ 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': '', \ 'e': '', \ 's': '', \ 'u': '', \ } 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\sS\]" || s:insert_mode_p(a:mode) endfunction function! s:named_key_before_action(submode) "{{{2 return printf('(submode-before-action:%s)', a:submode) endfunction function! s:named_key_before_entering(submode) "{{{2 return printf('(submode-before-entering:%s)', a:submode) endfunction function! s:named_key_before_entering_with(submode, lhs) "{{{2 return printf('(submode-before-entering:%s:with:%s)', a:submode, a:lhs) endfunction function! s:named_key_enter(submode) "{{{2 return printf('(submode-enter:%s)', a:submode) endfunction function! s:named_key_leave(submode) "{{{2 return printf('(submode-leave:%s)', a:submode) endfunction function! s:named_key_prefix(submode) "{{{2 return printf('(submode-prefix:%s)%s', a:submode, s:STEALTH_TYPEAHEAD) endfunction function! s:named_key_rhs(submode, lhs) "{{{2 return printf('(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! \" 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 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