[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