vim/bundle/arpeggio/autoload/arpeggio.vim @ 22a55caedce5

vim: another esc arpeggio
author Steve Losh <steve@stevelosh.com>
date Mon, 12 Jul 2010 16:34:29 -0400
parents 6bd31aa2672a
children (none)
" arpeggio - Mappings for simultaneously pressed keys
" Version: 0.0.6
" Copyright (C) 2008-2010 kana <http://whileimautomaton.net/>
" License: So-called MIT/X 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.
" }}}
" Notes  "{{{1
"
" CONVENTIONS FOR INTERNAL MAPPINGS
"
" <SID>work:...
"               Use to set 'timeoutlen', to restore 'timeoutlen', and to
"               determine whether keys are simultaneously pressed or not.
"
" <SID>success:...
"               Use to restore 'timeoutlen' and to do user-defined action for
"               the simultaneously pressed keys "...".
"
"
" MAPPING FLOWCHART
"
"                        {X}            (user types a key {X})
"                         |
"                         v
"                  <SID>work:{X}
"                         |  (are {Y}... simultaneously typed with {X}?)
"                  [yes]  |  [no]
"         .---------------*-------------------.
"         |                                   |
"         v                                   v
"  <SID>work:{X}{Y}...          <Plug>(arpeggio-default:{X})
"         |
"         v
" <SID>success:{X}{Y}...
"         |
"         v
"       {rhs}








" Variables  "{{{1

" See s:set_up_options() and s:restore_options().
let s:original_showcmd = &showcmd
let s:original_timeout = &timeout
let s:original_timeoutlen = &timeoutlen
let s:original_ttimeoutlen = &ttimeoutlen








" Public  "{{{1
function! arpeggio#list(modes, options, ...)  "{{{2
  let lhs = 1 <= a:0 ? a:1 : 0
  let opt_buffer = a:options =~# 'b' ? '<buffer>' : ''

  for mode in s:each_char(a:modes)
    execute printf('%smap %s <SID>success:%s',
    \              mode, opt_buffer, lhs is 0 ? '' : lhs)
  endfor
  return
endfunction




function! arpeggio#load()  "{{{2
  runtime! plugin/arpeggio.vim
endfunction




function! arpeggio#map(modes, options, remap_p, lhs, rhs)  "{{{2
  for mode in s:each_char(a:modes)
    call s:do_map(mode, a:options, a:remap_p, s:split_to_keys(a:lhs), a:rhs)
  endfor
  return
endfunction




function! arpeggio#unmap(modes, options, lhs)  "{{{2
  let v:errmsg = ''

  for mode in s:each_char(a:modes)
    call s:do_unmap(mode, a:options, s:split_to_keys(a:lhs))
  endfor

  if v:errmsg != ''
    echoerr v:errmsg
  endif
  return v:errmsg == ''
endfunction








" Core  "{{{1
function! arpeggio#_do(script)  "{{{2
  let _ = split(substitute(a:script, '^\s\+', '', ''), '^\S\+\zs')
  execute 'Arpeggio'._[0] join(_[1:], '')
  return
endfunction




function! arpeggio#_map_or_list(modes, remap_p, q_args)  "{{{2
  let [options, lhs, rhs] = s:parse_args(a:q_args)
  if rhs isnot 0
    return arpeggio#map(a:modes, options, a:remap_p, lhs, rhs)
  else
    return arpeggio#list(a:modes, options, lhs)
  endif
endfunction




function! arpeggio#_unmap(modes, q_args)  "{{{2
  let [options, lhs, rhs] = s:parse_args(a:q_args)
  return arpeggio#unmap(a:modes, options, lhs)
endfunction




function! s:chord_cancel(key)  "{{{2
  call s:restore_options()
  return "\<Plug>(arpeggio-default:" . a:key . ')'
endfunction




function! s:chord_key(key)  "{{{2
  call s:set_up_options(a:key)
  return s:SID . 'work:' . a:key  " <SID>work:...
endfunction




function! s:chord_success(keys)  "{{{2
  call s:restore_options()
  return s:SID . 'success:' . a:keys  " <SID>success:...
endfunction




function! s:do_map(mode, options, remap_p, keys, rhs)  "{{{2
  " Assumption: Values in a:keys are <>-escaped, e.g., "<Tab>" not "\<Tab>".
  let opt_buffer = a:options =~# 'b' ? '<buffer>' : ''

  let already_mapped_p = 0
  for key in a:keys
    let rhs = maparg(key, a:mode)
    if rhs != '' && rhs !=# ('<SNR>' . matchstr(s:SID, '\d\+') . '_'
    \                        . 'chord_key(' . string(key) . ')')
      echohl WarningMsg
      echomsg 'Key' string(key) 'is already mapped in mode' string(a:mode)
      echohl None
      let already_mapped_p = !0
    endif
  endfor
  if a:options =~# 'u' && already_mapped_p
    echoerr 'Abort to map because of the above reason'
    return
  endif

  for key in a:keys
    execute printf('%smap <expr> %s %s  <SID>chord_key(%s)',
    \              a:mode, opt_buffer, key, string(s:unescape_lhs(key)))
  endfor

  let combos = []
  for i in range(1, len(a:keys) - 1)
    call extend(combos, s:permutations(a:keys, i))
  endfor
  for combo in combos
    execute printf('%smap <expr> <SID>work:%s  <SID>chord_cancel(%s)',
    \              a:mode, combo, string(s:unescape_lhs(combo)))
    execute printf('silent! %snoremap <unique> <Plug>(arpeggio-default:%s) %s',
    \              a:mode, combo, combo)
  endfor

  for combo in s:permutations(a:keys, len(a:keys))
    execute printf('%smap <expr> <SID>work:%s  <SID>chord_success(%s)',
    \              a:mode, combo, string(s:unescape_lhs(combo)))
    execute printf('%s%smap %s <SID>success:%s  %s',
    \              a:mode,
    \              a:remap_p ? '' : 'nore',
    \              s:to_map_arguments(a:options),
    \              combo,
    \              a:rhs)
  endfor
  return
endfunction




function! s:do_unmap(mode, options, keys)  "{{{2
  " FIXME: Mediate key mappings "<SID>work:" should be removed.
  "        But they may be used by other arpeggio key mappings and it's hard
  "        to determine whether a given mediate key mappng is still used or
  "        not in fast and exact way.  So that they aren't removed currently.
  let opt_buffer = a:options =~# 'b' ? '<buffer>' : ''

  for key in a:keys
    silent! execute printf('%sunmap %s %s',
    \                      a:mode, opt_buffer, key)
  endfor

  for combo in s:permutations(a:keys, len(a:keys))
    silent! execute printf('%sunmap %s <SID>success:%s',
    \                      a:mode,
    \                      s:to_map_arguments(a:options),
    \                      combo)
  endfor

  return
endfunction








" Misc.  "{{{1
function! s:SID()  "{{{2
  return matchstr(expand('<sfile>'), '<SNR>\zs\d\+\ze_')
endfunction
let s:SID = "\<SNR>" . s:SID() . '_'




function! s:each_char(s)  "{{{2
  return split(a:s, '.\zs')
endfunction




function! s:parse_args(q_args)  "{{{2
  " Parse <q-args> for :map commands into {options}, {lhs} and {rhs}.
  " Omitted arguments are expressed as 0.
  let ss = s:split_to_keys(a:q_args)

  let options = ''
  let ss = s:skip_spaces(ss)
  while 0 < len(ss)
    if ss[0] =~? '<buffer>'
      let options .= 'b'
    elseif ss[0] =~? '<expr>'
      let options .= 'e'
    elseif ss[0] =~? '<silent>'
      let options .= 's'
    elseif ss[0] =~? '<unique>'
      let options .= 'u'
    else
      break
    endif
    let ss = s:skip_spaces(ss[1:])
  endwhile

  let i = 0
  while i < len(ss)
    if ss[i] =~ '\s'
      break
    endif
    let i += 1
  endwhile
  let lhs = 1 <= i ? join(ss[:i-1], '') : 0
  let ss = s:skip_spaces(ss[(i):])

  let rhs = 0 < len(ss) ? join(ss, '') : 0

  return [options, lhs, rhs]
endfunction




function! s:permutations(ss, r)  "{{{2
  " This function is translated one of itertools.permutations() of Python 2.6:
  " http://www.python.org/doc/2.6/library/itertools.html#itertools.permutations
  let result = []
  let n = len(a:ss)
  let r = a:r
  let indices = range(n)
  let cycles = range(n, n-r+1, -1)
  let rest = n
  for _ in range(n-1, n-r+1, -1)
    let rest = rest * _
  endfor

  call add(result, join(map(indices[:r-1], 'a:ss[v:val]'), ''))
  for _ in range(rest - 1)
    for i in range(r-1, 0, -1)
      let cycles[i] -= 1
      if cycles[i] == 0
        let indices[(i):] = indices[(i+1):] + indices[(i):(i)]
        let cycles[i] = n - i
      else
        let j = cycles[i]
        let [indices[i], indices[-j]] = [indices[-j], indices[i]]
        call add(result, join(map(indices[:r-1], 'a:ss[v:val]'), ''))
        break
      endif
    endfor
  endfor
  return result
endfunction




function! s:restore_options()  "{{{2
  let &showcmd = s:original_showcmd
  let &timeout = s:original_timeout
  let &timeoutlen = s:original_timeoutlen
  let &ttimeoutlen = s:original_ttimeoutlen
  return
endfunction




function! s:set_up_options(key)  "{{{2
  let s:original_showcmd = &showcmd
  let s:original_timeout = &timeout
  let s:original_timeoutlen = &timeoutlen
  let s:original_ttimeoutlen = &ttimeoutlen

  set noshowcmd  " To avoid flickering in the bottom line.
  set timeout  " To ensure time out on :mappings
  let &timeoutlen = get(g:arpeggio_timeoutlens, a:key, g:arpeggio_timeoutlen)
  let &ttimeoutlen = (0 <= s:original_ttimeoutlen
  \                   ? s:original_ttimeoutlen
  \                   : s:original_timeoutlen)
  return
endfunction




function! s:skip_spaces(ss)  "{{{2
  let i = 0
  for i in range(len(a:ss))
    if a:ss[i] !~# '\s'
      break
    endif
  endfor
  return a:ss[(i):]
endfunction




function! s:split_to_keys(lhs)  "{{{2
  " Assumption: Special keys such as <C-u> are escaped with < and >, i.e.,
  "             a:lhs doesn't directly contain any escape sequences.
  return split(a:lhs, '\(<[^<>]\+>\|.\)\zs')
endfunction




function! s:to_map_arguments(options)  "{{{2
  let _ = {'b': '<buffer>', 'e': '<expr>', 's': '<silent>', 'u': '<unique>'}
  return join(map(s:each_char(a:options), '_[v:val]'))
endfunction




function! s:unescape_lhs(escaped_lhs)  "{{{2
  let keys = s:split_to_keys(a:escaped_lhs)
  call map(keys, 'v:val =~ "^<.*>$" ? eval(''"\'' . v:val . ''"'') : v:val')
  return join(keys, '')
endfunction




function! s:without(list, i)  "{{{2
  if 0 < a:i
    return a:list[0 : (a:i-1)] + a:list[(a:i+1) : -1]
  else
    return a:list[1:]
  endif
endfunction








" __END__  "{{{1
" vim: foldmethod=marker