diff options
| author | Mauro Aranda | 2023-10-23 09:45:12 -0300 |
|---|---|---|
| committer | Juri Linkov | 2023-10-28 20:02:13 +0300 |
| commit | c79ea103efd6fa3004c14f373305a17c49d6462d (patch) | |
| tree | 10f2867d4b387e2991f90d720381c26f267261f9 | |
| parent | e81e625ab895f1bd3c5263f5b66251db0fd38bd6 (diff) | |
| download | emacs-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.texi | 5 | ||||
| -rw-r--r-- | etc/NEWS | 5 | ||||
| -rw-r--r-- | lisp/cus-edit.el | 289 |
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 | |||
| 1515 | valid filename, either @file{.dir-locals.el} or | 1515 | valid 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 | ||
| 1519 | There'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 |
| @@ -961,6 +961,11 @@ For links in 'webjump-sites' without an explicit URI scheme, it was | |||
| 961 | previously assumed that they should be prefixed with "http://". Such | 961 | previously assumed that they should be prefixed with "http://". Such |
| 962 | URIs are now prefixed with "https://" instead. | 962 | URIs are now prefixed with "https://" instead. |
| 963 | 963 | ||
| 964 | ** Customize | ||
| 965 | +++ | ||
| 966 | *** New command customize-dirlocals | ||
| 967 | This 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. | ||
| 5746 | See `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 | |||
| 5771 | Possible values are nil, a symbol (standing for a major mode) or a directory | ||
| 5772 | name." | ||
| 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 | |||
| 5787 | The 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 | |||
| 5841 | Sets the value of its parent, a cons widget, in order to create an | ||
| 5842 | appropriate widget to edit the value of WIDGET. | ||
| 5843 | |||
| 5844 | Moves 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 | |||
| 5902 | If at least an option doesn't validate, signals an error and moves point | ||
| 5903 | to 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 | |||
| 5943 | With optional argument FILENAME non-nil, customize the `.dir-locals.el' file | ||
| 5944 | that 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 | " | ||
| 5964 | To select another file, edit the above field and hit RET. | ||
| 5965 | |||
| 5966 | After you enter a user option name under the symbol field, | ||
| 5967 | be sure to press \\`RET' or \\`TAB', so that the field that holds the | ||
| 5968 | value changes to an appropriate field for the option. | ||
| 5969 | |||
| 5970 | Type \\`C-x C-s' when you've finished editing it, to save the | ||
| 5971 | settings 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 |