Coding Emacs's M-x in Lisp

« previous entry | next entry »
Sep. 8th, 2009 | 02:24 am

I have always wondered what terms stipulate whether an Emacs internal routine is written in Emacs Lisp or if it exists as a built-in and part of Emacs's C source code. I have grown accustomed to how most core Emacs commands and functions, no matter how small, are written in Emacs Lisp. There is no doubt a lot of Emacs that has to be written in C.

I recently wanted to change how `M-x' works. The command behind it is called `execute-extended-command'. It is written in C. This is disappointing for my desires to tinker, but not all together surprising either. It is a pretty central piece of the Emacs infrastructure.

To extend the `M-x' command, I didn't want to jump into having to write C code. Although, it was a chance to practice compiling Emacs, and potentially debugging my extension to the command. I wanted to avoid this development cycle, so I tried translating the C code into Emacs Lisp. Given how cleanly written the Emacs sources are, it was not a very difficult task. For extra cuteness, I even carried over the C comments.

Use at your own risk. I would be interested in feedback on how this works for people, but redefining a central command like this could potentially create a lot of problems for your Emacs. With that out of the way, I have used this for a few weeks without a problem.

The block of C code for defining Fexecute_extended_command hasn't changed significantly over the last ten years, so the Emacs Lisp version I present below should work as a replacement with the last two major releases of Emacs -- versions 22 and 23.

;; 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)
                           (= (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)

The result is half as many lines of code compared to the C. But the real benefit is being able to mess around with it. If this version that mimics the standard behavior is proved to be stable, I may try and see if a few customizations to these new bits will hold up as well.

I currently put the above code in a file called m-x.el, then put the file in my load-path and add the following to my .emacs file.

  (load "m-x")

Link | Leave a comment | Share

Comments {10}

(no subject)

from: yrk
date: Sep. 8th, 2009 10:09 am (UTC)
Link

This is really interesting. Did I miss this on the emacs-devel mailing list?

Reply | Thread

Aaron S. Hawley

re: emacs-devel

from: aaronhawley
date: Sep. 8th, 2009 02:48 pm (UTC)
Link

No, but with any success with this I will probably throw this back at the Emacs developers.

Reply | Parent | Thread

cool!

from: anonymous
date: Sep. 10th, 2009 10:09 am (UTC)
Link

this is way cool, please submit it to emacs-devel!

Reply | Thread

iNoah

(no subject)

from: inoah
date: Sep. 10th, 2009 05:15 pm (UTC)
Link

[referred to this post from a friend.]

I think you should submit this to emacs-devel. There's no performance-related reason for this to be in C; maybe in 1985 but not anymore, anyway. And while I've had plenty of success modifying execute-extended-command by putting defadvice wrappers on it, there are limits to what you can accomplish with that.

It should be easier to maintain as a lisp function.

(The one thing I'd have to go look at is whether that command is essential to have as a primitive for dumping emacs. But I doubt it.)

Reply | Thread

Aaron S. Hawley

re: defadvice

from: aaronhawley
date: Sep. 11th, 2009 05:30 am (UTC)
Link

I'm surprised you had such luck with defadvice since there isn't much available to advise.

Reply | Parent | Thread

iNoah

(no subject)

from: inoah
date: Sep. 10th, 2009 05:16 pm (UTC)
Link

I'll try doing a build from the current cvs snapshots with this function added to simple.el and the C primitive removed, and get back to you.


Edit: Looking good. Emacs dumps, everything works. Even my old defadvices. I say ship it.


Edited at 2009-09-10 08:15 pm (UTC)

Reply | Thread

Aaron S. Hawley

thanks

from: aaronhawley
date: Sep. 11th, 2009 05:31 am (UTC)
Link

Thanks for looking into the build situation, I didn't predict there would be a problem, but I wasn't about to check. Also, good idea to test your old advised functions. Thanks, again.

Reply | Parent | Thread

(no subject)

from: yrk
date: Sep. 10th, 2009 05:45 pm (UTC)
Link

An analogous effort was apparently being described here about windowing code.

In Hebrew we call this a "me'ga'ma"; when things seem to be all moving in a certain direction together but without someone necessarily orchestrating them all.

Reply | Thread

Aaron S. Hawley

Re: windowing code and me'ga'ma

from: aaronhawley
date: Sep. 11th, 2009 05:28 am (UTC)
Link

Thanks for the coincidental pointer. I am always pleased how well Stefan holds the party line.

From <http://article.gmane.org/gmane.emacs.devel/113630>
> I support this goal, as I'm generally in favor of moving code away
> from C.
>
> Stefan

Reply | Parent | Thread

Aaron S. Hawley

(no subject)

from: aaronhawley
date: Jan. 7th, 2013 08:22 pm (UTC)
Link

A Lisp version of the command, execute-extended-command, was added to Emacs in May 2012 with Stefan Monnier's help. It will be released with Emacs 24.3.

Reply | Thread