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