Skip to main content

Keymacs, a program to generate Emacs keybindings

For sure, I’m heading straight to Emacs hell for this.

Apparently, there are a lot of Emacs purists in the world who, over the years, have learned to type such complex key sequences as C-x r t M-. C-c C-p fluently. I'm not one of them. And when I tried to figure out how to customize it for myself, the web forums were full of comments saying it wasn't a good idea.

Essentially, for the following reasons:

  1. Portability: If you heavily customize your Emacs key bindings, you may have trouble using Emacs on another system where your customizations are not available. This could be problematic if you frequently switch between different computers or if you need to use Emacs on a remote server.
  2. Learning curve: Emacs already has a steep learning curve for beginners, and heavily customizing the key bindings can make it even more difficult for others to understand your configuration. This could be an issue if you collaborate with others or need to share your configuration with someone else.
  3. Conflicts: If you're not careful, heavily customized key bindings can lead to conflicts between different packages or modes in Emacs. This can result in unexpected behavior and make it more difficult to diagnose and fix problems.
  4. Dependency on customizations: If you become too reliant on your individual key bindings, you may find it difficult to use Emacs without them. This could be an issue if you need to work on a system where you can't use your custom configuration.

However, I never have to share my Emacs configuration with other people, or use Emacs anywhere else than on my two computers. So points 1 and 4 are already out of the way. As for the learning curve, I'm not planning to come up with even more complicated key combinations. I just want to be able to continue working with what has been burned into my memory for decades. So point 2 is also taken care of.

That leaves point 3, and it's true: If you're not careful, heavily customized key bindings can lead to conflicts between different packages or modes in Emacs.

I've avoided this so far by keeping track of the customizations I've made in nice tables (thanks to Org-Mode). This approach is of course quite prone to errors. The tables always have to be consistent with the keybindings in dozens of places in the config. It takes a lot of discipline, and it becomes difficult to just try something out.

It got a bit better when I started to consolidate all the keybindings in a single file. This file is loaded at the end of the init.el, and keybindings are not made anywhere else. This worked for a long time. No more tables. The truth is in the code. Unfortunately, it wasn't that easy to search the code for potential conflicts. You can't just change the sorting in the code from "Keymap Name" to "Used Key" like you could with the tables.

So I sat down and wrote a tool in Python where I can manage my desired keybindings in Org tables again, and the tool automatically generates the desired keybindings from that. It's called

Keymacs

The program is quite simple. It goes through a file (default: keymap.org in the current directory), collects all the table rows, and assumes that these are descriptions of keybindings. Header rows and separator rows are ignored, as well as almost all other surrounding text. So you can also describe what you had in mind for the individual bindings in the file.

There are two slightly different tables

The explicit table

Key Mode Ctrl Super Meta Shift Function Remark
c treemacs-mode         'treemacs-copy-file  
c treemacs-node-visit         'treemacs-visit-node-close-treemacs  
c treemacs-toggle         'treemacs-indicate-top-scroll-mode  
d treemacs-copy         'treemacs-paste-dir-at-point-to-minibuffer  
l treemacs-mode         treemacs-copy-map  
p treemacs-copy         'treemacs-copy-project-path-at-point  
r treemacs-copy         'treemacs-copy-relative-path-at-point  
n treemacs-mode X X     'treemacs-create-dir  
n treemacs-mode X       'treemacs-create-file  

I've picked Treemacs as an example here, which shows a few things quite well. Treemacs has the peculiarity that I don't have to make any inputs in the window. So there's no reason not to just use letters without constantly having my little finger on the Ctrl key. First, the first 3 lines:

Key Mode Ctrl Super Meta Shift Function Remark
c treemacs-mode         'treemacs-copy-file  
c treemacs-node-visit         'treemacs-visit-node-close-treemacs  
c treemacs-toggle         'treemacs-indicate-top-scroll-mode  

There are 3 different places where I can simply press c, but they are not in conflict with each other because they are in different keymaps. To get the name of the keymap, the program simply appends -map to the mode.

But wait, there's no treemacs-toggle mode? That's right, but there is a keymap for it and a prefix that it's bound to. I'll show that with the treemacs-copy-map:

Key Mode Ctrl Super Meta Shift Function Remark
l treemacs-mode         treemacs-copy-map  
d treemacs-copy         'treemacs-paste-dir-at-point-to-minibuffer  
p treemacs-copy         'treemacs-copy-project-path-at-point  
r treemacs-copy         'treemacs-copy-relative-path-at-point  
a treemacs-copy         'treemacs-copy-absolute-path-at-point  

This map is originally called treemacs-copy-map even though the original function in it just copies the current file (and I've mapped that to a shorter c). All the others just put various links in the clipboard, so I've chosen l as the key to get into the treemacs-copy-map, and the following key then selects what kind of link I want (absolute, relative or to the project root?) l a gives me the link as an absolute path.

And of course, I can still use Ctrl combinations if I want to. For example, many programs use Ctrl-n to create a new file, a new object, or something else new. Why should I ignore that? So:

Key Mode Ctrl Super Meta Shift Function Remark
n treemacs-mode X       'treemacs-create-file  
n treemacs-mode X X     'treemacs-create-dir  

So I can create a new file with Ctrl-n and a new directory with Ctrl-Super-n.

However, the common Ctrl combinations are of course useful in many different modes, and you have to be a little careful that the keybindings don't interfere with each other, especially when minor-modes come into play. For that, you'd better use:

The implicit table

At first glance, the table looks the same, but the Key column on the left is missing.

Mode Ctrl Super Meta Shift Function Remark
Global X       'org-insert-link-global  
org-mode X       'org-insert-link  
org-mode X X     'org-super-links-insert-link  
org-mode X     X 'org-super-links-link  

But how does keymacs know which key to bind to? I mentioned at the beginning that keymacs ignores header rows and separator rows, as well as almost all other surrounding text. Now here's the reason for the "almost", and for that we need to look at the Org-mode:

** N New
 | Mode       | Ctrl | Super | Meta | Shift | Function                 | Remark |
 |------------+------+-------+------+-------+--------------------------+--------|
 | Global     | X    |       |      |       | 'find-file               |        |
 | org-mode   | X    |       |      |       | 'find-file               |        |
 | dired-mode | X    |       |      |       | 'dired-create-empty-file |        |
 | dired-mode | X    | X     |      |       | 'dired-create-directory  |        |

Keymacs reads the first word from all Org header rows (here 'N') and uses it for the subsequent implicit tables. A new header sets a different key, e.g.

** F9 Treemacs - a tree layout file explorer for Emacs
 | Mode   | Ctrl | Super | Meta | Shift | Function                | Remark |
 |--------+------+-------+------+-------+-------------------------+--------|
 | Global |      |       |      |       | 'treemacs-select-window |        |
 | Global |      |       |      | X     | 'treemacs-find-file     |        |
 | Global | X    |       |      |       | 'treemacs-find-tag      |        |
 |        |      |       |      |       |                         |        |

Except for the first word, keymacs doesn't care about anything else. Incidentally, you can also see here that the global-map is filled with "Global" (with a capital G). The order of the columns must be correct, the headers are only for the reader and are ignored.

The program

I originally wrote this whole thing to solve my own problem, and later also to test the entire development process using Hatch. That's why the whole thing is also available for (test) PyPi for download. Since I want to internalize this whole Python workflow, I will continue to work on this tool, even though it already solves all my problems. But the code is ugly and so far only has a "happy path" without error handling.

The source code is here

If you just want to install it, I recommend using pipx. Then it's as simple as:

pipx install -i https://test.pypi.org/simple/ keymacs

The program only reads the keymap.org file and then writes a keymap.el that you can include at the end of your init.el

(load (concat user-emacs-directory "config.el"))
;;(load (concat user-emacs-directory "sanekeys.el"))
(load (concat user-emacs-directory "medusa.el"))
(org-babel-load-file (concat user-emacs-directory "elgato.org"))
(org-babel-load-file (concat user-emacs-directory "skeletons.org"))
(load (concat user-emacs-directory "keymap.el"))

(setq custom-file (concat user-emacs-directory "custom.el"))
;; (load (concat user-emacs-directory "custom.el"))
2024-04-23