Skip to main content

Projekt Medusa: Denote

Ab mit dem Kopf

Wer schon mal durch meine Projekte geschaut hat wird feststellen, dass es dort ein Projekt Medusa gibt, dass ich für mich angelegt habe. In Emacs gibt es immer wieder Funktionen die ich nur sehr selten brauche, und dann komme ich oft nicht direkt auf den notwendigen Befehl. Da ist es dann ganz hilfreich eine Hydra für diese Situation zu haben, und das Projekt unter dem ich all diese Hydras zusammenfasse, heißt bei mir Medusa. Neulich wollte ich für Denote eine Hydra anlegen, und habe festgestellt, dass es da ein paar Probleme gibt.

Generell halte ich es mit den Hydras so: Ich habe zwei Daumentasten über die ich auf meiner Tastatur die Hydras ansteuere. Auf dem linken Daumen liegt eine eher generelle Hydra die für alle Funktionen gedacht ist die nicht nur in einem speziellen Mode gebraucht werden. Hier kann man es sich etwa so vorstellen, dass dort die Funktion liegen die man normalerweise mit dem Präfix CTRL-x erreicht

Auf dem rechten Daumen liegt eine Art Kontextmenü, also die Hydra, die zu dem jeweiligen Mode gehört, in dem ich mich gerade befinde. Das kann man sich etwa so vorstellen, als wären dort alle Funktionen die über CRTL-c als Prefix zu erreichen sind. Dass ich das ganze wie ein Kontextmenü behandle, sieht man auch daran, dass ich diese Hydras gleichzeitig auf der rechten Maustaste liegen habe.

Modelose Kunst

Und hier beginnt auch schon das Problem. Denote ist eine Anwendung, die viele Befehle zur Verfügung stellt, um lose geschriebene Notizen miteinander zu verknüpfen. Da diese Notizen jedoch in allen möglichen Formaten geschrieben sein können, gilt natürlich immer der jeweilige Major Mode des Formats. Ich schreibe dieses Blog z.B im Org-Mode das heißt der Major Mode ist in dem Moment nicht Denote sondern Org-Mode. Das Package Major Mode Hydra, funktioniert deswegen schon mal nicht

Denote ist aber auch kein Minor Mode, den man beliebig bei allen möglichen Dokumenten ein- oder ausschalten kann. Ein Dokument ist genau dann ein Denote Dokument wenn es sich in einem bestimmten Verzeichnis (Silo) befindet und einer Namenskonvention folgt. Und da Denote kein Minor Mode ist, hat es auch keine eigene Keymap. Aber Emacs wäre nicht Emacs, wenn man da nicht etwas machen könnte.

Wir bauen uns einen Minor-Mode

Es wäre gut, wenn Denote ein Minor Mode hätte, der immer dann aktiv wäre, wenn sich das aktive Dokument in einem Denote-Silo befindet. Da ein Silo immer auch ein Verzeichnis ist, hat Emacs ein Werkzeug an Bord: Verzeichnisspezifische Variablen

Das Handbuch sagt

Der übliche Weg, verzeichnisspezifische Variablen zu definieren, besteht darin, eine Datei mit dem Namen .dir-locals.el in einem Verzeichnis zu platzieren. Wenn Emacs eine Datei in diesem Verzeichnis oder einem seiner Unterverzeichnisse öffnet, werden die verzeichnisspezifischen Variablen, die in .dir-locals.el angegeben sind, angewendet, als ob sie als dateispezifische Variablen für diese Datei definiert worden wären. Emacs sucht nach .dir-locals.el, beginnend im Verzeichnis der geöffneten Datei, und bewegt sich dabei aufwärts im Verzeichnisbaum.

Meine Datei .dir-locals.el liegt im obersten Verzeichnis meines Silos, und sieht so aus

((nil . ((denote-mode . t))))

Diese Zeile sorgt dafür, das bei allen Dateien in diesem Verzeichnis der denote-mode eingeschaltet wird. Das ist ein Minor Mode, den Denote nicht von Hause aus mitbringt, sondern den wir zuvor selbst erzeugen müssen. Das passiert im entsprechenden Teil meiner Emacs Konfiguration.

(define-minor-mode denote-mode
  "Denote is a simple note-taking tool for Emacs."
  :lighter " Note"
  :keymap (let ((map
                 (make-sparse-keymap)))
            (define-key map (kbd "C-l") 'denote-link-or-create)
            (define-key map (kbd "C-n") 'denote-link-after-creating)
            (define-key map (kbd "<f6>")(lambda () (interactive) (find-file "~/wiki")))
            map)) 

:lighter gibt an, was in die Mode-Line geschrieben wrd, wenn der Minor-Mode aktiv ist, und :keymap gibt an welche Keymap genutzt werden soll. In diesem Fall wird über make-sparse-keymap eine leere Keymap erzeugt, und mit den gängigsten Befehlen die ich so brauche gefüllt. Vermutlich könnte ich diese Erstbefüllung auch weglassen, da ich die endgültigen Keybindings sowieso später mache, aber ich habe kein Beispiel gefunden in dem eine leere Keymap angelegt wurde.

Die eigentliche Hydra

Ich bestücke meine frisch erzeugte Keymap dann noch mit den einzig entscheidenden Tasten. <menu> ist der KeyCode den meine rechte Daumentaste sendet.

(define-key denote-mode-map (kbd "<menu>") #'medusa/denote/body)
(define-key denote-mode-map (kbd "<mouse-3>") #'medusa/denote/body)

Wenn ich dann in einem Denote Silo die rechte Maustaste click erscheint folgende Hydra

nil

Erzeugt wird das Ganze durch folgenden Code. ich nutze dabei die etwas kürzere und hübschere Syntax die mir das Package Pretty Hydra zur Verfügung stellt.

(pretty-hydra-define medusa/denote
  (:color blue :quit-key "<escape>" :title "Denote")
  ("Create" (
    ("n" denote "_n_ew note" )
    ("t" denote-type "other _t_ype" )
    ("d" denote-date  "other _d_ate" )
    ("s" denote-subdirectory  "other _s_ubdir" )
    ("T" denote-template  "with _T_emplate" )
    ("S" denote-signature  "with _S_ignature" ))
   "Link" (
    ("l" denote-link-or-create "_l_ink" )
    ("L" denote-link-or-create-with-command "_L_ink with command" )
    ("h" denote-org-extras-link-to-heading  "specific _h_eader" )
    ("r" denote-add-links "by _r_egexp" )
    ("d" denote-add-links "by _d_ired" )
    ("b" denote-backlinks "_b_acklinks" ))
   "Rename" (
    ("RF" denote-rename-file "Rename File")
    ("FT" denote-change-file-type-and-front-matter  "only FileType")
    ("UF" denote-rename-file-using-front-matter "use Frontmatter"))
   "Dyn. Block" (
    ("DL" denote-org-extras-dblock-insert-links "dyn. Links" )
    ("DB" denote-org-extras-dblock-insert-backlinks "dyn. Backlinks" ))
   "Convert links" (
    ("CF" denote-org-extras-convert-links-to-file-type "to File Type" )
    ("CD" denote-org-extras-convert-links-to-denote-type "to Denote Type" ))
  "Other" (
     ("?" (info "denote") "Help")
     ("<menu>" major-mode-hydra "Major Mode Hydra"))))
2024-05-03