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