#+Title: Emacs Customizations #+Author: Ricardo Wurmus #+PROPERTY: header-args :tangle "~/.emacs.d/init.el" #+PROPERTY: mkdirp t #+OPTIONS: tasks:nil toc:1 * Introduction My Emacs configuration is a mess. As I’m writing this my Emacs configuration stretches across multiple files, each containing various snippets of code that seemed like a good idea to group. Unfortunately, there are a some things that don’t have a “natural” home. Enabling the same minor mode in various major modes is one of these cases—do I duplicate the hook and place it in a file for each major mode? Or do I write a new file for the minor mode in which I add it to the major modes at once? With multiple files I spend too much time trying to find the best place for any bit of configuration I add. This slows me down and sometimes I just append to the main =init.el=, so I often feel that my configuration is in need of reorganisation. But configuring Emacs should be fun! I don’t want it to create an uncomfortable clean-up task as a side-effect. This is why I’m now trying to use a literate approach with =org-mode=. My Emacs configuration should be prose first and code second. In my experience, finding the right spot in prose for a new paragraph requires a lot less effort as the text itself acts as a connection between unrelated bits of code. * How to use this? We take all code blocks in this file and assemble an =init.el= from it if the source file =init.org= is younger. At startup time we check if the =init.el= has to be regenerated. To get started you need to have an =init.el= with at least these contents. #+BEGIN_SRC elisp (setq lexical-binding t) (let ((orgfile (expand-file-name (concat user-emacs-directory "init.org"))) (target (expand-file-name (concat user-emacs-directory "init.el")))) (when (not (file-newer-than-file-p target orgfile)) (progn (require 'org) (org-babel-tangle-file orgfile) (byte-compile-file target) (load target)))) #+END_SRC * Performance Make startup faster by reducing the frequency of garbage collection and then use a hook to measure Emacs startup time. #+begin_src elisp ;; The default is 800 kilobytes. Measured in bytes. (setq gc-cons-threshold (* 50 1000 1000)) ;; Profile emacs startup (add-hook 'emacs-startup-hook (lambda () (message "*** Emacs loaded in %s with %d garbage collections." (format "%.2f seconds" (float-time (time-subtract after-init-time before-init-time))) gcs-done))) #+end_src Native compilation gives Emacs a speed boost, but it spews compiler warnings that are quite annoying. Silence them. #+begin_src elisp (setq comp-async-report-warnings-errors nil) #+end_src * Initialise packages Emacs is an operating system and I use it as such (see [[http://elephly.net/posts/2016-02-14-ilovefs-emacs.html][this blog post]]). I rely on quite a few extensions that have been made available on various ELPA repositories. Recently, I have moved to installing and managing Emacs packages like any other software package on my system with the functional package manager [[https://gnu.org/s/guix][GNU Guix]]. I find this more reliable, although at first it is slightly less convenient as I can no longer just use =package.el= but first need to package the Elisp code for Guix. I install a Guix profile just for Emacs in =~/.emacs.d/.guix-profile= using Guix Home. Make Emacs load packages from the profile: #+begin_src elisp (setq package-directory-list '("/home/rekado/.emacs.d/.guix-profile/share/emacs/site-lisp")) #+end_src * Better defaults Emacs defaults are hostile to most people. They are what kept me from using Emacs for many years. I’m easily confused by the way the cursor (point) keeps jumping around when scrolling by pages. Let the cursor keep its screen position constant even when scrolling by full screens and don’t jump around when scrolling. #+BEGIN_SRC elisp (setq scroll-margin 7 scroll-step 1 scroll-conservatively 10000 scroll-preserve-screen-position 1) #+END_SRC Here are a few more simple tweaks: #+BEGIN_SRC elisp ;; No splash screen please ... (setq inhibit-startup-message t) ;; Use UTF-8 by default (prefer-coding-system 'utf-8-unix) ;; Keep all visited files in recentf (setq recentf-max-saved-items nil) ;; When point is on a file name and find-file is used, populate the prompt with the name of the file at point. (ffap-bindings) ;; display tool tips in echo area only (tooltip-mode -1) (tool-bar-mode 0) (menu-bar-mode 0) ;; by default Emacs will only resize the frame line by line (setq frame-resize-pixelwise t) ;; don’t force me to input “yes” or “no” (defalias 'yes-or-no-p 'y-or-n-p) ;; Use ibuffer instead of list-buffer (global-set-key (kbd "C-x C-b") 'ibuffer) ;; disable mouse scrolling (mouse-wheel-mode -1) ;(set-frame-parameter nil 'undecorated t) #+END_SRC I don’t use Helm because it’s too “busy” but I do want a more intelligent way to select buffers and find files. #+BEGIN_SRC elisp (require 'use-package) (use-package vertico :init (vertico-mode)) #+END_SRC Save recent completion selections. #+BEGIN_SRC elisp (use-package savehist :init (savehist-mode)) #+END_SRC Display more information for vertico choices. #+BEGIN_SRC elisp (use-package marginalia :after vertico :custom (marginalia-annotators '(marginalia-annotators-heavy marginalia-annotators-light nil)) :init (marginalia-mode)) #+END_SRC Match any part in any order. #+BEGIN_SRC elisp (use-package orderless :custom (completion-styles '(orderless basic)) (completion-category-overrides '((file (styles basic partial-completion))))) #+END_SRC Also tell Emacs that I want to have a separate file for all other customisations that are handled through =M-x customize=. #+BEGIN_SRC elisp ;; Keep emacs Custom-settings in separate file (setq custom-file (expand-file-name "custom.el" user-emacs-directory)) (load custom-file) #+END_SRC * Theme The default behavior of Emacs is that you can compose multiple themes; however, in practice that’s never done and will likely just mess things up. With this little advice, we tell Emacs that once a theme is loaded, all prior themes should be disabled. #+BEGIN_SRC elisp (defadvice load-theme (before theme-dont-propagate activate) (progn (mapc #'disable-theme custom-enabled-themes) (run-hooks 'after-load-theme-hook))) #+END_SRC I like to highlight source buffers to distinguish them from side windows, so I enable =solaire-mode= first. #+BEGIN_SRC elisp (solaire-global-mode +1) #+END_SRC The Modus themes look pretty nice. #+BEGIN_SRC elisp (setq modus-themes-mode-line '(borderless)) (setq modus-themes-region '(bg-only)) (setq modus-themes-completions '(opinionated)) (setq modus-themes-paren-match '(bold intense)) (setq modus-themes-syntax nil) (setq modus-themes-org-agenda '((header-block . (variable-pitch scale-title)) (header-date . (grayscale workaholic bold-today)) (event . (accented scale-small)) (scheduled . uniform) (habit . traffic-light))) (setq modus-themes-headings '((1 . (bold 1.4)) (2 . (bold 1.3)) (3 . (bold 1.2)) (t . (semilight 1.1)))) (setq modus-themes-scale-headings t) (setq modus-themes-bold-constructs t) (setq modus-themes-org-blocks 'gray-background) (setq modus-themes-variable-pitch-headings t) (load-theme 'modus-vivendi t) #+END_SRC Dired mode becomes much prettier with =all-the-icons=. #+BEGIN_SRC elisp (add-hook 'dired-mode-hook 'all-the-icons-dired-mode) #+END_SRC I like the doom emacs modeline with buffer-appropriate icons. #+BEGIN_SRC elisp (use-package doom-modeline :after eshell ;Make sure it gets hooked after eshell :init (doom-modeline-mode) :custom (doom-modeline-height 35) (doom-modeline-bar-width 6) (doom-modeline-lsp t) (doom-modeline-github nil) (doom-modeline-mu4e nil) (doom-modeline-irc t) (doom-modeline-minor-modes nil) (doom-modeline-persp-name nil) (doom-modeline-buffer-file-name-style 'truncate-except-project) (doom-modeline-major-mode-icon nil)) #+END_SRC * Default fonts I like pretty faces. For coding I like to use the DejaVu Sans Mono font. In =org-mode= and in =eww= I like to use a font with variable pitch instead of the default mono-spaced font. #+BEGIN_SRC elisp (set-face-attribute 'default nil :family "DejaVu Sans Mono" :height 130) (set-face-attribute 'fixed-pitch nil :family "DejaVu Sans Mono" :height 1.0) (set-face-attribute 'variable-pitch nil :family "Vollkorn" :height 1.1) #+END_SRC * Guix Store paths have long hashes. In most cases I don’t really care, so I use =guix-prettify-mode= to hide them. #+BEGIN_SRC elisp (when (require 'guix-prettify nil t) (global-guix-prettify-mode)) #+END_SRC I’m often building Guix packages in the shell. =guix-build-log-minor-mode= gives me key bindings to fold and jump over build phases, and it adds pretty faces to the otherwise bland wall of text. #+BEGIN_SRC elisp (add-hook 'shell-mode-hook 'guix-build-log-minor-mode) #+END_SRC I’m monitoring the Guix build farm =berlin.guixsd.org=, which is hosted at the MDC. #+BEGIN_SRC elisp (setq guix-hydra-url "https://ci.guix.gnu.org") #+END_SRC For bug and patch tracking the Guix project uses debbugs. Here are some better defaults for using =debbugs-gnu= with Guix: #+BEGIN_SRC elisp (eval-when-compile (require 'debbugs-gnu)) (with-eval-after-load "debbugs-gnu" (setq debbugs-gnu-default-packages '("guix" "guix-patches")) (add-to-list 'debbugs-gnu-all-packages "guix-patches")) #+END_SRC Oleg Pykhalov shared this useful snippet to list bugs for which I am listed as the owner. #+BEGIN_SRC elisp (with-eval-after-load "debbugs-gnu" (defun my/debbugs-gnu () (interactive) (let ((debbugs-gnu-current-query `((submitter . ,user-mail-address)))) (debbugs-gnu nil)))) #+END_SRC When working on Guix it helps to reduce boilerplate with snippets. I like to have YASnippet enabled and let it use the snippets that are provided with Guix: #+BEGIN_SRC elisp (require 'yasnippet) (add-to-list 'yas-snippet-dirs "~/dev/gx/branches/master/etc/snippets") (yas-global-mode) #+END_SRC * Manuals Also in Info manuals I want to use variable-pitch fonts where possible. Unfortunately, Info manuals don’t contain enough semantic markup, so I cannot selectively use a monospace font for examples or inline code and use a variable pitch font for everything else. So I just use variable pitch in headings. #+BEGIN_SRC elisp (require 'info) (set-face-attribute 'info-title-1 nil :inherit 'variable-pitch :height 1.3) (set-face-attribute 'info-title-2 nil :inherit 'variable-pitch :height 1.3) (set-face-attribute 'info-title-3 nil :inherit 'variable-pitch :height 1.3) (set-face-attribute 'info-menu-header nil :inherit 'variable-pitch :height 1.1) #+END_SRC Since Emacs 25 there is a new face for quoted expressions in Info manuals. By default it uses the “courier” font, which looks terrible. #+BEGIN_SRC elisp (set-face-attribute 'Info-quoted nil :inherit 'fixed-pitch :family "Monospace") #+END_SRC * Org-mode This is my org mode configuration. Much of it is in one big blob and I haven’t yet taken the time to document it. #+BEGIN_SRC elisp (require 'org-indent) (setq org-ellipsis "⤵") (setq org-src-fontify-natively t) (global-set-key (kbd "C-c o l") 'org-store-link) (global-set-key (kbd "C-c o a") 'org-agenda) ;; TODO: make these available in org-mode only (global-set-key (kbd "C-c o s") 'org-schedule) (setq org-log-done t) (setq org-return-follows-link t) (setq org-directory "~/Documents/org") (setq org-agenda-files (mapcar (lambda (x) (concat org-directory x)) (list "/master.org" "/email.org" "/todo.org" "/inbox.org" "/birthdays.org"))) (setq org-default-notes-file (concat org-directory "/notes.org")) (setq org-agenda-custom-commands '(("w" todo "WAITING" nil) ("n" todo "NEXT" nil) ("d" "Agenda + Next Actions" ((agenda) (todo "NEXT"))))) (setq org-fontify-done-headline t) (require 'org-bullets) (setq org-bullets-bullet-list '("◉" "○" "◇" "◇")) (add-hook 'org-mode-hook (lambda () (org-bullets-mode 1) (variable-pitch-mode 1) (visual-line-mode 1))) (defun gtd () (interactive) (find-file (concat org-directory "/master.org"))) #+END_SRC I can never remember the syntax for babel blocks. With =org-tempo= I can type = (length pieces) 1) (concat (car pieces) ":/") "/")))) (defun my/tramp-home () "Print home directory path on the remote host." (interactive) (let ((pieces (split-string (eshell/pwd) ":/"))) (insert (if (> (length pieces) 1) (concat (car pieces) ":~/") "~/")))) (define-key eshell-mode-map (kbd "C-c /") 'my/tramp-root) (define-key eshell-mode-map (kbd "C-c ~") 'my/tramp-home) #+END_SRC TODO: here’s the rest of my shell configuration: #+BEGIN_SRC elisp (require 'shell-switcher) (setq shell-switcher-mode t) (add-hook 'eshell-mode-hook 'shell-switcher-manually-register-shell) (add-hook 'shell-mode-hook 'shell-switcher-manually-register-shell) (setq shell-switcher-new-shell-function 'shell-switcher-make-shell) ;; use cat as the pager in shell mode, because shell-mode is not an ;; ANSI terminal (setenv "PAGER" "cat") ;; C-d on an empty line in the shell terminates the process. (defun my/comint-delchar-or-eof-or-kill-buffer (arg) (interactive "p") (if (null (get-buffer-process (current-buffer))) (kill-buffer) (comint-delchar-or-maybe-eof arg))) (add-hook 'shell-mode-hook (lambda () ;; needed for proper display of "ls" (setq tab-width 8) ;; load shared bash history (setq comint-input-ring-file-name "~/.bash_history") (comint-read-input-ring t) (define-key shell-mode-map (kbd "C-d") 'my/comint-delchar-or-eof-or-kill-buffer) (define-key shell-mode-map (kbd "") 'comint-previous-matching-input-from-input))) ;; Show current path instead of just "*shell*<2>" (setq uniquify-buffer-name-style 'forward) (setq uniquify-min-dir-content 1024) (require 'uniquify) #+END_SRC * Magit #+BEGIN_SRC elisp ;; full screen magit-status (defadvice magit-status (around magit-fullscreen activate) (window-configuration-to-register :magit-fullscreen) ad-do-it (delete-other-windows)) (defun my/magit-quit-session () "Restores the previous window configuration and kills the magit buffer" (interactive) (kill-buffer) (jump-to-register :magit-fullscreen)) (defun my/magit-toggle-whitespace () "Toggles git option -w" (interactive) (if (member "-w" magit-diff-arguments) (my/magit-dont-ignore-whitespace) (my/magit-ignore-whitespace))) (defun my/magit-ignore-whitespace () "Adds git option -w" (interactive) (add-to-list 'magit-diff-arguments "-w") (magit-refresh)) (defun my/magit-dont-ignore-whitespace () "Removes git option -w" (interactive) (setq magit-diff-arguments (remove "-w" magit-diff-arguments)) (magit-refresh)) (define-key magit-status-mode-map (kbd "q") 'my/magit-quit-session) (define-key magit-status-mode-map (kbd "W") 'my/magit-toggle-whitespace) (setq magit-diff-refine-hunk 'all) #+END_SRC #+BEGIN_SRC elisp (global-set-key (kbd "C-c m") 'magit-status) #+END_SRC * Completion Company mode provides automatic completion. I like to enable it in all programming modes. I don’t use the global company mode because that would enable it in =org-mode= where the pop-up looks terrible with =variable-pitch-mode= enabled. #+BEGIN_SRC elisp (add-hook 'prog-mode-hook 'company-mode) #+END_SRC I like automatic completion, but it’s nice to also have a key to trigger completion. #+BEGIN_SRC elisp (setq company-idle-delay 0.5) (define-key company-mode-map (kbd "C-c ") 'company-complete) #+END_SRC Hippie expand is a neat way to expand text based on already existing text. Unfortunately, it collides with paredit (or smartparens) in that it may insert expansions that include unmatched parentheses. To avoid this I disable two types of expansions: #+BEGIN_SRC elisp (dolist (f '(try-expand-line try-expand-list)) (setq hippie-expand-try-functions-list (remq f hippie-expand-try-functions-list))) #+END_SRC I also use snippets for commonly typed expressions. #+BEGIN_SRC elisp (require 'yasnippet) (yas-global-mode 1) #+END_SRC * Paste The =scpaste= package allows me to quickly paste text to my personal web site. It only needs to know where to place the text files and where they would be publicly accessible via HTTP. #+BEGIN_SRC elisp (setq scpaste-http-destination "https://elephly.net/paste") (setq scpaste-scp-destination "elephly.net:~/elephly.net/paste/") (setq scpaste-scp-port "1022") (setq scpaste-make-name-function #'scpaste-make-name-from-timestamp) #+END_SRC * Pretty symbols #+BEGIN_SRC elisp (defun my/pretty-js-symbols () (push '("===" . ?≡) prettify-symbols-alist) (push '("function" . ?𝑓) prettify-symbols-alist)) (defun my/pretty-r-symbols () (push '("%>%" . ?⤚) prettify-symbols-alist) (push '("%$%" . ?⤜) prettify-symbols-alist) (push '("==" . ?≡) prettify-symbols-alist) (push '("function" . ?𝑓) prettify-symbols-alist)) (when (boundp 'global-prettify-symbols-mode) (add-hook 'js2-mode-hook 'my/pretty-js-symbols) (add-hook 'ess-mode-hook 'my/pretty-r-symbols) (add-hook 'inferior-ess-mode-hook 'my/pretty-r-symbols) (global-prettify-symbols-mode +1)) #+END_SRC * Resize buffer margins dynamically I don’t want to have any margins by default. #+BEGIN_SRC elisp (setq-default left-margin-width 0 right-margin-width 0) #+END_SRC When writing Org-mode documents or when browsing the web with Eww I prefer to see shorter lines. =olivetti-mode= adjusts the buffer margins such that the buffer contents are restricted in width and centered. I find this much more readable when editing Org documents or browsing with Eww. #+BEGIN_SRC elisp (use-package olivetti :hook ((org-mode-hook . olivetti-mode) (markdown-mode-hook . olivetti-mode))) #+END_SRC * Multimedia with EMMS Not EMMS but MPC: #+BEGIN_SRC elisp (setq simple-mpc-arguments "-h 192.168.178.20") #+END_SRC #+BEGIN_SRC elisp #+END_SRC * Lilypond Activate Lilypond mode when I’m opening a Lilypond score. #+BEGIN_SRC elisp (require 'lilypond-mode) (add-to-list 'auto-mode-alist '("\\.ly\\'" . LilyPond-mode)) #+END_SRC Enable =subword-mode= in Lilypond files because I use CamelCase for music variables. #+BEGIN_SRC elisp (add-hook 'LilyPond-mode-hook 'subword-mode) #+END_SRC I like to render Lilypond snippets in Org mode buffers. To do that I need to load the Lilypond backend first. However, I don’t think this should be enabled by default. #+BEGIN_SRC elisp :tangle nil (with-eval-after-load "org" (require 'ob-lilypond)) #+END_SRC * Scheme development Geiser makes Scheme development really nice. It’s also used for Guix development in combination with =guix-devel-mode=, so I’m adding the Guix development directory to Guile’s load path in all Geiser sessions. #+BEGIN_SRC elisp (with-eval-after-load "geiser" (setq geiser-active-implementations '(guile)) (setq geiser-guile-load-path '("~/dev/gx/branches/master"))) #+END_SRC Automatically start =guix-devel-mode= when in =scheme-mode= because I’m likely working on Guix anyway. #+BEGIN_SRC elisp (add-hook 'scheme-mode-hook 'guix-devel-mode) #+END_SRC Parentheses don’t annoy me but I still prefer to have them fade into the background a little. This is what =paren-face-mode= does. #+BEGIN_SRC elisp (require 'paren-face) (global-paren-face-mode 1) #+END_SRC Emacs also highlights matching parentheses, but it does so with a delay. Here I’m disabling the delay. #+BEGIN_SRC elisp (require 'paren) (setq show-paren-delay 0) (show-paren-mode 1) #+END_SRC Editing lispy languages is no fun without =paredit=, a mode to enforce balanced parentheses. Enable it automatically when editing Scheme, Common Lisp, or Elisp. #+BEGIN_SRC elisp (add-hook 'scheme-mode-hook (lambda () (paredit-mode 1))) (add-hook 'emacs-lisp-mode-hook (lambda () (paredit-mode 1))) (add-hook 'lisp-mode-hook (lambda () (paredit-mode 1))) (add-hook 'geiser-repl-mode-hook (lambda () (paredit-mode 1))) #+END_SRC Also enable =paredit= when editing Elisp in the minibuffer. #+BEGIN_SRC elisp ;; Enable `paredit-mode' in the minibuffer, during `eval-expression'. (defun conditionally-enable-paredit-mode () (if (eq this-command 'eval-expression) (paredit-mode 1))) (add-hook 'minibuffer-setup-hook 'conditionally-enable-paredit-mode) #+END_SRC Some customisations for =paredit=. #+BEGIN_SRC elisp (eval-when-compile (require 'paredit)) (with-eval-after-load "paredit" ;; don't hijack \ please (define-key paredit-mode-map (kbd "\\") nil) ;; keybindings (define-key paredit-mode-map (kbd "M-C-") 'backward-kill-sexp) ;; making paredit work with delete-selection-mode (put 'paredit-forward-delete 'delete-selection 'supersede) (put 'paredit-backward-delete 'delete-selection 'supersede) (put 'paredit-newline 'delete-selection t)) #+END_SRC TODO: the parentheses adjustments should happen only when I’m in programming mode. * Email TODO: this is a big blob of email configuration. Document this properly! #+BEGIN_SRC elisp (require 'mu4e) (require 'org-mu4e) (require 'mu4e-contrib) (setq shr-color-visible-luminance-min 30) (setq mu4e-get-mail-command "/home/rekado/.guix-home/profile/bin/mbsync -c /home/rekado/.config/mbsync.conf -a" mu4e-compose-signature-auto-include t mu4e-compose-dont-reply-to-self t mu4e-update-interval nil mu4e-headers-include-related t) (setq mu4e-use-fancy-chars t) (setq mu4e-headers-seen-mark '("" . "")) (setq mu4e-headers-unread-mark '("u" . "✉")) ;; Don't update list of emails automatically. I update the list ;; manually with "g". (setq mu4e-headers-auto-update nil) ;; Rename files when moving. This is NEEDED when using mbsync or else ;; there is a problem of duplicate UIDs! (setq mu4e-change-filenames-when-moving t) (setq mu4e-view-show-addresses t) (setq mu4e-hide-index-messages t) (setq mu4e-html2text-command 'mu4e-shr2text) (setq mu4e-view-show-images t) ;; use imagemagick, if available (when (fboundp 'imagemagick-register-types) (imagemagick-register-types)) (setq mu4e-attachment-dir "~/Downloads") ;; pretty quotes! (add-hook 'message-mode-hook (lambda () (require 'typo) (typo-mode 1))) (defun my/set-mu4e-bookmarks (maildir) (let ((guix "(list:guix-devel.gnu.org OR list:bug-guix.gnu.org OR list:help-guix.gnu.org OR list:guix-sysadmin.gnu.org OR list:guix-security.gnu.org OR list:guix-patches.gnu.org OR list:guix-commits.gnu.org)") (guile "(list:guile-user.gnu.org OR list:guile-devel.gnu.org OR list:bug-guile.gnu.org)") (kita "(from:hvd-bb.de OR tag:kita)") (waldow "(list:waldow.googlegroups.com OR from:artvivendi-immobilien.de)") (fsfe "(subject:\"Willkommen in der FSFE\" list:coordinators.lists.fsfe.org OR list:berlin.lists.fsfe.org OR list:newsletter-en@fsfeurope.org OR list:newsletter-de@fsfeurope.org)") (unread "(flag:unread AND NOT flag:trashed)") (me "(body:rekado OR body:Ricardo)")) (setq mu4e-bookmarks (list ;; TODO: don't match my own signature (list (concat me " " unread) "Mentioning me (unread)" ?R) (list (concat "maildir:\"/" maildir "/INBOX\"") "Inbox" ?i) (list "date:today..now" "Today's messages" ?t) (list "date:today..now" "Last 7 days" ?w) (list (concat "maildir:\"/" maildir "/INBOX\"" " " unread) "Unread messages" ?u) (list (concat guix " " unread) "Guix" ?1) (list (concat guile " " unread) "Guile" ?2) (list (concat fsfe " " unread) "FSFE" ?3) (list waldow "Waldow" ?4) (list (concat kita " " unread) "Kita" ?5) (list (concat "maildir:\"/private/mailinglists\"" " " unread " NOT " guix " NOT " guile " NOT " fsfe " NOT " waldow " NOT " kita) "Unread list messages" ?m) (list (concat "maildir:\"/" maildir "/INBOX\" flag:flagged") "Flagged" ?f))))) ; set up email sending with msmtp (setq mail-user-agent 'mu4e-user-agent) (setq mail-specify-envelope-from t) (setq mail-envelope-from 'header) (setq message-kill-buffer-on-exit t) (setq message-sendmail-envelope-from 'header) (setq message-send-mail-function 'message-send-mail-with-sendmail) ;;use msmtp instead of sendmail (setq sendmail-program "~/.guix-home/profile/bin/msmtp") ;; Crypto (setq mml-secure-openpgp-encrypt-to-self t) (setq mml-secure-openpgp-sign-with-sender t) (add-hook 'mu4e-compose-mode-hook (defun my/maybe-reply-encrypted () "Encrypt automatically if parent message was also encrypted." (let ((msg mu4e-compose-parent-message)) (when (and msg (or (member 'encrypted (mu4e-message-field msg :flags)) (string-match "-----BEGIN PGP MESSAGE-----$" (mu4e-message-field msg :body-txt)))) (mml-secure-message-sign-encrypt))))) (add-hook 'mu4e-compose-mode-hook (defun my/mu4e-add-headers () "Add some personal headers." (save-excursion (message-add-header "X-URL: https://elephly.net\n") (message-add-header "X-PGP-Key: https://elephly.net/rekado.pubkey\n") (message-add-header "X-PGP-Fingerprint: BCA6 89B6 3655 3801 C3C6 2150 197A 5888 235F ACAC")))) ;; Don't open a new window to show the results of signature validation (setq epa-popup-info-window nil) (require 'mu4e-actions) (add-to-list 'mu4e-view-actions '("git am" . mu4e-action-git-apply-mbox)) (add-to-list 'mu4e-headers-actions '("git am" . mu4e-action-git-apply-mbox)) (add-to-list 'mu4e-view-actions '("retag message" . mu4e-action-retag-message) t) ;; Don't ask for a 'context' upon opening mu4e (setq mu4e-context-policy 'pick-first) ;; Don't ask to quit (setq mu4e-confirm-quit nil) #+END_SRC I read and write email in different contexts. This is my context for private email: #+BEGIN_SRC elisp (setq my/mu4e-context-private (make-mu4e-context :name "Private" :enter-func (lambda () (mu4e-message "Switch to the Private context") (my/set-mu4e-bookmarks "private")) :match-func (lambda (msg) (when msg (or (mu4e-message-contact-field-matches msg :to (rot13 "erxnqb@ryrcuyl.arg")) (mu4e-message-contact-field-matches msg :from (rot13 "erxnqb@ryrcuyl.arg")) ;; Additional check if this is a mailing list email. (and (mu4e-message-field msg :mailing-list) (zerop (call-process "grep" nil nil nil "-E" "^Delivered-To: .*elephly.net" (mu4e-message-field msg :path))))))) :vars `((user-mail-address . ,(rot13 "erxnqb@ryrcuyl.arg")) (user-full-name . ,(rot13 "Evpneqb Jhezhf")) (mu4e-sent-folder . "/private/Sent") (mu4e-trash-folder . "/private/Trash") (mu4e-refile-folder . "/private/Archives") (mu4e-drafts-folder . "/private/Drafts") (mu4e-compose-signature . "Ricardo")))) #+END_SRC And here’s the context for work email: #+BEGIN_SRC elisp (setq my/mu4e-context-work (make-mu4e-context :name "Work" :enter-func (lambda () (mu4e-message "Switch to the Work context") (my/set-mu4e-bookmarks "mdc-personal")) :match-func (lambda (msg) (when msg (or (mu4e-message-contact-field-matches msg :to (rot13 "evpneqb.jhezhf@zqp-oreyva.qr")) (mu4e-message-contact-field-matches msg :from (rot13 "evpneqb.jhezhf@zqp-oreyva.qr")) ;; Additional check if this is a mailing list email. (and (mu4e-message-field msg :mailing-list) (zerop (call-process "grep" nil nil nil "-E" "^Received: from .*mdc-berlin.de" (mu4e-message-field msg :path))))))) :vars `((user-mail-address . ,(rot13 "evpneqb.jhezhf@zqp-oreyva.qr")) (user-full-name . ,(rot13 "Evpneqb Jhezhf")) (mu4e-sent-folder . "/mdc-personal/Sent Items") (mu4e-trash-folder . "/mdc-personal/Deleted Items") (mu4e-refile-folder . "/mdc-personal/Archive") (mu4e-drafts-folder . "/mdc-personal/Drafts") (mu4e-compose-signature . ,(rot13 (concat "Evpneqb Jhezhf\n\n" "Flfgrz nqzvavfgengbe\n" "OVZFO - Fpvragvsvp Ovbvasbezngvpf Cyngsbez\n" "Znk Qryoehrpx Pragre sbe Zbyrphyne Zrqvpvar\n\n" "rznvy: evpneqb.jhezhf@zqp-oreyva.qr\n" (concat "gry: +49 30 9406 " (number-to-string (+ (* 1 2 2 3 4 5 6) (expt 2 8) 100))))))))) #+END_SRC When I’m using the workstation in the office, all I want is the work context. #+BEGIN_SRC elisp (setq mu4e-contexts (if (string= (system-name) (rot13 "ovzfo-flf02.zqp-oreyva.arg")) (list my/mu4e-context-work) (list my/mu4e-context-private my/mu4e-context-work))) #+END_SRC Load all of this email configuration code only when I start =mu4e=. #+BEGIN_SRC elisp (require 'mu4e) (global-set-key (kbd "") 'mu4e) #+END_SRC * TODO More stuff This is even more stuff to be done after initialising packages. I still need to process all of this and clean it up. #+BEGIN_SRC elisp (require 'projectile) (projectile-mode) #+END_SRC * TODO And even more #+BEGIN_SRC elisp (setq backup-directory-alist `(;; Do not backup or auto-save remote files to prevent delays. (,tramp-file-name-regexp . nil) ;; Write backup files to a dedicated directory. ("." . ,(expand-file-name (concat user-emacs-directory "backups"))))) ;; Make backups of files, even when they're in version control (setq vc-make-backup-files t) (add-to-list 'auto-mode-alist '("\\.markdown\\'" . markdown-mode)) (add-to-list 'auto-mode-alist '("\\.md\\'" . markdown-mode)) (add-to-list 'auto-mode-alist '("\\.js$" . js2-mode)) (setq scss-compile-at-save nil) (desktop-save-mode t) (setq desktop-restore-eager 5) ; restore buffers lazily (setq desktop-lazy-idle-delay 3) ; no hurry ;; ediff settings (setq ediff-diff-options "-w") ;; fewer backslashes in regexp builder (require 're-builder) (setq reb-re-syntax 'string) ;; remove prompt on killing process buffer (setq kill-buffer-query-functions (remq 'process-kill-buffer-query-function kill-buffer-query-functions)) ;; enable features that are disabled by default (put 'narrow-to-region 'disabled nil) (put 'erase-buffer 'disabled nil) (put 'narrow-to-page 'disabled nil) (require 'fill-column-indicator) (setq fci-rule-use-dashes t) (setq fci-rule-color "#cccccc") (setq fci-dash-pattern 0.3) (add-hook 'prog-mode-hook 'fci-mode) ;; This is a workaround to display the company-mode completion popup ;; when fci-mode is enabled. It was taken from here: ;; https://github.com/company-mode/company-mode/issues/180#issuecomment-55047120 (defvar-local company-fci-mode-on-p nil) (defun company-turn-off-fci (&rest ignore) (when (boundp 'fci-mode) (setq company-fci-mode-on-p fci-mode) (when fci-mode (fci-mode -1)))) (defun company-maybe-turn-on-fci (&rest ignore) (when company-fci-mode-on-p (fci-mode 1))) (add-hook 'company-completion-started-hook 'company-turn-off-fci) (add-hook 'company-completion-finished-hook 'company-maybe-turn-on-fci) (add-hook 'company-completion-cancelled-hook 'company-maybe-turn-on-fci) ;; expand region (global-set-key (kbd "M-@") 'er/expand-region) ;; Swap C-t and C-x, so it's easier to type on Dvorak layout ;; `keyboard-translate` does not work when attaching an emacsclient to ;; a running emacs in daemon mode, so instead we define the key in the ;; key-translation-map. ;; http://lists.gnu.org/archive/html/help-gnu-emacs/2009-10/msg00505.html (define-key key-translation-map [?\C-x] [?\C-t]) (define-key key-translation-map [?\C-t] [?\C-x]) ;; Use narrow tab width (set-default 'tab-width 4) (setq tab-width 4) (setq gnus-select-method '(nntp "news.gmane.org")) ;; disable away timestamp in ERC (setq erc-away-timestamp-format nil) (setq erc-timestamp-format nil) ;; don’t switch to a newly created IRC buffer (setq erc-join-buffer 'bury) ;; Revert stale document graphics buffers automatically when the files ;; have changed. (add-hook 'doc-view-mode-hook 'auto-revert-mode) (page-break-lines-mode 1) (global-set-key (kbd "") 'backward-page) (global-set-key (kbd "") 'forward-page) (add-to-list 'auto-mode-alist '("\\.html\\'" . sgml-mode)) (eval-when-compile (require 'tagedit)) (eval-after-load "sgml-mode" '(progn (require 'tagedit) (tagedit-add-paredit-like-keybindings) (tagedit-add-experimental-features) (add-hook 'html-mode-hook (lambda () (tagedit-mode 1))))) (delete-selection-mode 1) ; delete seleted text when typing ;; don't let the cursor go into minibuffer prompt, HT Xah Lee (setq minibuffer-prompt-properties '(read-only t point-entered minibuffer-avoid-prompt face minibuffer-prompt)) (savehist-mode) ;; PDF view mode (setq pdf-info-epdfinfo-program "~/.emacs.d/.guix-profile/bin/epdfinfo") (pdf-tools-install) (add-to-list 'auto-mode-alist '("\\.pdf\\'" . pdf-view-mode)) ;; enable variable-pitch-mode in eww (add-hook 'eww-mode-hook (lambda () (variable-pitch-mode 1) (olivetti-mode 1))) ;; pretty quotes! (add-hook 'erc-mode-hook (lambda () (require 'typo) (typo-mode 1))) (add-hook 'org-mode-hook (lambda () (require 'typo) (typo-mode 1))) #+END_SRC Here are a few commands that I used pretty often: #+BEGIN_SRC elisp (defun my/new-empty-buffer () "Open a new empty buffer." (interactive) (let ((buf (generate-new-buffer "untitled"))) (switch-to-buffer buf) (funcall (and initial-major-mode)) (setq buffer-offer-save t))) (global-set-key (kbd "C-c n") 'my/new-empty-buffer) ;; http://whattheemacsd.com/key-bindings.el-01.html#disqus_thread (require 'linum) (defun my/goto-line-with-feedback () "Show line numbers temporarily, while prompting for the line number input" (interactive) (let ((line-numbers-off-p (not linum-mode))) (unwind-protect (progn (when line-numbers-off-p (linum-mode 1)) (call-interactively 'goto-line)) (when line-numbers-off-p (linum-mode -1))))) (global-set-key [remap goto-line] 'my/goto-line-with-feedback) ;; kill current buffer (global-set-key (kbd "C-x C-k") (lambda () (interactive) (kill-buffer (current-buffer)))) ;; delete up to non-whitespace character (global-set-key (kbd "C-c d") (lambda () (interactive) (cycle-spacing -1 t nil))) (defun ssh-dtach (host) "Open SSH connection to HOST and start dtach session." (interactive "sHost: ") (let ((explicit-shell-file-name "dtach") (explicit-dtach-args '("-A" "/tmp/emacs.dtach" "-z" "/bin/bash" "--noediting" "-login")) (default-directory (format "/ssh:%s:" host))) (shell (format "*ssh %s*" host)))) ;; http://blog.vivekhaldar.com/post/4809065853/dotemacs-extract-interactively-change-font-size (defun my/zoom-in () "Increase font size by 10 points" (interactive) (set-face-attribute 'default nil :height (+ (face-attribute 'default :height) 10))) (defun my/zoom-out () "Decrease font size by 10 points" (interactive) (set-face-attribute 'default nil :height (- (face-attribute 'default :height) 10))) ;; change font size, interactively (global-set-key (kbd "C->") 'my/zoom-in) (global-set-key (kbd "C-<") 'my/zoom-out) ;; easier way to jump to other window (global-set-key (kbd "M-o") 'other-window) (defun my/smart-open-line () "Insert an empty line after the current line. Position the cursor at its beginning, according to the current mode." (interactive) (move-end-of-line nil) (newline-and-indent)) (global-set-key [(shift return)] 'my/smart-open-line) ;; http://stackoverflow.com/a/18814469/519736 (defun my/copy-buffer-file-name (choice) "Copy the buffer-file-name to the kill-ring" (interactive "cCopy Buffer Name (F) Full, (D) Directory, (N) Name") (let ((new-kill-string) (name (if (eq major-mode 'dired-mode) (dired-get-filename) (or (buffer-file-name) "")))) (cond ((eq choice ?f) (setq new-kill-string name)) ((eq choice ?d) (setq new-kill-string (file-name-directory name))) ((eq choice ?n) (setq new-kill-string (file-name-nondirectory name))) (t (message "Quit"))) (when new-kill-string (message "%s copied" new-kill-string) (kill-new new-kill-string)))) #+END_SRC * Putting it all together Having defined named code blocks in the sections above we can finally put them all together to build the init file #+BEGIN_SRC elisp (global-set-key (kbd "C-x RET 1") (lambda () (interactive) (insert "¯\\_(ツ)_/¯"))) #+END_SRC