;;; editorconfig.el --- EditorConfig Emacs extension ;; Copyright (C) 2011-2014 EditorConfig Team ;; Author: EditorConfig Team ;; Version: 0.4 ;; URL: http://github.com/editorconfig/editorconfig-emacs#readme ;; See ;; http://github.com/editorconfig/editorconfig-emacs/graphs/contributors ;; or the CONTRIBUTORS file for the list of contributors. ;; Redistribution and use in source and binary forms, with or without ;; modification, are permitted provided that the following conditions are met: ;; ;; 1. Redistributions of source code must retain the above copyright notice, ;; this list of conditions and the following disclaimer. ;; 2. Redistributions in binary form must reproduce the above copyright notice, ;; this list of conditions and the following disclaimer in the documentation ;; and/or other materials provided with the distribution. ;; THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" ;; AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE ;; IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ;; ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE ;; LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR ;; CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF ;; SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS ;; INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN ;; CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ;; ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE ;; POSSIBILITY OF SUCH DAMAGE. ;;; Commentary: ;; EditorConfig helps developers define and maintain consistent ;; coding styles between different editors and IDEs. ;; The EditorConfig project consists of a file format for defining ;; coding styles and a collection of text editor plugins that enable ;; editors to read the file format and adhere to defined styles. ;; EditorConfig files are easily readable and they work nicely with ;; version control systems. ;;; Code: (defcustom edconf-exec-path "editorconfig" "EditorConfig command" :type 'string :group 'editorconfig) (defcustom edconf-indentation-alist '((emacs-lisp-mode lisp-indent-offset) (lisp-mode lisp-indent-offset) (c-mode c-basic-offset) (c++-mode c-basic-offset) (objc-mode c-basic-offset) (java-mode c-basic-offset) (idl-mode c-basic-offset) (pike-mode c-basic-offset) (awk-mode c-basic-offset) (cmake-mode cmake-tab-width) (coffee-mode coffee-tab-width) (cperl-mode cperl-indent-level) (css-mode css-indent-offset) (haskell-mode haskell-indent-spaces haskell-indent-offset shm-indent-spaces) (js-mode js-indent-level) (json-mode js-indent-level) (js2-mode js2-basic-offset) (js3-mode js3-indent-level) (perl-mode perl-indent-level) (python-mode . edconf-set-indentation/python-mode) (ruby-mode ruby-indent-level) (sh-mode sh-basic-offset sh-indentation) (nxml-mode nxml-child-indent (nxml-attribute-indent . 2)) (sgml-mode sgml-basic-offset) (livescript-mode livescript-tab-width) (mustache-mode mustache-basic-offset) (scala-mode scala-indent:step) (groovy-mode c-basic-offset) (latex-mode . edconf-set-indentation/latex-mode) (web-mode (web-mode-indent-style . (lambda (size) 2)) web-mode-markup-indent-offset web-mode-css-indent-offset web-mode-code-indent-offset web-mode-script-padding web-mode-style-padding)) "Alist of indentation setting methods by modes. Each element looks like (MODE . FUNCTION) or (MODE . INDENT-SPEC-LIST). If FUNCTION is provided, it will be called when setting the indentation. The indent size will be passed. If INDENT-SPEC-LIST is provided, each element of it must have one of the following forms: 1. VARIABLE It means (VARIABLE . 1). 2. (VARIABLE . SPEC) Setting VARIABLE according to the type of SPEC: - Integer The value is (* SPEC INDENT-SIZE); - Function The value is (funcall SPEC INDENT-SIZE); - Any other type. The value is SPEC. NOTE: Only the **buffer local** value of VARIABLE will be set." :type '(alist :key-type symbol :value-type sexp) :risky t :group 'editorconfig) (defun edconf-string-integer-p (string) "Whether a string representing integer" (if (stringp string) (string-match-p "\\`[0-9]+\\'" string) nil)) (defun edconf-set-indentation/python-mode (size) (set (make-local-variable (if (or (> emacs-major-version 24) (and (= emacs-major-version 24) (>= emacs-minor-version 3))) 'python-indent-offset 'python-indent)) size) ;; For https://launchpad.net/python-mode (when (boundp 'py-indent-offset) (set (make-local-variable 'py-indent-offset) size))) (defun edconf-set-indentation/latex-mode (size) (set (make-local-variable 'tex-indent-basic) size) (set (make-local-variable 'tex-indent-item) size) (set (make-local-variable 'tex-indent-arg) (* 2 size)) ;; For AUCTeX (when (boundp 'TeX-brace-indent-level) (set (make-local-variable 'TeX-brace-indent-level) size)) (when (boundp 'LaTeX-indent-level) (set (make-local-variable 'LaTeX-indent-level) size)) (when (boundp 'LaTeX-item-indent) (set (make-local-variable 'LaTeX-item-indent) (- size)))) (defun edconf-set-indentation (style &optional size tab_width) "Set indentation type from given style and size" (make-local-variable 'indent-tabs-mode) (make-local-variable 'tab-width) (if (edconf-string-integer-p size) (setq size (string-to-number size)) (when (not (equal size "tab")) (setq size nil))) (setq tab-width (cond (tab_width (string-to-number tab_width)) ((numberp size) size) (t tab-width))) (when (equal size "tab") (setq size tab-width)) (cond ((equal style "space") (setq indent-tabs-mode nil)) ((equal style "tab") (setq indent-tabs-mode t))) (when size (let ((parent major-mode) entry) ;; Find the closet parent mode of `major-mode' in ;; `edconf-indentation-alist'. (while (and (not (setq entry (assoc parent edconf-indentation-alist))) (setq parent (get parent 'derived-mode-parent)))) (when entry (let ((fn-or-list (cdr entry))) (cond ((functionp fn-or-list) (funcall fn-or-list size)) ((listp fn-or-list) (dolist (elem fn-or-list) (cond ((symbolp elem) (set (make-local-variable elem) size)) ((consp elem) (let ((spec (cdr elem))) (set (make-local-variable (car elem)) (cond ((functionp spec) (funcall spec size)) ((integerp spec) (* spec size)) (t spec)))))))))))))) (defun edconf-set-line-ending (end-of-line) "Set line ending style to CR, LF, or CRLF" (set-buffer-file-coding-system (cond ((equal end-of-line "lf") 'undecided-unix) ((equal end-of-line "cr") 'undecided-mac) ((equal end-of-line "crlf") 'undecided-dos) (t 'undecided)) nil t)) (defun edconf-set-trailing-nl (final-newline) (cond ((equal final-newline "true") ;; keep prefs around how/when the nl is added, if set - otherwise add on save (set (make-local-variable 'require-final-newline) (or require-final-newline t)) (set (make-local-variable 'mode-require-final-newline) (or mode-require-final-newline t))) ((equal final-newline "false") ;; FIXME: Add functionality for actually REMOVING any trailing newlines here! ;; (rather than just making sure we don't automagically ADD a new one) (set (make-local-variable 'require-final-newline) nil) (set (make-local-variable 'mode-require-final-newline) nil)))) (defun edconf-set-trailing-ws (trim-trailing-ws) "set up trimming of trailing whitespace at end of lines" (make-local-variable 'write-file-functions) ;; just current buffer (when (equal trim-trailing-ws "true") ;; when true we push delete-trailing-whitespace (emacs > 21) ;; to write-file-functions (add-to-list 'write-file-functions 'delete-trailing-whitespace)) (when (equal trim-trailing-ws "false") ;; when false we remove every delete-trailing-whitespace ;; from write-file-functions (setq write-file-functions (delete 'delete-trailing-whitespace write-file-functions)))) (defun edconf-set-line-length (length) "set the max line length (fill-column)" (when (edconf-string-integer-p length) (set-fill-column (string-to-number length)))) (defun edconf-get-properties () "Call EditorConfig core and return output" (let ((oldbuf (current-buffer))) (call-process edconf-exec-path nil "ecbuffer" nil (buffer-file-name oldbuf)) (set-buffer (get-buffer "ecbuffer")) (let (props-string) (setq props-string (buffer-string)) (set-buffer oldbuf) (kill-buffer (get-buffer "ecbuffer")) props-string))) (defun edconf-parse-properties (props-string) "Create properties hash table from string of properties" (let (props-list properties) (setq props-list (split-string props-string "\n") properties (make-hash-table :test 'equal)) (dolist (prop props-list properties) (let ((key-val (split-string prop " *= *"))) (when (> (length key-val) 1) (let ((key (intern (car key-val))) (val (mapconcat 'identity (cdr key-val) ""))) (puthash key val properties))))))) ;;;###autoload (defun edconf-find-file-hook () (when (executable-find edconf-exec-path) (let ((props (edconf-parse-properties (edconf-get-properties)))) (edconf-set-indentation (gethash 'indent_style props) (gethash 'indent_size props) (gethash 'tab_width props)) (edconf-set-line-ending (gethash 'end_of_line props)) (edconf-set-trailing-nl (gethash 'insert_final_newline props)) (edconf-set-trailing-ws (gethash 'trim_trailing_whitespace props)) (edconf-set-line-length (gethash 'max_line_length props))))) ;;;###autoload (add-hook 'find-file-hook 'edconf-find-file-hook) (provide 'editorconfig) ;;; editorconfig.el ends here