├── .gitignore ├── test ├── test1.patch ├── test2.patch └── test.vim ├── Makefile ├── autoload ├── patchreview │ ├── cvs.vim │ ├── bazaar.vim │ ├── mercurial.vim │ ├── monotone.vim │ ├── subversion.vim │ ├── fossil.vim │ ├── git.vim │ └── perforce.vim └── patchreview.vim ├── plugin └── patchreview.vim ├── README.rst └── doc └── patchreview.txt /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | .DS_Store 3 | .DS_Store? 4 | ._* 5 | patchreview*.zip 6 | -------------------------------------------------------------------------------- /test/test1.patch: -------------------------------------------------------------------------------- 1 | # random comment line 2 | diff --git a/x b/x 3 | index 5d361ad..33757f0 100644 4 | --- a/x 5 | # random comment line 6 | +++ b/x 7 | @@ -1,7 +1,7 @@ 8 | foo 9 | bar 10 | 11 | -fubar 12 | # random comment line 13 | +foobar 14 | baz 15 | quux 16 | hoge 17 | @@ -9,6 +9,9 @@ 18 | # random comment line 19 | a 20 | b 21 | c 22 | +d 23 | +e 24 | # random comment line 25 | +f 26 | g 27 | h 28 | i 29 | # random comment line 30 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: test zip 2 | .DEFAULT: test 3 | 4 | test: 5 | vim -U NONE -u test/test.vim 6 | @echo "Done." 7 | 8 | zip: 9 | @if [ -z "$(RELEASE)" ]; then \ 10 | echo "RELEASE not defined." ; \ 11 | exit 1 ; \ 12 | fi 13 | @missing="$$(grep -L "$(RELEASE)" $$(git ls-files | grep -Ev '^(README|Makefile|test/|.gitignore)'))" ; \ 14 | if [ -n "$${missing}" ]; then \ 15 | echo "$${RELEASE} is missing from the following files" ; \ 16 | echo ; \ 17 | echo "$${missing}" ; \ 18 | exit 1 ; \ 19 | fi 20 | @if [ -f "patchreview-${RELEASE}.zip" ]; then \ 21 | echo "patchreview-${RELEASE}.zip already exists" ; \ 22 | exit 1; \ 23 | else \ 24 | zip -r patchreview-${RELEASE}.zip autoload plugin doc ; \ 25 | fi 26 | -------------------------------------------------------------------------------- /test/test2.patch: -------------------------------------------------------------------------------- 1 | cruft 2 | # random comment line 3 | more cruft 4 | ------------ 5 | 6 | *** ../a/b 2013-06-29 13:22:22.000000000 +0800 7 | # random comment line 8 | --- a/b 2013-10-12 20:22:22.000000000 +0800 9 | *************** 10 | *** 1,4 **** 11 | ! " stuff. 12 | # random comment line 13 | 14 | foo 15 | bar 16 | --- 1,4 ---- 17 | ! other stuff 18 | 19 | foo 20 | bar 21 | *** ../k/x/y 2013-00-01 12:36:90.000000000 +0800 22 | --- x/y 2013-00-02 00:13:04.000000000 +0100 23 | *************** 24 | *** 740,741 **** 25 | --- 740,743 ---- 26 | { /* Add new patch number below this line */ 27 | + /**/ 28 | # random comment line 29 | + 53, 30 | /**/ 31 | 32 | cruft at the end 33 | 34 | and some more 35 | # random comment line 36 | -------------------------------------------------------------------------------- /autoload/patchreview/cvs.vim: -------------------------------------------------------------------------------- 1 | " Author : Manpreet Singh < junkblocker@yahoo.com > " {{{ 2 | " Copyright : 2006-2024 by Manpreet Singh 3 | " Version : 2.1.1 4 | " License : This file is placed in the public domain. 5 | " No warranties express or implied. Use at your own risk. 6 | " Initialization {{{ 7 | let s:driver = {} 8 | let s:cvs = {} 9 | " }}} 10 | function! s:cvs.detect() " {{{ 11 | return isdirectory('CVS') 12 | endfunction 13 | " }}} 14 | function! s:cvs.get_diff() " {{{ 15 | return {'strip': 0, 'diff': s:driver.generate_diff('cvs diff -q -u')} 16 | endfunction 17 | " }}} 18 | function! patchreview#cvs#register(remote) "{{{ 19 | let s:driver = a:remote 20 | return s:cvs 21 | endfunction 22 | " }}} 23 | " vim: set et fdl=1 fdm=marker fenc= ff=unix ft=vim sts=0 sw=2 ts=2 tw=79 nowrap : 24 | "}}} 25 | -------------------------------------------------------------------------------- /autoload/patchreview/bazaar.vim: -------------------------------------------------------------------------------- 1 | " Author : Manpreet Singh < junkblocker@yahoo.com > " {{{ 2 | " Copyright : 2006-2024 by Manpreet Singh 3 | " Version : 2.1.1 4 | " License : This file is placed in the public domain. 5 | " No warranties express or implied. Use at your own risk. 6 | " Initialization {{{ 7 | let s:driver = {} 8 | let s:bazaar = {} 9 | " }}} 10 | function! s:bazaar.detect() " {{{ 11 | return isdirectory('.bzr') 12 | endfunction 13 | " }}} 14 | function! s:bazaar.get_diff() " {{{ 15 | let l:diff = s:driver.generate_diff('bzr diff') 16 | return {'strip': 0, 'diff': l:diff} 17 | endfunction 18 | " }}} 19 | function! patchreview#bazaar#register(remote) "{{{ 20 | let s:driver = a:remote 21 | return s:bazaar 22 | endfunction 23 | " }}} 24 | " vim: set et fdl=1 fdm=marker fenc= ff=unix ft=vim sts=0 sw=2 ts=2 tw=79 nowrap : 25 | "}}} 26 | -------------------------------------------------------------------------------- /autoload/patchreview/mercurial.vim: -------------------------------------------------------------------------------- 1 | " Author : Manpreet Singh < junkblocker@yahoo.com > " {{{ 2 | " Copyright : 2006-2024 by Manpreet Singh 3 | " Version : 2.1.1 4 | " License : This file is placed in the public domain. 5 | " No warranties express or implied. Use at your own risk. 6 | " Initialization {{{ 7 | let s:driver = {} 8 | let s:mercurial = {} 9 | "}}} 10 | function! s:mercurial.detect() " {{{ 11 | return isdirectory('.hg') 12 | endfunction 13 | " }}} 14 | function! s:mercurial.get_diff() " {{{ 15 | let l:diff = s:driver.generate_diff('hg diff') 16 | return {'strip': 1, 'diff': l:diff} 17 | endfunction 18 | " }}} 19 | function! patchreview#mercurial#register(remote) "{{{ 20 | let s:driver = a:remote 21 | return s:mercurial 22 | endfunction 23 | " }}} 24 | " vim: set et fdl=1 fdm=marker fenc= ff=unix ft=vim sts=0 sw=2 ts=2 tw=79 nowrap : 25 | "}}} 26 | -------------------------------------------------------------------------------- /autoload/patchreview/monotone.vim: -------------------------------------------------------------------------------- 1 | " Author : Manpreet Singh < junkblocker@yahoo.com > " {{{ 2 | " Copyright : 2006-2024 by Manpreet Singh 3 | " Version : 2.1.1 4 | " License : This file is placed in the public domain. 5 | " No warranties express or implied. Use at your own risk. 6 | " Initialization {{{ 7 | let s:driver = {} 8 | let s:monotone = {} 9 | " }}} 10 | function! s:monotone.detect() " {{{ 11 | return isdirectory('_MTN') 12 | endfunction 13 | " }}} 14 | function! s:monotone.get_diff() " {{{ 15 | let l:diff = s:driver.generate_diff('mtn diff --unified') 16 | return {'diff': 0, 'diff': l:diff} 17 | endfunction 18 | " }}} 19 | function! patchreview#monotone#register(remote) "{{{ 20 | let s:driver = a:remote 21 | return s:monotone 22 | endfunction 23 | " }}} 24 | " vim: set et fdl=1 fdm=marker fenc= ff=unix ft=vim sts=0 sw=2 ts=2 tw=79 nowrap : 25 | "}}} 26 | -------------------------------------------------------------------------------- /autoload/patchreview/subversion.vim: -------------------------------------------------------------------------------- 1 | " Author : Manpreet Singh < junkblocker@yahoo.com > " {{{ 2 | " Copyright : 2006-2024 by Manpreet Singh 3 | " Version : 2.1.1 4 | " License : This file is placed in the public domain. 5 | " No warranties express or implied. Use at your own risk. 6 | " Initialization {{{ 7 | let s:driver = {} 8 | let s:subversion = {} 9 | " }}} 10 | function! s:subversion.detect() " {{{ 11 | return isdirectory('.svn') 12 | endfunction 13 | " }}} 14 | function! s:subversion.get_diff() " {{{ 15 | let l:diff = s:driver.generate_diff('svn diff') 16 | return {'strip': 0, 'diff': l:diff} 17 | endfunction 18 | " }}} 19 | function! patchreview#subversion#register(remote) "{{{ 20 | let s:driver = a:remote 21 | return s:subversion 22 | endfunction 23 | " }}} 24 | " vim: set et fdl=1 fdm=marker fenc= ff=unix ft=vim sts=0 sw=2 ts=2 tw=79 nowrap : 25 | "}}} 26 | -------------------------------------------------------------------------------- /autoload/patchreview/fossil.vim: -------------------------------------------------------------------------------- 1 | " Author : Manpreet Singh < junkblocker@yahoo.com > " {{{ 2 | " Copyright : 2006-2024 by Manpreet Singh 3 | " Version : 2.1.1 4 | " License : This file is placed in the public domain. 5 | " No warranties express or implied. Use at your own risk. 6 | " Initialization {{{ 7 | let s:driver = {} 8 | let s:fossil = {} 9 | " }}} 10 | function! s:fossil.detect() " {{{ 11 | if ! executable('fossil') 12 | return 0 13 | endif 14 | call system('fossil info > /dev/null 2>&1') 15 | return ! v:shell_error 16 | endfunction 17 | " }}} 18 | function! s:fossil.get_diff() " {{{ 19 | let l:diff = s:driver.generate_diff('fossil diff --unified -v --') 20 | return {'strip': 0, 'diff': l:diff} 21 | endfunction 22 | " }}} 23 | function! patchreview#fossil#register(remote) "{{{ 24 | let s:driver = a:remote 25 | return s:fossil 26 | endfunction 27 | " }}} 28 | " vim: set et fdl=1 fdm=marker fenc= ff=unix ft=vim sts=0 sw=2 ts=2 tw=79 nowrap : 29 | "}}} 30 | -------------------------------------------------------------------------------- /autoload/patchreview/git.vim: -------------------------------------------------------------------------------- 1 | " Author : Manpreet Singh < junkblocker@yahoo.com > " {{{ 2 | " Copyright : 2006-2024 by Manpreet Singh 3 | " Version : 2.1.1 4 | " License : This file is placed in the public domain. 5 | " No warranties express or implied. Use at your own risk. 6 | " Initialization {{{ 7 | let s:driver = {} 8 | let s:git = {} 9 | " }}} 10 | function! s:git.detect() " {{{ 11 | return isdirectory('.git') || filereadable('.git') 12 | endfunction 13 | " }}} 14 | function! s:git.get_diff() " {{{ 15 | if has('win16') || has('win32') || has('win64') || has('win95') || has('gui_win32') || has('gui_win32s') 16 | let l:diff = s:driver.generate_diff('git diff -p -U5 --no-color --no-renames 2> NUL') 17 | else 18 | let l:diff = s:driver.generate_diff('git diff -p -U5 --no-color --no-renames 2>/dev/null') 19 | endif 20 | return {'strip': 1, 'diff': l:diff} 21 | endfunction 22 | " }}} 23 | function! patchreview#git#register(remote) "{{{ 24 | let s:driver = a:remote 25 | return s:git 26 | endfunction 27 | " }}} 28 | " vim: set et fdl=1 fdm=marker fenc= ff=unix ft=vim sts=0 sw=2 ts=2 tw=79 nowrap : 29 | "}}} 30 | -------------------------------------------------------------------------------- /autoload/patchreview/perforce.vim: -------------------------------------------------------------------------------- 1 | " Author : Manpreet Singh < junkblocker@yahoo.com > " {{{ 2 | " Copyright : 2006-2024 by Manpreet Singh 3 | " Version : 2.1.1 4 | " License : This file is placed in the public domain. 5 | " No warranties express or implied. Use at your own risk. 6 | " Initialization {{{ 7 | let s:driver = {} 8 | let s:perforce = {} 9 | " }}} 10 | function! s:perforce.detect() " {{{ 11 | try 12 | let l:lines = split(system('p4 set'), '[\n\r]') 13 | let l:count = len(l:lines) 14 | let l:idx = 0 15 | let l:proofs_required = 2 16 | while l:idx < l:count 17 | let l:line = l:lines[l:idx] 18 | let l:idx += 1 19 | if l:line =~ '\(P4CLIENT\|P4PORT\)=' 20 | let l:proofs_required -= 1 21 | endif 22 | endwhile 23 | return l:proofs_required == 0 24 | catch 25 | call s:driver.buflog('Exception ' . v:exception) 26 | call s:driver.buflog('From ' . v:throwpoint) 27 | return 0 28 | endtry 29 | endfunction 30 | " }}} 31 | function! s:perforce.get_diff() " {{{ 32 | " Excepted to return an array with diff lines in it 33 | let l:diff = [] 34 | let l:lines = split(system('p4 opened'), '[\n\r]') 35 | let l:linescount = len(l:lines) 36 | let l:line_num = 0 37 | while l:line_num < l:linescount 38 | let l:line = l:lines[l:line_num] 39 | call s:driver.progress('Processing ' . l:line) 40 | let l:line_num += 1 41 | let l:fwhere = substitute(l:line, '\#.*', '', '') 42 | let l:fwhere = split(system('p4 where ' . shellescape(l:fwhere)), '[\n\r]')[0] 43 | let l:fwhere = substitute(l:fwhere, '^.\+ ', '', '') 44 | let l:fwhere = substitute(l:fwhere, expand(getcwd(), ':p') . '/', '', '') 45 | if l:line =~ '\(delete \(default \)\?change\) .*\(text\|unicode\|utf16\)' 46 | call s:driver.progress('Fetching original ' . l:fwhere) 47 | let l:diff += ['--- ' . l:fwhere] 48 | let l:diff += ['+++ /dev/null'] 49 | let l:diffl = map(split(system('p4 print -q ' . shellescape(l:fwhere)), '[\n\r]'), '"-" . v:val') 50 | let l:diff += ['@@ -1,' . len(l:diffl) . ' +0,0 @@'] 51 | let l:diff += l:diffl 52 | unlet! l:diffl 53 | elseif l:line =~ '\(\(add\|branch\) \(default \)\?change\) .*\(text\|unicode\|utf16\)' 54 | call s:driver.progress('Reading ' . l:fwhere) 55 | let l:diff += ['--- /dev/null'] 56 | let l:diff += ['+++ ' . l:fwhere] 57 | let l:diffl = map(readfile(l:fwhere, "b"), '"+" . v:val') 58 | let l:diff += ['@@ -0,0 +1,' . len(l:diffl) . ' @@'] 59 | let l:diff += l:diffl 60 | unlet! l:diffl 61 | elseif l:line =~ '\(\(edit\|integrate\) \(default \)\?change\) .*\(text\|unicode\|utf16\)' 62 | call s:driver.progress('Diffing ' . l:fwhere) 63 | let l:diff += ['--- ' . l:fwhere] 64 | let l:diff += ['+++ ' . l:fwhere] 65 | let l:diffl = split(system('p4 diff -du ' . shellescape(l:fwhere)), '[\n\r]') 66 | let l:diff += l:diffl[2:] 67 | unlet! l:diffl 68 | else 69 | "throw "Do not recognize/handle this p4 opened file mode: " . l:line 70 | let l:diff += ['Binary files ' . l:fwhere . ' and ' . l:fwhere . ' differ'] 71 | endif 72 | endwhile 73 | return {'strip': 0, 'diff': l:diff} 74 | endfunction 75 | " }}} 76 | function! patchreview#perforce#register(remote) "{{{ 77 | let s:driver = a:remote 78 | return s:perforce 79 | endfunction 80 | " }}} 81 | " vim: set et fdl=1 fdm=marker fenc= ff=unix ft=vim sts=0 sw=2 ts=2 tw=79 nowrap : 82 | "}}} 83 | -------------------------------------------------------------------------------- /plugin/patchreview.vim: -------------------------------------------------------------------------------- 1 | " VIM plugin for doing single, multi-patch or diff code reviews {{{ 2 | " Home: https://github.com/junkblocker/patchreview-vim 3 | " vim.org: http://www.vim.org/scripts/script.php?script_id=1563 4 | " Version : 2.1.1 " {{{ 5 | " Author : Manpreet Singh < junkblocker@yahoo.com > 6 | " Copyright : 2006-2024 by Manpreet Singh 7 | " License : This file is placed in the public domain. 8 | " No warranties express or implied. Use at your own risk. 9 | " 10 | "}}} 11 | " Documentation: "{{{ 12 | " =========================================================================== 13 | " This plugin allows single or multiple, patch or diff based code reviews to 14 | " be easily done in VIM. VIM has :diffpatch command to do single file reviews 15 | " but a) can not handle patch files containing multiple patches or b) do 16 | " automated diff generation for various version control systems. This plugin 17 | " attempts to provide those functionalities. It opens each changed / added or 18 | " removed file diff in new tabs. 19 | " 20 | " Requirements: 21 | " 22 | " 1) VIM 7.0 or higher built with +diff option. 23 | " 24 | " 2) A gnu compatible patch command installed. This is the standard patch 25 | " command on Linux, Mac OS X, *BSD, Cygwin or /usr/bin/gpatch on newer 26 | " Solaris. 27 | " 28 | " Usage: 29 | " 30 | " Please see :help patchreview, :help diffreview or :help reversepatchreview 31 | " for details. 32 | " 33 | ""}}} 34 | " init " {{{ 35 | " Enabled only during development 36 | " unlet! g:loaded_patchreview 37 | " unlet! g:patchreview_patch 38 | " let g:patchreview_patch = 'patch' 39 | 40 | if &cp || ((! exists('g:patchreview_debug') || g:patchreview_debug == 0) && exists('g:loaded_patchreview')) 41 | finish 42 | endif 43 | let g:loaded_patchreview="2.1.1" 44 | if v:version < 700 45 | echomsg 'patchreview: You need at least Vim 7.0' 46 | finish 47 | endif 48 | if ! has('diff') 49 | call confirm('patchreview.vim plugin needs (G)VIM built with +diff support to work.') 50 | finish 51 | endif 52 | " }}} 53 | function! wrap_event_ignore(f, persist, ...) 54 | let l:syn = exists("syntax_on") || exists("syntax_manual") 55 | try 56 | let l:eventignore = &eventignore 57 | if get(g:, 'patchreview_ignore_events', 1) 58 | set eventignore=all 59 | endif 60 | if a:persist 61 | let g:patchreview_persist = 1 62 | else 63 | unlet! g:patchreview_persist 64 | endif 65 | let l:call_args = [] + deepcopy(a:000) 66 | call call(a:f, l:call_args) 67 | finally 68 | let &eventignore=l:eventignore 69 | if l:syn 70 | syn on 71 | endif 72 | endtry 73 | endfunction 74 | " End user commands "{{{ 75 | "============================================================================ 76 | " :PatchReview 77 | command! -nargs=+ -complete=file PatchReview call s:wrap_event_ignore('patchreview#patchreview', 0, ) 78 | command! -nargs=+ -complete=file PatchReviewPersist call s:wrap_event_ignore('patchreview#patchreview', 1, ) 79 | 80 | " :ReversePatchReview 81 | command! -nargs=+ -complete=file ReversePatchReview call s:wrap_event_ignore('patchreview#reverse_patchreview', 0, ) 82 | command! -nargs=+ -complete=file ReversePatchReviewPersist call s:wrap_event_ignore('patchreview#reverse_patchreview', 1, ) 83 | 84 | " :DiffReview 85 | command! -nargs=* -complete=file DiffReview call s:wrap_event_ignore('patchreview#diff_review', 0, ) 86 | command! -nargs=* -complete=file DiffReviewPersist call s:wrap_event_ignore('patchreview#diff_review', 1, ) 87 | "}}} 88 | " vim: set et fdl=1 fdm=marker fenc= ff=unix ft=vim sw=2 sts=0 ts=2 tw=79 nowrap : 89 | "}}} 90 | -------------------------------------------------------------------------------- /test/test.vim: -------------------------------------------------------------------------------- 1 | " Some simple tests suited to my setup. 2 | set nocompatible 3 | 4 | " Uses thinca/vim-prettyprint to dump information if available. 5 | function! s:dump(something) 6 | if exists(":PP") 7 | echomsg PP(a:something) 8 | endif 9 | endfunction 10 | 11 | unlet! s:mess 12 | redir => s:mess 13 | try 14 | exe 'set runtimepath=' . expand("%:p:h") 15 | for names in ['bundles', 'bundle'] 16 | let s:bpath = expand("~/.vim/bundle/prettyprint") 17 | if isdirectory(s:bpath) 18 | exe 'set runtimepath+=' . s:bpath 19 | endif 20 | endfor 21 | 22 | set verbose=1 23 | runtime plugin/patchreview.vim 24 | runtime plugin/prettyprint.vim 25 | set verbose=0 26 | 27 | """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" 28 | " argument checking is not broken 29 | """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" 30 | try 31 | PatchReview 32 | echomsg 'Arguments check failed.' 33 | catch /^Vim:E471:\s*Argument required:\s*PatchReview/ 34 | echomsg 'Arguments check passed.' 35 | catch /.*/ 36 | echomsg 'Arguments check failed. Unexpected error: ' . v:exception 37 | endtry 38 | 39 | """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" 40 | " Check patch parsing is not broken 41 | """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" 42 | function! s:test_parse(patchfile, expected_parse) 43 | unlet! g:patches 44 | let l:patchlines = patchreview#get_patchfile_lines(a:patchfile) 45 | call patchreview#extract_diffs(l:patchlines, 0) 46 | if g:patches !=# a:expected_parse 47 | echomsg "Parsing" a:patchfile "failed." 48 | echomsg "Expected:" 49 | call s:dump(a:expected_parse) 50 | echomsg "Actual:" 51 | call s:dump(g:patches) 52 | else 53 | echomsg "Parsing" a:patchfile "succeeded." 54 | endif 55 | endfunction 56 | 57 | call s:test_parse('test/test1.patch', { 58 | \ 'fail': '', 59 | \ 'patch': [ 60 | \ { 61 | \ 'content': [ 62 | \ '--- a/x', 63 | \ '+++ b/x', 64 | \ '@@ -1,7 +1,7 @@', 65 | \ ' foo', 66 | \ ' bar', 67 | \ ' ', 68 | \ '-fubar', 69 | \ '+foobar', 70 | \ ' baz', 71 | \ ' quux', 72 | \ ' hoge', 73 | \ '@@ -9,6 +9,9 @@', 74 | \ ' a', 75 | \ ' b', 76 | \ ' c', 77 | \ '+d', 78 | \ '+e', 79 | \ '+f', 80 | \ ' g', 81 | \ ' h', 82 | \ ' i' 83 | \ ], 84 | \ 'filename': 'a/x', 85 | \ 'type': '!' 86 | \ } 87 | \ ] 88 | \ }) 89 | 90 | call s:test_parse('test/test2.patch', { 91 | \ 'fail': '', 92 | \ 'patch': [ 93 | \ { 94 | \ 'content': [ 95 | \ '*** ../a/b 2013-06-29 13:22:22.000000000 +0800', 96 | \ '--- a/b 2013-10-12 20:22:22.000000000 +0800', 97 | \ '***************', 98 | \ '*** 1,4 ****', 99 | \ '! " stuff.', 100 | \ ' ', 101 | \ ' foo', 102 | \ ' bar', 103 | \ '--- 1,4 ----', 104 | \ '! other stuff', 105 | \ ' ', 106 | \ ' foo', 107 | \ ' bar' 108 | \ ], 109 | \ 'filename': 'a/b', 110 | \ 'type': '!' 111 | \ }, 112 | \ { 113 | \ 'content': [ 114 | \ '*** ../k/x/y 2013-00-01 12:36:90.000000000 +0800', 115 | \ '--- x/y 2013-00-02 00:13:04.000000000 +0100', 116 | \ '***************', 117 | \ '*** 740,741 ****', 118 | \ '--- 740,743 ----', 119 | \ ' { /* Add new patch number below this line */', 120 | \ '+ /**/', 121 | \ '+ 53,', 122 | \ ' /**/' 123 | \ ], 124 | \ 'filename': 'x/y', 125 | \ 'type': '!' 126 | \ } 127 | \ ] 128 | \ }) 129 | 130 | finally 131 | echomsg 'All done.' 132 | redir END 133 | $put =s:mess 134 | redraw! 135 | setlocal nomodified nomodifiable 136 | endtry 137 | " vim: set et fdm=manual fenc=utf-8 ff=unix ft=vim sts=0 sw=2 ts=2 tw=79 nowrap : 138 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | =============== 2 | patchreview.vim 3 | =============== 4 | 5 | .. contents:: 6 | :depth: 5 7 | :backlinks: top 8 | 9 | Introduction 10 | ============ 11 | 12 | The Patch Review plugin allows easy single or multipatch code or diff reviews. 13 | 14 | It opens each affected (modified/added/deleted) file in the patch or in a 15 | version controlled workspace diff in a diff view in a separate tab. 16 | 17 | VIM provides the ``:diffpatch`` command to do single file reviews but can not 18 | handle patch files containing multiple patches as is common with software 19 | development projects. This plugin provides that missing functionality. 20 | 21 | It also does not pollute the workspace like ``:diffpatch`` does by writing 22 | files in the workspace. 23 | 24 | It does automatic diff generation for various version control systems by 25 | running their respective diff commands. 26 | 27 | (Keywords: codereview, codereviews, code review, patch, patchutils, diff, 28 | diffpatch, patchreview, patchreviews, patch review, vcs, scm, mercurial, 29 | bazaar, hg, bzr, cvs, monotone, mtn, git, perforce, fossil) 30 | 31 | 32 | Requirements 33 | ============ 34 | 35 | 1. Vim 7.0 or higher built with ``+diff`` option. 36 | 37 | 2. A gnu compatible patch command installed. This is the standard patch command 38 | on any Linux, Mac OS X, \*BSD, Cygwin or ``/usr/bin/gpatch`` on newer 39 | Solaris/OpenSolairs. 40 | 41 | For Windows, UnxUtils ( http://unxutils.sourceforge.net/ ) provides a 42 | compatible patch implementation. However, you might need to set: 43 | 44 | .. code:: vim 45 | 46 | let g:patchreview_patch_needs_crlf = 1 47 | 48 | in your .vimrc file. 49 | 50 | 51 | Installation 52 | ============ 53 | 54 | Option 1: Use a bundle manager 55 | ------------------------------ 56 | 57 | Use your favorite vim package manager to install from the github repository for 58 | the project. Examples: 59 | 60 | .. code:: vim 61 | 62 | " With vim-plug 63 | Plug 'junkblocker/patchreview-vim' 64 | 65 | " With NeoBundle 66 | NeoBundle 'junkblocker/patchreview-vim' 67 | 68 | " With dein.vim 69 | call dein#add('junkblocker/patchreview-vim') 70 | 71 | " With Vundle 72 | Bundle 'junkblocker/patchreview-vim' 73 | 74 | 75 | Option 2: Installation with Pathogen 76 | ------------------------------------ 77 | 78 | .. code:: sh 79 | 80 | % cd ~/.vim/bundle 81 | % git clone https://github.com/junkblocker/patchreview-vim 82 | 83 | 84 | Option 3: Install by hand 85 | ------------------------- 86 | 87 | 1) Extract the zip from http://www.vim.org/scripts/script.php?script_id=1563 88 | in your ``$HOME/.vim`` or ``$VIM/vimfiles`` directory and restart vim. The 89 | directory location relevant to your platform can be seen by running: 90 | 91 | .. code:: vim 92 | 93 | :help add-global-plugin 94 | 95 | in vim. 96 | 97 | Alternatively, if installing from extracted form, copy the directories by 98 | hand: 99 | 100 | .. code:: sh 101 | 102 | % cp -r autoload doc plugin $HOME/.vim/ 103 | 104 | 2) Generate help tags to use help 105 | 106 | .. code:: vim 107 | 108 | :helptags $HOME/.vim/doc 109 | 110 | or, for example on Windows if you installed under ``$VIM/vimfiles``: 111 | 112 | .. code:: vim 113 | 114 | :helptags $VIM/vimfiles/doc 115 | 116 | etc. 117 | 118 | 119 | Usage 120 | ===== 121 | 122 | * Reviewing current changes in your workspace: 123 | 124 | .. code:: vim 125 | 126 | :DiffReview 127 | 128 | * Reviewing staged git changes: 129 | 130 | .. code:: vim 131 | 132 | :DiffReview git staged --no-color -U5 133 | 134 | * Reviewing a patch: 135 | 136 | .. code:: vim 137 | 138 | :PatchReview some.patch 139 | 140 | * Reviewing a previously applied patch (AKA reverse patch review): 141 | 142 | .. code:: vim 143 | 144 | :ReversePatchReview some.patch 145 | 146 | * See 147 | 148 | .. code:: vim 149 | 150 | :h patchreview 151 | 152 | for usage details. 153 | 154 | 155 | Limitations 156 | =========== 157 | 158 | The plugin can not handle diffs/patches which change the line ending types 159 | between DOS, UNIX and Mac etc. This is a general patching problem and is not 160 | specific to this plugin. 161 | 162 | 163 | Fork me 164 | ======= 165 | 166 | Fork this project at https://github.com/junkblocker/patchreview-vim 167 | -------------------------------------------------------------------------------- /doc/patchreview.txt: -------------------------------------------------------------------------------- 1 | *patchreview.txt* Vim global plugin for doing single, multi-patch or diff code reviews 2 | Version 2.1.1 (for Vim version 7.0 or higher) 3 | 4 | Author: Manpreet Singh < junkblocker@yahoo.com > 5 | Copyright (C) 2006-2024 by Manpreet Singh 6 | License : This file is placed in the public domain. 7 | No warranties express or implied. Use at your own risk. 8 | 9 | ============================================================================= 10 | 11 | CONTENTS *PatchReview* *DiffReview* 12 | *patchreview* *diffreview* 13 | *patchreview-contents* 14 | 15 | 1. Contents.........................................: |patchreview-contents| 16 | 2. Introduction.....................................: |patchreview-intro| 17 | 3. PatchReview Usage................................: |patchreview-usage| 18 | 3.1 DiffReview Usage.............................: |:DiffReview| 19 | 3.2 PatchReview Usage............................: |:PatchReview| 20 | 3.3 ReversePatchReview Usage.....................: |:ReversePatchReview| 21 | 4. PatchReview options..............................: |patchreview-options| 22 | 4.1 Optional path to the "patch" binary..........: |patchreview_patch| 23 | 4.2 Dealing with DOS line endings................: |patchreview_patch_needs_crlf| 24 | 4.3 Vertical split direction control.............: |patchreview_split_right| 25 | 4.4 Hide/show generated .rej files...............: |patchreview_unified_rejects| 26 | 4.5 Optional path to "wiggle" binary.............: |patchreview_wiggle| 27 | 4.6 Compatibility vs. speed......................: |patchreview_ignore_events| 28 | 4.7 Syntax highlighting vs. speed................: |patchreview_disable_syntax| 29 | 5. Hooks............................................: |patchreview-hooks| 30 | 6. Adding new version control plugins...............: |patchreview-plugins| 31 | 32 | ============================================================================= 33 | 34 | PatchReview Introduction *patchreview-intro* 35 | 36 | The Patch Review plugin allows easy single or multipatch code or diff reviews. 37 | 38 | It opens each affected (modified/added/deleted) file in the patch or in a 39 | version controlled workspace diff in a diff view in a separate tab. 40 | 41 | VIM provides the |:diffpatch| and related commands to do single file reviews 42 | but can not handle patch files containing multiple patches as is common with 43 | software development projects. This plugin provides that missing 44 | functionality. 45 | 46 | It also improves on |:diffpatch|'s behaviour of creating the patched files in 47 | the same directory as original file which can lead to project workspace 48 | pollution. 49 | 50 | It does automatic diff generation for various version control systems by 51 | running their diff command. 52 | 53 | ============================================================================= 54 | 55 | PatchReview Usage *patchreview-usage* *diffreview-usage* 56 | 57 | The following commands are available: 58 | 59 | |:DiffReview| 60 | |:PatchReview| 61 | |:ReversePatchReview| 62 | 63 | *:DiffReview* 64 | 65 | :DiffReview [[optional_strip_count] optional_diff_generation_command] 66 | 67 | Perform a diff review in the current directory under version control. 68 | Currently supports Mercurial (hg), Subversion (svn), CVS, Bazaar (bzr), 69 | Monotone, Perforce (p4) and fossil version control systems. 70 | 71 | If the optional diff generation command is supplied, that is used to 72 | generate the diff to review instead. This allows you to handle commands 73 | which are not the default diff command of the version control system. 74 | 75 | For example, if you want to review git staged diffs, you can run this: > 76 | 77 | :DiffReview 1 git staged --no-color -U5 78 | < 79 | Here, since git diffs have a/ and b/ file path prefixes in the output, a 80 | strip level of 1 is specified. However, that is only needed rarely as the 81 | plugin tries to guess the correct strip count itself. 82 | 83 | Note Only context or unified diff format patches are supported. 84 | 85 | *:PatchReview* 86 | 87 | :PatchReview patchfile_path [optional_source_directory [optional_strip_count]] 88 | 89 | Perform a patch review in the current directory based on the supplied 90 | patchfile_path. If optional_source_directory is specified, patchreview is 91 | done on that directory. 92 | 93 | Note Only context or unified diff format patches are supported. 94 | 95 | *:ReversePatchReview* 96 | 97 | :ReversePatchReview patchfile_path [optional_source_directory [optional_strip_count]] 98 | 99 | Perform a reverse patch review of an already applied patch in the current 100 | directory based on the supplied patchfile_path. If 101 | optional_source_directory is specified, patchreview is done on that 102 | directory. 103 | 104 | Note Only context or unified diff format patches are supported. 105 | 106 | 107 | ============================================================================= 108 | 109 | PatchReview Options *patchreview-options* 110 | 111 | The following variables are available: 112 | 113 | |patchreview_ignore_events| 114 | |patchreview_disable_syntax| 115 | |patchreview_foldlevel| 116 | |patchreview_patch| 117 | |patchreview_patch_needs_crlf| 118 | |patchreview_split_right| 119 | |patchreview_unified_rejects| 120 | |patchreview_wiggle| 121 | 122 | *g:patchreview_ignore_events* 123 | *patchreview_ignore_events* 124 | g:patchreview_ignore_events = {0|1} 125 | Optional flag to disable or not disable events during processing. 126 | If the value is 0, events/autocmds are left enabled and avoid 127 | issues like disabled syntax highlighting etc. 128 | 129 | The default is 1 for a huge increase in speed. 130 | 131 | Example: > 132 | let g:patchreview_disable_autocmd = 0 133 | < 134 | 135 | *g:patchreview_disable_syntax* 136 | *patchreview_disable_syntax* 137 | g:patchreview_disable_syntax = {0|1} 138 | Optional flag to disable or not disable syntax highlighting in diff buffers. 139 | If the value is 0, syntax highlighting is left untouched. 140 | 141 | The default is 1 for speed. 142 | 143 | Example: > 144 | let g:patchreview_disable_syntax = 0 145 | < 146 | 147 | *g:patchreview_foldlevel* 148 | *patchreview_foldlevel* 149 | g:patchreview_foldlevel = {number} 150 | Optional flag to control the foldlevel of the generated windows independent 151 | of any filetype or other foldlevel settings. 152 | 153 | The default is 0 for Vim's usual behavior. 154 | 155 | Example: > 156 | let g:patchreview_disable_syntax = 20 " Almost disable folding 157 | < 158 | 159 | *g:patchreview_patch* 160 | *patchreview_patch* 161 | g:patchreview_patch = {string} 162 | Optional path to patch binary. |PatchReview| tries to locate patch on 163 | system path automatically. If the binary is not on system path, this 164 | option tell |PatchReview| the full path to the binary. This option, if 165 | specified, overrides the default patch binary on the path. 166 | 167 | Examples: 168 | (On Windows with Cygwin) > 169 | let g:patchreview_patch = 'c:\\cygwin\\bin\\patch.exe' 170 | < 171 | (On *nix systems) > 172 | let g:patchreview_patch = '/usr/bin/gpatch' 173 | < 174 | 175 | *g:patchreview_patch_needs_crlf* 176 | *patchreview_patch_needs_crlf* 177 | g:patchreview_patch_needs_crlf = {0|1} 178 | Optional flag to deal with patch command requiring DOS line endings in 179 | patch files and diffs to work. Useful on Windows with UnxUtils patch.exe. 180 | 181 | The default value is 0. 182 | 183 | Example: 184 | (On Windows with Cygwin) > 185 | let g:patchreview_patch_needs_crlf = 1 186 | < 187 | 188 | *g:patchreview_split_right* 189 | *patchreview_split_right* 190 | g:patchreview_split_right = {0|1} 191 | Optional flag to keep the current source on the left and to open the diff 192 | splits towards the right side or the current source file. 193 | 194 | The default value is 0. 195 | 196 | Example: > 197 | let g:patchreview_split_right = 1 198 | < 199 | 200 | *g:patchreview_unified_rejects* 201 | *patchreview_unified_rejects* 202 | g:patchreview_unified_rejects = {0|1} 203 | Usually, the patch utility generates context format reject files (.rej). 204 | These can be a bit hard to read. So, we try to automatically convert 205 | these to the unified format. You can set this option to 0 to disable this 206 | behavior. 207 | 208 | NOTE: Converting to unified format requires the filterdiff utility 209 | installed on the PATH. filterdiff can be installed via the patchutils 210 | package for your platform. 211 | 212 | Example: > 213 | let g:patchreview_unified_rejects = 1 214 | < 215 | 216 | *g:patchreview_wiggle* 217 | *patchreview_wiggle* 218 | g:patchreview_wiggle = {string} 219 | Optional path to the wiggle ( http://linux.die.net/man/1/wiggle ) 220 | utility. If specified, |:PatchReview| and |:ReversePatchReview| commands 221 | use it to create an addition diff when a review fails with some 222 | conflicts/rejections. 223 | 224 | Examples: 225 | (On Windows with Cygwin) > 226 | let g:patchreview_patch = 'c:\\cygwin\\bin\\wiggle.exe' 227 | < 228 | (On *nix systems) > 229 | let g:patchreview_patch = '/usr/local/bin/wiggle' 230 | < 231 | 232 | Hooks *patchreview-hooks* 233 | 234 | The following hook functions are available: 235 | 236 | |patchreview_prefunc| 237 | |patchreview_postfunc| 238 | 239 | *g:patchreview_prefunc* 240 | *patchreview_prefunc* 241 | *g:patchreview_postfunc* 242 | *patchreview_postfunc* 243 | g:patchreview_prefunc = '{function}' 244 | g:patchreview_postfunc = '{function}' 245 | 246 | A callback function to be called before starting or finishing review 247 | respectively. 248 | 249 | The first argument is review type - 'Patch Review', 'Diff Review' or 250 | 'Reverse Patch Review'. 251 | 252 | The rest of the arguments are the arguments passed to the call. 253 | 254 | For example, the following shows a way to use this to 255 | 256 | 1. create an audio notifications when a long review finishes on a Linux 257 | or Mac OS X machine. 258 | 2. open the original patch file too when review is completed. > 259 | 260 | function NotifyAndOpenOriginalPatchfile(review_kind, ...) 261 | if executable('espeak') && executable('aplay') 262 | call system('espeak --stdout ' 263 | \ . shellescape(a:review_kind . ' completed. ') 264 | \ . ' | aplay > /dev/null 2>&1') 265 | elseif executable('say') 266 | call system('say ' . fnameescape(a:review_kind) . ' completed.') 267 | endif 268 | if a:review_kind =~ 'Patch' && filereadable(a:1) 269 | if expand('%') != '' || line('$') != 1 || getline(1) != '' || &modified 270 | wincmd n 271 | endif 272 | exe 'e ' . fnameescape(a:1) 273 | endif 274 | endfunction 275 | let g:patchreview_postfunc = 'NotifyAndOpenOriginalPatchfile' 276 | < 277 | 278 | Adding new version control plugins *patchreview-plugins* 279 | 280 | New version control detection plugins can be written by the end user and 281 | dropped in the autoload/patchreview/ directory. See existing plugins for 282 | examples. 283 | 284 | ------------------------------------------------------------------------------- 285 | vim: ft=help:ts=8:sts=8:sw=8:noexpandtab:tw=78:norl: 286 | -------------------------------------------------------------------------------- /autoload/patchreview.vim: -------------------------------------------------------------------------------- 1 | " VIM plugin for doing single, multi-patch or diff code reviews {{{ 2 | " Home: http://www.vim.org/scripts/script.php?script_id=1563 3 | 4 | " Version : 2.1.1 {{{ 5 | " Author : Manpreet Singh < junkblocker@yahoo.com > 6 | " Copyright : 2006-2024 by Manpreet Singh 7 | " License : This file is placed in the public domain. 8 | " No warranties express or implied. Use at your own risk. 9 | " 10 | " Changelog : {{{ 11 | " 12 | " 2.1.1 - Protect against project style plugins automatically changing the 13 | " current directory and breaking us 14 | " 2.1.0 - Bugfix in handing added/deleted files 15 | " 2.0.1 - Bugfix in handing added/deleted files 16 | " 2.0.0 - Allow keeping autocmds enabled during processing with the 17 | " g:patchreview_ignore_events flag 18 | " - Better strip level guessing 19 | " - Miscellaneous accumulated fixes 20 | " 1.3.0 - Added g:patchreview_foldlevel setting 21 | " - Added g:patchreview_disable_syntax control syntax highlighting 22 | " - Prevent most autocmds from executing during plugin execution 23 | " - Add dein.vim & consolidate install instructions 24 | " - Linting, README and help fixes and improvements 25 | " - Fix and enhance the notification example 26 | " - Fix DiffReview count style invocation 27 | " - ignore shell error for bzr diff as it returns 1 on differences 28 | " (lutostag) 29 | " 30 | " 1.2.1 - Added pathogen instructions (Daniel Lobato García) 31 | " Fixed subversion support (wilywampa, Michael Leuchtenburg) 32 | " 33 | " 1.2.0 - Support # prefixed comment lines in patches 34 | " Better FreeBSD detection 35 | " 36 | " 1.1.1 - Better filepath/strip level calculation 37 | " Some cleanup 38 | " 39 | " 1.1.0 - Added option to open diffs on the right 40 | " - Added some basic tests (internal) 41 | " 42 | " 1.0.9 - Commented lines left uncommented 43 | " 44 | " 1.0.8 - Fix embarassing breakage 45 | " - Ensure folds are closed at diff creation 46 | " - Make string truncation wide character aware for vim older than 7.3 47 | " - Minor code style change 48 | " - Show parse result in case of inapplicable patch 49 | " - Prevent empty blank line 1 in log buffer 50 | " 51 | " 1.0.7 - Added support for fossil 52 | " - Internal code style changes. 53 | " - Minor improvement in help friendliness 54 | " 55 | " 1.0.6 - Convert rejects to unified format if possible unless disabled 56 | " 57 | " 1.0.5 - Fixed context format patch handling 58 | " minor *BSD detection improvement 59 | " 60 | " 1.0.4 - patchreview was broken in vim 7.2 61 | " 62 | " 1.0.3 - Perforce diff was skipping files added via branching 63 | " 64 | " 1.0.2 - Fix for system's patch command on BSDs. 65 | " - Better exception handling 66 | " 67 | " 1.0.1 - Set foldmethod to diff for patched buffers 68 | " 69 | " 1.0 - Added Perforce support 70 | " - Add support for arbitrary diff generation commands 71 | " - Added ability to plug in new version control systems by others 72 | " - Added ability to create diffs in a way which lets session 73 | " save/restore work better 74 | " 75 | " 0.4 - Added wiggle support 76 | " - Added ReversePatchReview command 77 | " - Added automatic strip count guessing support 78 | " - Handle paths with special characters in them 79 | " - Remove patchutils use completely as we can do more with the pure 80 | " vim version 81 | " - Show diff if rejections but partially applied 82 | " - Added patchreview_pre/postfunc for long postreview jobs 83 | " 84 | " 0.3.2 - Some diff extraction fixes and behavior improvement. 85 | " 86 | " 0.3.1 - Do not open the status buffer in all tabs. 87 | " 88 | " 0.3 - Added git diff support 89 | " - Added some error handling for open files by opening them in read 90 | " only mode 91 | " 92 | " 0.2.2 - Security fixes by removing custom tempfile creation 93 | " - Removed need for DiffReviewCleanup/PatchReviewCleanup 94 | " - Better command execution error detection and display 95 | " - Improved diff view and folding by ignoring modelines 96 | " - Improved tab labels display 97 | " 98 | " 0.2.1 - Minor temp directory autodetection logic and cleanup 99 | " 100 | " 0.2 - Removed the need for filterdiff by implementing it in pure vim script 101 | " - Added DiffReview command for reverse (changed repository to 102 | " pristine state) reviews. 103 | " (PatchReview does pristine repository to patch review) 104 | " - DiffReview does automatic detection and generation of diffs for 105 | " various Source Control systems 106 | " - Skip load if VIM 7.0 or higher unavailable 107 | " 108 | " 0.1 - First released 109 | "}}} 110 | "}}} 111 | " Initialization {{{ 112 | " Enabled only during development 113 | " unlet! g:loaded_patchreview 114 | " unlet! g:patchreview_patch 115 | " let g:patchreview_patch = 'patch' 116 | 117 | " load only once 118 | if &cp || v:version < 700 119 | finish 120 | endif 121 | 122 | let s:msgbufname = '--PatchReview_Messages--' 123 | 124 | let s:me = {} 125 | 126 | let s:vsplit = get(g:, 'patchreview_split_right', 0) 127 | \ ? 'vertical rightbelow' 128 | \ : 'vertical leftabove' 129 | 130 | let s:disable_syntax = get(g:, 'patchreview_disable_syntax', 1) 131 | 132 | let s:foldlevel = get(g:, 'patchreview_foldlevel', 0) 133 | 134 | let s:modules = {} 135 | " }}} 136 | " Functions {{{ 137 | let s:_executable = {} 138 | function! s:executable(expr) abort 139 | let s:_executable[a:expr] = get(s:_executable, a:expr, executable(a:expr)) 140 | return s:_executable[a:expr] 141 | endfunction 142 | " String display width utilities {{{ 143 | " The string display width functions were imported from vital.vim 144 | " https://github.com/vim-jp/vital.vim (Public Domain) 145 | if exists('*strdisplaywidth') 146 | " Use builtin function. 147 | function! s:me.wcswidth(str) " {{{ 148 | return strdisplaywidth(a:str) 149 | endfunction 150 | " }}} 151 | else 152 | function! s:me.wcswidth(str) " {{{ 153 | if a:str =~# '^[\x00-\x7f]*$' 154 | return 2 * strlen(a:str) 155 | \ - strlen(substitute(a:str, '[\x00-\x08\x0b-\x1f\x7f]', '', 'g')) 156 | end 157 | 158 | let l:mx_first = '^\(.\)' 159 | let l:str = a:str 160 | let l:width = 0 161 | while 1 162 | let l:ucs = char2nr(substitute(l:str, l:mx_first, '\1', '')) 163 | if l:ucs == 0 164 | break 165 | endif 166 | let l:width += s:_wcwidth(l:ucs) 167 | let l:str = substitute(l:str, l:mx_first, '', '') 168 | endwhile 169 | return l:width 170 | endfunction 171 | " }}} 172 | function! s:_wcwidth(ucs) " UTF-8 only. {{{ 173 | let l:ucs = a:ucs 174 | if l:ucs > 0x7f && l:ucs <= 0xff 175 | return 4 176 | endif 177 | if l:ucs <= 0x08 || 0x0b <= l:ucs && l:ucs <= 0x1f || l:ucs == 0x7f 178 | return 2 179 | endif 180 | if (l:ucs >= 0x1100 181 | \ && (l:ucs <= 0x115f 182 | \ || l:ucs == 0x2329 183 | \ || l:ucs == 0x232a 184 | \ || (l:ucs >= 0x2e80 && l:ucs <= 0xa4cf 185 | \ && l:ucs != 0x303f) 186 | \ || (l:ucs >= 0xac00 && l:ucs <= 0xd7a3) 187 | \ || (l:ucs >= 0xf900 && l:ucs <= 0xfaff) 188 | \ || (l:ucs >= 0xfe30 && l:ucs <= 0xfe6f) 189 | \ || (l:ucs >= 0xff00 && l:ucs <= 0xff60) 190 | \ || (l:ucs >= 0xffe0 && l:ucs <= 0xffe6) 191 | \ || (l:ucs >= 0x20000 && l:ucs <= 0x2fffd) 192 | \ || (l:ucs >= 0x30000 && l:ucs <= 0x3fffd) 193 | \ )) 194 | return 2 195 | endif 196 | return 1 197 | endfunction 198 | " }}} 199 | endif 200 | function! s:me.strwidthpart(str, width) " {{{ 201 | if a:width <= 0 202 | return '' 203 | endif 204 | let l:ret = a:str 205 | let l:width = s:me.wcswidth(a:str) 206 | while l:width > a:width 207 | let char = matchstr(l:ret, '.$') 208 | let l:ret = l:ret[: -1 - len(char)] 209 | let l:width -= s:me.wcswidth(char) 210 | endwhile 211 | 212 | return l:ret 213 | endfunction 214 | " }}} 215 | function! s:me.truncate(str, width) " {{{ 216 | if a:str =~# '^[\x20-\x7e]*$' 217 | return len(a:str) < a:width ? 218 | \ printf('%-'.a:width.'s', a:str) : strpart(a:str, 0, a:width) 219 | endif 220 | 221 | let l:ret = a:str 222 | let l:width = s:me.wcswidth(a:str) 223 | if l:width > a:width 224 | let l:ret = s:me.strwidthpart(l:ret, a:width) 225 | let l:width = s:me.wcswidth(l:ret) 226 | endif 227 | 228 | if l:width < a:width 229 | let l:ret .= repeat(' ', a:width - l:width) 230 | endif 231 | 232 | return l:ret 233 | endfunction 234 | " }}} 235 | " }}} 236 | function! s:me.progress(str) "{{{ 237 | call s:me.debug(a:str) 238 | if ! &cmdheight 239 | return 240 | endif 241 | redraw 242 | echo s:me.truncate(a:str, &columns * min([&cmdheight, 1]) - 1) 243 | endfunction 244 | " }}} 245 | function! s:me.debug(str) "{{{ 246 | if exists('g:patchreview_debug') && g:patchreview_debug 247 | call s:me.buflog('DEBUG: ' . a:str) 248 | endif 249 | endfunction 250 | "}}} 251 | function! s:wipe_message_buffer() "{{{ 252 | let l:cur_tabnr = tabpagenr() 253 | let l:cur_winnr = winnr() 254 | if exists('s:origtabpagenr') 255 | exe 'tabnext ' . s:origtabpagenr 256 | endif 257 | let l:winnum = bufwinnr(s:msgbufname) 258 | if l:winnum != -1 " If the window is already open, jump to it 259 | if winnr() != l:winnum 260 | exe l:winnum . 'wincmd w' 261 | bw 262 | endif 263 | endif 264 | exe 'tabnext ' . l:cur_tabnr 265 | if winnr() != l:cur_winnr 266 | exe l:cur_winnr . 'wincmd w' 267 | endif 268 | endfunction 269 | "}}} 270 | function! s:me.buflog(...) "{{{ 271 | " Usage: s:me.buflog(msg, [go_back]) 272 | " default go_back = 1 273 | " 274 | let l:cur_tabnr = tabpagenr() 275 | let l:cur_winnr = winnr() 276 | if exists('s:msgbuftabnr') 277 | exe 'tabnext ' . s:msgbuftabnr 278 | else 279 | let s:msgbuftabnr = tabpagenr() 280 | endif 281 | let l:msgtab_orgwinnr = winnr() 282 | if ! exists('s:msgbufname') 283 | let s:msgbufname = '--PatchReview_Messages--' 284 | endif 285 | let l:winnum = bufwinnr(s:msgbufname) 286 | if l:winnum != -1 " If the window is already open, jump to it 287 | if winnr() != l:winnum 288 | exe l:winnum . 'wincmd w' 289 | endif 290 | else 291 | let l:bufnum = bufnr(s:msgbufname) 292 | let l:wcmd = l:bufnum == -1 ? s:msgbufname : '+buffer' . l:bufnum 293 | exe 'silent! botright 5split ' . l:wcmd 294 | let s:msgbuftabnr = tabpagenr() 295 | setlocal buftype=nofile 296 | setlocal bufhidden=delete 297 | setlocal noswapfile 298 | setlocal nowrap 299 | setlocal nobuflisted 300 | let b:just_created = 1 301 | endif 302 | setlocal modifiable 303 | if a:0 != 0 304 | silent! $put =a:1 305 | if get(b:, 'just_created', 0) 306 | normal! gg 307 | 0 delete _ 308 | let b:just_created = 0 309 | endif 310 | endif 311 | normal! G 312 | setlocal nomodifiable 313 | setlocal winheight=5 314 | 315 | exe l:msgtab_orgwinnr . 'wincmd w' 316 | if a:0 == 1 || a:0 > 1 && a:2 != 0 317 | exe 'tabnext ' . l:cur_tabnr 318 | if l:cur_winnr != -1 && winnr() != l:cur_winnr 319 | exe l:cur_winnr . 'wincmd w' 320 | endif 321 | endif 322 | endfunction 323 | "}}} 324 | function! s:check_binary(binary_name) "{{{ 325 | " Verify that binary_name is specified or available 326 | if ! exists('g:patchreview_' . a:binary_name) 327 | if s:executable(a:binary_name) 328 | let g:patchreview_{a:binary_name} = a:binary_name 329 | return 1 330 | else 331 | call s:me.buflog('g:patchreview_' . a:binary_name . ' is not defined and ' . a:binary_name . ' command could not be found on path.') 332 | call s:me.buflog('Please define it in your .vimrc.') 333 | return 0 334 | endif 335 | elseif ! s:executable(g:patchreview_{a:binary_name}) 336 | call s:me.buflog('Specified g:patchreview_' . a:binary_name . ' [' . g:patchreview_{a:binary_name} . '] is not executable.') 337 | return 0 338 | else 339 | return 1 340 | endif 341 | endfunction 342 | "}}} 343 | function! s:guess_prefix_strip_value(diff_file_path, default_strip) " {{{ 344 | call s:me.debug("Trying to guess strip level for " . a:diff_file_path . " with " .a:default_strip . " as default") 345 | if stridx(a:diff_file_path, '/') != -1 346 | let l:splitchar = '/' 347 | elseif stridx(a:diff_file_path, '\') != -1 348 | let l:splitchar = '\' 349 | else 350 | let l:splitchar = '/' 351 | endif 352 | let l:path = split(a:diff_file_path, l:splitchar) 353 | let i = 0 354 | while i <= 15 355 | if len(l:path) >= i 356 | if filereadable(join(['.'] + l:path[i : ], l:splitchar)) 357 | let s:guess_strip[i] += 1 358 | call s:me.debug("Guessing strip: " . i) 359 | return 360 | endif 361 | endif 362 | let i = i + 1 363 | endwhile 364 | let l:path = split(a:diff_file_path, l:splitchar)[:-2] 365 | let i = 0 366 | while i <= 15 367 | let j = len(l:path) - i 368 | while j > 0 369 | let l:checkdir = join(['.'] + l:path[i:j], l:splitchar) 370 | if l:checkdir == '.' 371 | break 372 | endif 373 | if isdirectory(l:checkdir) 374 | let s:guess_strip[i] += 1 375 | call s:me.debug("Guessing strip via directory check : " . i) 376 | return 377 | endif 378 | let j = j - 1 379 | endwhile 380 | let i = i + 1 381 | endwhile 382 | call s:me.debug("REALLY Guessing strip: " . a:default_strip) 383 | let s:guess_strip[a:default_strip] += 1 384 | endfunction 385 | " }}} 386 | function! s:is_bsd() " {{{ 387 | return filereadable('/etc/rc.subr') 388 | endfunction 389 | " }}} 390 | function! s:state(...) " For easy manipulation of diff parsing state {{{ 391 | if a:0 != 0 392 | if ! exists('s:PARSE_STATE') || s:PARSE_STATE != a:1 393 | call s:me.debug('Set PARSE_STATE: ' . a:1) 394 | endif 395 | let s:PARSE_STATE = a:1 396 | else 397 | if ! exists('s:PARSE_STATE') 398 | let s:PARSE_STATE = 'START' 399 | endif 400 | return s:PARSE_STATE 401 | endif 402 | endfunction 403 | "}}} 404 | function! s:temp_name() "{{{ 405 | " Create diffs in a way which lets session save/restore work better 406 | if ! exists('g:patchreview_persist') 407 | return tempname() 408 | endif 409 | if ! exists('g:patchreview_persist_dir') 410 | let g:patchreview_persist_dir = expand("~/.patchreview/" . strftime("%y%m%d%H%M%S")) 411 | if ! isdirectory(g:patchreview_persist_dir) 412 | call mkdir(g:patchreview_persist_dir, "p", 0777) 413 | endif 414 | endif 415 | if ! exists('s:last_tempname') 416 | let s:last_tempname = 0 417 | endif 418 | let s:last_tempname += 1 419 | let l:temp_file_name = g:patchreview_persist_dir . '/' . s:last_tempname 420 | while filereadable(l:temp_file_name) 421 | let s:last_tempname += 1 422 | let l:temp_file_name = g:patchreview_persist_dir . '/' . s:last_tempname 423 | endwhile 424 | return l:temp_file_name 425 | endfunction 426 | " }}} 427 | function! patchreview#get_patchfile_lines(patchfile) " {{{ 428 | " 429 | " Throws: "File " . a:patchfile . " is not readable" 430 | " 431 | let l:patchfile = expand(a:patchfile, ":p") 432 | if ! filereadable(expand(l:patchfile)) 433 | throw "File " . l:patchfile . " is not readable" 434 | endif 435 | return readfile(l:patchfile, 'b') 436 | endfunction 437 | " }}} 438 | function! s:me.generate_diff(shell_escaped_cmd) "{{{ 439 | let l:diff = [] 440 | let v:errmsg = '' 441 | let l:cout = system(a:shell_escaped_cmd) 442 | if v:errmsg != '' || v:shell_error && split(a:shell_escaped_cmd)[0] != 'bzr' 443 | call s:me.buflog(v:errmsg) 444 | call s:me.buflog('Could not execute [' . a:shell_escaped_cmd . ']') 445 | if v:shell_error 446 | call s:me.buflog('Error code: ' . v:shell_error) 447 | endif 448 | call s:me.buflog(l:cout) 449 | else 450 | let l:diff = split(l:cout, '[\n\r]') 451 | endif 452 | return l:diff 453 | endfunction 454 | " }}} 455 | function! patchreview#extract_diffs(lines, default_strip_count) "{{{ 456 | call s:me.debug("patchreview#extract_diffs called with default_strip_count " . a:default_strip_count) 457 | " Sets g:patches = {'fail':'', 'patch':[ 458 | " { 459 | " 'filename': filepath 460 | " 'type' : '+' | '-' | '!' 461 | " 'content' : patch text for this file 462 | " }, 463 | " ... 464 | " ]} 465 | let s:guess_strip = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] 466 | let g:patches = {'fail' : '', 'patch' : []} 467 | " SVN Diff 468 | " Index: file/path 469 | " =================================================================== 470 | " --- file/path (revision 490) 471 | " +++ file/path (working copy) 472 | " @@ -24,7 +24,8 @@ 473 | " alias -s tar.gz="echo " 474 | " 475 | " Perforce Diff 476 | " ==== //prod/main/some/f/path#10 - /Users/junkblocker/ws/some/f/path ==== 477 | " @@ -18,10 +18,13 @@ 478 | " 479 | " 480 | let l:collect = [] 481 | let l:line_num = 0 482 | let l:p_first_file = '' 483 | let l:o_count = -1 484 | let l:n_count = -1 485 | let l:c_count = -1 486 | let l:goal_count = -1 487 | let l:old_goal_count = -1 488 | let l:new_goal_count = -1 489 | let l:p_type = '' 490 | let l:filepath = '' 491 | let l:linescount = len(a:lines) 492 | call s:state('START') 493 | while l:line_num < l:linescount 494 | let l:line = a:lines[l:line_num] 495 | call s:me.debug('|' . l:line . '|') 496 | let l:line_num += 1 497 | if l:line =~ '^#' 498 | continue 499 | endif 500 | if s:state() == 'START' " {{{ 501 | let l:mat = matchlist(l:line, '^--- \([^\t]\+\).*$') 502 | if ! empty(l:mat) && l:mat[1] != '' 503 | call s:state('MAYBE_UNIFIED_DIFF') 504 | let l:p_first_file = l:mat[1] 505 | let l:collect = [l:line] 506 | continue 507 | endif 508 | let l:mat = matchlist(l:line, '^\*\*\* \([^\t]\+\).*$') 509 | if ! empty(l:mat) && l:mat[1] != '' 510 | call s:state('MAYBE_CONTEXT_DIFF') 511 | let l:p_first_file = l:mat[1] 512 | let l:collect = [l:line] 513 | continue 514 | endif 515 | let l:mat = matchlist(l:line, '^\(Binary files\|Files\) \(.\+\) and \(.+\) differ$') 516 | if ! empty(l:mat) && l:mat[2] != '' && l:mat[3] != '' 517 | call s:me.buflog('Ignoring ' . tolower(l:mat[1]) . ' ' . l:mat[2] . ' and ' . l:mat[3]) 518 | continue 519 | endif 520 | " Note - Older Perforce (around 2006) generates incorrect diffs 521 | let l:thisp = escape(expand(getcwd(), ':p'), '\') . '/' 522 | let l:mat = matchlist(l:line, '^====.*\#\(\d\+\).*' . l:thisp . '\(.*\)\s====\( content\)\?\r\?$') 523 | if ! empty(l:mat) && l:mat[2] != '' 524 | let l:p_type = '!' 525 | let l:filepath = l:mat[2] 526 | call s:me.progress('Collecting ' . l:filepath) 527 | let l:collect = ['--- ' . l:filepath . ' (revision ' . l:mat[1] . ')', '+++ ' . l:filepath . ' (working copy)'] 528 | call s:state('EXPECT_UNIFIED_RANGE_CHUNK') 529 | continue 530 | endif 531 | continue 532 | " }}} 533 | elseif s:state() == 'MAYBE_CONTEXT_DIFF' " {{{ 534 | let l:mat = matchlist(l:line, '^--- \([^\t]\+\).*$') 535 | if empty(l:mat) || l:mat[1] == '' 536 | call s:state('START') 537 | let l:line_num -= 1 538 | continue 539 | endif 540 | let l:p_second_file = l:mat[1] 541 | if l:p_first_file == '/dev/null' 542 | if l:p_second_file == '/dev/null' 543 | let g:patches['fail'] = "Malformed diff found at line " . l:line_num 544 | return 545 | endif 546 | let l:p_type = '+' 547 | let l:filepath = l:p_second_file 548 | else 549 | if l:p_second_file == '/dev/null' 550 | let l:p_type = '-' 551 | let l:filepath = l:p_first_file 552 | else 553 | let l:p_type = '!' 554 | let l:filepath = l:p_second_file 555 | endif 556 | endif 557 | call s:me.debug('l:p_type ' . l:p_type) 558 | call s:me.progress('Collecting ' . l:filepath) 559 | call s:state('EXPECT_15_STARS') 560 | let l:collect += [l:line] 561 | " }}} 562 | elseif s:state() == 'EXPECT_15_STARS' " {{{ 563 | if l:line !~ '^*\{15}$' 564 | call s:state('START') 565 | let l:line_num -= 1 566 | continue 567 | endif 568 | call s:state('EXPECT_CONTEXT_CHUNK_HEADER_1') 569 | let l:collect += [l:line] 570 | " }}} 571 | elseif s:state() == 'EXPECT_CONTEXT_CHUNK_HEADER_1' " {{{ 572 | let l:mat = matchlist(l:line, '^\*\*\* \(\d\+,\)\?\(\d\+\) \*\*\*\*$') 573 | if empty(l:mat) || l:mat[1] == '' 574 | call s:state('START') 575 | let l:line_num -= 1 576 | continue 577 | endif 578 | let l:collect += [l:line] 579 | call s:state('READ_TILL_CONTEXT_FRAGMENT_2') 580 | continue 581 | " }}} 582 | elseif s:state() == 'READ_TILL_CONTEXT_FRAGMENT_2' " {{{ 583 | if l:line !~ '^[ !+-] .*$' 584 | let l:mat = matchlist(l:line, '^--- \(\d\+\),\(\d\+\) ----$') 585 | if ! empty(l:mat) && l:mat[1] != '' && l:mat[2] != '' 586 | let l:goal_count = l:mat[2] - l:mat[1] + 1 587 | let l:c_count = 0 588 | call s:state('READ_CONTEXT_CHUNK') 589 | let l:collect += [l:line] 590 | continue 591 | endif 592 | call s:state('START') 593 | let l:line_num -= 1 594 | continue 595 | endif 596 | let l:collect += [l:line] 597 | continue 598 | " }}} 599 | elseif s:state() == 'READ_CONTEXT_CHUNK' " {{{ 600 | let l:c_count += 1 601 | if l:c_count == l:goal_count 602 | let l:collect += [l:line] 603 | call s:state('BACKSLASH_OR_CRANGE_EOF') 604 | continue 605 | else " goal not met yet 606 | let l:mat = matchlist(l:line, '^\([\\!+ -]\) .*$') 607 | if empty(l:mat) || l:mat[1] == '' 608 | if l:line =~ '^\*\{15}$' 609 | let l:collect += [l:line] 610 | call s:state('EXPECT_CONTEXT_CHUNK_HEADER_1') 611 | continue 612 | else 613 | let l:line_num -= 1 614 | call s:state('START') 615 | continue 616 | endif 617 | endif 618 | let l:collect += [l:line] 619 | continue 620 | endif 621 | " }}} 622 | elseif s:state() == 'BACKSLASH_OR_CRANGE_EOF' " {{{ 623 | if l:line =~ '^\\ No newline.*$' " XXX: Can we go to another chunk from here?? 624 | let l:collect += [l:line] 625 | let l:this_patch = {} 626 | let l:this_patch['filename'] = l:filepath 627 | let l:this_patch['type'] = l:p_type 628 | let l:this_patch['content'] = l:collect 629 | let g:patches['patch'] += [l:this_patch] 630 | unlet! l:this_patch 631 | call s:me.progress('Collected ' . l:filepath) 632 | if l:p_type == '!' 633 | call s:guess_prefix_strip_value(l:filepath, a:default_strip_count) 634 | endif 635 | call s:state('START') 636 | continue 637 | endif 638 | if l:line =~ '^\*\{15}$' 639 | let l:collect += [l:line] 640 | call s:state('EXPECT_CONTEXT_CHUNK_HEADER_1') 641 | continue 642 | endif 643 | let l:this_patch = {'filename': l:filepath, 'type': l:p_type, 'content': l:collect} 644 | let g:patches['patch'] += [l:this_patch] 645 | unlet! l:this_patch 646 | let l:line_num -= 1 647 | call s:me.progress('Collected ' . l:filepath) 648 | if l:p_type == '!' 649 | call s:guess_prefix_strip_value(l:filepath, a:default_strip_count) 650 | endif 651 | call s:state('START') 652 | continue 653 | " }}} 654 | elseif s:state() == 'MAYBE_UNIFIED_DIFF' " {{{ 655 | let l:mat = matchlist(l:line, '^+++ \([^\t]\+\).*$') 656 | if empty(l:mat) || l:mat[1] == '' 657 | call s:state('START') 658 | let l:line_num -= 1 659 | continue 660 | endif 661 | let l:p_second_file = l:mat[1] 662 | if l:p_first_file == '/dev/null' 663 | if l:p_second_file == '/dev/null' 664 | let g:patches['fail'] = "Malformed diff found at line " . l:line_num 665 | return 666 | endif 667 | let l:p_type = '+' 668 | let l:filepath = l:p_second_file 669 | else 670 | if l:p_second_file == '/dev/null' 671 | let l:p_type = '-' 672 | let l:filepath = l:p_first_file 673 | else 674 | let l:p_type = '!' 675 | if l:p_first_file =~ '^//.*' " Perforce 676 | let l:filepath = l:p_second_file 677 | else 678 | let l:filepath = l:p_first_file 679 | endif 680 | endif 681 | endif 682 | call s:me.progress('Collecting ' . l:filepath) 683 | call s:state('EXPECT_UNIFIED_RANGE_CHUNK') 684 | let l:collect += [l:line] 685 | continue 686 | " }}} 687 | elseif s:state() == 'EXPECT_UNIFIED_RANGE_CHUNK' "{{{ 688 | let l:mat = matchlist(l:line, '^@@ -\(\d\+,\)\?\(\d\+\) +\(\d\+,\)\?\(\d\+\) @@.*$') 689 | if ! empty(l:mat) 690 | let l:old_goal_count = l:mat[2] 691 | let l:new_goal_count = l:mat[4] 692 | let l:o_count = 0 693 | let l:n_count = 0 694 | call s:state('READ_UNIFIED_CHUNK') 695 | let l:collect += [l:line] 696 | else 697 | let l:this_patch = {'filename': l:filepath, 'type': l:p_type, 'content': l:collect} 698 | let g:patches['patch'] += [l:this_patch] 699 | unlet! l:this_patch 700 | call s:me.progress('Collected ' . l:filepath) 701 | if l:p_type == '!' 702 | call s:guess_prefix_strip_value(l:filepath, a:default_strip_count) 703 | endif 704 | call s:state('START') 705 | let l:line_num -= 1 706 | endif 707 | continue 708 | "}}} 709 | elseif s:state() == 'READ_UNIFIED_CHUNK' " {{{ 710 | if l:o_count == l:old_goal_count && l:n_count == l:new_goal_count 711 | if l:line =~ '^\\.*$' " XXX: Can we go to another chunk from here?? 712 | let l:collect += [l:line] 713 | let l:this_patch = {'filename': l:filepath, 'type': l:p_type, 'content': l:collect} 714 | let g:patches['patch'] += [l:this_patch] 715 | unlet! l:this_patch 716 | call s:me.progress('Collected ' . l:filepath) 717 | if l:p_type == '!' 718 | call s:guess_prefix_strip_value(l:filepath, a:default_strip_count) 719 | endif 720 | call s:state('START') 721 | continue 722 | endif 723 | let l:mat = matchlist(l:line, '^@@ -\(\d\+,\)\?\(\d\+\) +\(\d\+,\)\?\(\d\+\) @@.*$') 724 | if ! empty(l:mat) 725 | let l:old_goal_count = l:mat[2] 726 | let l:new_goal_count = l:mat[4] 727 | let l:o_count = 0 728 | let l:n_count = 0 729 | let l:collect += [l:line] 730 | call s:me.debug("Will collect another unified chunk") 731 | continue 732 | endif 733 | let l:this_patch = {'filename': l:filepath, 'type': l:p_type, 'content': l:collect} 734 | let g:patches['patch'] += [l:this_patch] 735 | unlet! l:this_patch 736 | call s:me.progress('Collected ' . l:filepath) 737 | call s:guess_prefix_strip_value(l:filepath, a:default_strip_count) 738 | let l:line_num -= 1 739 | call s:state('START') 740 | continue 741 | else " goal not met yet 742 | let l:mat = matchlist(l:line, '^\([\\+ -]\).*$') 743 | if empty(l:mat) || l:mat[1] == '' 744 | let l:line_num -= 1 745 | call s:state('START') 746 | continue 747 | endif 748 | let chr = l:mat[1] 749 | if chr == '+' 750 | let l:n_count += 1 751 | elseif chr == ' ' 752 | let l:o_count += 1 753 | let l:n_count += 1 754 | elseif chr == '-' 755 | let l:o_count += 1 756 | endif 757 | let l:collect += [l:line] 758 | continue 759 | endif 760 | " }}} 761 | else " {{{ 762 | let g:patches['fail'] = "Internal error: Do not use the plugin anymore and if possible please send the diff or patch file you tried it with to Manpreet Singh " 763 | return 764 | endif " }}} 765 | endwhile 766 | "call s:me.buflog(s:state()) 767 | if ( 768 | \ (s:state() == 'READ_CONTEXT_CHUNK' 769 | \ && l:c_count == l:goal_count 770 | \ ) || 771 | \ (s:state() == 'READ_UNIFIED_CHUNK' 772 | \ && l:n_count == l:new_goal_count 773 | \ && l:o_count == l:old_goal_count 774 | \ ) 775 | \) 776 | let l:this_patch = {'filename': l:filepath, 'type': l:p_type, 'content': l:collect} 777 | let g:patches['patch'] += [l:this_patch] 778 | unlet! l:this_patch 779 | unlet! lines 780 | call s:me.progress('Collected ' . l:filepath) 781 | if l:p_type == '!' || (l:p_type == '+' && s:reviewmode == 'diff') 782 | call s:guess_prefix_strip_value(l:filepath, a:default_strip_count) 783 | endif 784 | endif 785 | return 786 | endfunction 787 | "}}} 788 | function! patchreview#patchreview(...) "{{{ 789 | let l:callback_args = ['Patch Review'] + deepcopy(a:000) 790 | if exists('g:patchreview_prefunc') 791 | call call(g:patchreview_prefunc, l:callback_args) 792 | endif 793 | augroup patchreview_plugin 794 | autocmd! 795 | 796 | " When opening files which may be open elsewhere, open them in read only 797 | " mode 798 | au SwapExists * :let v:swapchoice='o' | augroup! patchreview_plugin 799 | augroup end 800 | let s:save_shortmess = &shortmess 801 | let s:save_aw = &autowrite 802 | let s:save_awa = &autowriteall 803 | let s:origtabpagenr = tabpagenr() 804 | let s:equalalways = &equalalways 805 | let s:eadirection = &eadirection 806 | set equalalways 807 | set eadirection=hor 808 | set shortmess=aW 809 | call s:wipe_message_buffer() 810 | let s:reviewmode = 'patch' 811 | try 812 | let l:lines = patchreview#get_patchfile_lines(a:1) 813 | call s:generic_review([l:lines] + a:000[1:]) 814 | catch /.*/ 815 | call s:me.buflog('ERROR: ' . v:exception) 816 | endtry 817 | let &eadirection = s:eadirection 818 | let &equalalways = s:equalalways 819 | let &autowriteall = s:save_awa 820 | let &autowrite = s:save_aw 821 | let &shortmess = s:save_shortmess 822 | if exists('g:patchreview_postfunc') 823 | call call(g:patchreview_postfunc, l:callback_args) 824 | endif 825 | endfunction 826 | "}}} 827 | function! patchreview#reverse_patchreview(...) "{{{ 828 | let l:callback_args = ['Reverse Patch Review'] + deepcopy(a:000) 829 | if exists('g:patchreview_prefunc') 830 | call call(g:patchreview_prefunc, l:callback_args) 831 | endif 832 | augroup patchreview_plugin 833 | autocmd! 834 | 835 | " When opening files which may be open elsewhere, open them in read only 836 | " mode 837 | au SwapExists * :let v:swapchoice='o' | augroup! patchreview_plugin 838 | augroup end 839 | let s:save_shortmess = &shortmess 840 | let s:save_aw = &autowrite 841 | let s:save_awa = &autowriteall 842 | let s:origtabpagenr = tabpagenr() 843 | let s:equalalways = &equalalways 844 | let s:eadirection = &eadirection 845 | set equalalways 846 | set eadirection=hor 847 | set shortmess=aW 848 | call s:wipe_message_buffer() 849 | let s:reviewmode = 'rpatch' 850 | try 851 | let l:lines = patchreview#get_patchfile_lines(a:1) 852 | call s:generic_review([l:lines] + a:000[1:]) 853 | catch /.*/ 854 | call s:me.buflog('ERROR: ' . v:exception) 855 | endtry 856 | let &eadirection = s:eadirection 857 | let &equalalways = s:equalalways 858 | let &autowriteall = s:save_awa 859 | let &autowrite = s:save_aw 860 | let &shortmess = s:save_shortmess 861 | if exists('g:patchreview_postfunc') 862 | call call(g:patchreview_postfunc, l:callback_args) 863 | endif 864 | endfunction 865 | "}}} 866 | function! s:wiggle(out, rej) " {{{ 867 | if ! s:executable('wiggle') 868 | return 869 | endif 870 | let l:wiggle_out = s:temp_name() 871 | let v:errmsg = '' 872 | call system('wiggle --merge ' . shellescape(a:out) . ' ' . shellescape(a:rej) . ' > ' . shellescape(l:wiggle_out)) 873 | if v:errmsg != '' || v:shell_error 874 | call s:me.buflog('ERROR: wiggle was not completely successful.') 875 | if v:errmsg != '' 876 | call s:me.buflog('ERROR: ' . v:errmsg) 877 | endif 878 | endif 879 | if filereadable(l:wiggle_out) 880 | " modelines in loaded files mess with diff comparison 881 | let s:keep_modeline=&modeline 882 | let &modeline=0 883 | if s:disable_syntax 884 | syn off 885 | else 886 | syn enable 887 | endif 888 | silent! exe s:vsplit . ' diffsplit ' . fnameescape(l:wiggle_out) 889 | setlocal noswapfile 890 | if s:disable_syntax 891 | syn off 892 | else 893 | syn enable 894 | endif 895 | setlocal bufhidden=delete 896 | setlocal nobuflisted 897 | setlocal modifiable 898 | setlocal nowrap 899 | " Remove buffer name 900 | if ! exists('g:patchreview_persist') 901 | setlocal buftype=nofile 902 | silent! 0f 903 | endif 904 | let &modeline=s:keep_modeline 905 | if has('folding') 906 | let &foldlevel=s:foldlevel 907 | endif 908 | wincmd p 909 | endif 910 | endfunction 911 | " }}} 912 | function! s:generic_review(argslist) "{{{ 913 | " diff mode: 914 | " arg1 = patchlines 915 | " arg2 = strip count 916 | " patch mode: 917 | " arg1 = patchlines 918 | " arg2 = directory 919 | " arg3 = strip count 920 | 921 | " VIM 7+ required 922 | if v:version < 700 923 | call s:me.buflog('This plugin needs VIM 7 or higher') 924 | return 925 | endif 926 | 927 | " +diff required 928 | if ! has('diff') 929 | call s:me.buflog('This plugin needs VIM built with +diff feature.') 930 | return 931 | endif 932 | 933 | let l:filterdiff_warned = 0 934 | 935 | call s:me.debug("Reviewmode: " . s:reviewmode) 936 | if s:reviewmode == 'diff' || s:reviewmode == 'rpatch' 937 | let patch_R_options = ['-t', '-R'] 938 | elseif s:reviewmode == 'patch' 939 | let patch_R_options = [] 940 | else 941 | call s:me.buflog('Fatal internal error in patchreview.vim plugin') 942 | return 943 | endif 944 | 945 | " ----------------------------- patch ------------------------------------ 946 | if s:reviewmode =~ 'patch' 947 | let l:argc = len(a:argslist) 948 | if l:argc == 0 || l:argc > 3 949 | if s:reviewmode == 'patch' 950 | call s:me.buflog('PatchReview command needs 1 to 3 arguments.') 951 | return 952 | endif 953 | if s:reviewmode == 'rpatch' 954 | call s:me.buflog('ReversePatchReview command needs 1 to 3 arguments.') 955 | return 956 | endif 957 | endif 958 | " ARG[0]: patchlines 959 | let l:patchlines = a:argslist[0] 960 | " ARG[1]: directory [optional] 961 | if len(a:argslist) >= 2 962 | let s:src_dir = expand(a:argslist[1], ':p') 963 | if ! isdirectory(s:src_dir) 964 | call s:me.buflog('[' . s:src_dir . '] is not a directory') 965 | return 966 | endif 967 | try 968 | exe 'cd ' . fnameescape(s:src_dir) 969 | catch /^.*E344.*/ 970 | call s:me.buflog('Could not change to directory [' . s:src_dir . ']') 971 | return 972 | endtry 973 | endif 974 | " ARG[2]: strip count [optional] 975 | if len(a:argslist) == 3 976 | let l:strip_count = eval(a:argslist[2]) 977 | endif 978 | " ----------------------------- diff ------------------------------------- 979 | elseif s:reviewmode == 'diff' 980 | if len(a:argslist) > 2 981 | call s:me.buflog('Fatal internal error in patchreview.vim plugin') 982 | return 983 | endif 984 | let l:patchlines = a:argslist[0] 985 | " passed in by default 986 | let l:strip_count = eval(a:argslist[1]) 987 | else 988 | call s:me.buflog('Fatal internal error in patchreview.vim plugin') 989 | return 990 | endif " diff 991 | 992 | " Verify that patch command and temporary directory are available or specified 993 | if ! s:check_binary('patch') 994 | return 995 | endif 996 | 997 | call s:me.buflog('Source directory: ' . getcwd()) 998 | call s:me.buflog('------------------') 999 | if exists('l:strip_count') 1000 | let l:defsc = l:strip_count 1001 | elseif s:reviewmode =~ 'patch' 1002 | let l:defsc = 0 1003 | else 1004 | call s:me.buflog('Fatal internal error in patchreview.vim plugin') 1005 | return 1006 | endif 1007 | try 1008 | call patchreview#extract_diffs(l:patchlines, l:defsc) 1009 | catch 1010 | call s:me.buflog('Exception ' . v:exception) 1011 | call s:me.buflog('From ' . v:throwpoint) 1012 | return 1013 | endtry 1014 | let l:cand = 0 1015 | let l:inspecting = 1 1016 | while l:cand < l:inspecting && l:inspecting <= 15 1017 | if s:guess_strip[l:cand] >= s:guess_strip[l:inspecting] 1018 | let l:inspecting += 1 1019 | else 1020 | let l:cand = l:inspecting 1021 | endif 1022 | endwhile 1023 | let l:strip_count = l:cand 1024 | 1025 | let l:pwd = getcwd() 1026 | let l:this_patch_num = 0 1027 | let l:total_patches = len(g:patches['patch']) 1028 | for patch in g:patches['patch'] 1029 | let l:this_patch_num += 1 1030 | call s:me.progress('Processing ' . l:this_patch_num . '/' . l:total_patches . ' ' . patch.filename) 1031 | call s:me.buflog("Review mode: : " . s:reviewmode . " Patch type: " . patch.type) 1032 | if patch.type !~ '^[!+-]$' 1033 | call s:me.buflog('*** Skipping review generation due to unknown change [' . patch.type . ']') 1034 | unlet! patch 1035 | continue 1036 | endif 1037 | unlet! l:relpath 1038 | let l:relpath = patch.filename 1039 | " XXX: svn diff and hg diff produce different kind of outputs, one requires 1040 | " XXX: stripping but the other doesn't. We need to take care of that 1041 | let l:stripmore = l:strip_count 1042 | let l:stripped_rel_path = l:relpath 1043 | while l:stripmore > 0 1044 | " strip one 1045 | let l:stripped_rel_path = substitute(l:stripped_rel_path, '^[^\\\/]\+[^\\\/]*[\\\/]' , '' , '') 1046 | let l:stripmore -= 1 1047 | endwhile 1048 | if patch.type == '!' 1049 | if s:reviewmode =~ 'patch' 1050 | let l:msgtype = 'Patch modifies file: ' 1051 | elseif s:reviewmode == 'diff' 1052 | let l:msgtype = 'File has changes: ' 1053 | endif 1054 | elseif patch.type == '+' 1055 | if s:reviewmode =~ 'patch' 1056 | let l:msgtype = 'Patch adds file : ' 1057 | elseif s:reviewmode == 'diff' 1058 | let l:msgtype = 'New file : ' 1059 | endif 1060 | elseif patch.type == '-' 1061 | if s:reviewmode =~ 'patch' 1062 | let l:msgtype = 'Patch removes file : ' 1063 | elseif s:reviewmode == 'diff' 1064 | let l:msgtype = 'Removed file : ' 1065 | endif 1066 | else 1067 | call s:me.buflog('Fatal internal error in patchreview.vim plugin') 1068 | return 1069 | endif 1070 | call s:me.buflog(l:msgtype . ' ' . l:relpath) 1071 | let l:bufnum = bufnr(l:relpath) 1072 | if buflisted(l:bufnum) && getbufvar(l:bufnum, '&mod') 1073 | call s:me.buflog('Old buffer for file [' . l:relpath . '] exists in modified state. Skipping review.') 1074 | unlet! patch 1075 | continue 1076 | endif 1077 | let l:tmp_patch = s:temp_name() 1078 | let l:tmp_patched = s:temp_name() 1079 | let l:tmp_patched_rej = l:tmp_patched . '.rej' " Rejection file created by patch 1080 | 1081 | try 1082 | " write patch for patch.filename into l:tmp_patch 1083 | " some ports of GNU patch (e.g. UnxUtils) always want DOS end of line in 1084 | " patches while correctly handling any EOLs in files 1085 | if exists("g:patchreview_patch_needs_crlf") && g:patchreview_patch_needs_crlf 1086 | call map(patch.content, 'v:val . "\r"') 1087 | endif 1088 | if writefile(patch.content, l:tmp_patch) != 0 1089 | call s:me.buflog('*** ERROR: Could not save patch to a temporary file.') 1090 | continue 1091 | endif 1092 | "if exists('g:patchreview_debug') 1093 | " exe 'tabedit ' . l:tmp_patch 1094 | "endif 1095 | if patch.type == '+' && s:reviewmode =~ 'patch' 1096 | if s:reviewmode == 'rpatch' 1097 | let l:inputfile = expand(l:pwd . '/' . l:stripped_rel_path, ':p') 1098 | else 1099 | let l:inputfile = '' 1100 | endif 1101 | if s:is_bsd() 1102 | " BSD patch is not GNU patch but works just fine without the 1103 | " unavailable --binary option 1104 | let l:patchcmd = g:patchreview_patch . ' ' 1105 | \ . join(map(['-s', '-o', l:tmp_patched] 1106 | \ + patch_R_options + [l:inputfile], 1107 | \ "shellescape(v:val)"), ' ') . ' < ' 1108 | \ . shellescape(l:tmp_patch) 1109 | else 1110 | let l:patchcmd = g:patchreview_patch . ' ' 1111 | \ . join(map(['--binary', '-s', '-o', l:tmp_patched] 1112 | \ + patch_R_options + [l:inputfile], 1113 | \ "shellescape(v:val)"), ' ') . ' < ' 1114 | \ . shellescape(l:tmp_patch) 1115 | endif 1116 | elseif patch.type == '+' && s:reviewmode == 'diff' 1117 | let l:inputfile = '' 1118 | unlet! l:patchcmd 1119 | else 1120 | let l:inputfile = expand(l:pwd . '/' . l:stripped_rel_path, ':p') 1121 | if s:is_bsd() 1122 | " BSD patch is not GNU patch but works just fine without the 1123 | " unavailable --binary option 1124 | let l:patchcmd = g:patchreview_patch . ' ' 1125 | \ . join(map(['-s', '-o', l:tmp_patched] 1126 | \ + patch_R_options + [l:inputfile], 1127 | \ "shellescape(v:val)"), ' ') . ' < ' 1128 | \ . shellescape(l:tmp_patch) 1129 | else 1130 | let l:patchcmd = g:patchreview_patch . ' ' 1131 | \ . join(map(['--binary', '-s', '-o', l:tmp_patched] 1132 | \ + patch_R_options + [l:inputfile], 1133 | \ "shellescape(v:val)"), ' ') . ' < ' 1134 | \ . shellescape(l:tmp_patch) 1135 | endif 1136 | endif 1137 | let error = 0 1138 | let l:pout = '' 1139 | if exists('l:patchcmd') 1140 | let v:errmsg = '' 1141 | let l:pout = system(l:patchcmd) 1142 | if v:errmsg != '' || v:shell_error 1143 | let l:errmsg = v:errmsg 1144 | let error = 1 1145 | call s:me.buflog('ERROR: Could not execute patch command.') 1146 | call s:me.buflog('ERROR: ' . l:patchcmd) 1147 | if l:pout != '' 1148 | call s:me.buflog('ERROR: ' . l:pout) 1149 | endif 1150 | call s:me.buflog('ERROR: ' . l:errmsg) 1151 | if filereadable(l:tmp_patched) 1152 | call s:me.buflog('ERROR: Diff partially shown.') 1153 | else 1154 | call s:me.buflog('ERROR: Diff skipped.') 1155 | endif 1156 | endif 1157 | endif 1158 | silent! exe 'tabedit ' . fnameescape(l:pwd . '/' . l:stripped_rel_path) 1159 | if filereadable(l:tmp_patched) && l:pout =~ 'Only garbage was found in the patch input' 1160 | topleft new 1161 | exe 'r ' . fnameescape(l:tmp_patch) 1162 | normal! gg 1163 | 0 delete _ 1164 | exe 'file bad_patch_for_' . fnameescape(fnamemodify(l:inputfile, ':t')) 1165 | setlocal nomodifiable nomodified ft=diff bufhidden=delete 1166 | \ buftype=nofile noswapfile nowrap nobuflisted 1167 | wincmd p 1168 | endif 1169 | if ! error || filereadable(l:tmp_patched) 1170 | let l:filetype = &filetype 1171 | if exists('l:patchcmd') 1172 | " modelines in loaded files mess with diff comparison 1173 | let s:keep_modeline=&modeline 1174 | let &modeline=0 1175 | if s:disable_syntax 1176 | syn off 1177 | else 1178 | syn enable 1179 | endif 1180 | if patch.type == '+' && s:reviewmode == 'diff' 1181 | silent! exe s:vsplit . ' diffsplit /dev/null' 1182 | else 1183 | silent! exe s:vsplit . ' diffsplit ' . fnameescape(l:tmp_patched) 1184 | endif 1185 | setlocal noswapfile 1186 | if s:disable_syntax 1187 | syn off 1188 | else 1189 | syn enable 1190 | endif 1191 | setlocal bufhidden=delete 1192 | setlocal nobuflisted 1193 | setlocal modifiable 1194 | setlocal nowrap 1195 | " Remove buffer name when it makes sense 1196 | if ! exists('g:patchreview_persist') 1197 | setlocal buftype=nofile 1198 | silent! 0f 1199 | endif 1200 | let &filetype = l:filetype 1201 | let &fdm = 'diff' 1202 | if has('folding') 1203 | let &foldlevel=s:foldlevel 1204 | endif 1205 | wincmd p 1206 | let &modeline=s:keep_modeline 1207 | else 1208 | if s:disable_syntax 1209 | syn off 1210 | else 1211 | syn enable 1212 | endif 1213 | silent! exe s:vsplit . ' new' 1214 | let &filetype = l:filetype 1215 | let &fdm = 'diff' 1216 | if has('folding') 1217 | let &foldlevel=s:foldlevel 1218 | endif 1219 | wincmd p 1220 | endif 1221 | endif 1222 | if ! filereadable(l:pwd . "/" . l:stripped_rel_path) 1223 | if patch.type != '-' && patch.type != '+' 1224 | call s:me.buflog('ERROR: Original file "' . l:pwd . "/" . l:stripped_rel_path . '" does not exist.') 1225 | endif 1226 | " modelines in loaded files mess with diff comparison 1227 | let s:keep_modeline=&modeline 1228 | let &modeline=0 1229 | if s:disable_syntax 1230 | syn off 1231 | else 1232 | syn enable 1233 | endif 1234 | silent! exe s:noautocmd . 'topleft split ' . fnameescape(l:tmp_patch) 1235 | setlocal noswapfile 1236 | if s:disable_syntax 1237 | syn off 1238 | else 1239 | syn enable 1240 | endif 1241 | setlocal bufhidden=delete 1242 | setlocal nobuflisted 1243 | setlocal modifiable 1244 | setlocal nowrap 1245 | " Remove buffer name when it makes sense 1246 | if ! exists('g:patchreview_persist') && (patch.type != '+' || s:reviewmode != 'patch') 1247 | setlocal buftype=nofile 1248 | silent! 0f 1249 | endif 1250 | if has('folding') 1251 | let &foldlevel=s:foldlevel 1252 | endif 1253 | wincmd p 1254 | let &modeline=s:keep_modeline 1255 | endif 1256 | if filereadable(l:tmp_patched_rej) 1257 | " modelines in loaded files mess with diff comparison 1258 | let s:keep_modeline=&modeline 1259 | let &modeline=0 1260 | if s:disable_syntax 1261 | syn off 1262 | else 1263 | syn enable 1264 | endif 1265 | silent! exe 'topleft split ' . fnameescape(l:tmp_patched_rej) 1266 | " Try to convert rejects to unified format unless explicitly disabled 1267 | if (! exists('g:patchreview_unified_rejects') || g:patchreview_unified_rejects == 1) && 1268 | \ getline(1) =~ '\m\*\{15}' 1269 | if s:executable('filterdiff') 1270 | call append(0, '--- ' . l:stripped_rel_path . '.new') 1271 | call append(0, '*** ' . l:stripped_rel_path . '.old') 1272 | silent %!filterdiff --format=unified 1273 | elseif ! l:filterdiff_warned 1274 | if exists('g:patchreview_unified_rejects') 1275 | call s:me.buflog('WARNING: Option g:patchreview_unified_rejects requires filterdiff') 1276 | call s:me.buflog('WARNING: installed which I could not locate on the PATH.') 1277 | call s:me.buflog('WARNING: Please install it via diffutils package for your platform and make') 1278 | call s:me.buflog('WARNING: sure it is on the PATH.') 1279 | else 1280 | call s:me.buflog('WARNING: Converting rejections to unified format requires filterdiff installed') 1281 | call s:me.buflog('WARNING: which I could not locate on the PATH.') 1282 | call s:me.buflog('WARNING: Please install it via diffutils package for your platform and make') 1283 | call s:me.buflog('WARNING: sure it is on the PATH for better readable .rej output.') 1284 | endif 1285 | let l:filterdiff_warned = 1 1286 | endif 1287 | endif 1288 | setlocal noswapfile 1289 | if s:disable_syntax 1290 | syn off 1291 | else 1292 | syn enable 1293 | endif 1294 | setlocal bufhidden=delete 1295 | setlocal nobuflisted 1296 | setlocal modifiable 1297 | setlocal nowrap 1298 | " Remove buffer name 1299 | if ! exists('g:patchreview_persist') 1300 | setlocal buftype=nofile 1301 | silent! 0f 1302 | endif 1303 | if has('folding') 1304 | let &foldlevel=s:foldlevel 1305 | endif 1306 | wincmd p 1307 | let &modeline=s:keep_modeline 1308 | call s:me.buflog(l:msgtype . '*** REJECTED *** ' . l:relpath) 1309 | call s:wiggle(l:tmp_patched, l:tmp_patched_rej) 1310 | endif 1311 | finally 1312 | if ! exists('g:patchreview_persist') 1313 | call delete(l:tmp_patch) 1314 | call delete(l:tmp_patched) 1315 | call delete(l:tmp_patched_rej) 1316 | endif 1317 | unlet! patch 1318 | endtry 1319 | endfor 1320 | call s:me.buflog('-----') 1321 | call s:me.buflog('Done.', 0) 1322 | unlet! g:patches 1323 | endfunction 1324 | "}}} 1325 | function! patchreview#diff_review(...) " {{{ 1326 | let l:callback_args = ['Diff Review'] + deepcopy(a:000) 1327 | if exists('g:patchreview_prefunc') 1328 | call call(g:patchreview_prefunc, l:callback_args) 1329 | endif 1330 | augroup patchreview_plugin 1331 | autocmd! 1332 | 1333 | " When opening files which may be open elsewhere, open them in read only 1334 | " mode 1335 | au SwapExists * :let v:swapchoice='o' | augroup! patchreview_plugin 1336 | augroup end 1337 | 1338 | let s:save_shortmess = &shortmess 1339 | let s:save_aw = &autowrite 1340 | let s:save_awa = &autowriteall 1341 | let s:origtabpagenr = tabpagenr() 1342 | let s:equalalways = &equalalways 1343 | let s:eadirection = &eadirection 1344 | set equalalways 1345 | set eadirection=hor 1346 | set shortmess=aW 1347 | set shortmess=aW 1348 | call s:wipe_message_buffer() 1349 | 1350 | try 1351 | if a:0 != 0 " :DiffReview some command with arguments 1352 | let l:outfile = s:temp_name() 1353 | if a:1 =~ '^\d\+$' 1354 | " DiffReview strip_count command 1355 | let l:cmd = join(map(deepcopy(a:000[1:]), 'shellescape(v:val)') + ['>', shellescape(l:outfile)], ' ') 1356 | let l:binary = copy(a:000[1]) 1357 | let l:strip_count = eval(a:1) 1358 | else 1359 | let l:cmd = join(map(deepcopy(a:000), 'shellescape(v:val)') + ['>', shellescape(l:outfile)], ' ') 1360 | let l:binary = copy(a:000[0]) 1361 | let l:strip_count = 0 " fake it 1362 | endif 1363 | let v:errmsg = '' 1364 | let l:cout = system(l:cmd) 1365 | if v:errmsg == '' && 1366 | \ (a:0 != 0 && l:binary =~ '^\(cvs\|diff\|bzr\)$') 1367 | \ && v:shell_error == 1 1368 | " Ignoring diff and CVS non-error 1369 | elseif v:errmsg != '' || v:shell_error 1370 | call s:me.buflog(v:errmsg) 1371 | call s:me.buflog('Could not execute [' . l:cmd . ']') 1372 | if v:shell_error 1373 | call s:me.buflog('Error code: ' . v:shell_error) 1374 | endif 1375 | call s:me.buflog(l:cout) 1376 | call s:me.buflog('Diff review aborted.') 1377 | return 1378 | endif 1379 | let s:reviewmode = 'diff' 1380 | let l:lines = patchreview#get_patchfile_lines(l:outfile) 1381 | call s:generic_review([l:lines, l:strip_count]) 1382 | else " :DiffReview 1383 | call s:init_diff_modules() 1384 | let l:diff = {} 1385 | for module in keys(s:modules) 1386 | if s:modules[module].detect() 1387 | let l:diff = s:modules[module].get_diff() 1388 | "call s:me.debug('Detected ' . module) 1389 | break 1390 | endif 1391 | endfor 1392 | if !exists("l:diff['diff']") || empty(l:diff['diff']) 1393 | call s:me.buflog('No diff found. Make sure you are in a VCS controlled top directory.') 1394 | else 1395 | let s:reviewmode = 'diff' 1396 | call s:generic_review([l:diff['diff'], l:diff['strip']]) 1397 | endif 1398 | endif 1399 | finally 1400 | if exists('l:outfile') && ! exists('g:patchreview_persist') 1401 | call delete(l:outfile) 1402 | endif 1403 | unlet! l:diff 1404 | let &eadirection = s:eadirection 1405 | let &equalalways = s:equalalways 1406 | let &autowriteall = s:save_awa 1407 | let &autowrite = s:save_aw 1408 | let &shortmess = s:save_shortmess 1409 | endtry 1410 | if exists('g:patchreview_postfunc') 1411 | call call(g:patchreview_postfunc, l:callback_args) 1412 | endif 1413 | endfunction 1414 | "}}} 1415 | function! s:init_diff_modules() " {{{ 1416 | if ! empty(s:modules) 1417 | return 1418 | endif 1419 | for name in map(split(globpath(&runtimepath, 1420 | \ 'autoload/patchreview/*.vim'), '[\n\r]'), 1421 | \ 'fnamemodify(v:val, ":t:r")') 1422 | 1423 | let l:module = patchreview#{name}#register(s:me) 1424 | if !empty(l:module) && !has_key(s:modules, name) 1425 | let s:modules[name] = l:module 1426 | endif 1427 | unlet l:module 1428 | endfor 1429 | endfunction 1430 | "}}} 1431 | " }}} 1432 | " vim: set et fdl=1 fdm=marker fenc= ff=unix ft=vim sts=0 sw=2 ts=2 tw=79 nowrap : 1433 | " }}} 1434 | --------------------------------------------------------------------------------