If I type M-x customize in Emacs I am taken to customize-group for the Emacs group. At the top of the screen is a search entry dialogue box where I would normally search for a given string. Is there a way of saving searches typed in this dialogue box?
1 Answer
The following Elisp code adapts the history mechanism for text fields of elgrep-menu to Custom-menu.
The code is self-contained. You can copy-paste it into your init file.
After installing and running the code the search field of customization buffers has a history.
If you are in the text field for the search you can move backwards in history with M-up and forwards in history with M-down.
(require 'subr-x) ;; for `when-let'
(defun my-Custom-widget-update-hist (widget)
"Update the :prompt-history variable of WIDGET.
Return the value of WIDGET."
(when-let ((ret (widget-value widget))
(hist-var (widget-get widget :prompt-history))
(hist-length (or (get hist-var 'history-length) history-length)))
(unless (string-equal ret (car-safe (symbol-value hist-var)))
(set hist-var (cons ret (symbol-value hist-var)))
(when (> (length (symbol-value hist-var)) hist-length)
(setf (nthcdr hist-length (symbol-value hist-var)) nil)))
ret))
(defun my-Custom-widget-hist-action (widget)
"Add `my-Custom-widget-update-hist' to the :action of WIDGET."
(let ((action (widget-get widget :action)))
(widget-put widget :action
`(lambda (wid &optional event)
(my-Custom-widget-update-hist wid)
(funcall ,action wid event)))))
(defvar my-Custom-menu-search-history nil)
(require 'wid-edit) ;; for `widget-field-keymap'
(defvar my-Custom-menu-hist-map (let ((map (copy-keymap widget-field-keymap)))
(define-key map (kbd "<M-up>") #'my-Custom-menu-hist-up)
(define-key map (kbd "ESC <up>") #'my-Custom-menu-hist-up)
(define-key map (kbd "<M-down>") #'my-Custom-menu-hist-down)
(define-key map (kbd "ESC <down>") #'my-Custom-menu-hist-down)
map)
"Widget menu used for text widgets with history.
Binds M-up and M-down to one step in history up and down, respectively.")
(defvar my-Custom-menu-hist-pos nil
"Current position in text widget history.
Used in `my-Custom-menu-hist-up' and `my-Custom-menu-hist-down'.")
(defvar my-Custom-menu-hist-list nil)
(defun my-Custom-menu-hist-move (dir)
"Move in :prompt-history of widget at point in direction DIR which can be -1 or +1."
(when-let ((wid (widget-at))
(histvar (widget-get wid :prompt-history)))
(unless my-Custom-menu-hist-list (setq my-Custom-menu-hist-list (cons (widget-value wid) (symbol-value histvar))))
(unless (memq last-command '(my-Custom-menu-hist-up my-Custom-menu-hist-down))
(setq my-Custom-menu-hist-list (cons (widget-value wid) (symbol-value histvar)))
(setq my-Custom-menu-hist-pos 0))
(let ((start my-Custom-menu-hist-pos))
(while
(progn
(setq my-Custom-menu-hist-pos (mod (+ my-Custom-menu-hist-pos dir) (length my-Custom-menu-hist-list)))
(condition-case nil
(progn
(widget-value-set wid (nth my-Custom-menu-hist-pos my-Custom-menu-hist-list))
nil)
(error (/= my-Custom-menu-hist-pos start))))))))
(defun my-Custom-menu-hist-up ()
"Choose next item in :prompt-history of widget at point."
(interactive)
(my-Custom-menu-hist-move 1))
(defun my-Custom-menu-hist-down ()
"Choose next item in :prompt-history of widget at point."
(interactive)
(my-Custom-menu-hist-move -1))
(defmacro my-Custom-find-widget (widget type &rest conditions)
"Find widget of type TYPE, assign it to WIDGET and test CONDITIONS.
WIDGET and TYPE are unquoted symbols and CONDITIONS is a list of conditions.
The WIDGET symbol can be used in the CONDITIONS.
TYPE identifies the widget type."
(declare (debug (symbolp symbolp body)) (indent 2))
(let ((pos (make-symbol "pos"))
(found (make-symbol "found")))
`(let ((,pos (goto-char (point-min)))
,found
,widget)
(while (and
(progn
(widget-forward 1)
(<= ,pos (point)))
(setq ,pos (point)
,widget (widget-at))
(null (and (eq (widget-type ,widget) (quote ,type))
,@conditions
(setq ,found ,widget)))))
,found)))
(defun my-Custom-menu-search-history-ad (&rest _)
"Install search history for cus-edit.
Put this function into `Custom-mode-hook'."
(let ((search-field-widget (my-Custom-find-widget widget editable-field
(when-let (help-echo (widget-get widget :help-echo))
(string-match "\\`Search for custom items\\.$" help-echo)))))
(when search-field-widget
(my-Custom-widget-hist-action search-field-widget)
(let* ((search-button-widget (my-Custom-find-widget widget push-button
(when-let (tag (widget-get widget :tag))
(string-match "\\` Search \\'" tag))))
(b (widget-field-start search-field-widget))
(search-field-widget (prog1 (widget-copy search-field-widget) (widget-delete search-field-widget))))
(when search-button-widget
(widget-put search-button-widget :parent search-field-widget)
(widget-put search-button-widget :action #'widget-parent-action))
(widget-put search-field-widget :prompt-history 'my-Custom-menu-search-history)
(widget-put search-field-widget :keymap my-Custom-menu-hist-map)
(goto-char b)
(widget-apply search-field-widget :create) ;; delegating the propertizing of the input field to the widget
(widget-setup)
(goto-char (point-min))))))
(advice-add 'custom-buffer-create-internal :after #'my-Custom-menu-search-history-ad)
The code is tested with:
GNU Emacs 26.1 (build 2, i686-pc-linux-gnu, GTK+ Version 3.22.30) of 2018-05-29
-
This is awesome. You have helped me add a feature to Emacs I have wanted for a long time. To make the search history persist between sessions I have added "my-Custom-menu-search-history" to the list desktop-globals-to-save.edman– edman2019-03-15 06:11:49 +00:00Commented Mar 15, 2019 at 6:11
-
1@devcom The direct implementation in the Emacs libraries (e.g.,
cus-edit.el) would be easier and nicer. I have sent a feature request to[email protected].Tobias– Tobias2019-03-15 14:30:26 +00:00Commented Mar 15, 2019 at 14:30 -
-
This code worked in an earlier version of Emacs but I cannot seem to get it to work any longer in Emacs 27.edman– edman2021-01-04 13:36:04 +00:00Commented Jan 4, 2021 at 13:36
custom-buffer-create-internalrequires some Elisp magic.