aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMauro Aranda2023-10-23 09:45:12 -0300
committerJuri Linkov2023-10-28 20:02:13 +0300
commitc79ea103efd6fa3004c14f373305a17c49d6462d (patch)
tree10f2867d4b387e2991f90d720381c26f267261f9
parente81e625ab895f1bd3c5263f5b66251db0fd38bd6 (diff)
downloademacs-c79ea103efd6fa3004c14f373305a17c49d6462d.tar.gz
emacs-c79ea103efd6fa3004c14f373305a17c49d6462d.zip
Add easy customization for dir-locals files (Bug#66702)
* lisp/cus-edit.el (custom--editable-field-p): New utility function. (custom-dirlocals-widget, custom-dirlocals-file-widget) (custom-dirlocals-commands, custom-dirlocals-tool-bar-map): New variables. (custom-dirlocals-map, custom-dirlocals-field-map): New keymaps. (Custom-dirlocals-menu): New menu. (custom-dirlocals-key, custom-dynamic-cons, custom-dirlocals): New widgets. (custom-dirlocals-maybe-update-cons, custom-dirlocals-symbol-action) (custom-dirlocals-change-file, custom-dirlocals--set-widget-vars) (custom-dirlocals-get-options, custom-dirlocals-validate): New functions. (custom-dirlocals-with-buffer): New macro. (Custom-dirlocals-revert-buffer, Custom-dirlocals-save) (customize-dirlocals): New commands. * doc/emacs/custom.texi (Directory Variables): Document customize-dirlocals. * etc/NEWS: Announce.
-rw-r--r--doc/emacs/custom.texi5
-rw-r--r--etc/NEWS5
-rw-r--r--lisp/cus-edit.el289
3 files changed, 299 insertions, 0 deletions
diff --git a/doc/emacs/custom.texi b/doc/emacs/custom.texi
index d51912a75da..e2d35863bd0 100644
--- a/doc/emacs/custom.texi
+++ b/doc/emacs/custom.texi
@@ -1515,6 +1515,11 @@ want to modify. Although it doesn't have to exist, you must enter a
1515valid filename, either @file{.dir-locals.el} or 1515valid filename, either @file{.dir-locals.el} or
1516@file{.dir-locals-2.el}. 1516@file{.dir-locals-2.el}.
1517 1517
1518@findex customize-dirlocals
1519There's also a command to pop up an Easy Customization buffer
1520(@pxref{Easy Customization}) to edit directory local variables,
1521@code{customize-dirlocals}.
1522
1518@findex dir-locals-set-class-variables 1523@findex dir-locals-set-class-variables
1519@findex dir-locals-set-directory-class 1524@findex dir-locals-set-directory-class
1520 Another method of specifying directory-local variables is to define 1525 Another method of specifying directory-local variables is to define
diff --git a/etc/NEWS b/etc/NEWS
index 05fd1b7a390..ed9f1a2124c 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -961,6 +961,11 @@ For links in 'webjump-sites' without an explicit URI scheme, it was
961previously assumed that they should be prefixed with "http://". Such 961previously assumed that they should be prefixed with "http://". Such
962URIs are now prefixed with "https://" instead. 962URIs are now prefixed with "https://" instead.
963 963
964** Customize
965+++
966*** New command customize-dirlocals
967This command pops up a buffer to edit the settings in .dir-locals.el
968
964 969
965* New Modes and Packages in Emacs 30.1 970* New Modes and Packages in Emacs 30.1
966 971
diff --git a/lisp/cus-edit.el b/lisp/cus-edit.el
index 953b8b8b80f..6442ffeac24 100644
--- a/lisp/cus-edit.el
+++ b/lisp/cus-edit.el
@@ -512,6 +512,13 @@ WIDGET is the widget to apply the filter entries of MENU on."
512 (push name result))) 512 (push name result)))
513 (nreverse result))) 513 (nreverse result)))
514 514
515(defun custom--editable-field-p (widget)
516 "Non-nil if WIDGET is an editable-field widget, or inherits from it."
517 (let ((type (widget-type widget)))
518 (while (and type (not (eq type 'editable-field)))
519 (setq type (widget-type (get type 'widget-type))))
520 type))
521
515;;; Unlispify. 522;;; Unlispify.
516 523
517(defvar custom-prefix-list nil 524(defvar custom-prefix-list nil
@@ -5692,6 +5699,288 @@ This stores EXP (without evaluating it) as the saved spec for SYMBOL."
5692 (prin1 value (current-buffer))) 5699 (prin1 value (current-buffer)))
5693 (insert ")\n"))))) 5700 (insert ")\n")))))
5694 5701
5702;;; Directory Local Variables.
5703;; The following code provides an Easy Customization interface to manage
5704;; `.dir-locals.el' files.
5705;; The main command is `customize-dirlocals'. It presents a Custom-like buffer
5706;; but with a few tweaks. Variables are inserted in a repeat widget, and
5707;; update its associated widget (the one for editing the value) upon the user
5708;; hitting RET or TABbing out of it.
5709;; This is unlike the `cus-theme.el' interface for editing themes, that prompts
5710;; the user for the variable to then create the appropriate widget.
5711(defvar-local custom-dirlocals-widget nil
5712 "Widget that holds the dir-locals customizations.")
5713
5714(defvar-local custom-dirlocals-file-widget nil
5715 "Widget that holds the name of the dir-locals file being customized.")
5716
5717(defvar-keymap custom-dirlocals-map
5718 :doc "Keymap used in the \"*Customize Dirlocals*\" buffer."
5719 :full t
5720 :parent widget-keymap
5721 "SPC" #'scroll-up-command
5722 "S-SPC" #'scroll-down-command
5723 "DEL" #'scroll-down-command
5724 "C-x C-s" #'Custom-dirlocals-save
5725 "q" #'Custom-buffer-done
5726 "n" #'widget-forward
5727 "p" #'widget-backward)
5728
5729(defvar custom-dirlocals-field-map
5730 (let ((map (copy-keymap custom-field-keymap)))
5731 (define-key map "\C-x\C-s" #'Custom-dirlocals-save)
5732 (define-key map "\C-m" #'widget-field-activate)
5733 map)
5734 "Keymap for the editable fields in the \"*Customize Dirlocals*\" buffer .")
5735
5736(defvar custom-dirlocals-commands
5737 '((" Save Settings " Custom-dirlocals-save t
5738 "Save Settings to the dir-locals file." "save" "Save" t)
5739 (" Undo Edits " Custom-dirlocals-revert-buffer t
5740 "Revert buffer, undoing any editions."
5741 "refresh" "Undo" t)
5742 (" Help for Customize " Custom-help t "Get help for using Customize."
5743 "help" "Help" t)
5744 (" Exit " Custom-buffer-done t "Exit Customize." "exit" "Exit" t))
5745 "Alist of specifications for Customize menu items, tool bar icons and buttons.
5746See `custom-commands' for further explanation.")
5747
5748(easy-menu-define
5749 Custom-dirlocals-menu (list custom-dirlocals-map
5750 custom-dirlocals-field-map)
5751 "Menu used in dirlocals customization buffers."
5752 (nconc (list "Custom"
5753 (customize-menu-create 'customize))
5754 (mapcar (lambda (arg)
5755 (let ((tag (nth 0 arg))
5756 (command (nth 1 arg))
5757 (visible (nth 2 arg))
5758 (help (nth 3 arg))
5759 (active (nth 6 arg)))
5760 (vector tag command :visible (eval visible)
5761 :active `(eq t ',active)
5762 :help help)))
5763 custom-dirlocals-commands)))
5764
5765(defvar custom-dirlocals-tool-bar-map nil
5766 "Keymap for the toolbar in \"*Customize Dirlocals*\" buffer.")
5767
5768(define-widget 'custom-dirlocals-key 'menu-choice
5769 "Menu to choose between possible keys in a dir-locals file.
5770
5771Possible values are nil, a symbol (standing for a major mode) or a directory
5772name."
5773 :tag "Specification"
5774 :value nil
5775 :help-echo "Select a key for the dir-locals specification."
5776 :args '((const :tag "All modes" nil)
5777 (symbol :tag "Major mode" fundamental-mode)
5778 (directory :tag "Subdirectory")))
5779
5780(define-widget 'custom-dynamic-cons 'cons
5781 "A cons widget that changes its 2nd type based on the 1st type."
5782 :value-create #'custom-dynamic-cons-value-create)
5783
5784(defun custom-dynamic-cons-value-create (widget)
5785 "Select an appropriate 2nd type for the cons WIDGET and create WIDGET.
5786
5787The appropriate types are:
5788- A symbol, if the value to represent is a minor-mode.
5789- A boolean, if the value to represent is either the unibyte value or the
5790 subdirs value.
5791- A widget type suitable for editing a variable, in case of specifying a
5792 variable's value.
5793- A sexp widget, if none of the above happens."
5794 (let* ((args (widget-get widget :args))
5795 (value (widget-get widget :value))
5796 (val (car value)))
5797 (cond
5798 ((eq val 'mode) (setf (nth 1 args)
5799 '(symbol :keymap custom-dirlocals-field-map
5800 :tag "Minor mode")))
5801 ((eq val 'unibyte) (setf (nth 1 args) '(boolean)))
5802 ((eq val 'subdirs) (setf (nth 1 args) '(boolean)))
5803 ((custom-variable-p val)
5804 (let ((w (widget-convert (custom-variable-type val))))
5805 (when (custom--editable-field-p w)
5806 (widget-put w :keymap custom-dirlocals-field-map))
5807 (setf (nth 1 args) w)))
5808 (t (setf (nth 1 args) '(sexp :keymap custom-dirlocals-field-map))))
5809 (widget-put (nth 0 args) :keymap custom-dirlocals-field-map)
5810 (widget-group-value-create widget)))
5811
5812(defun custom-dirlocals-maybe-update-cons ()
5813 "If focusing out from the first widget in a cons widget, update its value."
5814 (when-let ((w (widget-at)))
5815 (when (widget-get w :custom-dirlocals-symbol)
5816 (widget-value-set (widget-get w :parent)
5817 (cons (widget-value w) ""))
5818 (widget-setup))))
5819
5820(define-widget 'custom-dirlocals 'editable-list
5821 "An editable list to edit settings in a dir-locals file."
5822 :entry-format "%i %d %v"
5823 :insert-button-args '(:help-echo "Insert new specification here.")
5824 :append-button-args '(:help-echo "Append new specification here.")
5825 :delete-button-args '(:help-echo "Delete this specification.")
5826 :args '((group :format "%v"
5827 custom-dirlocals-key
5828 (repeat
5829 :tag "Settings"
5830 :inline t
5831 (custom-dynamic-cons
5832 :tag "Setting"
5833 (symbol :action custom-dirlocals-symbol-action
5834 :custom-dirlocals-symbol t)
5835 ;; Will change according to the option being customized.
5836 (sexp :tag "Value"))))))
5837
5838(defun custom-dirlocals-symbol-action (widget &optional _event)
5839 "Action for the symbol WIDGET.
5840
5841Sets the value of its parent, a cons widget, in order to create an
5842appropriate widget to edit the value of WIDGET.
5843
5844Moves point into the widget that holds the value."
5845 (setq widget (or widget (widget-at)))
5846 (widget-value-set (widget-get widget :parent)
5847 (cons (widget-value widget) ""))
5848 (widget-setup)
5849 (widget-forward 1))
5850
5851(defun custom-dirlocals-change-file (widget &optional _event)
5852 "Switch to a buffer to customize the dir-locals file in WIDGET."
5853 (customize-dirlocals (expand-file-name (widget-value widget))))
5854
5855(defun custom-dirlocals--set-widget-vars ()
5856 "Set local variables for the Widget library."
5857 (custom--initialize-widget-variables)
5858 (add-hook 'widget-forward-hook #'custom-dirlocals-maybe-update-cons nil t))
5859
5860(defmacro custom-dirlocals-with-buffer (&rest body)
5861 "Arrange to execute BODY in a \"*Customize Dirlocals*\" buffer."
5862 ;; We don't use `custom-buffer-create' because the settings here
5863 ;; don't go into the `custom-file'.
5864 `(progn
5865 (switch-to-buffer "*Customize Dirlocals*")
5866 (kill-all-local-variables)
5867 (let ((inhibit-read-only t))
5868 (erase-buffer))
5869 (remove-overlays)
5870 (custom-dirlocals--set-widget-vars)
5871 ,@body
5872 (setq-local tool-bar-map
5873 (or custom-dirlocals-tool-bar-map
5874 ;; Set up `custom-dirlocals-tool-bar-map'.
5875 (let ((map (make-sparse-keymap)))
5876 (mapc
5877 (lambda (arg)
5878 (tool-bar-local-item-from-menu
5879 (nth 1 arg) (nth 4 arg) map custom-dirlocals-map
5880 :label (nth 5 arg)))
5881 custom-dirlocals-commands)
5882 (setq custom-dirlocals-tool-bar-map map))))
5883 (setq-local revert-buffer-function #'Custom-dirlocals-revert-buffer)
5884 (use-local-map custom-dirlocals-map)
5885 (widget-setup)))
5886
5887(defun custom-dirlocals-get-options ()
5888 "Return all options inside a custom-dirlocals widget."
5889 (let* ((groups (widget-get custom-dirlocals-widget :children))
5890 (repeats (mapcar (lambda (group)
5891 (nth 1 (widget-get group :children)))
5892 groups)))
5893 (mapcan (lambda (repeat)
5894 (mapcar (lambda (w)
5895 (nth 1 (widget-get w :children)))
5896 (widget-get repeat :children)))
5897 repeats)))
5898
5899(defun custom-dirlocals-validate ()
5900 "Non-nil if all customization options validate.
5901
5902If at least an option doesn't validate, signals an error and moves point
5903to the widget with the invalid value."
5904 (dolist (opt (custom-dirlocals-get-options))
5905 (when-let ((w (widget-apply opt :validate)))
5906 (goto-char (widget-get w :from))
5907 (error "%s" (widget-get w :error))))
5908 t)
5909
5910(defun Custom-dirlocals-revert-buffer (&rest _ignored)
5911 "Revert the buffer for Directory Local Variables customization."
5912 (interactive)
5913 (customize-dirlocals (widget-get custom-dirlocals-file-widget :value)))
5914
5915(defun Custom-dirlocals-save (&rest _ignore)
5916 "Save the settings to the dir-locals file being customized."
5917 (interactive)
5918 (when (custom-dirlocals-validate)
5919 (let* ((file (widget-value custom-dirlocals-file-widget))
5920 (old (widget-get custom-dirlocals-widget :value))
5921 (dirlocals (widget-value custom-dirlocals-widget)))
5922 (dolist (spec old)
5923 (let ((mode (car spec))
5924 (settings (cdr spec)))
5925 (dolist (setting settings)
5926 (delete-dir-local-variable mode (car setting) file))))
5927 (dolist (spec dirlocals)
5928 (let ((mode (car spec))
5929 (settings (cdr spec)))
5930 (dolist (setting (reverse settings))
5931 (when (memq (car setting) '(mode eval))
5932 (delete-dir-local-variable mode (car setting) file))
5933 (add-dir-local-variable mode (car setting) (cdr setting) file)))))
5934 ;; Write the dir-locals file and kill its buffer, to come back to
5935 ;; our own buffer.
5936 (write-file (expand-file-name buffer-file-name) nil)
5937 (kill-buffer)))
5938
5939;;;###autoload
5940(defun customize-dirlocals (&optional filename)
5941 "Customize Directory Local Variables in the current directory.
5942
5943With optional argument FILENAME non-nil, customize the `.dir-locals.el' file
5944that FILENAME specifies."
5945 (interactive)
5946 (let* ((file (or filename (expand-file-name ".dir-locals.el")))
5947 (dirlocals (when (file-exists-p file)
5948 (with-current-buffer (find-file-noselect file)
5949 (goto-char (point-min))
5950 (prog1
5951 (condition-case _
5952 (read (current-buffer))
5953 (end-of-file nil))
5954 (kill-buffer))))))
5955 (custom-dirlocals-with-buffer
5956 (widget-insert
5957 "This buffer is for customizing the Directory Local Variables in:\n")
5958 (setq custom-dirlocals-file-widget
5959 (widget-create `(file :action ,#'custom-dirlocals-change-file
5960 ,file)))
5961 (widget-insert
5962 (substitute-command-keys
5963 "
5964To select another file, edit the above field and hit RET.
5965
5966After you enter a user option name under the symbol field,
5967be sure to press \\`RET' or \\`TAB', so that the field that holds the
5968value changes to an appropriate field for the option.
5969
5970Type \\`C-x C-s' when you've finished editing it, to save the
5971settings to the file."))
5972 (widget-insert "\n\n\n")
5973 (widget-create 'push-button :tag " Revert "
5974 :action #'Custom-dirlocals-revert-buffer)
5975 (widget-insert " ")
5976 (widget-create 'push-button :tag " Save Settings "
5977 :action #'Custom-dirlocals-save)
5978 (widget-insert "\n\n")
5979 (setq custom-dirlocals-widget
5980 (widget-create 'custom-dirlocals :value dirlocals))
5981 (setq default-directory (file-name-directory file))
5982 (goto-char (point-min)))))
5983
5695(provide 'cus-edit) 5984(provide 'cus-edit)
5696 5985
5697;;; cus-edit.el ends here 5986;;; cus-edit.el ends here