[clang] [emacs][clang-format] Add elisp API for clang-format on git diffs (PR #112792)
Campbell Barton via cfe-commits
cfe-commits at lists.llvm.org
Sat Jan 4 21:36:17 PST 2025
ideasman42 wrote:
Checking again and am still considering this patch too spesific/incomplete, checking vc's diff calls to git - they are considerably more involved than in this PR, meaning this PR will likely require follow up commits to fix problems _(see `vc-diff-internal`, inlined below for reference, it deals with EOL conversion, added files, coding systems... things this PR doesn't really handle)._
Attached a patch that allows for formatting line-ranges, the line range generation must be implemented externally.
- The `clang-format-modified-fn` customizable function is used to return a list of "modified" line ranges, this can be set by 3rd party packages - VC implementation independent.
- This function simply returns a list of integer pairs (line ranges).
- An error is raised if the function isn't set.
Patch files:
- Patch on the main branch
[pr-112792-update.diff.txt](https://github.com/user-attachments/files/18309256/pr-112792-update.diff.txt)
- The whole file (for convenience).
[clang-format.el.update.txt](https://github.com/user-attachments/files/18309260/clang-format.el.update.txt)
- The git/diff logic extracted into a separate file - which would not be applied to the LLVM project, just use for testing.
[clang-format-git-vc-diff.el.txt](https://github.com/user-attachments/files/18309263/clang-format-git-vc-diff.el.txt)
-----
As mentioned earlier, calling diff can be quite involved if all corner cases are properly handled.
```
(defun vc-diff-internal (async vc-fileset rev1 rev2 &optional verbose buffer)
"Report diffs between revisions REV1 and REV2 of a fileset in VC-FILESET.
ASYNC non-nil means run the backend's commands asynchronously if possible.
VC-FILESET should have the format described in `vc-deduce-fileset'.
Output goes to the buffer BUFFER, which defaults to *vc-diff*.
BUFFER, if non-nil, should be a buffer or a buffer name.
Return t if the buffer had changes, nil otherwise."
(unless buffer
(setq buffer "*vc-diff*"))
(let* ((files (cadr vc-fileset))
(messages (cons (format "Finding changes in %s..."
(vc-delistify files))
(format "No changes between %s and %s"
(or rev1 "working revision")
(or rev2 "workfile"))))
;; Set coding system based on the first file. It's a kluge,
;; but the only way to set it for each file included would
;; be to call the back end separately for each file.
(coding-system-for-read
;; Force the EOL conversion to be -unix, in case the files
;; to be compared have DOS EOLs. In that case, EOL
;; conversion will produce a patch file that will either
;; fail to apply, or will change the EOL format of some of
;; the lines in the patched file.
(coding-system-change-eol-conversion
(if files (vc-coding-system-for-diff (car files)) 'undecided)
'unix))
(orig-diff-buffer-clone
(if revert-buffer-in-progress-p
(clone-buffer
(generate-new-buffer-name " *vc-diff-clone*") nil))))
;; On MS-Windows and MS-DOS, Diff is likely to produce DOS-style
;; EOLs, which will look ugly if (car files) happens to have Unix
;; EOLs. But for Git, we must force Unix EOLs in the diffs, since
;; Git always produces Unix EOLs in the parts that didn't come
;; from the file, and wants to see any CR characters when applying
;; patches.
(if (and (memq system-type '(windows-nt ms-dos))
(not (eq (car vc-fileset) 'Git)))
(setq coding-system-for-read
(coding-system-change-eol-conversion coding-system-for-read
'dos)))
(vc-setup-buffer buffer)
(message "%s" (car messages))
;; Many backends don't handle well the case of a file that has been
;; added but not yet committed to the repo (notably CVS and Subversion).
;; Do that work here so the backends don't have to futz with it. --ESR
;;
;; Actually most backends (including CVS) have options to control the
;; behavior since which one is better depends on the user and on the
;; situation). Worse yet: this code does not handle the case where
;; `file' is a directory which contains added files.
;; I made it conditional on vc-diff-added-files but it should probably
;; just be removed (or copied/moved to specific backends). --Stef.
(when vc-diff-added-files
(let ((filtered '())
process-file-side-effects)
(dolist (file files)
(if (or (file-directory-p file)
(not (string= (vc-working-revision file) "0")))
(push file filtered)
;; This file is added but not yet committed;
;; there is no repository version to diff against.
(if (or rev1 rev2)
(error "No revisions of %s exist" file)
;; We regard this as "changed".
;; Diff it against /dev/null.
(apply #'vc-do-command buffer
(if async 'async 1) "diff" file
(append (vc-switches nil 'diff) `(,(null-device)))))))
(setq files (nreverse filtered))))
(vc-call-backend (car vc-fileset) 'diff files rev1 rev2 buffer async)
(set-buffer buffer)
;; Make the *vc-diff* buffer read only, the diff-mode key
;; bindings are nicer for read only buffers. pcl-cvs does the
;; same thing.
(setq buffer-read-only t)
(diff-mode)
(setq-local diff-vc-backend (car vc-fileset))
(setq-local diff-vc-revisions (list rev1 rev2))
(setq-local revert-buffer-function
(lambda (_ignore-auto _noconfirm)
(vc-diff-internal async vc-fileset rev1 rev2 verbose)))
(if (and (zerop (buffer-size))
(not (get-buffer-process (current-buffer))))
;; Treat this case specially so as not to pop the buffer.
(progn
(message "%s" (cdr messages))
nil)
;; Display the buffer, but at the end because it can change point.
(pop-to-buffer (current-buffer))
;; The diff process may finish early, so call `vc-diff-finish'
;; after `pop-to-buffer'; the former assumes the diff buffer is
;; shown in some window.
(let ((buf (current-buffer)))
(vc-run-delayed (vc-diff-finish buf (when verbose messages)
orig-diff-buffer-clone)))
;; In the async case, we return t even if there are no differences
;; because we don't know that yet.
t)))
```
https://github.com/llvm/llvm-project/pull/112792
More information about the cfe-commits
mailing list