Aktualisiert: 2022-12-30 Fri 09:39

Emacs-Konfiguration
Eine persönliche GNU-Emacs-Konfiguration

Diese Konfiguration verwende ich sowohl unter Apple OS X (privat) als auch unter Microsoft Windows (beruflich). Es werden nur die wirklich von mir genutzten Funktionen implementiert und es wird versucht, soweit möglich, zusätzliche Pakete zu vermeiden, insbesondere um unnötige Komplexität und Risiken durch Drittpakete zu reduzieren.

Risikohinweis: Dies ist meine Emacs-Konfigurationsdatei und daher dauerhaft „under construction“. Ich gebe mir Mühe, regelmäßig aufzuräumen, aber: Wo gehobelt wird, da fallen Späne… Und natürlich: Verwendung auf eigene Gefahr!

Inhaltsverzeichnis

Meine Anwendungsbereiche für den GNU Emacs

Ich nutze den Emacs im Wesentlichen für die Aufgaben- und Notizverwaltung sowie für die Zeiterfassung. Die Notizverwaltung umfasst dabei auch Notizen im Sinne eines CRM sowie mein Wissensmanagement, welches vor allem aus einer Sammlung von PDF-Dateien (Fachaufsätze, Verwaltungsschreiben, juristische Kommentierungen etc.) und meinen Notizen zu diesen PDF-Dateien besteht.

Grundkonfiguration des Emacs

Einbindung der Emacs-Konfiguration

Diese Datei ist in der Syntax des org-mode geschrieben und enthält Emacs-Lisp-Code zur Konfiguration des Emacs in sog. Source Code Blocks. Der Code in diesen Source Code Blocks wird beim Start des Emacs extrahiert und ausgeführt. Folgender Eintrag in der Startdatei des Emacs stößt den Prozess an:

Unter Apple OS X in /Users/hendrik/.emacs:

(package-initialize)
(org-babel-load-file "/Users/hendrik/Documents/emacs/config.org")

Unter Windows in c:/Users/suenkler/AppData/Roaming/.emacs:

(package-initialize)
(org-babel-load-file "c:/Users/suenkler/Documents/emacs/config.org")

Paketverwaltung

Zusätzliche Pakete sollen soweit möglich aus den folgenden Repositories geladen werden:

(require 'package)
(setq package-enable-at-startup nil)

(setq package-archives '(("melpa"       . "https://melpa.org/packages/")
                         ("gnuelpa"     . "https://elpa.gnu.org/packages/")
                         ("nongnuelpa"  . "https://elpa.nongnu.org/nongnu/")))
(package-initialize)

;; =use-package= von John Wiegley
;; Bootstrap `use-package'
(unless (package-installed-p 'use-package)
  (package-refresh-contents)
  (package-install 'use-package))

Betriebssystemspezifische Konfiguration ermöglichen

Um die Konfiguration auf den verschiedenen Betriebssystemen zu erleichtern, definiere ich Variablen, die dann, wenn der Emacs unter dem jeweiligen Betriebssystem läuft, den Wert true oder false annehmen.

(defun system-type-is-darwin ()
   (interactive)
   "Return true if system is darwin-based (Mac OS X)"
   (string-equal system-type "darwin")
)

(defun system-type-is-windows ()
   (interactive)
   "Return true if system is Microsoft Windows"
   (string-equal system-type "windows-nt")
)

Nun kann ich in Konfigurationen soetwas machen:

(if (system-type-is-darwin)
  (message "Gott sei Dank laufe ich nicht unter Windows, sondern unter Apple OS X!")
)

Der (betriebssystemspezifische) Wurzelordner meiner Emacs-Datei wird unten in dieser Konfiguration häufig verwendet. Damit ich nicht an jeder Stelle zwischen den Betriebssystemen differenzieren muss, setze ich hier die Variable my-org-path, die abhängig vom Betriebssystem den passenden Wert annimmt.

(defvar my-org-path "")

(if (system-type-is-darwin)
    (setq my-org-path "/Users/hendrik/Documents/org/")
  )

(if (system-type-is-windows)
    (setq my-org-path "C:/Users/suenkler/Documents/org/")
  )

Unten kann ich nun dort, wo der Pfad meines Org-Verzeichnisses benötigt wird, folgendes machen:

(message (concat my-org-path "tasks.org"))

Ich kann aber auch reletiv verweisen, wie beispielsweise so:

(message (concat my-org-path "../emacs/config.org"))

UTF-8-Kodierung sicherstellen

(prefer-coding-system 'utf-8)
(set-default-coding-systems 'utf-8)
(set-terminal-coding-system 'utf-8)
(set-keyboard-coding-system 'utf-8)

;; Treat clipboard input as UTF-8 string first; compound text next, etc.
;;(setq x-select-request-type '(UTF8_STRING COMPOUND_TEXT TEXT STRING))

Visuelles

(tool-bar-mode -1)
(scroll-bar-mode -1)
(menu-bar-mode -1)
(setq visible-bell t)

;; Fenstertitel
(setq-default frame-title-format '("In Emacs We Trust"))

Ein dunkles Theme:

(load-theme 'wombat)

(if (system-type-is-windows)
  (set-face-attribute 'default nil :family "Consolas" :height 120)
)

Keine nervigen Backup-Dateien

(setq make-backup-files nil) ;; keine backup~-Dateien
(setq auto-save-default nil) ;; keine #autosave#-Dateien

Zeilennummern

Hier bestünde die Möglichkeit, Zeilennummern global zu aktivieren. Ich habe sie global deaktiviert und aktiviere Zeilennummern für den aktuellen Buffer mit M-x linum-mode.

(global-display-line-numbers-mode 0)

Soft Wrap

Text soll nich nach x Zeichen hart umgebrochen werden, sondern lange Zeilen sollen nur visuell umgebrochen werden:

(global-visual-line-mode 1)

Einige Komfort-Funktionen

;; ausgewählten Text bei Eingabe löschen
(delete-selection-mode 1) 

;; keine "yes-or-no"-Fragen - "y-or-n" reicht aus
(defalias 'yes-or-no-p 'y-or-n-p)

;; Zusammengehörende Klammern hervorheben
(show-paren-mode 1)

;; Text zwischen den Klammern nicht hervorheben
(setq show-paren-style 'parenthesis)

;; Aktuelle Spaltennummer in der mode line anzeigen
(column-number-mode nil)

Tastaturbelegung unter Mac OS X anpassen

Unter Apple OS X verwende ich Cmd als Meta-Taste:

(if (system-type-is-darwin)
  (setq mac-command-modifier 'meta)
  (setq mac-option-modifier nil)
  (setq ns-alternate-modifier 'none) ;; Meta weg vom Option Key, damit z.B. @ eingegeben werden kann
)

Completion Framework

Completion Frameworks sind ein Thema für sich. Der Vanilla Emacs ist auf jeden Fall etwas sehr rudimentät, wenn es um die Navigation im Minibuffer geht. Ich habe verschiedene Lösungen ausprobiert. Die ganz großen Lösungen wie insbesondere helm-mode empfand ich als too much für meine Zwecke; es wurde an zu vielen Stellen eingegriffen und der Emacs fühlte sich nicht mehr wirklich wie mein Emacs an. Außerdem möchte ich Abhängigkeiten möglichst reduzieren. Zielführend finde ich ansich, eher minimalistisch an dieses Thema heranzugehen und mit Emacs-Bordmitteln zu starten – und dann zu prüfen, was mir konkret noch fehlt.

Im Emacs ist der ido-mode bereits integriert.

;;(ido-mode t)
;;(setq ido-everywhere t)
;;(setq ido-enable-flex-matching t) ;; Fuzzy Matching aktivieren
;;(setq ido-enable-last-directory-history nil)
;;(setq org-completion-use-ido t)

Was mir dabei fehlte: Eingabehilfe beim Hinzufügen von Attachments (org-attach). Vor Org-Mode Version 9 gab es offenbar die Variable org-completion-use-ido; leider wurde diese Funktion entfernt. Damit scheint es nach meinen Recherchen keine einfache Möglichkeit zu geben, ido-Eingabehilfen für Org-Mode-Dialoge wie org-attach zu aktivieren.

helm-mode ist zwar wie erwähnt ein sehr umfangreiches Drittpaket. Doch man kann es möglicherweise auch „portioniert“ einsetzen. Hier beginne mit einer minimalen Konfiguration (derzeit wieder deaktiviert):

;;(use-package helm
;;  :ensure t
;;  :config (helm-mode 1)
;;  :bind (
;;      ("M-x" . helm-M-x)
;;    ("C-x C-f" . helm-find-files)
;;  )
;;)

Mit der Aktivierung des helm-mode ist Fuzzy Matching für org-attach aktiv.

Eine etwas leichtgewichtigere Alternative zu helm-mode ist ivy-mode, den ich aktuell verwende:

(use-package ivy
  :diminish (ivy-mode . "")
  :init (ivy-mode 1) ; globally at startup
  :config
  (setq ivy-use-virtual-buffers t)
  (setq ivy-height 15)
  (setq ivy-count-format "%d/%d "))

;;(setq ivy-re-builders-alist
;;      '((t . ivy--regex-fuzzy)))
;; fuzzy-matching statt Stanadard (=ivy--regex-plus=)

(setq enable-recursive-minibuffers t)

Counsel:

counsel bietet einige hilfreiche Funktionen wie counsel-M-x and counsel-find-file. Diese Funktionen nutzen die ivy-read-Funktion.

(use-package counsel
  :ensure t
  :bind* ; load when pressed
  (("M-x"     . counsel-M-x)
;;   ("C-s"     . swiper)
   ("C-x C-f" . counsel-find-file)
;;   ("C-x C-r" . counsel-recentf)  ; search for recently edited
;;   ("C-c g"   . counsel-git)      ; search for files in git repo
;;   ("C-c j"   . counsel-git-grep) ; search for regexp in git repo
;;   ("C-c /"   . counsel-ag)       ; Use ag for regexp
;;   ("C-x l"   . counsel-locate)
;;   ("C-x C-f" . counsel-find-file)
   ("<f1> f"  . counsel-describe-function)
   ("<f1> v"  . counsel-describe-variable)
;;   ("<f1> l"  . counsel-find-library)
;;   ("<f2> i"  . counsel-info-lookup-symbol)
;;   ("<f2> u"  . counsel-unicode-char)
   ("C-c C-r" . ivy-resume)))     ; Resume last Ivy-based completion

Swiper zum Suchen

swiper ersetzt die Standardsuche des Emacs. Die Bedienung von swiper ist etwas gewöhnungsbedürftig, aber dann m.E. eine feine Sache.

(use-package swiper
  :ensure t
  :bind ("C-s" . swiper))

Mode Line

(set-face-attribute 'mode-line nil
                    :background "#1a1a1a"
                    :foreground "#595959"
                    :box '(:line-width 6 :color "#353644")
                    :overline nil
                    :underline nil)

(set-face-attribute 'mode-line-inactive nil
                    :background "#1a1a1a"
                    :foreground "#595959"
                    :box '(:line-width 6 :color "#565063")
                    :overline nil
                    :underline nil)

Farbe Minibuffer

Hintergrundfarbe bei aktiviertem Minibuffer:

(defun minibuffer-bg ()
  (set (make-local-variable 'face-remapping-alist)
       '((default :background "#0000ff"))))
;;(add-hook 'minibuffer-setup-hook 'minibuffer-bg)

Fenstergröße und Margins

Fenstergröße beim Start des Emacs definieren:

(add-to-list 'default-frame-alist '(height . 50))
(add-to-list 'default-frame-alist '(width . 150))

Textblock im Fenster zentrieren

Margins setzen, so dass der Buffer zentiert wird:

(defun my-resize-margins ()
  (let ((margin-size (/ (- (frame-width) 100) 2)))
    (if (> margin-size 0)
        (set-window-margins nil margin-size margin-size))))

;;(add-hook 'window-configuration-change-hook #'my-resize-margins)

Keyboard Shortcuts

Deutsche Anführungszeichen mit M-v und M-b eingeben:

(global-set-key (kbd "M-v") (lambda ()
                  (interactive)
                  (insert "„")))

(global-set-key (kbd "M-b") (lambda ()
                  (interactive)
                  (insert "“")))

Aktuelle Zeile bei bestimmten Aktionen hervorheben

Ein Tip von Karl Voit:

(defun my-pulse-line (&rest _)
      "Pulse the current line."
      (pulse-momentary-highlight-one-line (point)))

(dolist (command '(recenter-top-bottom other-window ace-window my-scroll-down-half my-scroll-up-half))
  (advice-add command :after #'my-pulse-line))

Verschiedenes

Schicke Boxen um Texte zeichnen:

;;(use-package boxquote
;;  :ensure t)

Org-mode

Grundkonfiguration

Einbindung

(use-package org
  :mode (("\\.\\(org\\|org_archive\\)$" . org-mode))
  :ensure org
  :config 
  (progn
    (setq org-hide-leading-stars 'hidestars)
    (setq org-return-follows-link t)
    (setq org-drawers (quote ("PROPERTIES" "CLOCKTABLE" "LOGBOOK" "CLOCK")))
    (setq org-completion-use-ido t)
    (setq org-startup-folded t)
    ;; Fast TODO Selection
    (setq org-use-fast-todo-selection t)
    ;; Keinen Splash-Screen
    (setq inhibit-splash-screen t)
    (setq org-log-done 'time)
    (setq org-log-into-drawer t)
    ))

(use-package org-contrib
    :ensure t)

(use-package org-bullets
  :ensure t
  :config
  (add-hook 'org-mode-hook (lambda () (org-bullets-mode 1))))

Den Kalender mit Montag statt Sonntag starten:

(setq calendar-week-start-day 1)

Indentation

Die folgenden Einstellungen waren für mich sehr wichtig, um mich im org-mode wohlzufühlen. Wenn ich Ebenen von Überschriften ändere, soll der Inhalt darunter nicht ebenfalls verrückt werden. Ansonsten entstehen bei Texten links immer unschöne Leerzeichen vor dem Text.

Vgl. zu dem Thema Cleaner Outline View in der Orgmode-Dokumentation.

;; Keine Hard Indentation (also keine Leerzeichen am Anfang der Zeile zur Einrückung einfügen:
(setq org-adapt-indentation nil)

;; In allen Org Buffers den rein visuellen Indentation Mode aktivieren:
(add-hook 'org-mode-hook 'org-indent-mode)

In Kombination mit aktiviertem global-visual-line-mode, also Soft Wrap, finde ich es nun sehr angenehm.

Keyboard Shortcuts

;; Emacs-Konfigurationsdatei auf C-c e
(global-set-key "\C-ce" #'(lambda ()
                           (interactive)
                           (find-file (concat my-org-path "../emacs/emacs-config.org"))))

;; Tasks-Datei auf C-c g
(global-set-key "\C-ct" #'(lambda ()
                           (interactive)
                           (find-file (concat my-org-path "tasks.org"))))

;; Org Store Link
(global-set-key "\C-cl" 'org-store-link)

;; Agenda
(global-set-key "\C-ca" 'org-agenda)
(define-key org-mode-map (kbd "<f12>") #'(lambda (&optional arg) (interactive "P")(org-agenda arg "d")))

;; Org Capture
(define-key global-map "\C-cc" 'org-capture)
(global-set-key (kbd "<f9>") 'org-capture)

Aufgabenverwaltung

Aufgabenstadien

;; Ein "!" bedeutet Zeitstempel
;; Ein "@" bedeutet Notiz
(setq org-todo-keywords
      '((sequence "TODO(t)" "WAITING(w@/!)" "APPT(a)" "PROJ(p)" "NEXT(n)"
                  "DELEGATED(g@/!)" "|" "DONE(d!)" "ZKTO(z)" "CANCELED(c@)")))

;; Farben anpassen
(setq org-todo-keyword-faces
      '(("TODO"  . (:foreground "#b70101" :weight bold))
        ("APPT"  . (:foreground "sienna" :weight bold))
        ("PROJ"  . (:foreground "lightblue" :weight bold))
        ("NEXT"  . (:foreground "yellow" :weight bold))
        ("ZKTO"  . (:foreground "orange" :weight bold))
        ("WAITING"  . (:foreground "orange" :weight bold))
        ("DONE"  . (:foreground "forestgreen" :weight bold))
        ("DELEGATED"  . (:foreground "forestgreen" :weight bold))
        ("CANCELED"  . shadow)))

Statistiken

Es sollen hinter den Tasks ([/]) nicht nur die Anzahl der direkten Children, sondern des gesamten Subtree gezählt werden.

(setq org-hierarchical-todo-statistics nil)

Agenda

Neben den Standardansichten der org-agenda verwende ich lediglich eine benutzerdefinierte Ansicht, mein Dashboard. Darin sehe ich alles, was ich während der Arbeit benötige.

(setq org-agenda-custom-commands
      (quote (("d" "Dashboard"
                      ((todo "NEXT" ((org-agenda-overriding-header "Als nächstes zu tun:")))
                       ;;(tags "flagged" ((org-agenda-overriding-header "Markierte Einträge:")))
                       (todo "WAITING" ((org-agenda-overriding-header "Ich warte auf:")))
                       (todo "ZKTO" ((org-agenda-overriding-header "Zeitkonten:")))
                       (agenda ""))))))

Folgende Org-Dateien sollen für die Agenda ausgewertet werden:

(setq org-agenda-files
      (list
         (concat my-org-path "tasks.org")
         (concat my-org-path "/notes/")
         (concat my-org-path "/people/")))

Nette Icons in der Agenda ermöglichen:

;;(defun fw/agenda-icon-material (name)
;;  "Returns an all-the-icons-material icon"
;;  (list (all-the-icons-material name)))

Etwas Agenda-Kosmetik:

;; Aktuelle Zeile in der Agenda hervorheben
(add-hook 'org-agenda-mode-hook #'(lambda () (hl-line-mode 1 )))

(setq org-agenda-format-date
 "%Y-%m-%d ---------------------------------------------------------------------")

;; Tasks mit Prioriäten unterschiedlich darstellen:
(setq org-agenda-fontify-priorities
   (quote ((65 (:foreground "Red")) (66 (:foreground "Blue")) (67 (:foreground "Darkgreen")))))

(setq org-agenda-date-weekend (quote (:foreground "Yellow" :weight bold)))

(setq org-priority-faces '((?A . (:foreground "red" :weight bold))
                           (?B . (:foreground "yellow"))
                           (?C . (:foreground "green"))))

Weitere Agenda-Einstellungen:

;; Tasks mit Datum in der Agenda ausblenden, wenn sie bereits erledigt sind:
(setq org-agenda-skip-deadline-if-done t)
(setq org-agenda-skip-scheduled-if-done t)

;; Tasks mit Tag 'someday' per default nicht anzeigen
(setq org-agenda-filter-preset '("-someday"))

;; The default number of days displayed in the agenda is set by the variable org-agenda-span.
(setq org-agenda-span 1)

;; Vererbung von Tags
(setq org-use-tag-inheritance t)
(setq org-tags-exclude-from-inheritance '(quote ("review" "flagged")))

;; Diary einbeziehen
(setq org-agenda-include-diary t)

;; Agenda im aktuellen Window aufrufen
(setq org-agenda-window-setup 'current-window)

;;(setq org-agenda-log-mode-items '(closed state))
(setq org-agenda-log-mode-items '(closed clock))

Capture Templates

Vorlage für die schnelle Erfassung neuer Aufgaben und Kontakte:

(if (system-type-is-windows)
  (setq org-capture-templates
      '(("t" "Aufgabe in tasks.org" entry
             (file+headline "c:/Users/suenkler/Documents/org/tasks.org" "Inbox")
         "* TODO %?")
            ("c" "Contacts" entry (file "c:/Users/suenkler/Documents/org/contacts.org")
         "* %(org-contacts-template-name) \n :PROPERTIES:%(org-contacts-template-email)\n:BIRTHDAY: %^{yyyy-mm-dd}\n :END:")))
)

Inline Tasks

org-inlinetask ist eigentlich eine sehr praktische Funktion, allerdings insbesondere beim Export (z.B. in HTML) nach meiner Erfahrung nicht wirklich stabil. Ich versuche es zu vermeiden, org-inlinetask zu verwenden.

(use-package org-inlinetask
  :bind (:map org-mode-map
              ("C-c C-x t" . org-inlinetask-insert-task))
  :after (org)
  :commands (org-inlinetask-insert-task)
  :config (setq org-inlinetask-export t))

Faces

(set-face-attribute 'org-tag nil
  :foreground "dark grey"
  :weight 'bold)

Zeiterfassung

Clocking

org-mode ermöglicht die Erfassung von Zeiten auf Aufgaben – und die umfassende Auswertung.

;; Es soll ein eigener Drawer für die Zeiteinträge verwendet werden:
(setq org-clock-into-drawer "CLOCK")

;; Clock in with F5:
(fset 'my-clock-in "\C-c\C-x\C-i")
(global-set-key (kbd "<f5>") 'my-clock-in)

;; Clock out with F8:
(fset 'my-clock-out "\C-c\C-x\C-o")
(global-set-key (kbd "<f8>") 'my-clock-out)

;; Resume clocking tasks when emacs is restarted
;;(org-clock-persistence-insinuate)

(setq org-clock-history-length 35)

;; Resume clocking task on clock-in if the clock is open
(setq org-clock-in-resume t)

;; Change task state to STARTED when clocking in
;;(setq org-clock-in-switch-to-state "STARTED")

;; Sometimes I change tasks I'm clocking quickly
;; this removes clocked tasks with 0:00 duration
;;(setq org-clock-out-remove-zero-time-clocks t)

;; Don't clock out when moving task to a done state
(setq org-clock-out-when-done nil)

;; Save the running clock and all clock history when exiting Emacs,
;; load it on startup
(setq org-clock-persist t)

;; Disable auto clock resolution
(setq org-clock-auto-clock-resolution nil)

(setq org-global-properties (quote (("Effort_ALL" .
                                     "0:10 0:30 1:00 2:00 3:00 4:00 5:00 6:00 8:00"))))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Agenda Clock

;; Agenda direkt mit dem jeweiligen Clock-Report starten:
(setq org-agenda-start-with-clockreport-mode t)

;; Log Mode nicht beim Start aktivieren (Aktivierung in der Agenda mit "l")
(setq org-agenda-start-with-log-mode nil)

;; Auch die Zeit einer aktuell laufenden Uhr einbeziehen:
;;(setq org-clock-report-include-clocking-task t)

;; Nicht alle in die Agenda einbezogenen Org-Dateien im Clocking Report anzeigen, 
;; sondern nur diejenigen mit tatsächlichen Einträgen.
(setq org-agenda-clockreport-parameter-plist
      (quote (:link t :maxlevel 5 :fileskip0 t :compact t :narrow 80 :formula %)))

(setq org-columns-default-format "%80ITEM(Task) %10Effort(Effort){:} %10CLOCKSUM")

Wenn ich eine Aufgabe mit clocknote getaggt habe, soll beim „Ausclocken“ die Möglichkeit bestehen, eine Beschreibung der erfassten Tätigkeit einzugeben. Dies macht vor allem dann Sinn, wenn es Aufgaben sind die „Allgemeine Verwaltung“, bei denen sich die konkrete Tätigkeit nicht aus der Beschreibung ergibt.

(defun hs/check-for-clock-out-note()
      (interactive)
      (save-excursion
        (org-back-to-heading)
        (let ((tags (org-get-tags)))
          (and tags (message "tags: %s " tags)
               (when (member "clocknote" tags)
                 (org-add-note))))))

(add-hook 'org-clock-out-hook 'hs/check-for-clock-out-note)

Auswertung Clocking (clocktable-by-tag)

Die einfachste und auch von mir zumeist verwendete Auswertungsmöglichkeit ist die Anzeige der erfassten Zeiten in der org-agenda, diese dazu die Einstellungen oben. Manchmal kann es aber ganz hilfreich sein, Auswertungen auf Basis von Tags vornehmen zu können.

Quelle: https://stackoverflow.com/questions/70568361/org-mode-review-clocked-time-by-multiple-tags

(require 'org-clock)

(defun clocktable-by-tag/shift-cell (n)
  (let ((str ""))
    (dotimes (i n)
      (setq str (concat str "| ")))
    str))

(defun clocktable-by-tag/insert-tag (files params)
  (let ((tag (plist-get params :tags))
        (summary-only (plist-get params :summary))
        (total 0))
    ;;(insert "xxxxx")
    (insert "|--\n")
    (insert (format "| %s | *Tag time* |\n" tag))
    (mapcar
     (lambda (file)
       (let ((clock-data (with-current-buffer (find-buffer-visiting file)
                           (org-clock-get-table-data (buffer-name) params))))
         (when (> (nth 1 clock-data) 0)
           (setq total (+ total (nth 1 clock-data)))
           (if (not summary-only)
               (progn
                 (insert (format "| | File *%s* | %s |\n"
                                 (file-name-nondirectory file)
                                 (org-duration-from-minutes (nth 1 clock-data))))
                 (dolist (entry (nth 2 clock-data))
                   (insert (format "| | . %s%s | %s %s |\n"
                                   (org-clocktable-indent-string (nth 0 entry))
                                   (nth 1 entry)
                                   (clocktable-by-tag/shift-cell (nth 0 entry))
                                   (org-duration-from-minutes (nth 4 entry))))))))))
     files)
    (save-excursion
      (re-search-backward "*Tag time*")
      (org-table-next-field)
      (org-table-blank-field)
      (insert (org-duration-from-minutes total))))
  (org-table-align))

(defun org-dblock-write:clocktable-by-tag (params)
  (insert "#+TBLNAME: timetable\n")
  (insert "| Tag | Headline | Time (h) |\n")
  (let ((params (org-combine-plists org-clocktable-defaults params))
        (base-buffer (org-base-buffer (current-buffer)))
              (files (pcase (plist-get params :scope)
                             (`agenda
                              (org-agenda-files t))
                             (`agenda-with-archives
                              (org-add-archive-files (org-agenda-files t)))
                             (`file-with-archives
                              (let ((base-file (buffer-file-name base-buffer)))
                                (and base-file
                                           (org-add-archive-files (list base-file)))))
                             ((or `nil `file)
                              (list (buffer-file-name)))
                             (_ (user-error "Unknown scope: %S" scope))))
        (tags (plist-get params :tags)))
    (mapcar (lambda (tag)
              (clocktable-by-tag/insert-tag files (org-combine-plists params `(:match ,tag :tags ,tag))))
            tags)))

Authoring und Notizen

Export: HTML und DOCX

  • HTML: Grundeinstellungen

    Ich möchte Plain HTML5 exportieren, also ohne CSS und JavaScript.

    ;; HTML5
    (setq html-doctype "html5")
    
    ;; Org’s HTML exporter does not by default enable new block elements
    ;; introduced with the HTML5 standard.
    ;; To enable them, set org-html-html5-fancy to non-nil.
    ;; Or use an 'OPTIONS' line in the file to set 'html5-fancy'. 
    (setq org-html-html5-fancy t)
    
    ;; Kein Standard-CSS und -JavaScript einfügen:
    (setq org-html-head-include-scripts nil)
    (setq org-html-head-include-default-style nil)
    
  • CSS-Datei

    In einer Org-Datei kann mit der folgenden Angabe im Header eine CSS-Datei für die Formatierung des HTML-Exports angegeben werden:

    #+HTML_HEAD: <link rel="stylesheet" type="text/css" href="style.css" />
    

    Die Notizdateien, die ich im deft-mode verwalte, befinden sich zwar in drei unterschiedlichen Verzeichnissen. Allerdings sind diese alle drei auf der gleichen Ebene. Daher definiere ich den folgenden Pfad als Grundeinstellung:

    (setq org-html-head (concat "<link rel=\"stylesheet\" type=\"text/css\" href=\"../../emacs/doc/style.css\" />"))
    
  • HTML: Preamble und Postamble

    Im Folgenden definiere ich eine eigene Kopf- und Fußzeile für meinen HTML-Export. Die Formatierung erfolgt per CSS.

    (setq org-html-preamble "<div id=\"header-container\"><nav><a href=\"https://suenkler.info/emacs/\">⇐ zurück</a></nav>
      <div id=\"updated\">Aktualisiert: %C</div></div>")
    
    (setq org-html-postamble "<footer style=\"line-height: 18px;\">
      <div class=\"copyright-container\">
        <div class=\"cc-badge\">
           <a rel=\"license\" href=\"http://creativecommons.org/licenses/by-sa/4.0/\">
           <img alt=\"Creative Commons Lizenzvertrag\" style=\"border-width:0\"
                src=\"https://i.creativecommons.org/l/by-sa/4.0/88x31.png\" /></a><br />
           Die Inhalte dieser Internetseite sind lizenziert unter einer
           <a rel=\"license\"
           href=\"http://creativecommons.org/licenses/by-sa/4.0/\">Creative Commons<br/>Namensnennung -
           Weitergabe unter gleichen Bedingungen 4.0 International Lizenz</a>.
        </div>
        <div class=\"copyright\">Copyright © 1998-2023 Hendrik Sünkler |
             <a href=\"https://suenkler.info/impressum/\">Impressum</a> |
             <a href=\"https://suenkler.info/datenschutz/\">Datenschutzhinweise</a>
        </div>
      </div>
    </footer>")
    
  • HTML: Syntax Highlighting mit HTMLize

    Source Code Blocks sollen mit Syntax Highlighting versehen werden:

    (use-package htmlize
      :ensure t)
    
  • DOCX: Export mit Pandoc

    Manchmal muss ich Texte, die ich im Emacs geschrieben habe, als Word-Datei weitergeben. Als Emacs-Integration verwende ich ox-pandoc. pandoc muss im Pfad verfügbar sein.

    (use-package ox-pandoc
        :ensure t)
    

    Nun kann ich über den normalen org-export-dispatch (C-c C-e) in verschiedene Formate exportieren, auch in docx.

Publish: Veröffentlichung meiner Emacs-Texte auf suenkler.info

Meine private Internetseite ist mit Python/Django realisiert. Meine Texte zum Emacs exportiere ich allerdings schlicht als HTML aus dem Emacs heraus. Die folgenden Funktionen veröffentlichen die Dateien auf meinem Server. Zur Verfügung gestellt werden die Dateien dann per Nginx. Auf meiner Python/Django-Seite sind sie verlinkt.

(require 'ox-publish)
(setq org-publish-project-alist
      '(
        ("emacs" :components ("emacs-config" "emacs-doc" "emacs-doc-media-and-org-sources" "emacs-config-org-source"))
        ("emacs-config"
         :base-directory "/Users/hendrik/Documents/emacs/"
         :base-extension "org"
         :publishing-directory "/ssh:hendrik@www.suenkler.info:/var/www/emacs/doc/"
         :publishing-function org-html-publish-to-html
         )
        ("emacs-doc"
         :base-directory "/Users/hendrik/Documents/emacs/doc/"
         :base-extension "org"
         :publishing-directory "/ssh:hendrik@www.suenkler.info:/var/www/emacs/doc/"
         :publishing-function org-html-publish-to-html
         )
               ("emacs-doc-media-and-org-sources"
                :base-directory "/Users/hendrik/Documents/emacs/doc/"
                :base-extension "css\\|png\\|org"
                :publishing-directory "/ssh:hendrik@www.suenkler.info:/var/www/emacs/doc/"
                :recursive t
                :publishing-function org-publish-attachment
         )
               ("emacs-config-org-source"
                :base-directory "/Users/hendrik/Documents/emacs/"
                :base-extension "org"
                :publishing-directory "/ssh:hendrik@www.suenkler.info:/var/www/emacs/doc/"
                :recursive nil
                :publishing-function org-publish-attachment
         )
               )
)

;; Switches off use of time-stamps when publishing. I would prefer to publish
;; everything every time
(setq org-publish-use-timestamps-flag nil)

Nun kann das gesamte Projekt mit M-x org-publish-project RET emacs RET veröffentlicht werden.

Fußnoten

Mit C-c C-x f kann in einer Org-Datei eine Fußnote eingefügt werden. Ich möchte, dass Fußnoten inline stehen:

(setq org-footnote-define-inline t)

Bilder in Notizen mit org-download

Ermöglicht mir das Einfügen von Bildern in Org-Notizen:

(use-package org-download
  :ensure t
  :defer t
  :init
  ;; Add handlers for drag-and-drop when Org is loaded.
  (with-eval-after-load 'org
    (org-download-enable)))

Damit die Angabe der Breite in Org Buffern wirksam wird:

(setq org-image-actual-width nil)

Nun kann in Org-Dateien wie folgt die Breite angegeben werden:

#+TITLE: sdaasdasd
#+STARTUP: inlineimages

Das ist ein skaliertes Bild:

#+ATTR_ORG: :width 800
#+ATTR_HTML: :width 100
[[file:Bild.png]]

Auf diese Weise wird das Bild sowohl im Emacs im orgmode-Buffer als auch im HTML-Export skaliert.

(setq org-startup-with-inline-images t)

Werden die Bilder im Buffer nicht angezeigt, kann die Ansicht mit C-c C-x C-v (org-toggle-inline-images) umgeschaltet werden.

Literate Programming mit Babel

Initialisierung und Definition Sprachen

(org-babel-do-load-languages
 'org-babel-load-languages
 '((R . t)
   (shell . t)
   (python . t)
   (emacs-lisp . t)))

;;(setq tramp-default-method "plink") ;; statt ssh Putty verwenden
;;(setq python-shell-interpreter "c:/Users/suenkler/Anaconda3/python.exe")

Unter Windows muss ich Emacs sagen, welches Python er verwenden soll:

(if (system-type-is-windows)
  (setq org-babel-python-command "C:/Users/suenkler/Anaconda3/python.exe")
)

Verhalten

(setq org-edit-src-content-indentation 0)
(setq org-src-tab-acts-natively t)
(setq org-src-preserve-indentation t)

(setq org-confirm-babel-evaluate nil)

Bedienung

Schnelles Einfügen von Source Code Blocks (z.B. "<s TAB"):

(use-package org-tempo)

Graphische Darstellung

(setq org-src-fontify-natively t)

;; Neben Source Code Blocks auch #+BEGIN_QUOTE-Blocks /fontifizieren/.
(setq org-fontify-quote-and-verse-blocks t)

Hintergrundfarbe von Source Code Blocks:

(set-face-attribute 'org-block-begin-line nil
  :background "#404040")

(set-face-attribute 'org-block nil
  :background "Black")

(set-face-attribute 'org-block-end-line nil
  :background "#404040")

Weitere Org-Einstellungen

;; Targets include this file and any file contributing to the agenda - up to 5 levels deep
;;(setq org-refile-targets (quote ((org-agenda-files :maxlevel . 5) (nil :maxlevel . 5))))

;; Targets start with the file name - allows creating level 1 tasks
(setq org-refile-use-outline-path (quote file))

;; Targets complete in steps so we start with filename, TAB shows the next level of targets etc
(setq org-outline-path-complete-in-steps t)

(setq org-return-follows-link t)

Deft (Notizen)

Grundlegende Einrichtung

Deft lade ich nicht aus dem Repo, sondern habe eine lokale Version im Verzeichnis lib gespeichert.

(add-to-list 'load-path (concat my-org-path "../emacs/lib/"))
(require 'deft)

Konfiguration:

(use-package deft
    :ensure nil ;; da lokale Kopie verwendet wird
    :config (setq deft-directory (concat my-org-path "notes/"))
    ;;(setq deft-strip-title-regexp "\\(?:^%+\\|^#\\+TITLE: *\\|^#\\+COMMENT: *\\|^[#* ]+\\|-\\*-[[:alpha:]]+-\\*-\\|^Title:[  ]*\\|#+$\\)")
    (setq deft-text-mode 'org-mode)
    (setq deft-default-extension "org")
    (setq deft-extension "org")
    (setq deft-extensions '("org"))
  
    (setq deft-recursive t)
    (setq deft-auto-save-interval 1.0) ;; auto save

    (setq deft-use-filename-as-title nil)
    (setq deft-use-filter-string-for-filename t)
    (setq deft-file-naming-rules
             '((noslash . "-")
               (nospace . "-")
               (case-fn . downcase)))
)

Mit =deft-file-naming-rules= sage ich, dass Dateinamen, die aus Suchstrings erstellt werden, keine Leerschritte etc. enthalten sollen. Ich möchte aber zudem deutsche Sonderzeichen herausfiltern, um vernünftige Dateinamen zu bekommen. Auch vor allem deshalb, da ich mit Umlauten unter Windows mit Git Probleme bekommen habe. Also folgende Funktion eingehängt:

(defun hs-deft-strip-ue (args)
  "Replace german sonderzeichen."
  (list (replace-regexp-in-string "ü" "ue" (car args)))
  )
(advice-add 'deft-new-file-named :filter-args #'hs-deft-strip-ue)

(defun hs-deft-strip-ue2 (args)
  "Replace german sonderzeichen."
  (list (replace-regexp-in-string "Ü" "Ue" (car args)))
  )
(advice-add 'deft-new-file-named :filter-args #'hs-deft-strip-ue2)

(defun hs-deft-strip-ae (args)
  "Replace german sonderzeichen."
  (list (replace-regexp-in-string "ä" "ae" (car args)))
  )
(advice-add 'deft-new-file-named :filter-args #'hs-deft-strip-ae)

(defun hs-deft-strip-ae2 (args)
  "Replace german sonderzeichen."
  (list (replace-regexp-in-string "Ä" "Ae" (car args)))
  )
(advice-add 'deft-new-file-named :filter-args #'hs-deft-strip-ae2)

(defun hs-deft-strip-oe (args)
  "Replace german sonderzeichen."
  (list (replace-regexp-in-string "ö" "oe" (car args)))
  )
(advice-add 'deft-new-file-named :filter-args #'hs-deft-strip-oe)

(defun hs-deft-strip-oe2 (args)
  "Replace german sonderzeichen."
  (list (replace-regexp-in-string "Ö" "Oe" (car args)))
  )
(advice-add 'deft-new-file-named :filter-args #'hs-deft-strip-oe2)

(defun hs-deft-strip-ss (args)
  "Replace german sonderzeichen."
  (list (replace-regexp-in-string "ß" "ss" (car args)))
  )
(advice-add 'deft-new-file-named :filter-args #'hs-deft-strip-ss)

(defun hs-deft-strip-dp (args)
  "Replace Doppelpunkt."
  (list (replace-regexp-in-string ":" "" (car args)))
  )
(advice-add 'deft-new-file-named :filter-args #'hs-deft-strip-dp)

Mehrere Instanzen

Ich verwende mehrere Deft-Instanzen. Meine normalen Notizen:

(defun show-notes ()
  (interactive)
  ;; (kill-matching-buffers REGEXP &optional INTERNAL-TOO NO-ASK)
  (kill-matching-buffers "*Deft*" nil t)
  (setq-default deft-directory (concat my-org-path "notes/"))
  (deft))
(global-set-key "\C-cn" 'show-notes)

Mein CRM:

(defun show-people ()
  (interactive)
  ;; (kill-matching-buffers REGEXP &optional INTERNAL-TOO NO-ASK)
  (kill-matching-buffers "*Deft*" nil t)
  (setq-default deft-directory (concat my-org-path "people/"))
  (deft))
(global-set-key "\C-cp" 'show-people)

Meine Bibliothek:

(defun show-knowledge ()
  (interactive)
  ;; (kill-matching-buffers REGEXP &optional INTERNAL-TOO NO-ASK)
  (kill-matching-buffers "*Deft*" nil t)
  (setq-default deft-directory (concat my-org-path "knowledge/"))
  (deft))
(global-set-key "\C-ck" 'show-knowledge)

Org-contacts (Kontaktverwaltung)

Eine andere Möglichkeit der Kontaktverwaltung (statt einzelner Dateien im deft-mode) ist die Nutzung von org-contacts. Ganz anfreunden konnte ich mich mit dem Paket jedoch nie und es finden sich auch wenige Inhalte dazu online. Zudem wird das Paket offenbar nicht mehr aktiv gepflegt. Ich habe es daher derzeit deaktiviert und nutze die oben konfigurierte Lösung mit dem deft-mode.

;;(use-package org-contacts
;;  :ensure nil
;;  :after org
;;  :config
;;  (setq org-contacts-files '("C:/Users/suenkler/Documents/org/contacts.org")))
;;
;;(global-set-key (kbd "<f4>") 'org-contacts)

Hilfsprogramme

GPG

Dies ermöglicht es mir, beispielsweise Org-Dateien mit der Endung .gpg zu versehen. Dann werden die Dateien GPG-verschlüsselt.

(require 'epa-file)

Magit

Für die Versionsverwaltung verwende ich Git. Einbindung im Emacs mit magit:

(use-package magit
  :ensure t
  :bind (("C-x g" . magit-status)
         ("C-x C-g" . magit-status)))

TODO conda-mode

Ich arbeite gerne mit Python/Pandas in Emacs Org-mode Source Code Blocks. Unter Windows verwende ich Anaconda. Um über conda Python-Umgebungen verwenden zu können, nutze ich den conda-mode.

;;(use-package conda
;;  :ensure t)

;;(require 'conda)

;; if you want interactive shell support, include:
;;(conda-env-initialize-interactive-shells)

;; if you want eshell support, include:
;;(conda-env-initialize-eshell)

;; if you want auto-activation (see below for details), include:
;;(conda-env-autoactivate-mode t)

;; if you want to automatically activate a conda environment on the opening of a file:
;;(add-to-hook 'find-file-hook (lambda () (when (bound-and-true-p conda-project-env-path)
;;                                          (conda-env-activate-for-buffer))))

ivy-bibtex

Ich verwende Zotero zur Verwaltung von PDFs. Möchte ich in meinen Notizen im Emacs eine PDF-Datei verlinken, erfolgt dies per ivy-bibtex. ivy-bibtex liest eine BibTeX-Datei, die Zotero bei jeder Änderung automatisch aktualisiert.

(use-package ivy-bibtex
  :ensure t
  :bind (("C-c b" . ivy-bibtex)))

(autoload 'ivy-bibtex "ivy-bibtex" "" t)

(setq ivy-re-builders-alist
      '((ivy-bibtex . ivy--regex-ignore-order)
        ;;(t . ivy--regex-plus)
            (t . ivy--regex-fuzzy)))

Pfad zur BibTeX-Datei:

(setq bibtex-completion-bibliography
      '("C:/Users/suenkler/Documents/BibTeX/MeineBibliothek.bib"))

(setq bibtex-completion-pdf-field "File")

Die folgende Einstellung sorgt bei mir dafür, dass eine PDF-Datei in Adobe Reader – und nicht im Emacs selbst (DocView mode) – geöffnet wird:

(setq bibtex-completion-pdf-open-function 'org-open-file)

Im Orgmode soll beim Zitieren ein Link zur PDF-Datei angebracht werden. Als Link-Text wird das BibTeX-Kürzel verwendet.

(setq bibtex-completion-format-citation-functions
  '((org-mode      . bibtex-completion-format-citation-org-link-to-PDF)
    (latex-mode    . bibtex-completion-format-citation-cite)
    (markdown-mode . bibtex-completion-format-citation-pandoc-citeproc)
    (default       . bibtex-completion-format-citation-default)))

Wenn statt dessen der Titel der PDF-Datei verwendet werden soll, kann alternativ die Funktion bibtex-completion-format-citation-org-title-link-to-PDF verwendet werden.

Yasnippet

Derzeit nicht in Verwendung.

;;(use-package yasnippet
;;  :ensure t
;;  :init
;;  (yas-global-mode 1)
;;  :config
;;  (add-to-list 'yas-snippet-dirs (locate-user-emacs-file "snippets")))

PDF-Tools

Derzeit nicht in Verwendung.

;;(use-package pdf-tools
;;  :ensure t
;;  :mode ("\\.pdf\\'" . pdf-view-mode)
;;  :config
;;  (pdf-tools-install)
;;  (setq-default pdf-view-display-size 'fit-page)
;;  (setq pdf-annot-activate-created-annotations t))

Datei mit Aufgaben laden

Der Emacs soll beim Start die Datei tasks.org laden:

(find-file (concat my-org-path "tasks.org"))