Skip to main content

Keymacs, ein Programm um Emacs Keybindings zu erzeugen

Dafür komme ich mit Sicherheit in die Emacs-Hölle

Offensichtlich gibt es ein Menge Emacs-Puristen in der Welt, die im Laufe der Jahre gelernt haben so komplizerte Tastenfolgen wie C-x r t M-. C-c C-p fliessend zu tippen Ich gehöre nicht dazu. Und als ich versucht habe heraus zu finden wie ich das für mich anpassen kann, waren die Webforen voll von Kommentaren, dass das keine gute Idee sei.

Im wesentlichen aus folgenden Gründen:

  1. Portabilität: Wenn man seine Emacs-Tastenbelegung stark anpasst, könntest man Schwierigkeiten haben, Emacs auf einem anderen System zu verwenden, auf dem seine Anpassungen nicht verfügbar sind. Das könnte problematisch sein, wenn man häufig zwischen verschiedenen Rechnern wechselt oder wenn man Emacs auf einem entfernten Server verwenden muss.
  2. Lernkurve: Emacs hat bereits eine steile Lernkurve für Anfänger, und wenn man die Tastenbelegung stark anpasst, kann das die Sache für andere noch schwieriger machen, seine Konfiguration zu verstehen. Das könnte ein Problem sein, wenn man mit anderen zusammenarbeitet oder wenn man seine Konfiguration mit jemand anderem teilen muss.
  3. Konflikte: Wenn man nicht vorsichtig ist, können stark angepasste Tastenbelegungen zu Konflikten zwischen verschiedenen Paketen oder Modi in Emacs führen. Dies kann zu unerwartetem Verhalten führen und es schwieriger machen, Probleme zu diagnostizieren und zu beheben.
  4. Abhängigkeit von Anpassungen: Wenn man zu sehr auf seine individuellen Tastenbelegungen angewiesen ist, könnte man feststellen, dass es schwierig ist, Emacs ohne sie zu verwenden. Das könnte ein Problem sein, wenn man einmal auf einem System arbeiten muss, auf dem man seine individuelle Konfiguration nicht nutzen kann.

Ich muss allerdings nie meine Emacs Konfiguration mit anden Menschen teilen, oder Emacs woanders benutzen, als auf meinen beiden Rechnern. Daher fallen die Punkte 1 und 4 schon mal weg. Was die Lernkurve angeht, habe ich ja nicht vor mir noch kompliziertere Tastenkombinationen auszudenken. Ich möchte einfach mit dem weiterarbeiten können, das sich seit Jahrzehnten in mein Gedächtnis gebrannt hat. Punkt 2 hat sich damit auch erledigt.

Bleibt noch Punkt 3, und es stimmt: Wenn man nicht vorsichtig ist, können stark angepasste Tastenbelegungen zu Konflikten zwischen verschiedenen Paketen oder Modi in Emacs führen.

Ich bin dem bisher aus dem Weg gegangen, indem ich in hübschen Tabellen (Org-Mode sei Dank) Buch darüber geführt habe, welche Anpassung ich vorgenommen habe, um zu erkennen wo es evtl. zu Konflikten kommen kann. Dieses Vorgehen ist natürlich recht anfällig für Fehler. Die Tabellen müssen immer konsistent sein zu den Keybindings an dutzenden von Stellen in der Config. Da braucht man schon sehr viel Disziplin, und es wird schwierig, mal eben etwas auszuprobieren.

Etwas besser wurde es, als ich begonnen habe, alle Keybindings in einer Datei zusammenzufassen. Diese Datei wird am Ende der init.el geladen, und nigrgendwo anders werden Keybindings gemacht. Damit hat es lange funktioniert. Keine Tabellen mehr. Im Code steckt die Wahrheit. Leider war es nicht so einfach im Code nach potentiellen Konflikten zu suchen. Man kann nicht mal eben die Sortierung im Code von "Keymap Name" auf "verwendete Taste" ändern. Da waren Tabellen schon besser.

Also habe ich mich hingesetzt, und mir ein Tool in Python geschrieben, bei dem ich meine gewünschten Keybindings wieder in Org-Tabellen verwalten kann, und das Tool erzeugt mir daraus automatisch die gewünschten Keybindings. Es heisst

Keymacs

Das Programm ist ganz simpel. Es geht über eine Datei (default: keymap.org im aktuellen Verzeichnis), sucht sich alle Tabellenzeilen zusammen, und geht davon aus, dass es sich dann dabei um Beschreibungen von Keybindings handelt. Dabei werden Headerzeilen und Trennzeilen ignoriert, sowie fast aller anderer umgebender Text auch. Man kann in der Datei also auch beschreiben, was man sich bei den einzelnen Bindings gedacht hat.

Es gibt zwei leicht unterschiedliche Tabellen

Die explizite Tabelle

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  

Ich habe hier mal mit Treemacs ein Beispiel herausgesucht, and dem sich einige Dinge gut zeigen lassen. Treemacs hat die Besonderheit, dass ich im Fenster keine Eingaben machen musss. Es gibt also keinen Grund nicht einfach Buchstaben zu benutzen, ohne den kleinen Finger ständig auf der Ctrl Taste zu haben. Zunächst mal die ersten 3 Zeilen

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  

Es gibt 3 verschiedene Stellen. an denen ich einfach c drücken kann, die jedoch nicht im Konflikt miteinander sind, weil sie sich in verschiedenen Keymaps befinden. Um den namen der keymap zu erhalten, hängt das Programm einfach -map an den Mode.

Aber moment mal, es gibt doch keinen treemacs-toggle Mode? Stimmt, aber es gibt eine Keymap dazu und ein Prefix, an das diese gebunden ist. Ich zeige das mal and der 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  

Diese Map heist originellerweise treemacs-copy-map obwohl ursprünglich nur eine Funktion darin die aktuelle Datei kopiert (und die habe ich mir kürzer auf c gelegt. Alle anderen legen mir nur diverse Links ins Clipboard, daher habe ich als Taste auch l gewählt um in die treemacs-copy-map zu gelangen, dia nachfolgende Taste wählt dann welch Art Link ich haben möchte (absolut, relativ oder doch zum Project-Root?) l a gibt mir also den Link als absoluten Pfad.

Und natürlich kann ich auch, wenn ich will, trotzdem Ctrl Kombinationen verwenden. Zum Beispiel verwenden viele Programme Ctrl-n um eine Neue Datei, ein neues Object, oder sonst etwas neues zu erzeugen. Warum sollte ich das ignorieren? Also:

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

So kann ich mit Ctrl-n eine neue Datei anlegen und mit Ctrl-Super-n auch ein neues Verzeichnis.

Gerade die gängigen Ctrl Kombinationen sind aber natürlich in vielen verschiedenen Modi sinnvoll, und man muss ein wenig aufpassen, das sich die Keybindings nicht gegenseitig stören, Vor allem wenn minor-modes ins Spiel kommen. Dazu benutzt man dann besser:

Die implizite Tabelle

Auf Anhieb sieht die Tabelle gleich aus, es fehlt jedoch die Key Spalte links

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  

Doch woher weiss keymacs nun an welche Taste es binden soll? Ich hatte zu Beginn geschrieben, dass keymacs Headerzeilen und Trennzeilen ignoriert, sowie fast allen anderen umgebenden Text auch. Nun hier ist der Grund für das "fast", und duzu müssen wir in den Org-mode schauen

** 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 liest nämlich von allen Org Header Zeilen das erste Wort (hier 'N') und verwendet es für die darauf folgenden impliziten Tabellen. Ein neuer Header setzt wieder eine andere Taste z.B.

** 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      |        |
 |        |      |       |      |       |                         |        |

Ausser für das erste Wort interessiert sich keymacs für nichts. Nebenbei sieht man hier auch, das die global-map mit Global gefüllt wird (mit großem G). Bei den Spalten muss die Reihenfolge passen, die Überschriften sind nur für den Leser und werden ignoriert.

Das Programm

Geschrieben habe ich das ganze ursprünglich um mein Problem zu lösen, und später dann auch um den kompletten Enwicklungsprozess mittels Hatch zu testen. Daher gibt es das ganze auch auf (Test-)PyPi zum Download. Da ich diesen ganzen Python Workflow gerne verinnerlichen möchte, werde ich auch weiter an diesem Tool arbeiten, auch wenn es bisher schon alle meine Probleme löst. Aber der Code ist hässlich und hat bisher nur einen "happy Path" ohne Error Handling.

Der Sourcecode liegt hier

Wenn man es nur installieren möchte empfehle ich pipx zu nutzen. Dann geht das einfach so:

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

Das Programm liest die keymap.org Datei nur, und schreibt dann eine keymap.el die man dann am Ende seiner init.el einbindet

(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