Witam na mojej prywatnej stronie internetowej!
[If this is all Polish to you, click here: English]
Uwaga: z oczywistych powodów nie mogę zagwarantować swojej nieomylności, choć staram się o zgodność tego, co piszę, z Prawdą. Jest również oczywiste, że nie gwarantuję takiej zgodności w przypadku komentarzy. Umieszczenie linku do strony spoza niniejszego serwisu nie musi oznaczać, że podzielam poglądy autora tej strony, a jedynie, że uważam ją za wartościową z takich czy innych powodów.
Marcin ‘mbork’ Borkowski
One of the common complaints of Emacs users is “I defined this cool little command to make my life easier and then I forgot to use it”. Well, I found one way to help with that. Every Emacs function, variable and macro somehow knows in what file it was defined, so in theory it shouldn’t be too difficult to know which functions were defined, say, in the user’s init file. Then, I could create a special version of execute-extended-command
(usually bound to M-x
) which could only run commands defined in the user’s init file.
It turns out that the practice is slightly more complex. One complication is that some users employ Org mode to load things on startup. For example, I have this in my init.el
:
(org-babel-load-file "~/.emacs.d/init-file.org")
This means that my commands are not really defined in ~/.emacs.d/init.el
, but in ~/.emacs.d/init-file.el
instead. This means that I can’t just use user-init-file
to get the name of the file to look for “my” commands in. Also, it was not obvious for me at first where to look for the filename where a function was defined. I even asked about it on the Emacs mailing list, and after a few hours I got the answer I needed: the load-history
variable. (Interestingly, I could have found this variable myself. I looked into Emacs sources and I managed to find the symbol-file
function. Had I checked it in the Elisp reference, I would probably notice that load-history
is described in the same section. That is a good lesson for the future – when looking for a function, variable or macro I need, I can check the reference’s sections corresponding to related functions, variables or macros.)
Anyway, once I know about load-history
, it is very easy to get the list of all commands defined in a given file.
(defcustom my-command-file user-init-file "File to look for commands in `execute-my-command'.") (defun my-commands () "A list of all commands defined in `my-command-file'." (seq-reduce (lambda (acc elt) (if (and (consp elt) (eq (car elt) 'defun) (commandp (cdr elt))) (cons (cdr elt) acc) acc)) (alist-get my-command-file load-history nil nil #'string=) ()))
(After I wrote this, I learned about the file-loadhist-lookup
function, which might simplify the above code a tiny bit, but I decided it’s not worth it to change it.)
Like I said above, I keep my commands in a separate place, so I can do this instead.
(setq my-command-file (expand-file-name "init-file.el" user-emacs-directory))
Since load-history
contains absolute filenames, I decided to use expand-file-name
and user-emacs-directory
so that I can just give Emacs the name of “my command file” and not bother with typing (or copying) the absolute path to it; your mileage may vary.
Now, to run a command but with autocompletion restricted to “my commands” only it is enough to temporarily bind read-extended-command-predicate
.
(defun execute-my-command (prefix-arg) "Execute a command found defined `my-command-file'." (interactive "P") (let* ((my-commands (my-commands)) (read-extended-command-predicate (lambda (command buffer) (memq command my-commands)))) (execute-extended-command prefix-arg))) (global-set-key (kbd "s-X") #'execute-my-command)
I also bind my-commands
to the result of evaluating (my-commands)
(neatly using the fact that Elisp is a Lisp-2) so that the list of “my commands” is only created once per using execute-my-command
. Finally, I bind it to s-X
(that is, super + shift + X
) and that’s it.
A bit ironically, the only remaining issue is to remember to use execute-my-command
instead of the usual execute-extended-command
(M-x
)… Still, it’s way easier to remember one command than over a hundred of them (I indeed have that many commands defined in my init file!).
As usual, Emacs turned out to be a really great environment for people like me who like to tinker with their configs and the ways they use their computer. Not that I’m surprised!