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: