diff options
author | David Robillard <d@drobilla.net> | 2022-02-28 16:30:18 -0500 |
---|---|---|
committer | David Robillard <d@drobilla.net> | 2022-02-28 16:30:18 -0500 |
commit | bb3aafc481f3e950bca2b664f825de2412e0904c (patch) | |
tree | 7d31b2758bdab0e0a36ac40c21b8f900c4e425fb | |
parent | 319af180428e549d385e199ed845ffca4bc45da8 (diff) | |
download | dotfiles-bb3aafc481f3e950bca2b664f825de2412e0904c.tar.gz dotfiles-bb3aafc481f3e950bca2b664f825de2412e0904c.tar.bz2 dotfiles-bb3aafc481f3e950bca2b664f825de2412e0904c.zip |
Add clang-format package
Unfortunately this does not seem to be available in stable MELPA.
-rw-r--r-- | emacs/.emacs.d/init.el | 2 | ||||
-rw-r--r-- | emacs/.emacs.d/site-lisp/clang-format.el | 222 |
2 files changed, 224 insertions, 0 deletions
diff --git a/emacs/.emacs.d/init.el b/emacs/.emacs.d/init.el index c644804..b12b187 100644 --- a/emacs/.emacs.d/init.el +++ b/emacs/.emacs.d/init.el @@ -27,6 +27,8 @@ ;;; Code formatting +(require 'clang-format) + (defun get-clang-format-option (config-str field is-num) "Retrieve a config option from a clang-format config." (cond (is-num diff --git a/emacs/.emacs.d/site-lisp/clang-format.el b/emacs/.emacs.d/site-lisp/clang-format.el new file mode 100644 index 0000000..4abafcb --- /dev/null +++ b/emacs/.emacs.d/site-lisp/clang-format.el @@ -0,0 +1,222 @@ +;;; clang-format.el --- Format code using clang-format -*- lexical-binding: t; -*- + +;; Keywords: tools, c +;; Package-Version: 20191106.950 +;; Package-Commit: e48ff8ae18dc7ab6118c1f6752deb48cb1fc83ac +;; Package-Requires: ((cl-lib "0.3")) +;; SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +;;; Commentary: + +;; This package allows to filter code through clang-format to fix its formatting. +;; clang-format is a tool that formats C/C++/Obj-C code according to a set of +;; style options, see <http://clang.llvm.org/docs/ClangFormatStyleOptions.html>. +;; Note that clang-format 3.4 or newer is required. + +;; clang-format.el is available via MELPA and can be installed via +;; +;; M-x package-install clang-format +;; +;; when ("melpa" . "http://melpa.org/packages/") is included in +;; `package-archives'. Alternatively, ensure the directory of this +;; file is in your `load-path' and add +;; +;; (require 'clang-format) +;; +;; to your .emacs configuration. + +;; You may also want to bind `clang-format-region' to a key: +;; +;; (global-set-key [C-M-tab] 'clang-format-region) + +;;; Code: + +(require 'cl-lib) +(require 'xml) + +(defgroup clang-format nil + "Format code using clang-format." + :group 'tools) + +(defcustom clang-format-executable + (or (executable-find "clang-format") + "clang-format") + "Location of the clang-format executable. + +A string containing the name or the full path of the executable." + :group 'clang-format + :type '(file :must-match t) + :risky t) + +(defcustom clang-format-style nil + "Style argument to pass to clang-format. + +By default clang-format will load the style configuration from +a file named .clang-format located in one of the parent directories +of the buffer." + :group 'clang-format + :type '(choice (string) (const nil)) + :safe #'stringp) +(make-variable-buffer-local 'clang-format-style) + +(defcustom clang-format-fallback-style "none" + "Fallback style to pass to clang-format. + +This style will be used if clang-format-style is set to \"file\" +and no .clang-format is found in the directory of the buffer or +one of parent directories. Set to \"none\" to disable formatting +in such buffers." + :group 'clang-format + :type 'string + :safe #'stringp) +(make-variable-buffer-local 'clang-format-fallback-style) + +(defun clang-format--extract (xml-node) + "Extract replacements and cursor information from XML-NODE." + (unless (and (listp xml-node) (eq (xml-node-name xml-node) 'replacements)) + (error "Expected <replacements> node")) + (let ((nodes (xml-node-children xml-node)) + (incomplete-format (xml-get-attribute xml-node 'incomplete_format)) + replacements + cursor) + (dolist (node nodes) + (when (listp node) + (let* ((children (xml-node-children node)) + (text (car children))) + (cl-case (xml-node-name node) + ('replacement + (let* ((offset (xml-get-attribute-or-nil node 'offset)) + (length (xml-get-attribute-or-nil node 'length))) + (when (or (null offset) (null length)) + (error "<replacement> node does not have offset and length attributes")) + (when (cdr children) + (error "More than one child node in <replacement> node")) + + (setq offset (string-to-number offset)) + (setq length (string-to-number length)) + (push (list offset length text) replacements))) + ('cursor + (setq cursor (string-to-number text))))))) + + ;; Sort by decreasing offset, length. + (setq replacements (sort (delq nil replacements) + (lambda (a b) + (or (> (car a) (car b)) + (and (= (car a) (car b)) + (> (cadr a) (cadr b))))))) + + (list replacements cursor (string= incomplete-format "true")))) + +(defun clang-format--replace (offset length &optional text) + "Replace the region defined by OFFSET and LENGTH with TEXT. +OFFSET and LENGTH are measured in bytes, not characters. OFFSET +is a zero-based file offset, assuming ‘utf-8-unix’ coding." + (let ((start (clang-format--filepos-to-bufferpos offset 'exact 'utf-8-unix)) + (end (clang-format--filepos-to-bufferpos (+ offset length) 'exact + 'utf-8-unix))) + (goto-char start) + (delete-region start end) + (when text + (insert text)))) + +;; ‘bufferpos-to-filepos’ and ‘filepos-to-bufferpos’ are new in Emacs 25.1. +;; Provide fallbacks for older versions. +(defalias 'clang-format--bufferpos-to-filepos + (if (fboundp 'bufferpos-to-filepos) + 'bufferpos-to-filepos + (lambda (position &optional _quality _coding-system) + (1- (position-bytes position))))) + +(defalias 'clang-format--filepos-to-bufferpos + (if (fboundp 'filepos-to-bufferpos) + 'filepos-to-bufferpos + (lambda (byte &optional _quality _coding-system) + (byte-to-position (1+ byte))))) + +;;;###autoload +(defun clang-format-region (start end &optional style assume-file-name) + "Use clang-format to format the code between START and END according to STYLE. +If called interactively uses the region or the current statement if there is no +no active region. If no STYLE is given uses `clang-format-style'. Use +ASSUME-FILE-NAME to locate a style config file, if no ASSUME-FILE-NAME is given +uses the function `buffer-file-name'." + (interactive + (if (use-region-p) + (list (region-beginning) (region-end)) + (list (point) (point)))) + + (unless style + (setq style clang-format-style)) + + (unless assume-file-name + (setq assume-file-name buffer-file-name)) + + (let ((file-start (clang-format--bufferpos-to-filepos start 'approximate + 'utf-8-unix)) + (file-end (clang-format--bufferpos-to-filepos end 'approximate + 'utf-8-unix)) + (cursor (clang-format--bufferpos-to-filepos (point) 'exact 'utf-8-unix)) + (temp-buffer (generate-new-buffer " *clang-format-temp*")) + (temp-file (make-temp-file "clang-format")) + ;; Output is XML, which is always UTF-8. Input encoding should match + ;; the encoding used to convert between buffer and file positions, + ;; otherwise the offsets calculated above are off. For simplicity, we + ;; always use ‘utf-8-unix’ and ignore the buffer coding system. + (default-process-coding-system '(utf-8-unix . utf-8-unix))) + (unwind-protect + (let ((status (apply #'call-process-region + nil nil clang-format-executable + nil `(,temp-buffer ,temp-file) nil + `("-output-replacements-xml" + ;; Guard against a nil assume-file-name. + ;; If the clang-format option -assume-filename + ;; is given a blank string it will crash as per + ;; the following bug report + ;; https://bugs.llvm.org/show_bug.cgi?id=34667 + ,@(and assume-file-name + (list "-assume-filename" assume-file-name)) + ,@(and style (list "-style" style)) + "-fallback-style" ,clang-format-fallback-style + "-offset" ,(number-to-string file-start) + "-length" ,(number-to-string (- file-end file-start)) + "-cursor" ,(number-to-string cursor)))) + (stderr (with-temp-buffer + (unless (zerop (cadr (insert-file-contents temp-file))) + (insert ": ")) + (buffer-substring-no-properties + (point-min) (line-end-position))))) + (cond + ((stringp status) + (error "(clang-format killed by signal %s%s)" status stderr)) + ((not (zerop status)) + (error "(clang-format failed with code %d%s)" status stderr))) + + (cl-destructuring-bind (replacements cursor incomplete-format) + (with-current-buffer temp-buffer + (clang-format--extract (car (xml-parse-region)))) + (save-excursion + (dolist (rpl replacements) + (apply #'clang-format--replace rpl))) + (when cursor + (goto-char (clang-format--filepos-to-bufferpos cursor 'exact + 'utf-8-unix))) + (if incomplete-format + (message "(clang-format: incomplete (syntax errors)%s)" stderr) + (message "(clang-format: success%s)" stderr)))) + (delete-file temp-file) + (when (buffer-name temp-buffer) (kill-buffer temp-buffer))))) + +;;;###autoload +(defun clang-format-buffer (&optional style assume-file-name) + "Use clang-format to format the current buffer according to STYLE. +If no STYLE is given uses `clang-format-style'. Use ASSUME-FILE-NAME +to locate a style config file. If no ASSUME-FILE-NAME is given uses +the function `buffer-file-name'." + (interactive) + (clang-format-region (point-min) (point-max) style assume-file-name)) + +;;;###autoload +(defalias 'clang-format 'clang-format-region) + +(provide 'clang-format) +;;; clang-format.el ends here |