mirror of
https://github.com/SpaceVim/SpaceVim.git
synced 2025-01-23 22:40:04 +08:00
814 lines
23 KiB
VimL
814 lines
23 KiB
VimL
" 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
|