diff options
Diffstat (limited to 'start/signify/autoload')
-rw-r--r-- | start/signify/autoload/sy.vim | 199 | ||||
-rw-r--r-- | start/signify/autoload/sy/debug.vim | 48 | ||||
-rw-r--r-- | start/signify/autoload/sy/fold.vim | 125 | ||||
-rw-r--r-- | start/signify/autoload/sy/highlight.vim | 94 | ||||
-rw-r--r-- | start/signify/autoload/sy/jump.vim | 29 | ||||
-rw-r--r-- | start/signify/autoload/sy/repo.vim | 512 | ||||
-rw-r--r-- | start/signify/autoload/sy/sign.vim | 275 | ||||
-rw-r--r-- | start/signify/autoload/sy/util.vim | 109 |
8 files changed, 1391 insertions, 0 deletions
diff --git a/start/signify/autoload/sy.vim b/start/signify/autoload/sy.vim new file mode 100644 index 0000000..b994aff --- /dev/null +++ b/start/signify/autoload/sy.vim @@ -0,0 +1,199 @@ +" vim: et sw=2 sts=2 + +scriptencoding utf-8 + +" Init: values {{{1 +let s:has_doau_modeline = v:version > 703 || v:version == 703 && has('patch442') + +" Function: #start {{{1 +function! sy#start() abort + if g:signify_locked + call sy#verbose('Locked.') + return + endif + + let sy_path = resolve(expand('%:p')) + if has('win32') + let sy_path = substitute(sy_path, '\v^(\w):\\\\', '\1:\\', '') + endif + + if s:skip(sy_path) + call sy#verbose('Skip file: '. sy_path) + if exists('b:sy') + call sy#sign#remove_all_signs(bufnr('')) + unlet! b:sy + endif + return + endif + + if !exists('b:sy') || b:sy.path != sy_path + call sy#verbose('Register new file: '. sy_path) + let b:sy = { + \ 'path': sy_path, + \ 'buffer': bufnr(''), + \ 'active': 0, + \ 'detecting': 0, + \ 'vcs': [], + \ 'hunks': [], + \ 'signid': 0x100, + \ 'updated_by': '', + \ 'stats': [-1, -1, -1], + \ 'info': { + \ 'dir': fnamemodify(sy_path, ':p:h'), + \ 'path': sy#util#escape(sy_path), + \ 'file': sy#util#escape(fnamemodify(sy_path, ':t')) + \ }} + if get(g:, 'signify_disable_by_default') + call sy#verbose('Disabled by default.') + return + endif + let b:sy.active = 1 + call sy#repo#detect() + elseif has('vim_starting') + call sy#verbose("Don't run Sy more than once during startup.") + return + elseif !b:sy.active + call sy#verbose('Inactive buffer.') + return + elseif empty(b:sy.vcs) + if get(b:sy, 'retry') + let b:sy.retry = 0 + call sy#verbose('Redetecting VCS.') + call sy#repo#detect() + else + if get(b:sy, 'detecting') + call sy#verbose('Detection is already in progress.') + else + call sy#verbose('No VCS found. Disabling.') + call sy#disable() + endif + endif + else + for vcs in b:sy.vcs + let job_id = get(b:, 'sy_job_id_'. vcs) + if type(job_id) != type(0) || job_id > 0 + call sy#verbose('Update is already in progress.', vcs) + else + call sy#verbose('Updating signs.', vcs) + call sy#repo#get_diff_start(vcs) + endif + endfor + endif +endfunction + +" Function: #set_signs {{{1 +function! sy#set_signs(sy, vcs, diff) abort + call sy#verbose('set_signs()', a:vcs) + + if a:sy.stats == [-1, -1, -1] + let a:sy.stats = [0, 0, 0] + endif + + if empty(a:diff) + call sy#verbose('No changes found.', a:vcs) + let a:sy.stats = [0, 0, 0] + call sy#sign#remove_all_signs(a:sy.buffer) + return + endif + + if get(g:, 'signify_line_highlight') + call sy#highlight#line_enable() + else + call sy#highlight#line_disable() + endif + + call sy#sign#process_diff(a:sy, a:vcs, a:diff) + + if exists('#User#Signify') + execute 'doautocmd' (s:has_doau_modeline ? '<nomodeline>' : '') 'User Signify' + endif +endfunction + +" Function: #stop {{{1 +function! sy#stop(bufnr) abort + let sy = getbufvar(a:bufnr, 'sy') + if empty(sy) + return + endif + + call sy#sign#remove_all_signs(a:bufnr) +endfunction + +" Function: #enable {{{1 +function! sy#enable() abort + if !exists('b:sy') + call sy#start() + return + endif + + if !b:sy.active + let b:sy.active = 1 + let b:sy.retry = 1 + call sy#start() + endif +endfunction + +" Function: #disable {{{1 +function! sy#disable() abort + if exists('b:sy') && b:sy.active + call sy#stop(b:sy.buffer) + let b:sy.active = 0 + let b:sy.stats = [-1, -1, -1] + endif +endfunction + +" Function: #toggle {{{1 +function! sy#toggle() abort + if !exists('b:sy') || !b:sy.active + call sy#enable() + else + call sy#disable() + endif +endfunction + +" Function: #buffer_is_active {{{1 +function! sy#buffer_is_active() + return exists('b:sy') && b:sy.active +endfunction + +" Function: #verbose {{{1 +function! sy#verbose(msg, ...) abort + if &verbose + if type(a:msg) == type([]) + for msg in a:msg + echomsg printf('[sy%s] %s', (a:0 ? ':'.a:1 : ''), msg) + endfor + else + echomsg printf('[sy%s] %s', (a:0 ? ':'.a:1 : ''), a:msg) + endif + endif +endfunction + +" Function: s:skip {{{1 +function! s:skip(path) + if &diff || !filereadable(a:path) + return 1 + endif + + if exists('g:signify_skip_filetype') + if has_key(g:signify_skip_filetype, &filetype) + return 1 + elseif has_key(g:signify_skip_filetype, 'help') && (&buftype == 'help') + return 1 + endif + endif + + if exists('g:signify_skip_filename') && has_key(g:signify_skip_filename, a:path) + return 1 + endif + + if exists('g:signify_skip_filename_pattern') + for pattern in g:signify_skip_filename_pattern + if a:path =~ pattern + return 1 + endif + endfor + endif + + return 0 +endfunction diff --git a/start/signify/autoload/sy/debug.vim b/start/signify/autoload/sy/debug.vim new file mode 100644 index 0000000..5beafd5 --- /dev/null +++ b/start/signify/autoload/sy/debug.vim @@ -0,0 +1,48 @@ +" vim: et sw=2 sts=2 + +scriptencoding utf-8 + +" Function: #list_active_buffers {{{1 +function! sy#debug#list_active_buffers() abort + for b in range(1, bufnr('$')) + if !buflisted(b) || empty(getbufvar(b, 'sy')) + continue + endif + + let sy = copy(getbufvar(b, 'sy')) + let path = remove(sy, 'path') + + echo "\n". path ."\n". repeat('=', strlen(path)) + + for k in ['active', 'buffer', 'vcs', 'stats', 'signid'] + if k == 'stats' + echo printf("%10s = %d added, %d changed, %d removed\n", + \ k, + \ sy.stats[0], + \ sy.stats[1], + \ sy.stats[2]) + else + echo printf("%10s = %s\n", k, string(sy[k])) + endif + endfor + + if empty(sy.hunks) + echo printf("%10s = %s\n", 'hunks', '[]') + else + for i in range(len(sy.hunks)) + if i == 0 + echo printf("%10s = start: %d, end: %d, IDs: %s\n", + \ 'hunks', + \ sy.hunks[i].start, + \ sy.hunks[i].end, + \ string(sy.hunks[i].ids)) + else + echo printf("%20s: %d, %s: %d, %s: %s\n", + \ 'start', sy.hunks[i].start, + \ 'end', sy.hunks[i].end, + \ 'IDs', string(sy.hunks[i].ids)) + endif + endfor + endif + endfor +endfunction diff --git a/start/signify/autoload/sy/fold.vim b/start/signify/autoload/sy/fold.vim new file mode 100644 index 0000000..e3afb97 --- /dev/null +++ b/start/signify/autoload/sy/fold.vim @@ -0,0 +1,125 @@ +" vim: et sw=2 sts=2 + +" Function: SignifyFoldExpr {{{1 +function! SignifyFoldExpr(lnum) + return s:levels[a:lnum] +endfunction + +" Function: SignifyFoldText {{{1 +function! SignifyFoldText() + let linelen = &textwidth ? &textwidth : 80 + let marker = &foldmarker[:stridx(&foldmarker, ',')-1] + let range = foldclosedend(v:foldstart) - foldclosed(v:foldstart) + 1 + + let left = substitute(getline(v:foldstart), marker, '', '') + let leftlen = len(left) + + let right = printf('%d [%d]', range, v:foldlevel) + let rightlen = len(right) + + let tmp = strpart(left, 0, linelen - rightlen) + let tmplen = len(tmp) + + if leftlen > tmplen + let left = strpart(tmp, 0, tmplen - 4) . '... ' + let leftlen = tmplen + endif + + let fill = repeat(' ', linelen - (leftlen + rightlen)) + + " return left . fill . right . repeat(' ', 100) + return left . fill . right +endfunction + +" Function: #dispatch {{{1 +function! sy#fold#dispatch(do_tab) abort + if a:do_tab + call sy#fold#enable(1) + else + call sy#fold#toggle() + endif +endfunction + +" Function: #enable {{{1 +function! sy#fold#enable(do_tab) abort + execute sy#util#return_if_no_changes() + + if a:do_tab + tabedit % + endif + + let [s:context0, s:context1] = get(g:, 'signify_fold_context', [3, 8]) + let s:levels = s:get_levels(s:get_lines()) + + setlocal foldexpr=SignifyFoldExpr(v:lnum) + setlocal foldtext=SignifyFoldText() + setlocal foldmethod=expr + setlocal foldlevel=0 +endfunction + +" Function: #disable {{{1 +function! sy#fold#disable() abort + let &l:foldmethod = b:sy_folded.method + let &l:foldtext = b:sy_folded.text + normal! zv +endfunction + +" Function: #toggle {{{1 +function! sy#fold#toggle() abort + if exists('b:sy_folded') + call sy#fold#disable() + if b:sy_folded.method == 'manual' + loadview + endif + unlet b:sy_folded + else + let b:sy_folded = { 'method': &foldmethod, 'text': &foldtext } + if &foldmethod == 'manual' + let old_vop = &viewoptions + mkview + let &viewoptions = old_vop + endif + call sy#fold#enable(0) + endif + + redraw! + call sy#start() +endfunction + +" Function: s:get_lines {{{1 +function! s:get_lines() abort + let signlist = sy#util#execute('sign place buffer='. b:sy.buffer) + + let lines = [] + for line in split(signlist, '\n')[2:] + call insert(lines, matchlist(line, '\v^\s+line\=(\d+)')[1], 0) + endfor + + return reverse(lines) +endfunction +" }}} + +" Function: s:get_levels {{{1 +function! s:get_levels(lines) abort + let levels = {} + + for line in range(1, line('$')) + let levels[line] = 2 + endfor + + for line in a:lines + for l in range(line - s:context1, line + s:context1) + if (l < 1) || (l > line('$')) + continue + endif + if levels[l] == 2 + let levels[l] = 1 + endif + for ll in range(line - s:context0, line + s:context0) + let levels[ll] = 0 + endfor + endfor + endfor + + return levels +endfunction diff --git a/start/signify/autoload/sy/highlight.vim b/start/signify/autoload/sy/highlight.vim new file mode 100644 index 0000000..2ebe507 --- /dev/null +++ b/start/signify/autoload/sy/highlight.vim @@ -0,0 +1,94 @@ +" vim: et sw=2 sts=2 + +scriptencoding utf-8 + +" Init: values {{{1 +if get(g:, 'signify_sign_show_text', 1) + let s:sign_add = get(g:, 'signify_sign_add', '+') + let s:sign_delete_first_line = get(g:, 'signify_sign_delete_first_line', '‾') + let s:sign_change = get(g:, 'signify_sign_change', '!') + let s:sign_changedelete = get(g:, 'signify_sign_changedelete', s:sign_change) +else + let s:sign_add = ' ' + let s:sign_delete_first_line = ' ' + let s:sign_change = ' ' + let s:sign_changedelete = ' ' +endif + +let s:sign_show_count = get(g:, 'signify_sign_show_count', 1) + +" Function: #setup {{{1 +function! sy#highlight#setup() abort + highlight default link SignifyLineAdd DiffAdd + highlight default link SignifyLineDelete DiffDelete + highlight default link SignifyLineDeleteFirstLine SignifyLineDelete + highlight default link SignifyLineChange DiffChange + highlight default link SignifyLineChangeDelete SignifyLineChange + + highlight default link SignifySignAdd DiffAdd + highlight default link SignifySignDelete DiffDelete + highlight default link SignifySignDeleteFirstLine SignifySignDelete + highlight default link SignifySignChange DiffChange + highlight default link SignifySignChangeDelete SignifySignChange +endfunction + +" Function: #line_enable {{{1 +function! sy#highlight#line_enable() abort + execute 'sign define SignifyAdd text='. s:sign_add 'texthl=SignifySignAdd linehl=SignifyLineAdd' + execute 'sign define SignifyChange text='. s:sign_change 'texthl=SignifySignChange linehl=SignifyLineChange' + execute 'sign define SignifyRemoveFirstLine text='. s:sign_delete_first_line 'texthl=SignifySignDeleteFirstLine linehl=SignifyLineDeleteFirstLine' + + if s:sign_show_count + let s:sign_changedelete = substitute(s:sign_changedelete, '^.\zs.*', '', '') + for n in range(1, 9) + execute 'sign define SignifyChangeDelete'. n 'text='. s:sign_changedelete . n 'texthl=SignifySignChangeDelete linehl=SignifyLineChangeDelete' + endfor + execute 'sign define SignifyChangeDeleteMore text='. s:sign_changedelete .'> texthl=SignifySignChangeDelete linehl=SignifyLineChangeDelete' + else + for n in range(1, 9) + execute 'sign define SignifyChangeDelete'. n 'text='. s:sign_changedelete 'texthl=SignifySignChangeDelete linehl=SignifyLineChangeDelete' + endfor + execute 'sign define SignifyChangeDeleteMore text='. s:sign_changedelete 'texthl=SignifySignChangeDelete linehl=SignifyLineChangeDelete' + endif + + let g:signify_line_highlight = 1 +endfunction + +" Function: #line_disable {{{1 +function! sy#highlight#line_disable() abort + execute 'sign define SignifyAdd text='. s:sign_add 'texthl=SignifySignAdd linehl=' + execute 'sign define SignifyChange text='. s:sign_change 'texthl=SignifySignChange linehl=' + execute 'sign define SignifyRemoveFirstLine text='. s:sign_delete_first_line 'texthl=SignifySignDeleteFirstLine linehl=' + + if s:sign_show_count + while strwidth(s:sign_changedelete) > 1 + let s:sign_changedelete = substitute(s:sign_changedelete, '.', '', '') + endwhile + for n in range(1, 9) + execute 'sign define SignifyChangeDelete'. n 'text='. s:sign_changedelete . n 'texthl=SignifySignChangeDelete linehl=' + endfor + execute 'sign define SignifyChangeDeleteMore text='. s:sign_changedelete .'> texthl=SignifySignChangeDelete linehl=' + else + for n in range(1, 9) + execute 'sign define SignifyChangeDelete'. n 'text='. s:sign_changedelete 'texthl=SignifySignChangeDelete linehl=' + endfor + execute 'sign define SignifyChangeDeleteMore text='. s:sign_changedelete 'texthl=SignifySignChangeDelete linehl=' + endif + + let g:signify_line_highlight = 0 +endfunction + +" Function: #line_toggle {{{1 +function! sy#highlight#line_toggle() abort + if get(g:, 'signify_line_highlight') + call sy#highlight#line_disable() + else + call sy#highlight#line_enable() + endif + + redraw! + call sy#start() +endfunction +" }}} + +call sy#highlight#setup() diff --git a/start/signify/autoload/sy/jump.vim b/start/signify/autoload/sy/jump.vim new file mode 100644 index 0000000..69756b1 --- /dev/null +++ b/start/signify/autoload/sy/jump.vim @@ -0,0 +1,29 @@ +" vim: et sw=2 sts=2 + +scriptencoding utf-8 + +" Function: #next_hunk {{{1 +function! sy#jump#next_hunk(count) + execute sy#util#return_if_no_changes() + + let lnum = line('.') + let hunks = filter(copy(b:sy.hunks), 'v:val.start > lnum') + let hunk = get(hunks, a:count - 1, get(hunks, -1, {})) + + if !empty(hunk) + execute 'sign jump '. hunk.ids[0] .' buffer='. b:sy.buffer + endif +endfunction + +" Function: #prev_hunk {{{1 +function! sy#jump#prev_hunk(count) + execute sy#util#return_if_no_changes() + + let lnum = line('.') + let hunks = filter(copy(b:sy.hunks), 'v:val.start < lnum') + let hunk = get(hunks, 0 - a:count, get(hunks, 0, {})) + + if !empty(hunk) + execute 'sign jump '. hunk.ids[0] .' buffer='. b:sy.buffer + endif +endfunction diff --git a/start/signify/autoload/sy/repo.vim b/start/signify/autoload/sy/repo.vim new file mode 100644 index 0000000..64bf07a --- /dev/null +++ b/start/signify/autoload/sy/repo.vim @@ -0,0 +1,512 @@ +" vim: et sw=2 sts=2 + +scriptencoding utf-8 + +" Function: #detect {{{1 +function! sy#repo#detect() abort + for vcs in s:vcs_list + let b:sy.detecting += 1 + call sy#repo#get_diff_start(vcs) + endfor +endfunction + +" Function: s:callback_nvim_stdout{{{1 +function! s:callback_nvim_stdout(_job_id, data, _event) dict abort + if empty(self.stdoutbuf) || empty(self.stdoutbuf[-1]) + let self.stdoutbuf += a:data + else + let self.stdoutbuf = self.stdoutbuf[:-2] + \ + [self.stdoutbuf[-1] . get(a:data, 0, '')] + \ + a:data[1:] + endif +endfunction + +" Function: s:callback_nvim_exit {{{1 +function! s:callback_nvim_exit(_job_id, exitval, _event) dict abort + call s:job_exit(self.bufnr, self.vcs, a:exitval, self.stdoutbuf) +endfunction + +" Function: s:callback_vim_stdout {{{1 +function! s:callback_vim_stdout(_job_id, data) dict abort + let self.stdoutbuf += [a:data] +endfunction + +" Function: s:callback_vim_close {{{1 +function! s:callback_vim_close(channel) dict abort + let job = ch_getjob(a:channel) + while 1 + if job_status(job) == 'dead' + let exitval = job_info(job).exitval + break + endif + sleep 10m + endwhile + call s:job_exit(self.bufnr, self.vcs, exitval, self.stdoutbuf) +endfunction + +" Function: s:job_exit {{{1 +function! s:job_exit(bufnr, vcs, exitval, diff) abort + call sy#verbose('job_exit()', a:vcs) + let sy = getbufvar(a:bufnr, 'sy') + if empty(sy) + call sy#verbose(printf('No b:sy found for %s', bufname(a:bufnr)), a:vcs) + return + elseif !empty(sy.updated_by) && sy.updated_by != a:vcs + call sy#verbose(printf('Signs already got updated by %s.', sy.updated_by), a:vcs) + return + elseif empty(sy.vcs) && sy.active + let sy.detecting -= 1 + endif + call sy#repo#get_diff_{a:vcs}(sy, a:exitval, a:diff) + call setbufvar(a:bufnr, 'sy_job_id_'.a:vcs, 0) +endfunction + +" Function: sy#get_diff_start {{{1 +function! sy#repo#get_diff_start(vcs) abort + call sy#verbose('get_diff_start()', a:vcs) + + let job_id = get(b:, 'sy_job_id_'.a:vcs) + " Neovim + if has('nvim') + if job_id + silent! call jobstop(job_id) + endif + + let [cmd, options] = s:initialize_job(a:vcs) + let [cwd, chdir] = sy#util#chdir() + + call sy#verbose(['CMD: '. string(cmd), 'CMD DIR: '. b:sy.info.dir, 'ORIG DIR: '. cwd], a:vcs) + + try + execute chdir fnameescape(b:sy.info.dir) + catch + echohl ErrorMsg + echomsg 'signify: Changing directory failed: '. b:sy.info.dir + echohl NONE + return + endtry + let b:sy_job_id_{a:vcs} = jobstart(cmd, extend(options, { + \ 'on_stdout': function('s:callback_nvim_stdout'), + \ 'on_exit': function('s:callback_nvim_exit'), + \ })) + execute chdir fnameescape(cwd) + + " Newer Vim + elseif has('patch-7.4.1967') + if type(job_id) != type(0) + silent! call job_stop(job_id) + endif + + let [cmd, options] = s:initialize_job(a:vcs) + let [cwd, chdir] = sy#util#chdir() + + call sy#verbose(['CMD: '. string(cmd), 'CMD DIR: '. b:sy.info.dir, 'ORIG DIR: '. cwd], a:vcs) + + try + execute chdir fnameescape(b:sy.info.dir) + catch + echohl ErrorMsg + echomsg 'signify: Changing directory failed: '. b:sy.info.dir + echohl NONE + return + endtry + let opts = { + \ 'in_io': 'null', + \ 'out_cb': function('s:callback_vim_stdout', options), + \ 'close_cb': function('s:callback_vim_close', options), + \ } + let b:sy_job_id_{a:vcs} = job_start(cmd, opts) + execute chdir fnameescape(cwd) + + " Older Vim + else + let diff = split(s:run(a:vcs), '\n') + call sy#repo#get_diff_{a:vcs}(b:sy, v:shell_error, diff) + endif +endfunction + +" Function: s:get_diff_end {{{1 +function! s:get_diff_end(sy, found_diff, vcs, diff) abort + call sy#verbose('get_diff_end()', a:vcs) + if a:found_diff + if index(a:sy.vcs, a:vcs) == -1 + let a:sy.vcs += [a:vcs] + endif + call sy#set_signs(a:sy, a:vcs, a:diff) + else + call sy#verbose('No valid diff found. Disabling this VCS.', a:vcs) + endif +endfunction + +" Function: #get_diff_git {{{1 +function! sy#repo#get_diff_git(sy, exitval, diff) abort + call sy#verbose('get_diff_git()', 'git') + let [found_diff, diff] = a:exitval ? [0, []] : [1, a:diff] + call s:get_diff_end(a:sy, found_diff, 'git', diff) +endfunction + +" Function: #get_diff_hg {{{1 +function! sy#repo#get_diff_hg(sy, exitval, diff) abort + call sy#verbose('get_diff_hg()', 'hg') + let [found_diff, diff] = a:exitval ? [0, []] : [1, a:diff] + call s:get_diff_end(a:sy, found_diff, 'hg', diff) +endfunction + +" Function: #get_diff_svn {{{1 +function! sy#repo#get_diff_svn(sy, exitval, diff) abort + call sy#verbose('get_diff_svn()', 'svn') + let [found_diff, diff] = a:exitval ? [0, []] : [1, a:diff] + call s:get_diff_end(a:sy, found_diff, 'svn', diff) +endfunction + +" Function: #get_diff_bzr {{{1 +function! sy#repo#get_diff_bzr(sy, exitval, diff) abort + call sy#verbose('get_diff_bzr()', 'bzr') + let [found_diff, diff] = (a:exitval =~ '[012]') ? [1, a:diff] : [0, []] + call s:get_diff_end(a:sy, found_diff, 'bzr', diff) +endfunction + +" Function: #get_diff_darcs {{{1 +function! sy#repo#get_diff_darcs(sy, exitval, diff) abort + call sy#verbose('get_diff_darcs()', 'darcs') + let [found_diff, diff] = a:exitval ? [0, []] : [1, a:diff] + call s:get_diff_end(a:sy, found_diff, 'darcs', diff) +endfunction + +" Function: #get_diff_fossil {{{1 +function! sy#repo#get_diff_fossil(sy, exitval, diff) abort + call sy#verbose('get_diff_fossil()', 'fossil') + let [found_diff, diff] = a:exitval ? [0, []] : [1, a:diff] + call s:get_diff_end(a:sy, found_diff, 'fossil', diff) +endfunction + +" Function: #get_diff_cvs {{{1 +function! sy#repo#get_diff_cvs(sy, exitval, diff) abort + call sy#verbose('get_diff_cvs()', 'cvs') + let [found_diff, diff] = [0, []] + if a:exitval == 1 + for diffline in a:diff + if diffline =~ '^+++' + let [found_diff, diff] = [1, a:diff] + break + endif + endfor + elseif a:exitval == 0 && len(a:diff) == 0 + let found_diff = 1 + endif + call s:get_diff_end(a:sy, found_diff, 'cvs', diff) +endfunction + +" Function: #get_diff_rcs {{{1 +function! sy#repo#get_diff_rcs(sy, exitval, diff) abort + call sy#verbose('get_diff_rcs()', 'rcs') + let [found_diff, diff] = a:exitval == 2 ? [0, []] : [1, a:diff] + call s:get_diff_end(a:sy, found_diff, 'rcs', diff) +endfunction + +" Function: #get_diff_accurev {{{1 +function! sy#repo#get_diff_accurev(sy, exitval, diff) abort + call sy#verbose('get_diff_accurev()', 'accurev') + let [found_diff, diff] = (a:exitval >= 2) ? [0, []] : [1, a:diff] + call s:get_diff_end(a:sy, found_diff, 'accurev', diff) +endfunction + +" Function: #get_diff_perforce {{{1 +function! sy#repo#get_diff_perforce(sy, exitval, diff) abort + call sy#verbose('get_diff_perforce()', 'perforce') + let [found_diff, diff] = a:exitval ? [0, []] : [1, a:diff] + call s:get_diff_end(a:sy, found_diff, 'perforce', diff) +endfunction + +" Function: #get_diff_tfs {{{1 +function! sy#repo#get_diff_tfs(sy, exitval, diff) abort + call sy#verbose('get_diff_tfs()', 'tfs') + let [found_diff, diff] = a:exitval ? [0, []] : [1, s:strip_context(a:diff)] + call s:get_diff_end(a:sy, found_diff, 'tfs', diff) +endfunction + +" Function: #get_stats {{{1 +function! sy#repo#get_stats() abort + return exists('b:sy') ? b:sy.stats : [-1, -1, -1] +endfunction + +" Function: #debug_detection {{{1 +function! sy#repo#debug_detection() + if !exists('b:sy') + echomsg 'signify: I cannot detect any changes!' + return + endif + + for vcs in s:vcs_list + let cmd = s:expand_cmd(vcs, g:signify_vcs_cmds) + echohl Statement + echo cmd + echo repeat('=', len(cmd)) + echohl NONE + + let diff = s:run(vcs) + if v:shell_error + echohl ErrorMsg + echo diff + echohl NONE + else + echo empty(diff) ? "<none>" : diff + endif + echo "\n" + endfor +endfunction + +" Function: #diffmode {{{1 +function! sy#repo#diffmode(do_tab) abort + execute sy#util#return_if_no_changes() + + let vcs = b:sy.updated_by + if !has_key(g:signify_vcs_cmds_diffmode, vcs) + echomsg 'SignifyDiff has no support for: '. vcs + echomsg 'Open an issue for it at: https://github.com/mhinz/vim-signify/issues' + return + endif + let cmd = s:expand_cmd(vcs, g:signify_vcs_cmds_diffmode) + call sy#verbose('SignifyDiff: '. cmd, vcs) + let ft = &filetype + let fenc = &fenc + if a:do_tab + tabedit % + endif + diffthis + let [cwd, chdir] = sy#util#chdir() + try + execute chdir fnameescape(b:sy.info.dir) + leftabove vnew + if has('iconv') + silent put =iconv(system(cmd), fenc, &enc) + else + silent put =system(cmd) + endif + finally + execute chdir fnameescape(cwd) + endtry + silent 1delete + set buftype=nofile bufhidden=wipe nomodified + let &filetype = ft + diffthis + wincmd p + normal! ]czt +endfunction + +" Function: s:initialize_job {{{1 +function! s:initialize_job(vcs) abort + let vcs_cmd = s:expand_cmd(a:vcs, g:signify_vcs_cmds) + if has('win32') + if has('nvim') + let cmd = &shell =~ '\v%(cmd|powershell)' ? vcs_cmd : ['sh', '-c', vcs_cmd] + else + if &shell =~ 'cmd' + let cmd = vcs_cmd + elseif empty(&shellxquote) + let cmd = join([&shell, &shellcmdflag, &shellquote, vcs_cmd, &shellquote]) + else + let cmd = join([&shell, &shellcmdflag, &shellxquote, vcs_cmd, &shellxquote]) + endif + endif + else + let cmd = ['sh', '-c', vcs_cmd] + endif + let options = { + \ 'stdoutbuf': [], + \ 'vcs': a:vcs, + \ 'bufnr': bufnr('%'), + \ } + return [cmd, options] +endfunction + +" Function: s:get_vcs_path {{{1 +function! s:get_vcs_path(vcs) abort + return (a:vcs =~# '\v(git|cvs|accurev|tfs)') ? b:sy.info.file : b:sy.info.path +endfunction + +" Function: s:expand_cmd {{{1 +function! s:expand_cmd(vcs, vcs_cmds) abort + let cmd = a:vcs_cmds[a:vcs] + let cmd = s:replace(cmd, '%f', s:get_vcs_path(a:vcs)) + let cmd = s:replace(cmd, '%d', s:difftool) + let cmd = s:replace(cmd, '%n', s:devnull) + return cmd +endfunction + +" Function: s:run {{{1 +function! s:run(vcs) + let [cwd, chdir] = sy#util#chdir() + try + execute chdir fnameescape(b:sy.info.dir) + let ret = system(s:expand_cmd(a:vcs, g:signify_vcs_cmds)) + catch + " This exception message can be seen via :SignifyDebugUnknown. + " E.g. unquoted VCS programs in vcd_cmds can lead to E484. + let ret = v:exception .' at '. v:throwpoint + finally + execute chdir fnameescape(cwd) + return ret + endtry +endfunction + +" Function: s:replace {{{1 +function! s:replace(cmd, pat, sub) + let parts = split(a:cmd, a:pat, 1) + return join(parts, a:sub) +endfunction + +" Function: s:strip_context {{{1 +function! s:strip_context(context) + let diff = [] + let hunk = [] + let state = 0 + let lines = a:context + let linenr = 0 + + while linenr < len(lines) + let line = lines[linenr] + + if state == 0 + if line =~ "^@@ " + let tokens = matchlist(line, '^@@ -\v(\d+),?(\d*) \+(\d+),?(\d*)') + let old_line = str2nr(tokens[1]) + let new_line = str2nr(tokens[3]) + let old_count = empty(tokens[2]) ? 1 : str2nr(tokens[2]) + let new_count = empty(tokens[4]) ? 1 : str2nr(tokens[4]) + let hunk = [] + let state = 1 + else + call add(diff,line) + endif + let linenr += 1 + elseif index([1,2,3],state) >= 0 && index(['\','/'],line[0]) >= 0 + let linenr += 1 + call add(hunk,line) + elseif state == 1 + if line[0] == ' ' + let old_line += 1 + let new_line += 1 + let old_count -= 1 + let new_count -= 1 + let linenr += 1 + else + let old_count_part = 0 + let new_count_part = 0 + let state = 2 + endif + elseif state == 2 + if line[0] == '-' + call add(hunk,line) + let old_count_part += 1 + let linenr += 1 + else + let state = 3 + endif + elseif state == 3 + if line[0] == '+' + call add(hunk,line) + let new_count_part += 1 + let linenr += 1 + else + call add(diff, printf("@@ -%d%s +%d%s @@",(old_count_part == 0 && old_line > 0) ? old_line -1 : old_line, old_count_part == 1 ? "" : printf(",%d", old_count_part), (new_count_part == 0 && new_line > 0) ? new_line - 1 : new_line, new_count_part == 1 ? "" : printf(",%d", new_count_part))) + let diff += hunk + let hunk = [] + let old_count -= old_count_part + let new_count -= new_count_part + let old_line += old_count_part + let new_line += new_count_part + let state = 1 + endif + endif + + if state > 0 && new_count <= 0 && old_count <= 0 + if len(hunk) > 0 + call add(diff, printf("@@ -%d%s +%d%s @@",(old_count_part == 0 && old_line > 0) ? old_line -1 : old_line, old_count_part == 1 ? "" : printf(",%d", old_count_part), (new_count_part == 0 && new_line > 0) ? new_line - 1 : new_line, new_count_part == 1 ? "" : printf(",%d", new_count_part))) + let diff = diff + hunk + let hunk = [] + endif + let state = 0 + endif + endwhile + if len(hunk) > 0 + call add(diff, printf("@@ -%d%s +%d%s @@",(old_count_part == 0 && old_line > 0) ? old_line -1 : old_line, old_count_part == 1 ? "" : printf(",%d", old_count_part), (new_count_part == 0 && new_line > 0) ? new_line - 1 : new_line, new_count_part == 1 ? "" : printf(",%d", new_count_part))) + let diff = diff + hunk + let hunk = [] + endif + return diff +endfunction + +" Variables {{{1 +let s:difftool = get(g:, 'signify_difftool', 'diff') +if executable(s:difftool) + let s:vcs_dict = { + \ 'git': 'git', + \ 'hg': 'hg', + \ 'svn': 'svn', + \ 'darcs': 'darcs', + \ 'bzr': 'bzr', + \ 'fossil': 'fossil', + \ 'cvs': 'cvs', + \ 'rcs': 'rcsdiff', + \ 'accurev': 'accurev', + \ 'perforce': 'p4', + \ 'tfs': 'tf' + \ } +else + call sy#verbose('No "diff" executable found. Disable support for svn, darcs, bzr.') + let s:vcs_dict = { + \ 'git': 'git', + \ 'hg': 'hg', + \ 'fossil': 'fossil', + \ 'cvs': 'cvs', + \ 'rcs': 'rcsdiff', + \ 'accurev': 'accurev', + \ 'perforce': 'p4', + \ 'tfs': 'tf' + \ } +endif + +let s:vcs_list = get(g:, 'signify_vcs_list', []) +if empty(s:vcs_list) + let s:vcs_list = keys(filter(s:vcs_dict, 'executable(v:val)')) +endif + +let s:default_vcs_cmds = { + \ 'git': 'git diff --no-color --no-ext-diff -U0 -- %f', + \ 'hg': 'hg diff --color=never --config aliases.diff= --nodates -U0 -- %f', + \ 'svn': 'svn diff --diff-cmd %d -x -U0 -- %f', + \ 'bzr': 'bzr diff --using %d --diff-options=-U0 -- %f', + \ 'darcs': 'darcs diff --no-pause-for-gui --no-unified --diff-opts=-U0 -- %f', + \ 'fossil': 'fossil diff --unified -c 0 -- %f', + \ 'cvs': 'cvs diff -U0 -- %f', + \ 'rcs': 'rcsdiff -U0 %f 2>%n', + \ 'accurev': 'accurev diff %f -- -U0', + \ 'perforce': 'p4 info '. sy#util#shell_redirect('%n') . (has('win32') ? ' &&' : ' && env P4DIFF= P4COLORS=') .' p4 diff -du0 %f', + \ 'tfs': 'tf diff -version:W -noprompt -format:Unified %f' + \ } + +let s:default_vcs_cmds_diffmode = { + \ 'git': 'git show HEAD:./%f', + \ 'hg': 'hg cat %f', + \ 'svn': 'svn cat %f', + \ 'bzr': 'bzr cat %f', + \ 'darcs': 'darcs show contents -- %f', + \ 'fossil': 'fossil cat %f', + \ 'cvs': 'cvs up -p -- %f 2>%n', + \ 'perforce': 'p4 print %f', + \ } + +if exists('g:signify_vcs_cmds') + call extend(g:signify_vcs_cmds, s:default_vcs_cmds, 'keep') +else + let g:signify_vcs_cmds = s:default_vcs_cmds +endif +if exists('g:signify_vcs_cmds_diffmode') + call extend(g:signify_vcs_cmds_diffmode, s:default_vcs_cmds_diffmode, 'keep') +else + let g:signify_vcs_cmds_diffmode = s:default_vcs_cmds_diffmode +endif + +let s:difftool = sy#util#escape(s:difftool) +let s:devnull = has('win32') || has ('win64') ? 'NUL' : '/dev/null' diff --git a/start/signify/autoload/sy/sign.vim b/start/signify/autoload/sy/sign.vim new file mode 100644 index 0000000..23b7038 --- /dev/null +++ b/start/signify/autoload/sy/sign.vim @@ -0,0 +1,275 @@ +" vim: et sw=2 sts=2 + +scriptencoding utf-8 + +" Init: values {{{1 +if get(g:, 'signify_sign_show_text', 1) + let s:sign_delete = get(g:, 'signify_sign_delete', '_') +else + let s:sign_delete = ' ' +endif + +let s:sign_show_count = get(g:, 'signify_sign_show_count', 1) +let s:delete_highlight = ['', 'SignifyLineDelete'] + +" Function: #id_next {{{1 +function! sy#sign#id_next(sy) abort + let id = a:sy.signid + let a:sy.signid += 1 + return id +endfunction + +" Function: #get_current_signs {{{1 +function! sy#sign#get_current_signs(sy) abort + let a:sy.internal = {} + let a:sy.external = {} + + let signlist = sy#util#execute('sign place buffer='. a:sy.buffer) + + for signline in split(signlist, '\n')[2:] + let tokens = matchlist(signline, '\v^\s+\S+\=(\d+)\s+\S+\=(\d+)\s+\S+\=(.*)$') + let line = str2nr(tokens[1]) + let id = str2nr(tokens[2]) + let type = tokens[3] + + if type =~# '^Signify' + " Handle ambiguous signs. Assume you have signs on line 3 and 4. + " Removing line 3 would lead to the second sign to be shifted up + " to line 3. Now there are still 2 signs, both one line 3. + if has_key(a:sy.internal, line) + execute 'sign unplace' a:sy.internal[line].id 'buffer='.a:sy.buffer + endif + let a:sy.internal[line] = { 'type': type, 'id': id } + else + let a:sy.external[line] = id + endif + endfor +endfunction + + +" Function: #process_diff {{{1 +function! sy#sign#process_diff(sy, vcs, diff) abort + let a:sy.signtable = {} + let a:sy.hunks = [] + let [added, modified, deleted] = [0, 0, 0] + + call sy#sign#get_current_signs(a:sy) + + " Determine where we have to put our signs. + for line in filter(a:diff, 'v:val =~ "^@@ "') + let a:sy.lines = [] + let ids = [] + + let tokens = matchlist(line, '^@@ -\v(\d+),?(\d*) \+(\d+),?(\d*)') + + let old_line = str2nr(tokens[1]) + let new_line = str2nr(tokens[3]) + + let old_count = empty(tokens[2]) ? 1 : str2nr(tokens[2]) + let new_count = empty(tokens[4]) ? 1 : str2nr(tokens[4]) + + " Workaround for non-conventional diff output in older Fossil versions: + " https://fossil-scm.org/forum/forumpost/834ce0f1e1 + " Fixed as of: https://fossil-scm.org/index.html/info/7fd2a3652ea7368a + if a:vcs == 'fossil' && new_line == 0 + let new_line = old_line - 1 - deleted + endif + + " 2 lines added: + + " @@ -5,0 +6,2 @@ this is line 5 + " +this is line 5 + " +this is line 5 + if (old_count == 0) && (new_count >= 1) + let added += new_count + let offset = 0 + while offset < new_count + let line = new_line + offset + let offset += 1 + if s:external_sign_present(a:sy, line) | continue | endif + call add(ids, s:add_sign(a:sy, line, 'SignifyAdd')) + endwhile + + " 2 lines removed: + + " @@ -6,2 +5,0 @@ this is line 5 + " -this is line 6 + " -this is line 7 + elseif (old_count >= 1) && (new_count == 0) + if s:external_sign_present(a:sy, new_line) | continue | endif + let deleted += old_count + if new_line == 0 + call add(ids, s:add_sign(a:sy, 1, 'SignifyRemoveFirstLine')) + elseif s:sign_show_count + let text = s:sign_delete . (old_count <= 99 ? old_count : '>') + while strwidth(text) > 2 + let text = substitute(text, '.', '', '') + endwhile + call add(ids, s:add_sign(a:sy, new_line, 'SignifyDelete'. old_count, text)) + else + call add(ids, s:add_sign(a:sy, new_line, 'SignifyDeleteMore', s:sign_delete)) + endif + + " 2 lines changed: + + " @@ -5,2 +5,2 @@ this is line 4 + " -this is line 5 + " -this is line 6 + " +this os line 5 + " +this os line 6 + elseif old_count == new_count + let modified += old_count + let offset = 0 + while offset < new_count + let line = new_line + offset + let offset += 1 + if s:external_sign_present(a:sy, line) | continue | endif + call add(ids, s:add_sign(a:sy, line, 'SignifyChange')) + endwhile + else + + " 2 lines changed; 2 lines removed: + + " @@ -5,4 +5,2 @@ this is line 4 + " -this is line 5 + " -this is line 6 + " -this is line 7 + " -this is line 8 + " +this os line 5 + " +this os line 6 + if old_count > new_count + let modified += new_count + let removed = old_count - new_count + let deleted += removed + let offset = 0 + while offset < new_count - 1 + let line = new_line + offset + let offset += 1 + if s:external_sign_present(a:sy, line) | continue | endif + call add(ids, s:add_sign(a:sy, line, 'SignifyChange')) + endwhile + let line = new_line + offset + if s:external_sign_present(a:sy, line) | continue | endif + call add(ids, s:add_sign(a:sy, line, (removed > 9) + \ ? 'SignifyChangeDeleteMore' + \ : 'SignifyChangeDelete'. removed)) + + " lines changed and added: + + " @@ -5 +5,3 @@ this is line 4 + " -this is line 5 + " +this os line 5 + " +this is line 42 + " +this is line 666 + else + let modified += old_count + let offset = 0 + while offset < old_count + let line = new_line + offset + let offset += 1 + if s:external_sign_present(a:sy, line) | continue | endif + call add(ids, s:add_sign(a:sy, line, 'SignifyChange')) + endwhile + while offset < new_count + let added += 1 + let line = new_line + offset + let offset += 1 + if s:external_sign_present(a:sy, line) | continue | endif + call add(ids, s:add_sign(a:sy, line, 'SignifyAdd')) + endwhile + endif + endif + + if !empty(ids) + call add(a:sy.hunks, { + \ 'ids' : ids, + \ 'start': a:sy.lines[0], + \ 'end' : a:sy.lines[-1] }) + endif + endfor + + " Remove obsoleted signs. + for line in filter(keys(a:sy.internal), '!has_key(a:sy.signtable, v:val)') + execute 'sign unplace' a:sy.internal[line].id 'buffer='.a:sy.buffer + endfor + + if has('gui_macvim') && has('gui_running') && mode() == 'n' + " MacVim needs an extra kick in the butt, when setting signs from the + " exit handler. :redraw would trigger a "hanging cursor" issue. + call feedkeys("\<c-l>", 'n') + endif + + if empty(a:sy.updated_by) && empty(a:sy.hunks) + call sy#verbose('Successful exit value, but no diff. Keep VCS for time being.', a:vcs) + return + endif + + call sy#verbose('Signs updated.', a:vcs) + let a:sy.updated_by = a:vcs + if len(a:sy.vcs) > 1 + call sy#verbose('Disable all other VCS.', a:vcs) + let a:sy.vcs = [a:vcs] + endif + + let a:sy.stats = [added, modified, deleted] +endfunction + +" Function: #remove_all_signs {{{1 +function! sy#sign#remove_all_signs(bufnr) abort + let sy = getbufvar(a:bufnr, 'sy') + + for hunk in sy.hunks + for id in hunk.ids + execute 'sign unplace' id 'buffer='.a:bufnr + endfor + endfor + + let sy.hunks = [] +endfunction + +" Function: s:add_sign {{{1 +function! s:add_sign(sy, line, type, ...) abort + call add(a:sy.lines, a:line) + let a:sy.signtable[a:line] = 1 + + if has_key(a:sy.internal, a:line) + " There is a sign on this line already. + if a:type == a:sy.internal[a:line].type + " Keep current sign since the new one is of the same type. + return a:sy.internal[a:line].id + else + " Update sign by overwriting the ID of the current sign. + let id = a:sy.internal[a:line].id + endif + endif + + if !exists('id') + let id = sy#sign#id_next(a:sy) + endif + + if a:type =~# 'SignifyDelete' + execute printf('sign define %s text=%s texthl=SignifySignDelete linehl=%s', + \ a:type, + \ a:1, + \ s:delete_highlight[g:signify_line_highlight]) + endif + execute printf('sign place %d line=%d name=%s buffer=%s', + \ id, + \ a:line, + \ a:type, + \ a:sy.buffer) + + return id +endfunction + +" Function: s:external_sign_present {{{1 +function! s:external_sign_present(sy, line) abort + if has_key(a:sy.external, a:line) + if has_key(a:sy.internal, a:line) + " Remove Sy signs from lines with other signs. + execute 'sign unplace' a:sy.internal[a:line].id 'buffer='.a:sy.buffer + endif + return 1 + endif +endfunction + diff --git a/start/signify/autoload/sy/util.vim b/start/signify/autoload/sy/util.vim new file mode 100644 index 0000000..43a9cc6 --- /dev/null +++ b/start/signify/autoload/sy/util.vim @@ -0,0 +1,109 @@ +" vim: et sw=2 sts=2 + +scriptencoding utf-8 + +" Function: #escape {{{1 +function! sy#util#escape(path) abort + if exists('+shellslash') + let old_ssl = &shellslash + if fnamemodify(&shell, ':t') == 'cmd.exe' + set noshellslash + else + set shellslash + endif + endif + + let path = shellescape(a:path) + + if exists('old_ssl') + let &shellslash = old_ssl + endif + + return path +endfunction + +" Function: #refresh_windows {{{1 +function! sy#util#refresh_windows() abort + if exists('*win_getid') + let winid = win_getid() + else + let winnr = winnr() + endif + + if !get(g:, 'signify_cmdwin_active') + keepjumps windo if exists('b:sy') | call sy#start() | endif + endif + + if exists('winid') + call win_gotoid(winid) + else + execute winnr .'wincmd w' + endif +endfunction + +" Function: #hunk_text_object {{{1 +function! sy#util#hunk_text_object(emptylines) abort + execute sy#util#return_if_no_changes() + + let lnum = line('.') + let hunks = filter(copy(b:sy.hunks), 'v:val.start <= lnum && v:val.end >= lnum') + + if empty(hunks) + echomsg 'signify: Here is no hunk.' + return + endif + + execute hunks[0].start + normal! V + + if a:emptylines + let lnum = hunks[0].end + while getline(lnum+1) =~ '^$' + let lnum += 1 + endwhile + execute lnum + else + execute hunks[0].end + endif +endfunction + +" Function: #shell_redirect {{{1 +function! sy#util#shell_redirect(path) abort + " if shellredir contains a %s it is replaced with the path + " otherwise, just append it (from :help shellredir: + " The name of the temporary file can be represented by '%s' if necessary + " (the file name is appended automatically if no %s appears in the value + " of this option) + if &shellredir =~# '%s' + return substitute(&shellredir, '\C%s', a:path, 'g') + else + return &shellredir .' '. a:path + endif +endfunction + +" Function: #chdir {{{1 +function! sy#util#chdir() abort + let chdir = haslocaldir() + \ ? 'lcd' + \ : (exists(':tcd') && haslocaldir(-1, 0)) ? 'tcd' : 'cd' + return [getcwd(), chdir] +endfunction + +" Function: #has_changes {{{1 +function! sy#util#return_if_no_changes() abort + if !exists('b:sy') || empty(b:sy.hunks) + echomsg 'signify: There are no changes.' + return 'return' + endif + return '' +endfunction + +" Function: #execute {{{1 +function! sy#util#execute(cmd) abort + let lang = v:lang + redir => output + silent! execute a:cmd + redir END + silent! execute 'language message' lang + return output +endfunction |