Regenerate nvim config
This commit is contained in:
@ -0,0 +1,607 @@
|
||||
" operator-sandwich: wrap by buns!
|
||||
" TODO Give API to get information from operator object.
|
||||
" It would be helpful for users in use of 'expr_filter' and 'command' option.
|
||||
" TODO Add 'at' option
|
||||
|
||||
" variables "{{{
|
||||
let s:constants = function('sandwich#constants#get')
|
||||
|
||||
" patchs
|
||||
if v:version > 704 || (v:version == 704 && has('patch237'))
|
||||
let s:has_patch_7_4_392 = has('patch-7.4.392')
|
||||
else
|
||||
let s:has_patch_7_4_392 = v:version == 704 && has('patch392')
|
||||
endif
|
||||
|
||||
" Others
|
||||
" NOTE: This would be updated in each operator functions (operator#sandwich#{add/delete/replce})
|
||||
let s:is_in_cmdline_window = 0
|
||||
" Current set operator
|
||||
let s:operator = ''
|
||||
"}}}
|
||||
" highlight {{{
|
||||
function! s:default_highlight() abort
|
||||
highlight default link OperatorSandwichBuns IncSearch
|
||||
highlight default link OperatorSandwichAdd DiffAdd
|
||||
highlight default link OperatorSandwichDelete DiffDelete
|
||||
|
||||
if hlexists('OperatorSandwichStuff')
|
||||
highlight default link OperatorSandwichChange OperatorSandwichStuff
|
||||
else
|
||||
" obsolete
|
||||
highlight default link OperatorSandwichChange DiffChange
|
||||
endif
|
||||
endfunction
|
||||
call s:default_highlight()
|
||||
|
||||
augroup sandwich-event-ColorScheme
|
||||
autocmd!
|
||||
autocmd ColorScheme * call s:default_highlight()
|
||||
augroup END
|
||||
"}}}
|
||||
|
||||
""" Public functions
|
||||
" Prerequisite
|
||||
function! operator#sandwich#prerequisite(kind, mode, ...) abort "{{{
|
||||
" make new operator object
|
||||
let g:operator#sandwich#object = operator#sandwich#operator#new()
|
||||
|
||||
" prerequisite
|
||||
let operator = g:operator#sandwich#object
|
||||
let operator.state = 1
|
||||
let operator.kind = a:kind
|
||||
let operator.count = a:mode ==# 'x' ? max([1, v:prevcount]) : v:count1
|
||||
let operator.mode = a:mode
|
||||
let operator.view = winsaveview()
|
||||
let operator.cursor.keepable = 1
|
||||
let operator.cursor.keep[0:3] = getpos('.')[0:3]
|
||||
let operator.opt = sandwich#opt#new(a:kind, {}, get(a:000, 0, {}))
|
||||
let operator.recipes.arg = get(a:000, 1, [])
|
||||
let operator.recipes.arg_given = a:0 > 1
|
||||
|
||||
let [operator.extended, operator.blockwidth] = s:blockwisevisual_info(a:mode)
|
||||
|
||||
let &l:operatorfunc = 'operator#sandwich#' . a:kind
|
||||
let s:operator = a:kind
|
||||
return
|
||||
endfunction
|
||||
"}}}
|
||||
function! operator#sandwich#keymap(kind, mode, ...) abort "{{{
|
||||
if a:0 == 0
|
||||
call operator#sandwich#prerequisite(a:kind, a:mode)
|
||||
elseif a:0 == 1
|
||||
call operator#sandwich#prerequisite(a:kind, a:mode, a:1)
|
||||
else
|
||||
call operator#sandwich#prerequisite(a:kind, a:mode, a:1, a:2)
|
||||
endif
|
||||
|
||||
let cmd = a:mode ==# 'x' ? 'gvg@' : 'g@'
|
||||
call feedkeys(cmd, 'inx')
|
||||
return
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:blockwisevisual_info(mode) abort "{{{
|
||||
if a:mode ==# 'x' && visualmode() ==# "\<C-v>"
|
||||
" The case for blockwise selections in visual mode
|
||||
" NOTE: 'extended' could be recorded safely only at here. Do not move.
|
||||
let registers = s:saveregisters()
|
||||
let lazyredraw = &lazyredraw
|
||||
set lazyredraw
|
||||
let view = winsaveview()
|
||||
try
|
||||
normal! gv
|
||||
let extended = winsaveview().curswant == s:constants('colmax')
|
||||
silent noautocmd normal! ""y
|
||||
let regtype = getregtype('"')
|
||||
finally
|
||||
call winrestview(view)
|
||||
let &lazyredraw = lazyredraw
|
||||
call s:restoreregisters(registers)
|
||||
endtry
|
||||
let blockwidth = str2nr(regtype[1:])
|
||||
else
|
||||
let extended = 0
|
||||
let blockwidth = 0
|
||||
endif
|
||||
return [extended, blockwidth]
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:saveregisters() abort "{{{
|
||||
let registers = {}
|
||||
let registers['0'] = s:getregister('0')
|
||||
let registers['"'] = s:getregister('"')
|
||||
return registers
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:restoreregisters(registers) abort "{{{
|
||||
for [register, contains] in items(a:registers)
|
||||
call s:setregister(register, contains)
|
||||
endfor
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:getregister(register) abort "{{{
|
||||
return [getreg(a:register), getregtype(a:register)]
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:setregister(register, contains) abort "{{{
|
||||
let [value, options] = a:contains
|
||||
return setreg(a:register, value, options)
|
||||
endfunction
|
||||
"}}}
|
||||
|
||||
" Operator functions
|
||||
function! operator#sandwich#add(motionwise, ...) abort "{{{
|
||||
call s:do('add', a:motionwise, 'OperatorSandwichAddPre', 'OperatorSandwichAddPost')
|
||||
endfunction
|
||||
"}}}
|
||||
function! operator#sandwich#delete(motionwise, ...) abort "{{{
|
||||
call s:do('delete', a:motionwise, 'OperatorSandwichDeletePre', 'OperatorSandwichDeletePost')
|
||||
endfunction
|
||||
"}}}
|
||||
function! operator#sandwich#replace(motionwise, ...) abort "{{{
|
||||
call s:do('replace', a:motionwise, 'OperatorSandwichReplacePre', 'OperatorSandwichReplacePost')
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:do(kind, motionwise, AutocmdPre, AutocmdPost) abort "{{{
|
||||
let s:operator = ''
|
||||
if exists('g:operator#sandwich#object')
|
||||
let operator = g:operator#sandwich#object
|
||||
let messenger = sandwich#messenger#new()
|
||||
let defaultopt = s:default_options(a:kind, a:motionwise)
|
||||
call operator.opt.update('default', defaultopt)
|
||||
call s:update_is_in_cmdline_window()
|
||||
call s:doautocmd(a:AutocmdPre)
|
||||
call operator.execute(a:motionwise)
|
||||
call s:doautocmd(a:AutocmdPost)
|
||||
call messenger.notify('operator-sandwich: ')
|
||||
endif
|
||||
endfunction
|
||||
"}}}
|
||||
" function! s:update_is_in_cmdline_window() abort "{{{
|
||||
if s:has_patch_7_4_392
|
||||
function! s:update_is_in_cmdline_window() abort
|
||||
let s:is_in_cmdline_window = getcmdwintype() !=# ''
|
||||
endfunction
|
||||
else
|
||||
function! s:update_is_in_cmdline_window() abort
|
||||
let s:is_in_cmdline_window = 0
|
||||
try
|
||||
execute 'tabnext ' . tabpagenr()
|
||||
catch /^Vim\%((\a\+)\)\=:E11/
|
||||
let s:is_in_cmdline_window = 1
|
||||
catch
|
||||
endtry
|
||||
endfunction
|
||||
endif
|
||||
"}}}
|
||||
function! s:doautocmd(name) abort "{{{
|
||||
let view = s:saveview()
|
||||
try
|
||||
if exists('#User#' . a:name)
|
||||
execute 'doautocmd <nomodeline> User ' . a:name
|
||||
endif
|
||||
catch
|
||||
let errormsg = printf('operator-sandwich: An error occurred in autocmd %s. [%s]', a:name, v:exception)
|
||||
echoerr errormsg
|
||||
finally
|
||||
call s:restview(view, a:name)
|
||||
endtry
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:saveview() abort "{{{
|
||||
return [tabpagenr(), winnr(), winsaveview(), getpos("'["), getpos("']")]
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:restview(view, name) abort "{{{
|
||||
let [tabpagenr, winnr, view, modhead, modtail] = a:view
|
||||
|
||||
if s:is_in_cmdline_window
|
||||
" in cmdline-window
|
||||
return
|
||||
endif
|
||||
|
||||
" tabpage
|
||||
try
|
||||
execute 'tabnext ' . tabpagenr
|
||||
if tabpagenr() != tabpagenr
|
||||
throw 'OperatorSandwichError:CouldNotRestoreTabpage'
|
||||
endif
|
||||
catch /^OperatorSandwichError:CouldNotRestoreTabpage/
|
||||
let errormsg = printf('operator-sandwich: Could not have restored tabpage after autocmd %s.', a:name)
|
||||
echoerr errormsg
|
||||
endtry
|
||||
|
||||
" window
|
||||
try
|
||||
execute winnr . 'wincmd w'
|
||||
catch /^Vim\%((\a\+)\)\=:E16/
|
||||
let errormsg = printf('operator-sandwich: Could not have restored window after autocmd %s.', a:name)
|
||||
echoerr errormsg
|
||||
endtry
|
||||
" view
|
||||
call winrestview(view)
|
||||
" marks
|
||||
call setpos("'[", modhead)
|
||||
call setpos("']", modtail)
|
||||
endfunction
|
||||
"}}}
|
||||
|
||||
" For the query1st series mappings
|
||||
function! operator#sandwich#query1st(kind, mode, ...) abort "{{{
|
||||
if a:kind !=# 'add' && a:kind !=# 'replace'
|
||||
return
|
||||
endif
|
||||
|
||||
" prerequisite
|
||||
let arg_opt = get(a:000, 0, {})
|
||||
let arg_recipes = get(a:000, 1, [])
|
||||
call operator#sandwich#prerequisite(a:kind, a:mode, arg_opt, arg_recipes)
|
||||
let operator = g:operator#sandwich#object
|
||||
" NOTE: force to set highlight=0 and query_once=1
|
||||
call operator.opt.update('default', {'highlight': 0, 'query_once': 1, 'expr': 0, 'listexpr': 0})
|
||||
let operator.recipes.arg_given = a:0 > 1
|
||||
|
||||
let stuff = operator#sandwich#stuff#new()
|
||||
call stuff.initialize(operator.count, operator.cursor, operator.modmark)
|
||||
let operator.basket = [stuff]
|
||||
|
||||
" pick 'recipe' up and query prefered buns
|
||||
call operator.recipes.integrate(a:kind, 'all', a:mode)
|
||||
for i in range(operator.count)
|
||||
let opt = operator.opt
|
||||
let recipe = operator.query()
|
||||
let operator.recipes.dog_ear += [recipe]
|
||||
if !has_key(recipe, 'buns') || recipe.buns == []
|
||||
break
|
||||
endif
|
||||
|
||||
call opt.update('recipe_add', recipe)
|
||||
if i == 0 && operator.count > 1 && opt.of('query_once')
|
||||
call operator.recipes.fill(recipe, operator.count)
|
||||
break
|
||||
endif
|
||||
endfor
|
||||
|
||||
if filter(copy(operator.recipes.dog_ear), 'has_key(v:val, "buns")') != []
|
||||
let operator.state = 0
|
||||
let cmd = a:mode ==# 'x'
|
||||
\ ? "\<Plug>(operator-sandwich-gv)\<Plug>(operator-sandwich-g@)"
|
||||
\ : "\<Plug>(operator-sandwich-g@)"
|
||||
call feedkeys(cmd, 'im')
|
||||
else
|
||||
unlet g:operator#sandwich#object
|
||||
endif
|
||||
return
|
||||
endfunction
|
||||
"}}}
|
||||
|
||||
" Supplementary keymappings
|
||||
function! operator#sandwich#synchro_count() abort "{{{
|
||||
if exists('g:operator#sandwich#object')
|
||||
return g:operator#sandwich#object.count
|
||||
else
|
||||
return ''
|
||||
endif
|
||||
endfunction
|
||||
"}}}
|
||||
function! operator#sandwich#release_count() abort "{{{
|
||||
if exists('g:operator#sandwich#object')
|
||||
let l:count = g:operator#sandwich#object.count
|
||||
let g:operator#sandwich#object.count = 1
|
||||
return l:count
|
||||
else
|
||||
return ''
|
||||
endif
|
||||
endfunction
|
||||
"}}}
|
||||
function! operator#sandwich#squash_count() abort "{{{
|
||||
if exists('g:operator#sandwich#object')
|
||||
let g:operator#sandwich#object.count = 1
|
||||
endif
|
||||
return ''
|
||||
endfunction
|
||||
"}}}
|
||||
function! operator#sandwich#predot() abort "{{{
|
||||
if exists('g:operator#sandwich#object')
|
||||
let operator = g:operator#sandwich#object
|
||||
let operator.cursor.keepable = 1
|
||||
let operator.cursor.keep[0:3] = getpos('.')[0:3]
|
||||
endif
|
||||
return ''
|
||||
endfunction
|
||||
"}}}
|
||||
function! operator#sandwich#dot() abort "{{{
|
||||
call operator#sandwich#predot()
|
||||
return '.'
|
||||
endfunction
|
||||
"}}}
|
||||
|
||||
" visualrepeat.vim (vimscript #3848) support
|
||||
function! operator#sandwich#visualrepeat(kind) abort "{{{
|
||||
let operator = g:operator#sandwich#object
|
||||
|
||||
let original_mode = operator.mode
|
||||
let original_extended = operator.extended
|
||||
let original_blockwidth = operator.blockwidth
|
||||
|
||||
let operator.mode = 'x'
|
||||
let [operator.extended, operator.blockwidth] = s:blockwisevisual_info('x')
|
||||
try
|
||||
normal! gv
|
||||
let operator.cursor.keepable = 1
|
||||
let operator.cursor.keep[0:3] = getpos('.')[0:3]
|
||||
|
||||
let l:count = v:count ? v:count : ''
|
||||
let &l:operatorfunc = 'operator#sandwich#' . a:kind
|
||||
let cmd = printf("normal! %sg@", l:count)
|
||||
execute cmd
|
||||
finally
|
||||
let operator.mode = original_mode
|
||||
let operator.extended = original_extended
|
||||
let operator.blockwidth = original_blockwidth
|
||||
endtry
|
||||
endfunction
|
||||
"}}}
|
||||
|
||||
" API
|
||||
function! operator#sandwich#show(...) abort "{{{
|
||||
if !exists('g:operator#sandwich#object') || !g:operator#sandwich#object.at_work
|
||||
echoerr 'operator-sandwich: Not in an operator-sandwich operation!'
|
||||
return 1
|
||||
endif
|
||||
|
||||
let operator = g:operator#sandwich#object
|
||||
let kind = operator.kind
|
||||
let opt = operator.opt
|
||||
let place = get(a:000, 0, '')
|
||||
if kind ==# 'add'
|
||||
if place ==# ''
|
||||
let place = 'stuff'
|
||||
endif
|
||||
if place ==# 'added'
|
||||
let hi_group = s:get_ifnotempty(a:000, 1, 'OperatorSandwichAdd')
|
||||
else
|
||||
let hi_group = opt.of('highlight') >= 2
|
||||
\ ? s:get_ifnotempty(a:000, 1, 'OperatorSandwichChange')
|
||||
\ : s:get_ifnotempty(a:000, 1, 'OperatorSandwichBuns')
|
||||
endif
|
||||
elseif kind ==# 'delete'
|
||||
if place ==# ''
|
||||
let place = 'target'
|
||||
endif
|
||||
let hi_group = opt.of('highlight') >= 2
|
||||
\ ? s:get_ifnotempty(a:000, 1, 'OperatorSandwichDelete')
|
||||
\ : s:get_ifnotempty(a:000, 1, 'OperatorSandwichBuns')
|
||||
elseif kind ==# 'replace'
|
||||
if place ==# ''
|
||||
let place = 'target'
|
||||
endif
|
||||
if place ==# 'added'
|
||||
let hi_group = s:get_ifnotempty(a:000, 1, 'OperatorSandwichAdd')
|
||||
elseif place ==# 'target'
|
||||
let hi_group = opt.of('highlight') >= 2
|
||||
\ ? s:get_ifnotempty(a:000, 1, 'OperatorSandwichDelete')
|
||||
\ : s:get_ifnotempty(a:000, 1, 'OperatorSandwichBuns')
|
||||
else
|
||||
let hi_group = opt.of('highlight') >= 2
|
||||
\ ? s:get_ifnotempty(a:000, 1, 'OperatorSandwichChange')
|
||||
\ : s:get_ifnotempty(a:000, 1, 'OperatorSandwichBuns')
|
||||
endif
|
||||
else
|
||||
return 1
|
||||
endif
|
||||
let forcibly = get(a:000, 2, 0)
|
||||
return operator.show(place, hi_group, forcibly)
|
||||
endfunction
|
||||
"}}}
|
||||
function! operator#sandwich#quench(...) abort "{{{
|
||||
if exists('g:operator#sandwich#object')
|
||||
let operator = g:operator#sandwich#object
|
||||
let kind = operator.kind
|
||||
let place = get(a:000, 0, '')
|
||||
if place ==# ''
|
||||
if kind ==# 'add'
|
||||
let place = 'stuff'
|
||||
elseif kind ==# 'delete'
|
||||
let place = 'target'
|
||||
elseif kind ==# 'replace'
|
||||
let place = 'target'
|
||||
else
|
||||
return 1
|
||||
endif
|
||||
endif
|
||||
return operator.quench(place)
|
||||
endif
|
||||
endfunction
|
||||
"}}}
|
||||
function! operator#sandwich#get_info(...) abort "{{{
|
||||
if !exists('g:operator#sandwich#object') || !g:operator#sandwich#object.at_work
|
||||
echoerr 'operator-sandwich: Not in an operator-sandwich operation!'
|
||||
return 1
|
||||
endif
|
||||
|
||||
let info = get(a:000, 0, '')
|
||||
if a:0 == 0 || info ==# ''
|
||||
return g:operator#sandwich#object
|
||||
elseif info ==# 'state' || info ==# 'kind' || info ==# 'count' || info ==# 'mode' || info ==# 'motionwise'
|
||||
return g:operator#sandwich#object[info]
|
||||
else
|
||||
echoerr printf('operator-sandwich: Identifier "%s" is not supported.', string(info))
|
||||
return 1
|
||||
endif
|
||||
endfunction
|
||||
"}}}
|
||||
function! operator#sandwich#kind() abort "{{{
|
||||
return exists('g:operator#sandwich#object') && g:operator#sandwich#object.at_work
|
||||
\ ? g:operator#sandwich#object.kind
|
||||
\ : s:operator
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:get_ifnotempty(list, idx, default) abort "{{{
|
||||
let item = get(a:list, a:idx, '')
|
||||
if item ==# ''
|
||||
let item = a:default
|
||||
endif
|
||||
return item
|
||||
endfunction
|
||||
"}}}
|
||||
|
||||
" For internal communication
|
||||
function! operator#sandwich#is_in_cmd_window() abort "{{{
|
||||
return s:is_in_cmdline_window
|
||||
endfunction
|
||||
"}}}
|
||||
function! operator#sandwich#synchronize(kind, recipe) abort "{{{
|
||||
if exists('g:operator#sandwich#object') && !empty(a:recipe)
|
||||
let g:operator#sandwich#object.recipes.synchro.on = 1
|
||||
let g:operator#sandwich#object.recipes.synchro.kind = a:kind
|
||||
let g:operator#sandwich#object.recipes.synchro.recipe = [a:recipe]
|
||||
endif
|
||||
endfunction
|
||||
"}}}
|
||||
|
||||
" recipes "{{{
|
||||
function! operator#sandwich#get_recipes() abort "{{{
|
||||
if exists('b:operator_sandwich_recipes')
|
||||
let recipes = b:operator_sandwich_recipes
|
||||
elseif exists('g:operator#sandwich#recipes')
|
||||
let recipes = g:operator#sandwich#recipes
|
||||
else
|
||||
let recipes = g:operator#sandwich#default_recipes
|
||||
endif
|
||||
return deepcopy(recipes)
|
||||
endfunction
|
||||
"}}}
|
||||
if exists('g:operator#sandwich#default_recipes')
|
||||
unlockvar! g:operator#sandwich#default_recipes
|
||||
endif
|
||||
let g:operator#sandwich#default_recipes = []
|
||||
lockvar! g:operator#sandwich#default_recipes
|
||||
"}}}
|
||||
|
||||
" options "{{{
|
||||
function! s:default_options(kind, motionwise) abort "{{{
|
||||
return get(b:, 'operator_sandwich_options', g:operator#sandwich#options)[a:kind][a:motionwise]
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:initialize_options(...) abort "{{{
|
||||
let manner = a:0 ? a:1 : 'keep'
|
||||
let g:operator#sandwich#options = get(g:, 'operator#sandwich#options', {})
|
||||
for kind in ['add', 'delete', 'replace']
|
||||
if !has_key(g:operator#sandwich#options, kind)
|
||||
let g:operator#sandwich#options[kind] = {}
|
||||
endif
|
||||
for motionwise in ['char', 'line', 'block']
|
||||
if !has_key(g:operator#sandwich#options[kind], motionwise)
|
||||
let g:operator#sandwich#options[kind][motionwise] = {}
|
||||
endif
|
||||
call extend(g:operator#sandwich#options[kind][motionwise],
|
||||
\ sandwich#opt#defaults(kind, motionwise), manner)
|
||||
endfor
|
||||
endfor
|
||||
endfunction
|
||||
call s:initialize_options()
|
||||
"}}}
|
||||
function! operator#sandwich#set_default() abort "{{{
|
||||
call s:initialize_options('force')
|
||||
endfunction
|
||||
"}}}
|
||||
function! operator#sandwich#set(kind, motionwise, option, value) abort "{{{
|
||||
if s:argument_error(a:kind, a:motionwise, a:option, a:value)
|
||||
return
|
||||
endif
|
||||
|
||||
if a:kind ==# 'all'
|
||||
let kinds = ['add', 'delete', 'replace']
|
||||
else
|
||||
let kinds = [a:kind]
|
||||
endif
|
||||
|
||||
if a:motionwise ==# 'all'
|
||||
let motionwises = ['char', 'line', 'block']
|
||||
else
|
||||
let motionwises = [a:motionwise]
|
||||
endif
|
||||
|
||||
call s:set_option_value(g:operator#sandwich#options, kinds, motionwises, a:option, a:value)
|
||||
endfunction
|
||||
"}}}
|
||||
function! operator#sandwich#setlocal(kind, motionwise, option, value) abort "{{{
|
||||
if s:argument_error(a:kind, a:motionwise, a:option, a:value)
|
||||
return
|
||||
endif
|
||||
|
||||
if !exists('b:operator_sandwich_options')
|
||||
let b:operator_sandwich_options = deepcopy(g:operator#sandwich#options)
|
||||
endif
|
||||
|
||||
if a:kind ==# 'all'
|
||||
let kinds = ['add', 'delete', 'replace']
|
||||
else
|
||||
let kinds = [a:kind]
|
||||
endif
|
||||
|
||||
if a:motionwise ==# 'all'
|
||||
let motionwises = ['char', 'line', 'block']
|
||||
else
|
||||
let motionwises = [a:motionwise]
|
||||
endif
|
||||
|
||||
call s:set_option_value(b:operator_sandwich_options, kinds, motionwises, a:option, a:value)
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:argument_error(kind, motionwise, option, value) abort "{{{
|
||||
if !(a:kind ==# 'add' || a:kind ==# 'delete' || a:kind ==# 'replace' || a:kind ==# 'all')
|
||||
echohl WarningMsg
|
||||
echomsg 'Invalid kind "' . a:kind . '".'
|
||||
echohl NONE
|
||||
return 1
|
||||
endif
|
||||
|
||||
if !(a:motionwise ==# 'char' || a:motionwise ==# 'line' || a:motionwise ==# 'block' || a:motionwise ==# 'all')
|
||||
echohl WarningMsg
|
||||
echomsg 'Invalid motion-wise "' . a:motionwise . '".'
|
||||
echohl NONE
|
||||
return 1
|
||||
endif
|
||||
|
||||
if a:kind !=# 'all' && a:motionwise !=# 'all'
|
||||
let defaults = sandwich#opt#defaults(a:kind, a:motionwise)
|
||||
if filter(keys(defaults), 'v:val ==# a:option') == []
|
||||
echohl WarningMsg
|
||||
echomsg 'Invalid option name "' . a:option . '".'
|
||||
echohl NONE
|
||||
return 1
|
||||
endif
|
||||
|
||||
if a:option !~# 'indentkeys[-+]\?' && type(a:value) != type(defaults[a:option])
|
||||
echohl WarningMsg
|
||||
echomsg 'Invalid type of value. ' . string(a:value)
|
||||
echohl NONE
|
||||
return 1
|
||||
endif
|
||||
endif
|
||||
return 0
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:set_option_value(dest, kinds, motionwises, option, value) abort "{{{
|
||||
for kind in a:kinds
|
||||
for motionwise in a:motionwises
|
||||
let defaults = sandwich#opt#defaults(kind, motionwise)
|
||||
if filter(keys(defaults), 'v:val ==# a:option') != []
|
||||
if a:option =~# 'indentkeys[-+]\?' || type(a:value) == type(defaults[a:option])
|
||||
let a:dest[kind][motionwise][a:option] = a:value
|
||||
endif
|
||||
endif
|
||||
endfor
|
||||
endfor
|
||||
endfunction
|
||||
"}}}
|
||||
"}}}
|
||||
|
||||
unlet! g:operator#sandwich#object
|
||||
|
||||
|
||||
" vim:set foldmethod=marker:
|
||||
" vim:set commentstring="%s:
|
||||
" vim:set ts=2 sts=2 sw=2:
|
||||
@ -0,0 +1,836 @@
|
||||
" act object - editing buffer
|
||||
|
||||
let s:lib = operator#sandwich#lib#import()
|
||||
|
||||
" variables "{{{
|
||||
let s:constants = function('sandwich#constants#get')
|
||||
let s:TRUE = 1
|
||||
let s:FALSE = 0
|
||||
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>(i) i
|
||||
nnoremap <SID>(o) o
|
||||
nnoremap <SID>(O) O
|
||||
|
||||
let s:KEY_i = printf('%s(i)', s:SNR)
|
||||
let s:KEY_o = printf('%s(o)', s:SNR)
|
||||
let s:KEY_O = printf('%s(O)', s:SNR)
|
||||
|
||||
" null valiables
|
||||
let s:null_pos = [0, 0, 0, 0]
|
||||
let s:null_4pos = {
|
||||
\ 'head1': copy(s:null_pos),
|
||||
\ 'tail1': copy(s:null_pos),
|
||||
\ 'head2': copy(s:null_pos),
|
||||
\ 'tail2': copy(s:null_pos),
|
||||
\ }
|
||||
|
||||
" types
|
||||
let s:type_str = type('')
|
||||
|
||||
" features
|
||||
let s:has_gui_running = has('gui_running')
|
||||
"}}}
|
||||
|
||||
function! operator#sandwich#act#new() abort "{{{
|
||||
return deepcopy(s:act)
|
||||
endfunction
|
||||
"}}}
|
||||
|
||||
" s:act "{{{
|
||||
let s:act = {
|
||||
\ 'cursor' : {},
|
||||
\ 'modmark': {},
|
||||
\ 'opt' : {},
|
||||
\ 'success': 0,
|
||||
\ 'added' : [],
|
||||
\ }
|
||||
"}}}
|
||||
function! s:act.initialize(cursor, modmark, added) dict abort "{{{
|
||||
let self.cursor = a:cursor
|
||||
let self.modmark = a:modmark
|
||||
let self.opt = {}
|
||||
let self.added = a:added
|
||||
let self.success = 0
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:act.add_pair(buns, stuff, undojoin) dict abort "{{{
|
||||
let target = a:stuff.target
|
||||
let edges = a:stuff.edges
|
||||
let modmark = self.modmark
|
||||
let opt = self.opt
|
||||
let indent = [0, 0]
|
||||
let is_linewise = [0, 0]
|
||||
|
||||
if s:lib.is_valid_4pos(target) && s:lib.is_equal_or_ahead(target.head2, target.head1)
|
||||
if target.head2[2] != col([target.head2[1], '$'])
|
||||
let target.head2[0:3] = s:lib.get_right_pos(target.head2)
|
||||
endif
|
||||
|
||||
let indentopt = s:set_indent(opt)
|
||||
let messenger = sandwich#messenger#get()
|
||||
try
|
||||
let pos = target.head1
|
||||
let [is_linewise[0], indent[0], head1, tail1] = s:add_former(a:buns, pos, opt, a:undojoin)
|
||||
let pos = s:push1(copy(target.head2), target, a:buns, indent, is_linewise)
|
||||
let [is_linewise[1], indent[1], head2, tail2] = s:add_latter(a:buns, pos, opt)
|
||||
catch /^Vim\%((\a\+)\)\=:E21/
|
||||
call messenger.notice.queue(['Cannot make changes to read-only buffer.', 'WarningMsg'])
|
||||
throw 'OperatorSandwichError:ReadOnly'
|
||||
finally
|
||||
call s:restore_indent(indentopt)
|
||||
endtry
|
||||
let [mod_head, mod_tail] = s:execute_command(head1, tail2, opt.of('command'))
|
||||
|
||||
if opt.of('highlight', '') >= 3
|
||||
call map(self.added, 's:shift_added("s:shift_for_add", v:val, target, a:buns, indent, is_linewise)')
|
||||
call add(self.added, {
|
||||
\ 'head1': head1,
|
||||
\ 'tail1': s:added_tail(head1, tail1, is_linewise[0]),
|
||||
\ 'head2': head2,
|
||||
\ 'tail2': s:added_tail(head2, tail2, is_linewise[1]),
|
||||
\ 'linewise': is_linewise,
|
||||
\ })
|
||||
endif
|
||||
|
||||
" update modmark
|
||||
if modmark.head == s:null_pos || s:lib.is_ahead(modmark.head, mod_head)
|
||||
let modmark.head = mod_head
|
||||
endif
|
||||
if modmark.tail == s:null_pos
|
||||
let modmark.tail = mod_tail
|
||||
else
|
||||
call s:shift_for_add(modmark.tail, target, a:buns, indent, is_linewise)
|
||||
if s:lib.is_ahead(mod_tail, modmark.tail)
|
||||
let modmark.tail = mod_tail
|
||||
endif
|
||||
endif
|
||||
|
||||
" update cursor positions
|
||||
call s:shift_for_add(self.cursor.inner_head, target, a:buns, indent, is_linewise)
|
||||
call s:shift_for_add(self.cursor.keep, target, a:buns, indent, is_linewise)
|
||||
call s:shift_for_add(self.cursor.inner_tail, target, a:buns, indent, is_linewise)
|
||||
|
||||
" update next target positions
|
||||
let edges.head = copy(head1)
|
||||
let edges.tail = s:lib.get_left_pos(tail2)
|
||||
|
||||
let self.success = 1
|
||||
endif
|
||||
return self.success
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:act.delete_pair(stuff, modified) dict abort "{{{
|
||||
let target = a:stuff.target
|
||||
let edges = a:stuff.edges
|
||||
let modmark = self.modmark
|
||||
let opt = self.opt
|
||||
|
||||
if s:lib.is_valid_4pos(target) && s:lib.is_ahead(target.head2, target.tail1)
|
||||
let reg = ['"', getreg('"'), getregtype('"')]
|
||||
let deletion = ['', '']
|
||||
let is_linewise = [0, 0]
|
||||
let messenger = sandwich#messenger#get()
|
||||
try
|
||||
let former_head = target.head1
|
||||
let former_tail = target.tail1
|
||||
let latter_head = target.head2
|
||||
let [deletion[0], is_linewise[0], head] = s:delete_former(former_head, former_tail, latter_head, opt)
|
||||
|
||||
let latter_head = s:pull1(copy(target.head2), target, deletion, is_linewise)
|
||||
let latter_tail = s:pull1(copy(target.tail2), target, deletion, is_linewise)
|
||||
let [deletion[1], is_linewise[1], tail] = s:delete_latter(latter_head, latter_tail, former_head, opt)
|
||||
catch /^Vim\%((\a\+)\)\=:E21/
|
||||
call messenger.notice.queue(['Cannot make changes to read-only buffer.', 'WarningMsg'])
|
||||
throw 'OperatorSandwichError:ReadOnly'
|
||||
finally
|
||||
call call('setreg', reg)
|
||||
endtry
|
||||
let [mod_head, mod_tail] = s:execute_command(head, tail, opt.of('command'))
|
||||
|
||||
" update modmark
|
||||
if modmark.head == s:null_pos || s:lib.is_ahead(modmark.head, mod_head)
|
||||
let modmark.head = mod_head
|
||||
endif
|
||||
" NOTE: Probably, there is no possibility to delete breakings along multiple acts.
|
||||
if !a:modified
|
||||
if modmark.tail == s:null_pos
|
||||
let modmark.tail = mod_tail
|
||||
else
|
||||
call s:shift_for_delete(modmark.tail, target, deletion, is_linewise)
|
||||
if mod_tail[1] >= modmark.tail[1]
|
||||
let modmark.tail = mod_tail
|
||||
endif
|
||||
endif
|
||||
endif
|
||||
|
||||
" update cursor positions
|
||||
call s:shift_for_delete(self.cursor.inner_head, target, deletion, is_linewise)
|
||||
call s:shift_for_delete(self.cursor.keep, target, deletion, is_linewise)
|
||||
call s:shift_for_delete(self.cursor.inner_tail, target, deletion, is_linewise)
|
||||
|
||||
" update target positions
|
||||
let edges.head = copy(head)
|
||||
let edges.tail = s:lib.get_left_pos(tail)
|
||||
|
||||
let self.success = 1
|
||||
endif
|
||||
return self.success
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:act.replace_pair(buns, stuff, undojoin, modified) dict abort "{{{
|
||||
let target = a:stuff.target
|
||||
let edges = a:stuff.edges
|
||||
let modmark = self.modmark
|
||||
let opt = self.opt
|
||||
|
||||
if s:lib.is_valid_4pos(target) && s:lib.is_ahead(target.head2, target.tail1)
|
||||
set virtualedit=
|
||||
let next_head = s:lib.get_right_pos(target.tail1)
|
||||
let next_tail = s:lib.get_left_pos(target.head2)
|
||||
set virtualedit=onemore
|
||||
|
||||
let reg = ['"', getreg('"'), getregtype('"')]
|
||||
let deletion = ['', '']
|
||||
let indent = [0, 0]
|
||||
let is_linewise = [0, 0]
|
||||
let indentopt = s:set_indent(opt)
|
||||
let messenger = sandwich#messenger#get()
|
||||
try
|
||||
let within_a_line = target.tail1[1] == target.head2[1]
|
||||
let former_head = target.head1
|
||||
let former_tail = target.tail1
|
||||
let latter_head = copy(target.head2)
|
||||
let latter_tail = copy(target.tail2)
|
||||
let [deletion[0], is_linewise[0], indent[0], head1, tail1] = s:replace_former(a:buns, former_head, former_tail, within_a_line, opt, a:undojoin)
|
||||
|
||||
call s:pull1(latter_head, target, deletion, is_linewise)
|
||||
call s:push1(latter_head, target, a:buns, indent, is_linewise)
|
||||
call s:pull1(latter_tail, target, deletion, is_linewise)
|
||||
call s:push1(latter_tail, target, a:buns, indent, is_linewise)
|
||||
let [deletion[1], is_linewise[1], indent[1], head2, tail2] = s:replace_latter(a:buns, latter_head, latter_tail, within_a_line, opt)
|
||||
catch /^Vim\%((\a\+)\)\=:E21/
|
||||
call messenger.notice.queue(['Cannot make changes to read-only buffer.', 'WarningMsg'])
|
||||
throw 'OperatorSandwichError:ReadOnly'
|
||||
finally
|
||||
call call('setreg', reg)
|
||||
call s:restore_indent(indentopt)
|
||||
endtry
|
||||
let [mod_head, mod_tail] = s:execute_command(head1, tail2, opt.of('command'))
|
||||
|
||||
if opt.of('highlight', '') >= 3
|
||||
call map(self.added, 's:shift_added("s:shift_for_replace", v:val, target, a:buns, deletion, indent, is_linewise)')
|
||||
call add(self.added, {
|
||||
\ 'head1': head1,
|
||||
\ 'tail1': s:lib.get_left_pos(tail1),
|
||||
\ 'head2': head2,
|
||||
\ 'tail2': s:lib.get_left_pos(tail2),
|
||||
\ 'linewise': is_linewise
|
||||
\ })
|
||||
endif
|
||||
|
||||
" update modmark
|
||||
if modmark.head == s:null_pos || s:lib.is_ahead(modmark.head, mod_head)
|
||||
let modmark.head = copy(mod_head)
|
||||
endif
|
||||
if !a:modified
|
||||
if modmark.tail == s:null_pos
|
||||
let modmark.tail = copy(mod_tail)
|
||||
else
|
||||
call s:shift_for_replace(modmark.tail, target, a:buns, deletion, indent, is_linewise)
|
||||
if modmark.tail[1] < mod_tail[1]
|
||||
let modmark.tail = copy(mod_tail)
|
||||
endif
|
||||
endif
|
||||
endif
|
||||
|
||||
" update cursor positions
|
||||
call s:shift_for_replace(self.cursor.keep, target, a:buns, deletion, indent, is_linewise)
|
||||
call s:shift_for_replace(next_head, target, a:buns, deletion, indent, is_linewise)
|
||||
call s:shift_for_replace(next_tail, target, a:buns, deletion, indent, is_linewise)
|
||||
if self.cursor.inner_head == s:null_pos || target.head1[1] <= self.cursor.inner_head[1]
|
||||
let self.cursor.inner_head = copy(next_head)
|
||||
endif
|
||||
if self.cursor.inner_tail == s:null_pos
|
||||
let self.cursor.inner_tail = copy(next_tail)
|
||||
else
|
||||
call s:shift_for_replace(self.cursor.inner_tail, target, a:buns, deletion, indent, is_linewise)
|
||||
if self.cursor.inner_tail[1] <= next_tail[1]
|
||||
let self.cursor.inner_tail = copy(next_tail)
|
||||
endif
|
||||
endif
|
||||
|
||||
" update target positions
|
||||
let edges.head = next_head
|
||||
let edges.tail = next_tail
|
||||
|
||||
let self.success = 1
|
||||
endif
|
||||
return self.success
|
||||
endfunction
|
||||
"}}}
|
||||
|
||||
" supplemental classes
|
||||
" Indent class {{{
|
||||
let s:Indent = {
|
||||
\ 'len': 0,
|
||||
\ 'str': '',
|
||||
\ 'savedstr': '',
|
||||
\ 'linehead': s:FALSE
|
||||
\ }
|
||||
function! s:Indent(pos, opt) abort "{{{
|
||||
let line = getline(a:pos[1])
|
||||
let indent = deepcopy(s:Indent)
|
||||
if a:pos[2] == 1
|
||||
let indent.linehead = s:TRUE
|
||||
return indent
|
||||
endif
|
||||
|
||||
let indent.str = matchstr(line, '^\s*')
|
||||
let indent.len = strlen(indent.str)
|
||||
if a:pos[2] <= indent.len
|
||||
" shorten if cursor is in indentation
|
||||
let indent.str = indent.str[: a:pos[2] - 2]
|
||||
let indent.len = strlen(indent.str)
|
||||
let indent.linehead = s:TRUE
|
||||
endif
|
||||
|
||||
if a:opt.of('linewise') && a:opt.of('autoindent') == 4
|
||||
let indent.savedstr = indent.str
|
||||
endif
|
||||
return indent
|
||||
endfunction "}}}
|
||||
function! s:Indent.diff(addition) abort "{{{
|
||||
let addition = split(a:addition, '\m\%(\n\|\r\|\r\n\)', 1)
|
||||
if len(addition) == 1
|
||||
let indentstr = matchstr(getline("'["), '\m^\s*')
|
||||
let indentlen = strlen(indentstr)
|
||||
if self.linehead
|
||||
let indentlen -= strlen(matchstr(addition[0], '\m^\s*'))
|
||||
endif
|
||||
let diff = indentlen - self.len
|
||||
else
|
||||
let indentstr = matchstr(getline("']"), '\m^\s*')
|
||||
let indentlen = strlen(indentstr)
|
||||
let diff = indentlen - strlen(matchstr(addition[-1], '\m^\s*'))
|
||||
endif
|
||||
return diff
|
||||
endfunction "}}}
|
||||
"}}}
|
||||
|
||||
" private functions
|
||||
function! s:set_indent(opt) abort "{{{
|
||||
let indentopt = {
|
||||
\ 'autoindent': {
|
||||
\ 'restore': 0,
|
||||
\ 'value' : [&l:autoindent, &l:smartindent, &l:cindent, &l:indentexpr],
|
||||
\ },
|
||||
\ 'indentkeys': {
|
||||
\ 'restore': 0,
|
||||
\ 'name' : '',
|
||||
\ 'value' : '',
|
||||
\ },
|
||||
\ }
|
||||
|
||||
" set autoindent options
|
||||
if a:opt.of('autoindent') == 0 || a:opt.of('autoindent') == 4
|
||||
let [&l:autoindent, &l:smartindent, &l:cindent, &l:indentexpr] = [0, 0, 0, '']
|
||||
let indentopt.autoindent.restore = 1
|
||||
elseif a:opt.of('autoindent') == 1
|
||||
let [&l:autoindent, &l:smartindent, &l:cindent, &l:indentexpr] = [1, 0, 0, '']
|
||||
let indentopt.autoindent.restore = 1
|
||||
elseif a:opt.of('autoindent') == 2
|
||||
" NOTE: 'Smartindent' requires 'autoindent'. :help 'smartindent'
|
||||
let [&l:autoindent, &l:smartindent, &l:cindent, &l:indentexpr] = [1, 1, 0, '']
|
||||
let indentopt.autoindent.restore = 1
|
||||
elseif a:opt.of('autoindent') == 3
|
||||
let [&l:cindent, &l:indentexpr] = [1, '']
|
||||
let indentopt.autoindent.restore = 1
|
||||
endif
|
||||
|
||||
" set indentkeys
|
||||
if &l:indentexpr !=# ''
|
||||
let indentopt.indentkeys.name = 'indentkeys'
|
||||
let indentopt.indentkeys.value = &l:indentkeys
|
||||
else
|
||||
let indentopt.indentkeys.name = 'cinkeys'
|
||||
let indentopt.indentkeys.value = &l:cinkeys
|
||||
endif
|
||||
|
||||
let val = a:opt.of('indentkeys')
|
||||
if type(val) == s:type_str
|
||||
execute printf('setlocal %s=%s', indentopt.indentkeys.name, val)
|
||||
let indentopt.indentkeys.restore = 1
|
||||
endif
|
||||
|
||||
let val = a:opt.of('indentkeys+')
|
||||
if type(val) == s:type_str && val !=# ''
|
||||
execute printf('setlocal %s+=%s', indentopt.indentkeys.name, val)
|
||||
let indentopt.indentkeys.restore = 1
|
||||
endif
|
||||
|
||||
let val = a:opt.of('indentkeys-')
|
||||
if type(val) == s:type_str && val !=# ''
|
||||
" It looks there is no way to add ',' itself to 'indentkeys'
|
||||
for item in split(val, ',')
|
||||
execute printf('setlocal %s-=%s', indentopt.indentkeys.name, item)
|
||||
endfor
|
||||
let indentopt.indentkeys.restore = 1
|
||||
endif
|
||||
return indentopt
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:restore_indent(indentopt) abort "{{{
|
||||
" restore indentkeys first
|
||||
if a:indentopt.indentkeys.restore
|
||||
execute printf('setlocal %s=%s', a:indentopt.indentkeys.name, escape(a:indentopt.indentkeys.value, ' \'))
|
||||
endif
|
||||
|
||||
" restore autoindent options
|
||||
if a:indentopt.autoindent.restore
|
||||
let [&l:autoindent, &l:smartindent, &l:cindent, &l:indentexpr] = a:indentopt.autoindent.value
|
||||
endif
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:add_former(buns, pos, opt, ...) abort "{{{
|
||||
let undojoin_cmd = get(a:000, 0, 0) ? 'undojoin | ' : ''
|
||||
let indent = s:Indent(a:pos, a:opt)
|
||||
let opt_linewise = a:opt.of('linewise')
|
||||
if opt_linewise
|
||||
let startinsert = a:opt.of('noremap') ? 'normal! O' : 'normal ' . s:KEY_O
|
||||
let insertion = indent.savedstr . a:buns[0]
|
||||
else
|
||||
let startinsert = a:opt.of('noremap') ? 'normal! i' : 'normal ' . s:KEY_i
|
||||
let insertion = a:buns[0]
|
||||
endif
|
||||
call s:add_portion(insertion, a:pos, undojoin_cmd, startinsert)
|
||||
return [opt_linewise, indent.diff(a:buns[0]), getpos("'["), getpos("']")]
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:add_latter(buns, pos, opt) abort "{{{
|
||||
let undojoin_cmd = ''
|
||||
let indent = s:Indent(a:pos, a:opt)
|
||||
let opt_linewise = a:opt.of('linewise')
|
||||
if opt_linewise
|
||||
let startinsert = a:opt.of('noremap') ? 'normal! o' : 'normal ' . s:KEY_o
|
||||
let insertion = indent.savedstr . a:buns[1]
|
||||
else
|
||||
let startinsert = a:opt.of('noremap') ? 'normal! i' : 'normal ' . s:KEY_i
|
||||
let insertion = a:buns[1]
|
||||
endif
|
||||
call s:add_portion(insertion, a:pos, undojoin_cmd, startinsert)
|
||||
return [opt_linewise, indent.diff(a:buns[1]), getpos("'["), getpos("']")]
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:add_portion(bun, pos, undojoin_cmd, startinsert) abort "{{{
|
||||
call setpos('.', a:pos)
|
||||
if operator#sandwich#is_in_cmd_window()
|
||||
" workaround for a bug in cmdline-window
|
||||
call s:paste(a:bun, a:undojoin_cmd)
|
||||
else
|
||||
execute a:undojoin_cmd . 'silent noautocmd ' . a:startinsert . a:bun
|
||||
endif
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:delete_former(head, tail, latter_head, opt, ...) abort "{{{
|
||||
let is_linewise = 0
|
||||
let opt_linewise = a:opt.of('linewise')
|
||||
let undojoin_cmd = get(a:000, 0, 0) ? 'undojoin | ' : ''
|
||||
let deletion = s:delete_portion(a:head, a:tail, undojoin_cmd)
|
||||
if opt_linewise == 2 || (opt_linewise == 1 && match(getline('.'), '^\s*$') > -1)
|
||||
if line('.') != a:latter_head[1]
|
||||
.delete
|
||||
let is_linewise = 1
|
||||
endif
|
||||
let head = getpos("']")
|
||||
else
|
||||
let head = getpos('.')
|
||||
endif
|
||||
return [deletion, is_linewise, head]
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:delete_latter(head, tail, former_head, opt) abort "{{{
|
||||
let is_linewise = 0
|
||||
let opt_linewise = a:opt.of('linewise')
|
||||
let undojoin_cmd = ''
|
||||
let deletion = s:delete_portion(a:head, a:tail, undojoin_cmd)
|
||||
if opt_linewise == 2 || (opt_linewise == 1 && match(getline('.'), '^\s*$') > -1)
|
||||
.delete
|
||||
let is_linewise = 1
|
||||
let tail = getpos("']")
|
||||
if tail[1] != 1 && tail[1] != a:former_head[1]
|
||||
let prevline = line("']") - 1
|
||||
let tail = [0, prevline, col([prevline, '$']), 0]
|
||||
endif
|
||||
else
|
||||
let tail = getpos("']")
|
||||
endif
|
||||
return [deletion, is_linewise, tail]
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:delete_portion(head, tail, undojoin_cmd) abort "{{{
|
||||
let cmd = "%ssilent noautocmd normal! \"\"dv:call setpos('\.', %s)\<CR>"
|
||||
call setpos('.', a:head)
|
||||
let @@ = ''
|
||||
execute printf(cmd, a:undojoin_cmd, 'a:tail')
|
||||
return @@
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:replace_former(buns, head, tail, within_a_line, opt, ...) abort "{{{
|
||||
let is_linewise = 0
|
||||
let opt_linewise = a:opt.of('linewise')
|
||||
let undojoin_cmd = get(a:000, 0, 0) ? 'undojoin | ' : ''
|
||||
let deletion = s:delete_portion(a:head, a:tail, undojoin_cmd)
|
||||
let indent = s:Indent(a:head, a:opt)
|
||||
|
||||
if operator#sandwich#is_in_cmd_window()
|
||||
" workaround for a bug in cmdline-window
|
||||
call s:paste(a:buns[0])
|
||||
else
|
||||
if opt_linewise == 1 && getline('.') =~# '^\s*$'
|
||||
.delete
|
||||
let startinsert = a:opt.of('noremap', 'recipe_add') ? 'normal! O' : 'normal ' . s:KEY_O
|
||||
execute 'silent noautocmd ' . startinsert . indent.savedstr . a:buns[0]
|
||||
let is_linewise = 1
|
||||
elseif opt_linewise == 2
|
||||
if !a:within_a_line
|
||||
.delete
|
||||
endif
|
||||
let startinsert = a:opt.of('noremap', 'recipe_add') ? 'normal! O' : 'normal ' . s:KEY_O
|
||||
execute 'silent noautocmd ' . startinsert . indent.savedstr . a:buns[0]
|
||||
let is_linewise = 1
|
||||
else
|
||||
let startinsert = a:opt.of('noremap', 'recipe_add') ? 'normal! i' : 'normal ' . s:KEY_i
|
||||
execute 'silent noautocmd ' . startinsert . a:buns[0]
|
||||
endif
|
||||
endif
|
||||
return [deletion, is_linewise, indent.diff(a:buns[0]), getpos("'["), getpos("']")]
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:replace_latter(buns, head, tail, within_a_line, opt) abort "{{{
|
||||
let is_linewise = 0
|
||||
let opt_linewise = a:opt.of('linewise')
|
||||
let undojoin_cmd = ''
|
||||
let deletion = s:delete_portion(a:head, a:tail, undojoin_cmd)
|
||||
let indent = s:Indent(a:head, a:opt)
|
||||
|
||||
if operator#sandwich#is_in_cmd_window()
|
||||
" workaround for a bug in cmdline-window
|
||||
call s:paste(a:buns[1])
|
||||
let head = getpos("'[")
|
||||
let tail = getpos("']")
|
||||
else
|
||||
if opt_linewise == 1 && getline('.') =~# '^\s*$'
|
||||
let startinsert = a:opt.of('noremap', 'recipe_add') ? 'normal! o' : 'normal ' . s:KEY_o
|
||||
let current = line('.')
|
||||
let fileend = line('$')
|
||||
.delete
|
||||
if current != fileend
|
||||
normal! k
|
||||
endif
|
||||
execute 'silent noautocmd ' . startinsert . indent.savedstr . a:buns[1]
|
||||
let head = getpos("'[")
|
||||
let tail = getpos("']")
|
||||
let is_linewise = 1
|
||||
elseif opt_linewise == 2
|
||||
let startinsert = a:opt.of('noremap', 'recipe_add') ? 'normal! o' : 'normal ' . s:KEY_o
|
||||
if a:within_a_line
|
||||
" exceptional behavior
|
||||
let lnum = line('.')
|
||||
execute 'silent noautocmd ' . startinsert . indent.savedstr . a:buns[1]
|
||||
let head = getpos("'[")
|
||||
let tail = getpos("']")
|
||||
execute lnum . 'delete'
|
||||
let head = [0, head[1]-1, head[2], 0]
|
||||
let tail = [0, tail[1]-1, tail[2], 0]
|
||||
else
|
||||
" usual way (same as opt_linewise == 1)
|
||||
let current = line('.')
|
||||
let fileend = line('$')
|
||||
.delete
|
||||
if current != fileend
|
||||
normal! k
|
||||
endif
|
||||
execute 'silent noautocmd ' . startinsert . indent.savedstr . a:buns[1]
|
||||
let head = getpos("'[")
|
||||
let tail = getpos("']")
|
||||
endif
|
||||
let is_linewise = 1
|
||||
else
|
||||
let startinsert = a:opt.of('noremap', 'recipe_add') ? 'normal! i' : 'normal ' . s:KEY_i
|
||||
execute 'silent noautocmd ' . startinsert . a:buns[1]
|
||||
let head = getpos("'[")
|
||||
let tail = getpos("']")
|
||||
endif
|
||||
endif
|
||||
return [deletion, is_linewise, indent.diff(a:buns[1]), head, tail]
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:paste(bun, ...) abort "{{{
|
||||
let undojoin_cmd = a:0 > 0 ? a:1 : ''
|
||||
let reg = ['"', getreg('"'), getregtype('"')]
|
||||
let @@ = a:bun
|
||||
if s:has_gui_running
|
||||
execute undojoin_cmd . 'normal! ""P'
|
||||
else
|
||||
let paste = &paste
|
||||
let &paste = 1
|
||||
execute undojoin_cmd . 'normal! ""P'
|
||||
let &paste = paste
|
||||
endif
|
||||
call call('setreg', reg)
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:execute_command(head, tail, command_list) abort "{{{
|
||||
let mod_head = deepcopy(a:head)
|
||||
let mod_tail = deepcopy(a:tail)
|
||||
|
||||
if a:command_list != []
|
||||
let before_mod_head = getpos("'[")
|
||||
let before_mod_tail = getpos("']")
|
||||
call setpos("'[", a:head)
|
||||
call setpos("']", a:tail)
|
||||
for command in a:command_list
|
||||
execute command
|
||||
endfor
|
||||
|
||||
let after_mod_head = getpos("'[")
|
||||
let after_mod_tail = getpos("']")
|
||||
if before_mod_head != after_mod_head || before_mod_tail != after_mod_tail
|
||||
let mod_head = after_mod_head
|
||||
let mod_tail = after_mod_tail
|
||||
endif
|
||||
endif
|
||||
return [mod_head, mod_tail]
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:shift_for_add(shifted_pos, target, addition, indent, is_linewise) abort "{{{
|
||||
call s:push2(a:shifted_pos, a:target, a:addition, a:indent, a:is_linewise)
|
||||
call s:push1(a:shifted_pos, a:target, a:addition, a:indent, a:is_linewise)
|
||||
return a:shifted_pos
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:shift_for_delete(shifted_pos, target, deletion, is_linewise) abort "{{{
|
||||
call s:pull2(a:shifted_pos, a:target, a:deletion, a:is_linewise)
|
||||
call s:pull1(a:shifted_pos, a:target, a:deletion, a:is_linewise)
|
||||
return a:shifted_pos
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:shift_for_replace(shifted_pos, target, addition, deletion, indent, is_linewise) abort "{{{
|
||||
if s:lib.is_in_between(a:shifted_pos, a:target.head1, a:target.tail1)
|
||||
let startpos = copy(a:target.head1)
|
||||
let endpos = copy(startpos)
|
||||
call s:push1(endpos, a:target, a:addition, a:indent, a:is_linewise)
|
||||
let endpos = s:lib.get_left_pos(endpos)
|
||||
|
||||
if s:lib.is_equal_or_ahead(a:shifted_pos, endpos)
|
||||
let a:shifted_pos[0:3] = endpos
|
||||
endif
|
||||
elseif s:lib.is_in_between(a:shifted_pos, a:target.head2, a:target.tail2)
|
||||
let startpos = copy(a:target.head2)
|
||||
call s:pull1(startpos, a:target, a:deletion, a:is_linewise)
|
||||
call s:push1(startpos, a:target, a:addition, a:indent, a:is_linewise)
|
||||
let endpos = copy(startpos)
|
||||
let target = copy(s:null_4pos)
|
||||
let target.head2 = copy(startpos)
|
||||
call s:push2(endpos, target, a:addition, a:indent, a:is_linewise)
|
||||
let endpos = s:lib.get_left_pos(endpos)
|
||||
|
||||
call s:pull1(a:shifted_pos, a:target, a:deletion, a:is_linewise)
|
||||
call s:push1(a:shifted_pos, a:target, a:addition, a:indent, a:is_linewise)
|
||||
|
||||
if s:lib.is_equal_or_ahead(a:shifted_pos, endpos)
|
||||
let a:shifted_pos[0:3] = endpos
|
||||
endif
|
||||
else
|
||||
call s:pull2(a:shifted_pos, a:target, a:deletion, a:is_linewise)
|
||||
if a:is_linewise[1]
|
||||
let a:target.head2[1] -= 1
|
||||
endif
|
||||
call s:push2(a:shifted_pos, a:target, a:addition, a:indent, a:is_linewise)
|
||||
if a:is_linewise[1]
|
||||
let a:target.head2[1] += 1
|
||||
endif
|
||||
call s:pull1(a:shifted_pos, a:target, a:deletion, a:is_linewise)
|
||||
if a:is_linewise[0]
|
||||
let a:target.head1[1] -= 1
|
||||
endif
|
||||
call s:push1(a:shifted_pos, a:target, a:addition, a:indent, a:is_linewise)
|
||||
if a:is_linewise[0]
|
||||
let a:target.head1[1] += 1
|
||||
endif
|
||||
endif
|
||||
return a:shifted_pos
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:shift_added(func_name, added, ...) abort "{{{
|
||||
for pos in ['head1', 'tail1', 'head2', 'tail2']
|
||||
call call(a:func_name, [a:added[pos]] + a:000)
|
||||
endfor
|
||||
return a:added
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:push1(shifted_pos, target, addition, indent, is_linewise) abort "{{{
|
||||
if a:shifted_pos != s:null_pos
|
||||
let shift = [0, 0, 0, 0]
|
||||
let head = a:target.head1
|
||||
|
||||
if a:is_linewise[0] && a:shifted_pos[1] >= head[1]
|
||||
" lnum
|
||||
let shift[1] += 1
|
||||
endif
|
||||
|
||||
if s:lib.is_equal_or_ahead(a:shifted_pos, head) || (a:is_linewise[0] && a:shifted_pos[1] == head[1])
|
||||
call s:push(shift, a:shifted_pos, head, a:addition[0], a:indent[0], a:is_linewise[0])
|
||||
endif
|
||||
let a:shifted_pos[1:2] += shift[1:2]
|
||||
endif
|
||||
return a:shifted_pos
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:push2(shifted_pos, target, addition, indent, is_linewise) abort "{{{
|
||||
if a:shifted_pos != s:null_pos
|
||||
let shift = [0, 0, 0, 0]
|
||||
let head = a:target.head2
|
||||
|
||||
if a:is_linewise[1] && a:shifted_pos[1] > head[1]
|
||||
" lnum
|
||||
let shift[1] += 1
|
||||
endif
|
||||
|
||||
if s:lib.is_equal_or_ahead(a:shifted_pos, head)
|
||||
call s:push(shift, a:shifted_pos, head, a:addition[1], a:indent[1], a:is_linewise[1])
|
||||
endif
|
||||
let a:shifted_pos[1:2] += shift[1:2]
|
||||
endif
|
||||
return a:shifted_pos
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:push(shift, shifted_pos, head, addition, indent, is_linewise) abort "{{{
|
||||
let addition = split(a:addition, '\%(\n\|\r\|\r\n\)', 1)
|
||||
|
||||
" lnum
|
||||
let a:shift[1] += len(addition) - 1
|
||||
" column
|
||||
if !a:is_linewise && a:head[1] == a:shifted_pos[1]
|
||||
let a:shift[2] += a:indent + strlen(addition[-1])
|
||||
if len(addition) > 1
|
||||
let a:shift[2] -= a:head[2] - 1
|
||||
endif
|
||||
endif
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:pull1(shifted_pos, target, deletion, is_linewise) abort "{{{
|
||||
if a:shifted_pos != s:null_pos
|
||||
let shift = [0, 0, 0, 0]
|
||||
let head = a:target.head1
|
||||
let tail = a:target.tail1
|
||||
|
||||
" lnum
|
||||
if a:shifted_pos[1] > head[1]
|
||||
if a:shifted_pos[1] <= tail[1]
|
||||
let shift[1] -= a:shifted_pos[1] - head[1]
|
||||
else
|
||||
let shift[1] -= tail[1] - head[1]
|
||||
endif
|
||||
endif
|
||||
" column
|
||||
if s:lib.is_ahead(a:shifted_pos, head) && a:shifted_pos[1] <= tail[1]
|
||||
if s:lib.is_ahead(a:shifted_pos, tail)
|
||||
let shift[2] -= strlen(split(a:deletion[0], '\%(\n\|\r\|\r\n\)', 1)[-1])
|
||||
let shift[2] += head[1] != a:shifted_pos[1] ? head[2] - 1 : 0
|
||||
else
|
||||
let shift[2] -= a:shifted_pos[2]
|
||||
let shift[2] += head[2]
|
||||
endif
|
||||
endif
|
||||
|
||||
let a:shifted_pos[1] += shift[1]
|
||||
|
||||
" the case for linewise action
|
||||
if a:is_linewise[0]
|
||||
if a:shifted_pos[1] == head[1]
|
||||
" col
|
||||
let a:shifted_pos[2] = 0
|
||||
endif
|
||||
if a:shifted_pos[1] > head[1]
|
||||
" lnum
|
||||
let a:shifted_pos[1] -= 1
|
||||
endif
|
||||
endif
|
||||
|
||||
if a:shifted_pos[2] == 0
|
||||
let a:shifted_pos[2] = 1
|
||||
elseif a:shifted_pos[2] == s:constants('colmax')
|
||||
let a:shifted_pos[2] = col([a:shifted_pos[1], '$']) - 1
|
||||
let a:shifted_pos[2] += shift[2]
|
||||
else
|
||||
let a:shifted_pos[2] += shift[2]
|
||||
endif
|
||||
endif
|
||||
return a:shifted_pos
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:pull2(shifted_pos, target, deletion, is_linewise) abort "{{{
|
||||
if a:shifted_pos != s:null_pos
|
||||
let shift = [0, 0, 0, 0]
|
||||
let head = a:target.head2
|
||||
let tail = a:target.tail2
|
||||
|
||||
" lnum
|
||||
if a:shifted_pos[1] >= head[1]
|
||||
if a:shifted_pos[1] < tail[1]
|
||||
let shift[1] -= a:shifted_pos[1] - head[1]
|
||||
else
|
||||
let shift[1] -= tail[1] - head[1]
|
||||
endif
|
||||
endif
|
||||
" column
|
||||
if s:lib.is_equal_or_ahead(a:shifted_pos, head) && a:shifted_pos[1] <= tail[1]
|
||||
if s:lib.is_ahead(a:shifted_pos, tail)
|
||||
let shift[2] -= strlen(split(a:deletion[1], '\%(\n\|\r\|\r\n\)', 1)[-1])
|
||||
let shift[2] += head[1] != a:shifted_pos[1] ? head[2] - 1 : 0
|
||||
else
|
||||
let shift[2] -= a:shifted_pos[2] + 1
|
||||
let shift[2] += head[2]
|
||||
endif
|
||||
endif
|
||||
let a:shifted_pos[1:2] += shift[1:2]
|
||||
|
||||
" the case for linewise action
|
||||
if a:is_linewise[1]
|
||||
if a:shifted_pos[1] == head[1]
|
||||
" col
|
||||
let a:shifted_pos[2] = s:constants('colmax')
|
||||
endif
|
||||
if a:shifted_pos[1] >= head[1]
|
||||
" lnum
|
||||
let a:shifted_pos[1] -= 1
|
||||
endif
|
||||
endif
|
||||
endif
|
||||
return a:shifted_pos
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:added_tail(head, tail, linewise) abort "{{{
|
||||
if a:tail[1] > 1 && a:tail[2] == 1
|
||||
let lnum = a:tail[1] - 1
|
||||
let col = col([lnum, '$'])
|
||||
let tail = [a:tail[0], lnum, col, a:tail[3]]
|
||||
elseif a:linewise
|
||||
let tail = a:tail
|
||||
else
|
||||
let tail = s:lib.get_left_pos(a:tail)
|
||||
endif
|
||||
return tail
|
||||
endfunction
|
||||
"}}}
|
||||
|
||||
|
||||
" vim:set foldmethod=marker:
|
||||
" vim:set commentstring="%s:
|
||||
" vim:set ts=2 sts=2 sw=2:
|
||||
@ -0,0 +1,108 @@
|
||||
" common functions
|
||||
|
||||
" variables "{{{
|
||||
" null valiables
|
||||
let s:null_pos = [0, 0, 0, 0]
|
||||
"}}}
|
||||
|
||||
let s:lib = {}
|
||||
function! s:get_wider_region(head_edge, tail_edge) abort "{{{
|
||||
return [s:get_left_pos(a:head_edge), s:get_right_pos(a:tail_edge)]
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:get_left_pos(pos) abort "{{{
|
||||
call setpos('.', a:pos)
|
||||
normal! h
|
||||
return getpos('.')
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:get_right_pos(pos) abort "{{{
|
||||
call setpos('.', a:pos)
|
||||
normal! l
|
||||
return getpos('.')
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:c2p(coord) abort "{{{
|
||||
return [0] + a:coord + [0]
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:is_valid_2pos(pos) abort "{{{
|
||||
" NOTE: This function do not check the geometric relationships.
|
||||
" It should be checked by s:is_ahead or s:is_equal_or_ahead
|
||||
" separately.
|
||||
return a:pos.head != s:null_pos && a:pos.tail != s:null_pos
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:is_valid_4pos(pos) abort "{{{
|
||||
" NOTE: This function do not check the geometric relationships.
|
||||
" It should be checked by s:is_ahead or s:is_equal_or_ahead
|
||||
" separately.
|
||||
return a:pos.head1 != s:null_pos && a:pos.tail1 != s:null_pos
|
||||
\ && a:pos.head2 != s:null_pos && a:pos.tail2 != s:null_pos
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:is_ahead(pos1, pos2) abort "{{{
|
||||
return a:pos1[1] > a:pos2[1] || (a:pos1[1] == a:pos2[1] && a:pos1[2] > a:pos2[2])
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:is_equal_or_ahead(pos1, pos2) abort "{{{
|
||||
return a:pos1[1] > a:pos2[1] || (a:pos1[1] == a:pos2[1] && a:pos1[2] >= a:pos2[2])
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:is_in_between(pos, head, tail) abort "{{{
|
||||
return (a:pos != s:null_pos) && (a:head != s:null_pos) && (a:tail != s:null_pos)
|
||||
\ && ((a:pos[1] > a:head[1]) || ((a:pos[1] == a:head[1]) && (a:pos[2] >= a:head[2])))
|
||||
\ && ((a:pos[1] < a:tail[1]) || ((a:pos[1] == a:tail[1]) && (a:pos[2] <= a:tail[2])))
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:get_sandwich_option(name, default) abort "{{{
|
||||
if exists('g:operator#sandwich#' . a:name)
|
||||
return eval('g:operator#sandwich#' . a:name)
|
||||
endif
|
||||
if exists('g:sandwich#' . a:name)
|
||||
return eval('g:sandwich#' . a:name)
|
||||
endif
|
||||
return a:default
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:get_operator_option(name, default) abort "{{{
|
||||
return get(g:, 'operator#sandwich#' . a:name, a:default)
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:escape(string) abort "{{{
|
||||
return escape(a:string, '~"\.^$[]*')
|
||||
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([
|
||||
\ 'get_wider_region',
|
||||
\ 'get_left_pos',
|
||||
\ 'get_right_pos',
|
||||
\ 'c2p',
|
||||
\ 'is_valid_2pos',
|
||||
\ 'is_valid_4pos',
|
||||
\ 'is_ahead',
|
||||
\ 'is_equal_or_ahead',
|
||||
\ 'is_in_between',
|
||||
\ 'get_sandwich_option',
|
||||
\ 'get_operator_option',
|
||||
\ 'escape',
|
||||
\ ])
|
||||
lockvar! s:lib
|
||||
|
||||
function! operator#sandwich#lib#import() abort "{{{
|
||||
return s:lib
|
||||
endfunction
|
||||
"}}}
|
||||
|
||||
" vim:set foldmethod=marker:
|
||||
" vim:set commentstring="%s:
|
||||
" vim:set ts=2 sts=2 sw=2:
|
||||
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,507 @@
|
||||
" stuff object - managing a line on buffer
|
||||
|
||||
let s:lib = operator#sandwich#lib#import()
|
||||
|
||||
" variables "{{{
|
||||
" null valiables
|
||||
let s:null_coord = [0, 0]
|
||||
let s:null_pos = [0, 0, 0, 0]
|
||||
let s:null_2pos = {
|
||||
\ 'head': copy(s:null_pos),
|
||||
\ 'tail': copy(s:null_pos),
|
||||
\ }
|
||||
let s:null_4pos = {
|
||||
\ 'head1': copy(s:null_pos),
|
||||
\ 'tail1': copy(s:null_pos),
|
||||
\ 'head2': copy(s:null_pos),
|
||||
\ 'tail2': copy(s:null_pos),
|
||||
\ }
|
||||
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
|
||||
|
||||
let s:KEY_v = printf('%s(v)', s:SNR)
|
||||
|
||||
" types
|
||||
let s:type_list = type([])
|
||||
|
||||
" patchs
|
||||
if v:version > 704 || (v:version == 704 && has('patch237'))
|
||||
let s:has_patch_7_4_771 = has('patch-7.4.771')
|
||||
let s:has_patch_7_4_358 = has('patch-7.4.358')
|
||||
else
|
||||
let s:has_patch_7_4_771 = v:version == 704 && has('patch771')
|
||||
let s:has_patch_7_4_358 = v:version == 704 && has('patch358')
|
||||
endif
|
||||
"}}}
|
||||
|
||||
function! operator#sandwich#stuff#new() abort "{{{
|
||||
return deepcopy(s:stuff)
|
||||
endfunction
|
||||
"}}}
|
||||
|
||||
" s:stuff "{{{
|
||||
let s:stuff = {
|
||||
\ 'active' : 1,
|
||||
\ 'edges' : copy(s:null_2pos),
|
||||
\ 'target' : copy(s:null_4pos),
|
||||
\ 'acts' : [],
|
||||
\ 'added' : [],
|
||||
\ }
|
||||
"}}}
|
||||
function! s:stuff.initialize(count, cursor, modmark) dict abort "{{{
|
||||
let self.active = 1
|
||||
let self.acts = map(range(a:count), 'operator#sandwich#act#new()')
|
||||
let self.added = []
|
||||
for act in self.acts
|
||||
call act.initialize(a:cursor, a:modmark, self.added)
|
||||
endfor
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:stuff.set_target() dict abort "{{{
|
||||
if self.active
|
||||
let head = copy(self.edges.head)
|
||||
let tail = copy(self.edges.tail)
|
||||
let self.target.head1 = head
|
||||
let self.target.tail1 = head
|
||||
let self.target.head2 = tail
|
||||
let self.target.tail2 = tail
|
||||
endif
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:stuff.match(recipes, opt, ...) dict abort "{{{
|
||||
if !self.active
|
||||
return 0
|
||||
endif
|
||||
|
||||
let edges = self.edges
|
||||
let edges_saved = deepcopy(edges)
|
||||
let match_edges = get(a:000, 0, 1)
|
||||
if s:lib.is_valid_2pos(edges) && s:lib.is_ahead(edges.tail, edges.head)
|
||||
let edge_chars = ['', '']
|
||||
if self._match_recipes(a:recipes, a:opt) || (match_edges && self._match_edges(a:recipes, a:opt, edge_chars))
|
||||
" found!
|
||||
return 1
|
||||
else
|
||||
let [head_c, tail_c] = edge_chars
|
||||
if head_c =~# '\s' || tail_c =~# '\s'
|
||||
call self.skip_space()
|
||||
if s:lib.is_ahead(edges.head, edges.tail)
|
||||
" invalid edges after skip spaces
|
||||
let self.active = 0
|
||||
return 0
|
||||
else
|
||||
if self._match_recipes(a:recipes, a:opt, 1) || self._match_edges(a:recipes, a:opt, edge_chars)
|
||||
" found
|
||||
return 1
|
||||
endif
|
||||
endif
|
||||
endif
|
||||
endif
|
||||
let edges = edges_saved
|
||||
|
||||
" skip characters
|
||||
let target_list = []
|
||||
for candidate in a:recipes
|
||||
call a:opt.update('recipe_delete', candidate)
|
||||
if a:opt.of('skip_char') && has_key(candidate, 'buns')
|
||||
let head = edges.head
|
||||
let tail = edges.tail
|
||||
let opt_regex = a:opt.of('regex')
|
||||
let patterns = s:get_patterns(candidate, opt_regex)
|
||||
let target_list += [s:search_edges(head, tail, patterns)]
|
||||
endif
|
||||
endfor
|
||||
if filter(target_list, 's:lib.is_valid_4pos(v:val)') != []
|
||||
" found
|
||||
let self.target = s:shortest(target_list)
|
||||
return 1
|
||||
endif
|
||||
|
||||
call a:opt.clear('recipe_delete')
|
||||
endif
|
||||
let self.active = 0
|
||||
return 0
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:stuff._match_recipes(recipes, opt, ...) dict abort "{{{
|
||||
let is_space_skipped = get(a:000, 0, 0)
|
||||
|
||||
let found = 0
|
||||
for candidate in a:recipes
|
||||
call a:opt.update('recipe_delete', candidate)
|
||||
if !is_space_skipped || a:opt.of('skip_space')
|
||||
if has_key(candidate, 'buns')
|
||||
" search buns
|
||||
let patterns = s:get_patterns(candidate, a:opt.of('regex'))
|
||||
let target = s:check_edges(self.edges.head, self.edges.tail, patterns)
|
||||
elseif has_key(candidate, 'external')
|
||||
" get difference of external motion/textobject
|
||||
let target = s:check_textobj_diff(self.edges.head, self.edges.tail, candidate, a:opt.of('noremap', 'recipe_delete'))
|
||||
else
|
||||
let target = deepcopy(s:null_4pos)
|
||||
endif
|
||||
|
||||
if s:lib.is_valid_4pos(target)
|
||||
let found = 1
|
||||
let self.target = target
|
||||
break
|
||||
endif
|
||||
endif
|
||||
endfor
|
||||
return found
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:stuff._match_edges(recipes, opt, edge_chars) dict abort "{{{
|
||||
let head = self.edges.head
|
||||
let tail = self.edges.tail
|
||||
let head_c = s:get_cursorchar(head)
|
||||
let tail_c = s:get_cursorchar(tail)
|
||||
|
||||
let found = 0
|
||||
if head_c ==# tail_c
|
||||
" check duplicate with recipe
|
||||
for candidate in a:recipes
|
||||
call a:opt.update('recipe_delete', candidate)
|
||||
if has_key(candidate, 'buns') && !a:opt.of('regex')
|
||||
\ && candidate.buns[0] ==# head_c
|
||||
\ && candidate.buns[1] ==# tail_c
|
||||
" The pair has already checked by a recipe.
|
||||
return 0
|
||||
endif
|
||||
endfor
|
||||
|
||||
" both edges are matched
|
||||
call a:opt.clear('recipe_delete')
|
||||
if !(a:opt.of('skip_space') == 2 && head_c =~# '\s')
|
||||
let found = 1
|
||||
let self.target = {
|
||||
\ 'head1': head, 'tail1': head,
|
||||
\ 'head2': tail, 'tail2': tail,
|
||||
\ }
|
||||
endif
|
||||
endif
|
||||
|
||||
let a:edge_chars[0] = head_c
|
||||
let a:edge_chars[1] = tail_c
|
||||
return found
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:stuff.skip_space() dict abort "{{{
|
||||
if self.active
|
||||
call s:skip_space(self.edges.head, self.edges.tail)
|
||||
if s:lib.is_ahead(self.edges.head, self.edges.tail)
|
||||
let self.active = 0
|
||||
endif
|
||||
endif
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:stuff.hi_list(place, linewise) dict abort "{{{
|
||||
if !self.active
|
||||
return []
|
||||
endif
|
||||
|
||||
let orderlist = []
|
||||
if a:place ==# 'target'
|
||||
let orderlist += [[self.target, [a:linewise, a:linewise]]]
|
||||
elseif a:place ==# 'added'
|
||||
for added in self.added
|
||||
let orderlist += [[added, added.linewise]]
|
||||
endfor
|
||||
elseif a:place ==# 'stuff'
|
||||
let stuff = {'head1': self.edges.head, 'tail1': self.edges.tail, 'head2': copy(s:null_pos), 'tail2': copy(s:null_pos)}
|
||||
let orderlist += [[stuff, [0, 0]]]
|
||||
endif
|
||||
return orderlist
|
||||
endfunction
|
||||
"}}}
|
||||
|
||||
" private functions
|
||||
function! s:check_edges(head, tail, patterns) abort "{{{
|
||||
if a:patterns[0] ==# '' || a:patterns[1] ==# '' | return s:null_4pos | endif
|
||||
|
||||
call setpos('.', a:head)
|
||||
let head1 = searchpos(a:patterns[0], 'c', a:tail[1])
|
||||
|
||||
if head1 != a:head[1:2] | return s:null_4pos | endif
|
||||
|
||||
call setpos('.', a:tail)
|
||||
let tail2 = s:searchpos_bce(a:tail, a:patterns[1], a:head[1])
|
||||
|
||||
if tail2 != a:tail[1:2] | return s:null_4pos | endif
|
||||
|
||||
let head2 = searchpos(a:patterns[1], 'bc', a:head[1])
|
||||
call setpos('.', a:head)
|
||||
let tail1 = searchpos(a:patterns[0], 'ce', a:tail[1])
|
||||
|
||||
if head1 == s:null_coord || tail1 == s:null_coord
|
||||
\ || head2 == s:null_coord || tail2 == s:null_coord
|
||||
\ || s:lib.is_equal_or_ahead(s:lib.c2p(tail1), s:lib.c2p(head2))
|
||||
return s:null_4pos
|
||||
endif
|
||||
|
||||
let target = {
|
||||
\ 'head1': head1, 'tail1': tail1,
|
||||
\ 'head2': head2, 'tail2': tail2,
|
||||
\ }
|
||||
return map(target, 's:lib.c2p(v:val)')
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:search_edges(head, tail, patterns) abort "{{{
|
||||
if a:patterns[0] ==# '' || a:patterns[1] ==# '' | return s:null_4pos | endif
|
||||
|
||||
call setpos('.', a:head)
|
||||
let head1 = searchpos(a:patterns[0], 'c', a:tail[1])
|
||||
|
||||
call setpos('.', a:tail)
|
||||
let tail2 = s:searchpos_bce(a:tail, a:patterns[1], a:head[1])
|
||||
|
||||
if head1 == s:null_coord || tail2 == s:null_coord
|
||||
\ || s:lib.is_equal_or_ahead(s:lib.c2p(head1), s:lib.c2p(tail2))
|
||||
return s:null_4pos
|
||||
endif
|
||||
|
||||
let head2 = searchpos(a:patterns[1], 'bc', head1[0])
|
||||
call setpos('.', s:lib.c2p(head1))
|
||||
let tail1 = searchpos(a:patterns[0], 'ce', head2[0])
|
||||
|
||||
if tail1 == s:null_coord || head2 == s:null_coord
|
||||
\ || s:lib.is_ahead(s:lib.c2p(head1), s:lib.c2p(tail1))
|
||||
\ || s:lib.is_ahead(s:lib.c2p(head2), s:lib.c2p(tail2))
|
||||
\ || s:lib.is_equal_or_ahead(s:lib.c2p(tail1), s:lib.c2p(head2))
|
||||
return s:null_4pos
|
||||
endif
|
||||
|
||||
let target = {
|
||||
\ 'head1': head1, 'tail1': tail1,
|
||||
\ 'head2': head2, 'tail2': tail2,
|
||||
\ }
|
||||
return map(target, 's:lib.c2p(v:val)')
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:check_textobj_diff(head, tail, candidate, opt_noremap) abort "{{{
|
||||
let target = deepcopy(s:null_4pos)
|
||||
if has_key(a:candidate, 'excursus')
|
||||
let coord = a:candidate.excursus.coord
|
||||
let target.head1 = s:lib.c2p(coord.head)
|
||||
let target.tail1 = s:lib.get_left_pos(s:lib.c2p(coord.inner_head))
|
||||
let target.head2 = s:lib.get_right_pos(s:lib.c2p(coord.inner_tail))
|
||||
let target.tail2 = s:lib.c2p(coord.tail)
|
||||
|
||||
if target.head1 == a:head && target.tail2 == a:tail
|
||||
\ && target.tail1 != s:null_pos && target.head2 != s:null_pos
|
||||
\ && s:lib.is_equal_or_ahead(target.tail1, target.head1)
|
||||
\ && s:lib.is_equal_or_ahead(target.tail2, target.head2)
|
||||
return target
|
||||
endif
|
||||
endif
|
||||
|
||||
let [textobj_i, textobj_a] = a:candidate.external
|
||||
let [visual_head, visual_tail] = [getpos("'<"), getpos("'>")]
|
||||
let visualmode = visualmode()
|
||||
if a:opt_noremap
|
||||
let cmd = 'silent! normal!'
|
||||
let v = 'v'
|
||||
else
|
||||
let cmd = 'silent! normal'
|
||||
let v = s:KEY_v
|
||||
endif
|
||||
|
||||
let order_list = [[1, a:head], [1, a:tail]]
|
||||
if has_key(a:candidate, 'excursus')
|
||||
let order_list = [[a:candidate.excursus.count, a:candidate.excursus.cursor]] + order_list
|
||||
endif
|
||||
|
||||
let found = 0
|
||||
for [l:count, cursor] in order_list
|
||||
" get outer positions
|
||||
let [target.head1, target.tail2, motionwise_a] = s:get_textobj_region(cursor, cmd, v, l:count, textobj_a)
|
||||
|
||||
" get inner positions
|
||||
let [target.tail1, target.head2, motionwise_i] = s:get_textobj_region(cursor, cmd, v, l:count, textobj_i)
|
||||
if motionwise_i ==# "\<C-v>"
|
||||
normal! gv
|
||||
if getpos('.')[1] == target.head2[1]
|
||||
normal! O
|
||||
let target.tail1 = getpos('.')
|
||||
normal! o
|
||||
let target.head2 = getpos('.')
|
||||
else
|
||||
normal! O
|
||||
let target.head2 = getpos('.')
|
||||
normal! o
|
||||
let target.tail1 = getpos('.')
|
||||
endif
|
||||
execute "normal! \<Esc>"
|
||||
endif
|
||||
|
||||
" check validity
|
||||
if target.head1 == a:head && target.tail2 == a:tail
|
||||
\ && target.tail1 != s:null_pos && target.head2 != s:null_pos
|
||||
\ && s:lib.is_ahead(target.tail1, target.head1)
|
||||
\ && s:lib.is_ahead(target.tail2, target.head2)
|
||||
let [target.tail1, target.head2] = s:lib.get_wider_region(target.tail1, target.head2)
|
||||
let found = 1
|
||||
break
|
||||
endif
|
||||
endfor
|
||||
|
||||
" restore visualmode
|
||||
if visualmode ==# ''
|
||||
call visualmode(1)
|
||||
else
|
||||
execute 'normal! ' . visualmode
|
||||
execute 'normal! ' . "\<Esc>"
|
||||
endif
|
||||
" restore marks
|
||||
call setpos("'<", visual_head)
|
||||
call setpos("'>", visual_tail)
|
||||
|
||||
if found
|
||||
return target
|
||||
else
|
||||
return s:null_4pos
|
||||
endif
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:get_textobj_region(cursor, cmd, visualmode, count, key_seq) abort "{{{
|
||||
call setpos('.', a:cursor)
|
||||
execute printf('%s %s%d%s', a:cmd, a:visualmode, 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("'<"), getpos("'>")]
|
||||
" NOTE: V never comes for v. Thus if head == tail == self.cursor, then
|
||||
" it is failed.
|
||||
if head == a:cursor && tail == a:cursor
|
||||
let [head, tail] = [copy(s:null_pos), copy(s:null_pos)]
|
||||
elseif visualmode ==# 'V'
|
||||
let tail[2] = col([tail[1], '$'])
|
||||
endif
|
||||
return [head, tail, visualmode]
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:get_patterns(candidate, opt_regex) abort "{{{
|
||||
let patterns = deepcopy(a:candidate.buns)
|
||||
if !a:opt_regex
|
||||
let patterns = map(patterns, 's:lib.escape(v:val)')
|
||||
endif
|
||||
|
||||
" substitute a break "\n" to a regular expression pattern '\n'
|
||||
let patterns = map(patterns, 'substitute(v:val, ''\n'', ''\\n'', ''g'')')
|
||||
return patterns
|
||||
endfunction
|
||||
"}}}
|
||||
" function! s:searchpos_bce(curpos, pattern, stopline) "{{{
|
||||
if s:has_patch_7_4_771
|
||||
function! s:searchpos_bce(curpos, pattern, stopline) abort
|
||||
return searchpos(a:pattern, 'bce', a:stopline)
|
||||
endfunction
|
||||
else
|
||||
" workaround for unicode string (because of a bug of vim)
|
||||
" If the cursor is on a unicode character(uc), searchpos(uc, 'bce', stopline) always returns [0, 0],
|
||||
" though searchpos(uc, 'bce') returns a correct value.
|
||||
function! s:searchpos_bce(curpos, pattern, stopline) abort
|
||||
if a:curpos[1] == line('$') && a:curpos[2] == col([line('$'), '$'])
|
||||
silent! normal! h
|
||||
return searchpos(a:pattern, 'e', a:stopline)
|
||||
else
|
||||
silent! normal! l
|
||||
return searchpos(a:pattern, 'be', a:stopline)
|
||||
endif
|
||||
endfunction
|
||||
endif
|
||||
"}}}
|
||||
function! s:get_cursorchar(pos) abort "{{{
|
||||
let reg = ['"', getreg('"'), getregtype('"')]
|
||||
try
|
||||
call setpos('.', a:pos)
|
||||
silent noautocmd normal! yl
|
||||
let c = @@
|
||||
finally
|
||||
call call('setreg', reg)
|
||||
endtry
|
||||
return c
|
||||
endfunction
|
||||
"}}}
|
||||
" function! s:shortest(list) abort "{{{
|
||||
if s:has_patch_7_4_358
|
||||
function! s:shortest(list) abort
|
||||
call map(a:list, '[v:val, s:get_buf_length(v:val.head1, v:val.tail2)]')
|
||||
call sort(a:list, 's:compare_buf_length')
|
||||
return a:list[0][0]
|
||||
endfunction
|
||||
|
||||
function! s:compare_buf_length(i1, i2) abort
|
||||
return a:i2[1] - a:i1[1]
|
||||
endfunction
|
||||
else
|
||||
function! s:shortest(list) abort
|
||||
call map(a:list, '[v:val, s:get_buf_length(v:val.head1, v:val.tail2)]')
|
||||
let len = len(a:list)
|
||||
let min = len - 1
|
||||
if len - 2 >= 0
|
||||
for i in range(len - 2, 0, -1)
|
||||
if a:list[min][1] >= a:list[i][1]
|
||||
let min = i
|
||||
endif
|
||||
endfor
|
||||
endif
|
||||
return a:list[min][0]
|
||||
endfunction
|
||||
endif
|
||||
"}}}
|
||||
function! s:get_buf_length(start, end) abort "{{{
|
||||
if a:start[1] == a:end[1]
|
||||
let len = a:end[2] - a:start[2] + 1
|
||||
else
|
||||
let length_list = map(getline(a:start[1], a:end[1]), 'len(v:val) + 1')
|
||||
let idx = 0
|
||||
let accumm_length = 0
|
||||
let accumm_list = [0]
|
||||
for length in length_list[1:]
|
||||
let accumm_length = accumm_length + length_list[idx]
|
||||
let accumm_list += [accumm_length]
|
||||
let idx += 1
|
||||
endfor
|
||||
let len = accumm_list[a:end[1] - a:start[1]] + a:end[2] - a:start[2] + 1
|
||||
endif
|
||||
return len
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:skip_space(head, tail) abort "{{{
|
||||
" NOTE: This function is destructive, directly update a:head and a:tail.
|
||||
call setpos('.', a:head)
|
||||
if a:head[2] == col([a:head[1], '$'])
|
||||
" if the cursor is on a line breaking, it should not be skipped.
|
||||
let head = a:head
|
||||
else
|
||||
let head = s:lib.c2p(searchpos('\_S', 'c', a:tail[1]))
|
||||
endif
|
||||
|
||||
call setpos('.', a:tail)
|
||||
if a:tail[2] == 1
|
||||
let tail = a:tail
|
||||
else
|
||||
let tail = s:lib.c2p(searchpos('\_S', 'bc', a:head[1]))
|
||||
endif
|
||||
|
||||
if head != s:null_pos && tail != s:null_pos && s:lib.is_equal_or_ahead(tail, head)
|
||||
let a:head[0:3] = head[0:3]
|
||||
let a:tail[0:3] = tail[0:3]
|
||||
endif
|
||||
endfunction
|
||||
"}}}
|
||||
|
||||
|
||||
|
||||
" vim:set foldmethod=marker:
|
||||
" vim:set commentstring="%s:
|
||||
" vim:set ts=2 sts=2 sw=2:
|
||||
@ -0,0 +1,52 @@
|
||||
" prepare recipes
|
||||
function! sandwich#get_recipes() abort "{{{
|
||||
if exists('b:sandwich_recipes')
|
||||
let recipes = b:sandwich_recipes
|
||||
elseif exists('g:sandwich#recipes')
|
||||
let recipes = g:sandwich#recipes
|
||||
else
|
||||
let recipes = g:sandwich#default_recipes
|
||||
endif
|
||||
return deepcopy(recipes)
|
||||
endfunction
|
||||
"}}}
|
||||
if exists('g:sandwich#default_recipes')
|
||||
unlockvar! g:sandwich#default_recipes
|
||||
endif
|
||||
let g:sandwich#default_recipes = [
|
||||
\ {'buns': ['\s\+', '\s\+'], 'regex': 1, 'kind': ['delete', 'replace', 'query'], 'input': [' ']},
|
||||
\ {'buns': ['', ''], 'action': ['add'], 'motionwise': ['line'], 'linewise': 1, 'input': ["\<CR>"]},
|
||||
\ {'buns': ['^$', '^$'], 'regex': 1, 'linewise': 1, 'input': ["\<CR>"]},
|
||||
\ {'buns': ['<', '>'], 'expand_range': 0},
|
||||
\ {'buns': ['"', '"'], 'quoteescape': 1, 'expand_range': 0, 'nesting': 0, 'linewise': 0},
|
||||
\ {'buns': ["'", "'"], 'quoteescape': 1, 'expand_range': 0, 'nesting': 0, 'linewise': 0},
|
||||
\ {'buns': ["`", "`"], 'quoteescape': 1, 'expand_range': 0, 'nesting': 0, 'linewise': 0},
|
||||
\ {'buns': ['{', '}'], 'nesting': 1, 'skip_break': 1},
|
||||
\ {'buns': ['[', ']'], 'nesting': 1},
|
||||
\ {'buns': ['(', ')'], 'nesting': 1},
|
||||
\ {'buns': 'sandwich#magicchar#t#tag()', 'listexpr': 1, 'kind': ['add'], 'action': ['add'], 'input': ['t', 'T']},
|
||||
\ {'buns': 'sandwich#magicchar#t#tag()', 'listexpr': 1, 'kind': ['replace'], 'action': ['add'], 'input': ['T']},
|
||||
\ {'buns': 'sandwich#magicchar#t#tagname()', 'listexpr': 1, 'kind': ['replace'], 'action': ['add'], 'input': ['t']},
|
||||
\ {'external': ["\<Plug>(textobj-sandwich-tag-i)", "\<Plug>(textobj-sandwich-tag-a)"], 'noremap' : 0, 'kind' : ['delete', 'textobj'], 'expr_filter': ['operator#sandwich#kind() !=# "replace"'], 'linewise': 1, 'input': ['t', 'T']},
|
||||
\ {'external': ["\<Plug>(textobj-sandwich-tag-i)", "\<Plug>(textobj-sandwich-tag-a)"], 'noremap' : 0, 'kind' : ['replace', 'query'], 'expr_filter': ['operator#sandwich#kind() ==# "replace"'], 'input': ['T']},
|
||||
\ {'external': ["\<Plug>(textobj-sandwich-tagname-i)", "\<Plug>(textobj-sandwich-tagname-a)"], 'noremap' : 0, 'kind' : ['replace', 'textobj'], 'expr_filter': ['operator#sandwich#kind() ==# "replace"'], 'input': ['t']},
|
||||
\ {'buns': ['sandwich#magicchar#f#fname()', '")"'], 'kind': ['add', 'replace'], 'action': ['add'], 'expr': 1, 'input': ['f']},
|
||||
\ {'external': ["\<Plug>(textobj-sandwich-function-ip)", "\<Plug>(textobj-sandwich-function-i)"], 'noremap': 0, 'kind': ['delete', 'replace', 'query'], 'input': ['f']},
|
||||
\ {'external': ["\<Plug>(textobj-sandwich-function-ap)", "\<Plug>(textobj-sandwich-function-a)"], 'noremap': 0, 'kind': ['delete', 'replace', 'query'], 'input': ['F']},
|
||||
\ {'buns': 'sandwich#magicchar#i#input("operator")', 'kind': ['add', 'replace'], 'action': ['add'], 'listexpr': 1, 'input': ['i']},
|
||||
\ {'buns': 'sandwich#magicchar#i#input("textobj", 1)', 'kind': ['delete', 'replace', 'query'], 'listexpr': 1, 'input': ['i']},
|
||||
\ {'buns': 'sandwich#magicchar#i#lastinput("operator", 1)', 'kind': ['add', 'replace'], 'action': ['add'], 'listexpr': 1, 'input': ['I']},
|
||||
\ {'buns': 'sandwich#magicchar#i#lastinput("textobj")', 'kind': ['delete', 'replace', 'query'], 'listexpr': 1, 'input': ['I']},
|
||||
\ ]
|
||||
lockvar! g:sandwich#default_recipes
|
||||
|
||||
let g:sandwich#jsx_filetypes = [
|
||||
\ 'javascript.jsx',
|
||||
\ 'typescript.tsx',
|
||||
\ 'javascriptreact',
|
||||
\ 'typescriptreact'
|
||||
\ ]
|
||||
|
||||
" vim:set foldmethod=marker:
|
||||
" vim:set commentstring="%s:
|
||||
" vim:set ts=2 sts=2 sw=2:
|
||||
@ -0,0 +1,60 @@
|
||||
" clock object - measuring elapsed time in a operation
|
||||
|
||||
" variables "{{{
|
||||
" features
|
||||
let s:has_reltime_and_float = has('reltime') && has('float')
|
||||
"}}}
|
||||
|
||||
function! sandwich#clock#new() abort "{{{
|
||||
return deepcopy(s:clock)
|
||||
endfunction
|
||||
"}}}
|
||||
|
||||
" s:clock "{{{
|
||||
let s:clock = {
|
||||
\ 'started' : 0,
|
||||
\ 'paused' : 0,
|
||||
\ 'zerotime': reltime(),
|
||||
\ 'stoptime': reltime(),
|
||||
\ 'losstime': 0,
|
||||
\ }
|
||||
"}}}
|
||||
function! s:clock.start() dict abort "{{{
|
||||
if self.started
|
||||
if self.paused
|
||||
let self.losstime += str2float(reltimestr(reltime(self.stoptime)))
|
||||
let self.paused = 0
|
||||
endif
|
||||
else
|
||||
if s:has_reltime_and_float
|
||||
let self.zerotime = reltime()
|
||||
let self.started = 1
|
||||
endif
|
||||
endif
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:clock.pause() dict abort "{{{
|
||||
let self.stoptime = reltime()
|
||||
let self.paused = 1
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:clock.elapsed() dict abort "{{{
|
||||
if self.started
|
||||
let total = str2float(reltimestr(reltime(self.zerotime)))
|
||||
return floor((total - self.losstime)*1000)
|
||||
else
|
||||
return 0
|
||||
endif
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:clock.stop() dict abort "{{{
|
||||
let self.started = 0
|
||||
let self.paused = 0
|
||||
let self.losstime = 0
|
||||
endfunction
|
||||
"}}}
|
||||
|
||||
|
||||
" vim:set foldmethod=marker:
|
||||
" vim:set commentstring="%s:
|
||||
" vim:set ts=2 sts=2 sw=2:
|
||||
@ -0,0 +1,42 @@
|
||||
" constants - storing essential constants
|
||||
|
||||
" variables "{{{
|
||||
" types
|
||||
let s:type_list = type([])
|
||||
"}}}
|
||||
|
||||
function! sandwich#constants#get(name) abort "{{{
|
||||
return type(a:name) == s:type_list
|
||||
\ ? map(copy(a:name), 's:constants[v:val]()')
|
||||
\ : s:constants[a:name]()
|
||||
endfunction
|
||||
"}}}
|
||||
|
||||
" s:constants "{{{
|
||||
let s:constants = {}
|
||||
"}}}
|
||||
" The maximum number of columns "{{{
|
||||
function! s:constants.colmax() dict abort
|
||||
return s:colmax_obtained ? s:colmax : s:colmax()
|
||||
endfunction
|
||||
|
||||
let s:colmax = 2147483647 " default value in many cases
|
||||
let s:colmax_obtained = 0
|
||||
function! s:colmax() abort
|
||||
let view = winsaveview()
|
||||
try
|
||||
normal! $
|
||||
let colmax = winsaveview().curswant
|
||||
call winrestview(view)
|
||||
let s:colmax_obtained = 1
|
||||
catch
|
||||
let colmax = s:colmax
|
||||
let s:colmax_obtained = 0
|
||||
endtry
|
||||
return colmax
|
||||
endfunction
|
||||
"}}}
|
||||
|
||||
" vim:set foldmethod=marker:
|
||||
" vim:set commentstring="%s:
|
||||
" vim:set ts=2 sts=2 sw=2:
|
||||
@ -0,0 +1,34 @@
|
||||
let g:sandwich#filetype#tex#environments = get(g:, 'sandwich#filetype#tex#environments', [
|
||||
\ 'array', 'center', 'description', 'enumerate', 'eqnarray', 'equation',
|
||||
\ 'equation*', 'figure', 'flushleft', 'flushright', 'itemize', 'list',
|
||||
\ 'minipage', 'picture', 'quotation', 'quote', 'tabbing', 'table',
|
||||
\ 'tabular', 'tabular*', 'thebibliography', 'theorem', 'titlepage',
|
||||
\ 'verbatim', 'verse'
|
||||
\ ])
|
||||
|
||||
function! sandwich#filetype#tex#EnvCompl(argread, cmdline, cursorpos) abort
|
||||
let n = strlen(a:argread)
|
||||
if exists('b:sandwich#filetype#tex#environments')
|
||||
let list = copy(b:sandwich#filetype#tex#environments)
|
||||
else
|
||||
let list = copy(g:sandwich#filetype#tex#environments)
|
||||
endif
|
||||
if n > 0
|
||||
let list = filter(list, 'v:val[: n-1] ==# a:argread')
|
||||
endif
|
||||
return list
|
||||
endfunction
|
||||
|
||||
function! sandwich#filetype#tex#EnvInput() abort
|
||||
echohl MoreMsg
|
||||
let env = input('Environment-name: ', '', 'customlist,sandwich#filetype#tex#EnvCompl')
|
||||
echohl NONE
|
||||
return [printf('\begin{%s}', env), printf('\end{%s}', env)]
|
||||
endfunction
|
||||
|
||||
function! sandwich#filetype#tex#CmdInput() abort
|
||||
echohl MoreMsg
|
||||
let cmd = input('Command: ', '')
|
||||
echohl NONE
|
||||
return [printf('\%s{', cmd), '}']
|
||||
endfunction
|
||||
@ -0,0 +1,514 @@
|
||||
" highlight object - managing highlight on a buffer
|
||||
|
||||
" variables "{{{
|
||||
" null valiables
|
||||
let s:null_pos = [0, 0, 0, 0]
|
||||
|
||||
" types
|
||||
let s:type_list = type([])
|
||||
|
||||
" patchs
|
||||
if v:version > 704 || (v:version == 704 && has('patch237'))
|
||||
let s:has_patch_7_4_362 = has('patch-7.4.362')
|
||||
let s:has_patch_7_4_392 = has('patch-7.4.392')
|
||||
else
|
||||
let s:has_patch_7_4_362 = v:version == 704 && has('patch362')
|
||||
let s:has_patch_7_4_392 = v:version == 704 && has('patch392')
|
||||
endif
|
||||
|
||||
" features
|
||||
let s:has_gui_running = has('gui_running')
|
||||
let s:has_window_ID = exists('*win_getid')
|
||||
|
||||
" SID
|
||||
function! s:SID() abort
|
||||
return matchstr(expand('<sfile>'), '<SNR>\zs\d\+\ze_SID$')
|
||||
endfunction
|
||||
let s:SID = printf("\<SNR>%s_", s:SID())
|
||||
delfunction s:SID
|
||||
"}}}
|
||||
|
||||
function! sandwich#highlight#new() abort "{{{
|
||||
return deepcopy(s:highlight)
|
||||
endfunction
|
||||
"}}}
|
||||
|
||||
" s:highlight "{{{
|
||||
let s:highlight = {
|
||||
\ 'status': 0,
|
||||
\ 'group' : '',
|
||||
\ 'id' : [],
|
||||
\ 'order_list': [],
|
||||
\ 'region': {},
|
||||
\ 'linewise': 0,
|
||||
\ 'bufnr': 0,
|
||||
\ 'winid': 0,
|
||||
\ }
|
||||
"}}}
|
||||
function! s:highlight.initialize() dict abort "{{{
|
||||
call self.quench()
|
||||
let self.status = 0
|
||||
let self.group = ''
|
||||
let self.id = []
|
||||
let self.order_list = []
|
||||
let self.region = {}
|
||||
let self.linewise = 0
|
||||
let self.bufnr = 0
|
||||
let self.winid = 0
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:highlight.order(target, linewise) dict abort "{{{
|
||||
let order = []
|
||||
let order_list = []
|
||||
for [head, tail, linewise] in [[a:target.head1, a:target.tail1, a:linewise[0]],
|
||||
\ [a:target.head2, a:target.tail2, a:linewise[1]]]
|
||||
if linewise
|
||||
call s:highlight_order_linewise(order_list, order, head, tail)
|
||||
else
|
||||
call s:highlight_order_charwise(order_list, order, head, tail)
|
||||
endif
|
||||
endfor
|
||||
if order != []
|
||||
call add(order_list, order)
|
||||
endif
|
||||
let self.order_list += order_list
|
||||
let self.region = deepcopy(a:target)
|
||||
let self.linewise = a:linewise
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:highlight.show(...) dict abort "{{{
|
||||
if self.order_list == []
|
||||
return 0
|
||||
endif
|
||||
|
||||
if a:0 < 1
|
||||
if self.group ==# ''
|
||||
return 0
|
||||
else
|
||||
let hi_group = self.group
|
||||
endif
|
||||
else
|
||||
let hi_group = a:1
|
||||
endif
|
||||
|
||||
if self.status
|
||||
if hi_group ==# self.group
|
||||
return 0
|
||||
else
|
||||
call self.quench()
|
||||
endif
|
||||
endif
|
||||
|
||||
for order in self.order_list
|
||||
let self.id += s:matchaddpos(hi_group, order)
|
||||
endfor
|
||||
call filter(self.id, 'v:val > 0')
|
||||
let self.status = 1
|
||||
let self.group = hi_group
|
||||
let self.bufnr = bufnr('%')
|
||||
if s:has_window_ID
|
||||
let self.winid = win_getid()
|
||||
endif
|
||||
return 1
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:highlight.quench() dict abort "{{{
|
||||
if !self.status
|
||||
return 0
|
||||
endif
|
||||
|
||||
let tabnr = tabpagenr()
|
||||
let winnr = winnr()
|
||||
let view = winsaveview()
|
||||
if self.highlighted_window()
|
||||
call s:matchdelete_all(self.id)
|
||||
let self.status = 0
|
||||
return 1
|
||||
endif
|
||||
|
||||
if s:is_in_cmdline_window() || s:is_in_popup_terminal_window()
|
||||
let s:paused += [self]
|
||||
augroup sandwich-pause-quenching
|
||||
autocmd!
|
||||
autocmd WinEnter * call s:got_out_of_cmdwindow()
|
||||
augroup END
|
||||
return 0
|
||||
endif
|
||||
|
||||
if self.goto_highlighted_window()
|
||||
call s:matchdelete_all(self.id)
|
||||
else
|
||||
call filter(self.id, 0)
|
||||
endif
|
||||
let self.status = 0
|
||||
call s:goto_window(winnr, tabnr, view)
|
||||
return 1
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:highlight.quench_timer(time) dict abort "{{{
|
||||
let id = timer_start(a:time, s:SID . 'quench')
|
||||
let s:quench_table[string(id)] = self
|
||||
" this is called when user gets control again
|
||||
call timer_start(1, {-> s:set_autocmds(id)})
|
||||
return id
|
||||
endfunction
|
||||
"}}}
|
||||
" function! s:highlight.highlighted_window() dict abort "{{{
|
||||
if s:has_window_ID
|
||||
function! s:highlight.highlighted_window() dict abort
|
||||
return self.winid == win_getid()
|
||||
endfunction
|
||||
else
|
||||
function! s:highlight.highlighted_window() dict abort
|
||||
if self.id == []
|
||||
return 0
|
||||
endif
|
||||
|
||||
let id = self.id[0]
|
||||
return filter(getmatches(), 'v:val.id == id') != [] ? 1 : 0
|
||||
endfunction
|
||||
endif
|
||||
"}}}
|
||||
" function! s:highlight.goto_highlighted_window() dict abort "{{{
|
||||
if s:has_window_ID
|
||||
function! s:highlight.goto_highlighted_window() dict abort
|
||||
noautocmd let reached = win_gotoid(self.winid)
|
||||
return reached
|
||||
endfunction
|
||||
else
|
||||
function! s:highlight.goto_highlighted_window() dict abort
|
||||
return s:search_highlighted_windows(self.id, tabpagenr()) != [0, 0]
|
||||
endfunction
|
||||
endif
|
||||
"}}}
|
||||
|
||||
" for delayed quenching "{{{
|
||||
let s:quench_table = {}
|
||||
let s:paused = []
|
||||
function! s:quench(id) abort "{{{
|
||||
let options = s:shift_options()
|
||||
let highlight = s:get(a:id)
|
||||
try
|
||||
if highlight != {}
|
||||
call highlight.quench()
|
||||
endif
|
||||
catch /^Vim\%((\a\+)\)\=:E523/
|
||||
" NOTE: For "textlock"
|
||||
if highlight != {}
|
||||
call highlight.quench_timer(50)
|
||||
endif
|
||||
return 1
|
||||
finally
|
||||
unlet s:quench_table[a:id]
|
||||
call timer_stop(a:id)
|
||||
call s:clear_autocmds()
|
||||
call s:restore_options(options)
|
||||
endtry
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:get(id) abort "{{{
|
||||
return get(s:quench_table, string(a:id), {})
|
||||
endfunction
|
||||
"}}}
|
||||
function! sandwich#highlight#cancel(...) abort "{{{
|
||||
if a:0 > 0
|
||||
let id_list = type(a:1) == s:type_list ? a:1 : a:000
|
||||
else
|
||||
let id_list = map(keys(s:quench_table), 'str2nr(v:val)')
|
||||
endif
|
||||
|
||||
for id in id_list
|
||||
call s:quench(id)
|
||||
endfor
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:quench_paused(...) abort "{{{
|
||||
if s:is_in_cmdline_window() || s:is_in_popup_terminal_window()
|
||||
return
|
||||
endif
|
||||
|
||||
augroup sandwich-pause-quenching
|
||||
autocmd!
|
||||
augroup END
|
||||
|
||||
for highlight in s:paused
|
||||
call highlight.quench()
|
||||
endfor
|
||||
let s:paused = []
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:got_out_of_cmdwindow() abort "{{{
|
||||
augroup sandwich-pause-quenching
|
||||
autocmd!
|
||||
autocmd CursorMoved * call s:quench_paused()
|
||||
augroup END
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:set_autocmds(id) abort "{{{
|
||||
augroup sandwich-highlight
|
||||
autocmd!
|
||||
execute printf('autocmd TextChanged,InsertEnter,BufUnload <buffer> call s:cancel_highlight(%s)', a:id)
|
||||
execute printf('autocmd BufEnter * call s:switch_highlight(%s)', a:id)
|
||||
augroup END
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:clear_autocmds() abort "{{{
|
||||
augroup sandwich-highlight
|
||||
autocmd!
|
||||
augroup END
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:cancel_highlight(id) abort "{{{
|
||||
call s:quench(a:id)
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:switch_highlight(id) abort "{{{
|
||||
let highlight = s:get(a:id)
|
||||
if highlight == {} || !highlight.highlighted_window()
|
||||
return
|
||||
endif
|
||||
|
||||
if highlight.bufnr == bufnr('%')
|
||||
call highlight.show()
|
||||
else
|
||||
call highlight.quench()
|
||||
endif
|
||||
endfunction
|
||||
"}}}
|
||||
"}}}
|
||||
|
||||
" private functions
|
||||
function! s:highlight_order_charwise(order_list, order, head, tail) abort "{{{
|
||||
let n = len(a:order)
|
||||
if a:head != s:null_pos && a:tail != s:null_pos && s:is_equal_or_ahead(a:tail, a:head)
|
||||
if a:head[1] == a:tail[1]
|
||||
call add(a:order, a:head[1:2] + [a:tail[2] - a:head[2] + 1])
|
||||
let n += 1
|
||||
else
|
||||
for lnum in range(a:head[1], a:tail[1])
|
||||
if lnum == a:head[1]
|
||||
call add(a:order, a:head[1:2] + [col([a:head[1], '$']) - a:head[2] + 1])
|
||||
elseif lnum == a:tail[1]
|
||||
call add(a:order, [a:tail[1], 1] + [a:tail[2]])
|
||||
else
|
||||
call add(a:order, [lnum])
|
||||
endif
|
||||
|
||||
if n == 7
|
||||
call add(a:order_list, deepcopy(a:order))
|
||||
call filter(a:order, 0)
|
||||
let n = 0
|
||||
else
|
||||
let n += 1
|
||||
endif
|
||||
endfor
|
||||
endif
|
||||
endif
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:highlight_order_linewise(order_list, order, head, tail) abort "{{{
|
||||
let n = len(a:order)
|
||||
if a:head != s:null_pos && a:tail != s:null_pos && a:head[1] <= a:tail[1]
|
||||
for lnum in range(a:head[1], a:tail[1])
|
||||
call add(a:order, [lnum])
|
||||
if n == 7
|
||||
call add(a:order_list, deepcopy(a:order))
|
||||
call filter(a:order, 0)
|
||||
let n = 0
|
||||
else
|
||||
let n += 1
|
||||
endif
|
||||
endfor
|
||||
endif
|
||||
endfunction
|
||||
"}}}
|
||||
" function! s:matchaddpos(group, pos) abort "{{{
|
||||
if s:has_patch_7_4_362
|
||||
function! s:matchaddpos(group, pos) abort
|
||||
return [matchaddpos(a:group, a:pos)]
|
||||
endfunction
|
||||
else
|
||||
function! s:matchaddpos(group, pos) abort
|
||||
let id_list = []
|
||||
for pos in a:pos
|
||||
if len(pos) == 1
|
||||
let id_list += [matchadd(a:group, printf('\%%%dl', pos[0]))]
|
||||
else
|
||||
let id_list += [matchadd(a:group, printf('\%%%dl\%%>%dc.*\%%<%dc', pos[0], pos[1]-1, pos[1]+pos[2]))]
|
||||
endif
|
||||
endfor
|
||||
return id_list
|
||||
endfunction
|
||||
endif
|
||||
"}}}
|
||||
function! s:matchdelete_all(ids) abort "{{{
|
||||
if empty(a:ids)
|
||||
return
|
||||
endif
|
||||
|
||||
let alive_ids = map(getmatches(), 'v:val.id')
|
||||
" Return if another plugin called clearmatches() which clears *ALL*
|
||||
" highlights including others set.
|
||||
if empty(alive_ids)
|
||||
return
|
||||
endif
|
||||
if !count(alive_ids, a:ids[0])
|
||||
return
|
||||
endif
|
||||
|
||||
for id in a:ids
|
||||
try
|
||||
call matchdelete(id)
|
||||
catch
|
||||
endtry
|
||||
endfor
|
||||
call filter(a:ids, 0)
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:is_ahead(pos1, pos2) abort "{{{
|
||||
return a:pos1[1] > a:pos2[1] || (a:pos1[1] == a:pos2[1] && a:pos1[2] > a:pos2[2])
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:is_equal_or_ahead(pos1, pos2) abort "{{{
|
||||
return a:pos1[1] > a:pos2[1] || (a:pos1[1] == a:pos2[1] && a:pos1[2] >= a:pos2[2])
|
||||
endfunction
|
||||
"}}}
|
||||
" function! s:is_in_cmdline_window() abort "{{{
|
||||
if s:has_patch_7_4_392
|
||||
function! s:is_in_cmdline_window() abort
|
||||
return getcmdwintype() !=# ''
|
||||
endfunction
|
||||
else
|
||||
function! s:is_in_cmdline_window() abort
|
||||
let is_in_cmdline_window = 0
|
||||
try
|
||||
execute 'tabnext ' . tabpagenr()
|
||||
catch /^Vim\%((\a\+)\)\=:E11/
|
||||
let is_in_cmdline_window = 1
|
||||
catch
|
||||
finally
|
||||
return is_in_cmdline_window
|
||||
endtry
|
||||
endfunction
|
||||
endif
|
||||
"}}}
|
||||
" function! s:is_in_popup_terminal_window() abort "{{{
|
||||
if exists('*popup_list')
|
||||
function! s:is_in_popup_terminal_window() abort
|
||||
return &buftype is# 'terminal' && count(popup_list(), win_getid())
|
||||
endfunction
|
||||
else
|
||||
function! s:is_in_popup_terminal_window() abort
|
||||
return 0
|
||||
endfunction
|
||||
endif
|
||||
" }}}
|
||||
function! s:shift_options() abort "{{{
|
||||
let options = {}
|
||||
|
||||
""" tweak appearance
|
||||
" hide_cursor
|
||||
if s:has_gui_running
|
||||
let options.cursor = &guicursor
|
||||
set guicursor+=a:block-NONE
|
||||
else
|
||||
let options.cursor = &t_ve
|
||||
set t_ve=
|
||||
endif
|
||||
|
||||
return options
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:restore_options(options) abort "{{{
|
||||
if s:has_gui_running
|
||||
set guicursor&
|
||||
let &guicursor = a:options.cursor
|
||||
else
|
||||
let &t_ve = a:options.cursor
|
||||
endif
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:search_highlighted_windows(id, ...) abort "{{{
|
||||
if a:id == []
|
||||
return 0
|
||||
endif
|
||||
|
||||
let original_winnr = winnr()
|
||||
let original_tabnr = tabpagenr()
|
||||
let original_view = winsaveview()
|
||||
let tablist = range(1, tabpagenr('$'))
|
||||
if a:0 > 0
|
||||
let tabnr = a:1
|
||||
let [tabnr, winnr] = s:scan_windows(a:id, tabnr)
|
||||
if tabnr != 0
|
||||
return [tabnr, winnr]
|
||||
endif
|
||||
call filter(tablist, 'v:val != tabnr')
|
||||
endif
|
||||
|
||||
for tabnr in tablist
|
||||
let [tabnr, winnr] = s:scan_windows(a:id, tabnr)
|
||||
if tabnr != 0
|
||||
return [tabnr, winnr]
|
||||
endif
|
||||
endfor
|
||||
call s:goto_window(original_winnr, original_tabnr, original_view)
|
||||
return [0, 0]
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:scan_windows(id, tabnr) abort "{{{
|
||||
if s:goto_tab(a:tabnr)
|
||||
for winnr in range(1, winnr('$'))
|
||||
if s:goto_window(winnr) && s:is_highlighted_window(a:id)
|
||||
return [a:tabnr, winnr]
|
||||
endif
|
||||
endfor
|
||||
endif
|
||||
return [0, 0]
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:is_highlighted_window(id) abort "{{{
|
||||
if a:id != []
|
||||
let id = a:id[0]
|
||||
if filter(getmatches(), 'v:val.id == id') != []
|
||||
return 1
|
||||
endif
|
||||
endif
|
||||
return 0
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:goto_window(winnr, ...) abort "{{{
|
||||
if a:0 > 0
|
||||
if !s:goto_tab(a:1)
|
||||
return 0
|
||||
endif
|
||||
endif
|
||||
|
||||
|
||||
try
|
||||
if a:winnr != winnr()
|
||||
execute printf('noautocmd %swincmd w', a:winnr)
|
||||
endif
|
||||
catch /^Vim\%((\a\+)\)\=:E16/
|
||||
return 0
|
||||
endtry
|
||||
|
||||
if a:0 > 1
|
||||
noautocmd call winrestview(a:2)
|
||||
endif
|
||||
|
||||
return 1
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:goto_tab(tabnr) abort "{{{
|
||||
if a:tabnr != tabpagenr()
|
||||
execute 'noautocmd tabnext ' . a:tabnr
|
||||
endif
|
||||
return tabpagenr() == a:tabnr ? 1 : 0
|
||||
endfunction
|
||||
"}}}
|
||||
|
||||
|
||||
" vim:set foldmethod=marker:
|
||||
" vim:set commentstring="%s:
|
||||
" vim:set ts=2 sts=2 sw=2:
|
||||
@ -0,0 +1,438 @@
|
||||
let s:save_cpo = &cpo
|
||||
set cpo&vim
|
||||
|
||||
" variables "{{{
|
||||
let s:FALSE = 0
|
||||
let s:TRUE = 1
|
||||
let s:null_pos = [0, 0]
|
||||
|
||||
" patchs
|
||||
if v:version > 704 || (v:version == 704 && has('patch237'))
|
||||
let s:has_patch_7_4_1685 = has('patch-7.4.1685')
|
||||
let s:has_patch_7_4_2011 = has('patch-7.4.2011')
|
||||
else
|
||||
let s:has_patch_7_4_1685 = v:version == 704 && has('patch1685')
|
||||
let s:has_patch_7_4_2011 = v:version == 704 && has('patch2011')
|
||||
endif
|
||||
"}}}
|
||||
|
||||
" default patterns
|
||||
let g:sandwich#magicchar#f#default_patterns = [
|
||||
\ {
|
||||
\ 'header' : '\<\h\k*',
|
||||
\ 'bra' : '(',
|
||||
\ 'ket' : ')',
|
||||
\ 'footer' : '',
|
||||
\ },
|
||||
\ ]
|
||||
|
||||
function! sandwich#magicchar#f#fname() abort "{{{
|
||||
call operator#sandwich#show()
|
||||
try
|
||||
echohl MoreMsg
|
||||
if &filetype ==# 'vim'
|
||||
let funcname = input('funcname: ', '', 'custom,sandwich#magicchar#f#fnamecompl_vim')
|
||||
else
|
||||
let funcname = input('funcname: ', '', 'custom,sandwich#magicchar#f#fnamecompl')
|
||||
endif
|
||||
" flash prompt
|
||||
echo ''
|
||||
finally
|
||||
echohl NONE
|
||||
call operator#sandwich#quench()
|
||||
endtry
|
||||
if funcname ==# ''
|
||||
throw 'OperatorSandwichCancel'
|
||||
endif
|
||||
return funcname . '('
|
||||
endfunction
|
||||
"}}}
|
||||
function! sandwich#magicchar#f#fnamecompl(ArgLead, CmdLine, CursorPos) abort "{{{
|
||||
return join(uniq(sort(s:buffer_completion())), "\n")
|
||||
endfunction
|
||||
"}}}
|
||||
function! sandwich#magicchar#f#fnamecompl_vim(ArgLead, CmdLine, CursorPos) abort "{{{
|
||||
if s:has_patch_7_4_2011
|
||||
let getcomp = map(filter(getcompletion(a:ArgLead, 'function'), 'v:val =~# ''\C^[a-z][a-zA-Z0-9_]*($'''), 'matchstr(v:val, ''\C^[a-z][a-zA-Z0-9_]*'')')
|
||||
else
|
||||
let getcomp = []
|
||||
endif
|
||||
let buffer = s:buffer_completion()
|
||||
return join(uniq(sort(getcomp + buffer)), "\n")
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:buffer_completion() abort "{{{
|
||||
" NOTE: This func does neither sort nor uniq.
|
||||
let list = []
|
||||
let lines = getline(1, '$')
|
||||
let pattern_list = s:resolve_patterns()
|
||||
for func in pattern_list
|
||||
let pat = printf('%s\ze%s', func.header, func.bra)
|
||||
for line in lines
|
||||
let list += s:extract_pattern(line, pat)
|
||||
endfor
|
||||
endfor
|
||||
return list
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:extract_pattern(string, pat) abort "{{{
|
||||
let list = []
|
||||
let end = 0
|
||||
while 1
|
||||
let [str, start, end] = s:matchstrpos(a:string, a:pat, end)
|
||||
if start < 0
|
||||
break
|
||||
endif
|
||||
let list += [str]
|
||||
endwhile
|
||||
return list
|
||||
endfunction
|
||||
"}}}
|
||||
" function! s:matchstrpos(expr, pat, ...) abort "{{{
|
||||
if s:has_patch_7_4_1685
|
||||
let s:matchstrpos = function('matchstrpos')
|
||||
else
|
||||
function! s:matchstrpos(expr, pat, ...) abort
|
||||
if a:0 == 0
|
||||
return [matchstr(a:expr, a:pat), match(a:expr, a:pat), matchend(a:expr, a:pat)]
|
||||
elseif a:0 == 1
|
||||
return [matchstr(a:expr, a:pat, a:1), match(a:expr, a:pat, a:1), matchend(a:expr, a:pat, a:1)]
|
||||
else
|
||||
return [matchstr(a:expr, a:pat, a:1, a:2), match(a:expr, a:pat, a:1, a:2), matchend(a:expr, a:pat, a:1, a:2)]
|
||||
endif
|
||||
endfunction
|
||||
endif
|
||||
"}}}
|
||||
|
||||
|
||||
|
||||
" textobj-functioncall (bundle version)
|
||||
" NOTE: https://github.com/machakann/vim-textobj-functioncall
|
||||
|
||||
function! sandwich#magicchar#f#i(mode) abort "{{{
|
||||
call sandwich#magicchar#f#textobj('i', a:mode)
|
||||
endfunction
|
||||
"}}}
|
||||
function! sandwich#magicchar#f#a(mode) abort "{{{
|
||||
call sandwich#magicchar#f#textobj('a', a:mode)
|
||||
endfunction
|
||||
"}}}
|
||||
function! sandwich#magicchar#f#ip(mode) abort "{{{
|
||||
call sandwich#magicchar#f#textobj('ip', a:mode)
|
||||
endfunction
|
||||
"}}}
|
||||
function! sandwich#magicchar#f#ap(mode) abort "{{{
|
||||
call sandwich#magicchar#f#textobj('ap', a:mode)
|
||||
endfunction
|
||||
"}}}
|
||||
function! sandwich#magicchar#f#textobj(kind, mode, ...) abort "{{{
|
||||
let l:count = v:count1
|
||||
if a:0
|
||||
let pattern_list = a:1
|
||||
else
|
||||
let pattern_list = s:resolve_patterns()
|
||||
endif
|
||||
let searchlines = s:get('textobj_sandwich_function_searchlines' , 30)
|
||||
let stopline = {}
|
||||
if searchlines < 0
|
||||
let stopline.upper = 1
|
||||
let stopline.lower = line('$')
|
||||
else
|
||||
let stopline.upper = max([1, line('.') - searchlines])
|
||||
let stopline.lower = min([line('.') + searchlines, line('$')])
|
||||
endif
|
||||
|
||||
let [start, end] = [s:null_pos, s:null_pos]
|
||||
let view = winsaveview()
|
||||
try
|
||||
let candidates = s:gather_candidates(a:kind, a:mode, l:count, pattern_list, stopline)
|
||||
let elected = s:elect(candidates, l:count)
|
||||
if elected != {}
|
||||
let [start, end] = s:to_range(a:kind, elected)
|
||||
endif
|
||||
finally
|
||||
call winrestview(view)
|
||||
endtry
|
||||
call s:select(start, end)
|
||||
endfunction
|
||||
"}}}
|
||||
|
||||
|
||||
function! s:Candidate(head, bra, ket, tail, pat, rank) abort
|
||||
return {
|
||||
\ 'head': a:head,
|
||||
\ 'bra': a:bra,
|
||||
\ 'ket': a:ket,
|
||||
\ 'tail': a:tail,
|
||||
\ 'rank': a:rank,
|
||||
\ 'pattern': a:pat,
|
||||
\ 'len': s:buflen(a:head, a:tail),
|
||||
\ }
|
||||
endfunction
|
||||
|
||||
|
||||
function! s:gather_candidates(kind, mode, count, pattern_list, stopline) abort "{{{
|
||||
let curpos = getpos('.')[1:2]
|
||||
let rank = 0
|
||||
let candidates = []
|
||||
for pattern in a:pattern_list
|
||||
let rank += 1
|
||||
let candidates += s:search_pattern(pattern, a:kind, a:mode, a:count, rank, curpos, a:stopline)
|
||||
call cursor(curpos)
|
||||
endfor
|
||||
return a:kind[0] is# 'a' && candidates == []
|
||||
\ ? s:gather_candidates('i', a:mode, a:count, a:pattern_list, a:stopline)
|
||||
\ : candidates
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:search_pattern(pat, kind, mode, count, rank, curpos, stopline) abort "{{{
|
||||
let a:pat.head = a:pat.header . a:pat.bra
|
||||
let a:pat.tail = a:pat.ket . a:pat.footer
|
||||
|
||||
let brapos = s:search_key_bra(a:kind, a:curpos, a:pat, a:stopline)
|
||||
if brapos == s:null_pos | return [] | endif
|
||||
let is_string = s:is_string_syntax(brapos)
|
||||
|
||||
let candidates = []
|
||||
while len(candidates) < a:count
|
||||
let c = s:get_candidate(a:pat, a:kind, a:mode, a:rank, brapos, is_string, a:stopline)
|
||||
if c != {}
|
||||
call add(candidates, c)
|
||||
endif
|
||||
call cursor(brapos)
|
||||
|
||||
" move to the next 'bra'
|
||||
let brapos = searchpairpos(a:pat.bra, '', a:pat.ket, 'b', '', a:stopline.upper)
|
||||
if brapos == s:null_pos | break | endif
|
||||
let is_string = s:is_string_syntax(brapos)
|
||||
endwhile
|
||||
return candidates
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:search_key_bra(kind, curpos, pat, stopline) abort "{{{
|
||||
let brapos = s:null_pos
|
||||
if a:kind[0] is# 'a'
|
||||
" search for the first 'bra'
|
||||
if searchpos(a:pat.tail, 'cn', a:stopline.lower) == a:curpos
|
||||
let brapos = searchpairpos(a:pat.bra, '', a:pat.ket, 'b', '', a:stopline.upper)
|
||||
endif
|
||||
let brapos = searchpairpos(a:pat.bra, '', a:pat.ket, 'b', '', a:stopline.upper)
|
||||
elseif a:kind[0] is# 'i'
|
||||
let head_start = searchpos(a:pat.head, 'bc', a:stopline.upper)
|
||||
let head_end = searchpos(a:pat.head, 'ce', a:stopline.lower)
|
||||
call cursor(a:curpos)
|
||||
let tail_start = searchpos(a:pat.tail, 'bc', a:stopline.upper)
|
||||
let tail_end = searchpos(a:pat.tail, 'ce', a:stopline.lower)
|
||||
|
||||
" check the initial position
|
||||
if s:is_in_between(a:curpos, head_start, head_end)
|
||||
" cursor is on a header
|
||||
call cursor(head_end)
|
||||
elseif s:is_in_between(a:curpos, tail_start, tail_end)
|
||||
" cursor is on a footer
|
||||
call cursor(tail_start)
|
||||
if tail_start[1] == 1
|
||||
normal! k$
|
||||
else
|
||||
normal! h
|
||||
endif
|
||||
else
|
||||
" cursor is in between a bra and a ket
|
||||
call cursor(a:curpos)
|
||||
endif
|
||||
|
||||
" move to the corresponded 'bra'
|
||||
let brapos = searchpairpos(a:pat.bra, '', a:pat.ket, 'bc', '', a:stopline.upper)
|
||||
endif
|
||||
return brapos
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:get_candidate(pat, kind, mode, rank, brapos, is_string, stopline) abort "{{{
|
||||
" 'bra' should accompany with 'header'
|
||||
let headstart = searchpos(a:pat.head, 'bc', a:stopline.upper)
|
||||
let headend = searchpos(a:pat.head, 'ce', a:stopline.lower)
|
||||
call cursor(a:brapos)
|
||||
if !s:is_in_between(a:brapos, headstart, headend)
|
||||
return {}
|
||||
endif
|
||||
let headpos = headstart
|
||||
|
||||
" search for the paired 'ket'
|
||||
let skip = 's:is_string_syntax(getpos(".")[1:2]) != a:is_string'
|
||||
let ketpos = searchpairpos(a:pat.bra, '', a:pat.ket, '', skip, a:stopline.lower)
|
||||
if ketpos == s:null_pos
|
||||
return {}
|
||||
endif
|
||||
let tailpos = searchpos(a:pat.tail, 'ce', a:stopline.lower)
|
||||
|
||||
if searchpos(a:pat.tail, 'bcn', a:stopline.upper) != ketpos
|
||||
return {}
|
||||
endif
|
||||
|
||||
let c = s:Candidate(headpos, a:brapos, ketpos, tailpos, a:pat, a:rank)
|
||||
if !s:is_valid_candidate(c, a:kind, a:mode, a:is_string)
|
||||
return {}
|
||||
endif
|
||||
return c
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:elect(candidates, count) abort "{{{
|
||||
if a:candidates == []
|
||||
return {}
|
||||
endif
|
||||
let filter = 'v:val.head != s:null_pos && v:val.bra != s:null_pos && v:val.ket != s:null_pos && v:val.tail != s:null_pos'
|
||||
call filter(a:candidates, filter)
|
||||
call sort(a:candidates, 's:compare')
|
||||
if len(a:candidates) < a:count
|
||||
return {}
|
||||
endif
|
||||
return a:candidates[a:count - 1]
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:to_range(kind, candidate) abort "{{{
|
||||
if a:kind[1] is# 'p'
|
||||
let [start, end] = s:parameter_region(a:candidate)
|
||||
else
|
||||
let start = a:candidate.head
|
||||
let end = a:candidate.tail
|
||||
endif
|
||||
return [start, end]
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:select(start, end) abort "{{{
|
||||
if a:start == s:null_pos || a:end == s:null_pos
|
||||
return
|
||||
endif
|
||||
normal! v
|
||||
call cursor(a:start)
|
||||
normal! o
|
||||
call cursor(a:end)
|
||||
if &selection is# 'exclusive'
|
||||
normal! l
|
||||
endif
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:is_in_between(pos, start, end) abort "{{{
|
||||
return (a:pos != s:null_pos) && (a:start != s:null_pos) && (a:end != s:null_pos)
|
||||
\ && ((a:pos[0] > a:start[0]) || ((a:pos[0] == a:start[0]) && (a:pos[1] >= a:start[1])))
|
||||
\ && ((a:pos[0] < a:end[0]) || ((a:pos[0] == a:end[0]) && (a:pos[1] <= a:end[1])))
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:is_string_syntax(pos) abort "{{{
|
||||
return match(map(synstack(a:pos[0], a:pos[1]), 'synIDattr(synIDtrans(v:val), "name")'), 'String') > -1
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:is_continuous_syntax(brapos, ketpos) abort "{{{
|
||||
let start_col = a:brapos[1]
|
||||
for lnum in range(a:brapos[0], a:ketpos[0])
|
||||
if lnum == a:ketpos[0]
|
||||
let end_col= a:ketpos[1]
|
||||
else
|
||||
let end_col= col([lnum, '$'])
|
||||
endif
|
||||
for col in range(start_col, end_col)
|
||||
if match(map(synstack(lnum, col), 'synIDattr(synIDtrans(v:val), "name")'), 'String') < 0
|
||||
return 0
|
||||
endif
|
||||
endfor
|
||||
let start_col = 1
|
||||
endfor
|
||||
return 1
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:is_valid_syntax(candidate, is_string) abort "{{{
|
||||
return !a:is_string || s:is_continuous_syntax(a:candidate.bra, a:candidate.ket)
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:is_same_or_adjacent(p1, p2) abort "{{{
|
||||
return a:p1 == a:p2 || (a:p1[0] == a:p2[0] && a:p1[1]+1 == a:p2[1])
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:is_wider(candidate, start, end) abort "{{{
|
||||
return (s:is_ahead(a:start, a:candidate.head) && s:is_same_or_ahead(a:candidate.tail, a:end)) ||
|
||||
\ (s:is_same_or_ahead(a:start, a:candidate.head) && s:is_ahead(a:candidate.tail, a:end))
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:is_ahead(p1, p2) abort "{{{
|
||||
return (a:p1[0] > a:p2[0]) || (a:p1[0] == a:p2[0] && a:p1[1] > a:p2[1])
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:is_same_or_ahead(p1, p2) abort "{{{
|
||||
return (a:p1[0] > a:p2[0]) || (a:p1[0] == a:p2[0] && a:p1[1] >= a:p2[1])
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:is_valid_candidate(c, kind, mode, is_string) abort "{{{
|
||||
if a:kind[1] is# 'p' && s:is_same_or_adjacent(a:c.bra, a:c.ket)
|
||||
return s:FALSE
|
||||
endif
|
||||
if a:mode is# 'x' && !s:is_wider(a:c, getpos("'<")[1:2], getpos("'>")[1:2])
|
||||
return s:FALSE
|
||||
endif
|
||||
if !s:is_valid_syntax(a:c, a:is_string)
|
||||
return s:FALSE
|
||||
endif
|
||||
return s:TRUE
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:resolve_patterns() abort "{{{
|
||||
return deepcopy(get(b:, 'sandwich_magicchar_f_patterns',
|
||||
\ get(g:, 'sandwich#magicchar#f#patterns',
|
||||
\ g:sandwich#magicchar#f#default_patterns)))
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:parameter_region(candidate) abort "{{{
|
||||
let whichwrap = &whichwrap
|
||||
let &whichwrap = 'h,l'
|
||||
let [visualhead, visualtail] = [getpos("'<"), getpos("'>")]
|
||||
try
|
||||
normal! v
|
||||
call cursor(a:candidate.bra)
|
||||
call search(a:candidate.pattern.bra, 'ce', a:candidate.ket[0])
|
||||
normal! l
|
||||
let head = getpos('.')[1:2]
|
||||
normal! o
|
||||
call cursor(a:candidate.ket)
|
||||
normal! h
|
||||
let tail = getpos('.')[1:2]
|
||||
execute "normal! \<Esc>"
|
||||
finally
|
||||
let &whichwrap = whichwrap
|
||||
call setpos("'<", visualhead)
|
||||
call setpos("'>", visualtail)
|
||||
endtry
|
||||
return [head, tail]
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:buflen(start, end) abort "{{{
|
||||
" start, end -> [lnum, col]
|
||||
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:compare(i1, i2) abort "{{{
|
||||
" i1, i2 -> Candidate
|
||||
if a:i1.len < a:i2.len
|
||||
return -1
|
||||
elseif a:i1.len > a:i2.len
|
||||
return 1
|
||||
else
|
||||
return a:i2.rank - a:i1.rank
|
||||
endif
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:get(name, default) abort "{{{
|
||||
return exists('b:' . a:name) ? b:[a:name]
|
||||
\ : exists('g:' . a:name) ? g:[a:name]
|
||||
\ : a:default
|
||||
endfunction
|
||||
"}}}
|
||||
|
||||
let &cpo = s:save_cpo
|
||||
unlet s:save_cpo
|
||||
|
||||
" vim:set foldmethod=marker:
|
||||
" vim:set commentstring="%s:
|
||||
@ -0,0 +1,102 @@
|
||||
let s:last_inserted = []
|
||||
let s:last_searched = []
|
||||
|
||||
" variables "{{{
|
||||
" patchs
|
||||
if v:version > 704 || (v:version == 704 && has('patch237'))
|
||||
let s:has_patch_7_4_1685 = has('patch-7.4.1685')
|
||||
else
|
||||
let s:has_patch_7_4_1685 = v:version == 704 && has('patch1685')
|
||||
endif
|
||||
"}}}
|
||||
|
||||
function! sandwich#magicchar#i#input(kind, ...) abort "{{{
|
||||
let storepoint = a:kind ==# 'operator' ? 'last_inserted' : 'last_searched'
|
||||
let former = s:input(a:kind . '-sandwich:head: ')
|
||||
let quit_if_empty = get(a:000, 0, 0)
|
||||
if quit_if_empty && former ==# ''
|
||||
let s:{storepoint} = []
|
||||
return ['', '']
|
||||
endif
|
||||
let latter = s:input(a:kind . '-sandwich:tail: ')
|
||||
let s:{storepoint} = [former, latter]
|
||||
return copy(s:{storepoint})
|
||||
endfunction
|
||||
"}}}
|
||||
function! sandwich#magicchar#i#lastinput(kind, ...) abort "{{{
|
||||
let storepoint = a:kind ==# 'operator' ? 'last_inserted' : 'last_searched'
|
||||
let cancel_if_invalid = get(a:000, 0, 0)
|
||||
if cancel_if_invalid && s:{storepoint} == []
|
||||
throw 'OperatorSandwichCancel'
|
||||
endif
|
||||
return s:{storepoint} != [] ? copy(s:{storepoint}) : ['', '']
|
||||
endfunction
|
||||
"}}}
|
||||
function! sandwich#magicchar#i#compl(ArgLead, CmdLine, CursorPos) abort "{{{
|
||||
let list = []
|
||||
let lines = getline(1, '$')
|
||||
if a:ArgLead ==# ''
|
||||
let list += s:extract_patterns_from_lines(lines, '\<\k\{3,}\>')
|
||||
else
|
||||
let list += s:extract_patterns_from_lines(lines, printf('\<%s.\{-}\>', a:ArgLead))
|
||||
if list == []
|
||||
let tail = matchstr(a:ArgLead, '\<\k\+$')
|
||||
if tail !=# '' && tail !=# a:ArgLead
|
||||
let list += s:extract_patterns_from_lines(lines, tail . '.\{-}\>')
|
||||
endif
|
||||
endif
|
||||
endif
|
||||
return join(uniq(sort(list)), "\n")
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:input(mes) abort "{{{
|
||||
echohl MoreMsg
|
||||
try
|
||||
let input = input(a:mes, '', 'custom,sandwich#magicchar#i#compl')
|
||||
" flash prompt
|
||||
echo ''
|
||||
finally
|
||||
echohl NONE
|
||||
endtry
|
||||
return input
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:extract_patterns_from_lines(lines, pat) abort "{{{
|
||||
let list = []
|
||||
for line in a:lines
|
||||
let list += s:extract_pattern(line, a:pat)
|
||||
endfor
|
||||
return list
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:extract_pattern(string, pat) abort "{{{
|
||||
let list = []
|
||||
let end = 0
|
||||
while 1
|
||||
let [str, start, end] = s:matchstrpos(a:string, a:pat, end)
|
||||
if start < 0
|
||||
break
|
||||
endif
|
||||
let list += [str]
|
||||
endwhile
|
||||
return list
|
||||
endfunction
|
||||
"}}}
|
||||
" function! s:matchstrpos(expr, pat, ...) abort "{{{
|
||||
if s:has_patch_7_4_1685
|
||||
let s:matchstrpos = function('matchstrpos')
|
||||
else
|
||||
function! s:matchstrpos(expr, pat, ...) abort
|
||||
if a:0 == 0
|
||||
return [matchstr(a:expr, a:pat), match(a:expr, a:pat), matchend(a:expr, a:pat)]
|
||||
elseif a:0 == 1
|
||||
return [matchstr(a:expr, a:pat, a:1), match(a:expr, a:pat, a:1), matchend(a:expr, a:pat, a:1)]
|
||||
else
|
||||
return [matchstr(a:expr, a:pat, a:1, a:2), match(a:expr, a:pat, a:1, a:2), matchend(a:expr, a:pat, a:1, a:2)]
|
||||
endif
|
||||
endfunction
|
||||
endif
|
||||
"}}}
|
||||
|
||||
" vim:set foldmethod=marker:
|
||||
" vim:set commentstring="%s:
|
||||
@ -0,0 +1,446 @@
|
||||
" variables "{{{
|
||||
let s:null_coord = [0, 0]
|
||||
|
||||
" types
|
||||
let s:type_num = type(0)
|
||||
let s:type_str = type('')
|
||||
let s:type_list = type([])
|
||||
"}}}
|
||||
|
||||
function! sandwich#magicchar#t#tag() abort "{{{
|
||||
call operator#sandwich#show()
|
||||
try
|
||||
echohl MoreMsg
|
||||
let old_imsearch = &l:imsearch
|
||||
let &l:imsearch = 0
|
||||
let tag = input('Input tag: ')
|
||||
let &l:imsearch = old_imsearch
|
||||
" flash prompt
|
||||
echo ''
|
||||
finally
|
||||
echohl NONE
|
||||
call operator#sandwich#quench()
|
||||
endtry
|
||||
if tag ==# ''
|
||||
throw 'OperatorSandwichCancel'
|
||||
endif
|
||||
let [open, close] = s:expand(tag)
|
||||
return [printf('<%s>', open), printf('</%s>', close)]
|
||||
endfunction
|
||||
"}}}
|
||||
function! sandwich#magicchar#t#tagname() abort "{{{
|
||||
call operator#sandwich#show()
|
||||
try
|
||||
echohl MoreMsg
|
||||
let old_imsearch = &l:imsearch
|
||||
let &l:imsearch = 0
|
||||
let tagname = input('Input tag name: ')
|
||||
let &l:imsearch = old_imsearch
|
||||
" flash prompt
|
||||
echo ''
|
||||
finally
|
||||
echohl NONE
|
||||
call operator#sandwich#quench()
|
||||
endtry
|
||||
if tagname ==# ''
|
||||
throw 'OperatorSandwichCancel'
|
||||
endif
|
||||
return s:expand(tagname)
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:tokenize(text) abort "{{{
|
||||
let j = 0
|
||||
let n = strlen(a:text)
|
||||
let tokenlist = []
|
||||
while j >= 0 && j < n
|
||||
let i = j
|
||||
let j = match(a:text, '\m[[[:blank:]#.]', i)
|
||||
if i < j
|
||||
let tokenlist += [a:text[i : j-1]]
|
||||
elseif i == j
|
||||
let char = a:text[i]
|
||||
if char =~# '\m\s'
|
||||
" space is not allowed
|
||||
throw 'SandwichMagiccharTIncorrectSyntax'
|
||||
elseif char ==# '['
|
||||
" tokenize custom attributes
|
||||
let tokenlist += [char]
|
||||
let j += 1
|
||||
let [j, tokenlist] += s:tokenize_custom_attributes(a:text, j)
|
||||
else
|
||||
let tokenlist += [char]
|
||||
let j += 1
|
||||
endif
|
||||
endif
|
||||
endwhile
|
||||
|
||||
let i = j >= 0 ? j : i
|
||||
if i < n
|
||||
let tokenlist += [a:text[i :]]
|
||||
endif
|
||||
return tokenlist
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:tokenize_custom_attributes(text, j) abort "{{{
|
||||
let j = a:j
|
||||
let n = strlen(a:text)
|
||||
let closed = 0
|
||||
let tokenlist = []
|
||||
while j >= 0 && j < n
|
||||
let i = j
|
||||
let j = match(a:text, '\m[][:blank:]="'']', i)
|
||||
if i < j
|
||||
let string = a:text[i : j-1]
|
||||
let tokenlist += [string]
|
||||
elseif i == j
|
||||
let char = a:text[i]
|
||||
if char =~# '\m\s'
|
||||
" skip space
|
||||
let j = matchend(a:text, '\m\s\+', i)
|
||||
if j > i
|
||||
let tokenlist += [a:text[i : j-1]]
|
||||
endif
|
||||
elseif char =~# '\m["'']'
|
||||
" skip string literal
|
||||
let j = match(a:text, char, i+1)
|
||||
if j > 0
|
||||
let tokenlist += [a:text[i : j]]
|
||||
let j += 1
|
||||
else
|
||||
" unclosed string literal
|
||||
throw 'SandwichMagiccharTIncorrectSyntax'
|
||||
endif
|
||||
elseif char ==# ']'
|
||||
" the end of custom attributes region
|
||||
let tokenlist += [char]
|
||||
let j += 1
|
||||
let closed = 1
|
||||
break
|
||||
else
|
||||
let tokenlist += [char]
|
||||
let j += 1
|
||||
endif
|
||||
endif
|
||||
endwhile
|
||||
if !closed
|
||||
" unclosed braket
|
||||
throw 'SandwichMagiccharTIncorrectSyntax'
|
||||
endif
|
||||
return [j - a:j, tokenlist]
|
||||
endfunction
|
||||
"}}}
|
||||
|
||||
|
||||
function! s:matches_filetype(filetype, list) abort "{{{
|
||||
return index(a:list, a:filetype) != -1
|
||||
endfunction
|
||||
" }}}
|
||||
|
||||
function! s:parse(tokenlist) abort "{{{
|
||||
let itemdict = deepcopy(s:itemdict)
|
||||
let itemlist = map(copy(a:tokenlist), '{"is_operator": v:val =~# ''^\%([][#.=]\|\s\+\)$'' ? 1 : 0, "string": v:val}')
|
||||
|
||||
let i = 0
|
||||
let n = len(itemlist)
|
||||
let item = itemlist[i]
|
||||
if item.is_operator
|
||||
call itemdict.queue_element('div')
|
||||
else
|
||||
call itemdict.queue_element(item.string)
|
||||
let i += 1
|
||||
endif
|
||||
while i < n
|
||||
let item = itemlist[i]
|
||||
if item.is_operator
|
||||
if item.string ==# '#'
|
||||
let i = s:handle_id(itemdict, itemlist, i)
|
||||
elseif item.string ==# '.'
|
||||
if s:matches_filetype(&filetype, g:sandwich#jsx_filetypes)
|
||||
let i = s:handle_className(itemdict, itemlist, i)
|
||||
else
|
||||
let i = s:handle_class(itemdict, itemlist, i)
|
||||
endif
|
||||
elseif item.string ==# '['
|
||||
let i = s:parse_custom_attributes(itemdict, itemlist, i)
|
||||
else
|
||||
" unanticipated operator (should not reach here)
|
||||
throw 'SandwichMagiccharTIncorrectSyntax'
|
||||
endif
|
||||
else
|
||||
" successive operand is not allowed here (should not reach here)
|
||||
throw 'SandwichMagiccharTIncorrectSyntax'
|
||||
endif
|
||||
endwhile
|
||||
|
||||
let parsed = deepcopy(itemdict.queue)
|
||||
for item in parsed
|
||||
call filter(item, 'v:key =~# ''\%(name\|value\)''')
|
||||
endfor
|
||||
return parsed
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:parse_custom_attributes(itemdict, itemlist, i) abort "{{{
|
||||
" check ket
|
||||
let i_ket = s:check_ket(a:itemlist, a:i)
|
||||
if i_ket < 0
|
||||
" ket does not exist (should not reach here)
|
||||
throw 'SandwichMagiccharTIncorrectSyntax'
|
||||
endif
|
||||
|
||||
let i = a:i + 1
|
||||
while i < i_ket
|
||||
let item = a:itemlist[i]
|
||||
if item.is_operator
|
||||
if item.string ==# '='
|
||||
let i = s:handle_equal(a:itemdict, a:itemlist, i)
|
||||
else
|
||||
let i += 1
|
||||
endif
|
||||
else
|
||||
let i = s:handle_custom_attr_name(a:itemdict, a:itemlist, i)
|
||||
endif
|
||||
endwhile
|
||||
call a:itemdict.queue_custom_attr()
|
||||
return i_ket + 1
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:handle_id(itemdict, itemlist, i) abort "{{{
|
||||
let i = a:i + 1
|
||||
let item = get(a:itemlist, i, {'is_operator': 1})
|
||||
if item.is_operator
|
||||
call a:itemdict.queue_id()
|
||||
else
|
||||
call a:itemdict.queue_id(item.string)
|
||||
let i += 1
|
||||
endif
|
||||
return i
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:handle_class(itemdict, itemlist, i) abort "{{{
|
||||
let i = a:i + 1
|
||||
let item = get(a:itemlist, i, {'is_operator': 1})
|
||||
if item.is_operator
|
||||
call a:itemdict.queue_class()
|
||||
else
|
||||
call a:itemdict.queue_class(item.string)
|
||||
let i += 1
|
||||
endif
|
||||
return i
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:handle_className(itemdict, itemlist, i) abort "{{{
|
||||
let i = a:i + 1
|
||||
let item = get(a:itemlist, i, {'is_operator': 1})
|
||||
if item.is_operator
|
||||
call a:itemdict.queue_className()
|
||||
else
|
||||
call a:itemdict.queue_className(item.string)
|
||||
let i += 1
|
||||
endif
|
||||
return i
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:handle_equal(itemdict, itemlist, i, ...) abort "{{{
|
||||
let i = a:i + 1
|
||||
let item = a:itemlist[i]
|
||||
let name = get(a:000, 0, '')
|
||||
if item.is_operator
|
||||
if item.string ==# '='
|
||||
call a:itemdict.list_custom_attr(name)
|
||||
else
|
||||
call a:itemdict.list_custom_attr(name)
|
||||
let i += 1
|
||||
endif
|
||||
else
|
||||
call a:itemdict.list_custom_attr(name, item.string)
|
||||
let i += 1
|
||||
endif
|
||||
return i
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:handle_custom_attr_name(itemdict, itemlist, i) abort "{{{
|
||||
let item = a:itemlist[a:i]
|
||||
let name = item.string
|
||||
let i = a:i + 1
|
||||
let item = a:itemlist[i]
|
||||
if item.is_operator
|
||||
if item.string ==# '='
|
||||
let i = s:handle_equal(a:itemdict, a:itemlist, i, name)
|
||||
else
|
||||
call a:itemdict.list_custom_attr(name)
|
||||
let i += 1
|
||||
endif
|
||||
else
|
||||
call a:itemdict.list_custom_attr(name)
|
||||
endif
|
||||
return i
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:check_ket(itemlist, i) abort "{{{
|
||||
let i = a:i + 1
|
||||
let n = len(a:itemlist)
|
||||
let i_ket = -1
|
||||
while i < n
|
||||
let item = a:itemlist[i]
|
||||
if item.is_operator && item.string ==# ']'
|
||||
let i_ket = i
|
||||
break
|
||||
endif
|
||||
let i += 1
|
||||
endwhile
|
||||
return i_ket
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:build(itemlist) abort "{{{
|
||||
let fragments = [a:itemlist[0]['value']]
|
||||
if len(a:itemlist) > 1
|
||||
for item in a:itemlist[1:]
|
||||
let name = item.name
|
||||
let value = type(item.value) == s:type_list ? join(filter(item.value, 'v:val !=# ""')) : item.value
|
||||
let value = value !~# '^\(["'']\).*\1$' ? printf('"%s"', value) : value
|
||||
let fragments += [printf('%s=%s', name, value)]
|
||||
endfor
|
||||
endif
|
||||
return join(fragments)
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:expand(text) abort "{{{
|
||||
try
|
||||
let tokenlist = s:tokenize(a:text)
|
||||
let itemlist = s:parse(tokenlist)
|
||||
catch /^SandwichMagiccharTIncorrectSyntax$/
|
||||
return [a:text, matchstr(a:text, '^\s*\zs\a[^[:blank:]>/]*')]
|
||||
endtry
|
||||
let element = itemlist[0]['value']
|
||||
return [s:build(itemlist), element]
|
||||
endfunction
|
||||
"}}}
|
||||
|
||||
function! sandwich#magicchar#t#i() abort "{{{
|
||||
call s:prototype('i')
|
||||
endfunction
|
||||
"}}}
|
||||
function! sandwich#magicchar#t#a() abort "{{{
|
||||
call s:prototype('a')
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:prototype(kind) abort "{{{
|
||||
let view = winsaveview()
|
||||
let visualhead = getpos("'<")
|
||||
let visualtail = getpos("'>")
|
||||
execute printf('silent! normal! v%dat', v:count1)
|
||||
execute "normal! \<Esc>"
|
||||
if getpos("'<") != getpos("'>")
|
||||
normal! gv
|
||||
let pat = a:kind ==# 'i' ? '</\ze\a[^[:blank:]>/]*' : '</\a[^[:blank:]>/]*\ze\s*>'
|
||||
call search(pat, 'be', line("'<"))
|
||||
normal! o
|
||||
let pat = a:kind ==# 'i' ? '<\a[^[:blank:]>/]*\zs\_.' : '<\zs\a[^[:blank:]>/]*'
|
||||
call search(pat, '', line("'>"))
|
||||
else
|
||||
call winrestview(view)
|
||||
call setpos("'<", visualhead)
|
||||
call setpos("'>", visualtail)
|
||||
endif
|
||||
endfunction
|
||||
"}}}
|
||||
function! sandwich#magicchar#t#it() abort "{{{
|
||||
call s:textobj('i')
|
||||
endfunction
|
||||
"}}}
|
||||
function! sandwich#magicchar#t#at() abort "{{{
|
||||
call s:textobj('a')
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:textobj(a_or_i) abort "{{{
|
||||
let head = searchpos('<\a[^[:blank:]>/]*', 'bcn', 0, 50)
|
||||
if head != s:null_coord
|
||||
let tagname = matchstr(getline(head[0])[head[1]-1 :], '^<\zs\a[^[:blank:]>/]*')
|
||||
if search(printf('</%s>', s:escape(tagname)), 'cn', 0, 50)
|
||||
" add :silent! to suppress errorbell
|
||||
if a:a_or_i ==# 'i'
|
||||
silent! normal! vit
|
||||
else
|
||||
silent! normal! vat
|
||||
endif
|
||||
endif
|
||||
endif
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:escape(string) abort "{{{
|
||||
return escape(a:string, '~"\.^$[]*')
|
||||
endfunction
|
||||
"}}}
|
||||
|
||||
" itemdict object "{{{
|
||||
let s:itemdict = {
|
||||
\ 'element': {'name': 'element', 'value': ''},
|
||||
\ 'id' : {'name': 'id', 'value': '', 'queued': 0},
|
||||
\ 'class' : {'name': 'class', 'value': [], 'queued': 0},
|
||||
\ 'className' : {'name': 'className', 'value': [], 'queued': 0},
|
||||
\ 'queue' : [],
|
||||
\ 'customlist': [],
|
||||
\ }
|
||||
|
||||
function! s:itemdict.queue_element(value) dict abort "{{{
|
||||
let self.element.value = a:value
|
||||
call add(self.queue, self.element)
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:itemdict.queue_id(...) dict abort "{{{
|
||||
let self.id.value = get(a:000, 0, '')
|
||||
if !self.id.queued
|
||||
call add(self.queue, self.id)
|
||||
let self.id.queued = 1
|
||||
endif
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:itemdict.queue_class(...) dict abort "{{{
|
||||
call add(self.class.value, get(a:000, 0, ''))
|
||||
if !self.class.queued
|
||||
call add(self.queue, self.class)
|
||||
let self.class.queued = 1
|
||||
endif
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:itemdict.queue_className(...) dict abort "{{{
|
||||
call add(self.className.value, get(a:000, 0, ''))
|
||||
if !self.className.queued
|
||||
call add(self.queue, self.className)
|
||||
let self.className.queued = 1
|
||||
endif
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:itemdict.queue_custom_attr() dict abort "{{{
|
||||
if self.customlist != []
|
||||
call extend(self.queue, remove(self.customlist, 0, -1))
|
||||
endif
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:itemdict.list_custom_attr(name, ...) dict abort "{{{
|
||||
let value = get(a:000, 0, '')
|
||||
if a:name ==# 'id'
|
||||
call self.queue_id(value)
|
||||
elseif a:name ==# 'class'
|
||||
call self.queue_class(value)
|
||||
endif
|
||||
|
||||
if a:name ==# ''
|
||||
let custom_attr = {'kind': 'custom', 'name': a:name, 'value': value}
|
||||
call add(self.customlist, custom_attr)
|
||||
else
|
||||
if !has_key(self, a:name)
|
||||
let self[a:name] = {'kind': 'custom', 'name': a:name, 'value': value}
|
||||
call add(self.customlist, self[a:name])
|
||||
else
|
||||
let self[a:name]['value'] = value
|
||||
endif
|
||||
endif
|
||||
endfunction
|
||||
"}}}
|
||||
"}}}
|
||||
|
||||
|
||||
|
||||
" vim:set foldmethod=marker:
|
||||
" vim:set commentstring="%s:
|
||||
@ -0,0 +1,67 @@
|
||||
" messenger object - managing messages and errors.
|
||||
|
||||
function! sandwich#messenger#new() abort "{{{
|
||||
let s:messenger = deepcopy(s:messenger_prototype)
|
||||
return s:messenger
|
||||
endfunction
|
||||
"}}}
|
||||
function! sandwich#messenger#get() abort "{{{
|
||||
return s:messenger
|
||||
endfunction
|
||||
"}}}
|
||||
|
||||
" s:messenger_prototype "{{{
|
||||
let s:messenger_prototype = {
|
||||
\ 'error' : {'flag': 0, 'string': ''},
|
||||
\ 'notice': {'flag': 0, 'list': []},
|
||||
\ }
|
||||
"}}}
|
||||
function! s:messenger_prototype.initialize() dict abort "{{{
|
||||
let self.error.flag = 0
|
||||
let self.error.string = ''
|
||||
let self.notice.flag = 0
|
||||
let self.notice.list = []
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:messenger_prototype.notify(prefix) dict abort "{{{
|
||||
if self.error.flag
|
||||
call self.error.echoerr(a:prefix)
|
||||
elseif self.notice.flag
|
||||
call self.notice.echo(a:prefix)
|
||||
endif
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:messenger_prototype.error.echoerr(prefix) dict abort "{{{
|
||||
echoerr a:prefix . self.string
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:messenger_prototype.notice.echo(prefix) dict abort "{{{
|
||||
let queue = []
|
||||
if self.list != []
|
||||
for line in self.list
|
||||
let queue += [[a:prefix, 'MoreMsg']]
|
||||
let queue += [line]
|
||||
let queue += [["\n", 'NONE']]
|
||||
endfor
|
||||
call remove(queue, -1)
|
||||
endif
|
||||
call sandwich#util#echo(queue)
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:messenger_prototype.error.set(errmes) dict abort "{{{
|
||||
let self.flag = 1
|
||||
let self.string = a:errmes
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:messenger_prototype.notice.queue(line) dict abort "{{{
|
||||
let self.flag = 1
|
||||
call add(self.list, a:line)
|
||||
endfunction
|
||||
"}}}
|
||||
|
||||
call sandwich#messenger#new()
|
||||
|
||||
|
||||
" vim:set foldmethod=marker:
|
||||
" vim:set commentstring="%s:
|
||||
" vim:set ts=2 sts=2 sw=2:
|
||||
@ -0,0 +1,346 @@
|
||||
" opt object - managing options
|
||||
|
||||
function! sandwich#opt#new(kind, defaultopt, argopt) abort "{{{
|
||||
let opt = deepcopy(s:opt)
|
||||
if a:kind ==# 'auto' || a:kind ==# 'query'
|
||||
let opt.recipe = {}
|
||||
let opt.of = function('s:of_for_textobj')
|
||||
let opt.filter = s:default_values['textobj']['filter']
|
||||
call opt.update('arg', a:argopt)
|
||||
call opt.update('default', a:defaultopt)
|
||||
else
|
||||
let opt.recipe_add = {}
|
||||
let opt.recipe_delete = {}
|
||||
let opt.of = function('s:of_for_' . a:kind)
|
||||
let opt.filter = s:default_values[a:kind]['filter']
|
||||
call opt.update('arg', a:argopt)
|
||||
endif
|
||||
return opt
|
||||
endfunction
|
||||
"}}}
|
||||
function! sandwich#opt#defaults(kind, ...) abort "{{{
|
||||
if a:kind ==# 'auto' || a:kind ==# 'query'
|
||||
return deepcopy(s:default_values['textobj'][a:kind])
|
||||
elseif a:kind ==# 'add' || a:kind ==# 'delete' || a:kind ==# 'replace'
|
||||
let motionwise = get(a:000, 0, 'char')
|
||||
return deepcopy(s:default_values[a:kind][motionwise])
|
||||
else
|
||||
return {}
|
||||
endif
|
||||
endfunction
|
||||
"}}}
|
||||
|
||||
" opt "{{{
|
||||
let s:opt = {
|
||||
\ 'default' : {},
|
||||
\ 'arg' : {},
|
||||
\ 'filter' : '',
|
||||
\ }
|
||||
"}}}
|
||||
function! s:opt.clear(target) dict abort "{{{
|
||||
call filter(self[a:target], 0)
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:opt.update(target, dict) dict abort "{{{
|
||||
call self.clear(a:target)
|
||||
call extend(self[a:target], filter(deepcopy(a:dict), self.filter), 'force')
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:opt.has(opt_name) dict abort "{{{
|
||||
return has_key(self.default, a:opt_name)
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:opt.max(opt_name, ...) dict abort "{{{
|
||||
let minimum = get(a:000, 0, 0)
|
||||
let list = []
|
||||
if has_key(self['recipe_delete'], a:opt_name)
|
||||
let list += [self['recipe_delete'][a:opt_name]]
|
||||
endif
|
||||
if has_key(self['recipe_add'], a:opt_name)
|
||||
let list += [self['recipe_add'][a:opt_name]]
|
||||
endif
|
||||
if list == []
|
||||
let list += [self._of(a:opt_name)]
|
||||
endif
|
||||
return max([minimum] + list)
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:opt.get(opt_name, kind, ...) dict abort "{{{
|
||||
return self.has(a:opt_name) ? self.of(a:opt_name, a:kind) : get(a:000, 0, 0)
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:opt._of(opt_name, ...) dict abort "{{{
|
||||
let kind = get(a:000, 0, '')
|
||||
if kind !=# '' && has_key(self[kind], a:opt_name)
|
||||
return self[kind][a:opt_name]
|
||||
elseif has_key(self['arg'], a:opt_name)
|
||||
return self['arg'][a:opt_name]
|
||||
else
|
||||
return self['default'][a:opt_name]
|
||||
endif
|
||||
endfunction
|
||||
"}}}
|
||||
|
||||
function! s:of_for_add(opt_name, ...) dict abort "{{{
|
||||
return self._of(a:opt_name, 'recipe_add')
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:of_for_delete(opt_name, ...) dict abort "{{{
|
||||
return self._of(a:opt_name, 'recipe_delete')
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:of_for_replace(opt_name, ...) dict abort "{{{
|
||||
let kind = get(a:000, 0, '')
|
||||
if kind !=# ''
|
||||
return self._of(a:opt_name, kind)
|
||||
else
|
||||
if a:opt_name ==# 'cursor'
|
||||
" recipe_add > recipe_delete > arg > default
|
||||
if has_key(self.recipe_add, a:opt_name)
|
||||
return self.recipe_add[a:opt_name]
|
||||
else
|
||||
return self._of(a:opt_name, 'recipe_delete')
|
||||
endif
|
||||
elseif a:opt_name ==# 'query_once'
|
||||
return self._of(a:opt_name, 'recipe_add')
|
||||
elseif a:opt_name ==# 'regex'
|
||||
return self._of(a:opt_name, 'recipe_delete')
|
||||
elseif a:opt_name ==# 'expr'
|
||||
return self._of(a:opt_name, 'recipe_add')
|
||||
elseif a:opt_name ==# 'listexpr'
|
||||
return self._of(a:opt_name, 'recipe_add')
|
||||
elseif a:opt_name ==# 'noremap'
|
||||
return self._of(a:opt_name, '')
|
||||
elseif a:opt_name ==# 'skip_space'
|
||||
return self._of(a:opt_name, 'recipe_delete')
|
||||
elseif a:opt_name ==# 'skip_char'
|
||||
return self._of(a:opt_name, 'recipe_delete')
|
||||
elseif a:opt_name ==# 'highlight'
|
||||
return self._of(a:opt_name, '')
|
||||
elseif a:opt_name ==# 'hi_duration'
|
||||
return self._of(a:opt_name, '')
|
||||
elseif a:opt_name ==# 'command'
|
||||
let commands = []
|
||||
let commands += get(self.recipe_delete, a:opt_name, [])
|
||||
let commands += get(self.recipe_add, a:opt_name, [])
|
||||
if commands == []
|
||||
let commands += self._of(a:opt_name)
|
||||
endif
|
||||
return commands
|
||||
elseif a:opt_name ==# 'linewise'
|
||||
return self.max(a:opt_name)
|
||||
elseif a:opt_name ==# 'autoindent'
|
||||
return self._of(a:opt_name, 'recipe_add')
|
||||
elseif a:opt_name ==# 'indentkeys'
|
||||
return self._of(a:opt_name, 'recipe_add')
|
||||
elseif a:opt_name ==# 'indentkeys+'
|
||||
return self._of(a:opt_name, 'recipe_add')
|
||||
elseif a:opt_name ==# 'indentkeys-'
|
||||
return self._of(a:opt_name, 'recipe_add')
|
||||
else
|
||||
" should not reach here!
|
||||
throw 'OperatorSandwichError:Replace:InvalidOption:' . a:opt_name
|
||||
endif
|
||||
endif
|
||||
endfunction
|
||||
"}}}
|
||||
function! s:of_for_textobj(opt_name) dict abort "{{{
|
||||
if has_key(self['recipe'], a:opt_name)
|
||||
return self['recipe'][a:opt_name]
|
||||
elseif has_key(self['arg'], a:opt_name)
|
||||
return self['arg'][a:opt_name]
|
||||
else
|
||||
return self['default'][a:opt_name]
|
||||
endif
|
||||
endfunction
|
||||
"}}}
|
||||
|
||||
" default values "{{{
|
||||
let s:default_values = {}
|
||||
let s:default_values.add = {}
|
||||
let s:default_values.add.char = {
|
||||
\ 'cursor' : 'default',
|
||||
\ 'query_once' : 0,
|
||||
\ 'expr' : 0,
|
||||
\ 'listexpr' : 0,
|
||||
\ 'noremap' : 1,
|
||||
\ 'skip_space' : 0,
|
||||
\ 'highlight' : 3,
|
||||
\ 'hi_duration': 200,
|
||||
\ 'command' : [],
|
||||
\ 'linewise' : 0,
|
||||
\ 'autoindent' : -1,
|
||||
\ 'indentkeys' : 0,
|
||||
\ 'indentkeys+': 0,
|
||||
\ 'indentkeys-': 0,
|
||||
\ }
|
||||
let s:default_values.add.line = {
|
||||
\ 'cursor' : 'default',
|
||||
\ 'query_once' : 0,
|
||||
\ 'expr' : 0,
|
||||
\ 'listexpr' : 0,
|
||||
\ 'noremap' : 1,
|
||||
\ 'skip_space' : 1,
|
||||
\ 'highlight' : 3,
|
||||
\ 'hi_duration': 200,
|
||||
\ 'command' : [],
|
||||
\ 'linewise' : 1,
|
||||
\ 'autoindent' : -1,
|
||||
\ 'indentkeys' : 0,
|
||||
\ 'indentkeys+': 0,
|
||||
\ 'indentkeys-': 0,
|
||||
\ }
|
||||
let s:default_values.add.block = {
|
||||
\ 'cursor' : 'default',
|
||||
\ 'query_once' : 0,
|
||||
\ 'expr' : 0,
|
||||
\ 'listexpr' : 0,
|
||||
\ 'noremap' : 1,
|
||||
\ 'skip_space' : 1,
|
||||
\ 'highlight' : 3,
|
||||
\ 'hi_duration': 200,
|
||||
\ 'command' : [],
|
||||
\ 'linewise' : 0,
|
||||
\ 'autoindent' : -1,
|
||||
\ 'indentkeys' : 0,
|
||||
\ 'indentkeys+': 0,
|
||||
\ 'indentkeys-': 0,
|
||||
\ }
|
||||
let s:default_values.add.filter = printf('v:key =~# ''\%%(%s\)''', join(keys(s:default_values['add']['char']), '\|'))
|
||||
|
||||
let s:default_values.delete = {}
|
||||
let s:default_values.delete.char = {
|
||||
\ 'cursor' : 'default',
|
||||
\ 'noremap' : 1,
|
||||
\ 'regex' : 0,
|
||||
\ 'skip_space' : 1,
|
||||
\ 'skip_char' : 0,
|
||||
\ 'highlight' : 3,
|
||||
\ 'hi_duration': 200,
|
||||
\ 'command' : [],
|
||||
\ 'linewise' : 0,
|
||||
\ }
|
||||
let s:default_values.delete.line = {
|
||||
\ 'cursor' : 'default',
|
||||
\ 'noremap' : 1,
|
||||
\ 'regex' : 0,
|
||||
\ 'skip_space' : 2,
|
||||
\ 'skip_char' : 0,
|
||||
\ 'highlight' : 3,
|
||||
\ 'hi_duration': 200,
|
||||
\ 'command' : [],
|
||||
\ 'linewise' : 1,
|
||||
\ }
|
||||
let s:default_values.delete.block = {
|
||||
\ 'cursor' : 'default',
|
||||
\ 'noremap' : 1,
|
||||
\ 'regex' : 0,
|
||||
\ 'skip_space' : 1,
|
||||
\ 'skip_char' : 0,
|
||||
\ 'highlight' : 3,
|
||||
\ 'hi_duration': 200,
|
||||
\ 'command' : [],
|
||||
\ 'linewise' : 0,
|
||||
\ }
|
||||
let s:default_values.delete.filter = printf('v:key =~# ''\%%(%s\)''', join(keys(s:default_values['delete']['char']), '\|'))
|
||||
|
||||
let s:default_values.replace = {}
|
||||
let s:default_values.replace.char = {
|
||||
\ 'cursor' : 'default',
|
||||
\ 'query_once' : 0,
|
||||
\ 'regex' : 0,
|
||||
\ 'expr' : 0,
|
||||
\ 'listexpr' : 0,
|
||||
\ 'noremap' : 1,
|
||||
\ 'skip_space' : 1,
|
||||
\ 'skip_char' : 0,
|
||||
\ 'highlight' : 3,
|
||||
\ 'hi_duration': 200,
|
||||
\ 'command' : [],
|
||||
\ 'linewise' : 0,
|
||||
\ 'autoindent' : -1,
|
||||
\ 'indentkeys' : 0,
|
||||
\ 'indentkeys+': 0,
|
||||
\ 'indentkeys-': 0,
|
||||
\ }
|
||||
let s:default_values.replace.line = {
|
||||
\ 'cursor' : 'default',
|
||||
\ 'query_once' : 0,
|
||||
\ 'regex' : 0,
|
||||
\ 'expr' : 0,
|
||||
\ 'listexpr' : 0,
|
||||
\ 'noremap' : 1,
|
||||
\ 'skip_space' : 2,
|
||||
\ 'skip_char' : 0,
|
||||
\ 'highlight' : 3,
|
||||
\ 'hi_duration': 200,
|
||||
\ 'command' : [],
|
||||
\ 'linewise' : 0,
|
||||
\ 'autoindent' : -1,
|
||||
\ 'indentkeys' : 0,
|
||||
\ 'indentkeys+': 0,
|
||||
\ 'indentkeys-': 0,
|
||||
\ }
|
||||
let s:default_values.replace.block = {
|
||||
\ 'cursor' : 'default',
|
||||
\ 'query_once' : 0,
|
||||
\ 'regex' : 0,
|
||||
\ 'expr' : 0,
|
||||
\ 'listexpr' : 0,
|
||||
\ 'noremap' : 1,
|
||||
\ 'skip_space' : 1,
|
||||
\ 'skip_char' : 0,
|
||||
\ 'highlight' : 3,
|
||||
\ 'hi_duration': 200,
|
||||
\ 'command' : [],
|
||||
\ 'linewise' : 0,
|
||||
\ 'autoindent' : -1,
|
||||
\ 'indentkeys' : 0,
|
||||
\ 'indentkeys+': 0,
|
||||
\ 'indentkeys-': 0,
|
||||
\ }
|
||||
let s:default_values.replace.filter = printf('v:key =~# ''\%%(%s\)''', join(keys(s:default_values['replace']['char']), '\|'))
|
||||
|
||||
let s:default_values.textobj = {}
|
||||
let s:default_values.textobj.auto = {
|
||||
\ 'expr' : 0,
|
||||
\ 'listexpr' : 0,
|
||||
\ 'regex' : 0,
|
||||
\ 'skip_regex' : [],
|
||||
\ 'skip_regex_head': [],
|
||||
\ 'skip_regex_tail': [],
|
||||
\ 'quoteescape' : 0,
|
||||
\ 'expand_range' : -1,
|
||||
\ 'nesting' : 0,
|
||||
\ 'synchro' : 1,
|
||||
\ 'noremap' : 1,
|
||||
\ 'syntax' : [],
|
||||
\ 'inner_syntax' : [],
|
||||
\ 'match_syntax' : 0,
|
||||
\ 'skip_break' : 0,
|
||||
\ 'skip_expr' : [],
|
||||
\ }
|
||||
let s:default_values.textobj.query = {
|
||||
\ 'expr' : 0,
|
||||
\ 'listexpr' : 0,
|
||||
\ 'regex' : 0,
|
||||
\ 'skip_regex' : [],
|
||||
\ 'skip_regex_head': [],
|
||||
\ 'skip_regex_tail': [],
|
||||
\ 'quoteescape' : 0,
|
||||
\ 'expand_range' : -1,
|
||||
\ 'nesting' : 0,
|
||||
\ 'synchro' : 1,
|
||||
\ 'noremap' : 1,
|
||||
\ 'syntax' : [],
|
||||
\ 'inner_syntax' : [],
|
||||
\ 'match_syntax' : 0,
|
||||
\ 'skip_break' : 0,
|
||||
\ 'skip_expr' : [],
|
||||
\ }
|
||||
let s:default_values.textobj.filter = printf('v:key =~# ''\%%(%s\)''', join(keys(s:default_values.textobj.auto), '\|'))
|
||||
"}}}
|
||||
|
||||
" vim:set foldmethod=marker:
|
||||
" vim:set commentstring="%s:
|
||||
" vim:set ts=2 sts=2 sw=2:
|
||||
@ -0,0 +1,41 @@
|
||||
function! sandwich#util#echo(messages) abort "{{{
|
||||
echo ''
|
||||
for [mes, hi_group] in a:messages
|
||||
execute 'echohl ' . hi_group
|
||||
echon mes
|
||||
echohl NONE
|
||||
endfor
|
||||
endfunction
|
||||
"}}}
|
||||
function! sandwich#util#addlocal(recipes) abort "{{{
|
||||
return s:extend_local(a:recipes)
|
||||
endfunction
|
||||
"}}}
|
||||
function! sandwich#util#insertlocal(recipes) abort "{{{
|
||||
return s:extend_local(a:recipes, 0)
|
||||
endfunction
|
||||
"}}}
|
||||
function! sandwich#util#ftrevert(filetype) abort "{{{
|
||||
if exists('b:sandwich_recipes')
|
||||
call filter(b:sandwich_recipes, 'get(v:val, "__filetype__", "") !=# a:filetype')
|
||||
endif
|
||||
endfunction
|
||||
"}}}
|
||||
|
||||
|
||||
function! s:extend_local(recipes, ...) abort "{{{
|
||||
if !exists('b:sandwich_recipes')
|
||||
if exists('g:sandwich#recipes')
|
||||
let b:sandwich_recipes = deepcopy(g:sandwich#recipes)
|
||||
else
|
||||
let b:sandwich_recipes = deepcopy(g:sandwich#default_recipes)
|
||||
endif
|
||||
endif
|
||||
let i = get(a:000, 0, len(b:sandwich_recipes))
|
||||
call extend(b:sandwich_recipes, copy(a:recipes), i)
|
||||
return b:sandwich_recipes
|
||||
endfunction "}}}
|
||||
|
||||
" vim:set foldmethod=marker:
|
||||
" vim:set commentstring="%s:
|
||||
" vim:set ts=2 sts=2 sw=2:
|
||||
@ -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