Skip to content

Commit 17634d6

Browse files
authored
Merge pull request rhysd#118 from jreybert/git_diff
Add git_diff behavior
2 parents 261d02c + 5113cf8 commit 17634d6

File tree

3 files changed

+138
-5
lines changed

3 files changed

+138
-5
lines changed

Diff for: autoload/clang_format.vim

+113-4
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,14 @@ function! clang_format#is_invalid() abort
139139
let s:version = v
140140
endif
141141

142+
if g:clang_format#auto_format_git_diff &&
143+
\ !exists('s:git_available')
144+
if !executable(g:clang_format#git)
145+
return 1
146+
endif
147+
let s:git_available = 1
148+
endif
149+
142150
return 0
143151
endfunction
144152

@@ -184,6 +192,7 @@ let g:clang_format#extra_args = s:getg('clang_format#extra_args', "")
184192
if type(g:clang_format#extra_args) == type([])
185193
let g:clang_format#extra_args = join(g:clang_format#extra_args, " ")
186194
endif
195+
let g:clang_format#git = s:getg('clang_format#git', 'git')
187196

188197
let g:clang_format#code_style = s:getg('clang_format#code_style', 'google')
189198
let g:clang_format#style_options = s:getg('clang_format#style_options', {})
@@ -193,6 +202,8 @@ let g:clang_format#detect_style_file = s:getg('clang_format#detect_style_file',
193202
let g:clang_format#enable_fallback_style = s:getg('clang_format#enable_fallback_style', 1)
194203

195204
let g:clang_format#auto_format = s:getg('clang_format#auto_format', 0)
205+
let g:clang_format#auto_format_git_diff = s:getg('clang_format#auto_format_git_diff', 0)
206+
let g:clang_format#auto_format_git_diff_fallback = s:getg('clang_format#auto_format_git_diff_fallback', 'file')
196207
let g:clang_format#auto_format_on_insert_leave = s:getg('clang_format#auto_format_on_insert_leave', 0)
197208
let g:clang_format#auto_formatexpr = s:getg('clang_format#auto_formatexpr', 0)
198209
let g:clang_format#auto_filetypes = s:getg( 'clang_format#auto_filetypes',
@@ -206,8 +217,13 @@ function! s:detect_style_file() abort
206217
return findfile('.clang-format', dirname.';') != '' || findfile('_clang-format', dirname.';') != ''
207218
endfunction
208219

209-
function! clang_format#format(line1, line2) abort
210-
let args = printf(' -lines=%d:%d', a:line1, a:line2)
220+
" clang_format#format_ranges is were the magic happends.
221+
" ranges is a list of pairs, like [[start1,end1],[start2,end2]...]
222+
function! clang_format#format_ranges(ranges) abort
223+
let args = ''
224+
for range in a:ranges
225+
let args .= printf(' -lines=%d:%d', range[0], range[1])
226+
endfor
211227
if ! (g:clang_format#detect_style_file && s:detect_style_file())
212228
if g:clang_format#enable_fallback_style
213229
let args .= ' ' . s:shellescape(printf('-style=%s', s:make_style_options())) . ' '
@@ -226,14 +242,18 @@ function! clang_format#format(line1, line2) abort
226242
let source = join(getline(1, '$'), "\n")
227243
return s:system(clang_format, source)
228244
endfunction
245+
246+
function! clang_format#format(line1, line2) abort
247+
return clang_format#format_ranges([[line1, line2]])
248+
endfunction
229249
" }}}
230250

231251
" replace buffer {{{
232-
function! clang_format#replace(line1, line2, ...) abort
252+
function! clang_format#replace_ranges(ranges, ...) abort
233253
call s:verify_command()
234254

235255
let pos_save = a:0 >= 1 ? a:1 : getpos('.')
236-
let formatted = clang_format#format(a:line1, a:line2)
256+
let formatted = clang_format#format_ranges(a:ranges)
237257
if !s:success(formatted)
238258
call s:error_message(formatted)
239259
return
@@ -250,6 +270,10 @@ function! clang_format#replace(line1, line2, ...) abort
250270
call winrestview(winview)
251271
call setpos('.', pos_save)
252272
endfunction
273+
274+
function! clang_format#replace(line1, line2, ...) abort
275+
call call(function("clang_format#replace_ranges"), [[line1, line2]], a:000)
276+
endfunction
253277
" }}}
254278

255279
" auto formatting on insert leave {{{
@@ -294,6 +318,91 @@ endfunction
294318
function! clang_format#disable_auto_format() abort
295319
let g:clang_format#auto_format = 0
296320
endfunction
321+
322+
" s:strip: helper function to strip a string
323+
function! s:strip(string)
324+
return substitute(a:string, '^\s*\(.\{-}\)\s*\r\=\n\=$', '\1', '')
325+
endfunction
326+
327+
" clang_format#get_git_diff
328+
" a:file must be an absolute path to the file to be processed
329+
" this function compares the current buffer content against the
330+
" git index content of the file.
331+
" this function returns a list of pair of ranges if the file is tracked
332+
" and has changes, an empty list otherwise
333+
function! clang_format#get_git_diff(cur_file)
334+
let file_path = isdirectory(a:cur_file) ? a:cur_file :
335+
\ fnamemodify(a:cur_file, ":h")
336+
let top_dir=s:strip(system(
337+
\ g:clang_format#git." -C ".shellescape(file_path).
338+
\ " rev-parse --show-toplevel"))
339+
if v:shell_error != 0
340+
return []
341+
endif
342+
let cur_file = s:strip(s:system(
343+
\ g:clang_format#git." -C ".shellescape(top_dir).
344+
\ " ls-files --error-unmatch ".shellescape(a:cur_file)))
345+
if v:shell_error != 0
346+
return []
347+
endif
348+
let source = join(getline(1, '$'), "\n")
349+
" git show :file shows the staged content of the file:
350+
" - content in index if any (staged but not commmited)
351+
" - else content in HEAD
352+
" this solution also solves the problem for 'git mv'ed file:
353+
" - if the current buffer has been renamed by simple mv (without git
354+
" add), the file is considered as untracked
355+
" - if the renamed file has been git added or git mv, git show :file
356+
" will show the expected content.
357+
" this barbarian command does the following:
358+
" - diff --*-group-* options will return ranges (start,end) for each
359+
" diff chunk
360+
" - <(git show :file) is a process substitution, using /dev/fd/<n> as
361+
" temporary file for the output
362+
" - - is stdin, which is current buffer content in variable 'source'
363+
let diff_cmd =
364+
\ 'diff <('.g:clang_format#git.' show :'.shellescape(cur_file).') - '.
365+
\ '--old-group-format="" --unchanged-group-format="" '.
366+
\ '--new-group-format="%dF-%dL%c''\\012''" '.
367+
\ '--changed-group-format="%dF-%dL%c''\\012''"'
368+
let ranges = s:system(diff_cmd, source)
369+
if !(v:shell_error == 0 || v:shell_error == 1)
370+
throw printf("clang-format: git diff failed `%s` for ranges %s",
371+
\ diff_cmd, ranges)
372+
endif
373+
let ranges = split(ranges, '\n')
374+
" ranges is now a list of pairs [[start1, end1],[start2,end2]...]
375+
let ranges = map(ranges, "split(v:val, '-')")
376+
return ranges
377+
endfunction
378+
379+
" this function will try to format only buffer lines diffing from git index
380+
" content.
381+
" If the file is untracked (not in a git repo or not tracked in a git repo),
382+
" it returns 1.
383+
" If the format succeeds, it returns 0.
384+
function! clang_format#do_auto_format_git_diff()
385+
let cur_file = expand("%:p")
386+
let ranges = clang_format#get_git_diff(cur_file)
387+
if !empty(ranges)
388+
call clang_format#replace_ranges(ranges)
389+
return 0
390+
else
391+
return 1
392+
endif
393+
endfunction
394+
395+
function! clang_format#do_auto_format()
396+
if g:clang_format#auto_format_git_diff
397+
let ret = clang_format#do_auto_format_git_diff()
398+
if ret == 0 ||
399+
\ g:clang_format#auto_format_git_diff_fallback != 'file'
400+
return
401+
endif
402+
endif
403+
call clang_format#replace_ranges([[1, line('$')]])
404+
endfunction
405+
297406
" }}}
298407
let &cpo = s:save_cpo
299408
unlet s:save_cpo

Diff for: doc/clang-format.txt

+24
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,11 @@ g:clang_format#command *g:clang_format#command*
128128
Name of clang-format command.
129129
The default value is "clang-format".
130130

131+
g:clang_format#git *g:clang_format#git*
132+
133+
Name of the git command.
134+
The default value is "git".
135+
131136
g:clang_format#code_style *g:clang_format#code_style*
132137

133138
Base coding style for formatting. Available coding styles are "llvm",
@@ -187,6 +192,25 @@ g:clang_format#auto_format *g:clang_format#auto_format*
187192
buffer on saving the buffer. Formatting is executed at |BufWritePre| event.
188193
The default value is 0.
189194

195+
g:clang_format#auto_format_git_diff *g:clang_format#auto_format_git_diff*
196+
197+
When this value is 1, and when g:clang_format#auto_format is 1, the auto
198+
format only formats modified lines is the file is tracked in git.
199+
If the file is not tracked, or even not in a git project, fallback
200+
behavior depends on |g:clang_format#auto_format_git_diff_fallback|.
201+
WARNING: this option should not be used with
202+
|g:clang_format#auto_format_on_insert_leave|.
203+
The default value is 0.
204+
205+
g:clang_format#auto_format_git_diff_fallback
206+
*g:clang_format#auto_format_git_diff_fallback*
207+
208+
Fallback behavior when |g:clang_format#auto_format_git_diff| is 1 and the
209+
current file is not tracked. The value can be:
210+
- 'file': the whole file is formatted (which is the default
211+
|g:clang_format#auto_format| behavior).
212+
- 'pass': the file is not formatted.
213+
190214
g:clang_format#auto_format_on_insert_leave
191215
*g:clang_format#auto_format_on_insert_leave*
192216

Diff for: plugin/clang_format.vim

+1-1
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ augroup plugin-clang-format-auto-format
2222
\ if index(g:clang_format#auto_filetypes, &ft) >= 0 &&
2323
\ g:clang_format#auto_format &&
2424
\ !clang_format#is_invalid() |
25-
\ call clang_format#replace(1, line('$')) |
25+
\ call clang_format#do_auto_format() |
2626
\ endif
2727
autocmd FileType *
2828
\ if index(g:clang_format#auto_filetypes, &ft) >= 0 &&

0 commit comments

Comments
 (0)