mirror of
https://github.com/machakann/vim-sandwich.git
synced 2026-05-28 00:26:34 +02:00
3a7cffd07e
VimTeX's indentkeys contain space, so it should be escaped.
837 lines
27 KiB
VimL
837 lines
27 KiB
VimL
" 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:
|