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: