mirror of
https://github.com/SpaceVim/SpaceVim.git
synced 2025-02-03 22:30:03 +08:00
248 lines
7.7 KiB
VimL
248 lines
7.7 KiB
VimL
|
" ___vital___
|
||
|
" NOTE: lines between '" ___vital___' is generated by :Vitalize.
|
||
|
" Do not modify the code nor insert new lines before '" ___vital___'
|
||
|
function! s:_SID() abort
|
||
|
return matchstr(expand('<sfile>'), '<SNR>\zs\d\+\ze__SID$')
|
||
|
endfunction
|
||
|
execute join(['function! vital#_gina#Vim#Buffer#Writer#import() abort', printf("return map({'replace': '', '_vital_created': '', 'new': ''}, \"vital#_gina#function('<SNR>%s_' . v:key)\")", s:_SID()), 'endfunction'], "\n")
|
||
|
delfunction s:_SID
|
||
|
" ___vital___
|
||
|
let s:t_number = type(0)
|
||
|
let s:running_writers = {}
|
||
|
let s:exiting = v:false
|
||
|
|
||
|
function! s:_vital_created(module) abort
|
||
|
" Default updatetime
|
||
|
let a:module.updatetime = 100
|
||
|
|
||
|
" Subscribe VimLeave to cancel 'replace()' during VimLeave
|
||
|
let sid = matchstr(
|
||
|
\ get(function('s:_vital_created'), 'name'),
|
||
|
\ '<SNR>\zs\d\+\ze'
|
||
|
\)
|
||
|
execute 'augroup vital_vim_buffer_writer_' . sid
|
||
|
execute 'autocmd! *'
|
||
|
execute 'autocmd VimLeave * let s:exiting = v:true'
|
||
|
execute 'augroup END'
|
||
|
endfunction
|
||
|
|
||
|
function! s:_iconv(bufnr, content) abort
|
||
|
let fileencoding = getbufvar(a:bufnr, '&fileencoding')
|
||
|
if fileencoding ==# '' || fileencoding ==? &encoding || empty(a:content)
|
||
|
return a:content
|
||
|
endif
|
||
|
let result = iconv(join(a:content, "\n"), fileencoding, &encoding)
|
||
|
return empty(result) ? a:content : split(result, '\n', 1)
|
||
|
endfunction
|
||
|
|
||
|
if exists('*nvim_buf_set_lines')
|
||
|
function! s:_replace(bufnr, start, end, replacement) abort
|
||
|
try
|
||
|
call nvim_buf_set_lines(a:bufnr, a:start, a:end, v:true, a:replacement)
|
||
|
catch /^Vim(call):\%(E5555: API call: \)\?Index out of bounds/
|
||
|
return 1
|
||
|
endtry
|
||
|
endfunction
|
||
|
else
|
||
|
function! s:_replace(bufnr, start, end, replacement) abort
|
||
|
if bufnr('%') == a:bufnr
|
||
|
return s:_replace_local(a:start, a:end, a:replacement)
|
||
|
elseif bufwinnr(a:bufnr) != -1
|
||
|
return s:_replace_shown(a:bufnr, a:start, a:end, a:replacement)
|
||
|
else
|
||
|
return s:_replace_hidden(a:bufnr, a:start, a:end, a:replacement)
|
||
|
endif
|
||
|
endfunction
|
||
|
|
||
|
" Used to replace content of current window
|
||
|
function! s:_replace_local(start, end, replacement) abort
|
||
|
" Calculate negative indices and validate
|
||
|
let lnum = line('$')
|
||
|
let s = a:start >= 0 ? a:start : lnum + 1 + a:start
|
||
|
let e = a:end >= 0 ? a:end : lnum + 1 + a:end
|
||
|
if s < 0 || s > lnum || e < 0 || e > lnum
|
||
|
return 1
|
||
|
endif
|
||
|
" Save current cursor pos
|
||
|
let cursor_saved = getcurpos()
|
||
|
try
|
||
|
" NOTE: append() affects jumplist so the following does not work
|
||
|
" " Insert replacement to the index
|
||
|
" let failed = append(s, a:replacement)
|
||
|
" " Shrink lines between {start} and {end} (exclusive)
|
||
|
" if !failed && e - s > 0
|
||
|
" let length = len(a:replacement)
|
||
|
" execute printf(
|
||
|
" \ 'silent keepjumps %d,%ddelete _',
|
||
|
" \ length + s + 1,
|
||
|
" \ length + e,
|
||
|
" \)
|
||
|
" endif
|
||
|
" NOTE: Use setline() instead
|
||
|
let suffixes = getline(e + 1, '$')
|
||
|
if s < lnum
|
||
|
execute printf(
|
||
|
\ 'silent keepjumps %d,$delete _',
|
||
|
\ s + 1,
|
||
|
\)
|
||
|
endif
|
||
|
return setline(s + 1, a:replacement + suffixes)
|
||
|
finally
|
||
|
call setpos('.', cursor_saved)
|
||
|
endtry
|
||
|
endfunction
|
||
|
|
||
|
" Used to replace content of shown buffer (in current tabpage)
|
||
|
function! s:_replace_shown(bufnr, start, end, replacement) abort
|
||
|
let winnr_saved = winnr()
|
||
|
execute printf('%dwincmd w', bufwinnr(a:bufnr))
|
||
|
try
|
||
|
return s:_replace_local(a:start, a:end, a:replacement)
|
||
|
finally
|
||
|
execute printf('%dwincmd w', winnr_saved)
|
||
|
endtry
|
||
|
endfunction
|
||
|
|
||
|
" Used to replace content of hidden buffer
|
||
|
function! s:_replace_hidden(bufnr, start, end, replacement) abort
|
||
|
let bufnr_saved = bufnr('%')
|
||
|
let bufhidden_saved = &l:bufhidden
|
||
|
setlocal bufhidden=hide
|
||
|
execute printf('keepjumps %dbuffer', a:bufnr)
|
||
|
try
|
||
|
return s:_replace_local(a:start, a:end, a:replacement)
|
||
|
finally
|
||
|
execute printf('keepjumps %dbuffer', bufnr_saved)
|
||
|
let &l:bufhidden = bufhidden_saved
|
||
|
endtry
|
||
|
endfunction
|
||
|
endif
|
||
|
|
||
|
|
||
|
" Public method ------------------------------------------------------------
|
||
|
function! s:replace(expr, start, end, replacement) abort
|
||
|
let bufnr = bufnr(a:expr)
|
||
|
if v:dying || s:exiting || !bufexists(bufnr)
|
||
|
return 1
|
||
|
endif
|
||
|
let data = s:_iconv(bufnr, a:replacement)
|
||
|
let modifiable = getbufvar(bufnr, '&modifiable')
|
||
|
let buflisted = getbufvar(bufnr, '&buflisted')
|
||
|
let readonly = getbufvar(bufnr, '&readonly')
|
||
|
try
|
||
|
call setbufvar(bufnr, '&modifiable', 1)
|
||
|
call setbufvar(bufnr, '&readonly', 0)
|
||
|
return s:_replace(bufnr, a:start, a:end, data)
|
||
|
finally
|
||
|
call setbufvar(bufnr, '&modifiable', modifiable)
|
||
|
call setbufvar(bufnr, '&buflisted', buflisted)
|
||
|
call setbufvar(bufnr, '&readonly', readonly)
|
||
|
endtry
|
||
|
endfunction
|
||
|
|
||
|
function! s:new(...) abort dict
|
||
|
let options = a:0 ? a:1 : {}
|
||
|
let options = extend({
|
||
|
\ 'bufnr': bufnr('%'),
|
||
|
\ 'updatetime': self.updatetime,
|
||
|
\}, a:0 ? a:1 : {},
|
||
|
\)
|
||
|
let writer = extend(options, s:writer)
|
||
|
let writer.__timer = v:null
|
||
|
let writer.__running = 0
|
||
|
let writer.__content = []
|
||
|
return writer
|
||
|
endfunction
|
||
|
|
||
|
|
||
|
" Writer instance ----------------------------------------------------------
|
||
|
function! s:_writer_timer_callback(writer, ...) abort
|
||
|
" Check environment if writer is available and kill forcedly if not
|
||
|
if v:dying || s:exiting || !bufexists(a:writer.bufnr)
|
||
|
call a:writer.kill()
|
||
|
return
|
||
|
endif
|
||
|
" To improve UX, flush only when a target buffer is shown
|
||
|
if bufwinnr(a:writer.bufnr) != -1
|
||
|
call a:writer.flush()
|
||
|
endif
|
||
|
" Kill writer when writing has been completed
|
||
|
if !a:writer.__running && empty(a:writer.__content)
|
||
|
" Content may has an extra line at EOF (POSIX text) so remove it.
|
||
|
if empty(getbufline(a:writer.bufnr, '$')[-1])
|
||
|
call s:replace(a:writer.bufnr, -2, -1, [])
|
||
|
endif
|
||
|
call a:writer.kill()
|
||
|
endif
|
||
|
endfunction
|
||
|
|
||
|
function! s:_writer_start() abort dict
|
||
|
if self.__timer isnot# v:null
|
||
|
return 1
|
||
|
endif
|
||
|
if has_key(s:running_writers, self.bufnr)
|
||
|
call s:running_writers[self.bufnr].kill()
|
||
|
endif
|
||
|
let self.__running = 1
|
||
|
let self.__timer = timer_start(
|
||
|
\ self.updatetime,
|
||
|
\ function('s:_writer_timer_callback', [self]),
|
||
|
\ { 'repeat': -1 },
|
||
|
\)
|
||
|
let s:running_writers[self.bufnr] = self
|
||
|
if has_key(self, 'on_start')
|
||
|
call self.on_start()
|
||
|
endif
|
||
|
endfunction
|
||
|
|
||
|
function! s:_writer_stop() abort dict
|
||
|
let self.__running = 0
|
||
|
endfunction
|
||
|
|
||
|
function! s:_writer_kill() abort dict
|
||
|
if self.__timer is# v:null
|
||
|
return 1
|
||
|
endif
|
||
|
silent! call timer_stop(self.__timer)
|
||
|
silent! unlet! s:running_writers[self.bufnr]
|
||
|
let self.__running = 0
|
||
|
let self.__timer = v:null
|
||
|
if has_key(self, 'on_exit')
|
||
|
call self.on_exit()
|
||
|
endif
|
||
|
endfunction
|
||
|
|
||
|
function! s:_writer_write(content) abort dict
|
||
|
if empty(self.__content)
|
||
|
let self.__content = ['']
|
||
|
endif
|
||
|
let self.__content[-1] .= a:content[0]
|
||
|
call extend(self.__content, a:content[1:])
|
||
|
endfunction
|
||
|
|
||
|
function! s:_writer_flush(...) abort dict
|
||
|
if empty(self.__content)
|
||
|
return 1
|
||
|
endif
|
||
|
try
|
||
|
let content = remove(self.__content, 0, -1)
|
||
|
let replacement = has_key(self, 'on_read')
|
||
|
\ ? self.on_read(content)
|
||
|
\ : deepcopy(content)
|
||
|
let replacement[0] = getbufline(self.bufnr, '$')[-1] . replacement[0]
|
||
|
call s:replace(self.bufnr, -2, -1, replacement)
|
||
|
catch /^Vim\%((\a\+)\)\=:E523/
|
||
|
" Vim raise 'E523: Not allowed here' when called in 'BufReadCmd'
|
||
|
" so rollback the operation
|
||
|
call extend(self.__content, content, 0)
|
||
|
endtry
|
||
|
endfunction
|
||
|
|
||
|
let s:writer = {
|
||
|
\ 'start': function('s:_writer_start'),
|
||
|
\ 'stop': function('s:_writer_stop'),
|
||
|
\ 'kill': function('s:_writer_kill'),
|
||
|
\ 'write': function('s:_writer_write'),
|
||
|
\ 'flush': function('s:_writer_flush'),
|
||
|
\}
|