@@ -146,24 +146,133 @@ is a zero-based file offset, assuming ‘utf-8-unix’ coding."
146146 (lambda (byte &optional _quality _coding-system )
147147 (byte-to-position (1+ byte)))))
148148
149- ;;;### autoload
150- (defun clang-format-region (start end &optional style assume-file-name )
151- " Use clang-format to format the code between START and END according to STYLE.
152- If called interactively uses the region or the current statement if there is no
153- no active region. If no STYLE is given uses `clang-format-style' . Use
154- ASSUME-FILE-NAME to locate a style config file, if no ASSUME-FILE-NAME is given
155- uses the function `buffer-file-name' ."
156- (interactive
157- (if (use-region-p )
158- (list (region-beginning ) (region-end ))
159- (list (point ) (point ))))
149+ (defmacro clang-format--with-delete-files-guard (bind-files-to-delete &rest body )
150+ " Execute BODY which may add temp files to BIND-FILES-TO-DELETE."
151+ (declare (indent 1 ))
152+ `(let ((, bind-files-to-delete nil ))
153+ (unwind-protect
154+ (progn
155+ ,@body )
156+ (while , bind-files-to-delete
157+ (with-demoted-errors " failed to remove file: %S"
158+ (delete-file (pop , bind-files-to-delete )))))))
159+
160+
161+ (defun clang-format--vc-diff-get-diff-lines (file-orig file-new )
162+ " Return all line regions that contain diffs between FILE-ORIG and
163+ FILE-NEW. If there is no diff ‘nil’ is returned. Otherwise the return
164+ is a ‘list’ of line ranges to format. The list of line ranges can be
165+ passed to ‘clang-format--region-impl’"
166+ ; ; Use temporary buffer for output of diff.
167+ (with-temp-buffer
168+ ; ; We could use diff.el:diff-no-select here. The reason we don't
169+ ; ; is diff-no-select requires extra copies on the buffers which
170+ ; ; induces noticeable slowdowns, especially on larger files.
171+ (let ((status (call-process
172+ diff-command
173+ nil
174+ (current-buffer )
175+ nil
176+ ; ; Binary diff has different behaviors that we
177+ ; ; aren't interested in.
178+ " -a"
179+ ; ; Get minimal diff (copy diff config for git-clang-format).
180+ " -U0"
181+ file-orig
182+ file-new))
183+ (stderr (concat (if (zerop (buffer-size )) " " " : " )
184+ (buffer-substring-no-properties
185+ (point-min ) (line-end-position ))))
186+ (diff-lines '()))
187+ (cond
188+ ((stringp status)
189+ (error " clang-format: (diff killed by signal %s%s ) " status stderr))
190+ ; ; Return of 0 indicates no diff.
191+ ((= status 0 ) nil )
192+ ; ; Return of 1 indicates found diffs and no error.
193+ ((= status 1 )
194+ ; ; Find and collect all diff lines.
195+ ; ; We are matching something like:
196+ ; ; "@@ -80 +80 @@" or "@@ -80,2 +80,2 @@"
197+ (goto-char (point-min ))
198+ (while (re-search-forward
199+ " ^@@[[:blank:]]-[[:digit:],]+[[:blank:]]\\ +\\ ([[:digit:]]+\\ )\\ (,\\ ([[:digit:]]+\\ )\\ )?[[:blank:]]@@$"
200+ nil
201+ t
202+ 1 )
203+ (let ((match1 (string-to-number (match-string 1 )))
204+ (match3 (let ((match3_or_nil (match-string 3 )))
205+ (if match3_or_nil
206+ (string-to-number match3_or_nil)
207+ nil ))))
208+ (push (cons match1 (if match3 (+ match1 match3) match1)) diff-lines)))
209+ (nreverse diff-lines))
210+ ; ; Any return != 0 && != 1 indicates some level of error.
211+ (t
212+ (error " clang-format: (diff returned unsuccessfully %s%s ) " status stderr))))))
213+
214+ (defun clang-format--vc-diff-get-vc-head-file (tmpfile-vc-head )
215+ " Stores the contents of ‘buffer-file-name’ at vc revision HEAD into
216+ ‘tmpfile-vc-head’. If the current buffer is either not a file or not
217+ in a vc repo, this results in an error. Currently git is the only
218+ supported vc."
219+ ; ; We need the current buffer to be a file.
220+ (unless (buffer-file-name )
221+ (error " clang-format: Buffer is not visiting a file " ))
222+
223+ (let ((base-dir (vc-root-dir ))
224+ (backend (vc-backend (buffer-file-name ))))
225+ ; ; We need to be able to find version control (git) root.
226+ (unless base-dir
227+ (error " clang-format: File not known to git " ))
228+ (cond
229+ ((string-equal backend " Git" )
230+ ; ; Get the filename relative to git root.
231+ (let ((vc-file-name (substring
232+ (expand-file-name (buffer-file-name ))
233+ (string-width (expand-file-name base-dir))
234+ nil )))
235+ (let ((status (call-process
236+ vc-git-program
237+ nil
238+ `(:file , tmpfile-vc-head )
239+ nil
240+ " show" (concat " HEAD:" vc-file-name)))
241+ (stderr (with-temp-buffer
242+ (unless (zerop (cadr (insert-file-contents tmpfile-vc-head)))
243+ (insert " : " ))
244+ (buffer-substring-no-properties
245+ (point-min ) (line-end-position )))))
246+ (when (stringp status)
247+ (error " clang-format: (git show HEAD:%s killed by signal %s%s ) "
248+ vc-file-name status stderr))
249+ (unless (zerop status)
250+ (error " clang-format: (git show HEAD:%s returned unsuccessfully %s%s ) "
251+ vc-file-name status stderr)))))
252+ (t
253+ (error
254+ " Version control %s isn't supported, currently supported backends: git"
255+ backend)))))
256+
160257
258+ (defun clang-format--region-impl (start end &optional style assume-file-name lines )
259+ " Common implementation for ‘clang-format-buffer’,
260+ ‘clang-format-region’, and ‘clang-format-vc-diff’. START and END
261+ refer to the region to be formatter. STYLE and ASSUME-FILE-NAME are
262+ used for configuring the clang-format. And LINES is used to pass
263+ specific locations for reformatting (i.e diff locations)."
161264 (unless style
162265 (setq style clang-format-style))
163266
164267 (unless assume-file-name
165268 (setq assume-file-name (buffer-file-name (buffer-base-buffer ))))
166269
270+ ; ; Convert list of line ranges to list command for ‘clang-format’ executable.
271+ (when lines
272+ (setq lines (mapcar (lambda (range )
273+ (format " --lines=%d :%d " (car range) (cdr range)))
274+ lines)))
275+
167276 (let ((file-start (clang-format--bufferpos-to-filepos start 'approximate
168277 'utf-8-unix ))
169278 (file-end (clang-format--bufferpos-to-filepos end 'approximate
@@ -190,8 +299,12 @@ uses the function `buffer-file-name'."
190299 (list " --assume-filename" assume-file-name))
191300 ,@(and style (list " --style" style))
192301 " --fallback-style" , clang-format-fallback-style
193- " --offset" ,(number-to-string file-start)
194- " --length" ,(number-to-string (- file-end file-start))
302+ ,@(and lines lines)
303+ ,@(and (not lines)
304+ (list
305+ " --offset" (number-to-string file-start)
306+ " --length" (number-to-string
307+ (- file-end file-start))))
195308 " --cursor" ,(number-to-string cursor ))))
196309 (stderr (with-temp-buffer
197310 (unless (zerop (cadr (insert-file-contents temp-file)))
@@ -216,17 +329,74 @@ uses the function `buffer-file-name'."
216329 (if incomplete-format
217330 (message " (clang-format: incomplete (syntax errors)%s ) " stderr)
218331 (message " (clang-format: success%s ) " stderr))))
219- (delete-file temp-file)
332+ (with-demoted-errors
333+ " clang-format: Failed to delete temporary file: %S"
334+ (delete-file temp-file))
220335 (when (buffer-name temp-buffer) (kill-buffer temp-buffer)))))
221336
337+
338+ ;;;### autoload
339+ (defun clang-format-vc-diff (&optional style assume-file-name )
340+ " The same as ‘clang-format-buffer’ but only operates on the vc
341+ diffs from HEAD in the buffer. If no STYLE is given uses
342+ ‘clang-format-style’. Use ASSUME-FILE-NAME to locate a style config
343+ file. If no ASSUME-FILE-NAME is given uses the function
344+ ‘buffer-file-name’."
345+ (interactive )
346+ (clang-format--with-delete-files-guard tmp-files
347+ (let ((tmpfile-vc-head nil )
348+ (tmpfile-curbuf nil ))
349+ (setq tmpfile-vc-head
350+ (make-temp-file " clang-format-vc-tmp-head-content" ))
351+ (push tmpfile-vc-head tmp-files)
352+ (clang-format--vc-diff-get-vc-head-file tmpfile-vc-head)
353+ ; ; Move the current buffer to a temporary file to take a
354+ ; ; diff. Even if current-buffer is backed by a file, we
355+ ; ; want to diff the buffer contents which might not be
356+ ; ; saved.
357+ (setq tmpfile-curbuf (make-temp-file " clang-format-vc-tmp" ))
358+ (push tmpfile-curbuf tmp-files)
359+ (write-region nil nil tmpfile-curbuf nil 'nomessage )
360+ ; ; Get a list of lines with a diff.
361+ (let ((diff-lines
362+ (clang-format--vc-diff-get-diff-lines
363+ tmpfile-vc-head tmpfile-curbuf)))
364+ ; ; If we have any diffs, format them.
365+ (when diff-lines
366+ (clang-format--region-impl
367+ (point-min )
368+ (point-max )
369+ style
370+ assume-file-name
371+ diff-lines))))))
372+
373+
374+ ;;;### autoload
375+ (defun clang-format-region (start end &optional style assume-file-name )
376+ " Use clang-format to format the code between START and END according
377+ to STYLE. If called interactively uses the region or the current
378+ statement if there is no no active region. If no STYLE is given uses
379+ `clang-format-style' . Use ASSUME-FILE-NAME to locate a style config
380+ file, if no ASSUME-FILE-NAME is given uses the function
381+ `buffer-file-name' ."
382+ (interactive
383+ (if (use-region-p )
384+ (list (region-beginning ) (region-end ))
385+ (list (point ) (point ))))
386+ (clang-format--region-impl start end style assume-file-name))
387+
222388;;;### autoload
223389(defun clang-format-buffer (&optional style assume-file-name )
224390 " Use clang-format to format the current buffer according to STYLE.
225391If no STYLE is given uses `clang-format-style' . Use ASSUME-FILE-NAME
226392to locate a style config file. If no ASSUME-FILE-NAME is given uses
227393the function `buffer-file-name' ."
228394 (interactive )
229- (clang-format-region (point-min ) (point-max ) style assume-file-name))
395+ (clang-format--region-impl
396+ (point-min )
397+ (point-max )
398+ style
399+ assume-file-name))
230400
231401;;;### autoload
232402(defalias 'clang-format 'clang-format-region )
0 commit comments