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 43 A 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 51 By default clang-format will load the style configuration from 52 a file named .clang-format located in one of the parent directories 53 of 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. 105 If called interactively uses the region or the current statement if there 106 is 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