Formatting the changed lines (VC) of C code buffer.
The following elisp function (and it's support functions) will clang-format the changed lines in buffer containing C/C++. If the function looks a bit long that's because it is being careful to not delete and replace many lines so that the point is not disturned as much as possible.
(defvar diffu-new-lines-re "^@@.*\\+\\([0-9]+\\)\\(,\\([0-9]+\\)\\)?")
(defun clang-format-args-from-diff-buffer (&optional buffer)
(let ((use-buf (or buffer (current-buffer)))
lines)
(with-current-buffer use-buf
(save-excursion
(goto-char (point-max))
(while (re-search-backward diffu-new-lines-re nil 'noerror)
(let ((start (string-to-number (match-string 1)))
(count (string-to-number (or (match-string 3) "1"))))
(unless (= count 0)
(setq lines (cons (format "--lines=%d:%d" start (+ start count -1)) lines)))))))
lines))
(defun clang-format-vc-diff ()
"Reformat only the changed lines from VC in the buffer"
(interactive)
(let* ((code-buffer (current-buffer))
(cursor (clang-format--bufferpos-to-filepos (point) 'exact 'utf-8-unix))
;; vc-working-revision has some bizarre cache bug, it
;; returns the revision that was HEAD the first time it is
;; checked in a buffer. If that buffer is saved, and checked
;; the next call to vc-working-revision is not updated, one
;; must reload the file. So instead invoke the backend directly.
(head-revision (vc-call-backend (vc-backend buffer-file-name)
'working-revision
buffer-file-name))
(temp-buffer (generate-new-buffer " *clang-format-temp*"))
(temp-file (make-temp-file "clang-format"))
diff-line-args
head-temp-file)
(unwind-protect
(if (not head-revision)
(progn
(message "(clang-format-vc-diff: %s not revision controlled, formatting whole buffer"
buffer-file-name)
(clang-format-buffer))
;; Get the HEAD revision into a temp file
(let ((head-temp-buf (vc-find-revision buffer-file-name head-revision)))
(setq head-temp-file (buffer-file-name head-temp-buf))
(kill-buffer head-temp-buf))
;; Get the diff of buffer from the HEAD revision
(let ((status (call-process-region
nil nil (executable-find "diff")
nil `(,temp-buffer ,temp-file) nil
"-u0"
head-temp-file
"-"))
(stderr (with-temp-buffer
(unless (zerop (cadr (insert-file-contents temp-file)))
(insert ": "))
(buffer-substring-no-properties
(point-min) (line-end-position)))))
(cond
((stringp status)
(error "(clang-format-vc-diff diff killed by signal %s%s)" status stderr))
((zerop status)
(message "(clang-format-vc-diff no diff"))
((not (= 1 status))
(error "(clang-format-vc-diff diff failed with code %d%s)" status stderr))))
(setq diff-line-args (clang-format-args-from-diff-buffer temp-buffer))
;; empty the temp buffer, and delete the temp file
(with-current-buffer temp-buffer (erase-buffer))
;; Get xml-replacements into now-empty temp-buffer
(if (not diff-line-args)
(message "(clang-format-vc-diff no differences to format)")
(let ((status (apply #'call-process-region
nil nil clang-format-executable
nil `(,temp-buffer ,temp-file) nil
`("-output-replacements-xml"
"-assume-filename" ,(file-name-nondirectory buffer-file-name)
"-fallback-style" ,clang-format-fallback-style
"-cursor" ,(number-to-string cursor)
,@diff-line-args)))
(stderr (with-temp-buffer
(unless (zerop (cadr (insert-file-contents temp-file)))
(insert ": "))
(buffer-substring-no-properties
(point-min) (line-end-position)))))
(cond
((stringp status)
(error "(clang-format killed by signal %s%s)" status stderr))
((not (zerop status))
(error "(clang-format failed with code %d%s)" status stderr)))
(if (= 0 (buffer-size temp-buffer))
(message "(vpp-clang-diff-format-buffer no formatting changes")
(cl-destructuring-bind (replacements cursor incomplete-format)
(with-current-buffer temp-buffer
(clang-format--extract (car (xml-parse-region))))
(save-excursion
(dolist (rpl replacements)
(apply #'clang-format--replace rpl)))
(when cursor
(goto-char (clang-format--filepos-to-bufferpos cursor 'exact
'utf-8-unix)))
(if incomplete-format
(message "(clang-format: incomplete (syntax errors)%s)" stderr)
(message "(clang-format: success%s)" stderr)))))))
(ignore-errors (kill-buffer temp-buffer))
(ignore-errors (delete-file temp-file))
(ignore-errors (delete-file head-temp-file)))))