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         (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.
    106 If called interactively uses the region or the current statement if there
    107 is 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