Skip to main content

Emacs mit dem Elgato Streamdeck(r)

Ich denke ich habe endlich meinen persönlichen Workflow gefunden

In seinem Blog-Beitrag A Draft Workflow for Advanced Project Management Using Org Mode and Org Edna schrieb Karl Voit über seinen Traum von einem Workflow, der es ihm ermöglichen würde, ein komplexes Projektmanagement im Org-mode durchzuführen. Wenn du es nicht gelesen hast, tue dies bitte. Wie immer ist es ein toller Text über gut durchdachter Prozesse, und ich kann die meisten hier nicht wiederholen.

Der größte Teil des Arbeitsablaufs wird bereits vom Org mode abgedeckt, und ein weiterer wichtiger Teil der Behandlung von Abhängigkeiten zwischen den Schritten wird vom Paket org-edna geleistet. Um sein Ziel zu erreichen, benötigte er nur zwei Patches für einige von ihm verwendete Emacs-Packeges.

Und so begann meine Reise

Ich habe noch nie einen längeren Code in Emacs Lisp geschrieben, aber ich dachte, "halt' mal meine Teetasse" und fing an. Um fair zu sein, am Ende hat der Code seinen Job gemacht, aber was noch wichtiger war, ich habe einige mal ernsthaft über meinen eigenen Workflow nachgedacht und festgestellt, dass ich Projekte etwas anders als Karl mache.

Das wurde mir klar, als ich an seinem Garagenbeispiel arbeitete (eine Garage neu streichen). Nachdem der Lack gekauft wurde, müssen das Auto, das Fahrrad und die Regale entfernt werden. Um letzteres zu tun, müssen diese erst ausgeräumt werden. Nun entwickelten sich folgende Gedanken:

  1. Beim Durchsuchen des Codes von org-edna, um die Stelle zu finden, an der ich eine Abhängigkeitsbehandlung einfügen konnte, die gruppierte Abhängigkeiten zulässt, stellte ich fest, dass der Beispielworkflow vollständig flach war und dass diese Abhängigkeiten im Org mode behandelt werden konnten wenn ich nur die Hierarchie als Baum verwende
  2. Danach wurde klar, dass ich nie mehr als höchstens drei Aufgaben als nächste Aufgabe planen würde. Mit GTD wäre die reine Lehre, nur eine aktive nächste Aufgabe pro Kontext zu haben, aber in Wirklichkeit verliere ich den Überblick, wenn sie zu viele werden.
  3. Und die Reihenfolge ist mir wichtig. Was ist, wenn ich 5 Minuten brauche, um das Auto zum nächsten öffentlichen Parkplatz zu bringen, und 3 Tage, um die Regale zu leeren und zu entfernen? Ich müsste die ganzen Tage zum Parkplatz gehen, während ich die Garage ausräume. Also würde ich diese Schritte sowieso in der besten Reihenfolge planen.
  4. Wenn es jetzt wieder eine flache Liste ist, aber mit einer bestimmten Reihenfolge, warum nicht die Eigenschaft ORDERED verwenden?
** proj [1/4] Brille richten                    :Brille:project:
       :PROPERTIES:
       :COOKIE_DATA: todo recursive
       :ORDERED: t
       :TRIGGER: next-sibling todo!(NEXT)
       :END:
*** done Zum Optiker fahren
        CLOSED: [2020-08-22 Sa 15:22]
*** NEXT Warten ob es ein neues Mittelteil gibt
*** todo Bestellen 
*** todo Gläser einsetzen

Wenn wir diese Eigenschaft verwenden und auf "t" setzen, wird erzwungen, dass jede Aufgabe nur ausgeführt werden kann, wenn alle vorherigen Aufgaben abgeschlossen sind. Wenn mir dabei etwas in den Weg kommt, möchte ich zuerst die nächste Aufgabe erledigen. Ich bin immer noch frei, sie nur einen Schritt nach vorne zu bewegen und dann zuerst daran zu arbeiten. Zumindest muss ich manuell zugewiesene Abhängigkeiten nicht neu zuweisen. Ich denke, ohne einen Graphen, muss sowas in der Dependency Hölle enden.

Aber was kommt als nächstes?

Nachdem sichergestellt ist, dass ich so nah wie möglich am Plan arbeite, wie wäre es mit der nächsten Aktion. Wenn ich eine Aufgabe beendet habe, sollte der nächste Task die nächste Aktion sein, an der gearbeitet werden soll. Dafür benutze ich die Hilfe von org-edna. Die TRIGGER-Eigenschaft im obigen Beispiel setzt den ToDo-Status des nächsten Tasks auf NEXT, wenn eine Aufgabe auf einen fertigen Status gesetzt wird. Dies muss als Eigenschaft in jeder Aufgabe festgelegt werden, in der sie benötigt wird, oder im Fall einer einfachen flachen Aufgabenliste kann dies nur im Header erfolgen, indem die Variable org-edna-use-inheritance auf 't' gesetzt wird.

Es gibt also immer eine neue, als NEXT umsetzbare Aufgabe, wenn ich die vorherige abgeschlossen habe. Aber was ist, wenn ich mich entscheide, mit dieser Aufgabe zu warten und das Projekt zum Stillstand zu bringen? Dafür habe ich eine Agenda-Ansicht eingerichtet, die mir alle Projekte zeigt (sie werden durch einen TODO-Status von 'proj' definiert), die keine nächste umsetzbare Aufgabe haben. Diese Projekte werden in der Agenda für feststeckende Projekte angezeigt, die in den Org-Modus integriert ist.

Was wäre ein Projekt ohne eine DEADLINE?

Bisher konnten alle Probleme mit meinem Workflow nur mit dem Org-Modus oder org-edna gelöst werden, ohne den Code zu manipulieren. Aber eines blieb. Wie soll ich SCHEDULED oder DEADLINE Daten setzen, abhängig vom Enddatum der vorherigen Aufgabe? Karls Idee war es, es an die Fertigstellung der Aufgabe oder einer Gruppe von Aufgaben vorher zu binden, aber da ich das Konzept von Aufgaben aufgegeben habe, die durch statische Verknüpfungen explizit miteinander verbunden sind, ist dies auch nicht möglich.

Ich musste es vom empfangenden Ende aus angehen. Immer wenn eine Aufgabe zum ersten Mal auf NEXT gesetzt wird, berechne ich das Datum SCHEDULED und DEADLINE. Ich habe eine Funktion geschrieben, die an den hook org-after-todo-state-change-hook gebunden ist, die nach einer Eigenschaft mit dem Namen PLANNED sucht und deren Wert zur Berechnung eines Datums verwendet.

(add-hook 'org-after-todo-state-change-hook 'gtd/planning-trigger)

Nehmen wir an, ich möchte die Aufgabe 'Geschirrspüler leeren' 3 Stunden nach Abschluss der Aufgabe 'Geschirrspüler laden' ausführen. Also gebe ich ihm einen Eigenschaftswert von + 3h für PLANNED wie folgt:

** proj [0/2] Wash dishes         :dishes:project:
               :PROPERTIES:
               :COOKIE_DATA: todo recursive
               :ORDERED: t
               :TRIGGER: next-sibling todo!(NEXT)
               :END:
*** NEXT Load dishwasher
*** todo Empty dishwasher
               :PROPERTIES:
               :PLANNED:: +3h
               :END:

Nachdem die erste Aufgabe auf done gesetzt wurde, wird die zweite Aufgabe durch : TRIGGER: next-sibling todo! (NEXT) auf NEXT gesetzt. Bei erfolgreicher Änderung in NEXT zieht der Hook meine Funktion und voila:

** proj [1/2] Wash dishes                       :dishes:project:
               :PROPERTIES:
               :COOKIE_DATA: todo recursive
               :ORDERED: t
               :TRIGGER: next-sibling todo!(NEXT)
               :END:
*** done Load dishwasher
        CLOSED: [2020-08-24 Mo 17:18]
*** NEXT Empty dishwasher
        SCHEDULED: <2020-08-24 Mo 20:18>

Der Code dafür ist ziemlich einfach, und ich muss ihn noch auf DEADLINE erweitern und gegen Fehler sichern, aber es funktioniert bisher.

(defun gtd/planning-trigger ()
  "Automatically insert chain-find-next trigger when entry becomes NEXT"
  (when (equal org-state "NEXT")
    (message "das war next")
    (setq planned (car (org-map-entries (lambda () (
      org-entry-get nil  "PLANNED")) "PLANNED<>\"\"" 'tree)))
    (if planned (
      (message "Geplant ist %s" planned)
      (org-entry-put nil "SCHEDULED" planned)
      (org-entry-delete nil "PLANNED")
  ) nil) ))

Abschliessend

Während der letzten zehn Tage habe ich viel über meinen eigenen Workflow gelernt, indem ich versucht habe, den Gedanken eines anderen über seinen eigenen Workflow zu folgen. Genau dafür wurde das Internet gemacht, und es hat mich glücklich gemacht. Und ich habe noch viel mehr gelernt.

  • Elisp in den Griff bekommen
  • gelernt, wie man in Emacs debuggt
  • und vor allem habe ich gelernt, wie sehr ich meinem Workflow vertraue, wenn er eine vollständige Agenda erstellt.

Aber das wird eine andere Geschichte sein …  

2021-05-27