Home | History | Annotate | Download | only in clang-format
      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