dot-emacs/config.org

2277 lines
80 KiB
Org Mode

#+TITLE: Emacs Configuration file
#+AUTHOR: Marcel Kapfer
#+EMAIL: opensource(at)mmk2410(dot)org
# Source: https://gitlab.com/mmk2410/dot-emacs
# License: This file is licensed under the GPL v3
#+STARTUP: indent overview
*Welcome!* You found my (Marcel Kapfer aka mmk2410) personal Emacs configuration file. Please feel free to read through it, use it or get inspiration!
This file (=config.org=) is written in [[https://orgmode.org/][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:
- [[https://karl-voit.at/][Karl Voit]] especially for his [[https://karl-voit.at/2021/06/01/Thunderbird-to-Evolution][Evolution email workflow]] and this [[https://karl-voit.at/2017/06/03/emacs-org/][setup approach]] ([[https://github.com/novoid/dot-emacs/blob/master/config.org][his Emacs config]])
- [[https://daviwil.com/][David Wilson]] and his [[https://www.youtube.com/c/SystemCrafters][System Crafters]] YouTube channel for his [[https://www.youtube.com/c/SystemCrafters/videos][Emacs videos]] ([[https://config.daviwil.com/emacs][his Emacs config]])
/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 [[https://git.mmk2410.org/mmk2410/dotfiles/src/commit/f6d220554ab0946a1a4efb7e3400194139a99550/emacs/emacs.el][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.
** Personal Information
#+begin_src emacs-lisp
(setq user-full-name "Marcel Kapfer"
user-mail-address "opensource@mmk2410.org")
#+end_src
** Date & Time
Set the first day of the week to Mondays.
#+begin_src emacs-lisp
(setq calendar-week-start-day 1)
#+end_src
Automatically create and save UIDs when exporting an iCalendar using Org.
#+begin_src emacs-lisp
(setq org-icalendar-store-UID t)
#+end_src
Set timezone for Org iCalendar export.
#+begin_src emacs-lisp
(setq org-icalendar-timezone "Europe/Berlin")
#+end_src
Use 24h time display including day and date.
#+begin_src emacs-lisp
(setq display-time-24hr-format t
display-time-day-and-date t)
#+end_src
Use a sane date style.
#+begin_src emacs-lisp
(setq calendar-date-style 'european)
#+end_src
** Turn off backup files
#+begin_src emacs-lisp
(setq-default backup-inhibited t)
#+end_src
** Performance tweaks
As recommended by [[https://emacs-lsp.github.io/lsp-mode/page/performance/][lsp-mode]],
#+begin_src emacs-lisp
(setq gc-cons-threshold 100000000
read-process-output-max (* 1024 1024))
#+end_src
** Disable adding double spaces after periods
Source: [[http://www.reddit.com/r/emacs/comments/2l5gtz/real_sentence_in_emacs/][Real sentence in Emacs : emacs]]
#+begin_src emacs-lisp
(setq-default sentence-end-double-space nil)
#+end_src
** Enable truncate lines mode
Sources:
- https://www.emacswiki.org/emacs/TruncateLines
- [[http://stackoverflow.com/questions/7577614/emacs-truncate-lines-in-all-buffers][elisp - Emacs truncate lines in all buffers - Stack Overflow]]
#+begin_src emacs-lisp
(setq-default truncate-lines t)
(setq-default global-visual-line-mode t)
(add-hook 'org-mode-hook 'visual-line-mode)
#+end_src
** Delete trailing white-space
Source: [[https://www.emacswiki.org/emacs/DeletingWhitespace][EmacsWiki: Deleting Whitespace]]
#+begin_src emacs-lisp
(add-hook 'before-save-hook 'delete-trailing-whitespace)
#+end_src
** Require final newline
#+begin_src emacs-lisp
(setq require-final-newline t)
#+end_src
** Keep Emacs custom settings in a seperate file
This includes creating the file if not existent.
#+begin_src emacs-lisp
(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)
#+end_src
** Always follow VCS symlinks
#+begin_src emacs-lisp
(setq vc-follow-symlink t)
#+end_src
** Indent using spaces, not tabs
#+begin_src emacs-lisp
(setq-default indent-tabs-mode nil)
#+end_src
** Save minibuffer histroy
#+begin_src emacs-lisp
(savehist-mode 1)
#+end_src
** Auto-revert externally changed files
#+begin_src emacs-lisp
(global-auto-revert-mode t)
(setq global-auto-revert-non-file-buffers t)
#+end_src
** Save cursor place in file for next visit
#+begin_src emacs-lisp
(save-place-mode t)
#+end_src
** Spell checking
Enable aspell. Options set according to [[https://www.emacswiki.org/emacs/FlySpell][Emacswiki: FlySpell]]
#+begin_src emacs-lisp
(setq-default ispell-program-name "aspell")
(setq ispell-list-command "--list")
#+end_src
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 [[https://www.msys2.org/][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][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 [[https://ftp.gnu.org/gnu/aspell/dict/0index.html][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.
#+begin_src emacs-lisp
(setq initial-scratch-message nil)
(setq initial-major-mode 'org-mode)
#+end_src
** doc-view
Set doc-view resolution to 300 for sharp PDFs.
#+begin_src emacs-lisp
(setq doc-view-resolution 300)
#+end_src
** Raise emacsclient frame on open
Raise new ~emacsclient~ frames when created.
Source: [[https://gitlab.com/bkhl/dotfiles/-/blob/master/.emacs.d/configuration.org][bkhl's dotfiles]]
#+begin_src emacs-lisp
(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)
#+end_src
** Enable electric pair mode
=electric-pair-mode= enables adding closing parenthesis & co when opening one.
#+begin_src emacs-lisp
(electric-pair-mode 1)
#+end_src
** 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.
#+begin_src emacs-lisp
(add-hook 'prog-mode-hook 'subword-mode)
#+end_src
** Make scripts executable when saving
Files that start with =#!= should be made executable on save.
#+begin_src emacs-lisp
(add-hook 'after-save-hook
'executable-make-buffer-file-executable-if-script-p)
#+end_src
** Follow symlinks automatically
When visiting a symlink, follow it automatically.
#+begin_src emacs-lisp
(setq vc-follow-symlinks t)
#+end_src
** Dired customizations
Show filesize human readable.
#+begin_src emacs-lisp
(setq-default dired-listing-switches "-alh")
#+end_src
* Package configuration
** General configuration
Add relevant archives.
#+begin_src emacs-lisp
(add-to-list 'package-archives '("nongnu" . "https://elpa.nongnu.org/nongnu/") t)
(add-to-list 'package-archives '("melpa" . "https://melpa.org/packages/") t)
#+end_src
Configure the package manager to prefer newer packages and to not load packages automatically on startup.
#+begin_src emacs-lisp
(setq-default load-prefer-newer t)
(setq-default package-enable-at-startup nil)
#+end_src
** use-package
Install/enable use-package.
#+begin_src emacs-lisp
(unless (package-installed-p 'use-package)
(package-refresh-contents)
(package-install 'use-package))
(require 'use-package)
#+end_src
Configure use-package to load packages by default.
#+begin_src emacs-lisp
(setq use-package-always-ensure t)
#+end_src
* Visual appearance
I don't agree with some visual defaults, so I redefine them.
** Short yes/no questions
#+begin_src emacs-lisp
(defalias 'yes-or-no-p 'y-or-n-p)
#+end_src
** Confirm before closing emacs
#+begin_src emacs-lisp
(setq confirm-kill-emacs #'y-or-n-p)
#+end_src
** Disable the startup screen
Source: [[https://stackoverflow.com/a/744681][Stack Overflow: Unable to hide welcome screen in Emacs]]
#+begin_src emacs-lisp
(setq inhibit-startup-screen t)
#+end_src
** Replace BEEP by visual-bell
#+begin_src emacs-lisp
(setq visible-bell 1)
#+end_src
** Disable cursor blinking
#+begin_src emacs-lisp
(blink-cursor-mode -1)
#+end_src
** Highlight current line
#+begin_src emacs-lisp
(add-hook 'text-mode-hook #'hl-line-mode)
#+end_src
** Show line numbers
#+begin_src emacs-lisp
(add-hook 'text-mode-hook #'display-line-numbers-mode)
(add-hook 'prog-mode-hook #'display-line-numbers-mode)
#+end_src
And disable this for some modes.
#+begin_src emacs-lisp
(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)))
#+end_src
** Replace selected text by typing
#+begin_src emacs-lisp
(delete-selection-mode t)
#+end_src
** Vertical bar as cursor
#+begin_src emacs-lisp
(setq-default cursor-type 'bar)
#+end_src
** 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.
#+begin_src emacs-lisp
(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))
#+end_src
** Highlight matching parenthesis
#+begin_src emacs-lisp
(show-paren-mode 1)
#+end_src
** 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 [[https://github.com/baskerville/bspwm/issues/551][GitHub: baskerville/bspwm #551]]) solves this problem.
#+begin_src emacs-lisp
(setq frame-resize-pixelwise t)
#+end_src
** 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.
#+begin_src emacs-lisp
(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)
#+end_src
** Set theme
Installing and enabling Gruvbox light theme. /Disabled in favor of the Doom themes./
Source: [[https://github.com/greduan/emacs-theme-gruvbox][Github: greduan/emacs-theme-gruvbox]]
#+begin_src emacs-lisp
(use-package gruvbox-theme
:disabled t
:config
(load-theme 'gruvbox-light-medium t))
#+end_src
Installing and enabling Modus themes. /Disabled in favor of the Doom themes./
#+begin_src emacs-lisp
(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))
#+end_src
Installing and enabling Doom themes.
#+begin_src emacs-lisp
(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))
#+end_src
Add a slight transparency to the frames. Code provided by David Wilson / System Crafters [[https://github.com/daviwil/emacs-from-scratch/blob/master/Emacs.org][Emacs from Scratch]] video series.
#+begin_src emacs-lisp
(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))
#+end_src
** 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.
#+begin_src emacs-lisp
(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))
(defun mmk2410/switch-theme-night ()
(interactive)
(mmk2410/switch-theme mmk2410/theme-night))
(defun mmk2410/switch-theme-day ()
(interactive)
(mmk2410/switch-theme mmk2410/theme-day))
#+end_src
** Customize modeline
I'd like to use telephone line as a mode line.
#+begin_src emacs-lisp
(use-package telephone-line
:disabled t
:init (telephone-line-mode t))
#+end_src
I'm currently trying doom-modeline instead of telephone-line.
#+begin_src emacs-lisp
(use-package doom-modeline
:init (doom-modeline-mode 1)
:config (setq doom-modeline-icon t))
#+end_src
Additionally to that I also like to know, in which column I am.
#+begin_src emacs-lisp
(column-number-mode 1)
#+end_src
** 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.
#+begin_src emacs-lisp
(setq tab-bar-close-button-show nil
tab-bar-new-button-show nil)
#+end_src
** Emojis
Minor mode for displaying emojis in Emacs.
Source: [[https://github.com/iqbalansari/emacs-emojify][iqbalansari/emacs-emojify]]
#+begin_src emacs-lisp
(use-package emojify
:ensure t
:hook (erc-mode . emojify-mode)
:commands emojify-mode)
#+end_src
* Keybinding
List of own custom keybindings
** Emacs Lisp related
#+begin_src emacs-lisp
(global-set-key (kbd "C-c x") 'eval-region)
#+end_src
** Ibuffer as default
#+begin_src emacs-lisp
(global-set-key (kbd "C-x C-b") 'ibuffer)
#+end_src
** 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.
#+begin_src emacs-lisp
(unbind-key "C-z")
#+end_src
** The evil side
I currently try out =evil-mode= for evaluating if it is any better for my hand.
Source: [[https://github.com/emacs-evil/evil][GitHub: emacs-evil/evil]]
#+begin_src emacs-lisp
(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))
#+end_src
An even better integration may be achieved when additionall using the [[https://github.com/emacs-evil/evil-collection][evil-collection]] package.
#+begin_src emacs-lisp
(use-package evil-collection
:after evil
:config (evil-collection-init))
#+end_src
* General packages
** diminish-mode
Diminished modes are minor modes with no modeline display
Source: [[https://github.com/myrjola/diminish.el][GitHub: myrjola/diminish.el]]
#+begin_src emacs-lisp
(use-package diminish
:init
(diminish 'abbrev-mode))
#+end_src
** crux
A Collection of Ridiculously Useful eXtensions for Emacs.
Used especially for =crux-delete-file-and-buffer= and =crux-rename-file-and-buffer=
Source: [[https://github.com/bbatsov/crux][GitHub: bbatsov/crux]]
#+begin_src emacs-lisp
(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)))
#+end_src
** which-key
Emacs package that displays available keybindings in popup
Source: [[https://github.com/justbur/emacs-which-key][GitHub: justbur/emacs-which-key]]
#+begin_src emacs-lisp
(use-package which-key
:diminish which-key-mode
:init (which-key-mode)
:config (setq which-key-idle-delay 0.5))
#+end_src
** multiple-cursors
Multiple cursors for emacs.
Source: [[https://github.com/magnars/multiple-cursors.el][GitHub: magnars/multiple-cursors]]
#+begin_src emacs-lisp
(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)))
#+end_src
** magit
A Git Porcelain inside Emacs
Source: [[https://magit.vc/][magit.vc]]
#+begin_src emacs-lisp
(use-package magit
:bind
("C-x g" . magit-status))
#+end_src
** orgit
Support for Org links to Magit buffers.
- Source :: [[https://github.com/magit/orgit][GitHub: magit/orgit]]
- Blog post :: [[https://mmk2410.org/2022/01/21/my-emacs-package-of-the-week-orgit/][My Emacs package of the week: orgit]]
#+begin_src emacs-lisp
(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")))
#+end_src
** diff-hl
Emacs package for highlighting uncommitted changes
Source: [[https://github.com/dgutov/diff-hl][Github: dgutov/diff-hl]]
#+begin_src emacs-lisp
(use-package diff-hl
:config (global-diff-hl-mode t)
:hook (magit-post-refresh-hook . diff-hl-magit-post-refresh))
#+end_src
** project.el
Load the included =project= package.
#+begin_src emacs-lisp
(use-package project)
#+end_src
** 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: [[https://github.com/Alexander-Miller/treemacs][Alexander-Miller/treemacs]]
#+begin_src emacs-lisp
(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))
#+end_src
** counsel, ivy and swiper
Emacs package collection (ivy, swiper and counsel) of flexible, simple tools for minibuffer completion.
Source: [[https://github.com/abo-abo/swiper][GitHub: abo-abo/swiper]]
#+begin_src emacs-lisp
(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)))
#+end_src
** ivy-rich
ivy-rich enhances M-x and similar even more by giving more information directly in the minibuffer.
Source: [[https://github.com/Yevgnen/ivy-rich][GitHub: Yevgnen/ivy-rich]]
#+begin_src emacs-lisp
(use-package ivy-rich
:init (ivy-rich-mode 1))
#+end_src
** avy
Jump to things in Emacs tree-style
Source: [[https://github.com/abo-abo/avy][GitHub: abo-abo/avy]]
#+begin_src emacs-lisp
(use-package avy
:bind
(("C-:" . avy-goto-char)
("C-M-:" . avy-goto-char-2)))
#+end_src
** yasnippet
YASnippet is a template system for Emacs.
Snippets can be put in the ~yas-snippet-dirs~, e.g. =~/.emacs.d/snippets=.
Source: [[https://github.com/joaotavora/yasnippet][GitHub: joaotavora/yasnippet]]
#+begin_src emacs-lisp
(use-package yasnippet
:config (yas-global-mode 1))
#+end_src
** pdf-tools
An advanced Emacs support library for PDF files.
Source: [[https://github.com/politza/pdf-tools][GitHub: politza/pdf-tools]]
#+begin_src emacs-lisp
(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))
#+end_src
** 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.
#+begin_src emacs-lisp
(use-package deft
:bind ("C-c d" . deft)
:commands (deft)
:config (setq deft-directory "~/org"
deft-extensions '("org")
deft-recursive t))
#+end_src
** ace-window
Quickly switch windows in Emacs
Source: [[https://github.com/abo-abo/ace-window][GitHub: abo-abo/ace-window]]
#+begin_src emacs-lisp
(use-package ace-window
:bind (("M-o" . 'ace-window)))
#+end_src
* Org mode configuration
This is my personal org-mode configuration.
** Org modules
#+begin_src emacs-lisp
(use-package org
:pin gnu)
(add-to-list 'org-modules 'org-habit)
(require 'org-protocol)
#+end_src
** General settings
Follow links when pressing enter on them.
#+begin_src emacs-lisp
(setq org-return-follows-link t)
#+end_src
Disable extra indent in src-blocks.
#+begin_src emacs-lisp
(setq org-edit-src-content-indentation 0)
#+end_src
Prevent accidental edits in invisible regions.
#+begin_src emacs-lisp
(setq org-catch-invisible-edits 'error)
#+end_src
Replace the three dots for collapsed stuff with a nicer symbol.
#+begin_src emacs-lisp
(setq org-ellipsis "")
#+end_src
Hide emphasis markers. While this sometimes is very distracting it mostly looks absolutely beautiful.
#+begin_src emacs-lisp
(setq org-hide-emphasis-markers t)
#+end_src
** Org font adjustments
I like to have larger headlines in Org for better identifying them. The configuration of [[https://config.daviwil.com/emacs#fonts-and-bullets][David Wilson]] was a large help in this regard.
#+begin_src emacs-lisp
(with-eval-after-load 'org
(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))))
#+end_src
When using a variable-pitch font this also applies to stuff like code blocks. The following block resets this change for these unwanted faces.
#+begin_src emacs-lisp
(with-eval-after-load 'org
(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))
#+end_src
** Org Superstar Mode
=org-superstar-mode= is a nice package for enriching the display of heading stars as well as list items.
#+begin_src emacs-lisp
(with-eval-after-load 'org
(use-package org-superstar
:hook (org-mode . org-superstar-mode)))
#+end_src
** LaTeX-related settings
Add =scratcl= KOMA-Script class as available LaTeX class.
#+begin_src emacs-lisp
(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}"))))
#+end_src
Set the scrartcl class as default.
#+begin_src emacs-lisp
(setq org-latex-default-class "scrartcl")
#+end_src
Us LuaLaTeX as default LaTeX compiler.
#+begin_src emacs-lisp
(setq org-latex-compiler "lualatex")
#+end_src
Customize LaTeX packages and configurations.
#+begin_src emacs-lisp
(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}"))
#+end_src
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.
#+begin_src emacs-lisp
(setq org-latex-minted-options
'(("baselinestretch" "1.2")
("breaklines" "true")
("fontsize" "\\footnotesize")
("frame" "single")
("framesep" "2mm")
("linenos" "true")
("style" "pastie")))
#+end_src
Removed unused default packages.
#+begin_src emacs-lisp
(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))
#+end_src
Configure the org LaTeX preview to work with LuaLaTeX.
#+begin_src emacs-lisp
(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)
#+end_src
Also adjust the LaTeX preview to be a little bit larger, since it is to small for me.
#+begin_src emacs-lisp
(setq org-format-latex-options (plist-put org-format-latex-options :scale 1.5))
#+end_src
** ToDo-Management and Org Agenda
Set TODO Keywords.
#+begin_src emacs-lisp
(setq org-todo-keywords '((sequence
"TODO(t!)" "NEXT(n!)" "STARTED(a!)" "WAIT(w@/!)" "SOMEDAY(s)"
"|" "DONE(d!)" "CANCELLED(c@/!)")))
#+end_src
Set time tag when re-something on todos
#+begin_src emacs-lisp
(setq org-log-done 'time
org-log-reschedule 'time
org-log-redeadline 'time
org-log-into-drawer t)
#+end_src
Forbid closing of todos with open subtasks.
#+begin_src emacs-lisp
(setq
org-enforce-todo-dependencies t
org-enforce-todo-checkbox-dependencies t)
#+end_src
Set org agenda files.
#+begin_src emacs-lisp
(setq org-agenda-files (list "~/org/")
org-agenda-text-search-extra-files (list "~/org/archive/"
"~/org/roam/"
"~/org/articles/"))
#+end_src
Open org-agenda in current window.
#+begin_src emacs-lisp
(setq org-agenda-window-setup (quote current-window))
#+end_src
Define org-capture templates.
#+begin_src emacs-lisp
(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")))
#+end_src
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
#+begin_src emacs-lisp
(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)))
#+end_src
Warn me on deadlines in the next year.
#+begin_src emacs-lisp
(setq org-deadline-warning-days 365)
#+end_src
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.
#+begin_src emacs-lisp
(setq org-agenda-span 'week)
(setq org-agenda-skip-scheduled-if-deadline-is-shown t)
(setq org-agenda-start-on-weekday nil)
#+end_src
Make the column for categories wider.
#+begin_src emacs-lisp
(setq org-agenda-prefix-format
'((agenda . " %i %-18:c%?-12t% s")
(todo . " %i %-18:c")
(tags . " %i %-18:c")
(search . " %i %-18:c")))
#+end_src
Finally define the org-agenda display using [[https://github.com/alphapapa/org-super-agenda][org-super-agenda]] by alphapapa.
#+begin_src emacs-lisp
(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))))))))))
#+end_src
** 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.
#+begin_src emacs-lisp
(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)))
#+end_src
** 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=
#+begin_src emacs-lisp
(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)
#+end_src
** Nicer display for Org buffers
From [[https://github.com/daviwil/emacs-from-scratch/blob/master/Emacs.org#center-org-buffers][System Crafters' Emacs from Scratch Config]].
#+begin_src emacs-lisp
(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)))
#+end_src
** Hooks
Enable =flyspell-mode= and disable line numbers in org-agenda.
#+begin_src emacs-lisp
(add-hook 'org-mode-hook 'flyspell-mode)
(add-hook 'org-agenda-mode-hook (lambda () (display-line-numbers-mode -1)))
#+end_src
** Org Journal
Until now I did not thing 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.
#+begin_src emacs-lisp
(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)))
#+end_src
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).
#+begin_src emacs-lisp
(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))))
#+end_src
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: [[https://gitlab.com/phillord/org-drill/][GitLab: phillord/org-drill]]
#+begin_src emacs-lisp
(use-package org-drill
:after org
:config
(setq org-drill-learn-fraction 0.4)
(setq org-drill-maximum-items-per-session 50))
#+end_src
** Org for blogging
Since [[https://mmk2410.org/2020/05/15/switching-my-website-to-hugo-using-ox-hugo/][May 2020]] I'm using [[https://gohugo.io/][hugo]] with [[https://ox-hugo.scripter.co/][ox-hugo]] as a org-mode based static site generator for my [[https://mmk2410.org][mmk2410.org]] site. You can find the source code for the website [[https://git.mmk2410.org/mmk2410/mmk2410.org][on my Gitea instance]] (and also [[https://gitlab.com/mmk2410/mmk2410.org][GitLab]] and [[https://github.com/mmk2410/mmk2410.org][GitHub]] if you prefer that). First of all I need to include =ox-hugo= by [[https://scripter.co/][Kaushal Modi]]:
#+begin_src emacs-lisp
(use-package ox-hugo
:after org)
#+end_src
After I wrote an [[https://mmk2410.org/2022/01/15/improving-my-new-blog-post-creation/][own capture template]] for starting a new blog post, Kashual Modi, the creator of =ox-hugo=, made me aware of [[https://ox-hugo.scripter.co/doc/org-capture-setup/][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.
#+begin_src emacs-lisp
(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)))
#+end_src
* Org Research Assistant
** Org Roam
Roam Research / Zettelkasten system using org-mode.
Source: [[https://www.orgroam.com][orgroam.com]]
#+begin_src emacs-lisp
(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)))
#+end_src
** Org Roam Bibtex
„Official“ connector between org-roam, BibTeX and org-ref.
Source: [[https://github.com/org-roam/org-roam-bibtex][GitHub: org-roam/org-roam-bibtex]]
#+begin_src emacs-lisp
(use-package org-roam-bibtex
:after org-roam
:hook (org-roam-mode . org-roam-bibtex-mode))
#+end_src
** 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: [[https://github.com/org-roam/org-roam-ui][GitHub: org-roam/org-roam-ui]]
#+begin_src emacs-lisp
(use-package websocket
:disabled t
:after org-roam)
(use-package org-roam-ui
:disabled t
:after org-roam
: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))
#+end_src
** Helm Bibtex
Search and manage bibliographies in Emacs.
Source: [[https://github.com/tmalsburg/helm-bibtex][GitHub: tmalsburg/helm-bibtex]]
#+begin_src emacs-lisp
(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))
#+end_src
** Org Ref
org-mode modules for citations, cross-references, bibliographies in org-mode and useful bibtex tools to go with it.
Source: [[https://github.com/jkitchin/org-ref][Github: jkitchin/org-ref]]
#+begin_src emacs-lisp
(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))
#+end_src
** Org Noter
A synchronized, Org-mode, document annotator
Source: [[https://github.com/weirdNox/org-noter][GitHub: weirdNorx/org-noter]]
#+begin_src emacs-lisp
(use-package org-noter
:after (:any org pdf-tools)
:config
(setq org-noter-notes-search-path '("~/org/roam")))
#+end_src
* LaTeX configuration
** auctex
For LaTeX typesetting in Emacs I like to use the great AUCTeX package.
#+begin_src emacs-lisp
(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))
#+end_src
** company-auctex
I also like to turn on company completion for auctex.
#+begin_src emacs-lisp
(use-package company-auctex
:after auctex
:config
(company-auctex-init))
#+end_src
** cdlatex
Fast input methods to enter LaTeX environments and math.
Source: [[https://github.com/cdominik/cdlatex][GitHub: cdominik/cdlatex]]
#+begin_src emacs-lisp
(use-package cdlatex
:hook ((LaTeX-mode . turn-on-cdlatex)
(org-mode . turn-on-org-cdlatex)))
#+end_src
* 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: [[https://github.com/Fanael/rainbow-delimiters][GitHub: Fanael/rainbow-delimiters]]
#+begin_src emacs-lisp
(use-package rainbow-delimiters
:hook (prog-mode . rainbow-delimiters-mode))
#+end_src
** highlight-indent-guides
Minor mode for highlighting indentation.
Source: [[https://github.com/DarthFennec/highlight-indent-guides][DarthFennec/highlight-indent-guides]]
#+begin_src emacs-lisp
(use-package highlight-indent-guides
:config
(setq highlight-indent-guides-method 'character
highlight-indent-guides-responsive 'top)
:hook (prog-mode . highlight-indent-guides-mode))
#+end_src
** company
Auto completion
Source: [[https://github.com/company-mode/company-mode][GitHub: company-mode]]
#+begin_src emacs-lisp
(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))
#+end_src
** lsp
Language Server Protocoll capabilities for Emacs
Source: [[https://github.com/emacs-lsp/lsp-mode][GitHub:emacs-lsp/lsp-mode]]
#+begin_src emacs-lisp
(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)
#+end_src
** editorconfig
EditorConfig plugin for Emacs
Source: [[https://github.com/editorconfig/editorconfig-emacs][GitHub: editorconfig/editorconfig-emacs]]
#+begin_src emacs-lisp
(use-package editorconfig
:ensure t
:config
(editorconfig-mode 1))
#+end_src
* Language-Specific Programming Packages
** Go
Emacs mode for the Go programming language
Source: [[https://github.com/dominikh/go-mode.el][dominikh/go-mode.el]]
#+begin_src emacs-lisp
(use-package go-mode
:mode "\\.go\\'")
#+end_src
** PHP
PHP major mode for Emacs.
Source: [[https://github.com/emacs-php/php-mode][GitHub: emacs-php/php-mode]]
#+begin_src emacs-lisp
(use-package php-mode
:mode "\\.php\\'"
:config (setq php-mode-coding-style 'PSR2))
#+end_src
** TypeScript
TypeScript-support for Emacs
Source: [[https://github.com/emacs-typescript/typescript.el][GitHub: emacs-typescript/typescript.el]]
#+begin_src emacs-lisp
(use-package typescript-mode
:mode "\\.ts\\'")
#+end_src
** Web Mode
web-mode.el is an autonomous emacs major-mode for editing web templates.
Source: [[https://github.com/fxbois/web-mode][GitHub: fxbois/web-mode]]
#+begin_src emacs-lisp
(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)))
#+end_src
** TypoScript
TypoScript major mode for Emacs.
Source: [[https://github.com/ksjogo/typoscript-mode][GitHub: ksjogo/typoscript-mode]]
#+begin_src emacs-lisp
(use-package typoscript-mode
:mode "\\.typoscript\\'")
#+end_src
** YAML
YAML major mode for Emacs.
Source: [[https://github.com/yoshiki/yaml-mode][GitHub: yoshiki/yaml-mode]]
#+begin_src emacs-lisp
(use-package yaml-mode
:mode (("\\.yaml\\'" . yaml-mode)
("\\.yml\\'" . yaml-mode)))
#+end_src
** MoinMoin
MoinMoin mode for Emacs.
Source: [[https://moinmo.in/action/raw/EmacsForMoinMoin/MoinMoinMode?action=raw][MoinMoin wiki: EmacsForMoinMoin]]
#+begin_src emacs-lisp
(use-package moinmoin-mode
:load-path "lisp/moinmoin-mode/"
:mode "\\.wiki\\'"
:commands (moinmoin-mode))
#+end_src
* 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.
#+begin_src emacs-lisp
(use-package emms
:init
(emms-all)
(emms-default-players)
:config
(setq emms-source-file-default-directory "~/Music"))
#+end_src
* 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=).
#+begin_src emacs-lisp
(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"))
#+end_src
Now the configuration for ERC. Some stuff I found in the deepest depths of the internet. Worth mentioning is the [[https://www.emacswiki.org/emacs/ERC][EmacsWiki]] and the following two videos by David Wilson on the [[https://www.youtube.com/c/SystemCrafters][System Crafters YouTube channel]]: [[https://www.youtube.com/watch?v=qWHTZIYTA4s][Chat Like the 90's in Emacs with ERC]] and [[https://www.youtube.com/watch?v=Qci8t_jpVGA][System Crafters Live! - Improving the IRC Experience in ERC]].
#+begin_src emacs-lisp
(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)))
#+end_src
** Additional packages
Highlight nicknames in ERC.
Source: [[https://github.com/leathekd/erc-hl-nicks][leathekd/erc-hl-nicks]]
#+begin_src emacs-lisp
(use-package erc-hl-nicks
:after erc
:config
(add-to-list 'erc-modules 'hl-nicks))
#+end_src
Fetch and show received images in a ERC buffer
Source: [[https://github.com/kidd/erc-image.el][kidd/erc-image.el]]
#+begin_src emacs-lisp
(use-package erc-image
:after erc
:config
(setq erc-image-inline-rescale 600)
(add-to-list 'erc-modules 'image))
#+end_src
* 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.
#+begin_src emacs-lisp
(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)))
#+end_src
* Terminals & Shells
** vterm
A fully-fledged terminal emulator based on libvterm.
Source: [[https://github.com/akermu/emacs-libvterm][GitHub: akermu/emacs-libvterm]]
#+begin_src emacs-lisp
(use-package vterm
:if (not (eq system-type 'windows-nt))
:bind (("C-c t" . 'vterm)))
#+end_src
** eshell
Let's try out eshell.
#+begin_src emacs-lisp
(use-package eshell
:bind (("C-c s" . 'eshell)))
#+end_src
* 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: [[https://github.com/skeeto/elfeed][GitHub: skeeto/elfeed]]
#+begin_src emacs-lisp
(use-package elfeed
:demand t
:config (setq elfeed-sort-order 'ascending)
:bind (("C-c e" . elfeed)))
#+end_src
** 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 [[https://emacs.stackexchange.com/a/55268][RichieHH]] who put me on the right track).
#+begin_src emacs-lisp
(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)))
#+end_src
Using this function I can now define a org-capture template for my purpose.
#+begin_src emacs-lisp
(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))
#+end_src
** 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: [[https://github.com/fasheng/elfeed-protocol][GitHub: fasheng/elfeed-protocol]]
#+begin_src emacs-lisp
(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))
#+end_src
* 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!
Sadly since my workplace heavily uses Microsoft Office 365 including Outlook for mails I'm currently using Evolution, with a setup heavily inspired by [[https://karl-voit.at/2021/06/01/Thunderbird-to-Evolution/][Karl Voit]].
** 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.
#+begin_src emacs-lisp
(when (and (executable-find "mu")
(not (string-equal system-name "knuth"))) ;; Closed after all mu4e configuration
#+end_src
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.
#+begin_src emacs-lisp
(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!"))))))
#+end_src
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.
#+begin_src emacs-lisp
(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
#+end_src
Once it is loaded I'd like to set mu4e as my default mail program in Emacs.
#+begin_src emacs-lisp
(setq mail-user-agent 'mu4e-user-agent)
#+end_src
** Fetching new mail
I use mbsync for synchronising my messages and prefer that I can trigger the refresh from mu4e. Normally I use a [[https://git.mmk2410.org/mmk2410/dotfiles/src/branch/main/systemd][SystemD user service and timer]] for running mbsync every five minutes.
Apparently mbsync requires a filename change, when moving mails.
#+begin_src emacs-lisp
(setq mu4e-get-mail-command "mbsync -a"
mu4e-change-filenames-when-moving t)
#+end_src
** Header view customizations
I'd like to use the IS-8601 date format.
#+begin_src emacs-lisp
(setq mu4e-headers-date-format "%Y-%m-%d %H:%M")
#+end_src
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".
#+begin_src emacs-lisp
(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))))))))
#+end_src
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.
#+begin_src emacs-lisp
(setq mu4e-headers-fields
'((:human-date . 16)
(:flags . 4)
(:mdir . 12)
(:mailing-list . 10)
(:from-or-to . 22)
(:thread-subject)))
#+end_src
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.
#+begin_src emacs-lisp
(setq mu4e-headers-sort-direction 'ascending)
#+end_src
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:
#+begin_src emacs-lisp
(setq mu4e-headers-skip-duplicates nil)
#+end_src
** 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.
#+begin_src emacs-lisp
(setq mu4e-view-scroll-to-next nil)
#+end_src
I like to keep my Downloads folder quite empy and also download everything that I don't intent to keep to =/tmp=.
#+begin_src emacs-lisp
(setq mu4e-attachment-dir "/tmp")
#+end_src
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.
#+begin_src emacs-lisp
(setq mu4e-view-show-images t)
(when (fboundp 'imagemagick-register-types)
(imagemagick-register-types))
#+end_src
I like to see the complete addresses and not just the name.
#+begin_src emacs-lisp
(setq mu4e-view-show-addresses t)
#+end_src
** 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.
#+begin_src emacs-lisp
(add-hook 'mu4e-compose-mode-hook
(lambda ()
(save-excursion
(message-add-header (concat "Bcc: " user-mail-address "\n")))))
#+end_src
Since I get the emails via the BCC it is useless to also put them in a send folder.
#+begin_src emacs-lisp
(setq mu4e-sent-messages-behavior 'delete)
#+end_src
Don't keep "zombie" buffers around after sending a mail.
#+begin_src emacs-lisp
(setq message-kill-buffer-on-exit t)
#+end_src
Also don't reply to myself in case a "reply to all".
#+begin_src emacs-lisp
(setq mu4e-compose-dont-reply-to-self t)
#+end_src
I'd like to send my messages as flowed format.
#+begin_src emacs-lisp
(setq mu4e-compose-format-flowed t)
#+end_src
I like a "special" citation line...
#+begin_src emacs-lisp
(setq message-citation-line-format "%f @ %Y-%m-%d %T %Z:\n"
message-citation-line-function 'message-insert-formatted-citation-line)
#+end_src
** 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.
#+begin_src emacs-lisp
(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")
(t "mailbox"))))
(setq message-sendmail-extra-arguments (list '"-a" account))))))
#+end_src
** 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.
#+begin_src emacs-lisp
(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" )))
#+end_src
** Define some shortcuts
I like to get to some maildir quite fast and with a shortcut.
#+begin_src emacs-lisp
(setq mu4e-maildir-shortcuts
'((:maildir "/university/inbox" :key ?e)
(:maildir "/mailbox/inbox" :key ?p)))
#+end_src
** 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.
#+begin_src emacs-lisp
(add-to-list 'org-capture-templates
'("m" "mail" entry (file "~/org/inbox.org")
"* TODO %a%?\n :PROPERTIES:\n :CREATED: %U\n :END:\n"))
#+end_src
** 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.
#+begin_src emacs-lisp
(setq mu4e-context-policy 'ask
mu4e-compose-context-policy 'ask)
#+end_src
Now define the contexts.
#+begin_src emacs-lisp
(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" )))))
#+end_src
** Other settings
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.
#+begin_src emacs-lisp
(setq mu4e-hide-index-messages t)
#+end_src
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.
#+begin_src emacs-lisp
(setq mu4e-modeline-max-width 60)
#+end_src
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!
#+begin_src emacs-lisp
(setq mu4e-user-mailing-lists '(("debian-user.lists.debian.org" . "DebUsr")
("debian-security-announce.lists.debian.org" . "DebSecAnn")))
#+end_src
Finally the mu4e configuration is done! So lets close the parenthesis left open since the ~use-package~ call and the ~(when~ call.
#+begin_src emacs-lisp
) ;; closing parens for (use-package mu4e
) ;; closing parens for (when (executable-find "mu")
#+end_src
** Load work mail
Load new mail setup which includes support for work mail when running on my work machine.
#+begin_src emacs-lisp
(when (string-equal system-name "knuth")
(push "~/.emacs.d/lisp" load-path)
(require 'mmk2410-mail-setup))
#+end_src
* 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 [[https://karl-voit.at/2021/06/01/Thunderbird-to-Evolution/][Karl Voit]]. Using [[https://github.com/novoid/dot-emacs/blob/master/config.org][his configuration]] as an inspiration and source I could setup the following workflow.
I first define the path to my evolution binary.
#+begin_src emacs-lisp
(setq mmk2410/evolution-path "/usr/bin/evolution")
#+end_src
Then I copied and slightly adjusted Karl Voits function for handling =message-id= link in Org mode.
#+begin_src emacs-lisp
(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)
#+end_src
The following function from him takes care of the other way: bringing a message as a link into Org mode.
#+begin_src emacs-lisp
(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)
#+end_src
* 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 ~mmk2410/tab-bar-setup~ function (bound to =C-c f C-f=) creating the following setup:
- First tab: *Mail* with mu4e
- Second tab: *Agenda / Journal* with an Org agenda on the left and an Org journal on the right
- Third tab: *IRC* with ERC
- Fourth tab: *RSS* with elfeed
This is accompanied with five helper functions to switch to the wished application in the correct tab and window.
|------------+-----------------------------------+------------|
| *Applicaton* | *Command* | *Keybinding* |
| Mail | ~mmk2410/tab-bar-switch-to-mail~ | =C-c f m= |
| Agenda | ~mmk2410/tab-bar-switch-to-agenda~ | =C-c f a= |
| Journal | ~mmk2410/tab-bar-switch-to-journal~ | =C-c f j= |
| IRC | ~mmk2410/tab-bar-switch-to-irc~ | =C-c f i= |
| RSS | ~mmk2410/tab-bar-switch-to-rss~ | =C-c f r= |
|------------+-----------------------------------+------------|
#+begin_src emacs-lisp
(defun mmk2410/tab-bar-setup ()
"Enable tab bar and setup mu4e, Org Journal, Org Agenda and ERC."
(interactive)
(tab-bar-mode 1)
;; Disabled since I'm currently not using mu4e that much (see Evolution Mail and mu4e setup).
;; (tab-bar-rename-tab "Mail")
;; (mu4e-context-switch :name "Private")
;; (mu4e)
;; (sit-for 1)
;; (tab-bar-new-tab)
(tab-bar-rename-tab "Agenda / Journal")
(split-window-horizontally)
(org-agenda nil "c")
(sit-for 1)
(other-window 1)
(org-journal-open-current-journal-file)
(sit-for 1)
(tab-bar-new-tab)
(tab-bar-rename-tab "IRC")
(mmk2410/erc-connect)
(sit-for 1)
(switch-to-buffer "Libera.Chat")
(tab-bar-new-tab)
(tab-bar-rename-tab "RSS")
(elfeed))
(defun mmk2410/tab-bar-switch-to-mail ()
"Switch to mail tab."
(interactive)
(tab-bar-switch-to-tab "Mail"))
(defun mmk2410/tab-bar-switch-to-agenda ()
"Switch to agenda/journal tab and there in the agenda window."
(interactive)
(tab-bar-switch-to-tab "Agenda / Journal")
(unless (string= (buffer-name) "*Org Agenda*")
(other-window 1)))
(defun mmk2410/tab-bar-switch-to-journal ()
"Switch to agenda/journal tab and there in the journal window."
(interactive)
(tab-bar-switch-to-tab "Agenda / Journal")
(if (string= (buffer-name) "*Org Agenda*")
(other-window 1)))
(defun mmk2410/tab-bar-switch-to-irc ()
"Switch to IRC tab."
(interactive)
(tab-bar-switch-to-tab "IRC"))
(defun mmk2410/tab-bar-switch-to-rss ()
"Switch to RSS tab."
(interactive)
(tab-bar-switch-to-tab "RSS"))
(global-set-key (kbd "C-c f C-f") 'mmk2410/tab-bar-setup)
(global-set-key (kbd "C-c f m") 'mmk2410/tab-bar-switch-to-mail)
(global-set-key (kbd "C-c f a") 'mmk2410/tab-bar-switch-to-agenda)
(global-set-key (kbd "C-c f j") 'mmk2410/tab-bar-switch-to-journal)
(global-set-key (kbd "C-c f i") 'mmk2410/tab-bar-switch-to-irc)
(global-set-key (kbd "C-c f r") 'mmk2410/tab-bar-switch-to-rss)
#+end_src