branch: scratch/editorconfig commit 54ee2b48626e6688c21699253865f57f45ff2679 Author: Stefan Monnier <monn...@iro.umontreal.ca> Commit: Stefan Monnier <monn...@iro.umontreal.ca>
(editorconfig-set-local-variables): Get first, set later In preparation for the use of Emacs-30's `hack-dir-local-get-variables-functions`, split the job of `editorconfig-set-local-variables` into getting an alist of settings and then applying them. Rename the `editorconfig-set-*` functions to `editorconfig--get-*` and make them return an alist of settings rather than applying them (except for `editorconfig-set-coding-system-revert` which arguably belongs in `editorconfig-tools.el` anyway). * editorconfig.el (editorconfig-indentation-alist): Change the doc to mention that the functions should return an alist. Use the new `editorconfig--get-indentation-*-mode` functions. (editorconfig--get-indentation-*-mode): Don't `boundp`-test the vars. (editorconfig--get-indentation): Don't set `tab-width` from SIZE. Don't test `editorconfig--should-set` here. Test `boundp` here. (editorconfig--add-hook-safe): New function. Add it to the `safe-local-eval-function` property of `add-hook`. (editorconfig-set-local-variables): Test `editorconfig--should-set` here. Rewrite it using `editorconfig--get-local-variables`. --- editorconfig.el | 253 ++++++++++++++++++++++++++++++-------------------------- 1 file changed, 138 insertions(+), 115 deletions(-) diff --git a/editorconfig.el b/editorconfig.el index 66e99c4185..ca271b5004 100644 --- a/editorconfig.el +++ b/editorconfig.el @@ -38,6 +38,11 @@ ;; EditorConfig files are easily readable and they work nicely with ;; version control systems. +;; News: + +;; In `editorconfig-indentation-alist', if a mode is associated to a function +;; that function should not set the vars but should instead *return* them. + ;;; Code: (require 'cl-lib) @@ -204,7 +209,7 @@ This hook will be run even when there are no matching sections in (css-ts-mode css-indent-offset) (d-mode c-basic-offset) (elixir-ts-mode elixir-ts-indent-offset) - (emacs-lisp-mode . editorconfig-set-indentation-lisp-mode) + (emacs-lisp-mode . editorconfig--get-indentation-lisp-mode) (enh-ruby-mode enh-ruby-indent-level) (erlang-mode erlang-indent-level) (ess-mode ess-indent-offset) @@ -252,8 +257,8 @@ This hook will be run even when there are no matching sections in (julia-mode julia-indent-offset) (kotlin-mode kotlin-tab-width) (kotlin-ts-mode kotlin-ts-mode-indent-offset) - (latex-mode . editorconfig-set-indentation-latex-mode) - (lisp-mode . editorconfig-set-indentation-lisp-mode) + (latex-mode . editorconfig--get-indentation-latex-mode) + (lisp-mode . editorconfig--get-indentation-lisp-mode) (livescript-mode livescript-tab-width) (lua-mode lua-indent-level) (lua-ts-mode lua-ts-indent-offset) @@ -280,8 +285,8 @@ This hook will be run even when there are no matching sections in (ps-mode ps-mode-tab) (pug-mode pug-tab-width) (puppet-mode puppet-indent-level) - (python-mode . editorconfig-set-indentation-python-mode) - (python-ts-mode . editorconfig-set-indentation-python-mode) + (python-mode . editorconfig--get-indentation-python-mode) + (python-ts-mode . editorconfig--get-indentation-python-mode) (rjsx-mode js-indent-level sgml-basic-offset) (ruby-mode ruby-indent-level) (ruby-ts-mode ruby-indent-level) @@ -327,7 +332,8 @@ This hook will be run even when there are no matching sections in Each element looks like (MODE . FUNCTION) or (MODE . INDENT-SPEC-LIST). If FUNCTION is provided, it will be called when setting the -indentation. The indent size will be passed. +indentation. The indent size will be passed and it should return +a list of settings of the form (VAR . VAL). If INDENT-SPEC-LIST is provided, each element of it must have one of the following forms: @@ -438,35 +444,30 @@ Make a message by passing ARGS to `format-message'." (and (stringp string) (string-match-p "\\`[0-9]+\\'" string))) -(defun editorconfig-set-indentation-python-mode (size) - "Set `python-mode' indent size to SIZE." - (when (boundp 'python-indent-offset) - (setq-local python-indent-offset size)) - ;; For https://gitlab.com/python-mode-devs/python-mode - (when (boundp 'py-indent-offset) - (setq-local py-indent-offset size))) - -(defun editorconfig-set-indentation-latex-mode (size) - "Set `latex-mode' indent size to SIZE." - (setq-local tex-indent-basic size) - (setq-local tex-indent-item size) - (setq-local tex-indent-arg (* 2 size)) - ;; For AUCTeX - (when (boundp 'TeX-brace-indent-level) - (setq-local TeX-brace-indent-level size)) - (when (boundp 'LaTeX-indent-level) - (setq-local LaTeX-indent-level size)) - (when (boundp 'LaTeX-item-indent) - (setq-local LaTeX-item-indent (- size)))) - -(defun editorconfig-set-indentation-lisp-mode (size) - "Set indent size to SIZE for Lisp mode(s)." - (when (cond ((null editorconfig-lisp-use-default-indent) t) - ((eql t editorconfig-lisp-use-default-indent) nil) - ((numberp editorconfig-lisp-use-default-indent) - (not (eql size editorconfig-lisp-use-default-indent))) - (t t)) - (setq-local lisp-indent-offset size))) +(defun editorconfig--get-indentation-python-mode (size) + "Var to set `python-mode' indent size to SIZE." + `((python-indent-offset . ,size) + ;; For https://gitlab.com/python-mode-devs/python-mode + (py-indent-offset . ,size))) + +(defun editorconfig--get-indentation-latex-mode (size) + "Vars to set `latex-mode' indent size to SIZE." + `((tex-indent-basic . ,size) + (tex-indent-item . ,size) + (tex-indent-arg . ,(* 2 size)) + ;; For AUCTeX + (TeX-brace-indent-level . ,size) + (LaTeX-indent-level . ,size) + (LaTeX-item-indent . ,(- size)))) + +(defun editorconfig--get-indentation-lisp-mode (size) + "Set indent size to SIZE for Lisp mode(s)." + (when (cond ((null editorconfig-lisp-use-default-indent) t) + ((eql t editorconfig-lisp-use-default-indent) nil) + ((numberp editorconfig-lisp-use-default-indent) + (not (eql size editorconfig-lisp-use-default-indent))) + (t t)) + `((lisp-indent-offset . ,size)))) (cl-defun editorconfig--should-set (symbol) "Determine if editorconfig should set SYMBOL." @@ -486,58 +487,62 @@ Make a message by passing ARGS to `format-message'." t) -(defun editorconfig-set-indentation (style &optional size tab_width) - "Set indentation type from STYLE, SIZE and TAB_WIDTH." +(defun editorconfig--get-indentation (style &optional size tab_width) + "Get indentation vars according to STYLE, SIZE, and TAB_WIDTH." + (when tab_width + (setq tab_width (string-to-number tab_width))) + (setq size (cond ((editorconfig-string-integer-p size) (string-to-number size)) ((equal size "tab") - "tab") + (or tab_width tab-width)) (t nil))) - (cond ((not (editorconfig--should-set 'tab-width)) - nil) - (tab_width - (setq tab-width (string-to-number tab_width))) - ((numberp size) - (setq tab-width size))) - - (when (equal size "tab") - (setq size tab-width)) - - (cond ((not (editorconfig--should-set 'indent-tabs-mode)) - nil) - ((equal style "space") - (setq indent-tabs-mode nil)) - ((equal style "tab") - (setq indent-tabs-mode t))) - - (when size - (when (and (featurep 'evil) - (editorconfig--should-set 'evil-shift-width)) - (setq-local evil-shift-width size)) - (let ((parent major-mode) - entry) - ;; Find the closet parent mode of `major-mode' in - ;; `editorconfig-indentation-alist'. - (while (and (not (setq entry (assoc parent editorconfig-indentation-alist))) - (setq parent (get parent 'derived-mode-parent)))) - (when entry - (let ((fn-or-list (cdr entry))) - (cond ((functionp fn-or-list) (funcall fn-or-list size)) - ((listp fn-or-list) - (dolist (elem fn-or-list) - (cond ((and (symbolp elem) - (editorconfig--should-set elem)) - (set (make-local-variable elem) size)) - ((and (consp elem) - (editorconfig--should-set (car elem))) - (let ((spec (cdr elem))) - (set (make-local-variable (car elem)) - (cond ((functionp spec) (funcall spec size)) - ((integerp spec) (* spec size)) - (t spec)))))))))))))) + `(,@(cond (tab_width `((tab-width . ,tab_width))) + ;; FIXME: This seems wrong: `tab-width' controls the display width + ;; of TAB characters in the buffer, which is largely independent + ;; from the indentation step. + ;;((numberp size) `((tab-width . ,size))) + ) + + ,@(cond ((equal style "space") + `((indent-tabs-mode . nil))) + ((equal style "tab") + `((indent-tabs-mode . t)))) + + ,@(when (and size (featurep 'evil)) + `((evil-shift-width . ,size))) + ,@(when size + (let ((parent major-mode) + entry) + ;; Find the closet parent mode of `major-mode' in + ;; `editorconfig-indentation-alist'. + (while (and (not (setq entry + (assoc parent editorconfig-indentation-alist))) + (setq parent (get parent 'derived-mode-parent)))) + (when entry + (let ((fn-or-list (cdr entry))) + (cond ((functionp fn-or-list) + (let ((alist (funcall fn-or-list size))) + ;; Filter out settings of unknown vars. + (delq nil + (mapcar (lambda (elem) + (if (boundp (car elem)) elem)) + alist)))) + ((listp fn-or-list) + (mapcar + (lambda (elem) + (cond ((and (symbolp elem)) `(,elem . ,size)) + ((and (consp elem)) + `(,(car elem) + . ,(let ((spec (cdr elem))) + (cond ((functionp spec) + (funcall spec size)) + ((integerp spec) (* spec size)) + (t spec))))))) + fn-or-list))))))))) (defvar-local editorconfig--apply-coding-system-currently nil "Used internally.") @@ -596,48 +601,58 @@ This function will revert buffer when the coding-system has been changed." (revert-buffer-with-coding-system coding-system))) (setq editorconfig--apply-coding-system-currently nil))))) -(defun editorconfig-set-trailing-nl (final-newline) - "Set up requiring final newline by FINAL-NEWLINE. - -This function will set `require-final-newline' and `mode-require-final-newline' -to non-nil when FINAL-NEWLINE is true." +(defun editorconfig--get-trailing-nl (final-newline) + "Get the vars to require final newline according to FINAL-NEWLINE." (pcase final-newline ("true" - ;; keep prefs around how/when the nl is added, if set - otherwise add on save - (setq-local require-final-newline (or require-final-newline t)) - (setq-local mode-require-final-newline (or mode-require-final-newline t))) + ;; Keep prefs around how/when the nl is added, if set - otherwise add on + ;; save + `((require-final-newline . ,(or require-final-newline t)) + ;; FIXME: Why do we set `mode-require-final-newline'? + (mode-require-final-newline . ,(or mode-require-final-newline t)))) ("false" - ;; FIXME: Add functionality for actually REMOVING any trailing newlines here! - ;; (rather than just making sure we don't automagically ADD a new one) - (setq-local require-final-newline nil) - (setq-local mode-require-final-newline nil)))) + ;; FIXME: Add functionality to actually REMOVE any trailing newlines here! + ;; (rather than just making sure we don't automagically ADD a new one) + `((require-final-newline . nil) + (mode-require-final-newline . nil))))) (defun editorconfig--delete-trailing-whitespace () "Call `delete-trailing-whitespace' unless the buffer is read-only." (unless buffer-read-only (delete-trailing-whitespace))) -(defun editorconfig-set-trailing-ws (trim-trailing-ws) - "Set up trimming of trailing whitespace at end of lines by TRIM-TRAILING-WS." - (when (equal trim-trailing-ws "true") - ;; when true we push delete-trailing-whitespace (emacs > 21) - ;; to write-file-functions - (if editorconfig-trim-whitespaces-mode - (funcall editorconfig-trim-whitespaces-mode 1) - (add-hook 'before-save-hook - #'editorconfig--delete-trailing-whitespace nil t))) - (when (equal trim-trailing-ws "false") - ;; when false we remove every delete-trailing-whitespace - ;; from write-file-functions +;; Arrange for our (eval . (add-hook ...)) "local var" to be considered safe. +(defun editorconfig--add-hook-safe (exp) + (equal exp '(add-hook 'before-save-hook + #'editorconfig--delete-trailing-whitespace nil t))) +(let ((predicates (get 'add-hook 'safe-local-eval-function))) + (when (functionp predicates) + (setq predicates (list predicates))) + (unless (memq #'editorconfig--add-hook-safe predicates) + (put 'add-hook 'safe-local-eval-function #'editorconfig--add-hook-safe))) + +(defun editorconfig--get-trailing-ws (trim-trailing-ws) + "Get vars to trim of trailing whitespace according to TRIM-TRAILING-WS." + (cond + ((equal trim-trailing-ws "true") + `((eval + . ,(if editorconfig-trim-whitespaces-mode + `(,editorconfig-trim-whitespaces-mode 1) + '(add-hook 'before-save-hook + #'editorconfig--delete-trailing-whitespace nil t))))) + ((equal trim-trailing-ws "false") + ;; Just do it right away rather than return a (VAR . VAL), which + ;; would be probably more trouble than it's worth. (when editorconfig-trim-whitespaces-mode (funcall editorconfig-trim-whitespaces-mode 0)) (remove-hook 'before-save-hook - #'editorconfig--delete-trailing-whitespace t))) + #'editorconfig--delete-trailing-whitespace t) + nil))) -(defun editorconfig-set-line-length (length) - "Set the max line length (`fill-column') to LENGTH." +(defun editorconfig--get-line-length (length) + "Get the max line length (`fill-column') to LENGTH." (when (and (editorconfig-string-integer-p length) (> (string-to-number length) 0)) - (setq fill-column (string-to-number length)))) + `((fill-column . ,(string-to-number length))))) (defun editorconfig--execute-editorconfig-exec (filename) @@ -722,15 +737,23 @@ This function also removes `unset' properties and calls when (equal v "unset") do (remhash k props)) props)) +(defun editorconfig--get-local-variables (props) + "Get variables settings according to EditorConfig PROPS." + (append + (editorconfig--get-indentation (gethash 'indent_style props) + (gethash 'indent_size props) + (gethash 'tab_width props)) + (editorconfig--get-trailing-nl (gethash 'insert_final_newline props)) + (editorconfig--get-trailing-ws (gethash 'trim_trailing_whitespace props)) + (editorconfig--get-line-length (gethash 'max_line_length props)))) + (defun editorconfig-set-local-variables (props) "Set buffer variables according to EditorConfig PROPS." - (editorconfig-set-indentation (gethash 'indent_style props) - (gethash 'indent_size props) - (gethash 'tab_width props)) - (editorconfig-set-trailing-nl (gethash 'insert_final_newline props)) - (editorconfig-set-trailing-ws (gethash 'trim_trailing_whitespace props)) - (editorconfig-set-line-length (gethash 'max_line_length props))) - + (pcase-dolist (`(,var . ,val) (editorconfig--get-local-variables props)) + (if (eq 'eval var) + (eval val t) + (when (editorconfig--should-set var) + (set (make-local-variable var) val))))) (defun editorconfig-major-mode-hook () "Function to run when `major-mode' has been changed.