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.

Reply via email to