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?

  • It is possible but adding it to the current version of custom-buffer-create-internal requires some Elisp magic. – Tobias Mar 14 '19 at 13:16

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)))

(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)
  "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))
            (setq my-Custom-menu-hist-pos (mod (+ my-Custom-menu-hist-pos dir) (length my-Custom-menu-hist-list)))
            (condition-case nil
                  (widget-value-set wid (nth my-Custom-menu-hist-pos my-Custom-menu-hist-list))
              (error (/= my-Custom-menu-hist-pos start))))))))

(defun my-Custom-menu-hist-up ()
  "Choose next item in :prompt-history of widget at point."
  (my-Custom-menu-hist-move 1))

(defun my-Custom-menu-hist-down ()
  "Choose next item in :prompt-history of widget at point."
  (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)))
       (while (and
         (widget-forward 1)
         (<= ,pos (point)))
           (setq ,pos (point)
             ,widget (widget-at))
           (null (and (eq (widget-type ,widget) (quote ,type))
              (setq ,found ,widget)))))

(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
    (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. – Raoul Comninos Mar 15 '19 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 bug-gnu-emacs@gnu.org. – Tobias Mar 15 '19 at 14:30
  • Any news on this? – Raoul Comninos Jan 4 at 13:15
  • This code worked in an earlier version of Emacs but I cannot seem to get it to work any longer in Emacs 27. – Raoul Comninos Jan 4 at 13:36

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.