3

I use an asterisk in my function names, so that to execute one of my own functions, I would begin by typing M-x *. Now I would like to save myself a keystroke and achieve the same by M-X. How do I do that?

I tried defining a keyboard macro with F3 M-x * F4, but that, for some reason, didn't work.

Toothrot
  • 3,204
  • 1
  • 12
  • 30

2 Answers2

3

The key sequence M-x calls execute-extended-command. That command uses read-extended-command for reading from minibuffer.

In read-extended-command the c-function completing-read does the main job. But, the INITIAL-INPUT argument for that command is hard-coded to nil.

We should not temporarily advise completing-read since that function can be compiled-in. So we need to define our own my-read-extended-command which is a copy of completing-read up to the INITIAL-INPUT argument "*" instead of nil.

But, we can temporarily advice read-extended-command within execute-extended-command.

The following code shows how. It defines a command my-execute-extended-command that temporarily advices read-extended-command with the help of cl-setf and calls execute-extended-command interactively within the body of this cl-setf.

(require 'cl-lib)

(defun my-read-extended-command ()
  "Read command name to invoke in `my-execute-extended-command'.
This is almost a copy of `read-extended-command'.
The only difference is ?* as initial input to M-x."
  (minibuffer-with-setup-hook
      (lambda ()
        (add-hook 'post-self-insert-hook
                  (lambda ()
                    (setq execute-extended-command--last-typed
                              (minibuffer-contents)))
                  nil 'local)
    (set (make-local-variable 'minibuffer-default-add-function)
         (lambda ()
           ;; Get a command name at point in the original buffer
           ;; to propose it after M-n.
           (with-current-buffer (window-buffer (minibuffer-selected-window))
         (and (commandp (function-called-at-point))
              (format "%S" (function-called-at-point)))))))
    ;; Read a string, completing from and restricting to the set of
    ;; all defined commands.  Don't provide any initial input.
    ;; Save the command read on the extended-command history list.
    (completing-read
     (concat (cond
          ((eq current-prefix-arg '-) "- ")
          ((and (consp current-prefix-arg)
            (eq (car current-prefix-arg) 4)) "C-u ")
          ((and (consp current-prefix-arg)
            (integerp (car current-prefix-arg)))
           (format "%d " (car current-prefix-arg)))
          ((integerp current-prefix-arg)
           (format "%d " current-prefix-arg)))
         ;; This isn't strictly correct if `execute-extended-command'
         ;; is bound to anything else (e.g. [menu]).
         ;; It could use (key-description (this-single-command-keys)),
         ;; but actually a prompt other than "M-x" would be confusing,
         ;; because "M-x" is a well-known prompt to read a command
         ;; and it serves as a shorthand for "Extended command: ".
         "M-x ")
     (lambda (string pred action)
       (let ((pred
              (if (memq action '(nil t))
                  ;; Exclude obsolete commands from completions.
                  (lambda (sym)
                    (and (funcall pred sym)
                         (or (equal string (symbol-name sym))
                             (not (get sym 'byte-obsolete-info)))))
                pred)))
         (complete-with-action action obarray string pred)))
     #'commandp t
     "*"
     'extended-command-history)))

(defun my-execute-extended-command ()
  "Run `execute-extended-command' interactively.
Thereby, replace `read-extended-command' by `my-read-extended-command', which see."
  (interactive)
  (cl-letf (((symbol-function 'read-extended-command) #'my-read-extended-command))
    (call-interactively #'execute-extended-command)))

(bind-key "M-X" #'my-execute-extended-command)
Tobias
  • 32,569
  • 1
  • 34
  • 75
2

The quick and dirty way is to pretend you pressed *.

(defun execute-my-extended-command (&rest args)
  "Read a command name to call, favoring commands that begin with `*'.

Like `execute-extended-command', but when called interactively, preload a
leading `*' into the minibuffer."
  (interactive)
  (if (interactive-p)
      (progn
        (setq unread-command-events (cons ?* unread-command-events))
        (call-interactively #'execute-extended-command))
    (funcall #'execute-extended-command args)))
(global-set-key [?\M-X] #'execute-my-extended-command)