dotemacs

My Emacs configuration
git clone git://git.entf.net/dotemacs
Log | Files | Refs | LICENSE

editorconfig.el (41599B)


      1 ;;; editorconfig.el --- EditorConfig Emacs Plugin  -*- lexical-binding: t -*-
      2 
      3 ;; Copyright (C) 2011-2021 EditorConfig Team
      4 
      5 ;; Author: EditorConfig Team <editorconfig@googlegroups.com>
      6 ;; Version: 0.8.2
      7 ;; URL: https://github.com/editorconfig/editorconfig-emacs#readme
      8 ;; Package-Requires: ((cl-lib "0.5") (nadvice "0.3") (emacs "24"))
      9 
     10 ;; See
     11 ;; https://github.com/editorconfig/editorconfig-emacs/graphs/contributors
     12 ;; or the CONTRIBUTORS file for the list of contributors.
     13 
     14 ;; This file is part of EditorConfig Emacs Plugin.
     15 
     16 ;; EditorConfig Emacs Plugin is free software: you can redistribute it and/or
     17 ;; modify it under the terms of the GNU General Public License as published by
     18 ;; the Free Software Foundation, either version 3 of the License, or (at your
     19 ;; option) any later version.
     20 
     21 ;; EditorConfig Emacs Plugin is distributed in the hope that it will be useful,
     22 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
     23 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General
     24 ;; Public License for more details.
     25 
     26 ;; You should have received a copy of the GNU General Public License along with
     27 ;; EditorConfig Emacs Plugin. If not, see <https://www.gnu.org/licenses/>.
     28 
     29 ;;; Commentary:
     30 
     31 ;; EditorConfig helps developers define and maintain consistent
     32 ;; coding styles between different editors and IDEs.
     33 
     34 ;; The EditorConfig project consists of a file format for defining
     35 ;; coding styles and a collection of text editor plugins that enable
     36 ;; editors to read the file format and adhere to defined styles.
     37 ;; EditorConfig files are easily readable and they work nicely with
     38 ;; version control systems.
     39 
     40 ;;; Code:
     41 (require 'cl-lib)
     42 (require 'nadvice)
     43 (eval-when-compile
     44   (require 'rx)
     45   (defvar tex-indent-basic)
     46   (defvar tex-indent-item)
     47   (defvar tex-indent-arg)
     48   (defvar evil-shift-width))
     49 
     50 (declare-function editorconfig-core-get-properties-hash
     51                   "editorconfig-core"
     52                   (&optional file confname confversion))
     53 
     54 (defgroup editorconfig nil
     55   "EditorConfig Emacs Plugin.
     56 
     57 EditorConfig helps developers define and maintain consistent
     58 coding styles between different editors and IDEs."
     59   :tag "EditorConfig"
     60   :prefix "editorconfig-"
     61   :group 'tools)
     62 
     63 (define-obsolete-variable-alias
     64   'edconf-exec-path
     65   'editorconfig-exec-path
     66   "0.5")
     67 (defcustom editorconfig-exec-path
     68   "editorconfig"
     69   "Path to EditorConfig executable.
     70 
     71 Used by `editorconfig--execute-editorconfig-exec'."
     72   :type 'string
     73   :group 'editorconfig)
     74 
     75 (define-obsolete-variable-alias
     76   'edconf-get-properties-function
     77   'editorconfig-get-properties-function
     78   "0.5")
     79 (defcustom editorconfig-get-properties-function
     80   'editorconfig-core-get-properties-hash
     81   "A function which gets EditorConfig properties for specified file.
     82 
     83 This function will be called with one argument, full path of the target file,
     84 and should return a hash object containing properties, or nil if any core
     85 program is not available.  Keys of this hash should be symbols of properties,
     86 and values should be strings of their values.
     87 
     88 
     89 For example, if you always want to use built-in core library instead
     90 of any EditorConfig executable to get properties, add following to
     91 your init.el:
     92 
     93   (set-variable 'editorconfig-get-properties-function
     94                 #'editorconfig-core-get-properties-hash)
     95 
     96 Possible known values are:
     97 
     98 * `editorconfig-core-get-properties-hash' (default)
     99   * Always use built-in Emacs-Lisp implementation to get properties
    100 * `editorconfig-get-properties'
    101   * Use `editorconfig-get-properties-from-exec' when
    102     `editorconfig-exec-path' executable is found, otherwise
    103     use `editorconfig-core-get-properties-hash'
    104 * `editorconfig-get-properties-from-exec'
    105   * Get properties by executing EditorConfig executable"
    106   :type 'function
    107   :group 'editorconfig)
    108 
    109 (defcustom editorconfig-mode-lighter " EditorConfig"
    110   "Command `editorconfig-mode' lighter string."
    111   :type 'string
    112   :group 'editorconfig)
    113 
    114 (define-obsolete-variable-alias
    115   'edconf-custom-hooks
    116   'editorconfig-after-apply-functions
    117   "0.5")
    118 (define-obsolete-variable-alias
    119   'editorconfig-custom-hooks
    120   'editorconfig-after-apply-functions
    121   "0.7.14")
    122 (defcustom editorconfig-after-apply-functions ()
    123   "A list of functions after loading common EditorConfig settings.
    124 
    125 Each element in this list is a hook function.  This hook function
    126 takes one parameter, which is a property hash table.  The value
    127 of properties can be obtained through gethash function.
    128 
    129 The hook does not have to be coding style related; you can add
    130 whatever functionality you want.  For example, the following is
    131 an example to add a new property emacs_linum to decide whether to
    132 show line numbers on the left:
    133 
    134   (add-hook 'editorconfig-after-apply-functions
    135     '(lambda (props)
    136        (let ((show-line-num (gethash 'emacs_linum props)))
    137          (cond ((equal show-line-num \"true\") (linum-mode 1))
    138            ((equal show-line-num \"false\") (linum-mode 0))))))
    139 
    140 This hook will be run even when there are no matching sections in
    141 \".editorconfig\", or no \".editorconfig\" file was found at all."
    142   :type 'hook
    143   :group 'editorconfig)
    144 
    145 (defcustom editorconfig-hack-properties-functions ()
    146   "A list of function to alter property values before applying them.
    147 
    148 These functions will be run after loading \".editorconfig\" files and before
    149 applying them to current buffer, so that you can alter some properties from
    150 \".editorconfig\" before they take effect.
    151 
    152 For example, Makefiles always use tab characters for indentation: you can
    153 overwrite \"indent_style\" property when current `major-mode' is a
    154 `makefile-mode' with following code:
    155 
    156   (add-hook 'editorconfig-hack-properties-functions
    157             '(lambda (props)
    158                (when (derived-mode-p 'makefile-mode)
    159                  (puthash 'indent_style \"tab\" props))))
    160 
    161 This hook will be run even when there are no matching sections in
    162 \".editorconfig\", or no \".editorconfig\" file was found at all."
    163   :type 'hook
    164   :group 'editorconfig)
    165 
    166 (define-obsolete-variable-alias
    167   'edconf-indentation-alist
    168   'editorconfig-indentation-alist
    169   "0.5")
    170 (defcustom editorconfig-indentation-alist
    171   ;; For contributors: Sort modes in alphabetical order
    172   '((apache-mode apache-indent-level)
    173     (awk-mode c-basic-offset)
    174     (bpftrace-mode c-basic-offset)
    175     (c++-mode c-basic-offset)
    176     (c-mode c-basic-offset)
    177     (cmake-mode cmake-tab-width)
    178     (coffee-mode coffee-tab-width)
    179     (cperl-mode cperl-indent-level)
    180     (crystal-mode crystal-indent-level)
    181     (csharp-mode c-basic-offset)
    182     (css-mode css-indent-offset)
    183     (d-mode c-basic-offset)
    184     (emacs-lisp-mode lisp-indent-offset)
    185     (enh-ruby-mode enh-ruby-indent-level)
    186     (erlang-mode erlang-indent-level)
    187     (ess-mode ess-indent-offset)
    188     (f90-mode f90-associate-indent
    189               f90-continuation-indent
    190               f90-critical-indent
    191               f90-do-indent
    192               f90-if-indent
    193               f90-program-indent
    194               f90-type-indent)
    195     (feature-mode feature-indent-offset
    196                   feature-indent-level)
    197     (fsharp-mode fsharp-continuation-offset
    198                  fsharp-indent-level
    199                  fsharp-indent-offset)
    200     (groovy-mode groovy-indent-offset)
    201     (haskell-mode haskell-indent-spaces
    202                   haskell-indent-offset
    203                   haskell-indentation-layout-offset
    204                   haskell-indentation-left-offset
    205                   haskell-indentation-starter-offset
    206                   haskell-indentation-where-post-offset
    207                   haskell-indentation-where-pre-offset
    208                   shm-indent-spaces)
    209     (haxor-mode haxor-tab-width)
    210     (idl-mode c-basic-offset)
    211     (jade-mode jade-tab-width)
    212     (java-mode c-basic-offset)
    213     (js-mode js-indent-level)
    214     (js-jsx-mode js-indent-level sgml-basic-offset)
    215     (js2-mode js2-basic-offset)
    216     (js2-jsx-mode js2-basic-offset sgml-basic-offset)
    217     (js3-mode js3-indent-level)
    218     (json-mode js-indent-level)
    219     (julia-mode julia-indent-offset)
    220     (kotlin-mode kotlin-tab-width)
    221     (latex-mode . editorconfig-set-indentation-latex-mode)
    222     (lisp-mode lisp-indent-offset)
    223     (livescript-mode livescript-tab-width)
    224     (lua-mode lua-indent-level)
    225     (matlab-mode matlab-indent-level)
    226     (meson-mode meson-indent-basic)
    227     (mips-mode mips-tab-width)
    228     (mustache-mode mustache-basic-offset)
    229     (nasm-mode nasm-basic-offset)
    230     (nginx-mode nginx-indent-level)
    231     (nxml-mode nxml-child-indent (nxml-attribute-indent . 2))
    232     (objc-mode c-basic-offset)
    233     (octave-mode octave-block-offset)
    234     (perl-mode perl-indent-level)
    235     ;; No need to change `php-mode-coding-style' value for php-mode
    236     ;; since we run editorconfig later than it resets `c-basic-offset'.
    237     ;; See https://github.com/editorconfig/editorconfig-emacs/issues/116
    238     ;; for details.
    239     (php-mode c-basic-offset)
    240     (pike-mode c-basic-offset)
    241     (ps-mode ps-mode-tab)
    242     (pug-mode pug-tab-width)
    243     (puppet-mode puppet-indent-level)
    244     (python-mode . editorconfig-set-indentation-python-mode)
    245     (rjsx-mode js-indent-level sgml-basic-offset)
    246     (ruby-mode ruby-indent-level)
    247     (rust-mode rust-indent-offset)
    248     (rustic-mode rustic-indent-offset)
    249     (scala-mode scala-indent:step)
    250     (scss-mode css-indent-offset)
    251     (sgml-mode sgml-basic-offset)
    252     (sh-mode sh-basic-offset sh-indentation)
    253     (slim-mode slim-indent-offset)
    254     (sml-mode sml-indent-level)
    255     (tcl-mode tcl-indent-level
    256               tcl-continued-indent-level)
    257     (terra-mode terra-indent-level)
    258     (typescript-mode typescript-indent-level)
    259     (verilog-mode verilog-indent-level
    260                   verilog-indent-level-behavioral
    261                   verilog-indent-level-declaration
    262                   verilog-indent-level-module
    263                   verilog-cexp-indent
    264                   verilog-case-indent)
    265     (web-mode (web-mode-indent-style . (lambda (size) 2))
    266               web-mode-attr-indent-offset
    267               web-mode-attr-value-indent-offset
    268               web-mode-code-indent-offset
    269               web-mode-css-indent-offset
    270               web-mode-markup-indent-offset
    271               web-mode-sql-indent-offset
    272               web-mode-block-padding
    273               web-mode-script-padding
    274               web-mode-style-padding)
    275     (yaml-mode yaml-indent-offset))
    276   "Alist of indentation setting methods by modes.
    277 
    278 Each element looks like (MODE . FUNCTION) or (MODE . INDENT-SPEC-LIST).
    279 
    280 If FUNCTION is provided, it will be called when setting the
    281 indentation.  The indent size will be passed.
    282 
    283 If INDENT-SPEC-LIST is provided, each element of it must have one of the
    284 following forms:
    285 
    286  1. VARIABLE
    287     It means (VARIABLE . 1).
    288 
    289  2. (VARIABLE . SPEC)
    290     Setting VARIABLE according to the type of SPEC:
    291 
    292       - Integer
    293         The value is (* SPEC INDENT-SIZE);
    294 
    295       - Function
    296         The value is (funcall SPEC INDENT-SIZE);
    297 
    298       - Any other type.
    299         The value is SPEC.
    300 
    301 NOTE: Only the **buffer local** value of VARIABLE will be set."
    302   :type '(alist :key-type symbol :value-type sexp)
    303   :risky t
    304   :group 'editorconfig)
    305 
    306 (defcustom editorconfig-exclude-modes ()
    307   "Modes in which `editorconfig-mode-apply' will not run."
    308   :type '(repeat (symbol :tag "Major Mode"))
    309   :group 'editorconfig)
    310 
    311 (defcustom editorconfig-exclude-regexps ()
    312   "List of regexp for buffer filenames `editorconfig-mode-apply' will not run.
    313 
    314 When variable `buffer-file-name' matches any of the regexps, then
    315 `editorconfig-mode-apply' will not do its work."
    316   :type '(repeat string)
    317   :group 'editorconfig)
    318 (with-eval-after-load 'recentf
    319   (add-to-list 'editorconfig-exclude-regexps
    320                (rx-to-string '(seq string-start
    321                                    (eval (file-truename (expand-file-name recentf-save-file))))
    322                              t)))
    323 
    324 (defcustom editorconfig-trim-whitespaces-mode nil
    325   "Buffer local minor-mode to use to trim trailing whitespaces.
    326 
    327 If set, enable that mode when `trim_trailing_whitespace` is set to true.
    328 Otherwise, use `delete-trailing-whitespace'."
    329   :type 'symbol
    330   :group 'editorconfig)
    331 
    332 (defvar editorconfig-properties-hash nil
    333   "Hash object of EditorConfig properties that was enabled for current buffer.
    334 Set by `editorconfig-apply' and nil if that is not invoked in
    335 current buffer yet.")
    336 (make-variable-buffer-local 'editorconfig-properties-hash)
    337 (put 'editorconfig-properties-hash
    338      'permanent-local
    339      t)
    340 
    341 (defvar editorconfig-lisp-use-default-indent nil
    342   "Selectively ignore the value of indent_size for Lisp files.
    343 Prevents `lisp-indent-offset' from being set selectively.
    344 
    345 nil - `lisp-indent-offset' is always set normally.
    346 t   - `lisp-indent-offset' is never set normally
    347        (always use default indent for lisps).
    348 number - `lisp-indent-offset' is not set only if indent_size is
    349          equal to this number.  For example, if this is set to 2,
    350          `lisp-indent-offset' will not be set only if indent_size is 2.")
    351 
    352 (define-error 'editorconfig-error
    353   "Error thrown from editorconfig lib")
    354 
    355 (defun editorconfig-error (&rest args)
    356   "Signal an `editorconfig-error'.
    357 Make a message by passing ARGS to `format-message'."
    358   (signal 'editorconfig-error (list (apply #'format-message args))))
    359 
    360 (defun editorconfig--disabled-for-filename (filename)
    361   "Return non-nil when EditorConfig is disabled for FILENAME."
    362   (cl-assert (stringp filename))
    363   (cl-loop for regexp in editorconfig-exclude-regexps
    364            if (string-match regexp filename) return t
    365            finally return nil))
    366 
    367 (defun editorconfig--disabled-for-majormode (majormode)
    368   "Return non-nil when Editorconfig is disabled for MAJORMODE."
    369   (cl-assert majormode)
    370   (or (editorconfig--provided-mode-derived-p majormode 'special-mode)
    371       (memq majormode
    372             editorconfig-exclude-modes)))
    373 
    374 (defun editorconfig-string-integer-p (string)
    375   "Return non-nil if STRING represents integer."
    376   (and (stringp string)
    377        (string-match-p "\\`[0-9]+\\'" string)))
    378 
    379 (defun editorconfig-set-indentation-python-mode (size)
    380   "Set `python-mode' indent size to SIZE."
    381   (set (make-local-variable (if (or (> emacs-major-version 24)
    382                                     (and (= emacs-major-version 24)
    383                                          (>= emacs-minor-version 3)))
    384                                 'python-indent-offset
    385                               'python-indent))
    386        size)
    387   ;; For https://launchpad.net/python-mode
    388   (when (boundp 'py-indent-offset)
    389     (set (make-local-variable 'py-indent-offset) size)))
    390 
    391 (defun editorconfig-set-indentation-latex-mode (size)
    392   "Set `latex-mode' indent size to SIZE."
    393   (set (make-local-variable 'tex-indent-basic) size)
    394   (set (make-local-variable 'tex-indent-item) size)
    395   (set (make-local-variable 'tex-indent-arg) (* 2 size))
    396   ;; For AUCTeX
    397   (when (boundp 'TeX-brace-indent-level)
    398     (set (make-local-variable 'TeX-brace-indent-level) size))
    399   (when (boundp 'LaTeX-indent-level)
    400     (set (make-local-variable 'LaTeX-indent-level) size))
    401   (when (boundp 'LaTeX-item-indent)
    402     (set (make-local-variable 'LaTeX-item-indent) (- size))))
    403 
    404 (defun editorconfig--should-set (size symbol)
    405   "Determines if editorconfig should set SYMBOL using SIZE."
    406   (if (eq symbol 'lisp-indent-offset)
    407       (cond
    408        ((eql nil editorconfig-lisp-use-default-indent)
    409         t)
    410        ((eql t editorconfig-lisp-use-default-indent)
    411         nil)
    412        ((numberp editorconfig-lisp-use-default-indent)
    413         (not (eql size editorconfig-lisp-use-default-indent)))
    414        (t t))
    415     t))
    416 
    417 (defun editorconfig-set-indentation (style &optional size tab_width)
    418   "Set indentation type from STYLE, SIZE and TAB_WIDTH."
    419   (if (editorconfig-string-integer-p size)
    420       (setq size (string-to-number size))
    421     (when (not (equal size "tab")) (setq size nil)))
    422   (cond (tab_width
    423          (setq tab-width (string-to-number tab_width)))
    424         ((numberp size)
    425          (setq tab-width size)))
    426   (when (equal size "tab")
    427     (setq size tab-width))
    428   (cond ((equal style "space")
    429          (setq indent-tabs-mode nil))
    430         ((equal style "tab")
    431          (setq indent-tabs-mode t)))
    432   (when size
    433     (when (featurep 'evil)
    434       (set (make-local-variable 'evil-shift-width) size))
    435     (let ((parent major-mode)
    436           entry)
    437       ;; Find the closet parent mode of `major-mode' in
    438       ;; `editorconfig-indentation-alist'.
    439       (while (and (not (setq entry (assoc parent editorconfig-indentation-alist)))
    440                   (setq parent (get parent 'derived-mode-parent))))
    441       (when entry
    442         (let ((fn-or-list (cdr entry)))
    443           (cond ((functionp fn-or-list) (funcall fn-or-list size))
    444                 ((listp fn-or-list)
    445                  (dolist (elem fn-or-list)
    446                    (cond ((and (symbolp elem)
    447                                (editorconfig--should-set size elem))
    448                           (set (make-local-variable elem) size))
    449                          ((and (consp elem)
    450                                (editorconfig--should-set size (car elem)))
    451                           (let ((spec (cdr elem)))
    452                             (set (make-local-variable (car elem))
    453                                  (cond ((functionp spec) (funcall spec size))
    454                                        ((integerp spec) (* spec size))
    455                                        (t spec))))))))))))))
    456 
    457 (defvar editorconfig--apply-coding-system-currently nil
    458   "Used internally.")
    459 (make-variable-buffer-local 'editorconfig--apply-coding-system-currently)
    460 (put 'editorconfig--apply-coding-system-currently
    461      'permanent-local
    462      t)
    463 
    464 (defun editorconfig-merge-coding-systems (end-of-line charset)
    465   "Return merged coding system symbol of END-OF-LINE and CHARSET."
    466   (let ((eol (cond
    467               ((equal end-of-line "lf") 'undecided-unix)
    468               ((equal end-of-line "cr") 'undecided-mac)
    469               ((equal end-of-line "crlf") 'undecided-dos)
    470               (t 'undecided)))
    471         (cs (cond
    472              ((equal charset "latin1") 'iso-latin-1)
    473              ((equal charset "utf-8") 'utf-8)
    474              ((equal charset "utf-8-bom") 'utf-8-with-signature)
    475              ((equal charset "utf-16be") 'utf-16be-with-signature)
    476              ((equal charset "utf-16le") 'utf-16le-with-signature)
    477              (t 'undecided))))
    478     (merge-coding-systems cs eol)))
    479 
    480 (cl-defun editorconfig-set-coding-system-revert (end-of-line charset)
    481   "Set buffer coding system by END-OF-LINE and CHARSET.
    482 
    483 This function will revert buffer when the coding-system has been changed."
    484   ;; `editorconfig--advice-find-file-noselect' does not use this function
    485   (let ((coding-system (editorconfig-merge-coding-systems end-of-line
    486                                                           charset)))
    487     (display-warning '(editorconfig editorconfig-set-coding-system-revert)
    488                      (format "editorconfig-set-coding-system-revert: buffer-file-name: %S | buffer-file-coding-system: %S | coding-system: %S | apply-currently: %S"
    489                              buffer-file-name
    490                              buffer-file-coding-system
    491                              coding-system
    492                              editorconfig--apply-coding-system-currently)
    493                      :debug)
    494     (when (eq coding-system 'undecided)
    495       (cl-return-from editorconfig-set-coding-system-revert))
    496     (when (and buffer-file-coding-system
    497                (memq buffer-file-coding-system
    498                      (coding-system-aliases (merge-coding-systems coding-system
    499                                                                   buffer-file-coding-system))))
    500       (cl-return-from editorconfig-set-coding-system-revert))
    501     (unless (file-readable-p buffer-file-name)
    502       (set-buffer-file-coding-system coding-system)
    503       (cl-return-from editorconfig-set-coding-system-revert))
    504     (unless (memq coding-system
    505                   (coding-system-aliases editorconfig--apply-coding-system-currently))
    506       ;; Revert functions might call editorconfig-apply again
    507       (unwind-protect
    508           (progn
    509             (setq editorconfig--apply-coding-system-currently
    510                   coding-system)
    511             ;; Revert without query if buffer is not modified
    512             (let ((revert-without-query '(".")))
    513               (revert-buffer-with-coding-system coding-system)))
    514         (setq editorconfig--apply-coding-system-currently
    515               nil)))))
    516 
    517 (defun editorconfig-set-trailing-nl (final-newline)
    518   "Set up requiring final newline by FINAL-NEWLINE.
    519 
    520 This function will set `require-final-newline' and `mode-require-final-newline'
    521 to non-nil when FINAL-NEWLINE is true."
    522   (cond
    523    ((equal final-newline "true")
    524     ;; keep prefs around how/when the nl is added, if set - otherwise add on save
    525     (set      (make-local-variable 'require-final-newline)      (or require-final-newline t))
    526     (set      (make-local-variable 'mode-require-final-newline) (or mode-require-final-newline t)))
    527    ((equal final-newline "false")
    528     ;; FIXME: Add functionality for actually REMOVING any trailing newlines here!
    529     ;;        (rather than just making sure we don't automagically ADD a new one)
    530     (set      (make-local-variable 'require-final-newline) nil)
    531     (set      (make-local-variable 'mode-require-final-newline) nil))))
    532 
    533 (defun editorconfig-set-trailing-ws (trim-trailing-ws)
    534   "Set up trimming of trailing whitespace at end of lines by TRIM-TRAILING-WS."
    535   (make-local-variable 'write-file-functions) ;; just current buffer
    536   (when (and (equal trim-trailing-ws "true")
    537              (not buffer-read-only))
    538     ;; when true we push delete-trailing-whitespace (emacs > 21)
    539     ;; to write-file-functions
    540     (if editorconfig-trim-whitespaces-mode
    541         (funcall editorconfig-trim-whitespaces-mode 1)
    542       (add-to-list
    543        'write-file-functions
    544        'delete-trailing-whitespace)))
    545   (when (or (equal trim-trailing-ws "false")
    546             buffer-read-only)
    547     ;; when false we remove every delete-trailing-whitespace
    548     ;; from write-file-functions
    549     (when editorconfig-trim-whitespaces-mode
    550       (funcall editorconfig-trim-whitespaces-mode 0))
    551     (setq
    552      write-file-functions
    553      (delete
    554       'delete-trailing-whitespace
    555       write-file-functions))))
    556 
    557 (defun editorconfig-set-line-length (length)
    558   "Set the max line length (`fill-column') to LENGTH."
    559   (when (and (editorconfig-string-integer-p length)
    560              (> (string-to-number length) 0))
    561     (setq fill-column (string-to-number length))))
    562 
    563 ;; Emacs<26 does not have provided-mode-derived-p
    564 (defun editorconfig--provided-mode-derived-p (mode &rest modes)
    565   "Non-nil if MODE is derived from one of MODES.
    566 Uses the `derived-mode-parent' property of the symbol to trace backwards.
    567 If you just want to check `major-mode', use `derived-mode-p'."
    568   (if (fboundp 'provided-mode-derived-p)
    569       (apply 'provided-mode-derived-p mode modes)
    570     (while (and (not (memq mode modes))
    571                 (setq mode (get mode 'derived-mode-parent))))
    572     mode))
    573 
    574 
    575 (defun editorconfig--execute-editorconfig-exec (filename)
    576   "Execute EditorConfig core with FILENAME and return output."
    577   (if filename
    578       (with-temp-buffer
    579         (let ((remote (file-remote-p filename))
    580               (remote-localname (file-remote-p filename
    581                                                'localname)))
    582           (display-warning '(editorconfig editorconfig--execute-editorconfig-exec)
    583                            (format "editorconfig--execute-editorconfig-exec: filename: %S | remote: %S | remote-localname: %S"
    584                                    filename
    585                                    remote
    586                                    remote-localname)
    587                            :debug)
    588           (if remote
    589               (progn
    590                 (cd (concat remote "/"))
    591                 (setq filename remote-localname))
    592             (cd "/")))
    593         (display-warning '(editorconfig editorconfig--execute-editorconfig-exec)
    594                          (format "editorconfig--execute-editorconfig-exec: default-directory: %S | filename: %S"
    595                                  default-directory
    596                                  filename
    597                                  )
    598                          :debug)
    599         (if (eq 0
    600                 (process-file editorconfig-exec-path nil t nil filename))
    601             (buffer-string)
    602           (editorconfig-error (buffer-string))))
    603     ""))
    604 
    605 (defun editorconfig--parse-properties (props-string)
    606   "Create properties hash table from PROPS-STRING."
    607   (let (props-list properties)
    608     (setq props-list (split-string props-string "\n")
    609           properties (make-hash-table))
    610     (dolist (prop props-list properties)
    611       (let ((key-val (split-string prop " *= *")))
    612         (when (> (length key-val) 1)
    613           (let ((key (intern (car key-val)))
    614                 (val (mapconcat 'identity (cdr key-val) "")))
    615             (puthash key val properties)))))))
    616 
    617 (defun editorconfig-get-properties-from-exec (filename)
    618   "Get EditorConfig properties of file FILENAME.
    619 
    620 This function uses value of `editorconfig-exec-path' to get properties."
    621   (if (executable-find editorconfig-exec-path)
    622       (editorconfig--parse-properties (editorconfig--execute-editorconfig-exec filename))
    623     (editorconfig-error "Unable to find editorconfig executable")))
    624 
    625 (defun editorconfig-get-properties (filename)
    626   "Get EditorConfig properties for file FILENAME.
    627 
    628 It calls `editorconfig-get-properties-from-exec' if
    629 `editorconfig-exec-path' is found, otherwise
    630 `editorconfig-core-get-properties-hash'."
    631   (if (and (executable-find editorconfig-exec-path)
    632            (not (file-remote-p buffer-file-name)))
    633       (editorconfig-get-properties-from-exec filename)
    634     (require 'editorconfig-core)
    635     (editorconfig-core-get-properties-hash filename)))
    636 
    637 (defun editorconfig-call-get-properties-function (filename)
    638   "Call `editorconfig-get-properties-function' with FILENAME and return result.
    639 
    640 This function also removes 'unset'ted properties and calls
    641 `editorconfig-hack-properties-functions'."
    642   (unless (functionp editorconfig-get-properties-function)
    643     (editorconfig-error "Invalid editorconfig-get-properties-function value"))
    644   (if (stringp filename)
    645       (setq filename (expand-file-name filename))
    646     (editorconfig-error "Invalid argument: %S" filename))
    647   (let ((props nil))
    648     (condition-case err
    649         (setq props (funcall editorconfig-get-properties-function
    650                              filename))
    651       (error
    652        (editorconfig-error "Error from editorconfig-get-properties-function: %S"
    653                            err)))
    654     (cl-loop for k being the hash-keys of props using (hash-values v)
    655              when (equal v "unset") do (remhash k props))
    656     props))
    657 
    658 (defun editorconfig-set-local-variables (props)
    659   "Set buffer variables according to EditorConfig PROPS."
    660   (editorconfig-set-indentation (gethash 'indent_style props)
    661                                 (gethash 'indent_size props)
    662                                 (gethash 'tab_width props))
    663   (editorconfig-set-trailing-nl (gethash 'insert_final_newline props))
    664   (editorconfig-set-trailing-ws (gethash 'trim_trailing_whitespace props))
    665   (editorconfig-set-line-length (gethash 'max_line_length props)))
    666 
    667 ;;;###autoload
    668 (defun editorconfig-apply ()
    669   "Get and apply EditorConfig properties to current buffer.
    670 
    671 This function does not respect the values of `editorconfig-exclude-modes' and
    672 `editorconfig-exclude-regexps' and always applies available properties.
    673 Use `editorconfig-mode-apply' instead to make use of these variables."
    674   (interactive)
    675   (when buffer-file-name
    676     (condition-case err
    677         (progn
    678           (let ((props (editorconfig-call-get-properties-function buffer-file-name)))
    679             (condition-case err
    680                 (run-hook-with-args 'editorconfig-hack-properties-functions props)
    681               (error
    682                (display-warning '(editorconfig editorconfig-hack-properties-functions)
    683                                 (format "Error while running editorconfig-hack-properties-functions, abort running hook: %S"
    684                                         err)
    685                                 :warning)))
    686             (setq editorconfig-properties-hash props)
    687             (editorconfig-set-local-variables props)
    688             (editorconfig-set-coding-system-revert
    689              (gethash 'end_of_line props)
    690              (gethash 'charset props))
    691             (condition-case err
    692                 (run-hook-with-args 'editorconfig-after-apply-functions props)
    693               (error
    694                (display-warning '(editorconfig editorconfig-after-apply-functions)
    695                                 (format "Error while running editorconfig-after-apply-functions, abort running hook: %S"
    696                                         err)
    697                                 :warning)))))
    698       (error
    699        (display-warning '(editorconfig editorconfig-apply)
    700                         (format "Error in editorconfig-apply, styles will not be applied: %S" err)
    701                         :error)))))
    702 
    703 (defun editorconfig-mode-apply ()
    704   "Get and apply EditorConfig properties to current buffer.
    705 
    706 This function does nothing when the major mode is listed in
    707 `editorconfig-exclude-modes', or variable `buffer-file-name' matches
    708 any of regexps in `editorconfig-exclude-regexps'."
    709   (interactive)
    710   (when (and major-mode
    711              (not (editorconfig--disabled-for-majormode major-mode))
    712              buffer-file-name
    713              (not (editorconfig--disabled-for-filename buffer-file-name)))
    714     (editorconfig-apply)))
    715 
    716 (defun editorconfig-major-mode-hook ()
    717   "Function to run when `major-mode' has been changed.
    718 
    719 This functions does not reload .editorconfig file, just sets local variables
    720 again.  Changing major mode can reset these variables.
    721 
    722 This function also executes `editorconfig-after-apply-functions' functions."
    723   (display-warning '(editorconfig editorconfig-major-mode-hook)
    724                    (format "editorconfig-major-mode-hook: editorconfig-mode: %S, major-mode: %S, -properties-hash: %S"
    725                            (and (boundp 'editorconfig-mode)
    726                                 editorconfig-mode)
    727                            major-mode
    728                            editorconfig-properties-hash)
    729                    :debug)
    730   (when (and (boundp 'editorconfig-mode)
    731              editorconfig-mode
    732              editorconfig-properties-hash)
    733     (editorconfig-set-local-variables editorconfig-properties-hash)
    734     (condition-case err
    735         (run-hook-with-args 'editorconfig-after-apply-functions editorconfig-properties-hash)
    736       (error
    737        (display-warning '(editorconfig editorconfig-major-mode-hook)
    738                         (format "Error while running `editorconfig-after-apply-functions': %S"
    739                                 err))))))
    740 
    741 (defvar editorconfig--cons-filename-codingsystem nil
    742   "Used interally.
    743 
    744 `editorconfig--advice-find-file-noselect' will set this variable, and
    745 `editorconfig--advice-insert-file-contents' will use this variable to set
    746 `coding-system-for-read' value.")
    747 
    748 (defun editorconfig--advice-insert-file-contents (f filename &rest args)
    749   "Set `coding-system-for-read'.
    750 
    751 This function should be added as an advice function to `insert-file-contents'.
    752 F is that function, and FILENAME and ARGS are arguments passed to F."
    753   ;; This function uses `editorconfig--cons-filename-codingsystem' to decide what coding-system
    754   ;; should be used, which will be set by `editorconfig--advice-find-file-noselect'.
    755   (display-warning '(editorconfig editorconfig--advice-insert-file-contents)
    756                    (format "editorconfig--advice-insert-file-contents: filename: %S args: %S codingsystem: %S bufferfilename: %S"
    757                            filename args
    758                            editorconfig--cons-filename-codingsystem
    759                            buffer-file-name)
    760                    :debug)
    761   (if (and (stringp filename)
    762            (stringp (car editorconfig--cons-filename-codingsystem))
    763            (string= (expand-file-name filename)
    764                     (car editorconfig--cons-filename-codingsystem))
    765            (cdr editorconfig--cons-filename-codingsystem)
    766            (not (eq (cdr editorconfig--cons-filename-codingsystem)
    767                     'undecided)))
    768       (let (
    769             (coding-system-for-read (cdr editorconfig--cons-filename-codingsystem))
    770             ;; (coding-system-for-read 'undecided)
    771             )
    772         (apply f filename args))
    773     (apply f filename args)))
    774 
    775 (defun editorconfig--advice-find-file-noselect (f filename &rest args)
    776   "Get EditorConfig properties and apply them to buffer to be visited.
    777 
    778 This function should be added as an advice function to `find-file-noselect'.
    779 F is that function, and FILENAME and ARGS are arguments passed to F."
    780   (let ((props nil)
    781         (coding-system nil)
    782         (ret nil))
    783     (condition-case err
    784         (when (and (stringp filename)
    785                    (not (editorconfig--disabled-for-filename filename)))
    786           (setq props (editorconfig-call-get-properties-function filename))
    787           (setq coding-system
    788                 (editorconfig-merge-coding-systems (gethash 'end_of_line props)
    789                                                    (gethash 'charset props))))
    790       (error
    791        (display-warning '(editorconfig editorconfig--advice-find-file-noselect)
    792                         (format "Failed to get properties, styles will not be applied: %S"
    793                                 err)
    794                         :warning)))
    795 
    796     (let ((editorconfig--cons-filename-codingsystem (cons (expand-file-name filename)
    797                                                           coding-system)))
    798       (setq ret (apply f filename args)))
    799 
    800     (condition-case err
    801         (with-current-buffer ret
    802           (when (and props
    803                      ;; filename has already been checked
    804                      (not (editorconfig--disabled-for-majormode major-mode)))
    805             (when (and (file-remote-p filename)
    806                        (not (local-variable-p 'buffer-file-coding-system))
    807                        (not (file-exists-p filename))
    808                        coding-system
    809                        (not (eq coding-system
    810                                 'undecided)))
    811               ;; When file path indicates it is a remote file and it actually
    812               ;; does not exists, `buffer-file-coding-system' will not be set.
    813               ;; (Seems `insert-file-contents' will not be called)
    814               ;; For that case, explicitly set this value so that saving will be done
    815               ;; with expected coding system.
    816               (set-buffer-file-coding-system coding-system))
    817 
    818             ;; NOTE: When using editorconfig-2-mode, hack-properties-functions cannot affect coding-system value,
    819             ;; because it has to be set before initializing buffers.
    820             (condition-case err
    821                 (run-hook-with-args 'editorconfig-hack-properties-functions props)
    822               (error
    823                (display-warning '(editorconfig editorconfig-hack-properties-functions)
    824                                 (format "Error while running editorconfig-hack-properties-functions, abort running hook: %S"
    825                                         err)
    826                                 :warning)))
    827             (setq editorconfig-properties-hash props)
    828             ;; When initializing buffer, `editorconfig-major-mode-hook'
    829             ;; will be called before setting `editorconfig-properties-hash', so
    830             ;; execute this explicitly here.
    831             (editorconfig-set-local-variables props)
    832 
    833 
    834             (condition-case err
    835                 (run-hook-with-args 'editorconfig-after-apply-functions props)
    836               (error
    837                (display-warning '(editorconfig editorconfig--advice-find-file-noselect)
    838                                 (format "Error while running `editorconfig-after-apply-functions': %S"
    839                                         err))))))
    840       (error
    841        (display-warning '(editorconfig editorconfig--advice-find-file-noselect)
    842                         (format "Error while setting variables from EditorConfig: %S" err))))
    843     ret))
    844 
    845 (defvar editorconfig--enable-20210221-testing nil
    846   "Enable testing version of `editorconfig-mode'.
    847 
    848 Currently this mode is not well tested yet and can cause unexpected behaviors
    849 like killing Emacs process or not able to visit files at all.")
    850 
    851 ;;;###autoload
    852 (define-minor-mode editorconfig-mode
    853   "Toggle EditorConfig feature.
    854 
    855 To disable EditorConfig in some buffers, modify
    856 `editorconfig-exclude-modes' or `editorconfig-exclude-regexps'."
    857   :global t
    858   :lighter editorconfig-mode-lighter
    859   (if editorconfig--enable-20210221-testing
    860       (let ((modehooks '(prog-mode-hook
    861                           text-mode-hook
    862                           read-only-mode-hook
    863                           ;; Some modes call `kill-all-local-variables' in their init
    864                           ;; code, which clears some values set by editorconfig.
    865                           ;; For those modes, editorconfig-apply need to be called
    866                           ;; explicitly through their hooks.
    867                           rpm-spec-mode-hook
    868                           )))
    869         (if editorconfig-mode
    870             (progn
    871               (advice-add 'find-file-noselect :around 'editorconfig--advice-find-file-noselect)
    872               (advice-add 'insert-file-contents :around 'editorconfig--advice-insert-file-contents)
    873               (dolist (hook modehooks)
    874                 (add-hook hook
    875                           'editorconfig-major-mode-hook
    876                           t)))
    877           (advice-remove 'find-file-noselect 'editorconfig--advice-find-file-noselect)
    878           (advice-remove 'insert-file-contents 'editorconfig--advice-insert-file-contents)
    879           (dolist (hook modehooks)
    880             (remove-hook hook
    881                          'editorconfig-major-mode-hook))))
    882 
    883     ;; editorconfig--enable-20210221-testing is disabled
    884     ;; See https://github.com/editorconfig/editorconfig-emacs/issues/141 for why
    885     ;; not `after-change-major-mode-hook'
    886     (dolist (hook '(change-major-mode-after-body-hook
    887                     read-only-mode-hook
    888                     ;; Some modes call `kill-all-local-variables' in their init
    889                     ;; code, which clears some values set by editorconfig.
    890                     ;; For those modes, editorconfig-apply need to be called
    891                     ;; explicitly through their hooks.
    892                     rpm-spec-mode-hook
    893                     ))
    894       (if editorconfig-mode
    895           (add-hook hook 'editorconfig-mode-apply)
    896         (remove-hook hook 'editorconfig-mode-apply)))))
    897 
    898 
    899 ;; Tools
    900 ;; Some useful commands for users, not required for EditorConfig to work
    901 
    902 ;;;###autoload
    903 (defun editorconfig-find-current-editorconfig ()
    904   "Find the closest .editorconfig file for current file."
    905   (interactive)
    906   (eval-and-compile (require 'editorconfig-core))
    907   (let ((file (editorconfig-core-get-nearest-editorconfig
    908                default-directory)))
    909     (when file
    910       (find-file file))))
    911 
    912 ;;;###autoload
    913 (defun editorconfig-display-current-properties ()
    914   "Display EditorConfig properties extracted for current buffer."
    915   (interactive)
    916   (if editorconfig-properties-hash
    917       (let (
    918             (buf (get-buffer-create "*EditorConfig Properties*"))
    919             (file buffer-file-name)
    920             (props editorconfig-properties-hash))
    921         (with-current-buffer buf
    922           (erase-buffer)
    923           (insert (format "# EditorConfig for %s\n" file))
    924           (maphash (lambda (k v)
    925                      (insert (format "%S = %s\n" k v)))
    926                    props))
    927         (display-buffer buf))
    928     (message "Properties are not applied to current buffer yet.")
    929     nil))
    930 ;;;###autoload
    931 (defalias 'describe-editorconfig-properties
    932   'editorconfig-display-current-properties)
    933 
    934 ;;;###autoload
    935 (defun editorconfig-format-buffer()
    936   "Format buffer according to .editorconfig indent_style and indent_width."
    937   (interactive)
    938   (if (string= (gethash 'indent_style editorconfig-properties-hash) "tab")
    939       (tabify (point-min) (point-max)))
    940   (if (string= (gethash 'indent_style editorconfig-properties-hash) "space")
    941       (untabify (point-min) (point-max)))
    942   (indent-region (point-min) (point-max)))
    943 
    944 
    945 
    946 ;; (defconst editorconfig--version
    947 ;;   (eval-when-compile
    948 ;;     (require 'lisp-mnt)
    949 ;;     (declare-function lm-version "lisp-mnt" nil)
    950 ;;     (lm-version))
    951 ;;   "EditorConfig version.")
    952 
    953 (declare-function find-library-name "find-func" (library))
    954 (declare-function lm-version "lisp-mnt" nil)
    955 
    956 ;;;###autoload
    957 (defun editorconfig-version (&optional show-version)
    958   "Get EditorConfig version as string.
    959 
    960 If called interactively or if SHOW-VERSION is non-nil, show the
    961 version in the echo area and the messages buffer."
    962   (interactive (list t))
    963   (let* ((version
    964           (with-temp-buffer
    965             (require 'find-func)
    966             (insert-file-contents (find-library-name "editorconfig"))
    967             (require 'lisp-mnt)
    968             (lm-version)))
    969          (pkg
    970           (and (eval-and-compile (require 'package nil t))
    971                (cadr (assq 'editorconfig
    972                            package-alist))))
    973          (pkg-version
    974           (and pkg
    975                (package-version-join (package-desc-version pkg))))
    976          (version-full (if (and pkg-version
    977                                 (not (string= version
    978                                               pkg-version)))
    979                            (concat version "-" pkg-version)
    980                          version)))
    981     (when show-version
    982       (message "EditorConfig Emacs v%s"
    983                version-full))
    984     version-full))
    985 
    986 (provide 'editorconfig)
    987 
    988 ;;; editorconfig.el ends here
    989 
    990 ;; Local Variables:
    991 ;; sentence-end-double-space: t
    992 ;; End: