dotemacs

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

editorconfig.el (41940B)


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