-
Notifications
You must be signed in to change notification settings - Fork 15.2k
[emacs][clang-format] Add elisp API for clang-format on git diffs #112792
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 4 commits
802764e
2c8395d
fc05d68
36aa80b
fcf504f
b42785d
95db21c
98d5894
1e65249
7cdfa4e
6d49dcf
7742bd1
9ae26d4
e547d0e
b277560
8c8c551
92247c1
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -146,18 +146,118 @@ 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)))) | ||
| (defun clang-format--vc-diff-match-diff-line (line) | ||
| ;; We are matching something like: | ||
| ;; "@@ -80 +80 @@" or "@@ -80,2 +80,2 @@" | ||
| ;; Return as "<LineStart>:<LineEnd>" | ||
| (when (string-match "^@@\s-[0-9,]+\s\\+\\([0-9]+\\)\\(,\\([0-9]+\\)\\)?\s@@$" line) | ||
| ;; If we have multi-line diff | ||
| (if (match-string 3 line) | ||
| (concat (match-string 1 line) | ||
| ":" | ||
| (number-to-string | ||
| (+ (string-to-number (match-string 1 line)) | ||
| (string-to-number (match-string 3 line))))) | ||
| (concat (match-string 1 line) ":" (match-string 1 line))))) | ||
|
|
||
| (defun clang-format--vc-diff-get-diff-lines (file-orig file-new) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 (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 | ||
goldsteinn marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| 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 | ||
| (let ((status (call-process | ||
goldsteinn marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| "diff" | ||
goldsteinn marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| 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) | ||
| ;; Iterate through all lines in diff buffer and collect all | ||
| ;; lines in current buffer that have a diff. | ||
| (goto-char (point-min)) | ||
| (while (not (eobp)) | ||
|
||
| (let ((diff-line (clang-format--vc-diff-match-diff-line | ||
| (buffer-substring-no-properties | ||
| (line-beginning-position) | ||
| (line-end-position))))) | ||
| (when diff-line | ||
| ;; Create list line regions with diffs to pass to | ||
| ;; clang-format. | ||
| (push (concat "--lines=" diff-line) diff-lines))) | ||
| (forward-line 1)) | ||
| (reverse diff-lines)) | ||
goldsteinn marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| ;; Any return != 0 && != 1 indicates some level of error. | ||
| (t | ||
| (error "(diff returned unsuccessfully %s%s)" status stderr)))))) | ||
|
||
|
|
||
| (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 | ||
| "git" | ||
goldsteinn marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| nil | ||
| `(:file, tmpfile-vc-head) | ||
goldsteinn marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| 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)) | ||
|
|
||
|
|
@@ -190,8 +290,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))) | ||
|
|
@@ -219,14 +323,72 @@ uses the function `buffer-file-name'." | |
| (delete-file temp-file) | ||
| (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) | ||
| (let ((tmpfile-vc-head nil) | ||
| (tmpfile-curbuf nil)) | ||
| (unwind-protect | ||
|
||
| (progn | ||
| (setq tmpfile-vc-head | ||
| (make-temp-file "clang-format-vc-tmp-head-content")) | ||
| (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")) | ||
|
||
| (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)))) | ||
| (progn | ||
| ;; Cleanup temporary files we created. | ||
| (when tmpfile-vc-head (delete-file tmpfile-vc-head)) | ||
goldsteinn marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| (when tmpfile-curbuf (delete-file tmpfile-curbuf)))))) | ||
|
|
||
|
|
||
| ;;;###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) | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.