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.