branch: scratch/editorconfig
commit beec90f1c49ee046c3f9b1bde07d40c631eda19c
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--get-trailing-ws): Use `before-save-hook`
    instead of `write-file-functions`.  Still set the vars when disabling
    the feature, but just return the setting in an alist instead when
    enabling it.
    (editorconfig-set-local-variables): Test `editorconfig--should-set` here.
    Rewrite it using `editorconfig--get-local-variables`.
---
 editorconfig.el | 235 ++++++++++++++++++++++++++++++--------------------------
 1 file changed, 124 insertions(+), 111 deletions(-)

diff --git a/editorconfig.el b/editorconfig.el
index d13fe13924..057cdccd45 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)
@@ -247,7 +252,7 @@ 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)
+    (latex-mode . editorconfig--get-indentation-latex-mode)
     (lisp-mode lisp-indent-offset)
     (livescript-mode livescript-tab-width)
     (lua-mode lua-indent-level)
@@ -275,8 +280,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)
@@ -322,7 +327,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:
@@ -427,26 +433,21 @@ 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--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))))
 
 (cl-defun editorconfig--should-set (symbol &optional size)
   "Determine if editorconfig should set SYMBOL.
@@ -478,58 +479,62 @@ See `editorconfig-lisp-use-default-indent' for details."
 
   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 size))
-                          (set (make-local-variable elem) size))
-                         ((and (consp elem)
-                               (editorconfig--should-set (car elem) size))
-                          (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.")
@@ -588,46 +593,46 @@ 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))))
-
-(defun editorconfig-set-trailing-ws (trim-trailing-ws)
-  "Set up trimming of trailing whitespace at end of lines by TRIM-TRAILING-WS."
-  (make-local-variable 'write-file-functions) ;; just current buffer
-  (when (and (equal trim-trailing-ws "true")
-             (not buffer-read-only))
-    ;; 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-to-list 'write-file-functions 'delete-trailing-whitespace)))
-  (when (or (equal trim-trailing-ws "false")
-            buffer-read-only)
-    ;; when false we remove every delete-trailing-whitespace
-    ;; from write-file-functions
-    (when editorconfig-trim-whitespaces-mode
-      (funcall editorconfig-trim-whitespaces-mode 0))
-    (setq write-file-functions
-          (remove 'delete-trailing-whitespace write-file-functions))))
-
-(defun editorconfig-set-line-length (length)
-  "Set the max line length (`fill-column') to LENGTH."
+     ;; 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--get-trailing-ws (trim-trailing-ws)
+  "Get vars to trim of trailing whitespace according to TRIM-TRAILING-WS."
+  `(,@(when (and (equal trim-trailing-ws "true")
+                 ;; FIXME: Test this in `before-save-hook'?
+                 (not buffer-read-only))
+        `((eval
+           . ,(if editorconfig-trim-whitespaces-mode
+                  `(,editorconfig-trim-whitespaces-mode 1)
+                ;; Don't use #' because this specific form is recognized
+                ;; by Emacs's `safe-local-eval-forms'.
+                '(add-hook 'before-save-hook 'delete-trailing-whitespace nil 
t)))))
+    ,@(when (or (equal trim-trailing-ws "false")
+                buffer-read-only)
+        ;; 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 #'delete-trailing-whitespace t)
+        nil)))
+
+(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)
@@ -712,15 +717,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