Regenerate nvim config
This commit is contained in:
@ -0,0 +1,204 @@
|
||||
" textobj-sandwich: pick up a sandwich!
|
||||
|
||||
function! textobj#sandwich#auto(mode, a_or_i, ...) abort "{{{
|
||||
let kind = 'auto'
|
||||
let defaultopt = s:default_options(kind)
|
||||
let argopt = get(a:000, 0, {})
|
||||
let opt = sandwich#opt#new(kind, defaultopt, argopt)
|
||||
if a:0 >= 2
|
||||
let recipes = textobj#sandwich#recipes#new(kind, a:mode, a:2)
|
||||
else
|
||||
let recipes = textobj#sandwich#recipes#new(kind, a:mode)
|
||||
endif
|
||||
call recipes.uniq(opt)
|
||||
|
||||
if recipes.integrated != []
|
||||
let g:textobj#sandwich#object = textobj#sandwich#textobj#new(
|
||||
\ kind, a:a_or_i, a:mode, v:count1, recipes, opt)
|
||||
let cmd = ":\<C-u>call textobj#sandwich#select()\<CR>"
|
||||
else
|
||||
let cmd = a:mode ==# 'o' ? "\<Esc>" : "\<Ignore>"
|
||||
endif
|
||||
return cmd
|
||||
endfunction
|
||||
"}}}
|
||||
function! textobj#sandwich#query(mode, a_or_i, ...) abort "{{{
|
||||
let kind = 'query'
|
||||
let defaultopt = s:default_options(kind)
|
||||
let argopt = get(a:000, 0, {})
|
||||
let opt = sandwich#opt#new(kind, defaultopt, argopt)
|
||||
if a:0 >= 2
|
||||
let recipes = textobj#sandwich#recipes#new(kind, a:mode, a:2)
|
||||
else
|
||||
let recipes = textobj#sandwich#recipes#new(kind, a:mode)
|
||||
endif
|
||||
let timeout = s:get_sandwich_option('timeout', &timeout)
|
||||
let timeoutlen = max([0, s:get_sandwich_option('timeoutlen', &timeoutlen)])
|
||||
call recipes.query(opt, timeout, timeoutlen)
|
||||
|
||||
if recipes.integrated != []
|
||||
let g:textobj#sandwich#object = textobj#sandwich#textobj#new(
|
||||
\ kind, a:a_or_i, a:mode, v:count1, recipes, opt)
|
||||
let cmd = ":\<C-u>call textobj#sandwich#select()\<CR>"
|
||||
else
|
||||
let cmd = a:mode ==# 'o' ? "\<Esc>" : "\<Ignore>"
|
||||
endif
|
||||
return cmd
|
||||
endfunction
|
||||
"}}}
|
||||
function! textobj#sandwich#select() abort "{{{
|
||||
if !exists('g:textobj#sandwich#object')
|
||||
return
|
||||
endif
|
||||
|
||||
let view = winsaveview()
|
||||
let textobj = g:textobj#sandwich#object
|
||||
call textobj.initialize()
|
||||
let stimeoutlen = max([0, s:get_textobj_option('stimeoutlen', 500)])
|
||||
let [virtualedit, whichwrap] = [&virtualedit, &whichwrap]
|
||||
let [&virtualedit, &whichwrap] = ['onemore', 'h,l']
|
||||
try
|
||||
let candidates = textobj.list(stimeoutlen)
|
||||
let elected = textobj.elect(candidates)
|
||||
call winrestview(view)
|
||||
call textobj.select(elected)
|
||||
finally
|
||||
let mark_latestjump = s:get_textobj_option('latest_jump', 1)
|
||||
call textobj.finalize(mark_latestjump)
|
||||
if !textobj.done
|
||||
call winrestview(view)
|
||||
endif
|
||||
let [&virtualedit, &whichwrap] = [virtualedit, whichwrap]
|
||||
endtry
|
||||
|
||||
let g:textobj#sandwich#object = textobj " This is required in case that textobj-sandwich call textobj-sandwich itself in its recipe.
|
||||
endfunction
|
||||
"}}}
|
||||
|
||||
function! s:get_sandwich_option(name, default) abort "{{{
|
||||
if exists('g:textobj#sandwich#' . a:name)
|
||||
return eval('g:textobj#sandwich#' . a:name)
|
||||
endif
|
||||
if exists('g:sandwich#' . a:name)
|
||||
return eval('g:sandwich#' . a:name)
|
||||
endif
|
||||
return a:default
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:get_textobj_option(name, default) abort "{{{
|
||||
return get(g:, 'textobj#sandwich#' . a:name, a:default)
|
||||
endfunction
|
||||
"}}}
|
||||
|
||||
" recipe "{{{
|
||||
function! textobj#sandwich#get_recipes() abort "{{{
|
||||
if exists('b:textobj_sandwich_recipes')
|
||||
let recipes = b:textobj_sandwich_recipes
|
||||
elseif exists('g:textobj#sandwich#recipes')
|
||||
let recipes = g:textobj#sandwich#recipes
|
||||
else
|
||||
let recipes = g:textobj#sandwich#default_recipes
|
||||
endif
|
||||
return deepcopy(recipes)
|
||||
endfunction
|
||||
"}}}
|
||||
if exists('g:textobj#sandwich#default_recipes')
|
||||
unlockvar! g:textobj#sandwich#default_recipes
|
||||
endif
|
||||
let g:textobj#sandwich#default_recipes = []
|
||||
lockvar! g:textobj#sandwich#default_recipes
|
||||
"}}}
|
||||
|
||||
" option "{{{
|
||||
function! s:default_options(kind) abort "{{{
|
||||
return get(b:, 'textobj_sandwich_options', g:textobj#sandwich#options)[a:kind]
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:initialize_options(...) abort "{{{
|
||||
let manner = a:0 ? a:1 : 'keep'
|
||||
let g:textobj#sandwich#options = s:get_textobj_option('options', {})
|
||||
for kind in ['auto', 'query']
|
||||
if !has_key(g:textobj#sandwich#options, kind)
|
||||
let g:textobj#sandwich#options[kind] = {}
|
||||
endif
|
||||
call extend(g:textobj#sandwich#options[kind],
|
||||
\ sandwich#opt#defaults(kind), manner)
|
||||
endfor
|
||||
endfunction
|
||||
call s:initialize_options()
|
||||
"}}}
|
||||
function! textobj#sandwich#set_default() abort "{{{
|
||||
call s:initialize_options('force')
|
||||
endfunction
|
||||
"}}}
|
||||
function! textobj#sandwich#set(kind, option, value) abort "{{{
|
||||
if s:argument_error(a:kind, a:option, a:value)
|
||||
return
|
||||
endif
|
||||
|
||||
if a:kind ==# 'all'
|
||||
let kinds = ['auto', 'query']
|
||||
else
|
||||
let kinds = [a:kind]
|
||||
endif
|
||||
|
||||
for kind in kinds
|
||||
let g:textobj#sandwich#options[kind][a:option] = a:value
|
||||
endfor
|
||||
endfunction
|
||||
"}}}
|
||||
function! textobj#sandwich#setlocal(kind, option, value) abort "{{{
|
||||
if s:argument_error(a:kind, a:option, a:value)
|
||||
return
|
||||
endif
|
||||
|
||||
if !exists('b:textobj_sandwich_options')
|
||||
let b:textobj_sandwich_options = deepcopy(g:textobj#sandwich#options)
|
||||
endif
|
||||
|
||||
if a:kind ==# 'all'
|
||||
let kinds = ['auto', 'query']
|
||||
else
|
||||
let kinds = [a:kind]
|
||||
endif
|
||||
|
||||
for kind in kinds
|
||||
let b:textobj_sandwich_options[kind][a:option] = a:value
|
||||
endfor
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:argument_error(kind, option, value) abort "{{{
|
||||
if !(a:kind ==# 'auto' || a:kind ==# 'query' || a:kind ==# 'all')
|
||||
echohl WarningMsg
|
||||
echomsg 'Invalid kind "' . a:kind . '".'
|
||||
echohl NONE
|
||||
return 1
|
||||
endif
|
||||
|
||||
" NOTE: Regaardless of a:kind, keys(defaults) is fixed since
|
||||
" the two textobjects have same kinds of options.
|
||||
let defaults = sandwich#opt#defaults('auto')
|
||||
if filter(keys(defaults), 'v:val ==# a:option') == []
|
||||
echohl WarningMsg
|
||||
echomsg 'Invalid option name "' . a:kind . '".'
|
||||
echohl NONE
|
||||
return 1
|
||||
endif
|
||||
|
||||
if type(a:value) != type(defaults[a:option])
|
||||
echohl WarningMsg
|
||||
echomsg 'Invalid type of value. ' . string(a:value)
|
||||
echohl NONE
|
||||
return 1
|
||||
endif
|
||||
return 0
|
||||
endfunction
|
||||
"}}}
|
||||
"}}}
|
||||
|
||||
unlet! g:textobj#sandwich#object
|
||||
|
||||
|
||||
" vim:set foldmethod=marker:
|
||||
" vim:set commentstring="%s:
|
||||
" vim:set ts=2 sts=2 sw=2:
|
||||
@ -0,0 +1,128 @@
|
||||
" common functions
|
||||
|
||||
" variables "{{{
|
||||
" null valiables
|
||||
let s:null_coord = [0, 0]
|
||||
|
||||
" patchs
|
||||
if v:version > 704 || (v:version == 704 && has('patch237'))
|
||||
let s:has_patch_7_4_358 = has('patch-7.4.358')
|
||||
else
|
||||
let s:has_patch_7_4_358 = v:version == 704 && has('patch358')
|
||||
endif
|
||||
"}}}
|
||||
|
||||
function! s:c2p(coord) abort "{{{
|
||||
return [0] + a:coord + [0]
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:escape(string) abort "{{{
|
||||
return escape(a:string, '~"\.^$[]*')
|
||||
endfunction
|
||||
"}}}
|
||||
" function! s:sort(list, func, count) abort "{{{
|
||||
if s:has_patch_7_4_358
|
||||
function! s:sort(list, func, count) abort
|
||||
return sort(a:list, a:func)
|
||||
endfunction
|
||||
else
|
||||
function! s:sort(list, func, count) abort
|
||||
" NOTE: len(a:list) is always larger than count or same.
|
||||
" FIXME: The number of item in a:list would not be large, but if there was
|
||||
" any efficient argorithm, I would rewrite here.
|
||||
let len = len(a:list)
|
||||
for i in range(a:count)
|
||||
if len - 2 >= i
|
||||
let min = len - 1
|
||||
for j in range(len - 2, i, -1)
|
||||
if call(a:func, [a:list[min], a:list[j]]) >= 0
|
||||
let min = j
|
||||
endif
|
||||
endfor
|
||||
|
||||
if min > i
|
||||
call insert(a:list, remove(a:list, min), i)
|
||||
endif
|
||||
endif
|
||||
endfor
|
||||
return a:list
|
||||
endfunction
|
||||
endif
|
||||
"}}}
|
||||
function! s:get_displaycoord(coord) abort "{{{
|
||||
let [lnum, col] = a:coord
|
||||
|
||||
if [lnum, col] != s:null_coord
|
||||
let disp_col = col == 1 ? 1 : strdisplaywidth(getline(lnum)[: col - 2]) + 1
|
||||
else
|
||||
let disp_col = 0
|
||||
endif
|
||||
return [lnum, disp_col]
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:set_displaycoord(disp_coord) abort "{{{
|
||||
if a:disp_coord != s:null_coord
|
||||
execute 'normal! ' . a:disp_coord[0] . 'G' . a:disp_coord[1] . '|'
|
||||
endif
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:get_displaysyntax(coord) abort "{{{
|
||||
return synIDattr(synIDtrans(synID(a:coord[0], a:coord[1], 1)), 'name')
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:is_matched_syntax(coord, syntaxID) abort "{{{
|
||||
if a:coord == s:null_coord
|
||||
return 0
|
||||
elseif a:syntaxID == []
|
||||
return 1
|
||||
else
|
||||
return s:get_displaysyntax(a:coord) ==? a:syntaxID[0]
|
||||
endif
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:is_included_syntax(coord, syntaxID) abort "{{{
|
||||
let synstack = map(synstack(a:coord[0], a:coord[1]),
|
||||
\ 'synIDattr(synIDtrans(v:val), "name")')
|
||||
|
||||
if a:syntaxID == []
|
||||
return 1
|
||||
elseif synstack == []
|
||||
if a:syntaxID == ['']
|
||||
return 1
|
||||
else
|
||||
return 0
|
||||
endif
|
||||
else
|
||||
return filter(map(copy(a:syntaxID), '''\c'' . v:val'), 'match(synstack, v:val) > -1') != []
|
||||
endif
|
||||
endfunction
|
||||
"}}}
|
||||
|
||||
function! s:export(namelist) abort "{{{
|
||||
let module = {}
|
||||
for name in a:namelist
|
||||
let module[name] = function('s:' . name)
|
||||
endfor
|
||||
return module
|
||||
endfunction
|
||||
"}}}
|
||||
let s:lib = s:export([
|
||||
\ 'c2p',
|
||||
\ 'escape',
|
||||
\ 'sort',
|
||||
\ 'get_displaycoord',
|
||||
\ 'set_displaycoord',
|
||||
\ 'get_displaysyntax',
|
||||
\ 'is_matched_syntax',
|
||||
\ 'is_included_syntax',
|
||||
\ ])
|
||||
lockvar! s:lib
|
||||
|
||||
function! textobj#sandwich#lib#import() abort "{{{
|
||||
return s:lib
|
||||
endfunction
|
||||
"}}}
|
||||
|
||||
" vim:set foldmethod=marker:
|
||||
" vim:set commentstring="%s:
|
||||
" vim:set ts=2 sts=2 sw=2:
|
||||
@ -0,0 +1,278 @@
|
||||
" recipes object
|
||||
|
||||
" variables "{{{
|
||||
let s:TRUE = 1
|
||||
let s:FALSE = 0
|
||||
|
||||
" types
|
||||
let s:type_num = type(0)
|
||||
let s:type_str = type('')
|
||||
"}}}
|
||||
|
||||
function! textobj#sandwich#recipes#new(kind, mode, ...) abort "{{{
|
||||
let recipes = deepcopy(s:recipes)
|
||||
if a:0 > 0
|
||||
let recipes.arg = a:1
|
||||
let recipes.arg_given = 1
|
||||
endif
|
||||
call recipes._integrate(a:kind, a:mode)
|
||||
return recipes
|
||||
endfunction
|
||||
"}}}
|
||||
|
||||
" s:recipes "{{{
|
||||
let s:recipes = {
|
||||
\ 'arg': [],
|
||||
\ 'arg_given': 0,
|
||||
\ 'integrated': [],
|
||||
\ }
|
||||
"}}}
|
||||
function! s:recipes.uniq(opt) dict abort "{{{
|
||||
let opt_regex = a:opt.of('regex')
|
||||
let opt_expr = a:opt.of('expr')
|
||||
let opt_noremap = a:opt.of('noremap')
|
||||
let recipes = copy(self.integrated)
|
||||
call filter(self.integrated, 0)
|
||||
while recipes != []
|
||||
let recipe = remove(recipes, 0)
|
||||
call add(self.integrated, recipe)
|
||||
if has_key(recipe, 'buns')
|
||||
call filter(recipes, '!s:is_duplicated_buns(v:val, recipe, opt_regex, opt_expr)')
|
||||
elseif has_key(recipe, 'external')
|
||||
call filter(recipes, '!s:is_duplicated_external(v:val, recipe, opt_noremap)')
|
||||
endif
|
||||
endwhile
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:recipes.query(opt, timeout, timeoutlen) dict abort "{{{
|
||||
let recipes = deepcopy(self.integrated)
|
||||
let clock = sandwich#clock#new()
|
||||
let input = ''
|
||||
let last_compl_match = ['', []]
|
||||
while 1
|
||||
let c = getchar(0)
|
||||
if empty(c)
|
||||
if clock.started && a:timeout && a:timeoutlen > 0 && clock.elapsed() > a:timeoutlen
|
||||
let [input, recipes] = last_compl_match
|
||||
break
|
||||
else
|
||||
sleep 20m
|
||||
continue
|
||||
endif
|
||||
endif
|
||||
|
||||
let c = type(c) == s:type_num ? nr2char(c) : c
|
||||
|
||||
" exit loop if <Esc> is pressed
|
||||
if c is# "\<Esc>"
|
||||
let input = "\<Esc>"
|
||||
break
|
||||
endif
|
||||
|
||||
let input .= c
|
||||
|
||||
" check forward match
|
||||
let n_fwd = len(filter(recipes, 's:is_input_matched(v:val, input, a:opt, 0)'))
|
||||
|
||||
" check complete match
|
||||
let n_comp = len(filter(copy(recipes), 's:is_input_matched(v:val, input, a:opt, 1)'))
|
||||
if n_comp || strchars(input) == 1
|
||||
if len(recipes) == n_comp
|
||||
break
|
||||
else
|
||||
call clock.stop()
|
||||
call clock.start()
|
||||
let last_compl_match = [input, copy(recipes)]
|
||||
endif
|
||||
else
|
||||
if clock.started && !n_fwd
|
||||
let [input, recipes] = last_compl_match
|
||||
" FIXME: Additional keypress, 'c', is ignored now, but it should be pressed.
|
||||
" The problem is feedkeys() cannot be used for here...
|
||||
break
|
||||
endif
|
||||
endif
|
||||
if recipes == [] | break | endif
|
||||
endwhile
|
||||
call clock.stop()
|
||||
|
||||
if filter(recipes, 's:is_input_matched(v:val, input, a:opt, 1)') != []
|
||||
let recipe = recipes[0]
|
||||
if has_key(recipe, 'buns') || has_key(recipe, 'external')
|
||||
let self.integrated = [recipe]
|
||||
else
|
||||
let self.integrated = []
|
||||
endif
|
||||
else
|
||||
if s:is_input_fallback(input)
|
||||
let c = split(input, '\zs')[0]
|
||||
let recipe = {'buns': [c, c], 'expr': 0, 'regex': 0}
|
||||
let self.integrated = [recipe]
|
||||
else
|
||||
let self.integrated = []
|
||||
endif
|
||||
endif
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:recipes._integrate(kind, mode) dict abort "{{{
|
||||
let self.integrated = []
|
||||
if self.arg_given
|
||||
let self.integrated += self.arg
|
||||
else
|
||||
let self.integrated += sandwich#get_recipes()
|
||||
let self.integrated += textobj#sandwich#get_recipes()
|
||||
endif
|
||||
let filter = 's:has_filetype(v:val)
|
||||
\ && s:has_kind(v:val, a:kind)
|
||||
\ && s:has_mode(v:val, a:mode)
|
||||
\ && s:has_action(v:val)
|
||||
\ && s:expr_filter(v:val)'
|
||||
call filter(self.integrated, filter)
|
||||
call reverse(self.integrated)
|
||||
endfunction
|
||||
"}}}
|
||||
|
||||
" filters
|
||||
function! s:has_filetype(candidate) abort "{{{
|
||||
if !has_key(a:candidate, 'filetype')
|
||||
return 1
|
||||
else
|
||||
let filetypes = split(&filetype, '\.')
|
||||
if filetypes == []
|
||||
let filter = 'v:val ==# "all" || v:val ==# ""'
|
||||
else
|
||||
let filter = 'v:val ==# "all" || index(filetypes, v:val) > -1'
|
||||
endif
|
||||
return filter(copy(a:candidate['filetype']), filter) != []
|
||||
endif
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:has_kind(candidate, kind) abort "{{{
|
||||
if !has_key(a:candidate, 'kind')
|
||||
return 1
|
||||
else
|
||||
let filter = 'v:val ==# a:kind || v:val ==# "textobj" || v:val ==# "all"'
|
||||
return filter(copy(a:candidate['kind']), filter) != []
|
||||
endif
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:has_mode(candidate, mode) abort "{{{
|
||||
if !has_key(a:candidate, 'mode')
|
||||
return 1
|
||||
else
|
||||
return stridx(join(a:candidate['mode'], ''), a:mode) > -1
|
||||
endif
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:has_action(candidate) abort "{{{
|
||||
if !has_key(a:candidate, 'action')
|
||||
return 1
|
||||
endif
|
||||
let filter = 'v:val ==# "delete" || v:val ==# "all"'
|
||||
return filter(copy(a:candidate['action']), filter) != []
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:expr_filter(candidate) abort "{{{
|
||||
if !has_key(a:candidate, 'expr_filter')
|
||||
return 1
|
||||
else
|
||||
for filter in a:candidate['expr_filter']
|
||||
if !eval(filter)
|
||||
return 0
|
||||
endif
|
||||
endfor
|
||||
return 1
|
||||
endif
|
||||
endfunction
|
||||
"}}}
|
||||
|
||||
function! s:is_duplicated_buns(item, ref, opt_regex, opt_expr) abort "{{{
|
||||
if has_key(a:item, 'buns')
|
||||
\ && type(a:ref['buns'][0]) == s:type_str
|
||||
\ && type(a:ref['buns'][1]) == s:type_str
|
||||
\ && type(a:item['buns'][0]) == s:type_str
|
||||
\ && type(a:item['buns'][1]) == s:type_str
|
||||
\ && a:ref['buns'][0] ==# a:item['buns'][0]
|
||||
\ && a:ref['buns'][1] ==# a:item['buns'][1]
|
||||
let regex_r = get(a:ref, 'regex', a:opt_regex)
|
||||
let regex_i = get(a:item, 'regex', a:opt_regex)
|
||||
let expr_r = get(a:ref, 'expr', a:opt_expr)
|
||||
let expr_i = get(a:item, 'expr', a:opt_expr)
|
||||
|
||||
let expr_r = expr_r ? 1 : 0
|
||||
let expr_i = expr_i ? 1 : 0
|
||||
|
||||
if regex_r == regex_i && expr_r == expr_i
|
||||
return 1
|
||||
endif
|
||||
endif
|
||||
return 0
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:is_duplicated_external(item, ref, opt_noremap) abort "{{{
|
||||
if has_key(a:item, 'external')
|
||||
\ && a:ref['external'][0] ==# a:item['external'][0]
|
||||
\ && a:ref['external'][1] ==# a:item['external'][1]
|
||||
let noremap_r = get(a:ref, 'noremap', a:opt_noremap)
|
||||
let noremap_i = get(a:item, 'noremap', a:opt_noremap)
|
||||
|
||||
if noremap_r == noremap_i
|
||||
return 1
|
||||
endif
|
||||
endif
|
||||
|
||||
return 0
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:is_input_matched(candidate, input, opt, flag) abort "{{{
|
||||
let has_buns = has_key(a:candidate, 'buns')
|
||||
let has_ext = has_key(a:candidate, 'external') && has_key(a:candidate, 'input')
|
||||
if !(has_buns || has_ext)
|
||||
return 0
|
||||
elseif !a:flag && a:input ==# ''
|
||||
return 1
|
||||
endif
|
||||
|
||||
let candidate = deepcopy(a:candidate)
|
||||
|
||||
if has_buns
|
||||
call a:opt.update('recipe', candidate)
|
||||
|
||||
" 'input' is necessary for 'expr' or 'regex' buns
|
||||
if (a:opt.of('expr') || a:opt.of('regex')) && !has_key(candidate, 'input')
|
||||
return 0
|
||||
endif
|
||||
|
||||
let inputs = copy(get(candidate, 'input', candidate['buns']))
|
||||
elseif has_ext
|
||||
" 'input' is necessary for 'external' textobjects assignment
|
||||
if !has_key(candidate, 'input')
|
||||
return 0
|
||||
endif
|
||||
|
||||
let inputs = copy(a:candidate['input'])
|
||||
endif
|
||||
|
||||
" If a:flag == 0, check forward match. Otherwise, check complete match.
|
||||
if a:flag
|
||||
return filter(inputs, 'v:val ==# a:input') != []
|
||||
else
|
||||
let idx = strlen(a:input) - 1
|
||||
return filter(inputs, 'v:val[: idx] ==# a:input') != []
|
||||
endif
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:is_input_fallback(input) abort "{{{
|
||||
if a:input ==# "\<Esc>" || a:input ==# '' || a:input =~# '^[\x80]'
|
||||
return s:FALSE
|
||||
endif
|
||||
let input_fallback = get(g:, 'sandwich#input_fallback', s:TRUE)
|
||||
if !input_fallback
|
||||
return s:FALSE
|
||||
endif
|
||||
return s:TRUE
|
||||
endfunction "}}}
|
||||
|
||||
" vim:set foldmethod=marker:
|
||||
" vim:set commentstring="%s:
|
||||
" vim:set ts=2 sts=2 sw=2:
|
||||
@ -0,0 +1,344 @@
|
||||
" sandwich object - manage recipe and positions of a sandwiched text
|
||||
|
||||
" variables "{{{
|
||||
" null valiables
|
||||
let s:null_coord = [0, 0]
|
||||
let s:null_4coord = {
|
||||
\ 'head': copy(s:null_coord),
|
||||
\ 'tail': copy(s:null_coord),
|
||||
\ 'inner_head': copy(s:null_coord),
|
||||
\ 'inner_tail': copy(s:null_coord),
|
||||
\ }
|
||||
|
||||
" types
|
||||
let s:type_num = type(0)
|
||||
let s:type_str = type('')
|
||||
let s:type_list = type([])
|
||||
let s:type_fref = type(function('tr'))
|
||||
|
||||
" common functions
|
||||
let s:lib = textobj#sandwich#lib#import()
|
||||
"}}}
|
||||
|
||||
function! textobj#sandwich#sandwich#new(recipe, opt) abort "{{{
|
||||
let sandwich = deepcopy(s:sandwich)
|
||||
let sandwich.opt = copy(a:opt)
|
||||
let sandwich.opt.recipe = {}
|
||||
call sandwich.opt.update('recipe', a:recipe)
|
||||
|
||||
if has_key(a:recipe, 'buns')
|
||||
let sandwich.searchby = 'buns'
|
||||
unlet sandwich.dough
|
||||
let sandwich.dough = deepcopy(a:recipe.buns)
|
||||
elseif has_key(a:recipe, 'external')
|
||||
let sandwich.searchby = 'external'
|
||||
let sandwich.external = deepcopy(a:recipe.external)
|
||||
else
|
||||
return {}
|
||||
endif
|
||||
let sandwich.recipe = a:recipe
|
||||
return sandwich
|
||||
endfunction
|
||||
"}}}
|
||||
|
||||
" coord object"{{{
|
||||
let s:coord = deepcopy(s:null_4coord)
|
||||
function! s:coord.initialize() dict abort "{{{
|
||||
let self.head = deepcopy(s:null_coord)
|
||||
let self.tail = deepcopy(s:null_coord)
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:coord.get_inner(buns, skip_break) dict abort "{{{
|
||||
call cursor(self.head)
|
||||
call search(a:buns[0], 'ce', self.tail[0])
|
||||
normal! l
|
||||
if a:skip_break && col('.') == col([line('.'), '$'])
|
||||
let self.inner_head = searchpos('\_S', '', self.tail[0])
|
||||
else
|
||||
let self.inner_head = getpos('.')[1:2]
|
||||
endif
|
||||
|
||||
call cursor(self.tail)
|
||||
call search(a:buns[1], 'bc', self.head[0])
|
||||
if a:skip_break && (col('.') < 2 || getline(line('.'))[: col('.')-2] =~# '^\s*$')
|
||||
let self.inner_tail = searchpos('\_S', 'be', self.head[0])
|
||||
else
|
||||
if getpos('.')[2] == 1
|
||||
normal! hl
|
||||
else
|
||||
normal! h
|
||||
endif
|
||||
let self.inner_tail = getpos('.')[1:2]
|
||||
endif
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:coord.next() dict abort "{{{
|
||||
call cursor(self.head)
|
||||
normal! h
|
||||
let self.head = getpos('.')[1:2]
|
||||
endfunction
|
||||
"}}}
|
||||
"}}}
|
||||
" range object"{{{
|
||||
let s:range = {
|
||||
\ 'valid' : 0,
|
||||
\ 'top' : 0,
|
||||
\ 'bottom' : 0,
|
||||
\ 'toplim' : 0,
|
||||
\ 'botlim' : 0,
|
||||
\ 'stride' : &lines,
|
||||
\ 'count' : 1,
|
||||
\ }
|
||||
function! s:range.initialize(cursor, expand_range) dict abort "{{{
|
||||
let filehead = 1
|
||||
let fileend = line('$')
|
||||
let self.valid = 1
|
||||
let self.top = a:cursor[0]
|
||||
let self.bottom = a:cursor[0]
|
||||
if a:expand_range >= 0
|
||||
let self.toplim = max([filehead, a:cursor[0] - a:expand_range])
|
||||
let self.botlim = min([a:cursor[0] + a:expand_range, fileend])
|
||||
else
|
||||
let self.toplim = filehead
|
||||
let self.botlim = fileend
|
||||
endif
|
||||
let self.count = 1
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:range.next() dict abort "{{{
|
||||
if (self.top == 1/0 && self.bottom < 1)
|
||||
\ || (self.top <= self.toplim && self.bottom >= self.botlim)
|
||||
let self.top = 1/0
|
||||
let self.bottom = 0
|
||||
let self.valid = 0
|
||||
else
|
||||
if self.top > self.toplim
|
||||
let self.top = self.top - self.stride
|
||||
let self.top = self.top < self.toplim ? self.toplim : self.top
|
||||
endif
|
||||
if self.bottom < self.botlim
|
||||
let self.bottom = self.bottom + self.stride
|
||||
let self.bottom = self.bottom > self.botlim ? self.botlim : self.bottom
|
||||
endif
|
||||
endif
|
||||
endfunction
|
||||
"}}}
|
||||
"}}}
|
||||
|
||||
" s:sandwich "{{{
|
||||
let s:sandwich = {
|
||||
\ 'buns' : [],
|
||||
\ 'dough' : [],
|
||||
\ 'external' : [],
|
||||
\ 'searchby' : '',
|
||||
\ 'recipe' : {},
|
||||
\ 'cursor' : copy(s:null_coord),
|
||||
\ 'coord' : deepcopy(s:coord),
|
||||
\ 'range' : deepcopy(s:range),
|
||||
\ 'visualmode': '',
|
||||
\ 'syntax_on' : 1,
|
||||
\ 'syntax' : [],
|
||||
\ 'opt' : {},
|
||||
\ 'escaped' : 0,
|
||||
\ 'not_escaped': [],
|
||||
\ }
|
||||
"}}}
|
||||
function! s:sandwich.initialize(cursor, is_syntax_on) dict abort "{{{
|
||||
let self.syntax_on = a:is_syntax_on
|
||||
let self.cursor = a:cursor
|
||||
call self.coord.initialize()
|
||||
call self.range.initialize(a:cursor, self.opt.of('expand_range'))
|
||||
return self
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:sandwich.bake_buns(state, clock) dict abort "{{{
|
||||
let opt_listexpr = self.opt.of('listexpr')
|
||||
let opt_expr = self.opt.of('expr')
|
||||
let opt_regex = self.opt.of('regex')
|
||||
|
||||
call a:clock.pause()
|
||||
if a:state
|
||||
if opt_listexpr
|
||||
let self.buns = eval(self.dough)
|
||||
elseif opt_expr
|
||||
let self.buns = s:evalexpr(self.dough)
|
||||
else
|
||||
let self.buns = self.dough
|
||||
endif
|
||||
else
|
||||
if opt_listexpr >= 2
|
||||
let self.buns = eval(self.dough)
|
||||
let self.escaped = 0
|
||||
elseif opt_expr >= 2
|
||||
let self.buns = s:evalexpr(self.dough)
|
||||
let self.escaped = 0
|
||||
endif
|
||||
endif
|
||||
call a:clock.start()
|
||||
|
||||
if !s:valid_buns(self.buns)
|
||||
return ['', '']
|
||||
endif
|
||||
|
||||
if !self.escaped
|
||||
let self.not_escaped = copy(self.buns)
|
||||
if !opt_regex
|
||||
call map(self.buns, 's:lib.escape(v:val)')
|
||||
let self.escaped = 1
|
||||
endif
|
||||
endif
|
||||
return self.buns
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:sandwich.searchpair_head(stimeoutlen) dict abort "{{{
|
||||
let tailpos = searchpos(self.buns[1], 'cn', self.range.bottom, a:stimeoutlen)
|
||||
let flag = tailpos == getpos('.')[1:2] ? 'b' : 'bc'
|
||||
let stopline = self.range.top
|
||||
return searchpairpos(self.buns[0], '', self.buns[1], flag, 'self.skip(1)', stopline, a:stimeoutlen)
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:sandwich.searchpair_tail(stimeoutlen) dict abort "{{{
|
||||
let stopline = self.range.bottom
|
||||
return searchpairpos(self.buns[0], '', self.buns[1], '', 'self.skip(0)', stopline, a:stimeoutlen)
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:sandwich.search_head(flag, stimeoutlen) dict abort "{{{
|
||||
return self._searchpos(self.buns[0], a:flag, 1, self.range.top, a:stimeoutlen)
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:sandwich.search_tail(flag, stimeoutlen) dict abort "{{{
|
||||
return self._searchpos(self.buns[1], a:flag, 0, self.range.bottom, a:stimeoutlen)
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:sandwich._searchpos(pattern, flag, is_head, stopline, stimeoutlen) dict abort "{{{
|
||||
let coord = searchpos(a:pattern, a:flag, a:stopline, a:stimeoutlen)
|
||||
let flag = substitute(a:flag, '\m\Cc', '', 'g')
|
||||
while coord != s:null_coord && self.skip(a:is_head, coord)
|
||||
let coord = searchpos(a:pattern, flag, a:stopline, a:stimeoutlen)
|
||||
endwhile
|
||||
return coord
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:sandwich.skip(is_head, ...) dict abort "{{{
|
||||
" NOTE: for sandwich.searchpairpos()/sandwich.searchpos() functions.
|
||||
let opt = self.opt
|
||||
let checkpos = get(a:000, 0, getpos('.')[1:2])
|
||||
|
||||
if checkpos == s:null_coord
|
||||
return 1
|
||||
endif
|
||||
|
||||
" quoteescape option
|
||||
let skip_patterns = []
|
||||
if opt.of('quoteescape') && "eescape !=# ''
|
||||
for c in split("eescape, '\zs')
|
||||
let c = s:lib.escape(c)
|
||||
let pattern = printf('[^%s]%s\%%(%s%s\)*\zs\%%#', c, c, c, c)
|
||||
let skip_patterns += [pattern]
|
||||
endfor
|
||||
endif
|
||||
|
||||
" skip_regex option
|
||||
let skip_patterns += opt.of('skip_regex')
|
||||
let skip_patterns += a:is_head ? opt.of('skip_regex_head') : opt.of('skip_regex_tail')
|
||||
if skip_patterns != []
|
||||
call cursor(checkpos)
|
||||
for pattern in skip_patterns
|
||||
let skip = searchpos(pattern, 'cn', checkpos[0]) == checkpos
|
||||
if skip
|
||||
return 1
|
||||
endif
|
||||
endfor
|
||||
endif
|
||||
|
||||
" syntax, match_syntax option
|
||||
if self.syntax_on
|
||||
if a:is_head || !opt.of('match_syntax')
|
||||
let opt_syntax = opt.of('syntax')
|
||||
if opt_syntax != [] && !s:lib.is_included_syntax(checkpos, opt_syntax)
|
||||
return 1
|
||||
endif
|
||||
else
|
||||
if !s:lib.is_matched_syntax(checkpos, self.syntax)
|
||||
return 1
|
||||
endif
|
||||
endif
|
||||
endif
|
||||
|
||||
" skip_expr option
|
||||
for Expr in opt.of('skip_expr')
|
||||
if s:eval(Expr, a:is_head, s:lib.c2p(checkpos))
|
||||
return 1
|
||||
endif
|
||||
endfor
|
||||
|
||||
return 0
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:sandwich.check_syntax(coord) dict abort "{{{
|
||||
if !self.syntax_on || !self.opt.of('match_syntax')
|
||||
return
|
||||
endif
|
||||
|
||||
let synitem = s:lib.get_displaysyntax(a:coord)
|
||||
if synitem !=# ''
|
||||
let self.syntax = [synitem]
|
||||
endif
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:sandwich.export_recipe() dict abort "{{{
|
||||
" For the cooperation with operator-sandwich
|
||||
" NOTE: After visual selection by a user-defined textobject, v:operator is set as ':'
|
||||
" NOTE: 'synchro' option is not valid for visual mode, because there is no guarantee that g:operator#sandwich#object exists.
|
||||
if self.opt.of('synchro') && exists('g:operator#sandwich#object')
|
||||
\ && ((self.searchby ==# 'buns' && v:operator ==# 'g@') || (self.searchby ==# 'external' && v:operator =~# '\%(:\|g@\)'))
|
||||
\ && &operatorfunc =~# '^operator#sandwich#\%(delete\|replace\)'
|
||||
let recipe = self.recipe
|
||||
if self.searchby ==# 'buns'
|
||||
call extend(recipe, {'buns': self.not_escaped}, 'force')
|
||||
call extend(recipe, {'expr': 0}, 'force')
|
||||
call extend(recipe, {'listexpr': 0}, 'force')
|
||||
elseif self.searchby ==# 'external'
|
||||
let excursus = {
|
||||
\ 'count' : self.range.count,
|
||||
\ 'cursor': s:lib.c2p(self.cursor),
|
||||
\ 'coord' : self.coord,
|
||||
\ }
|
||||
call extend(recipe, {'excursus': excursus}, 'force')
|
||||
endif
|
||||
call extend(recipe, {'action': ['delete']}, 'force')
|
||||
else
|
||||
let recipe = {}
|
||||
endif
|
||||
return recipe
|
||||
endfunction
|
||||
"}}}
|
||||
|
||||
function! s:evalexpr(dough) abort "{{{
|
||||
let buns = []
|
||||
let buns += [eval(a:dough[0])]
|
||||
if buns[0] !=# ''
|
||||
let buns += [eval(a:dough[1])]
|
||||
endif
|
||||
return buns
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:valid_buns(buns) abort "{{{
|
||||
return type(a:buns) == s:type_list && len(a:buns) >= 2 && s:check_a_bun(a:buns[0]) && s:check_a_bun(a:buns[1])
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:check_a_bun(bun) abort "{{{
|
||||
let type_bun = type(a:bun)
|
||||
return (type_bun ==# s:type_str && a:bun !=# '') || type_bun ==# s:type_num
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:eval(expr, ...) abort "{{{
|
||||
return type(a:expr) == s:type_fref ? call(a:expr, a:000) : eval(a:expr)
|
||||
endfunction
|
||||
"}}}
|
||||
|
||||
|
||||
|
||||
" vim:set foldmethod=marker:
|
||||
" vim:set commentstring="%s:
|
||||
" vim:set ts=2 sts=2 sw=2:
|
||||
@ -0,0 +1,592 @@
|
||||
" textobj object - search & select a sandwiched text
|
||||
|
||||
" variables "{{{
|
||||
function! s:SID() abort
|
||||
return matchstr(expand('<sfile>'), '<SNR>\zs\d\+\ze_SID$')
|
||||
endfunction
|
||||
let s:SNR = printf("\<SNR>%s_", s:SID())
|
||||
delfunction s:SID
|
||||
|
||||
nnoremap <SID>(v) v
|
||||
nnoremap <SID>(V) V
|
||||
nnoremap <SID>(CTRL-v) <C-v>
|
||||
|
||||
let s:KEY_v = printf('%s(v)', s:SNR)
|
||||
let s:KEY_V = printf('%s(V)', s:SNR)
|
||||
let s:KEY_CTRL_v = printf('%s(CTRL-v)', s:SNR)
|
||||
|
||||
" null valiables
|
||||
let s:null_coord = [0, 0]
|
||||
|
||||
" common functions
|
||||
let s:lib = textobj#sandwich#lib#import()
|
||||
"}}}
|
||||
|
||||
function! textobj#sandwich#textobj#new(kind, a_or_i, mode, count, recipes, opt) abort "{{{
|
||||
let textobj = deepcopy(s:textobj)
|
||||
let textobj.kind = a:kind
|
||||
let textobj.a_or_i = a:a_or_i
|
||||
let textobj.mode = a:mode
|
||||
let textobj.count = a:count
|
||||
let textobj.recipes = a:recipes
|
||||
let textobj.opt = a:opt
|
||||
|
||||
" NOTE: The cursor position should be recorded in textobj#sandwich#auto()/textobj#sandwich#query().
|
||||
" It is impossible to get it in textobj#sandwich#select() if in visual mode.
|
||||
let textobj.cursor = getpos('.')[1:2]
|
||||
return textobj
|
||||
endfunction
|
||||
"}}}
|
||||
|
||||
" s:textobj "{{{
|
||||
let s:textobj = {
|
||||
\ 'state' : 1,
|
||||
\ 'kind' : '',
|
||||
\ 'a_or_i' : 'i',
|
||||
\ 'mode' : '',
|
||||
\ 'count' : 0,
|
||||
\ 'cursor' : copy(s:null_coord),
|
||||
\ 'view' : {},
|
||||
\ 'recipes': {
|
||||
\ 'arg' : [],
|
||||
\ 'arg_given' : 0,
|
||||
\ 'integrated': [],
|
||||
\ },
|
||||
\ 'visual': {
|
||||
\ 'mode': '',
|
||||
\ 'head': copy(s:null_coord),
|
||||
\ 'tail': copy(s:null_coord),
|
||||
\ },
|
||||
\ 'basket': [],
|
||||
\ 'opt' : {},
|
||||
\ 'done' : 0,
|
||||
\ 'clock' : sandwich#clock#new(),
|
||||
\ }
|
||||
"}}}
|
||||
function! s:textobj.initialize() dict abort "{{{
|
||||
let self.done = 0
|
||||
let self.count = !self.state && self.count != v:count1 ? v:count1 : self.count
|
||||
if self.state
|
||||
let self.basket = map(copy(self.recipes.integrated), 'textobj#sandwich#sandwich#new(v:val, self.opt)')
|
||||
call filter(self.basket, 'v:val != {}')
|
||||
else
|
||||
let self.cursor = getpos('.')[1:2]
|
||||
endif
|
||||
if self.mode ==# 'x'
|
||||
let self.visual.mode = visualmode() ==# "\<C-v>" ? "\<C-v>" : 'v'
|
||||
let self.visual.head = getpos("'<")[1:2]
|
||||
let self.visual.tail = getpos("'>")[1:2]
|
||||
else
|
||||
let self.visual.mode = 'v'
|
||||
endif
|
||||
let is_syntax_on = exists('g:syntax_on') || exists('g:syntax_manual')
|
||||
call map(self.basket, 'v:val.initialize(self.cursor, is_syntax_on)')
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:textobj.list(stimeoutlen) dict abort "{{{
|
||||
let clock = self.clock
|
||||
|
||||
" gather candidates
|
||||
let candidates = []
|
||||
call clock.stop()
|
||||
call clock.start()
|
||||
while filter(copy(self.basket), 'v:val.range.valid') != []
|
||||
for sandwich in self.basket
|
||||
let candidates += self.search(sandwich, a:stimeoutlen)
|
||||
endfor
|
||||
call s:uniq_candidates(candidates, self.a_or_i)
|
||||
if len(candidates) >= self.count
|
||||
break
|
||||
endif
|
||||
|
||||
" time out
|
||||
if clock.started && a:stimeoutlen > 0
|
||||
let elapsed = clock.elapsed()
|
||||
if elapsed > a:stimeoutlen
|
||||
echo 'textobj-sandwich: Timed out.'
|
||||
break
|
||||
endif
|
||||
endif
|
||||
endwhile
|
||||
call clock.stop()
|
||||
return candidates
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:textobj.search(sandwich, stimeoutlen) dict abort "{{{
|
||||
if a:sandwich.searchby ==# 'buns'
|
||||
if a:sandwich.opt.of('nesting')
|
||||
let candidates = self._search_with_nest(a:sandwich, a:stimeoutlen)
|
||||
else
|
||||
let candidates = self._search_without_nest(a:sandwich, a:stimeoutlen)
|
||||
endif
|
||||
elseif a:sandwich.searchby ==# 'external'
|
||||
let candidates = self._get_region(a:sandwich, a:stimeoutlen)
|
||||
else
|
||||
let candidates = []
|
||||
endif
|
||||
return candidates
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:textobj._search_with_nest(sandwich, stimeoutlen) dict abort "{{{
|
||||
let buns = a:sandwich.bake_buns(self.state, self.clock)
|
||||
let range = a:sandwich.range
|
||||
let coord = a:sandwich.coord
|
||||
let opt = a:sandwich.opt
|
||||
let candidates = []
|
||||
|
||||
if buns[0] ==# '' || buns[1] ==# ''
|
||||
let range.valid = 0
|
||||
endif
|
||||
if !range.valid | return candidates | endif
|
||||
|
||||
" check whether the cursor is on the buns[1] or not
|
||||
call cursor(self.cursor)
|
||||
let _head = searchpos(buns[1], 'bcW', range.top, a:stimeoutlen)
|
||||
let _tail = searchpos(buns[1], 'cenW', range.bottom, a:stimeoutlen)
|
||||
if _head != s:null_coord && _tail != s:null_coord && s:is_in_between(self.cursor, _head, _tail)
|
||||
call cursor(_head)
|
||||
else
|
||||
call cursor(self.cursor)
|
||||
endif
|
||||
|
||||
while 1
|
||||
" search head
|
||||
let head = a:sandwich.searchpair_head(a:stimeoutlen)
|
||||
if head == s:null_coord | break | endif
|
||||
let coord.head = head
|
||||
call a:sandwich.check_syntax(head)
|
||||
|
||||
" search tail
|
||||
let tail = a:sandwich.searchpair_tail(a:stimeoutlen)
|
||||
if tail == s:null_coord | break | endif
|
||||
let tail = searchpos(buns[1], 'ce', range.bottom, a:stimeoutlen)
|
||||
if tail == s:null_coord | break | endif
|
||||
let coord.tail = tail
|
||||
|
||||
" add to candidates
|
||||
call coord.get_inner(buns, opt.of('skip_break'))
|
||||
if self.is_valid_candidate(a:sandwich)
|
||||
let candidate = deepcopy(a:sandwich)
|
||||
let candidate.visualmode = self.visual.mode
|
||||
let candidates += [candidate]
|
||||
endif
|
||||
|
||||
if coord.head == [1, 1]
|
||||
" finish!
|
||||
let range.valid = 0
|
||||
break
|
||||
else
|
||||
call coord.next()
|
||||
endif
|
||||
endwhile
|
||||
call range.next()
|
||||
return candidates
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:textobj._search_without_nest(sandwich, stimeoutlen) dict abort "{{{
|
||||
let buns = a:sandwich.bake_buns(self.state, self.clock)
|
||||
let range = a:sandwich.range
|
||||
let coord = a:sandwich.coord
|
||||
let opt = a:sandwich.opt
|
||||
let candidates = []
|
||||
|
||||
if buns[0] ==# '' || buns[1] ==# ''
|
||||
let range.valid = 0
|
||||
endif
|
||||
if !range.valid | return candidates | endif
|
||||
|
||||
" search nearest head
|
||||
call cursor(self.cursor)
|
||||
let head = a:sandwich.search_head('bc', a:stimeoutlen)
|
||||
if head == s:null_coord
|
||||
call range.next()
|
||||
return candidates
|
||||
endif
|
||||
call a:sandwich.check_syntax(head)
|
||||
let _tail = searchpos(buns[0], 'ce', range.bottom, a:stimeoutlen)
|
||||
|
||||
" search nearest tail
|
||||
call cursor(self.cursor)
|
||||
let tail = a:sandwich.search_tail('ce', a:stimeoutlen)
|
||||
if tail == s:null_coord
|
||||
call range.next()
|
||||
return candidates
|
||||
endif
|
||||
|
||||
" If the cursor is right on a bun
|
||||
if tail == _tail
|
||||
" check whether it is head or tail
|
||||
let odd = 1
|
||||
call cursor([range.top, 1])
|
||||
let pos = searchpos(buns[0], 'c', range.top, a:stimeoutlen)
|
||||
while pos != head && pos != s:null_coord
|
||||
let odd = !odd
|
||||
let pos = searchpos(buns[0], '', range.top, a:stimeoutlen)
|
||||
endwhile
|
||||
if pos == s:null_coord | return candidates | endif
|
||||
|
||||
if odd
|
||||
" pos is head
|
||||
let head = pos
|
||||
call a:sandwich.check_syntax(head)
|
||||
|
||||
" search tail
|
||||
call search(buns[0], 'ce', range.bottom, a:stimeoutlen)
|
||||
let tail = a:sandwich.search_tail('e', a:stimeoutlen)
|
||||
if tail == s:null_coord
|
||||
call range.next()
|
||||
return candidates
|
||||
endif
|
||||
else
|
||||
" pos is tail
|
||||
call cursor(pos)
|
||||
let tail = a:sandwich.search_tail('ce', a:stimeoutlen)
|
||||
call a:sandwich.check_syntax(tail)
|
||||
|
||||
" search head
|
||||
call search(buns[1], 'bc', range.top, a:stimeoutlen)
|
||||
let head = a:sandwich.search_head('b', a:stimeoutlen)
|
||||
if head == s:null_coord
|
||||
call range.next()
|
||||
return candidates
|
||||
endif
|
||||
endif
|
||||
endif
|
||||
let coord.head = head
|
||||
let coord.tail = tail
|
||||
call coord.get_inner(buns, a:sandwich.opt.of('skip_break'))
|
||||
|
||||
if self.is_valid_candidate(a:sandwich)
|
||||
let candidate = deepcopy(a:sandwich)
|
||||
let candidate.visualmode = self.visual.mode
|
||||
let candidates += [candidate]
|
||||
endif
|
||||
let range.valid = 0
|
||||
return candidates
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:textobj._get_region(sandwich, stimeoutlen) dict abort "{{{
|
||||
" NOTE: Because of the restriction of vim, if a operator to get the assigned
|
||||
" region is employed for 'external' user-defined textobjects, it makes
|
||||
" impossible to repeat by dot command. Thus, 'external' is checked by
|
||||
" using visual selection xmap in any case.
|
||||
let range = a:sandwich.range
|
||||
let coord = a:sandwich.coord
|
||||
let opt = a:sandwich.opt
|
||||
let candidates = []
|
||||
|
||||
if !range.valid | return candidates | endif
|
||||
|
||||
if opt.of('noremap')
|
||||
let cmd = 'normal!'
|
||||
let v = self.visual.mode
|
||||
else
|
||||
let cmd = 'normal'
|
||||
let v = self.visual.mode ==# 'v' ? s:KEY_v :
|
||||
\ self.visual.mode ==# 'V' ? s:KEY_V :
|
||||
\ s:KEY_CTRL_v
|
||||
endif
|
||||
|
||||
if self.mode ==# 'x'
|
||||
let initpos = [self.visual.head, self.visual.tail]
|
||||
else
|
||||
let initpos = [self.cursor, self.cursor]
|
||||
endif
|
||||
|
||||
let selection = &selection
|
||||
set selection=inclusive
|
||||
try
|
||||
while 1
|
||||
let [prev_head, prev_tail] = [coord.head, coord.tail]
|
||||
let [prev_inner_head, prev_inner_tail] = [coord.inner_head, coord.inner_tail]
|
||||
" get outer positions
|
||||
let [head, tail, visualmode_a] = s:get_textobj_region(initpos, cmd, v, range.count, a:sandwich.external[1])
|
||||
|
||||
" get inner positions
|
||||
if head != s:null_coord && tail != s:null_coord
|
||||
let [inner_head, inner_tail, visualmode_i] = s:get_textobj_region(initpos, cmd, v, range.count, a:sandwich.external[0])
|
||||
else
|
||||
let [inner_head, inner_tail, visualmode_i] = [copy(s:null_coord), copy(s:null_coord), 'v']
|
||||
endif
|
||||
|
||||
if (self.a_or_i ==# 'i' && s:is_valid_region(inner_head, inner_tail, prev_inner_head, prev_inner_tail, range.count))
|
||||
\ || (self.a_or_i ==# 'a' && s:is_valid_region(head, tail, prev_head, prev_tail, range.count))
|
||||
if head[0] >= range.top && tail[0] <= range.bottom
|
||||
let coord.head = head
|
||||
let coord.tail = tail
|
||||
let coord.inner_head = inner_head
|
||||
let coord.inner_tail = inner_tail
|
||||
|
||||
if self.is_valid_candidate(a:sandwich)
|
||||
let candidate = deepcopy(a:sandwich)
|
||||
let candidate.visualmode = self.a_or_i ==# 'a' ? visualmode_a : visualmode_i
|
||||
let candidates += [candidate]
|
||||
endif
|
||||
else
|
||||
call range.next()
|
||||
break
|
||||
endif
|
||||
else
|
||||
let range.valid = 0
|
||||
break
|
||||
endif
|
||||
|
||||
let range.count += 1
|
||||
endwhile
|
||||
finally
|
||||
" restore visualmode
|
||||
execute 'normal! ' . self.visual.mode
|
||||
execute "normal! \<Esc>"
|
||||
call cursor(self.cursor)
|
||||
" restore marks
|
||||
call setpos("'<", s:lib.c2p(self.visual.head))
|
||||
call setpos("'>", s:lib.c2p(self.visual.tail))
|
||||
" restore options
|
||||
let &selection = selection
|
||||
endtry
|
||||
return candidates
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:textobj.is_valid_candidate(sandwich) dict abort "{{{
|
||||
let coord = a:sandwich.coord
|
||||
if !s:is_in_between(self.cursor, coord.head, coord.tail)
|
||||
return 0
|
||||
endif
|
||||
|
||||
if self.a_or_i ==# 'i'
|
||||
let [head, tail] = [coord.inner_head, coord.inner_tail]
|
||||
else
|
||||
let [head, tail] = [coord.head, coord.tail]
|
||||
endif
|
||||
if head == s:null_coord || tail == s:null_coord || s:is_ahead(head, tail)
|
||||
return 0
|
||||
endif
|
||||
|
||||
" specific condition in visual mode
|
||||
if self.mode !=# 'x'
|
||||
let visual_mode_affair = 1
|
||||
else
|
||||
let visual_mode_affair = s:visual_mode_affair(
|
||||
\ head, tail, self.a_or_i, self.cursor, self.visual)
|
||||
endif
|
||||
if !visual_mode_affair
|
||||
return 0
|
||||
endif
|
||||
|
||||
" specific condition for the option 'matched_syntax' and 'inner_syntax'
|
||||
let opt_syntax_affair = s:opt_syntax_affair(a:sandwich)
|
||||
if !opt_syntax_affair
|
||||
return 0
|
||||
endif
|
||||
|
||||
return 1
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:textobj.elect(candidates) dict abort "{{{
|
||||
let elected = {}
|
||||
if len(a:candidates) >= self.count
|
||||
" election
|
||||
let cursor = self.cursor
|
||||
let map_rule = 'extend(v:val, {"len": s:representative_length(v:val.coord, cursor)})'
|
||||
call map(a:candidates, map_rule)
|
||||
call s:lib.sort(a:candidates, function('s:compare_buf_length'), self.count)
|
||||
let elected = a:candidates[self.count - 1]
|
||||
endif
|
||||
return elected
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:textobj.select(sandwich) dict abort "{{{
|
||||
if a:sandwich == {}
|
||||
if self.mode ==# 'x'
|
||||
normal! gv
|
||||
endif
|
||||
let self.done = 1
|
||||
return
|
||||
endif
|
||||
|
||||
let head = self.a_or_i ==# 'i' ? a:sandwich.coord.inner_head : a:sandwich.coord.head
|
||||
let tail = self.a_or_i ==# 'i' ? a:sandwich.coord.inner_tail : a:sandwich.coord.tail
|
||||
if self.mode ==# 'x' && self.visual.mode ==# "\<C-v>"
|
||||
" trick for the blockwise visual mode
|
||||
if self.cursor[0] == self.visual.tail[0]
|
||||
let disp_coord = s:lib.get_displaycoord(head)
|
||||
let disp_coord[0] = self.visual.head[0]
|
||||
call s:lib.set_displaycoord(disp_coord)
|
||||
let head = getpos('.')[1:2]
|
||||
elseif self.cursor[0] == self.visual.head[0]
|
||||
let disp_coord = s:lib.get_displaycoord(tail)
|
||||
let disp_coord[0] = self.visual.tail[0]
|
||||
call s:lib.set_displaycoord(disp_coord)
|
||||
let tail = getpos('.')[1:2]
|
||||
endif
|
||||
endif
|
||||
|
||||
execute 'normal! ' . a:sandwich.visualmode
|
||||
call cursor(head)
|
||||
normal! o
|
||||
call cursor(tail)
|
||||
|
||||
" counter measure for the 'selection' option being 'exclusive'
|
||||
if &selection ==# 'exclusive'
|
||||
normal! l
|
||||
endif
|
||||
|
||||
call operator#sandwich#synchronize(self.kind, a:sandwich.export_recipe())
|
||||
let self.done = 1
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:textobj.finalize(mark_latestjump) dict abort "{{{
|
||||
if self.done && a:mark_latestjump
|
||||
call setpos("''", s:lib.c2p(self.cursor))
|
||||
endif
|
||||
|
||||
" flash echoing
|
||||
if !self.state
|
||||
echo ''
|
||||
endif
|
||||
|
||||
let self.state = 0
|
||||
endfunction
|
||||
"}}}
|
||||
|
||||
function! s:is_valid_region(head, tail, prev_head, prev_tail, count) abort "{{{
|
||||
return a:head != s:null_coord && a:tail != s:null_coord && (a:count == 1 || s:is_ahead(a:prev_head, a:head) || s:is_ahead(a:tail, a:prev_tail))
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:get_textobj_region(initpos, cmd, visualmode, count, key_seq) abort "{{{
|
||||
call cursor(a:initpos[0])
|
||||
execute printf('silent! %s %s', a:cmd, a:visualmode)
|
||||
call cursor(a:initpos[1])
|
||||
execute printf('silent! %s %d%s', a:cmd, a:count, a:key_seq)
|
||||
if mode() ==? 'v' || mode() ==# "\<C-v>"
|
||||
execute "normal! \<Esc>"
|
||||
else
|
||||
return [copy(s:null_coord), copy(s:null_coord), a:visualmode]
|
||||
endif
|
||||
let visualmode = visualmode()
|
||||
let [head, tail] = [getpos("'<")[1:2], getpos("'>")[1:2]]
|
||||
if head == a:initpos[0] && tail == a:initpos[1]
|
||||
let [head, tail] = [copy(s:null_coord), copy(s:null_coord)]
|
||||
elseif visualmode ==# 'V'
|
||||
let tail[2] = col([tail[1], '$'])
|
||||
endif
|
||||
return [head, tail, visualmode]
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:get_buf_length(start, end) abort "{{{
|
||||
if a:start[0] == a:end[0]
|
||||
let len = a:end[1] - a:start[1] + 1
|
||||
else
|
||||
let len = (line2byte(a:end[0]) + a:end[1]) - (line2byte(a:start[0]) + a:start[1]) + 1
|
||||
endif
|
||||
return len
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:uniq_candidates(candidates, a_or_i) abort "{{{
|
||||
let i = 0
|
||||
if a:a_or_i ==# 'i'
|
||||
let filter = 'v:val.coord.inner_head != candidate.coord.inner_head || v:val.coord.inner_tail != candidate.coord.inner_tail'
|
||||
else
|
||||
let filter = 'v:val.coord.head == candidate.coord.head || v:val.coord.tail == candidate.coord.tail'
|
||||
endif
|
||||
while i+1 < len(a:candidates)
|
||||
let candidate = a:candidates[i]
|
||||
call filter(a:candidates[i+1 :], filter)
|
||||
let i += 1
|
||||
endwhile
|
||||
return a:candidates
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:visual_mode_affair(head, tail, a_or_i, cursor, visual) abort "{{{
|
||||
" a:visual.mode ==# 'V' never comes.
|
||||
if a:visual.mode ==# 'v'
|
||||
" character-wise
|
||||
if a:a_or_i ==# 'i'
|
||||
let visual_mode_affair = s:is_ahead(a:visual.head, a:head)
|
||||
\ || s:is_ahead(a:tail, a:visual.tail)
|
||||
else
|
||||
let visual_mode_affair = (s:is_ahead(a:visual.head, a:head) && s:is_equal_or_ahead(a:tail, a:visual.tail))
|
||||
\ || (s:is_equal_or_ahead(a:visual.head, a:head) && s:is_ahead(a:tail, a:visual.tail))
|
||||
endif
|
||||
else
|
||||
" block-wise
|
||||
let orig_pos = getpos('.')
|
||||
let visual_head = s:lib.get_displaycoord(a:visual.head)
|
||||
let visual_tail = s:lib.get_displaycoord(a:visual.tail)
|
||||
call s:lib.set_displaycoord([a:cursor[0], visual_head[1]])
|
||||
let thr_head = getpos('.')
|
||||
call s:lib.set_displaycoord([a:cursor[0], visual_tail[1]])
|
||||
let thr_tail = getpos('.')
|
||||
let visual_mode_affair = s:is_ahead(thr_head, a:head)
|
||||
\ || s:is_ahead(a:tail, thr_tail)
|
||||
call setpos('.', orig_pos)
|
||||
endif
|
||||
return visual_mode_affair
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:opt_syntax_affair(sandwich) abort "{{{
|
||||
if !a:sandwich.syntax_on
|
||||
return 1
|
||||
endif
|
||||
|
||||
let coord = a:sandwich.coord
|
||||
let opt = a:sandwich.opt
|
||||
if opt.of('match_syntax') == 2
|
||||
let opt_syntax_affair = s:lib.is_included_syntax(coord.inner_head, a:sandwich.syntax)
|
||||
\ && s:lib.is_included_syntax(coord.inner_tail, a:sandwich.syntax)
|
||||
elseif opt.of('match_syntax') == 3
|
||||
" check inner syntax independently
|
||||
if opt.of('inner_syntax') == []
|
||||
let syntax = [s:lib.get_displaysyntax(coord.inner_head)]
|
||||
let opt_syntax_affair = s:lib.is_included_syntax(coord.inner_tail, syntax)
|
||||
else
|
||||
if s:lib.is_included_syntax(coord.inner_head, opt.of('inner_syntax'))
|
||||
let syntax = [s:lib.get_displaysyntax(coord.inner_head)]
|
||||
let opt_syntax_affair = s:lib.is_included_syntax(coord.inner_tail, syntax)
|
||||
else
|
||||
let opt_syntax_affair = 0
|
||||
endif
|
||||
endif
|
||||
else
|
||||
if opt.of('inner_syntax') == []
|
||||
let opt_syntax_affair = 1
|
||||
else
|
||||
let opt_syntax_affair = s:lib.is_included_syntax(coord.inner_head, opt.of('inner_syntax'))
|
||||
\ && s:lib.is_included_syntax(coord.inner_tail, opt.of('inner_syntax'))
|
||||
endif
|
||||
endif
|
||||
return opt_syntax_affair
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:is_in_between(coord, head, tail) abort "{{{
|
||||
return (a:coord != s:null_coord) && (a:head != s:null_coord) && (a:tail != s:null_coord)
|
||||
\ && ((a:coord[0] > a:head[0]) || ((a:coord[0] == a:head[0]) && (a:coord[1] >= a:head[1])))
|
||||
\ && ((a:coord[0] < a:tail[0]) || ((a:coord[0] == a:tail[0]) && (a:coord[1] <= a:tail[1])))
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:is_ahead(coord1, coord2) abort "{{{
|
||||
return a:coord1[0] > a:coord2[0] || (a:coord1[0] == a:coord2[0] && a:coord1[1] > a:coord2[1])
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:is_equal_or_ahead(coord1, coord2) abort "{{{
|
||||
return a:coord1[0] > a:coord2[0] || (a:coord1[0] == a:coord2[0] && a:coord1[1] >= a:coord2[1])
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:representative_length(coord, cursor) abort "{{{
|
||||
let inner_head = a:coord.inner_head
|
||||
let inner_tail = a:coord.inner_tail
|
||||
if s:is_in_between(a:cursor, inner_head, inner_tail)
|
||||
return s:get_buf_length(inner_head, inner_tail)
|
||||
else
|
||||
return s:get_buf_length(a:coord.head, a:coord.tail)
|
||||
endif
|
||||
endfunction "}}}
|
||||
function! s:compare_buf_length(i1, i2) abort "{{{
|
||||
return a:i1.len - a:i2.len
|
||||
endfunction
|
||||
"}}}
|
||||
|
||||
|
||||
" vim:set foldmethod=marker:
|
||||
" vim:set commentstring="%s:
|
||||
" vim:set ts=2 sts=2 sw=2:
|
||||
Reference in New Issue
Block a user