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        (incomplete-format (xml-get-attribute xml-node 'incomplete_format))
65        replacements
66        cursor)
67    (dolist (node nodes)
68      (when (listp node)
69        (let* ((children (xml-node-children node))
70               (text (car children)))
71          (cl-case (xml-node-name node)
72            ('replacement
73             (let* ((offset (xml-get-attribute-or-nil node 'offset))
74                    (length (xml-get-attribute-or-nil node 'length)))
75               (when (or (null offset) (null length))
76                 (error "<replacement> node does not have offset and length attributes"))
77               (when (cdr children)
78                 (error "More than one child node in <replacement> node"))
79
80               (setq offset (string-to-number offset))
81               (setq length (string-to-number length))
82               (push (list offset length text) replacements)))
83            ('cursor
84             (setq cursor (string-to-number text)))))))
85
86    ;; Sort by decreasing offset, length.
87    (setq replacements (sort (delq nil replacements)
88                             (lambda (a b)
89                               (or (> (car a) (car b))
90                                   (and (= (car a) (car b))
91                                        (> (cadr a) (cadr b)))))))
92
93    (list replacements cursor (string= incomplete-format "true"))))
94
95(defun clang-format--replace (offset length &optional text)
96  (let ((start (byte-to-position (1+ offset)))
97        (end (byte-to-position (+ 1 offset length))))
98    (goto-char start)
99    (delete-region start end)
100    (when text
101      (insert text))))
102
103;;;###autoload
104(defun clang-format-region (char-start char-end &optional style)
105  "Use clang-format to format the code between START and END according to STYLE.
106If called interactively uses the region or the current statement if there
107is no active region.  If no style is given uses `clang-format-style'."
108  (interactive
109   (if (use-region-p)
110       (list (region-beginning) (region-end))
111     (list (point) (point))))
112
113  (unless style
114    (setq style clang-format-style))
115
116  (let ((start (1- (position-bytes char-start)))
117        (end (1- (position-bytes char-end)))
118        (cursor (1- (position-bytes (point))))
119        (temp-buffer (generate-new-buffer " *clang-format-temp*"))
120        (temp-file (make-temp-file "clang-format")))
121    (unwind-protect
122        (let (status stderr operations)
123          (setq status
124                (call-process-region
125                 (point-min) (point-max) clang-format-executable
126                 nil `(,temp-buffer ,temp-file) nil
127
128                 "-output-replacements-xml"
129                 "-assume-filename" (or (buffer-file-name) "")
130                 "-style" style
131                 "-offset" (number-to-string start)
132                 "-length" (number-to-string (- end start))
133                 "-cursor" (number-to-string cursor)))
134          (setq stderr
135                (with-temp-buffer
136                  (insert-file-contents temp-file)
137                  (when (> (point-max) (point-min))
138                    (insert ": "))
139                  (buffer-substring-no-properties
140                   (point-min) (line-end-position))))
141
142          (cond
143           ((stringp status)
144            (error "(clang-format killed by signal %s%s)" status stderr))
145           ((not (equal 0 status))
146            (error "(clang-format failed with code %d%s)" status stderr)))
147
148          (with-current-buffer temp-buffer
149            (setq operations (clang-format--extract (car (xml-parse-region)))))
150
151          (let ((replacements (nth 0 operations))
152                (cursor (nth 1 operations))
153                (incomplete-format (nth 2 operations)))
154            (save-excursion
155              (mapc (lambda (rpl)
156                      (apply #'clang-format--replace rpl))
157                    replacements))
158            (when cursor
159              (goto-char (byte-to-position (1+ cursor))))
160            (message "%s" incomplete-format)
161            (if incomplete-format
162                (message "(clang-format: incomplete (syntax errors)%s)" stderr)
163              (message "(clang-format: success%s)" stderr))))
164      (delete-file temp-file)
165      (when (buffer-name temp-buffer) (kill-buffer temp-buffer)))))
166
167;;;###autoload
168(defun clang-format-buffer (&optional style)
169  "Use clang-format to format the current buffer according to STYLE."
170  (interactive)
171  (clang-format-region (point-min) (point-max) style))
172
173;;;###autoload
174(defalias 'clang-format 'clang-format-region)
175
176(provide 'clang-format)
177;;; clang-format.el ends here
178