1;;; clang-format.el --- Format code using clang-format
2
3;; Keywords: tools, c
4;; Package-Requires: ((cl-lib "0.3"))
5
6;;; Commentary:
7
8;; This package allows to filter code through clang-format to fix its formatting.
9;; clang-format is a tool that formats C/C++/Obj-C code according to a set of
10;; style options, see <http://clang.llvm.org/docs/ClangFormatStyleOptions.html>.
11;; Note that clang-format 3.4 or newer is required.
12
13;; clang-format.el is available via MELPA and can be installed via
14;;
15;;   M-x package-install clang-format
16;;
17;; when ("melpa" . "http://melpa.org/packages/") is included in
18;; `package-archives'. Alternatively, ensure the directory of this
19;; file is in your `load-path' and add
20;;
21;;   (require 'clang-format)
22;;
23;; to your .emacs configuration.
24
25;; You may also want to bind `clang-format-region' to a key:
26;;
27;;   (global-set-key [C-M-tab] 'clang-format-region)
28
29;;; Code:
30
31(require 'cl-lib)
32(require 'xml)
33
34(defgroup clang-format nil
35  "Format code using clang-format."
36  :group 'tools)
37
38(defcustom clang-format-executable
39  (or (executable-find "clang-format")
40      "clang-format")
41  "Location of the clang-format executable.
42
43A string containing the name or the full path of the executable."
44  :group 'clang-format
45  :type 'string
46  :risky t)
47
48(defcustom clang-format-style "file"
49  "Style argument to pass to clang-format.
50
51By default clang-format will load the style configuration from
52a file named .clang-format located in one of the parent directories
53of the buffer."
54  :group 'clang-format
55  :type 'string
56  :safe #'stringp)
57(make-variable-buffer-local 'clang-format-style)
58
59(defun clang-format--extract (xml-node)
60  "Extract replacements and cursor information from XML-NODE."
61  (unless (and (listp xml-node) (eq (xml-node-name xml-node) 'replacements))
62    (error "Expected <replacements> node"))
63  (let ((nodes (xml-node-children xml-node))
64        replacements
65        cursor)
66    (dolist (node nodes)
67      (when (listp node)
68        (let* ((children (xml-node-children node))
69               (text (car children)))
70          (cl-case (xml-node-name node)
71            ('replacement
72             (let* ((offset (xml-get-attribute-or-nil node 'offset))
73                    (length (xml-get-attribute-or-nil node 'length)))
74               (when (or (null offset) (null length))
75                 (error "<replacement> node does not have offset and length attributes"))
76               (when (cdr children)
77                 (error "More than one child node in <replacement> node"))
78
79               (setq offset (string-to-number offset))
80               (setq length (string-to-number length))
81               (push (list offset length text) replacements)))
82            ('cursor
83             (setq cursor (string-to-number text)))))))
84
85    ;; Sort by decreasing offset, length.
86    (setq replacements (sort (delq nil replacements)
87                             (lambda (a b)
88                               (or (> (car a) (car b))
89                                   (and (= (car a) (car b))
90                                        (> (cadr a) (cadr b)))))))
91
92    (cons replacements cursor)))
93
94(defun clang-format--replace (offset length &optional text)
95  (let ((start (byte-to-position (1+ offset)))
96        (end (byte-to-position (+ 1 offset length))))
97    (goto-char start)
98    (delete-region start end)
99    (when text
100      (insert text))))
101
102;;;###autoload
103(defun clang-format-region (char-start char-end &optional style)
104  "Use clang-format to format the code between START and END according to STYLE.
105If called interactively uses the region or the current statement if there
106is no active region.  If no style is given uses `clang-format-style'."
107  (interactive
108   (if (use-region-p)
109       (list (region-beginning) (region-end))
110     (list (point) (point))))
111
112  (unless style
113    (setq style clang-format-style))
114
115  (let ((start (1- (position-bytes char-start)))
116        (end (1- (position-bytes char-end)))
117        (cursor (1- (position-bytes (point))))
118        (temp-buffer (generate-new-buffer " *clang-format-temp*"))
119        (temp-file (make-temp-file "clang-format")))
120    (unwind-protect
121        (let (status stderr operations)
122          (setq status
123                (call-process-region
124                 (point-min) (point-max) clang-format-executable
125                 nil `(,temp-buffer ,temp-file) nil
126
127                 "-output-replacements-xml"
128                 "-assume-filename" (or (buffer-file-name) "")
129                 "-style" style
130                 "-offset" (number-to-string start)
131                 "-length" (number-to-string (- end start))
132                 "-cursor" (number-to-string cursor)))
133          (setq stderr
134                (with-temp-buffer
135                  (insert-file-contents temp-file)
136                  (when (> (point-max) (point-min))
137                    (insert ": "))
138                  (buffer-substring-no-properties
139                   (point-min) (line-end-position))))
140
141          (cond
142           ((stringp status)
143            (error "(clang-format killed by signal %s%s)" status stderr))
144           ((not (equal 0 status))
145            (error "(clang-format failed with code %d%s)" status stderr))
146           (t (message "(clang-format succeeded%s)" stderr)))
147
148          (with-current-buffer temp-buffer
149            (setq operations (clang-format--extract (car (xml-parse-region)))))
150
151          (let ((replacements (car operations))
152                (cursor (cdr operations)))
153            (save-excursion
154              (mapc (lambda (rpl)
155                      (apply #'clang-format--replace rpl))
156                    replacements))
157            (when cursor
158              (goto-char (byte-to-position (1+ cursor))))))
159      (delete-file temp-file)
160      (when (buffer-name temp-buffer) (kill-buffer temp-buffer)))))
161
162;;;###autoload
163(defun clang-format-buffer (&optional style)
164  "Use clang-format to format the current buffer according to STYLE."
165  (interactive)
166  (clang-format-region (point-min) (point-max) style))
167
168;;;###autoload
169(defalias 'clang-format 'clang-format-region)
170
171(provide 'clang-format)
172;;; clang-format.el ends here
173