Marcel Kapfer

Emacs Configuration file

Welcome! You found my (Marcel Kapfer aka mmk2410) personal Emacs configuration file. Please feel free to read through it, use it or get inspiration! If you would (for some very strange reason) like to read it online: I published it to config.mmk2410.org

This file (config.org) is written in org-mode and converted as Emacs Lisp to config.el on startup. This is achieved on org-babel and tangle (see the init.el file).

There are many people that I should thank for giving me the help needed to write this config. Two of them need to be mentioned here since many parts are either copied or heavily inspired by their configurations:

Note: Elisp blocks which are marked as :tangle no are not evaluated.

A few words on setup

Writing the initial "stable" version of this configuration took nearly two years. I used it constantly during this time after I declared Emacs bankruptcy with my old configuration. During this time many things happened and changed Back in March 2019 when I started I never imaged using this configuration on a native Microsoft® Windows® installation of Emacs. But in 2021 when I started using some creative tools that are only available on Windows® I first did and only later switched completely to the Windows Subsystem for Linux. Therefore some parts are compatible with a native installation of Emacs on Windows®.

For Microsoft(R) Windows(R) users it is highly adviced to set the HOME environment variable to the users home directory. This can be done by opening the file explorer WIN+e, right clicking on "This PC" and choosing "Properties". In the opening window it is possible to open "Advanced system settings" in the sidebar. In the then appearing dialg ther is a button labeled "Environment Variables…". Using the "New…" button in the "User variables" section of the "Environment Variables" dialog it is possible to add a new one. Enter as name HOME and use the "Browse Directory.." button for choosing your user home directory. Accept all the changes by clicking on "OK" until there are no more open dialogs left.

General settings

Settings for Emacs in general as well as for my personal setup.

System Detection

My work machine is called “knuth” and there is some configuration which I only want there.

(defun mmk2410/work ()
  "Return t if machine is my work machine"
  (string-equal (system-name) "knuth"))

Personal Information

(setq user-full-name "Marcel Kapfer"
      user-mail-address "opensource@mmk2410.org")

Date & Time

Set the first day of the week to Mondays.

(setq calendar-week-start-day 1)

Automatically create and save UIDs when exporting an iCalendar using Org.

(setq org-icalendar-store-UID t)

Set timezone for Org iCalendar export.

(setq org-icalendar-timezone "Europe/Berlin")

Use 24h time display including day and date.

(setq display-time-24hr-format t
      display-time-day-and-date t)

Use a sane date style.

(setq calendar-date-style 'european)

Turn off backup files

(setq-default backup-inhibited t)

Performance tweaks

As recommended by lsp-mode,

(setq gc-cons-threshold 100000000
      read-process-output-max (* 1024 1024))

Disable adding double spaces after periods

Source: Real sentence in Emacs : emacs

(setq-default sentence-end-double-space nil)

Enable truncate lines mode

Sources:

(setq-default truncate-lines t)
(setq-default global-visual-line-mode t)
(add-hook 'org-mode-hook 'visual-line-mode)

Delete trailing white-space

Source: EmacsWiki: Deleting Whitespace

(add-hook 'before-save-hook 'delete-trailing-whitespace)

Require final newline

(setq require-final-newline t)

Keep Emacs custom settings in a seperate file

This includes creating the file if not existent.

(setq custom-file (expand-file-name "custom.el" user-emacs-directory))
(unless (file-exists-p custom-file)
  (with-temp-buffer (write-file custom-file)))
(load custom-file)

Always follow VCS symlinks

(setq vc-follow-symlink t)

Indent using spaces, not tabs

(setq-default indent-tabs-mode nil)

Save minibuffer histroy

(savehist-mode 1)

Auto-revert externally changed files

(global-auto-revert-mode t)
(setq global-auto-revert-non-file-buffers t)

Spell checking

Enable aspell. Options set according to Emacswiki: FlySpell

(setq-default ispell-program-name "aspell")
(setq ispell-list-command "--list")

For using spell checking on Microsoft(R) Windows(R) a little bit more work is apparently needed. First of all some spell checking program is needed and when trying to install either ispell, aspell or hunspell I run into the problem that all three of them are primarily developed for Linux systems.

For installing aspell and the required dictionaries I decided to use MSYS2. After installing this and following the initial setup guide on their website I installed aspell using pacman -S mingw-w64-x86_64-aspell in an MSYS2 terminal (for the next steps it was also necessary to install the make program with pacman -S make). The installation itself does not make the program available to my Emacs installation (see A few words on setup) but I needed to add the C:\msys64\mingw64\bin directory to the Windows Path variable. Therefore I opened an explorer (WIN+e), right-clicked "This PC" and then properties. In the appearing window I continued by clicking "Advanced system settings" in the sidebar. In the now appearing window I continued by clicking the "Environment Variables…" button. There I edited the Path system variable. By clicking on "New" first and then clicking on "Browse…" I was able to add the previously named directory to the path. Afterwards it is possible to check weather this was successful by either opening a PowerShell or CMD prompt and executing aspell --version.

While Emacs now finds aspell (at least after a restart of Emacs) it does not find any dictionaries and therefore not work. This is quite understandable since by default non are installed. Apparently compared to some Linux distributions MSYS2 does not provide any dictionaries through their repositories. So I downloaded the ones I needed from the aspell dictionaries website. Using the MSYS2 MinGW 64-bit terminal (this is important, the MSYS MSYS2 one does not work for this) I could extract them with tar xjf aspell6-en-2020.12.07-0.tar.bz2 and switch to the extracted directory. Following the provided README I first run ./configure and the executed make. Since the make install step fails by default I needed to adjust the Makefile by deleting the two lines that change the directory and execute chmod 664 in the install rule. Afterwards running make install succeeded without any errors.

After re-visiting the current file or opening another one spell checking in Emacs works!

scratch-buffer

I prefer that the mode of my scratch buffer is org and don't want any initial lines.

(setq initial-scratch-message nil)
(setq initial-major-mode 'org-mode)

doc-view

Set doc-view resolution to 300 for sharp PDFs.

(setq doc-view-resolution 300)

Raise emacsclient frame on open

Raise new emacsclient frames when created.

Source: bkhl's dotfiles

(defun mmk2410/focus-frame ()
  (select-frame-set-input-focus (selected-frame)))
(add-hook 'server-switch-hook 'mmk2410/focus-frame)
(add-hook 'server-after-make-frame-hook 'mmk2410/focus-frame)

Enable electric pair mode

electric-pair-mode enables adding closing parenthesis & co when opening one.

(electric-pair-mode 1)

camel-case words as separate words

In many programming languages writing camel-case is often used. So I would like to tread them as separate words.

(add-hook 'prog-mode-hook 'subword-mode)

Make scripts executable when saving

Files that start with #! should be made executable on save.

(add-hook 'after-save-hook
          'executable-make-buffer-file-executable-if-script-p)

Follow symlinks automatically

When visiting a symlink, follow it automatically.

(setq vc-follow-symlinks t)

Dired customizations

Show filesize human readable.

(setq-default dired-listing-switches "-alh")

Package configuration

General configuration

Add relevant archives.

(add-to-list 'package-archives '("nongnu" . "https://elpa.nongnu.org/nongnu/") t)
(add-to-list 'package-archives '("melpa" . "https://melpa.org/packages/") t)

Configure the package manager to prefer newer packages and to not load packages automatically on startup.

(setq-default load-prefer-newer t)
(setq-default package-enable-at-startup nil)

use-package

Install/enable use-package.

(unless (package-installed-p 'use-package)
  (package-refresh-contents)
  (package-install 'use-package))
(require 'use-package)

Configure use-package to load packages by default.

(setq use-package-always-ensure t)

Visual appearance

I don't agree with some visual defaults, so I redefine them.

Short yes/no questions

(defalias 'yes-or-no-p 'y-or-n-p)

Confirm before closing emacs

(setq confirm-kill-emacs #'y-or-n-p)

Replace BEEP by visual-bell

(setq visible-bell 1)

Disable cursor blinking

(blink-cursor-mode -1)

Highlight current line

(add-hook 'text-mode-hook #'hl-line-mode)

Show line numbers

(add-hook 'text-mode-hook #'display-line-numbers-mode)
(add-hook 'prog-mode-hook #'display-line-numbers-mode)

And disable this for some modes.

(add-hook 'elfeed-show-mode-hook (lambda () (display-line-numbers-mode -1)))
(add-hook 'eshell-mode-hook (lambda () (display-line-numbers-mode -1)))
(add-hook 'pdf-view-mode-hook (lambda () (display-line-numbers-mode -1)))
(add-hook 'shell-mode-hook (lambda () (display-line-numbers-mode -1)))
(add-hook 'eww-mode-hook (lambda () (display-line-numbers-mode -1)))

But when Emacs shows line numbers, they should be relative. Why? Ever tried 20j in Evil mode or C-u 20 C-p in Emacs?

(setq display-line-numbers-type 'relative)

Replace selected text by typing

(delete-selection-mode t)

Vertical bar as cursor

(setq-default cursor-type 'bar)

Hide tool bar, menu bar and scroll bar

I get scroll bars when using Emacs with emacsclient. So I disable the scroll-bar-mode explicitly when creating a new frame, but only on graphical systems. The same fix is also applied for the typeface.

(when (fboundp 'tool-bar-mode)
  (tool-bar-mode -1))
(when (fboundp 'menu-bar-mode)
  (menu-bar-mode -1))
(when (fboundp 'scroll-bar-mode)
  (scroll-bar-mode -1))

Highlight matching parenthesis

(show-paren-mode 1)

Fix wrong Emacs size in some window managers

I have the problem in BSPWM that Emacs always starts floating and does not obey my rule to start in tiling mode.

Setting frame-resize-pixelwise to non-nil (as described in GitHub: baskerville/bspwm #551) solves this problem.

(setq frame-resize-pixelwise t)

Set typeface

My typeface is not loading when using emacsclient. So I set it explicitly when creating a frame. The same fix is applied for the scroll bars.

(setq my-font "Jetbrains Mono 11")

(set-frame-font my-font nil t)

(add-hook 'after-make-frame-functions
          #'(lambda (frame)
              (set-frame-font my-font nil t)))

(set-face-attribute 'default nil :font "Jetbrains Mono" :height 110 :weight 'regular)
(set-face-attribute 'variable-pitch nil :font "Open Sans" :height 130 :weight 'regular)
(set-face-attribute 'fixed-pitch nil :font "Jetbrains Mono" :height 110 :weight 'regular)

Set theme

Installing and enabling Gruvbox light theme. Disabled in favor of the Doom themes.

Source: Github: greduan/emacs-theme-gruvbox

(use-package gruvbox-theme
  :disabled t
  :config
  (load-theme 'gruvbox-light-medium t))

Installing and enabling Modus themes. Disabled in favor of the Doom themes.

(use-package modus-themes
  :disabled t
  :init
  (modus-themes-load-themes)
  (setq modus-themes-bold-constructs t)
  (setq modus-themes-syntax 'alt-syntax)
  (setq modus-themes-slanted-constructs t)
  (setq modus-themes-org-blocks 'grayscale)
  (setq modus-themes-variable-pitch-headings t)
  :config
  (modus-themes-load-operandi))

Installing and enabling Doom themes.

(use-package doom-themes
  :config
  (setq doom-themes-enable-bold t
        doom-themes-enable-italic t)
  (load-theme 'doom-one-light t)
  (doom-themes-visual-bell-config)
  (doom-themes-org-config))

Add a slight transparency to the frames. Code provided by David Wilson / System Crafters Emacs from Scratch video series.

(setq mmk2410/frame-transparency '(90 . 90))

(set-frame-parameter (selected-frame) 'alpha mmk2410/frame-transparency)
(add-to-list 'default-frame-alist `(alpha . ,mmk2410/frame-transparency))

Switch theme

During the last weeks I got used to using a light theme during daytime and a dark theme during nighttime. Luckily the themes based on Atom One are to my liking and the doom package provides a dark and light variant. One thing that was difficult with Emacs was actually switching theme since it involved disabling all current ones (for some reason on one machine there are two enabled while on the other machine its only one) and then loading the correct one. These functions try to make that process easier, at least for now. The long-term goal is to automatically change the theme based on the current system-wide setting.

(setq mmk2410/theme-night 'doom-one)
(setq mmk2410/theme-day 'doom-one-light)

(defun mmk2410/switch-theme (theme)
  (mapcar 'disable-theme custom-enabled-themes)
  (load-theme theme t)
  (mmk2410/org-font-adjust-headlines)
  (mmk2410/org-font-adjust-variable-pitch))

(defun mmk2410/switch-theme-night ()
  (interactive)
  (mmk2410/switch-theme mmk2410/theme-night))

(defun mmk2410/switch-theme-day ()
  (interactive)
  (mmk2410/switch-theme mmk2410/theme-day))

Customize modeline

I'd like to use telephone line as a mode line.

(use-package telephone-line
  :disabled t
  :init (telephone-line-mode t))

I'm currently trying doom-modeline instead of telephone-line.

(use-package doom-modeline
  :init (doom-modeline-mode 1)
  :config (setq doom-modeline-icon t))

Additionally to that I also like to know, in which column I am.

(column-number-mode 1)

Customize tab-bar

Since version 27 Emacs has a new tab-bar feature for keeping multiple window configurations open in different tabs. By default each tab has a close button and the tab bar includes an add button. I prefer not having either one.

(setq tab-bar-close-button-show nil
      tab-bar-new-button-show nil)

Emojis

Minor mode for displaying emojis in Emacs.

Source: iqbalansari/emacs-emojify

(use-package emojify
  :ensure t
  :hook (erc-mode . emojify-mode)
  :commands emojify-mode)

Keybinding

List of own custom keybindings

Emacs Lisp related

(global-set-key (kbd "C-c x") 'eval-region)

Ibuffer as default

(global-set-key (kbd "C-x C-b") 'ibuffer)

Disable C-z

C-z by default (as known from terminal applications) suspends the current frame. Since I use Emacs as GUI and not in terminal I do not want this behavior.

(unbind-key "C-z")

The evil side

I currently try out evil-mode for evaluating if it is any better for my hand.

Source: GitHub: emacs-evil/evil

(use-package evil
  :init
  (setq evil-want-integration t)
  (setq evil-want-keybinding nil)
  :config
  (evil-mode 1)
  (define-key evil-insert-state-map (kbd "C-g") 'evil-normal-state)
  (define-key evil-insert-state-map (kbd "C-h") 'evil-delete-backward-char-and-join)
  (evil-global-set-key 'motion "j" 'evil-next-visual-line)
  (evil-global-set-key 'motion "k" 'evil-previous-visual-line))

An even better integration may be achieved when additionall using the evil-collection package.

(use-package evil-collection
  :after evil
  :config (evil-collection-init))

General packages

diminish-mode

Diminished modes are minor modes with no modeline display

Source: GitHub: myrjola/diminish.el

(use-package diminish
  :init
  (diminish 'abbrev-mode))

crux

A Collection of Ridiculously Useful eXtensions for Emacs.

Used especially for crux-delete-file-and-buffer and crux-rename-file-and-buffer

Source: GitHub: bbatsov/crux

(use-package crux
  :bind (:map prog-mode-map
         ("C-c C-." . crux-duplicate-current-line-or-region)
         ("C-c C-M-." . crux-duplicate-and-comment-current-line-or-region)
         :map global-map
         ("C-c M-r" . crux-rename-file-and-buffer)))

which-key

Emacs package that displays available keybindings in popup

Source: GitHub: justbur/emacs-which-key

(use-package which-key
  :diminish which-key-mode
  :init (which-key-mode)
  :config (setq which-key-idle-delay 0.5))

multiple-cursors

Multiple cursors for emacs.

Source: GitHub: magnars/multiple-cursors

(use-package multiple-cursors
  :bind
  (("C->" . mc/mark-next-like-this)
   ("C-<" . mc/mark-previous-like-this)
   ("C-*" . mc/mark-all-like-this)
   ("C-;" . mc/edit-lines)))

magit

A Git Porcelain inside Emacs

Source: magit.vc

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

orgit

Support for Org links to Magit buffers.

Source
GitHub: magit/orgit
Blog post
My Emacs package of the week: orgit
(use-package orgit
  :after (magit org)
  :config
  (add-to-list 'orgit-export-alist
             '("git.mmk2410.org[:/]\\(.+?\\)\\(?:\\.git\\)?$"
               "https://git.mmk2410.org/%n"
               "https://git.mmk2410.org/%n/commits/commit/%r"
               "https://git.mmk2410.org/%n/commit/%r")))

diff-hl

Emacs package for highlighting uncommitted changes

Source: Github: dgutov/diff-hl

(use-package diff-hl
  :config (global-diff-hl-mode t)
  :hook (magit-post-refresh-hook . diff-hl-magit-post-refresh))

project.el

Load the included project package.

(use-package project)

treemacs

Treemacs is a package for displaying a directory tree in a (side) buffer. While browsing a project using project.el is also possible and perhaps fast, I think that the tree approach especially helps discovering a new project.

Source: Alexander-Miller/treemacs

(use-package treemacs
  :defer t
  :config
  (setq treemacs-litter-directories '("/node_modules" "/.venv" "/.cask" "/vendor")
        treemacs-indent-guide-style 'line)
  (treemacs-follow-mode t)
  (treemacs-git-mode 'deferred)
  (treemacs-indent-guide-mode t)
  :bind (:map global-map
              ("M-0" . treemacs-select-window)
              ("C-c b 1" . treemacs-delete-other-windows)
              ("C-c b b" . treemacs)
              ("C-c b B" . treemacs-bookmark)
              ("C-c b C-f" . treemacs-find-file)
              ("C-c b M-f" . treemacs-find-tag)))

(use-package treemacs-evil
  :after (treemacs evil))

counsel, ivy and swiper

Emacs package collection (ivy, swiper and counsel) of flexible, simple tools for minibuffer completion.

Source: GitHub: abo-abo/swiper

(use-package counsel
  :bind ("M-x" . counsel-M-x))

(use-package ivy
  :after (counsel)
  :diminish (ivy-mode)
  :config
  (ivy-mode t)
  (setq ivy-use-virtual-buffers t)
  (setq ivy-count-format "(%d/%d) ")
  :bind
  (("C-c g" . counsel-git)
   ("C-s" . swiper)
   ("C-r" . swiper)))

ivy-rich

ivy-rich enhances M-x and similar even more by giving more information directly in the minibuffer.

Source: GitHub: Yevgnen/ivy-rich

(use-package ivy-rich
  :init (ivy-rich-mode 1))

avy

Jump to things in Emacs tree-style

Source: GitHub: abo-abo/avy

(use-package avy
  :bind
  (("C-:" . avy-goto-char)
   ("C-M-:" . avy-goto-char-2)))

yasnippet

YASnippet is a template system for Emacs.

Snippets can be put in the yas-snippet-dirs, e.g. ~/.emacs.d/snippets.

Source: GitHub: joaotavora/yasnippet

(use-package yasnippet
  :config (yas-global-mode 1))

pdf-tools

An advanced Emacs support library for PDF files.

Source: GitHub: vedang/pdf-tools

(use-package pdf-tools
  :if (not (eq system-type 'windows-nt))
  :config
  (pdf-loader-install)
  ;; open pdfs scaled to fit page
  (setq-default pdf-view-display-size 'fit-page)
  ;; more fine-grained zooming
  (setq pdf-view-resize-factor 1.1)
  ;; use normal isearch
  (define-key pdf-view-mode-map (kbd "C-s") 'isearch-forward)
  (define-key pdf-view-mode-map (kbd "C-r") 'isearch-backward)
  ;; keyboard shortcuts
  (define-key pdf-view-mode-map (kbd "h") 'pdf-annot-add-highlight-markup-annotation)
  (define-key pdf-view-mode-map (kbd "t") 'pdf-annot-add-text-annotation)
  (define-key pdf-view-mode-map (kbd "D") 'pdf-annot-delete))

deft

Deft is a nice tool for searching for files (in my case my notes). Since it searches not only through the title of files but also through the contents it may be useful for me.

(use-package deft
  :bind ("C-c d" . deft)
  :commands (deft)
  :config (setq deft-directory "~/org"
                deft-extensions '("org")
                deft-recursive t))

ace-window

Quickly switch windows in Emacs

Source: GitHub: abo-abo/ace-window

(use-package ace-window
  :bind (("M-o" . 'ace-window)))

Org mode configuration

This is my personal org-mode configuration.

Org modules

(use-package org
  :pin gnu)
(add-to-list 'org-modules 'org-habit)
(require 'org-protocol)

General settings

Follow links when pressing enter on them.

(setq org-return-follows-link t)

Disable extra indent in src-blocks.

(setq org-edit-src-content-indentation 0)

Prevent accidental edits in invisible regions.

(setq org-catch-invisible-edits 'error)

Replace the three dots for collapsed stuff with a nicer symbol.

(setq org-ellipsis " ▼")

Hide emphasis markers. While this sometimes is very distracting it mostly looks absolutely beautiful.

(setq org-hide-emphasis-markers t)

Org font adjustments

I like to have larger headlines in Org for better identifying them. The configuration of David Wilson was a large help in this regard.

(defun mmk2410/org-font-adjust-headlines ()
  "Adjust headline sizes, font family and weight"
  (dolist (face '((org-level-1 . 1.4)
                  (org-level-2 . 1.3)
                  (org-level-3 . 1.2)
                  (org-level-4 . 1.15)
                  (org-level-5 . 1.1)
                  (org-level-6 . 1.1)
                  (org-level-7 . 1.1)
                  (org-level-8 . 1.1)))
    (set-face-attribute (car face) nil :font "Open Sans" :weight 'bold :height (cdr face))))

(with-eval-after-load 'org
  (mmk2410/org-font-adjust-headlines))

When using a variable-pitch font this also applies to stuff like code blocks. The following block resets this change for these unwanted faces.

(defun mmk2410/org-font-adjust-variable-pitch ()
  "Re-delare certain org font as fixed pitch when using variable pitch mode."
  (org-indent-mode)
  (set-face-attribute 'org-block nil :foreground nil :inherit 'fixed-pitch)
  (set-face-attribute 'org-code nil   :inherit '(shadow fixed-pitch))
  (set-face-attribute 'org-indent nil :inherit '(org-hide fixed-pitch))
  (set-face-attribute 'org-verbatim nil :inherit '(shadow fixed-pitch))
  (set-face-attribute 'org-special-keyword nil :inherit '(font-lock-comment-face fixed-pitch))
  (set-face-attribute 'org-meta-line nil :inherit '(font-lock-comment-face fixed-pitch))
  (set-face-attribute 'org-checkbox nil :inherit 'fixed-pitch))

(with-eval-after-load 'org
  (mmk2410/org-font-adjust-variable-pitch))

Org Superstar Mode

org-superstar-mode is a nice package for enriching the display of heading stars as well as list items.

(with-eval-after-load 'org
  (use-package org-superstar
    :hook (org-mode . org-superstar-mode)))

Org Appear Mode

Toggle visibility of hidden Org mode element parts upon entering and leaving an element.

Source
GitHub: awth13/org-appear
Blog Post
My Emacs package of the week: org-appear
(use-package org-appear
  :after org
  :hook (org-mode . org-appear-mode))

LaTeX-related settings

Add scratcl KOMA-Script class as available LaTeX class.

(with-eval-after-load 'ox-latex
  (add-to-list 'org-latex-classes
               '("scrartcl"
                 "\\documentclass{scrartcl}"
                 ("\\section{%s}" . "\\section*{%s}")
                 ("\\subsection{%s}" . "\\subsection*{%s}")
                 ("\\subsubsection{%s}" . "\\subsubsection*{%s}")
                 ("\\paragraph{%s}" . "\\paragraph*{%s}")
                 ("\\subparagraph{%s}" . "\\subparagraph*{%s}"))))

Set the scrartcl class as default.

(setq org-latex-default-class "scrartcl")

Us LuaLaTeX as default LaTeX compiler.

(setq org-latex-compiler "lualatex")

Customize LaTeX packages and configurations.

(setq org-latex-packages-alist
      '(
        ("" "booktabs" t)
        ("" "amsmath" t)
        ("AUTO" "polyglossia" t)
        ("colorlinks" "hyperref" t)
        "\\setmainfont{Linux Libertine O}"
        "\\setsansfont{Linux Biolinum O}"
        "\\setmonofont[Scale=0.9]{JetBrainsMono}"))

Configure minted source code listings for LaTeX export. For actually using the configuration it is necessary to set org-latex-listings to 'minted using a local variable and to add the minted and xcolor package to org-latex-packages-alist. This is not done by default since I don't need it for all files and it requires --shell-escape for the LaTeX compile process.

(setq org-latex-minted-options
      '(("baselinestretch" "1.2")
        ("breaklines" "true")
        ("fontsize" "\\footnotesize")
        ("frame" "single")
        ("framesep" "2mm")
        ("linenos" "true")
        ("style" "pastie")))

Removed unused default packages.

(unless (boundp 'org-latex-default-packages-alist)
  (setq org-latex-default-packages-alist nil))
(setq
 org-latex-default-packages-alist (remove '("AUTO" "inputenc" t) org-latex-default-packages-alist)
 org-latex-default-packages-alist (remove '("" "fixltx2e" nil) org-latex-default-packages-alist)
 org-latex-default-packages-alist (remove '("" "hyperref" nil) org-latex-default-packages-alist)
 org-latex-default-packages-alist (remove '"\\tolerance=1000" org-latex-default-packages-alist))

Configure the org LaTeX preview to work with LuaLaTeX.

(with-eval-after-load 'org
  (add-to-list 'org-preview-latex-process-alist
               '(luamagick :programs
                           ("lualatex" "convert")
                           :description "pdf > png" :message "you need to install the programs: lualatex and imagemagick."
                           :image-input-type "pdf"
                           :image-output-type "png"
                           :image-size-adjust (1.0 . 1.0)
                           :latex-compiler
                           ("lualatex --interaction=nonstopmode -output-directory=%o %f")
                           :image-converter
                           ("convert -density %D -trim -antialias %f -quality 100 %O"))))

(setq org-preview-latex-default-process 'luamagick)

Also adjust the LaTeX preview to be a little bit larger, since it is to small for me.

(setq org-format-latex-options (plist-put org-format-latex-options :scale 1.5))

ToDo-Management and Org Agenda

Set TODO Keywords.

(setq org-todo-keywords '((sequence
                           "TODO(t!)" "NEXT(n!)" "STARTED(a!)" "WAIT(w@/!)" "SOMEDAY(s)"
                           "|" "DONE(d!)" "CANCELLED(c@/!)")))

Set time tag when re-something on todos

(setq org-log-done 'time
      org-log-reschedule 'time
      org-log-redeadline 'time
      org-log-into-drawer t)

Forbid closing of todos with open subtasks.

(setq
 org-enforce-todo-dependencies t
 org-enforce-todo-checkbox-dependencies t)

Set org agenda files.

(setq org-agenda-files (list "~/org/")
      org-agenda-text-search-extra-files (list "~/org/archive/"
                                               "~/org/roam/"
                                               "~/org/articles/"))

Open org-agenda in current window.

(setq org-agenda-window-setup (quote current-window))

Define org-capture templates.

(setq org-capture-templates
      '(("t" "Normal task" entry (file "~/org/inbox.org")
         "* TODO %?\n  :PROPERTIES:\n  :CREATED: %U\n  :END:")
        ("w" "Work task" entry (file "~/org/work.inbox.org")
         "* TODO %?\n  :PROPERTIES:\n  :CREATED: %U\n  :END:")
        ("e" "E-Mail needing a reply" entry (file "~/org/emails.org")
         "* TODO Reply: %a\n  :PROPERTIES:\n  :CREATED: %U\n  :END:"
         :immediate-finish t)
        ("p" "org-protocol-capture" entry (file "~/org/inbox.org")
         "* TODO [[%:link][%:description]]\n  :PROPERTIES:\n  :CREATED: %U\n  :END:\n\n  %i"
         :immediate-finish t)
        ("s" "Event" entry (file "~/org/events.org")
         "* SOMEDAY %? %^G\n  SCHEDULED: %^T\n  :PROPERTIES:\n  :CREATED: %U\n  :END:")
        ("r" "Reading todo" entry (file "~/org/reading.org")
         "* TODO %^L %^G\n  :PROPERTIES:\n  :CREATED: %U\n  :END:\n")))

Configure refiling TODOs.

  • Show headline to refile to as path, also including the file name
  • Don't select hierarchically, but show everything at once
  • Enable the ability to create parent nodes when refiling
  • Set refile targets to agenda files and only consider the first and second level
(setq org-refile-use-outline-path 'file)
(setq org-outline-path-complete-in-steps nil)
(setq org-refile-allow-creating-parent-nodes 'confirm)
(setq org-refile-targets '((org-agenda-files :maxlevel . 2)))

Warn me on deadlines in the next year.

(setq org-deadline-warning-days 365)

Org Agenda mode settings:

  • Set agenda span to one day
  • Don't show entries twice (scheduled and deadline)
  • Start week today and not on Monday.
(setq org-agenda-span 'week)
(setq org-agenda-skip-scheduled-if-deadline-is-shown t)
(setq org-agenda-start-on-weekday nil)

Make the column for categories wider.

(setq org-agenda-prefix-format
      '((agenda . " %i %-18:c%?-12t% s")
        (todo . " %i %-18:c")
        (tags . " %i %-18:c")
        (search . " %i %-18:c")))

Finally define the org-agenda display using org-super-agenda by alphapapa.

(use-package org-super-agenda
  :after org
  :init (org-super-agenda-mode)
  :config
  (setq org-agenda-block-separator nil
        org-agenda-compact-blocks t)
  (setq org-agenda-custom-commands
        '(("c" "Complete Agenda with Todos"
           ((agenda "" ((org-agenda-span 'week)
                        (org-deadline-warning-days 365)
                        (org-super-agenda-groups
                         '((:name "" :time-grid t :deadline future)))))
            (alltodo "" ((org-agenda-overriding-header "")
                         (org-super-agenda-groups
                          '((:name "High Priority" :priority "A")
                            (:name "Inbox" :file-path ".*/org/inbox\\.org$")
                            (:name "[Work] Inbox" :file-path ".*/org/work\\.inbox\\.org$")
                            (:name "Emails" :file-path ".*/org/emails\\.org$")
                            (:name "Next" :todo "NEXT")
                            (:name "Started" :todo "STARTED")
                            (:name "[Work] Projects" :file-path ".*/org/work\\.projects\\.org$")
                            (:name "Study" :and (:file-path ".*/org/study\\.org$" :not(:scheduled future)))
                            (:name "Reading"  :file-path ".*/org/reading\\.org$")
                            (:name "Projects" :file-path ".*/org/projects\\.org$")
                            (:name "Habits" :habit t :order 100)
                            (:name "Someday" :todo "SOMEDAY" :scheduled future :order 101))))))))))

Automatic UUID creation

I am currently exploring the option in Org mode to export a file (or some entries) to an ics-file which I can then in turn import into my calendar. For reliably creating (and most importantly: updating) entries it is necessary that each one has an unique ID. To create a ID for the current entry I could just run M-x org-id-get-create and for an entire file execute the following Emacs Lisp (org-map-entries 'org-id-get-create). Of course this is not an ideal solution. But adding this s-expression to org-mode-hook would create IDs in all Org file I edit which I also don't like. Since the amount of files I do want the automatic creation is (currently) not that large it is OK for me to do some work on my own, at least if it is only a one time setup.

The idea which I had to achieve this goal was to create a file-local variable (called mmk2410/org-create-uuids) that I set to t in the buffers I need the automatic adding of IDs and adding a hook to org-mode-hook that in turn adds a hook to before-save-hook for calling the function mmk2410/org-create-all-uuids that executes the previously named s-expression to add a ID to each entry in the buffer.

(setq mmk2410/org-create-uuids nil)

(defun mmk2410/org-create-all-uuids ()
  "Create UUIDs for all entries in the current org-mode buffer."
  (interactive)
  (when mmk2410/org-create-uuids
    (org-map-entries 'org-id-get-create)))

(add-hook 'org-mode-hook
          (lambda () (add-hook 'before-save-hook 'mmk2410/org-create-all-uuids nil t)))

Keybindings

Fast org key bindings.

  • C-c l for org-store-link
  • C-c c for org-capture
  • C-c o for org-switchb
  • C-c a for org-agenda
(global-set-key (kbd "C-c l") 'org-store-link)
(global-set-key (kbd "C-c c") 'org-capture)
(global-set-key (kbd "C-c o") 'org-switchb)
(global-set-key (kbd "C-c a") 'org-agenda)

Nicer display for Org buffers

From System Crafters' Emacs from Scratch Config.

(defun efs/org-mode-visual-fill ()
  (setq visual-fill-column-width 100
        visual-fill-column-center-text t)
  (visual-fill-column-mode 1))

(use-package visual-fill-column
  :hook (org-mode . efs/org-mode-visual-fill))

(add-hook 'org-mode-hook (lambda ()
                           (display-line-numbers-mode -1)
                           (variable-pitch-mode)))

Hooks

Enable flyspell-mode and disable line numbers in org-agenda.

(add-hook 'org-mode-hook 'flyspell-mode)
(add-hook 'org-agenda-mode-hook (lambda () (display-line-numbers-mode -1)))

Org Journal

Until now I did not think that I needed to keep a (personal) journal. But since I is recommended to keep a journal of whom you met due to the corona pandemic I started thinking about setting up one. Further use cases came then to my mind and so I decided to give it a try. Therefore the configuration is still in a very early state and may change quite soon.

(use-package org-journal
  :config (setq org-journal-dir "~/org/journal"
                org-journal-file-type 'monthly
                org-journal-find-file 'find-file)
  :bind (("C-c j j" . 'org-journal-new-entry)
         ("C-c j s" . 'org-journal-new-scheduled-entry)
         ("C-c j o" . 'org-journal-open-current-journal-file)
         ("C-c j w" . 'mmk2410/org-journal-worktime)))

Part of my workflow with org journal is in the meantime to clock the time I work in the different entries using org-clock. At the end of the day I like to create a summary for that. Currently the process for this includes a table with to formulas I created some time ago and copy every day from the previous one (while the first formula calculates the times the second is just there to clean the fields). While this is certainly improvable in many ways the only thing that really annoyed me was the calculation of the time. Since I try to follow the 52:17 methodology I have to add many numbers around 52 up to get the usual hour:minutes format. Although this is certainly a good brain training it has been shown that the calculation is very error-prone and calculating this twice does not make it more fun.

Therefore I created a little interactive helper function to sum these times up. The function mmk2410/org-journal-worktime reads a list of space separated minute values (so the time 1:04 must be entered as 64) and returns it as an minibuffer message in the required format (hh:mm).

(defun mmk2410/org-journal-worktime (workmins)
  "Sum up the entered minutes and return it in hh:mm format."
  (interactive "Show many minutes did you work today? ")
  (let ((worksum (apply '+ (mapcar 'string-to-number (split-string workmins)))))
    (message "Total work time: %d:%d" (/ worksum 60) (mod worksum 60))))

I am quite sure that the function can be improved in various ways. I am not a Lisp hacker and have not written very much in it. But at the moment it fits my needs (and I currently do not have more time to spare on this).

Org Drill

org-drill is a package for spaced repetition in Emacs using org-mode files.

Source: GitLab: phillord/org-drill

(use-package org-drill
  :after org
  :config
  (setq org-drill-learn-fraction 0.4)
  (setq org-drill-maximum-items-per-session 50))

Org for blogging

Since May 2020 I'm using hugo with ox-hugo as a org-mode based static site generator for my mmk2410.org site. You can find the source code for the website on my Gitea instance (and also GitLab and GitHub if you prefer that). First of all I need to include ox-hugo by Kaushal Modi:

(use-package ox-hugo
  :after org)

After I wrote an own capture template for starting a new blog post, Kashual Modi, the creator of ox-hugo, made me aware of an existing template in the documentation that also handles the automatic creation of the EXPORT_FILE_NAME property. I copied if from there and made some very slight adjustments to fit my needs.

(defconst mmk2410/blog-posts-file
  "~/projects/mmk2410.org/content-org/blog.org"
  "Position of my org file containing all blog posts.")

;; Populates only the EXPORT_FILE_NAME property in the inserted heading.
(with-eval-after-load 'org-capture
  (defun org-hugo-new-subtree-post-capture-template ()
    "Returns `org-capture' template string for new Hugo post. See `org-capture-templates' for more information."
    (let* ((title (read-from-minibuffer "Post Title: ")) ;Prompt to enter the post title
           (fname (org-hugo-slug title)))
      (mapconcat #'identity
                 `(
                   ,(concat "* TODO " title)
                   ":PROPERTIES:"
                   ,(concat ":EXPORT_FILE_NAME: " fname)
                   ":END:")
                 "\n")))

  (add-to-list 'org-capture-templates
               '("b" "Blog post" entry
                 (file mmk2410/blog-posts-file)
                 (function org-hugo-new-subtree-post-capture-template)
                 :prepend t :empty-lines 1
                 :immediate-finish t :jump-to-captured t)))

Org Research Assistant

Org Roam

Roam Research / Zettelkasten system using org-mode.

Source: orgroam.com

(use-package org-roam
  :commands (org-roam-node-insert org-roam-node-find org-roam-capture org-roam-buffer-toggle org-roam)
  :init
  (setq org-roam-v2-ack t)
  :config
  (setq org-roam-directory "~/org/roam")
  (add-to-list 'display-buffer-alist
               '("\\*org-roam\\*"
                 (display-buffer-in-direction)
                 (direction . right)
                 (window-width . 0.33)
                 (window-height . fit-window-to-buffer)))
  (require 'org-roam-protocol)
  (org-roam-db-autosync-mode)
  :bind (("C-c r f" . org-roam-node-find)
         ("C-c r i" . org-roam-node-insert)
         ("C-c r b" . org-roam-buffer-toggle)
         ("C-c r c" . org-roam-capture)
         ("C-c r g" . org-roam-graph)
         :map org-roam-mode-map
         ("[mouse-1]" . #'org-roam-visit-thing)))

Org Roam Bibtex

„Official“ connector between org-roam, BibTeX and org-ref.

Source: GitHub: org-roam/org-roam-bibtex

(use-package org-roam-bibtex
  :after org-roam
  :hook (org-roam-mode . org-roam-bibtex-mode))

Org Roam UI

Disabled since org-roam-ui is not yet released on Melpa. And I currently have no time to setup or switch to straight (or to put it better: it is currently not important enough for me).

A graphical frontend for exploring your org-roam Zettelkasten

Source: GitHub: org-roam/org-roam-ui

(use-package websocket
  :after org-roam)

(use-package org-roam-ui
  :after (org-roam websocket)
  :config
  (setq org-roam-ui-sync-theme t
        org-roam-ui-follow t
        org-roam-ui-update-on-save t
        org-roam-ui-open-on-start t))

Helm Bibtex

Search and manage bibliographies in Emacs.

Source: GitHub: tmalsburg/helm-bibtex

(use-package ivy-bibtex
  :after ivy
  :config
  (setq bibtex-completion-bibliography "~/org/bibliography.bib")
  (setq bibtex-completion-library-path "~/papers")
  (setq bibtex-completion-notes-path "~/org/roam")
  (setq bibtex-completion-find-additional-pdfs t))

Org Ref

org-mode modules for citations, cross-references, bibliographies in org-mode and useful bibtex tools to go with it.

Source: Github: jkitchin/org-ref

(use-package org-ref
  :after org
  :config
  (setq org-ref-default-bibliography '("~/org/bibliography.bib"))
  (setq org-ref-pdf-directory "~/papers/")
  (setq org-ref-notes-directory "~/org/roam")
  (setq reftex-default-bibliography org-ref-default-bibliography)
  (setq bibtex-completion-bibliography org-ref-default-bibliography)
  (setq bibtex-completion-library-path org-ref-pdf-directory)
  (setq bibtex-completion-notes-path org-ref-notes-directory))

Org Noter

A synchronized, Org-mode, document annotator

Source: GitHub: weirdNorx/org-noter

(use-package org-noter
  :after (:any org pdf-tools)
  :config
  (setq org-noter-notes-search-path '("~/org/roam")))

LaTeX configuration

auctex

For LaTeX typesetting in Emacs I like to use the great AUCTeX package.

(use-package auctex
  :defer t
  :config
  (setq TeX-auto-save t)
  (setq TeX-parse-self t)
  (setq TeX-engine 'luatex)C
  (setq-default TeX-master nil))

company-auctex

I also like to turn on company completion for auctex.

(use-package company-auctex
  :after auctex
  :config
  (company-auctex-init))

cdlatex

Fast input methods to enter LaTeX environments and math.

Source: GitHub: cdominik/cdlatex

(use-package cdlatex
  :hook ((LaTeX-mode . turn-on-cdlatex)
         (org-mode . turn-on-org-cdlatex)))

General Programming Packages

rainbow-delimiters

Lisp languages involve many parentheses and it might get hard to know which one are on the same “level”. The rainbow-delimiters package is here to help with this problem.

Source: GitHub: Fanael/rainbow-delimiters

(use-package rainbow-delimiters
  :hook (prog-mode . rainbow-delimiters-mode))

highlight-indent-guides

Minor mode for highlighting indentation.

Source: DarthFennec/highlight-indent-guides

(use-package highlight-indent-guides
  :config
  (setq highlight-indent-guides-method 'character
        highlight-indent-guides-responsive 'top)
  :hook (prog-mode . highlight-indent-guides-mode))

company

Auto completion

Source: GitHub: company-mode

(use-package company
  :diminish company-mode
  :hook ((prog-mode . company-mode)
         (latex-mode . company-mode))
  :init
  (setq company-show-numbers t
        company-minimum-prefix-length 1
        company-idle-delay 0.0
        company-dabbrev-downcase nil))

lsp

Language Server Protocoll capabilities for Emacs

Source: GitHub:emacs-lsp/lsp-mode

(use-package lsp-mode
  :hook ((python-mode . lsp-deferred)
         (go-mode . lsp-deferred)
         (php-mode . lsp-deferred)
         (vue-mode . lsp-deferred)
         (web-mode . lsp-deferred)
         (typescript-mode . lsp-deferred)
         (js-mode . lsp-deferred)
         (lsp-mode . lsp-enable-which-key-integration))
  :commands (lsp lsp-deferred)
  :config
  (setq lsp-keymap-prefix "C-s-l"
        lsp-prefer-capf t))

(use-package lsp-ui
  :commands lsp-ui-mode)

(use-package lsp-ivy
  :commands lsp-ivy-workspace-symbol)

(use-package lsp-treemacs
  :commands lsp-treemacs-errors-list)

editorconfig

EditorConfig plugin for Emacs

Source: GitHub: editorconfig/editorconfig-emacs

(use-package editorconfig
  :ensure t
  :config
  (editorconfig-mode 1))

Docker

I use Docker and more importantly docker-compose for development. Thanks to Philippe Vaucher I can manage it from within Emacs.

Source
GitHub: Silex/docker.el
(use-package docker
  :bind ("C-c M-d" . docker))

Language-Specific Programming Packages

Go

Emacs mode for the Go programming language

Source: dominikh/go-mode.el

(use-package go-mode
  :mode "\\.go\\'")

PHP

PHP major mode for Emacs.

Source: GitHub: emacs-php/php-mode

(use-package php-mode
  :mode "\\.php\\'"
  :config (setq php-mode-coding-style 'PSR2))

TypeScript

TypeScript-support for Emacs

Source: GitHub: emacs-typescript/typescript.el

(use-package typescript-mode
  :mode "\\.ts\\'")

Web Mode

web-mode.el is an autonomous emacs major-mode for editing web templates.

Source: GitHub: fxbois/web-mode

(use-package web-mode
  :mode (("\\.phtml\\'" . web-mode)
         ("\\.tpl\\.php\\'" . web-mode)
         ("\\.[agj]sp\\'" . web-mode)
         ("\\.as[cp]x\\'" . web-mode)
         ("\\.erb\\'" . web-mode)
         ("\\.mustache\\'" . web-mode)
         ("\\.djhtml\\'" . web-mode)
         ("\\.vue\\'" . web-mode)
         ("\\.blade\\.php\\'" . web-mode))
  :config
  (setq web-mode-script-padding 0)
  (setq web-mode-code-indent-offset 2)
  (setq web-mode-markup-indent-offset 2))

TypoScript

TypoScript major mode for Emacs.

Source: GitHub: ksjogo/typoscript-mode

(use-package typoscript-mode
  :mode "\\.typoscript\\'")
(use-package js
  :config
  (setq js-indent-level 2))

YAML

YAML major mode for Emacs.

Source: GitHub: yoshiki/yaml-mode

(use-package yaml-mode
  :mode (("\\.yaml\\'" . yaml-mode)
         ("\\.yml\\'" . yaml-mode)))

MoinMoin

MoinMoin mode for Emacs.

Source: MoinMoin wiki: EmacsForMoinMoin

(use-package moinmoin-mode
  :load-path "lisp/moinmoin-mode/"
  :mode "\\.wiki\\'"
  :commands (moinmoin-mode))

Emacs Multimedia System (EMMS)

For notes, tasks, writing, and countless other things there is org-mode.

For professional typesetting with TeX and LaTeX there is AucTeX.

And for playing music in Emacs there is EMMS.

(use-package emms
  :init
  (emms-all)
  (emms-default-players)
  :config
  (setq emms-source-file-default-directory "~/Music"))

ERC (the Emacs IRC client)

The beast itself

For joining IRC channels I use ERC. For connecting with the network (using my ZNC bouncer) I would like to just press a keybinding. Therefore I warp the call to erc-tls with the correct arguments in the following function (which is later bound to C-c i j).

(defun mmk2410/erc-connect ()
  "Start a IRC session with ERC by connecting to my ZNC bouncer."
  (interactive)
  (erc-tls :server "znc.mmk2410.org" :port "6697" :nick "mmk2410"
           :full-name "Marcel Kapfer"))

Now the configuration for ERC. Some stuff I found in the deepest depths of the internet. Worth mentioning is the EmacsWiki and the following two videos by David Wilson on the System Crafters YouTube channel: Chat Like the 90's in Emacs with ERC and System Crafters Live! - Improving the IRC Experience in ERC.

(use-package erc
  :config
  ;; Display styling
  (setq erc-fill-column 100
        erc-fill-function 'erc-fill-static
        erc-fill-static-center 20)
  ;; Emacs behavior
  (setq erc-rename-buffers t
        erc-kill-buffer-on-part t
        erc-kill-queries-on-quit t
        erc-kill-server-buffer-on-quit t)
  ;; IRC behavior
  (setq erc-hide-list '("JOIN" "PART" "QUIT" "NICK" "MODE" "AWAY")
        erc-join-buffer 'bury
        erc-autojoin-timing 'ident
        erc-track-exclude '("#guix")
        erc-track-exclude-types '("JOIN" "NICK" "QUIT" "MODE" "AWAY" "PART")
        erc-track-exclude-server-buffer t)
  :bind (
         ("C-c i c" . 'mmk2410/erc-connect)
         :map erc-mode-map
         ("RET" . nil)
         ("C-c <C-return>" . 'erc-send-current-line)))

Additional packages

Highlight nicknames in ERC.

Source: leathekd/erc-hl-nicks

(use-package erc-hl-nicks
  :after erc
  :config
  (add-to-list 'erc-modules 'hl-nicks))

Fetch and show received images in a ERC buffer

Source: kidd/erc-image.el

(use-package erc-image
  :after erc
  :config
  (setq erc-image-inline-rescale 600)
  (add-to-list 'erc-modules 'image))

Emacs Web Wowser (EWW)

Normally I use Firefox for browsing the web, but when I am working with full focus in Emacs I sometimes prefer eww. For example while reading a paper and taking notes on it. In this case I don't want to lose my focus while switching to a web browser (and potentially see some open tabs from another project).

In recent times I have gotten really bad search results on DuckDuckGo (either I can't search anymore or they really got worse). So I'm using Startpage at the moment.

More important for me while reading a paper is looking up some things on Wikipedia. So I defined – additionally to a simple keybinding for eww itself – a function and a keybinding for searching on the English Wikipedia.

(eval-and-compile
  (defun eww-browse-wikipedia-en ()
    (interactive)
    (let ((search (read-from-minibuffer "Wikipedia (EN) search: ")))
      (eww-browse-url
       (concat "https://en.wikipedia.org/w/index.php?search=" search)))))

(eval-and-compile
  (defun eww-browser-english-dict ()
    (interactive)
    (let ((search (read-from-minibuffer "Dictionary (EN) search: ")))
      (eww-browse-url
       (concat "https://www.merriam-webster.com/dictionary/" search)))))

(use-package eww
  :config
  (setq eww-search-prefix "https://startpage.com/search/?q=")
  :bind (("C-c w b" . 'eww)
         ("C-c w d" . 'eww-browser-english-dict)
         ("C-c w w" . 'eww-browse-wikipedia-en)))

Terminals & Shells

vterm

A fully-fledged terminal emulator based on libvterm.

Source: GitHub: akermu/emacs-libvterm

(use-package vterm
  :if (not (eq system-type 'windows-nt))
  :bind (("C-c t" . 'vterm)))

eshell

Let's try out eshell.

(use-package eshell
  :bind (("C-c s" . 'eshell)))

Elfeed

Since now I used many different RSS feed solutions. I started with Feedly back in 2013/14, switched later to a self-hosted TinyTinyRSS and then to Nextcloud News. After some time using this solution, its many bugs annoyed me more and more and I finally switched to FreshRSS in early 2021. Since early October I'm trying Miniflux. Although I had no problems with FreshRSS the interface of Miniflux (especially on mobile) is IMO nicer (though a little bit more eye-candy would be nice).

Mostly I use Miniflux through its web app—on mobile and on the desktop as well. But I have used and liked elfeed in the past and so I keep it configured properly.

elfeed package

This is the elfeed program itself.

As with email I prefer showing older entries first so I can catch-up with these first before getting to newer stuff. Other than the binding to C-c e there is nothing interesting left to see here.

Source: GitHub: skeeto/elfeed

(use-package elfeed
  :demand t
  :config (setq elfeed-sort-order 'ascending)
  :bind (("C-c e" . elfeed)))

Storing articles

Currently I am not using this since I need the actual articles offline for reading in some situation and therefore currently rely on Pocket.

I like to call myself quite an avid org-mode user and so I also want to store RSS articles I read with Elfeed in a reading list. Since from time to time I prefer to use my tablet or smartphone to read instead of sitting at the computer and it is a little bit cumbersome to use Emacs with Elfeed using Termux I prefer using the fantastic Orgzly app. The problem is now that I cannot just org-store-link in the org-capture template for Elfeed entries since this would store a link to the corresponding Elfeed entry. While I can open such links at my desktop I cannot in Orgzly. And also on desktop I most likely will open the article in my web browser instead of viewing just the RSS entry. Therefore it would be very practical if the capture template would not store a Elfeed link but directly the web link. Additionally would also like to store the Feed title as part of the note.

Therefore I need a org-capture template which fetches these functions. And since I can only get the Elfeed entry properties in the buffer with the entry I cannot just use the %(EXP) option alone but need a function that switches to the Elfeed buffer, gets the property and then inserts the value in the capture template. For this I wrote the following function which does exactly this (thanks to RichieHH who put me on the right track).

(defun mmk2410/org-capture-elfeed-entry-props (prop-function)
  (save-window-excursion
    (switch-to-buffer (org-capture-get :original-buffer))
    (funcall prop-function elfeed-show-entry)))

Using this function I can now define a org-capture template for my purpose.

(add-to-list 'org-capture-templates
             '("a" "RSS Article" entry (file "~/org/reading.org")
               "* TODO %(elfeed-feed-title (mmk2410/org-capture-elfeed-entry-props 'elfeed-entry-feed)): [[%(mmk2410/org-capture-elfeed-entry-props 'elfeed-entry-link)][%(mmk2410/org-capture-elfeed-entry-props 'elfeed-entry-title)]]\n  :PROPERTIES:\n  :CREATED: %U\n  :END:\n"
               :immediate-finish t))

elfeed-protocol

elfeed-protocol is a addition to Elfeed which enables the usage of some cloud RSS services (e.g. Nextcloud News, TT-RSS).

I currently use it in combination with my Miniflux instance (previously I used it with Nextcloud News and FreshRSS). The reason why I not only use elfeed with a local feed list (managed for example by elfeed-org) is that a cloud service can check for new feed articles continuously and I can read articles on any device.

Source: GitHub: fasheng/elfeed-protocol

(use-package elfeed-protocol
  :init
  ;; FreshRSS (self-hosted)
  ;; Disabled since I currently use Miniflux
  (setq elfeed-feeds '(("fever+https://mmk2410@rss.mmk2410.org"
                        :api-url "https://rss.mmk2410.org/api/fever.php"
                        :use-authinfo t)))
  ;; Miniflux (self-hosted)
  ;; (setq elfeed-feeds '(("fever+https://mmk2410@miniflux.rss.mmk2410.org"
  ;;                       :api-url "https://miniflux.rss.mmk2410.org/fever/"
  ;;                       :use-authinfo t)))
  (elfeed-protocol-enable))

mu4e

Managing e-mails is maybe one of the most critical tasks in my life. Over the last years I tried countless different clients, including the famous (neo)mutt, KMail, and Thunderbird. But it seems that I always go back to one specific: mu4e. Maybe because it is integrated in Emacs and Org-mode, maybe because I like using a keyboard-focused client and that's where I end. I don't now. But what I know is: I enjoy using it!

Load mu4e

Not all systems that I use have mu/mu4e installed. Either because I cannot really use it at work or because I'm running a native Windows instance or because I didn't completely setup the machine. Therefore I only load/execute the complete mu4e configuration if Emacs can find a mu executable.

(when (executable-find "mu") ;; Closed after all mu4e configuration

Not all distributions that I use have a enough up-to-date package of mu/maildir-utils in their repositories. Therefore on some system I compile and install it myself which ends in /usr/local instead of just /usr. Therefore I have a small function that finds the correct load-path for the mu4e package.

(eval-and-compile
  (defun mmk2410/mu4e-load-path ()
    (let* ((mu4e-site-lisp-path "/share/emacs/site-lisp/mu4e")
           (mu4e-load-path-usr (concat "/usr" mu4e-site-lisp-path))
           (mu4e-load-path-usr-local (concat "/usr/local" mu4e-site-lisp-path)))
      (cond ((file-directory-p mu4e-load-path-usr)
             mu4e-load-path-usr)
            ((file-directory-p mu4e-load-path-usr-local)
             mu4e-load-path-usr-local)
            (t (error "mu4e not found!"))))))

Using the determined load path I now load mu4e itself and bind it to a handy keybinding. Since I don't close the parenthesis of the use-package everything that follows this source code blocks is part of the config section.

(use-package mu4e
  :load-path (lambda () (list (mmk2410/mu4e-load-path)))
  :demand t
  :bind ("C-c m" . mu4e)
  :config ;; final closing parens at th end of the file

Once it is loaded I'd like to set mu4e as my default mail program in Emacs.

(setq mail-user-agent 'mu4e-user-agent)

Fetching new mail

I use mbsync for synchronising my messages and prefer that I can trigger the refresh from mu4e. Normally I use a SystemD user service and timer for running mbsync every five minutes.

Apparently mbsync requires a filename change, when moving mails.

(setq mu4e-get-mail-command "mbsync -a"
      mu4e-change-filenames-when-moving t)

Header view customizations

I'd like to use the IS-8601 date format.

(setq mu4e-headers-date-format "%Y-%m-%d %H:%M")

For settings my header fields, I want to define a custom header field, which shows just an excerpt of my maildir path. My paths – created by mbsync – begin with the account name, which I want to shorten to just one letter. Since I archive my mail but let mu4e show me related mails, I want also to shorten the "Archive" path to "A". So the maildir path my be a short as e.g "m/INBOX" or "m/A".

(add-to-list 'mu4e-header-info-custom
             '(:mdir .
                     ( :name "Shortend Maildir path"
                             :shortname "Maildir"
                             :help "Shows a collapsed maildir path"
                             :function (lambda (msg)
                                         (let ((maildir (or (mu4e-message-field msg :maildir) "")))
                                           (cond ((string-match-p "Archives/" maildir)
                                                  (replace-regexp-in-string "^/\\(.\\).*/\\(.\\).*/\\(.*\\)" "\\1/\\2/\\3" maildir))
                                                 ((string-match-p "Archives" maildir)
                                                  (replace-regexp-in-string "^/\\(.\\).*/\\(.\\).*" "\\1/\\2" maildir))
                                                 (t
                                                  (replace-regexp-in-string "^/\\(.\\).*/\\(.*\\)" "\\1/\\2" maildir))))))))

I want to use the IMO improved :from-or-to header field instead of only a :from. Next to that I prefer to show the subject in a thread only once for a cleaner overview.

(setq mu4e-headers-fields
      '((:human-date . 16)
        (:flags . 4)
        (:mdir . 12)
        (:mailing-list . 10)
        (:from-or-to . 22)
        (:thread-subject)))

Additionally I prefer to change the sorting directory so that older messages are displayed on top. Since I try to keep an empty inbox and either reply to mails instantly or create a todo from them handling "old" mails should one the one hand be rare and on the other hand much more urgent. Actually I want them to distract and annoy me so that I handle them sooner than later.

(setq mu4e-headers-sort-direction 'ascending)

As already said, I like to archive my mail and have as few mailboxes as possible. Since I get some mails over different addresses and in different mailboxes mu4e displays only one and doesn't show the duplicate. So when I mark the message I see for archiving and refresh the list while applying the mark the then hidden duplicate is now shown and I have to mark that again for archiving. Since the mailbox is shown I also can tell which one to archive if necessary. That is why I disable the skipping of duplicates:

(setq mu4e-headers-skip-duplicates nil)

Message view customizations

By default mu4e jumps to the next message when pressing SPC at the end of a message. I explicitly do not want that behavior.

(setq mu4e-view-scroll-to-next nil)

I like to keep my Downloads folder quite empy and also download everything that I don't intent to keep to /tmp.

(setq mu4e-attachment-dir "/tmp")

The mu4e manual says, that viewing inline images may be not always correct. Let's try it anyway and find out how bad it really is. Additionally it is recommended, that imagemagick-register-types is called.

(setq mu4e-view-show-images t)
(when (fboundp 'imagemagick-register-types)
  (imagemagick-register-types))

I like to see the complete addresses and not just the name.

(setq mu4e-view-show-addresses t)

Compose messages

I prefer to BCC myself in mail so I now that a message was handled by the mail server and thereby I can also put all mails of a thread in the same folder which makes searching a bit easier. So I let mu4e automatically set the BCC header when I start to compose a message.

(add-hook 'mu4e-compose-mode-hook
          (lambda ()
            (save-excursion
              (message-add-header (concat  "Bcc: " user-mail-address "\n")))))

Since I get the emails via the BCC it is useless to also put them in a send folder.

(setq mu4e-sent-messages-behavior 'delete)

Don't keep "zombie" buffers around after sending a mail.

(setq message-kill-buffer-on-exit t)

Also don't reply to myself in case a "reply to all".

(setq mu4e-compose-dont-reply-to-self t)

I'd like to send my messages as flowed format.

(setq mu4e-compose-format-flowed t)

I like a "special" citation line…

(setq message-citation-line-format "%f @ %Y-%m-%d %T %Z:\n"
      message-citation-line-function 'message-insert-formatted-citation-line)

Sending mail

Since I already have a msmtp client configured and would also use it in case I would use mutt, I want to use it also in mu4e. Setting msmtp as default SMTP client is quite easy but is it a little bit harder to set the account that should be used for sending. Therefore I wrote a lambda function that chooses the account and passes it as extra arguments based on the senders email address.

(setq message-send-mail-function 'message-send-mail-with-sendmail
      sendmail-program "msmtp"
      message-sendmail-envelope-from 'header)
(add-hook 'message-send-mail-hook
          (lambda ()
            (save-excursion
              (let* ((from (save-restriction
                             (message-narrow-to-headers)
                             (message-fetch-field "from")))
                     (account (cond
                               ((string-match "@uni-ulm.de" from) "uniulm")
                               ((string-match "@emplify-software.de" from) "work")
                               (t "mailbox"))))
                (setq message-sendmail-extra-arguments (list '"-a" account))))))

Define custom bookmarks

I don't really like the default bookmarks that mu4e sets. Thats why I overwrite the mu4e-bookmarks variable completely instead of just appending new bookmarks.

(if (mmk2410/work)
  (setq mu4e-bookmarks
        '(( :name "Unread work messages"
                  :key ?w
                  :query "(flag:unread and m:/work/*) or m:/work/inbox" )
          ( :name "Unread private messages"
                  :key ?p
                  :query "(flag:unread and m:/mailbox/*) or m:/mailbox/inbox" )))
  (setq mu4e-bookmarks
        '(( :name "Unread messages"
                  :key ?u
                  :query "flag:unread or m:/university/inbox or m:/university/uulmhack or m:/mailbox/inbox" )
          ( :name "Unread university messages"
                  :key ?e
                  :query "(flag:unread and m:/university/*) or m:/university/inbox or m:/university/uulmhack" )
          ( :name "Unread private messages"
                  :key ?p
                  :query "(flag:unread and m:/mailbox/*) or m:/mailbox/inbox" ))))

Define some shortcuts

I like to get to some maildir quite fast and with a shortcut.

(if (mmk2410/work)
    (setq mu4e-maildir-shortcuts
          '((:maildir "/work/inbox" :key ?w)
            (:maildir "/mailbox/inbox" :key ?p)))
    (setq mu4e-maildir-shortcuts
          '((:maildir "/university/inbox" :key ?e)
            (:maildir "/mailbox/inbox" :key ?p))))

Org-Capture template and key bindings

I use org-mode heavily and also manage my todos within it. Since my INBOX is not a todo list I like the idea of quickly add a mail as a todo.

(if (mmk2410/work)
  (add-to-list 'org-capture-templates
               '("m" "mail" entry (file "~/org/work.inbox.org")
                 "* TODO %a%?\n  :PROPERTIES:\n  :CREATED: %U\n  :END:\n"))
  (add-to-list 'org-capture-templates
               '("m" "mail" entry (file "~/org/inbox.org")
                 "* TODO %a%?\n  :PROPERTIES:\n  :CREATED: %U\n  :END:\n")))

mu4e-contexts

Multiple mail accounts in mu4e can be achieved by using so called contexts.

First of all I like mu4e to only ask me, which content to use, if it can't figure it you.

(setq mu4e-context-policy 'ask
      mu4e-compose-context-policy 'ask)

Now define the contexts.

(if (mmk2410/work)
  (setq mu4e-contexts
        `( ,(make-mu4e-context
             :name "Private"
             :enter-func (lambda () (mu4e-message "Entering private context"))
             :match-func (lambda (msg)
                           (when msg
                             (string-prefix-p "/mailbox" (mu4e-message-field msg :maildir))))
             :vars '( ( user-mail-address . "me@mmk2410.org" )
                      ( user-full-name . "Marcel Kapfer" )
                      ( message-signature-file . nil)
                      ( mu4e-sent-folder . "/mailbox/Sent" )
                      ( mu4e-drafts-folder . "/mailbox/Drafts" )
                      ( mu4e-trash-folder . "/mailbox/Trash" )
                      ( mu4e-refile-folder . "/mailbox/Archives" )))
           ,(make-mu4e-context
             :name "Work"
             :enter-func (lambda () (mu4e-message "Entering work context"))
             :match-func (lambda (msg)
                           (when msg
                             (string-prefix-p "/work" (mu4e-message-field msg :maildir))))
             :vars '( ( user-mail-address . "m.kapfer@emplify-software.de" )
                      ( user-full-name . "Marcel Kapfer" )
                      ( message-signature-file . "~/dotfiles/dot-work/signature/mu4e")
                      ( mu4e-sent-folder . "/work/Sent Items" )
                      ( mu4e-drafts-folder . "/work/Drafts" )
                      ( mu4e-trash-folder . "/work/Trash" )
                      ( mu4e-refile-folder . "/work/Archive" )))))
  (setq mu4e-contexts
        `( ,(make-mu4e-context
             :name "Private"
             :enter-func (lambda () (mu4e-message "Entering Private context"))
             :match-func (lambda (msg)
                           (when msg
                             (string-prefix-p "/mailbox" (mu4e-message-field msg :maildir))))
             :vars '( ( user-mail-address . "me@mmk2410.org" )
                      ( user-full-name . "Marcel Kapfer" )
                      ( message-signature-file . nil)
                      ( mu4e-sent-folder . "/mailbox/Sent" )
                      ( mu4e-drafts-folder . "/mailbox/Drafts" )
                      ( mu4e-trash-folder . "/mailbox/Trash" )
                      ( mu4e-refile-folder . "/mailbox/Archives" )))
           ,(make-mu4e-context
             :name "uulmhack"
             :enter-func (lambda () (mu4e-message "Entering uulmhack context"))
             :match-func (lambda (msg)
                           (when msg
                             (or (string-prefix-p "/university/uulmhack" (mu4e-message-field msg :maildir))
                                 (string-prefix-p "/university/Archives/uulmhack" (mu4e-message-field msg :maildir)))))
             :vars '( ( user-mail-address . "uulmhack@uni-ulm.de" )
                      ( user-full-name . "uulmhack Orga-Team" )
                      ( message-signature-file . "~/dotfiles/dotdotfiles/sig/uulmhack")
                      ( mu4e-sent-folder . "/university/Sent" )
                      ( mu4e-drafts-folder . "/university/Drafts" )
                      ( mu4e-trash-folder . "/university/Trash" )
                      ( mu4e-refile-folder . "/university/Archives/uulmhack" )))
           ,(make-mu4e-context
             :name "Education"
             :enter-func (lambda () (mu4e-message "Entering education context"))
             :match-func (lambda (msg)
                           (when msg
                             (string-prefix-p "/university" (mu4e-message-field msg :maildir))))
             :vars '( ( user-mail-address . "marcel.kapfer@uni-ulm.de" )
                      ( user-full-name . "Marcel Kapfer" )
                      ( message-signature-file . nil)
                      ( mu4e-sent-folder . "/university/Sent" )
                      ( mu4e-drafts-folder . "/university/Drafts" )
                      ( mu4e-trash-folder . "/university/Trash" )
                      ( mu4e-refile-folder . "/university/Archives" ))))))

Other settings

The org-mime package thankfully allows sending HTML mails from mu4e. Not that I like HTML mails but I need to have a special HTML message signature for work.

(use-package org-mime
  :after (org mu4e)
  :config (setq org-mime-library 'mml))

Mu4e displays a message in the minibuffer after refreshing. And since I run mbsync and mu index (through emacsclient) automatically every few minutes these messages can get quite distracting. Luckily mu4e provides an option to disable these index messages.

(setq mu4e-hide-index-messages t)

Mu4e show its current search in the modeline and as you may have already noticed, my default searches tend to be large. So I increase the shown characters from 30 to 60.

(setq mu4e-modeline-max-width 60)

It is maybe not surprising that I am subscribed to some mailing lists. Some of these have quite some long names and so I would like to use a some alias for the display of these names. Thanks god mu4e also provides a possibility for this!

(setq mu4e-user-mailing-lists '(("debian-user.lists.debian.org" . "DebUsr")
                                ("debian-security-announce.lists.debian.org" . "DebSecAnn")))

Finally the mu4e configuration is done! So lets close the parenthesis left open since the use-package call and the (when call.

) ;; closing parens for (use-package mu4e
) ;; closing parens for (when (executable-find "mu")

Email with Evolution

Since my work place is using Microsoft Outlook for Email including its appointment features I sadly needed to switch away from mu4e to Evolution, as recommended by Karl Voit. Using his configuration as an inspiration and source I could setup the following workflow.

I first define the path to my evolution binary.

(setq mmk2410/evolution-path "/usr/bin/evolution")

Then I copied and slightly adjusted Karl Voits function for handling message-id link in Org mode.

(defun mmk2410/open-message-id-in-evolution (message-id)
  (interactive)
  (start-process
   (concat "mid: " message-id)
   nil
   mmk2410/evolution-path
   (concat "mid:<" message-id ">")))

(org-link-set-parameters "messageid" :follow #'mmk2410/open-message-id-in-evolution)

The following function from him takes care of the other way: bringing a message as a link into Org mode.

(defun mmk2410/convert-mail-header-to-org-link ()
  "Assumes an email header in the killring, parses it and returns an org mode link for it."
  (interactive)
  (with-temp-buffer
    (save-match-data
      (yank)

      (goto-char (point-min))
      (re-search-forward "^Message-Id:.+<\\(.+\\)>[ ]*$" nil nil 1)
      (setq message-id (match-string 1))

      (goto-char (point-min))
      (re-search-forward "^From:[        ]+\\(.+?\\)[ ]*$" nil nil 1)
      (setq from (match-string 1))

      (goto-char (point-min))
      (re-search-forward "^Subject:[     ]+\\(.+?\\)[ ]*$" nil nil 1)
      (setq subject (match-string 1))

      (goto-char (point-min))
      (re-search-forward "^Date:[        ]+\\(.+?\\)[ ]*$" nil nil 1)
      (setq rawdate (match-string 1))
      (setq date
            (let ((time (date-to-time rawdate)))
              (set-time-zone-rule t)
              (prog1 (format-time-string "%Y-%m-%d %H:%M" time)
                (set-time-zone-rule nil))))))
  (insert (concat "[[messageid:" message-id "][" date " " from ": " subject "]]")))

(global-set-key (kbd "C-c M-m") 'mmk2410/convert-mail-header-to-org-link)

Helpers

Tab Bar Setup

Since version 27 Emacs features a tab bar. In contrast to e.g. a browser a tab does not display just one file/buffer/window but an Emacs tab features an entire window configuration. Since I use Emacs not just for programming but (perhaps even mainly) for personal information management (including mail, agenda, journal, IRC and RSS) I had the idea to have one Emacs frame open with different tabs for all these things. Therefore I wrote a small package found in packages/mmk2410-tab-bar-helpers.el which I explain in a dedicated blog post.

(use-package mmk2410-tab-bar-helpers
  :after (hydra)
  :load-path "packages/"
  :bind ("C-c f" . mmk2410/tab-bar/body))

Author: Marcel Kapfer

Created: 2022-03-30 Wed 17:03