@@ -139,6 +139,14 @@ function! clang_format#is_invalid() abort
139
139
let s: version = v
140
140
endif
141
141
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
+
142
150
return 0
143
151
endfunction
144
152
@@ -184,6 +192,7 @@ let g:clang_format#extra_args = s:getg('clang_format#extra_args', "")
184
192
if type (g: clang_format #extra_args) == type ([])
185
193
let g: clang_format #extra_args = join (g: clang_format #extra_args, " " )
186
194
endif
195
+ let g: clang_format #git = s: getg (' clang_format#git' , ' git' )
187
196
188
197
let g: clang_format #code_style = s: getg (' clang_format#code_style' , ' google' )
189
198
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',
193
202
let g: clang_format #enable_fallback_style = s: getg (' clang_format#enable_fallback_style' , 1 )
194
203
195
204
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' )
196
207
let g: clang_format #auto_format_on_insert_leave = s: getg (' clang_format#auto_format_on_insert_leave' , 0 )
197
208
let g: clang_format #auto_formatexpr = s: getg (' clang_format#auto_formatexpr' , 0 )
198
209
" }}}
@@ -203,8 +214,13 @@ function! s:detect_style_file() abort
203
214
return findfile (' .clang-format' , dirname.' ;' ) != ' ' || findfile (' _clang-format' , dirname.' ;' ) != ' '
204
215
endfunction
205
216
206
- function ! clang_format#format (line1, line2) abort
207
- let args = printf (' -lines=%d:%d' , a: line1 , a: line2 )
217
+ " clang_format#format_ranges is were the magic happends.
218
+ " ranges is a list of pairs, like [[start1,end1],[start2,end2]...]
219
+ function ! clang_format#format_ranges (ranges ) abort
220
+ let args = ' '
221
+ for range in a: ranges
222
+ let args .= printf (' -lines=%d:%d' , range [0 ], range [1 ])
223
+ endfor
208
224
if ! (g: clang_format #detect_style_file && s: detect_style_file ())
209
225
if g: clang_format #enable_fallback_style
210
226
let args .= ' ' . s: shellescape (printf (' -style=%s' , s: make_style_options ())) . ' '
@@ -223,14 +239,18 @@ function! clang_format#format(line1, line2) abort
223
239
let source = join (getline (1 , ' $' ), " \n " )
224
240
return s: system (clang_format, source )
225
241
endfunction
242
+
243
+ function ! clang_format#format (line1, line2) abort
244
+ return clang_format#format_ranges ([[line1, line2]])
245
+ endfunction
226
246
" }}}
227
247
228
248
" replace buffer {{{
229
- function ! clang_format#replace (line1, line2 , ... ) abort
249
+ function ! clang_format#replace_ranges ( ranges , ... ) abort
230
250
call s: verify_command ()
231
251
232
252
let pos_save = a: 0 >= 1 ? a: 1 : getpos (' .' )
233
- let formatted = clang_format#format (a: line1 , a: line2 )
253
+ let formatted = clang_format#format_ranges (a: ranges )
234
254
if ! s: success (formatted)
235
255
call s: error_message (formatted)
236
256
return
@@ -247,6 +267,10 @@ function! clang_format#replace(line1, line2, ...) abort
247
267
call winrestview (winview)
248
268
call setpos (' .' , pos_save)
249
269
endfunction
270
+
271
+ function ! clang_format#replace (line1, line2, ... ) abort
272
+ call call (function (" clang_format#replace_ranges" ), [[line1, line2]], a: 000 )
273
+ endfunction
250
274
" }}}
251
275
252
276
" auto formatting on insert leave {{{
@@ -291,6 +315,91 @@ endfunction
291
315
function ! clang_format#disable_auto_format () abort
292
316
let g: clang_format #auto_format = 0
293
317
endfunction
318
+
319
+ " s:strip: helper function to strip a string
320
+ function ! s: strip (string )
321
+ return substitute (a: string , ' ^\s*\(.\{-}\)\s*\r\=\n\=$' , ' \1' , ' ' )
322
+ endfunction
323
+
324
+ " clang_format#get_git_diff
325
+ " a:file must be an absolute path to the file to be processed
326
+ " this function compares the current buffer content against the
327
+ " git index content of the file.
328
+ " this function returns a list of pair of ranges if the file is tracked
329
+ " and has changes, an empty list otherwise
330
+ function ! clang_format#get_git_diff (cur_file)
331
+ let file_path = isdirectory (a: cur_file ) ? a: cur_file :
332
+ \ fnamemodify (a: cur_file , " :h" )
333
+ let top_dir= s: strip (system (
334
+ \ g: clang_format #git." -C " .shellescape (file_path).
335
+ \ " rev-parse --show-toplevel" ))
336
+ if v: shell_error != 0
337
+ return []
338
+ endif
339
+ let cur_file = s: strip (s: system (
340
+ \ g: clang_format #git." -C " .shellescape (top_dir).
341
+ \ " ls-files --error-unmatch " .shellescape (a: cur_file )))
342
+ if v: shell_error != 0
343
+ return []
344
+ endif
345
+ let source = join (getline (1 , ' $' ), " \n " )
346
+ " git show :file shows the staged content of the file:
347
+ " - content in index if any (staged but not commmited)
348
+ " - else content in HEAD
349
+ " this solution also solves the problem for 'git mv'ed file:
350
+ " - if the current buffer has been renamed by simple mv (without git
351
+ " add), the file is considered as untracked
352
+ " - if the renamed file has been git added or git mv, git show :file
353
+ " will show the expected content.
354
+ " this barbarian command does the following:
355
+ " - diff --*-group-* options will return ranges (start,end) for each
356
+ " diff chunk
357
+ " - <(git show :file) is a process substitution, using /dev/fd/<n> as
358
+ " temporary file for the output
359
+ " - - is stdin, which is current buffer content in variable 'source'
360
+ let diff_cmd =
361
+ \ ' diff <(' .g: clang_format #git.' show :' .shellescape (cur_file).' ) - ' .
362
+ \ ' --old-group-format="" --unchanged-group-format="" ' .
363
+ \ ' --new-group-format="%dF-%dL%c'' \\012'' " ' .
364
+ \ ' --changed-group-format="%dF-%dL%c'' \\012'' "'
365
+ let ranges = s: system (diff_cmd, source )
366
+ if ! (v: shell_error == 0 || v: shell_error == 1 )
367
+ throw printf (" clang-format: git diff failed `%s` for ranges %s" ,
368
+ \ diff_cmd, ranges )
369
+ endif
370
+ let ranges = split (ranges , ' \n' )
371
+ " ranges is now a list of pairs [[start1, end1],[start2,end2]...]
372
+ let ranges = map (ranges , " split(v:val, '-')" )
373
+ return ranges
374
+ endfunction
375
+
376
+ " this function will try to format only buffer lines diffing from git index
377
+ " content.
378
+ " If the file is untracked (not in a git repo or not tracked in a git repo),
379
+ " it returns 1.
380
+ " If the format succeeds, it returns 0.
381
+ function ! clang_format#do_auto_format_git_diff ()
382
+ let cur_file = expand (" %:p" )
383
+ let ranges = clang_format#get_git_diff (cur_file)
384
+ if ! empty (ranges )
385
+ call clang_format#replace_ranges (ranges )
386
+ return 0
387
+ else
388
+ return 1
389
+ endif
390
+ endfunction
391
+
392
+ function ! clang_format#do_auto_format ()
393
+ if g: clang_format #auto_format_git_diff
394
+ let ret = clang_format#do_auto_format_git_diff ()
395
+ if ret == 0 ||
396
+ \ g: clang_format #auto_format_git_diff_fallback != ' file'
397
+ return
398
+ endif
399
+ endif
400
+ call clang_format#replace_ranges ([[1 , line (' $' )]])
401
+ endfunction
402
+
294
403
" }}}
295
404
let &cpo = s: save_cpo
296
405
unlet s: save_cpo
0 commit comments