" textobj-user - Create your own text objects
" Version: 0.7.6
" Copyright (C) 2007-2018 Kana Natsuno <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.
" }}}
" Interfaces  "{{{1
" simple  "{{{2

function! textobj#user#move(pattern, flags, previous_mode)
  let i = v:count1

  call s:prepare_movement(a:previous_mode)
  while 0 < i
    let result = searchpos(a:pattern, a:flags.'W')
    let i = i - 1
  endwhile
  return result
endfunction


" FIXME: growing the current selection like iw/aw, is/as, and others.
" FIXME: countable.
function! textobj#user#select(pattern, flags, previous_mode)
  let ORIG_POS = s:gpos_to_spos(getpos('.'))

  let pft = searchpos(a:pattern, 'ceW')
  let pfh = searchpos(a:pattern, 'bcW')
  call cursor(ORIG_POS)
  let pbh = searchpos(a:pattern, 'bcW')
  let pbt = searchpos(a:pattern, 'ceW')
  let pos = s:choose_better_pos(a:flags, ORIG_POS, pfh, pft, pbh, pbt)

  if pos isnot 0
    if a:flags !~# 'N'
      call s:range_select(pos[0], pos[1], s:choose_wise(a:flags))
    endif
    return pos
  else
    return s:cancel_selection(a:previous_mode, ORIG_POS)
  endif
endfunction

function! s:choose_better_pos(flags, ORIG_POS, pfh, pft, pbh, pbt)
  " search() family with 'c' flag may not be matched to a pattern which
  " matches to multiple lines.  To choose appropriate range, we have to check
  " another range [X] whether it contains the cursor or not.
  let vf = s:range_validp(a:pfh, a:pft)
  let vb = s:range_validp(a:pbh, a:pbt)
  let cf = vf && s:range_containsp(a:pfh, a:pft, a:ORIG_POS)
  let cb = vb && s:range_containsp(a:pbh, a:pbt, a:ORIG_POS)
  let lf = vf && s:range_in_linep(a:pfh, a:pft, a:ORIG_POS)
  let lb = vb && s:range_in_linep(a:pbh, a:pbt, a:ORIG_POS)

  if cb  " [X]
    return [a:pbh, a:pbt]
  elseif cf
    return [a:pfh, a:pft]
  elseif lf && a:flags =~# '[nl]'
    return [a:pfh, a:pft]
  elseif lb && a:flags =~# '[nl]'
    return [a:pbh, a:pbt]
  elseif vf && (a:flags =~# '[fn]' || a:flags !~# '[bcl]')
    return [a:pfh, a:pft]
  elseif vb && a:flags =~# '[bn]'
    return [a:pbh, a:pbt]
  else
    return 0
  endif
endfunction




" pair  "{{{2

" FIXME: NIY, but is this necessary?
" function! textobj#user#move_pair(pattern1, pattern2, flags)
" endfunction


" BUGS: With o_CTRL-V, this may not work properly.
function! textobj#user#select_pair(pattern1, pattern2, flags, previous_mode)
  let ORIG_POS = s:gpos_to_spos(getpos('.'))

  " adjust the cursor to the head of a:pattern2 if it's already in the range.
  let pos2c_tail = searchpos(a:pattern2, 'ceW')
  let pos2c_head = searchpos(a:pattern2, 'bcW')
  if !s:range_validp(pos2c_head, pos2c_tail)
    return s:cancel_selection(a:previous_mode, ORIG_POS)
  endif
  if s:range_containsp(pos2c_head, pos2c_tail, ORIG_POS)
    let more_flags = 'c'
  else
    let more_flags = ''
    call cursor(ORIG_POS)
  endif

  " get the positions of a:pattern1 and a:pattern2.
  let pos2p_head = searchpairpos(a:pattern1, '', a:pattern2, 'W'.more_flags)
  let pos2p_tail = searchpos(a:pattern2, 'ceW')
  if !s:range_validp(pos2p_head, pos2p_tail)
    return s:cancel_selection(a:previous_mode, ORIG_POS)
  endif
  call cursor(pos2p_head)
  let pos1p_head = searchpairpos(a:pattern1, '', a:pattern2, 'bW')
  let pos1p_tail = searchpos(a:pattern1, 'ceW')
  if !s:range_validp(pos1p_head, pos1p_tail)
    return s:cancel_selection(a:previous_mode, ORIG_POS)
  endif

  " select the range, then adjust if necessary.
  if a:flags =~# 'i'
    if s:range_no_text_without_edgesp(pos1p_tail, pos2p_head)
      return s:cancel_selection(a:previous_mode, ORIG_POS)
    endif
    call s:range_select(pos1p_tail, pos2p_head, s:choose_wise(a:flags))

    " adjust the range.
    let whichwrap_orig = &whichwrap
    let &whichwrap = '<,>'
    execute "normal! \<Left>o\<Right>"
    let &whichwrap = whichwrap_orig
  else
    call s:range_select(pos1p_head, pos2p_tail, s:choose_wise(a:flags))
  endif
  return
endfunction




function! textobj#user#define(pat0, pat1, pat2, guideline)  "{{{2
  let pat0 = s:rhs_escape(a:pat0)
  let pat1 = s:rhs_escape(a:pat1)
  let pat2 = s:rhs_escape(a:pat2)
  for function_name in keys(a:guideline)
    let _lhss = a:guideline[function_name]
    if type(_lhss) == type('')
      let lhss = [_lhss]
    else
      let lhss = _lhss
    endif

    for lhs in lhss
      if function_name == 'move-to-next'
        execute 'nnoremap' s:mapargs_single_move(lhs, pat0, '', 'n')
        execute 'vnoremap' s:mapargs_single_move(lhs, pat0, '', 'v')
        execute 'onoremap' s:mapargs_single_move(lhs, pat0, '', 'o')
      elseif function_name == 'move-to-next-end'
        execute 'nnoremap' s:mapargs_single_move(lhs, pat0, 'e', 'n')
        execute 'vnoremap' s:mapargs_single_move(lhs, pat0, 'e', 'v')
        execute 'onoremap' s:mapargs_single_move(lhs, pat0, 'e', 'o')
      elseif function_name == 'move-to-prev'
        execute 'nnoremap' s:mapargs_single_move(lhs, pat0, 'b', 'n')
        execute 'vnoremap' s:mapargs_single_move(lhs, pat0, 'b', 'v')
        execute 'onoremap' s:mapargs_single_move(lhs, pat0, 'b', 'o')
      elseif function_name == 'move-to-prev-end'
        execute 'nnoremap' s:mapargs_single_move(lhs, pat0, 'be', 'n')
        execute 'vnoremap' s:mapargs_single_move(lhs, pat0, 'be', 'v')
        execute 'onoremap' s:mapargs_single_move(lhs, pat0, 'be', 'o')
      elseif function_name == 'select-next' || function_name == 'select'
        execute 'vnoremap' s:mapargs_single_select(lhs, pat0, '', 'v')
        execute 'onoremap' s:mapargs_single_select(lhs, pat0, '', 'o')
      elseif function_name == 'select-prev'
        execute 'vnoremap' s:mapargs_single_select(lhs, pat0, 'b', 'v')
        execute 'onoremap' s:mapargs_single_select(lhs, pat0, 'b', 'o')
      elseif function_name == 'select-pair-all'
        execute 'vnoremap' s:mapargs_pair_select(lhs, pat1, pat2, 'a', 'v')
        execute 'onoremap' s:mapargs_pair_select(lhs, pat1, pat2, 'a', 'o')
      elseif function_name == 'select-pair-inner'
        execute 'vnoremap' s:mapargs_pair_select(lhs, pat1, pat2, 'i', 'v')
        execute 'onoremap' s:mapargs_pair_select(lhs, pat1, pat2, 'i', 'o')
      else
        throw 'Unknown function name: ' . string(function_name)
      endif
    endfor
  endfor
endfunction




function! textobj#user#map(plugin_name, obj_specs, ...)  "{{{2
  if a:0 == 0
    " It seems to be directly called by user - a:obj_specs are not normalized.
    call s:normalize(a:obj_specs)
  endif
  let banged_p = a:0 == 0 || a:1
  for [obj_name, specs] in items(a:obj_specs)
    for [spec_name, spec_info] in items(specs)
      let rhs = s:interface_mapping_name(a:plugin_name, obj_name, spec_name)
      if s:is_non_ui_property_name(spec_name)
        " ignore
      elseif spec_name =~# '^move-[npNP]$'
        for lhs in spec_info
          call s:map(banged_p, lhs, rhs)
        endfor
      elseif spec_name =~# '^select\(\|-[ai]\)$'
        for lhs in spec_info
          call s:objmap(banged_p, lhs, rhs)
        endfor
      else
        throw printf('Unknown property: %s given to %s',
        \            string(spec_name),
        \            string(obj_name))
      endif

      unlet spec_info  " to avoid E706.
    endfor
  endfor

  call s:define_failsafe_key_mappings(a:plugin_name, a:obj_specs)
endfunction




function! textobj#user#plugin(plugin_name, obj_specs)  "{{{2
  if a:plugin_name =~# '\L'
    throw '{plugin} contains non-lowercase alphabet: ' . string(a:plugin_name)
  endif
  let plugin = a:plugin_name
  let Plugin = substitute(a:plugin_name, '^\(\l\)', '\u\1', 0)

  let g:__textobj_{plugin} = s:plugin.new(a:plugin_name, a:obj_specs)

  execute
  \ 'command! -bang -bar -nargs=0 Textobj'.Plugin.'DefaultKeyMappings'
  \ 'call g:__textobj_'.plugin.'.define_default_key_mappings("<bang>" == "!")'
  call g:__textobj_{plugin}.define_interface_key_mappings()
  if (!has_key(a:obj_specs, '*no-default-key-mappings*'))
  \  && (!exists('g:textobj_'.plugin.'_no_default_key_mappings'))
    execute 'Textobj'.Plugin.'DefaultKeyMappings'
  endif

  return g:__textobj_{plugin}
endfunction








" Misc.  "{{{1
" pos  "{{{2

" Terms:
"   gpos        [bufnum, lnum, col, off] - a value returned by getpos()
"   spos        [lnum, col] - a value returned by searchpos()
"   pos         same as spos
function! s:gpos_to_spos(gpos)
  return a:gpos[1:2]
endfunction


function! s:pos_headp(pos)
  return a:pos[1] <= 1
endfunction

function! s:pos_lastp(pos)
  return a:pos[1] == len(getline(a:pos[0]))
endfunction


function! s:pos_le(pos1, pos2)  " less than or equal
  return ((a:pos1[0] < a:pos2[0])
  \       || (a:pos1[0] == a:pos2[0] && a:pos1[1] <= a:pos2[1]))
endfunction




" range  "{{{2

function! s:range_containsp(range_head, range_tail, target_pos)
  return (s:pos_le(a:range_head, a:target_pos)
  \       && s:pos_le(a:target_pos, a:range_tail))
endfunction


function! s:range_in_linep(range_head, range_tail, target_pos)
  return a:range_head[0] == a:target_pos[0]
  \      || a:range_tail[0] == a:target_pos[0]
endfunction


function! s:range_no_text_without_edgesp(range_head, range_tail)
  let [hl, hc] = a:range_head
  let [tl, tc] = a:range_tail
  return ((hl == tl && hc - tc == -1)
  \       || (hl - tl == -1
  \           && (s:pos_lastp(a:range_head) && s:pos_headp(a:range_tail))))
endfunction


function! s:range_validp(range_head, range_tail)
  let NULL_POS = [0, 0]
  return (a:range_head != NULL_POS) && (a:range_tail != NULL_POS)
endfunction


function! s:range_select(range_head, range_tail, fallback_wise)
  execute 'normal!' s:wise(a:fallback_wise)
  call cursor(a:range_head)
  normal! o
  call cursor(a:range_tail)
  if &selection ==# 'exclusive'
    normal! l
  endif
endfunction




" Save the last cursort position  "{{{2

noremap <expr> <SID>(save-cursor-pos) <SID>save_cursor_pos()

" let s:last_cursor_gpos = getpos('.')

function! s:save_cursor_pos()
  let s:last_cursor_gpos = getpos('.')
  return ''
endfunction




" for textobj#user#define()  "{{{2

function! s:rhs_escape(pattern)
  let r = a:pattern
  let r = substitute(r, '<', '<LT>', 'g')
  let r = substitute(r, '|', '<Bar>', 'g')
  return r
endfunction


function! s:mapargs_single_move(lhs, pattern, flags, previous_mode)
  return printf('<silent> %s  :<C-u>call textobj#user#move(%s, %s, %s)<CR>',
              \ a:lhs,
              \ string(a:pattern), string(a:flags), string(a:previous_mode))
endfunction

function! s:mapargs_single_select(lhs, pattern, flags, previous_mode)
  return printf('<silent> %s  :<C-u>call textobj#user#select(%s, %s, %s)<CR>',
              \ a:lhs,
              \ string(a:pattern), string(a:flags), string(a:previous_mode))
endfunction

function! s:mapargs_pair_select(lhs, pattern1, pattern2, flags, previous_mode)
  return printf(
       \   '<silent> %s  :<C-u>call textobj#user#select_pair(%s,%s,%s,%s)<CR>',
       \   a:lhs,
       \   string(a:pattern1), string(a:pattern2),
       \   string(a:flags), string(a:previous_mode)
       \ )
endfunction




" for textobj#user#plugin()  "{{{2
" basics  "{{{3
let s:plugin = {}

function! s:plugin.new(plugin_name, obj_specs)
  let _ = extend({'name': a:plugin_name, 'obj_specs': a:obj_specs},
  \              s:plugin, 'keep')
  call _.normalize()
  return _
endfunction

function! s:plugin.normalize()
  call s:normalize(self.obj_specs)
endfunction

function! s:normalize(obj_specs)
  call s:normalize_property_names(a:obj_specs)
  call s:normalize_property_values(a:obj_specs)
endfunction

function! s:normalize_property_names(obj_specs)
  for spec in values(a:obj_specs)
    for old_prop_name in keys(spec)
      if old_prop_name =~ '^\*.*\*$'
        let new_prop_name = substitute(old_prop_name, '^\*\(.*\)\*$', '\1', '')
        let spec[new_prop_name] = spec[old_prop_name]
        unlet spec[old_prop_name]
      endif
    endfor
  endfor
endfunction

function! s:normalize_property_values(obj_specs)
  for [obj_name, specs] in items(a:obj_specs)
    for [spec_name, spec_info] in items(specs)
      if s:is_ui_property_name(spec_name)
        if type(spec_info) == type('')
          let specs[spec_name] = [spec_info]
        endif
      endif

      if spec_name =~# '-function$'
        if spec_info =~# '^s:'
          if has_key(specs, 'sfile')
            let specs[spec_name] = substitute(spec_info,
            \                                 '^s:',
            \                                 s:snr_prefix(specs['sfile']),
            \                                 '')
          else
            echoerr '"sfile" must be given to use a script-local function:'
            \       string(spec_name) '/' string(spec_info)
          endif
        else
          " Nothing to do.
        endif
      elseif spec_name ==# 'pattern'
        if !has_key(specs, 'region-type')
          let specs['region-type'] = 'v'
        endif
        if !has_key(specs, 'scan')
          let specs['scan'] = 'forward'
        endif
      endif

      unlet spec_info  " to avoid E706.
    endfor
  endfor
endfunction


function! s:plugin.define_default_key_mappings(banged_p)  "{{{3
  call textobj#user#map(self.name, self.obj_specs, a:banged_p)
endfunction


function! s:plugin.define_interface_key_mappings()  "{{{3
  let RHS_FORMAT =
  \   '<SID>(save-cursor-pos)'
  \ . ':<C-u>call g:__textobj_' . self.name . '.%s('
  \ .   '"%s",'
  \ .   '"%s",'
  \ .   '"<mode>"'
  \ . ')<Return>'

  for [obj_name, specs] in items(self.obj_specs)
    for spec_name in filter(keys(specs), 's:is_ui_property_name(v:val)')
      " lhs
      let lhs = self.interface_mapping_name(obj_name, spec_name)

      " rhs
      let _ = spec_name . '-function'
      if has_key(specs, _)
        let do = 'do_by_function'
      elseif has_key(specs, 'pattern')
        let do = 'do_by_pattern'
      else
        " skip to allow to define user's own {rhs} of the interface mapping.
        continue
      endif
      let rhs = printf(RHS_FORMAT, do, spec_name, obj_name)

      " map
      if spec_name =~# '^move'
        let MapFunction = function('s:noremap')
      else  " spec_name =~# '^select'
        let MapFunction = function('s:objnoremap')
      endif
      call MapFunction(1, '<silent> <script>' . lhs, rhs)
    endfor
  endfor
endfunction


function! s:plugin.interface_mapping_name(obj_name, spec_name)  "{{{3
  return s:interface_mapping_name(self.name, a:obj_name, a:spec_name)
endfunction


function! s:interface_mapping_name(plugin_name, obj_name, spec_name)  "{{{3
  let _ = printf('<Plug>(textobj-%s-%s-%s)',
  \              a:plugin_name,
  \              a:obj_name,
  \              substitute(a:spec_name, '^\(move\|select\)', '', ''))
  let _ = substitute(_, '-\+', '-', 'g')
  let _ = substitute(_, '-\ze)$', '', '')
  return _
endfunction


" "pattern" wrappers  "{{{3
function! s:plugin.do_by_pattern(spec_name, obj_name, previous_mode)
  let specs = self.obj_specs[a:obj_name]
  let flags = s:PATTERN_FLAGS_TABLE[a:spec_name]
  \           . (a:spec_name =~# '^select' ? specs['region-type'] : '')
  \           . (a:spec_name ==# 'select' ? specs['scan'][0] : '')
  call {s:PATTERN_IMPL_TABLE[a:spec_name]}(
  \   specs['pattern'],
  \   flags,
  \   a:previous_mode
  \ )
endfunction

let s:PATTERN_IMPL_TABLE = {
\   'move-n': 's:move_wrapper',
\   'move-N': 's:move_wrapper',
\   'move-p': 's:move_wrapper',
\   'move-P': 's:move_wrapper',
\   'select': 'textobj#user#select',
\   'select-a': 's:select_pair_wrapper',
\   'select-i': 's:select_pair_wrapper',
\ }

let s:PATTERN_FLAGS_TABLE = {
\   'move-n': '',
\   'move-N': 'e',
\   'move-p': 'b',
\   'move-P': 'be',
\   'select': '',
\   'select-a': 'a',
\   'select-i': 'i',
\ }

function! s:move_wrapper(patterns, flags, previous_mode)
  " \x16 = CTRL-V
  call textobj#user#move(
  \   a:patterns,
  \   substitute(a:flags, '[vV\x16]', '', 'g'),
  \   a:previous_mode
  \ )
endfunction

function! s:select_pair_wrapper(patterns, flags, previous_mode)
  call textobj#user#select_pair(
  \   a:patterns[0],
  \   a:patterns[1],
  \   a:flags,
  \   a:previous_mode
  \ )
endfunction


" "*-function" wrappers  "{{{3
function! s:plugin.do_by_function(spec_name, obj_name, previous_mode)
  let specs = self.obj_specs[a:obj_name]
  call {s:FUNCTION_IMPL_TABLE[a:spec_name]}(
  \   specs[a:spec_name . '-function'],
  \   a:spec_name,
  \   a:previous_mode
  \ )
endfunction

let s:FUNCTION_IMPL_TABLE = {
\   'move-n': 's:move_function_wrapper',
\   'move-N': 's:move_function_wrapper',
\   'move-p': 's:move_function_wrapper',
\   'move-P': 's:move_function_wrapper',
\   'select': 's:select_function_wrapper',
\   'select-a': 's:select_function_wrapper',
\   'select-i': 's:select_function_wrapper',
\ }

function! s:select_function_wrapper(function_name, spec_name, previous_mode)
  let ORIG_POS = s:gpos_to_spos(getpos('.'))

  let _ = function(a:function_name)()
  if _ is 0
    call s:cancel_selection(a:previous_mode, ORIG_POS)
  else
    let [motion_type, start_position, end_position] = _
    call s:range_select(
    \   s:gpos_to_spos(start_position),
    \   s:gpos_to_spos(end_position),
    \   motion_type
    \ )
  endif
endfunction

function! s:move_function_wrapper(function_name, spec_name, previous_mode)
  " ":" in Visual mode moves the cursor to '< before executing Ex command
  " (= the context where this function is called).  For example:
  "
  " -----------------------------------------
  " | User typed | Cursor | '<     | '>     |
  " |---------------------------------------|
  " | 1GVV       | Line 1 | Line 1 | Line 1 |
  " |     Vjj    | Line 3 | Line 1 | Line 1 |
  " |        :   | Line 1 | Line 1 | Line 3 |
  " -----------------------------------------
  "
  " So that user-defined motion does not work correctly in Visual mode if the
  " last "expected" cursor position is not '<.  That's why we have to save and
  " restore s:last_cursor_gpos explicitly.
  call cursor(s:gpos_to_spos(s:last_cursor_gpos))

  let _ = function(a:function_name)()

  call s:prepare_movement(a:previous_mode)
  if _ is 0
    call cursor(s:gpos_to_spos(s:last_cursor_gpos))
  else
    " FIXME: Support motion_type.  But unlike selecting a text object, the
    " motion_type must be known before calling a user-given function.
    let [motion_type, start_position, end_position] = _
    call setpos('.', a:spec_name =~# '[np]$' ? start_position : end_position)
  endif
endfunction


" map wrappers  "{{{3
function! s:_map(map_commands, forced_p, lhs, rhs)
  for _ in a:map_commands
    execute 'silent!' (_) (a:forced_p ? '' : '<unique>') a:lhs
    \       substitute(a:rhs, '<mode>', _[0], 'g')
  endfor
endfunction


function! s:noremap(forced_p, lhs, rhs)
  let v = s:proper_visual_mode(a:lhs)
  call s:_map(['nnoremap', v.'noremap', 'onoremap'], a:forced_p, a:lhs, a:rhs)
endfunction

function! s:objnoremap(forced_p, lhs, rhs)
  let v = s:proper_visual_mode(a:lhs)
  call s:_map([v.'noremap', 'onoremap'], a:forced_p, a:lhs, a:rhs)
endfunction


function! s:map(forced_p, lhs, rhs)
  let v = s:proper_visual_mode(a:lhs)
  call s:_map(['nmap', v.'map', 'omap'], a:forced_p, a:lhs, a:rhs)
endfunction

function! s:objmap(forced_p, lhs, rhs)
  let v = s:proper_visual_mode(a:lhs)
  call s:_map([v.'map', 'omap'], a:forced_p, a:lhs, a:rhs)
endfunction




" Etc  "{{{2

function! textobj#user#_sid()
  return maparg('<SID>', 'n')
endfunction
nnoremap <SID>  <SID>


function! s:prepare_movement(previous_mode)
  if a:previous_mode ==# 'v'
    normal! gv
  endif
endfunction


function! s:cancel_selection(previous_mode, orig_pos)
  if a:previous_mode ==# 'v'
    normal! gv
  else  " if a:previous_mode ==# 'o'
    call cursor(a:orig_pos)
  endif
endfunction


function! s:snr_prefix(sfile)
  " :redir captures also 'verbose' messages.  Its result must be filtered.
  redir => result
  silent scriptnames
  redir END

  for line in split(result, '\n')
    let _ = matchlist(line, '^\s*\(\d\+\):\s*\(.*\)$')
    if !empty(_) && s:normalize_path(a:sfile) ==# s:normalize_path(_[2])
      return printf("\<SNR>%d_", _[1])
    endif
  endfor

  return 's:'
endfunction


function! s:normalize_path(unnormalized_path)
  return substitute(fnamemodify(a:unnormalized_path, ':p'), '\\', '/', 'g')
endfunction


function! s:choose_wise(flags)
  return
  \ a:flags =~# 'v' ? 'v' :
  \ a:flags =~# 'V' ? 'V' :
  \ a:flags =~# "\<C-v>" ? "\<C-v>" :
  \ 'v'
endfunction


function! s:wise(default)
  return (exists('v:motion_force') && v:motion_force != ''
  \       ? v:motion_force
  \       : a:default)
endfunction


function! s:proper_visual_mode(lhs)
  " Return the mode prefix of proper "visual" mode for a:lhs key sequence.
  " a:lhs should not be defined in Select mode if a:lhs starts with
  " a printable character.  Otherwise a:lhs may be defined in Select mode.

  " a:lhs may be prefixed with :map-arguments such as <buffer>.
  " It's necessary to remove them to determine the first character in a:lhs.
  let s1 = substitute(
  \   a:lhs,
  \   '\v^(\<(buffer|silent|special|script|expr|unique)\>\s*)*',
  \   '',
  \   ''
  \ )
  " All characters in a:lhs are printable characters, so it's necessary to
  " convert <>-escaped notation into corresponding characters.
  let s2 = substitute(s1,
  \                   '^\(<[^<>]\+>\)',
  \                   '\=eval("\"\\" . submatch(1) . "\"")',
  \                   '')
  return s2 =~# '^\p' ? 'x' : 'v'
endfunction


let s:ui_property_names = [
\   'move-N',
\   'move-P',
\   'move-n',
\   'move-p',
\   'select',
\   'select-a',
\   'select-i',
\ ]

function! s:is_ui_property_name(name)
  return 0 <= index(s:ui_property_names, a:name)
endfunction

let s:non_ui_property_names = [
\   'move-N-function',
\   'move-P-function',
\   'move-n-function',
\   'move-p-function',
\   'pattern',
\   'region-type',
\   'scan',
\   'select-a-function',
\   'select-function',
\   'select-i-function',
\   'sfile',
\ ]

function! s:is_non_ui_property_name(name)
  return 0 <= index(s:non_ui_property_names, a:name)
endfunction


function! s:define_failsafe_key_mappings(plugin_name, obj_specs)
  for [obj_name, specs] in items(a:obj_specs)
    for [spec_name, spec_info] in items(specs)
      if !s:is_non_ui_property_name(spec_name)
        let lhs = s:interface_mapping_name(a:plugin_name, obj_name, spec_name)
        if maparg(lhs, 'v') == ''
          let rhs = printf('<SID>fail(%s)',
          \                string(substitute(lhs, '<', '<LT>', 'g')))
          let mapf = spec_name =~# '^move-[npNP]$'
          \          ? 's:noremap'
          \          : 's:objnoremap'
          call {mapf}(0, '<expr>' . lhs, rhs)
        endif
      endif
      unlet spec_info  " to avoid E706.
    endfor
  endfor
endfunction

function! s:fail(interface_key_mapping_lhs)
  throw printf('Text object %s is not defined', a:interface_key_mapping_lhs)
endfunction








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