Skip to content

Commit ebed7ec

Browse files
author
jerome
committed
Add new 'auto_format_git_diff' behavior
When you edit a file tracked in git, it may happend that some tracked lines are not well formatted. If you modify this file, and you auto_format the file, lines with bad indentation will be formatted too. You will be end up with unrelated changes in your commit. It may even appear that these 'bad formatted lines' were indented like this with a purpose. To mitigate this issue, this commit proposes to only format diff'ed lines in a file. It roughly relies on git diff, and on clang-format '-lines' option. clang_format#format function has been modified to accept a list of ranges. It adds three new user options: - g:clang_format#auto_format_git_diff to enable this new behavior - g:clang_format#auto_format_git_diff_fallback to decide what to do in case of untracked file - g:clang_format#git to define non standard git path or options
1 parent 95593b6 commit ebed7ec

File tree

3 files changed

+103
-6
lines changed

3 files changed

+103
-6
lines changed

autoload/clang_format.vim

+78-5
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:get('clang_format#auto_format_git_diff', 0)
206+
let g:clang_format#auto_format_git_diff_fallback = s:get('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
" }}}
@@ -203,8 +214,11 @@ function! s:detect_style_file() abort
203214
return findfile('.clang-format', dirname.';') != '' || findfile('_clang-format', dirname.';') != ''
204215
endfunction
205216

206-
function! clang_format#format(line1, line2) abort
207-
let args = printf(' -lines=%d:%d', a:line1, a:line2)
217+
function! clang_format#format(ranges) abort
218+
let args = ''
219+
for range in a:ranges
220+
let args .= printf(' -lines=%d:%d', range[0], range[1])
221+
endfor
208222
if ! (g:clang_format#detect_style_file && s:detect_style_file())
209223
if g:clang_format#enable_fallback_style
210224
let args .= ' ' . s:shellescape(printf('-style=%s', s:make_style_options())) . ' '
@@ -226,11 +240,11 @@ endfunction
226240
" }}}
227241

228242
" replace buffer {{{
229-
function! clang_format#replace(line1, line2, ...) abort
243+
function! clang_format#replace(ranges, ...) abort
230244
call s:verify_command()
231245

232246
let pos_save = a:0 >= 1 ? a:1 : getpos('.')
233-
let formatted = clang_format#format(a:line1, a:line2)
247+
let formatted = clang_format#format(a:ranges)
234248
if !s:success(formatted)
235249
call s:error_message(formatted)
236250
return
@@ -256,7 +270,7 @@ function! s:format_inserted_area() abort
256270
let pos = getpos('.')
257271
" When in the same buffer
258272
if &modified && ! empty(s:pos_on_insertenter) && s:pos_on_insertenter[0] == pos[0]
259-
call clang_format#replace(s:pos_on_insertenter[1], line('.'))
273+
call clang_format#replace([[s:pos_on_insertenter[1], line('.')]])
260274
let s:pos_on_insertenter = []
261275
endif
262276
endfunction
@@ -291,6 +305,65 @@ endfunction
291305
function! clang_format#disable_auto_format() abort
292306
let g:clang_format#auto_format = 0
293307
endfunction
308+
" s:chdir will change the directory respecting
309+
" local/tab-local/global directory settings.
310+
function! s:chdir(dir)
311+
" This is a dirty hack to fix tcd breakages on neovim.
312+
" Future work should be based on nvim API.
313+
if exists(':tcd')
314+
let chdir = haslocaldir() ? 'lcd' : haslocaldir(-1, 0) ? 'tcd' : 'cd'
315+
else
316+
let chdir = exists('*haslocaldir') && haslocaldir() ? 'lcd' : 'cd'
317+
endif
318+
execute chdir fnameescape(a:dir)
319+
endfunction
320+
321+
" s:strip: helper function to strip a string
322+
function! s:strip(string)
323+
return substitute(a:string, '^\s*\(.\{-}\)\s*\n\=$', '\1', '')
324+
endfunction
325+
326+
function! clang_format#do_auto_format_git_diff()
327+
let dir = getcwd()
328+
let cur_file = expand("%:p")
329+
let cur_file_path = isdirectory(cur_file) ? cur_file : fnamemodify(cur_file, ":h")
330+
try
331+
call s:chdir(cur_file_path)
332+
let top_dir=s:strip(system(
333+
\ g:clang_format#git." rev-parse --show-toplevel"))
334+
call s:chdir(top_dir)
335+
let cur_file = s:system(
336+
\ g:clang_format#git." ls-files --error-unmatch ".cur_file)
337+
let source = join(getline(1, '$'), "\n")
338+
let ranges = s:system(
339+
\ 'diff <('.g:clang_format#git.' show :'.cur_file.') - '
340+
\ '--old-group-format="" --unchanged-group-format="" '
341+
\ '--new-group-format="%dF-%dL%c''\\012''" '
342+
\ '--changed-group-format="%dF-%dL%c''\\012''"',
343+
\ source)
344+
let ranges = split(ranges, '\n')
345+
let ranges = map(ranges, "split(v:val, '-')")
346+
call clang_format#replace(ranges)
347+
catch
348+
" not in a git dir, or not a tracked file
349+
return 1
350+
finally
351+
call s:chdir(dir)
352+
endtry
353+
return 0
354+
endfunction
355+
356+
function! clang_format#do_auto_format()
357+
if g:clang_format#auto_format_git_diff
358+
let ret = clang_format#do_auto_format_git_diff()
359+
if ret == 0 ||
360+
\ g:clang_format#auto_format_git_diff_fallback != 'file'
361+
return
362+
endif
363+
endif
364+
call clang_format#replace([[1, line('$')]])
365+
endfunction
366+
294367
" }}}
295368
let &cpo = s:save_cpo
296369
unlet s:save_cpo

doc/clang-format.txt

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

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

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

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

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 &ft =~# '^\%(c\|cpp\|objc\|java\|javascript\|typescript\|proto\|arduino\)$' &&
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 c,cpp,objc,java,javascript,typescript,proto,arduino
2828
\ if g:clang_format#auto_format_on_insert_leave &&

0 commit comments

Comments
 (0)