1

Regenerate nvim config

This commit is contained in:
2024-06-02 03:29:20 +02:00
parent 75eea0c030
commit ef2e28883d
5576 changed files with 604886 additions and 503 deletions

View File

@ -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:

View File

@ -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:

View File

@ -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:

View File

@ -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') && &quoteescape !=# ''
for c in split(&quoteescape, '\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:

View File

@ -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: