Skip to content
Merged
Changes from 15 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
195 changes: 180 additions & 15 deletions clang/tools/clang-format/clang-format.el
Original file line number Diff line number Diff line change
Expand Up @@ -146,18 +146,124 @@ is a zero-based file offset, assuming ‘utf-8-unix’ coding."
(lambda (byte &optional _quality _coding-system)
(byte-to-position (1+ byte)))))

;;;###autoload
(defun clang-format-region (start end &optional style assume-file-name)
"Use clang-format to format the code between START and END according to STYLE.
If called interactively uses the region or the current statement if there is no
no active region. If no STYLE is given uses `clang-format-style'. Use
ASSUME-FILE-NAME to locate a style config file, if no ASSUME-FILE-NAME is given
uses the function `buffer-file-name'."
(interactive
(if (use-region-p)
(list (region-beginning) (region-end))
(list (point) (point))))
(defmacro clang-format--with-delete-files-guard (bind-files-to-delete &rest body)
"Execute BODY which may add temp files to BIND-FILES-TO-DELETE."
(declare (indent 1))
`(let ((,bind-files-to-delete nil))
(unwind-protect
(progn
,@body)
(while ,bind-files-to-delete
(with-demoted-errors "failed to remove file: %S"
(delete-file (pop ,bind-files-to-delete)))))))


(defun clang-format--vc-diff-get-diff-lines (file-orig file-new)
Copy link
Contributor

@ideasman42 ideasman42 Jan 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

*picky* - personal preference, but I find it odd that the function returns command line arguments for clang-format.

It can return a list of cons-pairs of instead (list (cons int int) ...) instead, then the clang-format function can convert them to the arguments used by clang-format.

(the patch I attached did this), it's not a big change, I'd expect a formatting function that can operate on multiple ranges to take int-pairs instead of a list of strings.

"Return all line regions that contain diffs between FILE-ORIG and
FILE-NEW. If there is no diff ‘nil’ is returned. Otherwise the
return is a ‘list’ of lines in the format ‘--lines=<start>:<end>’
which can be passed directly to ‘clang-format’."
;; Use temporary buffer for output of diff.
(with-temp-buffer
;; We could use diff.el:diff-no-select here. The reason we don't
;; is diff-no-select requires extra copies on the buffers which
;; induces noticeable slowdowns, especially on larger files.
(let ((status (call-process
diff-command
nil
(current-buffer)
nil
;; Binary diff has different behaviors that we
;; aren't interested in.
"-a"
;; Get minimal diff (copy diff config for git-clang-format).
"-U0"
file-orig
file-new))
(stderr (concat (if (zerop (buffer-size)) "" ": ")
(buffer-substring-no-properties
(point-min) (line-end-position))))
(diff-lines '()))
(cond
((stringp status)
(error "(diff killed by signal %s%s)" status stderr))
;; Return of 0 indicates no diff.
((= status 0) nil)
;; Return of 1 indicates found diffs and no error.
((= status 1)
;; Find and collect all diff lines.
;; We are matching something like:
;; "@@ -80 +80 @@" or "@@ -80,2 +80,2 @@"
(goto-char (point-min))
(while (re-search-forward
"^@@\s-[0-9,]+\s\\+\\([0-9]+\\)\\(,\\([0-9]+\\)\\)?\s@@$"
nil
t
1)
(let ((match1 (string-to-number (match-string 1)))
(match3 (if (match-string 3)
(string-to-number (match-string 3))
nil)))
(push (format
"--lines=%d:%d"
match1
(if match3 (+ match1 match3) match1))
diff-lines)))
(nreverse diff-lines))
;; Any return != 0 && != 1 indicates some level of error.
(t
(error "(diff returned unsuccessfully %s%s)" status stderr))))))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All errors should use the "clang-format: " prefix, otherwise it can be difficult to track down errors when these functions are called indirectly.


(defun clang-format--vc-diff-get-vc-head-file (tmpfile-vc-head)
"Stores the contents of ‘buffer-file-name’ at vc revision HEAD into
‘tmpfile-vc-head’. If the current buffer is either not a file or not
in a vc repo, this results in an error. Currently git is the only
supported vc."
;; We need the current buffer to be a file.
(unless (buffer-file-name)
(error "Buffer is not visiting a file"))

(let ((base-dir (vc-root-dir))
(backend (vc-backend (buffer-file-name))))
;; We need to be able to find version control (git) root.
(unless base-dir
(error "File not known to git"))
(cond
((string-equal backend "Git")
;; Get the filename relative to git root.
(let ((vc-file-name (substring
(expand-file-name (buffer-file-name))
(string-width (expand-file-name base-dir))
nil)))
(let ((status (call-process
vc-git-program
nil
`(:file ,tmpfile-vc-head)
nil
"show" (concat "HEAD:" vc-file-name)))
(stderr (with-temp-buffer
(unless (zerop (cadr (insert-file-contents tmpfile-vc-head)))
(insert ": "))
(buffer-substring-no-properties
(point-min) (line-end-position)))))
(when (stringp status)
(error "(git show HEAD:%s killed by signal %s%s)"
vc-file-name status stderr))
(unless (zerop status)
(error "(git show HEAD:%s returned unsuccessfully %s%s)"
vc-file-name status stderr)))))
(t
(error
"Version control %s isn't supported, currently supported backends: git"
backend)))))


(defun clang-format--region-impl (start end &optional style assume-file-name lines)
"Common implementation for ‘clang-format-buffer’,
‘clang-format-region’, and ‘clang-format-vc-diff’. START and END
refer to the region to be formatter. STYLE and ASSUME-FILE-NAME are
used for configuring the clang-format. And LINES is used to pass
specific locations for reformatting (i.e diff locations)."
(unless style
(setq style clang-format-style))

Expand Down Expand Up @@ -190,8 +296,12 @@ uses the function `buffer-file-name'."
(list "--assume-filename" assume-file-name))
,@(and style (list "--style" style))
"--fallback-style" ,clang-format-fallback-style
"--offset" ,(number-to-string file-start)
"--length" ,(number-to-string (- file-end file-start))
,@(and lines lines)
,@(and (not lines)
(list
"--offset" (number-to-string file-start)
"--length" (number-to-string
(- file-end file-start))))
"--cursor" ,(number-to-string cursor))))
(stderr (with-temp-buffer
(unless (zerop (cadr (insert-file-contents temp-file)))
Expand All @@ -216,17 +326,72 @@ uses the function `buffer-file-name'."
(if incomplete-format
(message "(clang-format: incomplete (syntax errors)%s)" stderr)
(message "(clang-format: success%s)" stderr))))
(delete-file temp-file)
(ignore-errors (delete-file temp-file))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggest with-demoted-errors here too, could be a function as it's used elsewhere.

(when (buffer-name temp-buffer) (kill-buffer temp-buffer)))))


;;;###autoload
(defun clang-format-vc-diff (&optional style assume-file-name)
"The same as ‘clang-format-buffer’ but only operates on the vc
diffs from HEAD in the buffer. If no STYLE is given uses
‘clang-format-style’. Use ASSUME-FILE-NAME to locate a style config
file. If no ASSUME-FILE-NAME is given uses the function
‘buffer-file-name’."
(interactive)
(clang-format--with-delete-files-guard tmp-files
(let ((tmpfile-vc-head nil)
(tmpfile-curbuf nil))
(setq tmpfile-vc-head
(make-temp-file "clang-format-vc-tmp-head-content"))
(push tmpfile-vc-head tmp-files)
(clang-format--vc-diff-get-vc-head-file tmpfile-vc-head)
;; Move the current buffer to a temporary file to take a
;; diff. Even if current-buffer is backed by a file, we
;; want to diff the buffer contents which might not be
;; saved.
(setq tmpfile-curbuf (make-temp-file "clang-format-vc-tmp"))
(push tmpfile-curbuf tmp-files)
(write-region nil nil tmpfile-curbuf nil 'nomessage)
;; Get a list of lines with a diff.
(let ((diff-lines
(clang-format--vc-diff-get-diff-lines
tmpfile-vc-head tmpfile-curbuf)))
;; If we have any diffs, format them.
(when diff-lines
(clang-format--region-impl
(point-min)
(point-max)
style
assume-file-name
diff-lines))))))


;;;###autoload
(defun clang-format-region (start end &optional style assume-file-name)
"Use clang-format to format the code between START and END according
to STYLE. If called interactively uses the region or the current
statement if there is no no active region. If no STYLE is given uses
`clang-format-style'. Use ASSUME-FILE-NAME to locate a style config
file, if no ASSUME-FILE-NAME is given uses the function
`buffer-file-name'."
(interactive
(if (use-region-p)
(list (region-beginning) (region-end))
(list (point) (point))))
(clang-format--region-impl start end style assume-file-name))

;;;###autoload
(defun clang-format-buffer (&optional style assume-file-name)
"Use clang-format to format the current buffer according to STYLE.
If no STYLE is given uses `clang-format-style'. Use ASSUME-FILE-NAME
to locate a style config file. If no ASSUME-FILE-NAME is given uses
the function `buffer-file-name'."
(interactive)
(clang-format-region (point-min) (point-max) style assume-file-name))
(clang-format--region-impl
(point-min)
(point-max)
style
assume-file-name))

;;;###autoload
(defalias 'clang-format 'clang-format-region)
Expand Down