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