Aaron S. Hawley (aaronhawley) wrote,
Aaron S. Hawley
aaronhawley

Extending M-x in Emacs

Over 6 months ago, I revealed an Emacs Lisp version of `M-x'. I was pleased to have a few Emacs users who greeted a Lisp replacement for this core primitive with excitement.

What's the benefit of having a Lisp version? Well, it's easier to change than the C version. The syntax is easier and there is no compilation step.

Here's a modification I made to the original. It's a small change, but makes for a good example. It introduces a different representation of the prefix argument in the Minibuffer.

Typically, hitting `C-u' before `M-x' will show "C-u M-x" in the Minibuffer. However, if you issue more than one `C-u', then it just becomes a numeric argument and becomes "16 M-x", "64 M-x", "256 M-x" and so on. The small patch inserted below will show "C-u C-u M-x", "C-u C-u C-u M-x", "C-u C-u C-u C-u M-x" and so on, respectively. There's a good argument for the original behavior, since the powers of 4 are not that memorable to everyone. Letting Emacs handle the arithmetic and show it is helpful. However, visual feedback for what you're typing is a good interface design rule as well. As you can tell I'm agnostic, but like how this small change only takes a small bit of change to the code.

--- m-x.el	2009/08/25 00:03:17	1.1
+++ m-x.el	2009/08/25 00:07:12	1.1.1.1
@@ -22,8 +22,11 @@
 	       (cond ((eq prefixarg '-)
 		      "- ")
 		     ((and (consp prefixarg)
-			   (= (car prefixarg) 4))
-		      "C-u ")
+			   (zerop (% (car prefixarg) 4)))
+		      (apply 'concat
+			     (make-list (truncate
+					 (log (car prefixarg) 4))
+					"C-u ")))
 		     ((and (consp prefixarg)
 			   (integerp (car prefixarg)))
 		      (format "%d " (car prefixarg)))

Emacs knows the difference between a prefix argument with `C-u' and a numeric argument with `M-4' or `C-4'. So, `M-1 M-6 M-x' does not show "C-u C-u M-x" in the Minibuffer.

Here's the complete version of the code.

;; Based on Fexecute_extended_command in keyboard.c of Emacs.
;; Aaron S. Hawley <aaron.s.hawley(at)gmail.com> 2009-08-24
(defun execute-extended-command (prefixarg)
  "Read function name, then read its arguments and call it.

To pass a numeric argument to the command you are invoking with, specify
the numeric argument to this command.

Noninteractively, the argument PREFIXARG is the prefix argument to
give to the command you invoke, if it asks for an argument."
  (interactive "P")
  ;; The call to completing-read wil start and cancel the hourglass,
  ;; but if the hourglass was already scheduled, this means that no
  ;; hourglass will be shown for the actual M-x command itself.
  ;; So we restart it if it is already scheduled.  Note that checking
  ;; hourglass_shown_p is not enough,  normally the hourglass is not shown,
  ;; just scheduled to be shown.
  (let* ((hstarted (and (symbolp window-system)
                        (eq void-text-area-pointer 'hourglass)))
         (saved-keys (this-command-keys-vector)) ;; ?\M-x
         (buf (concat 
               (cond ((eq prefixarg '-)
                      "- ")
                     ((and (consp prefixarg)
                           (zerop (% (car prefixarg) 4)))
                      (apply 'concat
                             (make-list (truncate
                                         (log (car prefixarg) 4))
                                        "C-u ")))
                     ((and (consp prefixarg)
                           (integerp (car prefixarg)))
                      (format "%d " (car prefixarg)))
                     ((integerp prefixarg)
                      (format "%d " prefixarg))
                     (t ""))
               "M-x "))
         (function (completing-read buf obarray 'commandp t nil
                                    'extended-command-history)))
    (if hstarted (setq void-text-area-pointer 'hourglass))
    (if (and (stringp function)
             (= (length function) 0))
        (error "No command name given")
      ;; Set this_command_keys to the concatenation of saved-keys and
      ;; function, followed by a RET.
      (setq saved-keys (vconcat saved-keys
                                function
                                [return]))
      (setq function (intern function)))
    (setq prefix-arg prefixarg)
    (setq this-command function)
    (command-execute function 'record saved-keys)
    ;; If enabled, show which key runs this command.
    (if (and (not (null suggest-key-bindings))
             (null executing-kbd-macro))
        ;; If the command has a key binding, print it now.
        (let ((bindings (where-is-internal function
                                           overriding-local-map t))
              (waited))
          ;; But first wait, and skip the message if there is input.
          (when (and (not (null bindings))
                     (not (and (vectorp bindings)
                               (eq (aref bindings 0)
                                   'mouse-movement))))
              ;; If this command displayed something in the echo area;
              ;; wait a few seconds, then display our suggestion message.
              (if (null (current-message))
                  (setq waited (sit-for 0))
                (if (numberp suggest-key-bindings)
                    (setq waited (sit-for suggest-key-bindings))
                  (setq waited (sit-for 2))))
              (when (and (not (null waited))
                         (not (consp unread-command-events)))
                (with-temp-message (current-message)
                  (let ((binding (key-description bindings)))
                    (message "You can run the command `%s' with %s"
                             function binding)
                    (if (numberp suggest-key-bindings)
                        (setq waited (sit-for suggest-key-bindings))
                      (setq waited (sit-for 2)))))))))))

(provide 'm-x)

Consider putting the above in a file called m-x.el, then put the file in you load-path and add the following to your .emacs file.

(load "m-x")

In the next installment, I will show how to change `M-x' so that it notices a function name at point and offers it as the default command.

Tags: emacs, free software, programming
Subscribe

  • User liberation: New video from the FSF

    from fsf.org community blog The last 45 seconds is pretty cool. There's a build of Gstreamer, interspersed with screenshots of Gnome,…

  • Big Emacs reference card updated

    With the release of Emacs 24.3 last month and the big changes at EmacsWiki, I've posted an updated version of the giant Emacs reference card. It…

  • M-x in Emacs 24.3 is now in Lisp

    It didn't make the NEWS file for Emacs 24.3, but Emacs now ships with an ` M-x' (` execute-extended-command') that is written in Lisp. It is no…

  • Post a new comment

    Error

    default userpic

    Your reply will be screened

    Your IP address will be recorded 

    When you submit the form an invisible reCAPTCHA check will be performed.
    You must follow the Privacy Policy and Google Terms of use.
  • 4 comments