1e5dd7070Spatrick;;; clang-format.el --- Format code using clang-format  -*- lexical-binding: t; -*-
2e5dd7070Spatrick
3e5dd7070Spatrick;; Keywords: tools, c
4e5dd7070Spatrick;; Package-Requires: ((cl-lib "0.3"))
5e5dd7070Spatrick;; SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6e5dd7070Spatrick
7e5dd7070Spatrick;;; Commentary:
8e5dd7070Spatrick
9e5dd7070Spatrick;; This package allows to filter code through clang-format to fix its formatting.
10e5dd7070Spatrick;; clang-format is a tool that formats C/C++/Obj-C code according to a set of
11e5dd7070Spatrick;; style options, see <http://clang.llvm.org/docs/ClangFormatStyleOptions.html>.
12e5dd7070Spatrick;; Note that clang-format 3.4 or newer is required.
13e5dd7070Spatrick
14e5dd7070Spatrick;; clang-format.el is available via MELPA and can be installed via
15e5dd7070Spatrick;;
16e5dd7070Spatrick;;   M-x package-install clang-format
17e5dd7070Spatrick;;
18e5dd7070Spatrick;; when ("melpa" . "http://melpa.org/packages/") is included in
19e5dd7070Spatrick;; `package-archives'.  Alternatively, ensure the directory of this
20e5dd7070Spatrick;; file is in your `load-path' and add
21e5dd7070Spatrick;;
22e5dd7070Spatrick;;   (require 'clang-format)
23e5dd7070Spatrick;;
24e5dd7070Spatrick;; to your .emacs configuration.
25e5dd7070Spatrick
26e5dd7070Spatrick;; You may also want to bind `clang-format-region' to a key:
27e5dd7070Spatrick;;
28e5dd7070Spatrick;;   (global-set-key [C-M-tab] 'clang-format-region)
29e5dd7070Spatrick
30e5dd7070Spatrick;;; Code:
31e5dd7070Spatrick
32e5dd7070Spatrick(require 'cl-lib)
33e5dd7070Spatrick(require 'xml)
34e5dd7070Spatrick
35e5dd7070Spatrick(defgroup clang-format nil
36e5dd7070Spatrick  "Format code using clang-format."
37e5dd7070Spatrick  :group 'tools)
38e5dd7070Spatrick
39e5dd7070Spatrick(defcustom clang-format-executable
40e5dd7070Spatrick  (or (executable-find "clang-format")
41e5dd7070Spatrick      "clang-format")
42e5dd7070Spatrick  "Location of the clang-format executable.
43e5dd7070Spatrick
44e5dd7070SpatrickA string containing the name or the full path of the executable."
45e5dd7070Spatrick  :group 'clang-format
46e5dd7070Spatrick  :type '(file :must-match t)
47e5dd7070Spatrick  :risky t)
48e5dd7070Spatrick
49e5dd7070Spatrick(defcustom clang-format-style nil
50e5dd7070Spatrick  "Style argument to pass to clang-format.
51e5dd7070Spatrick
52e5dd7070SpatrickBy default clang-format will load the style configuration from
53e5dd7070Spatricka file named .clang-format located in one of the parent directories
54e5dd7070Spatrickof the buffer."
55e5dd7070Spatrick  :group 'clang-format
56e5dd7070Spatrick  :type '(choice (string) (const nil))
57e5dd7070Spatrick  :safe #'stringp)
58e5dd7070Spatrick(make-variable-buffer-local 'clang-format-style)
59e5dd7070Spatrick
60e5dd7070Spatrick(defcustom clang-format-fallback-style "none"
61e5dd7070Spatrick  "Fallback style to pass to clang-format.
62e5dd7070Spatrick
63e5dd7070SpatrickThis style will be used if clang-format-style is set to \"file\"
64e5dd7070Spatrickand no .clang-format is found in the directory of the buffer or
65e5dd7070Spatrickone of parent directories. Set to \"none\" to disable formatting
66e5dd7070Spatrickin such buffers."
67e5dd7070Spatrick  :group 'clang-format
68e5dd7070Spatrick  :type 'string
69e5dd7070Spatrick  :safe #'stringp)
70e5dd7070Spatrick(make-variable-buffer-local 'clang-format-fallback-style)
71e5dd7070Spatrick
72e5dd7070Spatrick(defun clang-format--extract (xml-node)
73e5dd7070Spatrick  "Extract replacements and cursor information from XML-NODE."
74e5dd7070Spatrick  (unless (and (listp xml-node) (eq (xml-node-name xml-node) 'replacements))
75e5dd7070Spatrick    (error "Expected <replacements> node"))
76e5dd7070Spatrick  (let ((nodes (xml-node-children xml-node))
77e5dd7070Spatrick        (incomplete-format (xml-get-attribute xml-node 'incomplete_format))
78e5dd7070Spatrick        replacements
79e5dd7070Spatrick        cursor)
80e5dd7070Spatrick    (dolist (node nodes)
81e5dd7070Spatrick      (when (listp node)
82e5dd7070Spatrick        (let* ((children (xml-node-children node))
83e5dd7070Spatrick               (text (car children)))
84e5dd7070Spatrick          (cl-case (xml-node-name node)
85e5dd7070Spatrick            ('replacement
86e5dd7070Spatrick             (let* ((offset (xml-get-attribute-or-nil node 'offset))
87e5dd7070Spatrick                    (length (xml-get-attribute-or-nil node 'length)))
88e5dd7070Spatrick               (when (or (null offset) (null length))
89e5dd7070Spatrick                 (error "<replacement> node does not have offset and length attributes"))
90e5dd7070Spatrick               (when (cdr children)
91e5dd7070Spatrick                 (error "More than one child node in <replacement> node"))
92e5dd7070Spatrick
93e5dd7070Spatrick               (setq offset (string-to-number offset))
94e5dd7070Spatrick               (setq length (string-to-number length))
95e5dd7070Spatrick               (push (list offset length text) replacements)))
96e5dd7070Spatrick            ('cursor
97e5dd7070Spatrick             (setq cursor (string-to-number text)))))))
98e5dd7070Spatrick
99e5dd7070Spatrick    ;; Sort by decreasing offset, length.
100e5dd7070Spatrick    (setq replacements (sort (delq nil replacements)
101e5dd7070Spatrick                             (lambda (a b)
102e5dd7070Spatrick                               (or (> (car a) (car b))
103e5dd7070Spatrick                                   (and (= (car a) (car b))
104e5dd7070Spatrick                                        (> (cadr a) (cadr b)))))))
105e5dd7070Spatrick
106e5dd7070Spatrick    (list replacements cursor (string= incomplete-format "true"))))
107e5dd7070Spatrick
108e5dd7070Spatrick(defun clang-format--replace (offset length &optional text)
109e5dd7070Spatrick  "Replace the region defined by OFFSET and LENGTH with TEXT.
110e5dd7070SpatrickOFFSET and LENGTH are measured in bytes, not characters.  OFFSET
111e5dd7070Spatrickis a zero-based file offset, assuming ‘utf-8-unix’ coding."
112e5dd7070Spatrick  (let ((start (clang-format--filepos-to-bufferpos offset 'exact 'utf-8-unix))
113e5dd7070Spatrick        (end (clang-format--filepos-to-bufferpos (+ offset length) 'exact
114e5dd7070Spatrick                                                 'utf-8-unix)))
115e5dd7070Spatrick    (goto-char start)
116e5dd7070Spatrick    (delete-region start end)
117e5dd7070Spatrick    (when text
118e5dd7070Spatrick      (insert text))))
119e5dd7070Spatrick
120e5dd7070Spatrick;; ‘bufferpos-to-filepos’ and ‘filepos-to-bufferpos’ are new in Emacs 25.1.
121e5dd7070Spatrick;; Provide fallbacks for older versions.
122e5dd7070Spatrick(defalias 'clang-format--bufferpos-to-filepos
123e5dd7070Spatrick  (if (fboundp 'bufferpos-to-filepos)
124e5dd7070Spatrick      'bufferpos-to-filepos
125e5dd7070Spatrick    (lambda (position &optional _quality _coding-system)
126e5dd7070Spatrick      (1- (position-bytes position)))))
127e5dd7070Spatrick
128e5dd7070Spatrick(defalias 'clang-format--filepos-to-bufferpos
129e5dd7070Spatrick  (if (fboundp 'filepos-to-bufferpos)
130e5dd7070Spatrick      'filepos-to-bufferpos
131e5dd7070Spatrick    (lambda (byte &optional _quality _coding-system)
132e5dd7070Spatrick      (byte-to-position (1+ byte)))))
133e5dd7070Spatrick
134e5dd7070Spatrick;;;###autoload
135e5dd7070Spatrick(defun clang-format-region (start end &optional style assume-file-name)
136e5dd7070Spatrick  "Use clang-format to format the code between START and END according to STYLE.
137e5dd7070SpatrickIf called interactively uses the region or the current statement if there is no
138e5dd7070Spatrickno active region. If no STYLE is given uses `clang-format-style'. Use
139e5dd7070SpatrickASSUME-FILE-NAME to locate a style config file, if no ASSUME-FILE-NAME is given
140e5dd7070Spatrickuses the function `buffer-file-name'."
141e5dd7070Spatrick  (interactive
142e5dd7070Spatrick   (if (use-region-p)
143e5dd7070Spatrick       (list (region-beginning) (region-end))
144e5dd7070Spatrick     (list (point) (point))))
145e5dd7070Spatrick
146e5dd7070Spatrick  (unless style
147e5dd7070Spatrick    (setq style clang-format-style))
148e5dd7070Spatrick
149e5dd7070Spatrick  (unless assume-file-name
150*12c85518Srobert    (setq assume-file-name (buffer-file-name (buffer-base-buffer))))
151e5dd7070Spatrick
152e5dd7070Spatrick  (let ((file-start (clang-format--bufferpos-to-filepos start 'approximate
153e5dd7070Spatrick                                                        'utf-8-unix))
154e5dd7070Spatrick        (file-end (clang-format--bufferpos-to-filepos end 'approximate
155e5dd7070Spatrick                                                      'utf-8-unix))
156e5dd7070Spatrick        (cursor (clang-format--bufferpos-to-filepos (point) 'exact 'utf-8-unix))
157e5dd7070Spatrick        (temp-buffer (generate-new-buffer " *clang-format-temp*"))
158e5dd7070Spatrick        (temp-file (make-temp-file "clang-format"))
159e5dd7070Spatrick        ;; Output is XML, which is always UTF-8.  Input encoding should match
160e5dd7070Spatrick        ;; the encoding used to convert between buffer and file positions,
161e5dd7070Spatrick        ;; otherwise the offsets calculated above are off.  For simplicity, we
162e5dd7070Spatrick        ;; always use ‘utf-8-unix’ and ignore the buffer coding system.
163e5dd7070Spatrick        (default-process-coding-system '(utf-8-unix . utf-8-unix)))
164e5dd7070Spatrick    (unwind-protect
165e5dd7070Spatrick        (let ((status (apply #'call-process-region
166e5dd7070Spatrick                             nil nil clang-format-executable
167e5dd7070Spatrick                             nil `(,temp-buffer ,temp-file) nil
168e5dd7070Spatrick                             `("-output-replacements-xml"
169e5dd7070Spatrick                               ;; Guard against a nil assume-file-name.
170e5dd7070Spatrick                               ;; If the clang-format option -assume-filename
171e5dd7070Spatrick                               ;; is given a blank string it will crash as per
172e5dd7070Spatrick                               ;; the following bug report
173e5dd7070Spatrick                               ;; https://bugs.llvm.org/show_bug.cgi?id=34667
174e5dd7070Spatrick                               ,@(and assume-file-name
175e5dd7070Spatrick                                      (list "-assume-filename" assume-file-name))
176e5dd7070Spatrick                               ,@(and style (list "-style" style))
177e5dd7070Spatrick                               "-fallback-style" ,clang-format-fallback-style
178e5dd7070Spatrick                               "-offset" ,(number-to-string file-start)
179e5dd7070Spatrick                               "-length" ,(number-to-string (- file-end file-start))
180e5dd7070Spatrick                               "-cursor" ,(number-to-string cursor))))
181e5dd7070Spatrick              (stderr (with-temp-buffer
182e5dd7070Spatrick                        (unless (zerop (cadr (insert-file-contents temp-file)))
183e5dd7070Spatrick                          (insert ": "))
184e5dd7070Spatrick                        (buffer-substring-no-properties
185e5dd7070Spatrick                         (point-min) (line-end-position)))))
186e5dd7070Spatrick          (cond
187e5dd7070Spatrick           ((stringp status)
188e5dd7070Spatrick            (error "(clang-format killed by signal %s%s)" status stderr))
189e5dd7070Spatrick           ((not (zerop status))
190e5dd7070Spatrick            (error "(clang-format failed with code %d%s)" status stderr)))
191e5dd7070Spatrick
192e5dd7070Spatrick          (cl-destructuring-bind (replacements cursor incomplete-format)
193e5dd7070Spatrick              (with-current-buffer temp-buffer
194e5dd7070Spatrick                (clang-format--extract (car (xml-parse-region))))
195e5dd7070Spatrick            (save-excursion
196e5dd7070Spatrick              (dolist (rpl replacements)
197e5dd7070Spatrick                (apply #'clang-format--replace rpl)))
198e5dd7070Spatrick            (when cursor
199e5dd7070Spatrick              (goto-char (clang-format--filepos-to-bufferpos cursor 'exact
200e5dd7070Spatrick                                                             'utf-8-unix)))
201e5dd7070Spatrick            (if incomplete-format
202e5dd7070Spatrick                (message "(clang-format: incomplete (syntax errors)%s)" stderr)
203e5dd7070Spatrick              (message "(clang-format: success%s)" stderr))))
204e5dd7070Spatrick      (delete-file temp-file)
205e5dd7070Spatrick      (when (buffer-name temp-buffer) (kill-buffer temp-buffer)))))
206e5dd7070Spatrick
207e5dd7070Spatrick;;;###autoload
208e5dd7070Spatrick(defun clang-format-buffer (&optional style assume-file-name)
209e5dd7070Spatrick  "Use clang-format to format the current buffer according to STYLE.
210e5dd7070SpatrickIf no STYLE is given uses `clang-format-style'. Use ASSUME-FILE-NAME
211e5dd7070Spatrickto locate a style config file. If no ASSUME-FILE-NAME is given uses
212e5dd7070Spatrickthe function `buffer-file-name'."
213e5dd7070Spatrick  (interactive)
214e5dd7070Spatrick  (clang-format-region (point-min) (point-max) style assume-file-name))
215e5dd7070Spatrick
216e5dd7070Spatrick;;;###autoload
217e5dd7070Spatrick(defalias 'clang-format 'clang-format-region)
218e5dd7070Spatrick
219e5dd7070Spatrick(provide 'clang-format)
220e5dd7070Spatrick;;; clang-format.el ends here
221