1
0
mirror of https://github.com/SpaceVim/SpaceVim.git synced 2025-01-24 06:30:03 +08:00
SpaceVim/bundle/vim-textobj-user/autoload/textobj/user.vim

814 lines
23 KiB
VimL
Raw Normal View History

2020-06-13 14:06:35 +08:00
" 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