1

I've been happy to replace smex with fido-mode in emacs 28, as fido-mode generally has much nicer behavior. However, one thing that I miss from smex is that smex would remember which strings correspond to which commands and persist them, so that the suggested commands over time would correspond to the commands I actually use.

As an example, with fido-mode, when I type M-x repl, it always shows me the options

coffee-repl
message-reply
replace-regexp
replace-string
uce-reply-to-uce
replace-rectangle            (string-rectangle)
gnus-button-reply
message-wide-reply
ethio-replace-space
replace-buffer-contents
replace-buffer-in-windows

But I am basically always looking for replace-string. With smex, after the first time I would chose replace-string it would always suggest this first when I did M-x repl. But fido seems to have no such memory, even within the same emacs session.

Drew
  • 75,699
  • 9
  • 109
  • 225
asmeurer
  • 1,552
  • 12
  • 30

1 Answers1

2

The following code resorts the completion results with usage counts.

(require 'subr-x)
(require 'cl-lib)
(defvar my-fido-command-completions-alist nil
  "Mapping commands to an alist of successful COMPLETIONS.
COMPLETIONS maps strings to the number of times these
strings were chosen.")

(defvar my-fido-this-command nil
  "Preserves `this-command' before the minibuffer is entered.
The execution of commands in the minibuffer changes `this-command'.")

(defun my-fido-mode-setup-function ()
  "Let `fido-mode' use `my-fido-this-command'."
  (if fido-mode
      (add-hook 'minibuffer-setup-hook #'my-fido-minibuffer-setup-function)
    (remove-hook 'minibuffer-setup-hook #'my-fido-minibuffer-setup-function)))

(add-hook 'fido-mode-hook #'my-fido-mode-setup-function)

(defun my-fido-minibuffer-setup-function ()
  "Preserve `this-command' for `my-fido-command-completions-alist'.
Save `this-command' in `my-fido-this-command' before any command is
executed in the minibuffer.
That is the command for which we run completion in the minibuffer."
  (setq my-fido-this-command this-command))

(defun my-fido-save-choice-on-ret ()
  "Increase the sorting weight for the chosen completion.
The weights are stored in `my-fido-command-completions-alist'."
  (when-let (((commandp my-fido-this-command))
         (chosen (car completion-all-sorted-completions))
         ((stringp chosen)))
    (setq chosen (substring-no-properties chosen)) ;; strip non-needed char properties
    (cl-incf (alist-get chosen (alist-get my-fido-this-command my-fido-command-completions-alist) 0 nil 'string-equal))))

(advice-add 'icomplete-fido-ret :before #'my-fido-save-choice-on-ret)

(defun my-fido-sorted-completions (completions)
  "Resort COMPLETIONS with weights from `my-fido-command-completions-alist'."
  (when (and fido-mode (listp completions) (stringp (car completions)))
    (let ((weights (alist-get my-fido-this-command my-fido-command-completions-alist))
      (last-cdr (cdr (last completions)))
      (completions (cl-copy-list completions)))
      (setcdr (last completions) nil)
      (setq completions
        (sort
         completions
         (lambda (first second)
           (let ((first-weight (alist-get first weights 0 nil #'string-equal))
             (second-weight (alist-get second weights 0 nil #'string-equal)))
         (> first-weight second-weight)))
         ))
      (setcdr (last completions) last-cdr)))
  completions)

(advice-add 'icomplete--sorted-completions :filter-return #'my-fido-sorted-completions)

If you want to save this state use

(require 'savehist)
(customize-set-variable 'savehist-additional-variables (append savehist-additional-variables (list 'my-fido-command-completions-alist)))

Tested with

GNU Emacs 28.1 (build 1, x86_64-pc-linux-gnu, GTK+ Version 3.24.20, cairo version 1.16.0) of 2022-05-31

by pasting the Elisp stuff into the init file, re-starting Emacs and calling M-x fido-mode RET M-x fido-vertical-mode RET... .

Tobias
  • 32,569
  • 1
  • 34
  • 75
  • It looks like this makes it persist the default order of commands, which is nice, but it doesn't do anything about making it remember which inputs correspond to which commands (e.g., no matter how many times I choose `replace-string` for `"repl"` it always presents the command in the order I showed above). – asmeurer Dec 02 '22 at 21:44