mirror of
https://github.com/SpaceVim/SpaceVim.git
synced 2025-02-09 16:30:05 +08:00
353 lines
10 KiB
VimL
Vendored
353 lines
10 KiB
VimL
Vendored
let s:Path = vital#gina#import('System.Filepath')
|
|
let s:String = vital#gina#import('Data.String')
|
|
|
|
let s:SCHEME = gina#command#scheme(expand('<sfile>'))
|
|
let s:WORKTREE = '@@'
|
|
let s:is_windows = has('win32') || has('win64')
|
|
|
|
|
|
function! gina#command#patch#call(range, args, mods) abort
|
|
call gina#core#options#help_if_necessary(a:args, s:get_options())
|
|
call gina#process#register(s:SCHEME, 1)
|
|
try
|
|
call s:call(a:range, a:args, a:mods)
|
|
finally
|
|
call gina#process#unregister(s:SCHEME, 1)
|
|
endtry
|
|
endfunction
|
|
|
|
function! gina#command#patch#complete(arglead, cmdline, cursorpos) abort
|
|
let args = gina#core#args#new(matchstr(a:cmdline, '^.*\ze .*'))
|
|
if a:arglead[0] ==# '-' || !empty(args.get(1))
|
|
let options = s:get_options()
|
|
return options.complete(a:arglead, a:cmdline, a:cursorpos)
|
|
endif
|
|
return gina#complete#filename#tracked(a:arglead, a:cmdline, a:cursorpos)
|
|
endfunction
|
|
|
|
|
|
|
|
" Private --------------------------------------------------------------------
|
|
function! s:get_options() abort
|
|
let options = gina#core#options#new()
|
|
call options.define(
|
|
\ '-h|--help',
|
|
\ 'Show this help.',
|
|
\)
|
|
call options.define(
|
|
\ '--opener=',
|
|
\ 'A Vim command to open a new buffer.',
|
|
\ ['edit', 'split', 'vsplit', 'tabedit', 'pedit'],
|
|
\)
|
|
call options.define(
|
|
\ '--group1=',
|
|
\ 'A window group name used for the 1st buffer.',
|
|
\)
|
|
call options.define(
|
|
\ '--group2=',
|
|
\ 'A window group name used for the 2nd buffer.',
|
|
\)
|
|
call options.define(
|
|
\ '--group3=',
|
|
\ 'A window group name used for the 3rd buffer.',
|
|
\)
|
|
call options.define(
|
|
\ '--line=',
|
|
\ 'An initial line number.',
|
|
\)
|
|
call options.define(
|
|
\ '--col=',
|
|
\ 'An initial column number.',
|
|
\)
|
|
call options.define(
|
|
\ '--oneside',
|
|
\ 'Use two buffers instead of three buffers.',
|
|
\)
|
|
call options.define(
|
|
\ '--diffoff',
|
|
\ 'Call diffoff! prior to open buffers.',
|
|
\)
|
|
return options
|
|
endfunction
|
|
|
|
function! s:build_args(git, args) abort
|
|
let args = a:args.clone()
|
|
let args.params.groups = [
|
|
\ args.pop('--group1', ''),
|
|
\ args.pop('--group2', ''),
|
|
\ args.pop('--group3', ''),
|
|
\]
|
|
let args.params.no_group = args.pop('--no-group', 0)
|
|
let args.params.opener = args.pop('--opener', 'tabnew')
|
|
let args.params.oneside = args.pop('--oneside', 0)
|
|
let args.params.diffoff = args.pop('--diffoff')
|
|
call gina#core#args#extend_path(a:git, args, args.pop(1))
|
|
call gina#core#args#extend_line(a:git, args, args.pop('--line'))
|
|
call gina#core#args#extend_col(a:git, args, args.pop('--col'))
|
|
return args.lock()
|
|
endfunction
|
|
|
|
function! s:open(n, mods, opener, rev, params) abort
|
|
if a:rev ==# s:WORKTREE
|
|
execute printf(
|
|
\ '%s Gina edit %s %s %s %s %s %s',
|
|
\ a:mods,
|
|
\ a:params.cmdarg,
|
|
\ gina#util#shellescape(a:opener, '--opener='),
|
|
\ gina#util#shellescape(a:params.groups[a:n], '--group='),
|
|
\ gina#util#shellescape(a:params.line, '--line='),
|
|
\ gina#util#shellescape(a:params.col, '--col='),
|
|
\ gina#util#shellescape(a:params.path),
|
|
\)
|
|
else
|
|
let treeish = gina#core#treeish#build(a:rev, a:params.path)
|
|
execute printf(
|
|
\ '%s Gina show %s %s %s %s %s %s',
|
|
\ a:mods,
|
|
\ a:params.cmdarg,
|
|
\ gina#util#shellescape(a:opener, '--opener='),
|
|
\ gina#util#shellescape(a:params.groups[a:n], '--group='),
|
|
\ gina#util#shellescape(a:params.line, '--line='),
|
|
\ gina#util#shellescape(a:params.col, '--col='),
|
|
\ gina#util#shellescape(treeish),
|
|
\)
|
|
endif
|
|
endfunction
|
|
|
|
function! s:call(range, args, mods) abort
|
|
let git = gina#core#get_or_fail()
|
|
let args = s:build_args(git, a:args)
|
|
let mods = gina#util#contain_direction(a:mods)
|
|
\ ? 'keepalt ' . a:mods
|
|
\ : join(['keepalt', 'rightbelow', a:mods])
|
|
|
|
if !empty(args.params.diffoff)
|
|
diffoff!
|
|
endif
|
|
|
|
let opener1 = args.params.opener
|
|
let opener2 = empty(matchstr(&diffopt, 'vertical'))
|
|
\ ? 'split'
|
|
\ : 'vsplit'
|
|
|
|
" Validate if all requirements exist
|
|
call gina#core#treeish#validate(git, ':0', args.params.path, printf(join([
|
|
\ 'The "%s" does not have an index content.',
|
|
\ 'Use "chaperon" instead if you would like to patch on conflicted file',
|
|
\], "\n"), args.params.path))
|
|
|
|
if args.params.oneside
|
|
call s:open(1, mods, opener1, ':0', args.params)
|
|
let bufnr2 = bufnr('%')
|
|
|
|
call s:open(2, mods, opener2, s:WORKTREE, args.params)
|
|
let bufnr3 = bufnr('%')
|
|
else
|
|
call s:open(0, mods, opener1, 'HEAD', args.params)
|
|
let bufnr1 = bufnr('%')
|
|
|
|
call s:open(1, mods, opener2, ':0', args.params)
|
|
let bufnr2 = bufnr('%')
|
|
|
|
call s:open(2, mods, opener2, s:WORKTREE, args.params)
|
|
let bufnr3 = bufnr('%')
|
|
endif
|
|
|
|
" WORKTREE
|
|
call gina#util#diffthis()
|
|
call s:define_plug_mapping('diffput', bufnr2)
|
|
call s:define_plug_mapping('diffget', bufnr2)
|
|
if g:gina#command#patch#use_default_mappings
|
|
nmap <buffer> dp <Plug>(gina-diffput)
|
|
nmap <buffer> do <Plug>(gina-diffget)
|
|
endif
|
|
|
|
" HEAD
|
|
if !args.params.oneside
|
|
execute printf('%dwincmd w', bufwinnr(bufnr1))
|
|
call gina#util#diffthis()
|
|
call s:define_plug_mapping('diffput', bufnr2)
|
|
if g:gina#command#patch#use_default_mappings
|
|
nmap <buffer> dp <Plug>(gina-diffput)
|
|
endif
|
|
endif
|
|
|
|
" INDEX
|
|
execute printf('%dwincmd w', bufwinnr(bufnr2))
|
|
call gina#util#diffthis()
|
|
call s:define_plug_mapping('diffput', bufnr3)
|
|
if !args.params.oneside
|
|
call s:define_plug_mapping('diffget', bufnr1, '-l')
|
|
endif
|
|
call s:define_plug_mapping('diffget', bufnr3, '-r')
|
|
if g:gina#command#patch#use_default_mappings
|
|
nmap <buffer> dp <Plug>(gina-diffput)
|
|
if !args.params.oneside
|
|
nmap <buffer> dol <Plug>(gina-diffget-l)
|
|
endif
|
|
nmap <buffer> dor <Plug>(gina-diffget-r)
|
|
endif
|
|
|
|
setlocal buftype=acwrite
|
|
setlocal modifiable
|
|
augroup gina_command_patch_internal
|
|
autocmd! * <buffer>
|
|
autocmd BufWriteCmd <buffer> call s:BufWriteCmd()
|
|
augroup END
|
|
|
|
call gina#util#diffupdate()
|
|
normal! zm
|
|
call gina#core#emitter#emit('command:called', s:SCHEME)
|
|
endfunction
|
|
|
|
function! s:define_plug_mapping(command, bufnr, ...) abort
|
|
let suffix = a:0 ? a:1 : ''
|
|
let lhs = printf('<Plug>(gina-%s%s)', a:command, suffix)
|
|
let rhs = printf(':<C-u>%s %d<CR>:diffupdate<CR>', a:command, a:bufnr)
|
|
call gina#util#map(lhs, rhs, {
|
|
\ 'mode': 'n',
|
|
\ 'noremap': 1,
|
|
\ 'silent': 1,
|
|
\})
|
|
endfunction
|
|
|
|
function! s:patch(git) abort
|
|
let abspath = gina#core#path#expand('%')
|
|
let path = gina#core#repo#relpath(a:git, abspath)
|
|
call gina#process#call_or_fail(a:git, [
|
|
\ 'add',
|
|
\ '--intent-to-add',
|
|
\ '--',
|
|
\ s:Path.realpath(abspath),
|
|
\])
|
|
let tempfile = tempname()
|
|
let tempfile1 = tempfile . '.index'
|
|
let tempfile2 = tempfile . '.buffer'
|
|
try
|
|
let diff = s:diff(a:git, path, getline(1, '$'), tempfile1, tempfile2)
|
|
let result = s:apply(a:git, diff)
|
|
finally
|
|
silent! call delete(tempfile1)
|
|
silent! call delete(tempfile2)
|
|
endtry
|
|
return result
|
|
endfunction
|
|
|
|
function! s:diff(git, path, buffer, tempfile1, tempfile2) abort
|
|
if writefile(s:index(a:git, a:path), a:tempfile1) == -1
|
|
return ''
|
|
endif
|
|
if writefile(a:buffer, a:tempfile2) == -1
|
|
return ''
|
|
endif
|
|
" NOTE:
|
|
" --no-index force --exit-code option.
|
|
" --exit-code mean that the program exits with 1 if there were differences
|
|
" and 0 means no differences
|
|
let result = gina#process#call(a:git, [
|
|
\ 'diff',
|
|
\ '--no-index',
|
|
\ '--unified=1',
|
|
\ '--',
|
|
\ a:tempfile1,
|
|
\ a:tempfile2,
|
|
\])
|
|
if !result.status
|
|
throw gina#core#revelator#info(
|
|
\ 'No difference between index and buffer'
|
|
\)
|
|
endif
|
|
return s:replace_filenames_in_diff(
|
|
\ result.stdout,
|
|
\ a:tempfile1,
|
|
\ a:tempfile2,
|
|
\ a:path,
|
|
\)
|
|
endfunction
|
|
|
|
function! s:index(git, path) abort
|
|
let result = gina#process#call(a:git, ['show', ':' . a:path])
|
|
if result.status
|
|
return []
|
|
endif
|
|
return result.stdout
|
|
endfunction
|
|
|
|
function! s:replace_filenames_in_diff(content, filename1, filename2, repl) abort
|
|
" replace tempfile1/tempfile2 in the header to a:filename
|
|
"
|
|
" diff --git a/<tempfile1> b/<tempfile2>
|
|
" index XXXXXXX..XXXXXXX XXXXXX
|
|
" --- a/<tempfile1>
|
|
" +++ b/<tempfile2>
|
|
"
|
|
let src1 = s:String.escape_pattern(a:filename1)
|
|
let src2 = s:String.escape_pattern(a:filename2)
|
|
if s:is_windows
|
|
" NOTE:
|
|
" '\' in {content} from 'git diff' are escaped so double escape is required
|
|
" to substitute such path
|
|
" NOTE:
|
|
" escape(src1, '\') cannot be used while other characters such as '.' are
|
|
" already escaped as well
|
|
let src1 = substitute(src1, '\\\\', '\\\\\\\\', 'g')
|
|
let src2 = substitute(src2, '\\\\', '\\\\\\\\', 'g')
|
|
endif
|
|
let repl = (a:filename1 =~# '^/' ? '/' : '') . a:repl
|
|
let content = copy(a:content)
|
|
let content[0] = substitute(content[0], src1, repl, '')
|
|
let content[0] = substitute(content[0], src2, repl, '')
|
|
let content[2] = substitute(content[2], src1, repl, '')
|
|
let content[3] = substitute(content[3], src2, repl, '')
|
|
return content
|
|
endfunction
|
|
|
|
function! s:apply(git, content) abort
|
|
let tempfile = tempname()
|
|
try
|
|
if writefile(a:content, tempfile) == -1
|
|
return
|
|
endif
|
|
let result = gina#process#call_or_fail(a:git, [
|
|
\ 'apply',
|
|
\ '--verbose',
|
|
\ '--cached',
|
|
\ '--',
|
|
\ tempfile,
|
|
\])
|
|
call gina#core#emitter#emit('command:called:patch')
|
|
return result
|
|
finally
|
|
silent! call delete(tempfile)
|
|
endtry
|
|
endfunction
|
|
|
|
function! s:BufWriteCmd() abort
|
|
let git = gina#core#get_or_fail()
|
|
let result = gina#core#revelator#call(function('s:patch'), [git])
|
|
if !empty(result)
|
|
setlocal nomodified
|
|
endif
|
|
call gina#util#diffupdate()
|
|
endfunction
|
|
|
|
|
|
" Event ----------------------------------------------------------------------
|
|
function! s:on_command_called_patch(...) abort
|
|
call gina#core#emitter#emit('modified:delay')
|
|
endfunction
|
|
|
|
if !exists('s:subscribed')
|
|
let s:subscribed = 1
|
|
call gina#core#emitter#subscribe(
|
|
\ 'command:called:patch',
|
|
\ function('s:on_command_called_patch')
|
|
\)
|
|
endif
|
|
|
|
|
|
" Config ---------------------------------------------------------------------
|
|
call gina#config(expand('<sfile>'), {
|
|
\ 'use_default_mappings': 1,
|
|
\})
|