fall back to diff filetype
[diffit.git] / diffit.vim
blob8541ffe1a4c2dbc520a785b3ff96982dbaee77ae
1 " ============================================================================
2 " File:        diffit.vim
3 " Description: Show diff for current buffer
4 " Maintainer:  Clemens Buchacher <drizzd@aon.at>
5 " License:     GPLv2
7 " ============================================================================
8 if exists('loaded_diffit')
9     finish
10 end
11 let loaded_diffit = 1
13 let s:diffit_version = '0'
15 "for line continuation - i.e dont want C in &cpo
16 let s:old_cpo = &cpo
17 set cpo&vim
19 map <silent> <Leader>d :call <SID>Diffit()<CR>
21 function s:Error(msg)
22         echohl ErrorMsg
23         echon 'diffit: ' . a:msg
24         echohl None
25 endfunction
27 function s:Info(msg)
28         echon 'diffit: ' . a:msg
29 endfunction
31 function s:Die(msg)
32         call s:Error('fatal: ' . a:msg)
33         throw "diffit"
34 endfunction
36 function s:System(...)
37         let out = system(join(a:000))
38         if v:shell_error
39                 call s:Die(a:0 . ' failed: ' . out)
40         end
41         return out
42 endfunction
44 function s:Header()
45         let header = []
46         for n in range(1, line('$'))
47                         let l = getline(n)
48                         if l =~ '^diff --git ' ||
49                                                 \l =~ '^diff --cc ' ||
50                                                 \l =~ '^diff --combined ' ||
51                                                 \l =~ '^old mode ' ||
52                                                 \l =~ '^new mode ' ||
53                                                 \l =~ '^--- ' ||
54                                                 \l =~ '^+++ '
55                                 call add(header, l)
56                                 continue
57                         end
58                         if l !~ '^index '
59                                 break
60                         end
61         endfor
62         return [header, n - 1]
63 endfunction
65 function s:Exit()
66         let view = b:view
67         bdelete
68         call winrestview(view)
69 endfunction
71 function s:Diffit()
72         if exists('b:diffit') && b:diffit == 1
73                 call s:Exit()
74                 return
75         end
76         try
77                 call s:Diffit_init()
78         catch /^diffit$/
79                 if exists('b:diffit') && b:diffit == 1
80                         call s:Exit()
81                 end
82         endtry
83 endfunction
85 function s:Read_diff(pathlist)
86         let diff = tempname()
87         let path = ''
88         for path in a:pathlist
89                 let out = s:System('git diff', '--', path, '>', diff)
90                 if getfsize(diff) > 0
91                         break
92                 end
93         endfor
94         return [diff, path]
95 endfunction
97 function s:Write_diff(diff, orig)
98         setlocal modifiable
99         silent 1,$delete _
100         silent exe 'read ' . a:diff
101         silent 1delete _
102         setlocal nomodifiable
104         if a:orig
105                 let orig_pos = b:view['lnum']
106                 let new_pos = s:Diffpos(orig_pos)
107                 let view = copy(b:view)
108                 let view['lnum'] = abs(new_pos)
109                 if new_pos > 0
110                         let view['topline'] += new_pos - orig_pos
111                         let view['topline'] = max([1, view['topline']])
112                 else
113                         let view['topline'] = -new_pos - 4
114                 end
115                 let view['curswant'] += 1
116                 let view['col'] += 1
117                 call winrestview(view)
118         else
119                 call cursor(abs(s:Diffpos(0)), 1)
120         end
121 endfunction
123 function s:Diffit_init()
124         update
125         let out = s:System('git rev-parse',  '--is-inside-work-tree')
126         if v:shell_error == 128 || split(out)[0] != 'true'
127                 call s:Error('not inside work tree')
128                 return
129         elseif v:shell_error
130                 call s:Error('git rev-parse failed: ' . out)
131                 return
132         end
133         let out = s:System('git diff', '--name-only')
134         let pathlist = split(out, '\n')
135         if empty(pathlist)
136                 call s:Info('no changes')
137                 return
138         end
139         let orig_path = bufname('%')
140         let k = index(pathlist, orig_path)
141         if k > 0
142                 call remove(pathlist, k)
143                 call insert(pathlist, orig_path, 0)
144         end
145         let [diff, path] = s:Read_diff(pathlist)
146         let orig = path == orig_path
147         if getfsize(diff) == 0
148                 call s:Info('no changes')
149                 return
150         end
152         let view = winsaveview()
153         silent! exe 'edit ' . tempname()
154         let b:pathlist = pathlist
155         let b:view = view
156         let b:diffit = 1
157         setf git-diff
158         if !exists('b:current_syntax')
159                 setf diff
160         end
161         setlocal noswapfile
162         setlocal buftype=nofile
163         setlocal nowrap
164         setlocal foldcolumn=0
165         setlocal nobuflisted
167         iabc <buffer>
169         nnoremap <silent> <buffer> s :call <SID>Stage_hunk(line('.'))<CR>
170         nnoremap <silent> <buffer> d :call <SID>Next_diff()<CR>
172         call s:Write_diff(diff, orig)
173         echon '"' . path . '"'
174 endfunction
176 function s:Next_diff()
177         call remove(b:pathlist, 0)
178         let [diff, path] = s:Read_diff(b:pathlist)
179         if getfsize(diff) > 0
180                 call s:Write_diff(diff, 0)
181                 echon '"' . path . '"'
182         else
183                 call s:Exit()
184         end
185 endfunction
187 function s:Diffpos(orig_pos)
188         let diffpos = -1
189         let hunk_start = 1
190         let hunk_end = 1
191         call cursor(1, 1)
192         while search('^@@', 'W') > 0
193                 let [start, length] = matchlist(getline('.'),
194                         \'^@@ -[0-9]*,[0-9]* +\%(\([0-9]*\),\)\?\([0-9]*\)')[1:2]
195                 if empty(start)
196                         let start = 1
197                 end
198                 if diffpos < 0
199                         let diffpos = -line('.')
200                 end
201                 if start > a:orig_pos
202                         break
203                 end
204                 let diffpos = line('.')
205                 let hunk_start = start
206                 let hunk_end = hunk_start + length - 1
207         endwhile
208         if diffpos < 0
209                 return diffpos
210         end
211         let pos = hunk_start - 1
212         let target_pos = min([a:orig_pos, hunk_end])
213         while diffpos < line('$')
214                 if getline(diffpos) =~ '^-'
215                         let diffpos += 1
216                         continue
217                 end
218                 if pos >= target_pos
219                         break
220                 end
221                 let diffpos += 1
222                 let pos += 1
223         endwhile
225         if getline(diffpos) =~ '^-'
226                 return -last
227         else
228                 return diffpos
229         end
230 endfunction
232 function s:Stage_hunk(pos)
233         call cursor(a:pos, 1)
234         let h_start = search('^@@', 'bcW')
235         if h_start == 0
236                 return
237         end
238         call cursor(h_start, 1)
239         let h_end = search('^@@', 'nW')-1
240         if h_end < 0
241                 let h_end = line('$')
242         end
243         let h_range = h_start . ',' . h_end
245         let [patch, header_end] = s:Header()
246         call extend(patch, getline(h_start, h_end))
247         let patchfile = tempname()
248         call writefile(patch, patchfile)
249         let out = s:System('git apply', '--cached', '--whitespace=nowarn', patchfile)
251         setlocal modifiable
252         silent exe h_range . 'delete _'
253         setlocal nomodifiable
254         if line('$') == header_end
255                 call s:Next_diff()
256                 return
257         end
258 endfunction
260 let &cpo = s:old_cpo