munen / emacs.d

My emacs configuration documented in literate programming style
GNU General Public License v3.0
350 stars 28 forks source link
emacs emacs-lisp

+TITLE: Emacs configuration

+AUTHOR: Alain M. Lafon

+EMAIL: alain@200ok.ch

This repository contains my Emacs configuration. It is written and documented in literate programming style.

If you're new to Emacs and just want to have a look around: Lean back and relax while enjoying a deep dive into the wonderful world of the Emacs editor. I have a talk "Play Emacs like an instrument" which is a small teaser of what Emacs can do - and what kinds of features you'll find in this repository: https://www.youtube.com/watch?v=gfZDwYeBlO4

Initial

Emacs configuration is usually done in the home directory in the =.emacs.d= folder. This holds true for Unix and Linux systems. For Windows, look it up [[https://www.gnu.org/software/emacs/manual/html_node/efaq-w32/Location-of-init-file.html][here]].

=git clone https://github.com/munen/emacs.d.git ~/.emacs.d=

** Dependencies

*** package.el

Emacs dependencies/libraries are managed via the internal [[https://www.gnu.org/software/emacs/manual/html_node/emacs/Packages.html#Packages][package management system]]. To initially install packages, open =~/.emacs.d/init.el=, refresh your package list with =M-x package-refresh-contents= and install everything using =M-x eval-buffer=.

*** Guix

I'm running Debian and for some things I use [[https://www.gnu.org/software/guix/][GNU Guix]] for package management. This Emacs config started out using just package.el, but it's moving towards using Guix whenever possible. The benefits are plenty:

**** Create a Guix manifest for Emacs

Spread throughout this config, there are source blocks with =:noweb-ref packages= config. They define Guix packages which are tangled into one Emacs manifest, so that all things Emacs are well defined in just one config file.

+begin_src fundamental :tangle ~/.config/guix/manifests/emacs.scm :noweb yes

(specifications->manifest '("emacs" <> ))

+end_src

**** Add guix packages to load-path

This adds the installed packages to the standard Emacs load path, so that =require= just works.

+BEGIN_SRC emacs-lisp

;; Single directory ;; (add-to-list 'load-path "~/.guix-profile/share/emacs/site-lisp/mu4e")

;; All sub directories (let ((default-directory "~/.guix-profile/share/emacs/site-lisp/")) (normal-top-level-add-subdirs-to-load-path))

+END_SRC

**** Automatically tangle Guix manifest

+auto_tangle: t

https://github.com/yilkalargaw/org-auto-tangle

Whenever a change to the Guix package blocks is made, it would be possible to manually call =M-x org-babel-tangle (C-c C-v t)=.

With =auto-tangle=, it's a bit more convenient, though.

+begin_src emacs-lisp

(require 'org-auto-tangle)

(add-hook 'org-mode-hook 'org-auto-tangle-mode)

+end_src

Guix package

+begin_src fundamental :noweb-ref packages

"emacs-org-auto-tangle"

+end_src

** Define package repositories(archives)

+BEGIN_SRC emacs-lisp

(require 'package)

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

+END_SRC

** Define packages that are to be installed

List all used third-party packages. Most will be configured further down in this file, some are used with the default configuration.

+BEGIN_SRC emacs-lisp

(defvar my-packages '(ace-window ac-js2 ag atomic-chrome auto-complete beacon bicycle browse-kill-ring cider clj-refactor clojure-mode coffee-mode counsel-jq comment-tags darktooth-theme dired-narrow diminish dumb-jump edit-indirect editorconfig elfeed elfeed-goodies emacs-everywhere enh-ruby-mode erc-image evil evil-escape evil-leader evil-mc evil-numbers evil-surround exec-path-from-shell flycheck flycheck-flow flycheck-clj-kondo flycheck-package gnuplot hcl-mode hide-mode-line impatient-mode ivy counsel swiper json-mode js2-mode js2-refactor js-comint ledger-mode magit-delta markdown-mode org-ai org-mime package-lint pdf-tools projectile rainbow-mode rjsx-mode ob-restclient restclient robe sass-mode smex synosaurus tide visual-fill-column web-mode which-key writegood-mode writeroom-mode quelpa yaml-mode zenburn-theme))

+END_SRC

** Install packages

+BEGIN_SRC emacs-lisp

(dolist (p my-packages) (unless (package-installed-p p) (package-refresh-contents) (package-install p)) (add-to-list 'package-selected-packages p))

+END_SRC

** Quelpa https://github.com/quelpa/quelpa

Build and install your Emacs Lisp packages on-the-fly directly from source.

** Gnu Elpa TLS Fix

Emacs 26.1 (for example in Debian Buster) requests the GNU Elpa repo with the wrong TLS version - which makes the request fail. This is a manual patch for older versions of Emacs. It's fixed from 26.3 and above upstream.

+BEGIN_SRC emacs-lisp

(if (string< emacs-version "26.3") (setq gnutls-algorithm-priority "NORMAL:-VERS-TLS1.3"))

+END_SRC

** Garbage Collection

Allow 20MB of memory (instead of 0.76MB) before calling garbage collection. This means GC runs less often, which speeds up some operations.

+BEGIN_SRC emacs-lisp

(setq gc-cons-threshold 20000000)

+END_SRC

** Do not create backup files

+BEGIN_SRC emacs-lisp

(setq make-backup-files nil)

+END_SRC

** Warn when opening big files

The default warning comes with a 10MB file size which my machine handles with no noticeable delay at all. Hence, only warn when opening files bigger than 200MB.

+begin_src emacs-lisp

 (setq large-file-warning-threshold 200000000)

+end_src

** Auto-Save in =/tmp=

Store backups and auto-saved files in =TEMPORARY-FILE-DIRECTORY= (which defaults to /tmp on Unix), instead of in the same directory as the file.

+BEGIN_SRC emacs-lisp

(setq backup-directory-alist ((".*" . ,temporary-file-directory))) (setq auto-save-file-name-transforms ((".*" ,temporary-file-directory t)))

+END_SRC

** Always follow symlinks When opening a file, always follow symlinks.

+BEGIN_SRC emacs-lisp

(setq vc-follow-symlinks t)

+END_SRC

** Sentences have one space after a period Don't assume that sentences should have two spaces after periods.

+BEGIN_SRC emacs-lisp

(setq sentence-end-double-space nil)

+END_SRC

** Confirm before closing Emacs

+BEGIN_SRC emacs-lisp

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

+END_SRC

** =dired-mode=

Ability to use =a= to visit a new directory or file in =dired= instead of using =RET=. =RET= works just fine, but it will create a new buffer for /every/ interaction whereas =a= reuses the current buffer.

+BEGIN_SRC emacs-lisp

(put 'dired-find-alternate-file 'disabled nil)

+END_SRC

Human readable units

+BEGIN_SRC emacs-lisp

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

+END_SRC

On =C=, recursively copy by default

+BEGIN_SRC emacs-lisp

(setq dired-recursive-copies 'always)

+END_SRC

*** =dired-narrow=

=dired-narrow= of the [[https://github.com/Fuco1/dired-hacks][dired-hacks]] repository allows to dynamically narrow a dired buffer down to contents of interest. A demo can be seen [[http://pragmaticemacs.com/emacs/dynamically-filter-directory-listing-with-dired-narrow/][on this blog post]].

+BEGIN_SRC emacs-lisp

(require 'dired) (define-key dired-mode-map (kbd "/") 'dired-narrow-fuzzy)

+END_SRC

Commands:

** Ask =y/n= instead of =yes/no= This is a favorable shorthand.

+BEGIN_SRC emacs-lisp

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

+END_SRC

** Auto revert files on change When something changes a file, automatically refresh the buffer containing that file so they can't get out of sync.

+BEGIN_SRC emacs-lisp

(global-auto-revert-mode t)

+END_SRC

** Shortcut for changing font-size

+BEGIN_SRC emacs-lisp

(defun zoom-in () (interactive) (let ((x (+ (face-attribute 'default :height) 10))) (set-face-attribute 'default nil :height x)))

(defun zoom-out () (interactive) (let ((x (- (face-attribute 'default :height) 10))) (set-face-attribute 'default nil :height x)))

(define-key global-map (kbd "C-1") 'zoom-in) (define-key global-map (kbd "C-0") 'zoom-out)

+END_SRC

** Disable startup message

+BEGIN_SRC emacs-lisp

(setq inhibit-splash-screen t) (setq inhibit-startup-message t)

+END_SRC

** Display the current time

+BEGIN_SRC emacs-lisp

(display-time-mode t)

+END_SRC

** Do not display GUI Toolbar

+BEGIN_SRC emacs-lisp

(tool-bar-mode 0)

+END_SRC

** Automatic Line Breaks

Do not enable automatic line breaks for all text-mode based hooks, because several text-modes (markdown, mails) enjoy the pain of long lines. So here, I only add whitelisted modes sparingly. The other modes have a =visual-line-mode== configuration which makes the text look nice locally, at least.

+BEGIN_SRC emacs-lisp

(add-hook 'org-mode-hook 'auto-fill-mode)

+END_SRC

*** =visual-fill-column-mode= https://github.com/joostkremers/visual-fill-column

=visual-fill-column-mode= is a small Emacs minor mode that mimics the effect of =fill-column= in =visual-line-mode=. Instead of wrapping lines at the window edge, which is the standard behaviour of =visual-line-mode=, it wraps lines at =fill-column=. If =fill-column= is too large for the window, the text is wrapped at the window edge.

Enable whenever upstream =visual-line-mode= is activated.

+begin_src emacs-lisp

(add-hook 'visual-line-mode-hook #'visual-fill-column-mode)

+end_src

Enable =visual-fill-mode= for all text based modes:

+begin_src emacs-lisp

;; Don't do it at this time, it's only enabled for some modes explicitly. ;; (add-hook 'text-mode-hook 'visual-line-mode)

+end_src

Enable =adative-wrap-prefix-mode=:

https://elpa.gnu.org/packages/adaptive-wrap.html

This package provides the `adaptive-wrap-prefix-mode' minor mode which sets the wrap-prefix property on the fly so that single-long-line paragraphs get word-wrapped in a way similar to what you'd get with M-q using adaptive-fill-mode, but without actually changing the buffer's text.

+begin_src emacs-lisp

(add-hook 'visual-line-mode-hook #'adaptive-wrap-prefix-mode)

+end_src

** Enable Narrow To Region

Enable narrow-to-region (=C-x n n= / =C-x n w=). This is disabled by default to not confuse beginners.

+BEGIN_SRC emacs-lisp

(put 'narrow-to-region 'disabled nil)

+END_SRC

** Disable scroll bars

+BEGIN_SRC emacs-lisp

(scroll-bar-mode -1)

+END_SRC

** Remember the cursor position of files when reopening them

+BEGIN_SRC emacs-lisp

(setq save-place-file "~/.emacs.d/saveplace") (if (version<= emacs-version "25.1") (progn (setq-default save-place t) (require 'saveplace)) (save-place-mode 1))

+END_SRC

** Set $MANPATH, $PATH and exec-path from shell even when started from GUI helpers like =dmenu= or =Spotlight=

+BEGIN_SRC emacs-lisp

;; Safeguard, so this only runs on Linux (or MacOS) (when (memq window-system '(mac ns x)) (exec-path-from-shell-initialize))

+END_SRC

** =ace-window= https://github.com/abo-abo/ace-window

Quickly switch windows in Emacs

+BEGIN_SRC emacs-lisp

(global-set-key (kbd "M-o") 'ace-window)

+END_SRC

** =winner-mode=

Allows to 'undo' (and 'redo') changes in the window configuration with the key commands ‘C-c left’ and ‘C-c right’.

+BEGIN_SRC emacs-lisp

(when (fboundp 'winner-mode) (winner-mode 1))

+END_SRC

Getting from many windows to one window is easy: 'C-x 1' will do it. But getting back to a delicate WindowConfiguration is difficult. This is where Winner Mode comes in: With it, going back to a previous session is easy. ** Bell Do not ring the system bell, but show a visible feedback.

+BEGIN_SRC emacs-lisp

(setq visible-bell t)

+END_SRC

** AngeFtp Try to use passive mode for FTP.

Note: Some firewalls might not allow standard active mode. However: Some FTP Servers might not allow passive mode. So if there's problems when connecting to an FTP, try to revert to active mode.

+BEGIN_SRC emacs-lisp

(setq ange-ftp-try-passive-mode t)

+END_SRC

** eww When entering eww, use cursors to scroll without changing point.

+BEGIN_SRC emacs-lisp

(add-hook 'eww-mode-hook 'scroll-lock-mode)

+END_SRC

** Custom-File

+BEGIN_SRC emacs-lisp

(setq custom-file "~/.emacs.d/custom-settings.el") (load custom-file t)

+END_SRC

** Bidirectional Editing

https://www.gnu.org/software/emacs/manual/html_node/emacs/Bidirectional-Editing.html

Emacs supports editing text written in scripts, such as Arabic, Farsi, and Hebrew, whose natural ordering of horizontal text for display is from right to left. However, digits and Latin text embedded in these scripts are still displayed left to right.

Whilst this is a great feature, it adds to the amount of line scans that Emacs has to do to render a line. Too many line scans will cause Emacs to hang. Since I personally do not work with right-to-left languages, I'm defaulting to displaying all paragraphs in a left-to-right manner.

+begin_src emacs-lisp

(setq-default bidi-paragraph-direction 'left-to-right)

(if (version<= "27.1" emacs-version) (setq bidi-inhibit-bpa t))

+end_src

** =so-long=

When the lines in a file are so long that performance could suffer to an unacceptable degree, we say "so long" to the slow modes and options enabled in that buffer, and invoke something much more basic in their place.

+begin_src emacs-lisp

(if (version<= "27.1" emacs-version) (global-so-long-mode 1))

+end_src

** Native compilation

Do not report warnings and errors from asynchronous native compilation.

+begin_src emacs-lisp

 (setq native-comp-async-report-warnings-errors nil)

+end_src

** Smooth scrolling

+begin_src emacs-lisp

(when (>= emacs-major-version 29) (pixel-scroll-precision-mode 1))

+end_src

** =undo-tree=

+begin_src emacs-lisp

(global-undo-tree-mode) (setq undo-tree-auto-save-history nil)

(with-eval-after-load 'evil (define-key evil-normal-state-map (kbd "u") 'undo-tree-undo) (define-key evil-normal-state-map (kbd "C-r") 'undo-tree-redo))

+end_src

Guix package

+begin_src fundamental :noweb-ref packages

"emacs-undo-tree"

+end_src

** Private config

Things that should not be public.

+begin_src emacs-lisp

(when (file-exists-p "~/.emacs.d/private_config.el") (load "~/.emacs.d/private_config.el"))

+end_src

Some helper functions and packages I wrote that are only accessible within this Git repository and not published to a package repository.

** Translations

Elisp wrapper around the dict.cc translation service. Translations are exposed in an org-mode table.

Demo: [[https://asciinema.org/a/hMTM9EDHE0cphaDRFr4JXr1iw][https://asciinema.org/a/hMTM9EDHE0cphaDRFr4JXr1iw.png]]

*** Load dict.el

+BEGIN_SRC emacs-lisp

(load "~/.emacs.d/dict")

+END_SRC

** Helper functions to clean up the gazillion buffers

When switching projects in Emacs, it can be prudent to clean up every once in a while. Deleting all buffers except the current one is one of the things I often do (especially in the long-running =emacsclient=).

+BEGIN_SRC emacs-lisp

(defun kill-other-buffers () "Kill all other buffers." (interactive) (mapc 'kill-buffer (delq (current-buffer) (buffer-list))))

+END_SRC

=dired= will create buffers for every visited folder. This is a helper to clear them out once you're done working with those folders.

+BEGIN_SRC emacs-lisp

(defun kill-dired-buffers () "Kill all open dired buffers." (interactive) (mapc (lambda (buffer) (when (eq 'dired-mode (buffer-local-value 'major-mode buffer)) (kill-buffer buffer))) (buffer-list)))

+END_SRC

** Encode HTML to HTML entities Rudimentary function converting certain HTML syntax to HTML entities.

+BEGIN_SRC emacs-lisp

(defun encode-html (start end) "Encodes HTML entities; works great in Visual Mode (START END)." (interactive "r") (save-excursion (save-restriction (narrow-to-region start end) (goto-char (point-min)) (replace-string "&" "&") (goto-char (point-min)) (replace-string "<" "<") (goto-char (point-min)) (replace-string ">" ">"))))

+END_SRC

** Convenience functions when working with PDF exports

When working on markdown or org-mode files that will be converted to PDF, I use =pdf-tools= to preview the PDF and shortcuts to automatically save, compile and reload on demand.

[[https://www.youtube.com/watch?v=Pd0JwOqh-gI][Here]] is a screencast showing how I edit Markdown or org-mode files in Emacs whilst having a PDF preview.

In a screenshot, it looks like this:

[[file:images/edit_markup_with_preview.png]]

+BEGIN_SRC emacs-lisp

(defun md-compile () "Compiles the currently loaded markdown file using pandoc into a PDF" (interactive) (save-buffer) (shell-command (concat "pandoc " (buffer-file-name) " -o " (replace-regexp-in-string "md" "pdf" (buffer-file-name)))))

(defun update-other-buffer () (interactive) (other-window 1) (revert-buffer nil t) (other-window -1))

(defun md-compile-and-update-other-buffer () "Has as a premise that it's run from a markdown-mode buffer and the other buffer already has the PDF open" (interactive) (md-compile) (update-other-buffer))

(defun latex-compile-and-update-other-buffer () "Has as a premise that it's run from a latex-mode buffer and the other buffer already has the PDF open" (interactive) (save-buffer) (shell-command (concat "pdflatex " (buffer-file-name))) (switch-to-buffer (other-buffer)) (kill-buffer) (update-other-buffer))

(defun org-compile-beamer-and-update-other-buffer () "Has as a premise that it's run from an org-mode buffer and the other buffer already has the PDF open" (interactive) (org-beamer-export-to-pdf) (update-other-buffer))

(defun org-compile-latex-and-update-other-buffer () "Has as a premise that it's run from an org-mode buffer and the other buffer already has the PDF open" (interactive) (org-latex-export-to-pdf) (update-other-buffer))

(eval-after-load 'latex-mode '(define-key latex-mode-map (kbd "C-c r") 'latex-compile-and-update-other-buffer))

(define-key org-mode-map (kbd "C-c lr") 'org-compile-latex-and-update-other-buffer) (define-key org-mode-map (kbd "C-c br") 'org-compile-beamer-and-update-other-buffer)

(eval-after-load 'markdown-mode '(define-key markdown-mode-map (kbd "C-c r") 'md-compile-and-update-other-buffer))

+END_SRC

** Use left Cmd to create Umlauts

Unrelated to Emacs, in macOS, you can write Umlauts by using the combo =M-u [KEY]=. For example =M-u u= will create the letter =ü=.

This is actually faster than the default way of Emacs or that of VIM. The following code ports that functionality to Emacs.

Thx [[https://github.com/jcfischer][@jcfischer]] for the function!

+BEGIN_SRC emacs-lisp

(define-key key-translation-map [dead-diaeresis] (lookup-key key-translation-map "\C-x8\"")) (define-key isearch-mode-map [dead-diaeresis] nil) (global-set-key (kbd "M-u") (lookup-key key-translation-map "\C-x8\""))

+END_SRC

** Generate passwords Through =pwgen=.

Thanks to [[https://github.com/branch14/][@branch14]] of [[https://200ok.ch][200ok]] fame for the function!

+BEGIN_SRC emacs-lisp

(defun generate-password-non-interactive () (string-trim (shell-command-to-string "pwgen -A 24")))

(defun generate-password () "Generates and inserts a new password" (interactive) (insert (shell-command-to-string (concat "pwgen -A " (read-string "Length: " "24") " 1"))))

+END_SRC

** Passwords file

Open the GPG encrypted password file.

Within this file, I'll search for passwords with =counsel-imenu= which has nice auto-completion and means that the headers will always be folded, so that no other person can see the passwords.

When the right header is found, I'll copy the password under the current header to the clipboard from where I can use it where I need it (for example a browser):

*** Copy password to clipboard

+BEGIN_SRC emacs-lisp

(fset 'copy-password-to-clipboard [?\C-s ?P ?a ?s ?s ?w ?o ?r ?d ?: return ?w ?v ?$ ?y C-up C-up C-up tab])

+END_SRC

*** Open passwords file

+BEGIN_SRC emacs-lisp

(defun passwords () "Open main 'passwords' file." (interactive) (find-file (concat org-directory "vault/primary.org.gpg")))

+END_SRC

** Running =M-x shell= with =zsh= If you're a =zsh= user, you might have configured a custom prompt and such. Also, you might be using a powerful =$TERM= for that. When running =zsh= within =M-x shell=, you will have to set the =$TERM= to =dumb=, though. Otherwise you'll get all kinds of escape sequences instead of colored text.

I'm using this within my =~/.zshrc=

+BEGIN_SRC shell

This allows running shell properly within Emacs

if [ -n "$INSIDE_EMACS" ]; then export TERM=dumb else export TERM=xterm-256color fi

+END_SRC

** =server-shutdown= This is the converse function to the built-in =server-start=.

+BEGIN_SRC emacs-lisp

(defun server-shutdown () "Save buffers, Quit, and Shutdown (kill) server" (interactive) (save-some-buffers) (kill-emacs))

+END_SRC

** Helper function to measure the running time of a function

+BEGIN_SRC emacs-lisp

(defmacro measure-time (&rest body) "Measure the time it takes to evaluate BODY." `(let ((time (current-time))) ,@body (message "%.06f" (float-time (time-since time)))))

+END_SRC

For example =(measure-time (prettier-eslint)=. ** Sudo Save

If the current buffer is not writable, ask if it should be saved with =sudo=.

Happily taken from Pascals configuration: https://github.com/SirPscl/emacs.d#sudo-save

+BEGIN_SRC emacs-lisp

(defun ph/sudo-file-name (filename) "Prepend '/sudo:root@system-name:' to FILENAME if appropriate. This is, when it doesn't already have a sudo-prefix." (if (not (or (string-prefix-p "/sudo:root@localhost:" filename) (string-prefix-p (format "/sudo:root@%s:" system-name) filename))) (format "/sudo:root@%s:%s" system-name filename) filename))

(defun ph/sudo-save-buffer () "Save FILENAME with sudo if the user approves." (interactive) (when buffer-file-name (let ((file (ph/sudo-file-name buffer-file-name))) (if (yes-or-no-p (format "Save file as %s ? " file)) (write-file file)))))

(advice-add 'save-buffer :around '(lambda (fn &rest args) (when (or (not (buffer-file-name)) (not (buffer-modified-p)) (file-writable-p (buffer-file-name)) (not (ph/sudo-save-buffer))) (call-interactively fn args))))

+END_SRC

** Open file with emacsclient using =filename:line= path This configuration is originally from the great [[https://github.com/bbatsov/prelude/commit/8c55c6f4bb8fab04040e178b97a9e68006525403][bbatsov's prelude]].

emacsclient somefile:1234

This will open file 'somefile' and set cursor on line 1234.

+BEGIN_SRC emacs-lisp

(defadvice server-visit-files (before parse-numbers-in-lines (files proc &optional nowait) activate) "Open file with emacsclient with cursors positioned on requested line. Most of console-based utilities prints filename in format 'filename:linenumber'. So you may wish to open filename in that format. Just call: emacsclient filename:linenumber and file 'filename' will be opened and cursor set on line 'linenumber'" (ad-set-arg 0 (mapcar (lambda (fn) (let ((name (car fn))) (if (string-match "^\(.*?\):\([0-9]+\)\(?::\([0-9]+\)\)?$" name) (cons (match-string 1 name) (cons (string-to-number (match-string 2 name)) (string-to-number (or (match-string 3 name) "")))) fn))) files)))

+END_SRC

** Emacs takes SVG screenshot of itself

+begin_src emacs-lisp

;; https://www.reddit.com/r/emacs/comments/idz35e/emacs_27_can_take_svg_screenshots_of_itself/
(defun screenshot-svg ()
  "Save a screenshot of the current frame as an SVG image.
Saves to a temp file and puts the filename in the kill ring."
  (interactive)
  (let* ((filename (make-temp-file "Emacs" nil ".svg"))
         (data (x-export-frames nil 'svg)))
    (with-temp-file filename
      (insert data))
    (kill-new filename)
    (message filename)))

+end_src

** Search non-ASCII characters

isearch can find a wide range of Unicode characters (like á, ⓐ, or 𝒶) when you search for ASCII characters (a in this example).

+begin_src emacs-lisp

(setq search-default-mode #'char-fold-to-regexp)

+end_src

** Move current line up or down

https://emacsredux.com/blog/2013/04/02/move-current-line-up-or-down/

+BEGIN_SRC emacs-lisp

 (defun move-line-up ()
   "Move up the current line."
   (interactive)
   (transpose-lines 1)
   (forward-line -2)
   (indent-according-to-mode))

 (defun move-line-down ()
   "Move down the current line."
   (interactive)
   (forward-line 1)
   (transpose-lines 1)
   (forward-line -1)
   (indent-according-to-mode))

 (global-set-key (kbd "M-<down>") 'move-line-down)
 (global-set-key (kbd "M-<up>") 'move-line-up)

+END_SRC

** How productive was I today?

The =productivity-of-the-day= returns the total number of TODO statements that have either been added or removed from all agenda files. This is a pretty good proxy for productivity - or at least to see that there's a bit of progress throughout the day.

The codes does the following:

  1. For any org-agenda-file, go to its base directory.
  2. Count the added or removed TODO statements with =git log --since=midnight -p things.org | grep TODO | grep -E "^+|^-" | wc -l=
  3. Aggregate and print.

+begin_src emacs-lisp

(defun count-lines-with-expression (s exp) "Count the number of lines in the string S that contain the regular expression EXP." (let ((count 0)) (mapc (lambda (line) (when (string-match-p exp line) (setq count (+ 1 count)))) (split-string s "\n")) count))

(defun productivity-of-the-day () (seq-reduce (lambda (acc it) (let* ((folder (file-name-directory it)) (file (file-name-nondirectory it)) (base-cmd (concat "cd " folder "; git log --since=midnight -p " file "| egrep 'TODO|WAITING'")) (changed (shell-command-to-string base-cmd)) (added (count-lines-with-expression changed "^\+")) (removed (count-lines-with-expression changed "^\-"))) (cons (+ (car acc) added) (- (cdr acc) removed)))) org-agenda-files '(0 . 0)))

+end_src

The =grep -E= part ensures that the function counts all occurences of TODO which have either been added or removed by discounting the ones that are just in the vicinity and also shown in the diff.

I add the result of =productivity-of-the-day= to my i3 status bar (polybar), so it's always visible. [[https://github.com/munen/dotfiles/commit/53b912cf12ddfb9769958690f14c1f80171a0e13][Here's the config for it]].

Whenever the window scrolls a light will shine on top of your cursor so you know where it is.

+BEGIN_SRC emacs-lisp

(beacon-mode 1)

+END_SRC

** =browse-kill-ring= Ever wish you could just look through everything you've killed recently to find out if you killed that piece of text that you think you killed (or yanked), but you're not quite sure? If so, then browse-kill-ring is the Emacs extension for you.

+BEGIN_SRC emacs-lisp

(require 'browse-kill-ring) (setq browse-kill-ring-highlight-inserted-item t browse-kill-ring-highlight-current-entry nil browse-kill-ring-show-preview t) (define-key browse-kill-ring-mode-map (kbd "j") 'browse-kill-ring-forward) (define-key browse-kill-ring-mode-map (kbd "k") 'browse-kill-ring-previous)

+END_SRC

** =emacs-everywhere= https://github.com/tecosaur/emacs-everywhere/

+begin_src fundamental :noweb-ref packages

"xclip" "xdotool" "xprop" "xwininfo"

+end_src

** =gnuplot= https://github.com/emacs-gnuplot/gnuplot

+begin_src fundamental :noweb-ref packages

"emacs-gnuplot"

+end_src

** Printing https://www.emacswiki.org/emacs/PrintingFromEmacs

+begin_src emacs-lisp

(setq ps-lpr-command "print_preview")

+end_src

This combines the best of both worlds: VIM being a great text-editor with modal editing through semantic commands and Emacs being a LISP REPL. ** Enable Evil

+BEGIN_SRC emacs-lisp

(evil-mode t) ;; Enable "M-x" in evil mode (global-set-key (kbd "M-x") 'execute-extended-command)

+END_SRC

** Leader Mode Config

+BEGIN_SRC emacs-lisp

(global-evil-leader-mode) (evil-leader/set-leader ",") (evil-leader/set-key "w" 'basic-save-buffer "s" 'flyspell-buffer "b" 'evil-buffer "q" 'evil-quit)

+END_SRC

** Evil Surround, emulating tpope's =surround.vim=

+BEGIN_SRC emacs-lisp

(require 'evil-surround) (global-evil-surround-mode 1)

+END_SRC

** Multiple Cursors https://github.com/gabesoft/evil-mc

=evil-mc= provides multiple cursors functionality for Emacs when used with =evil-mode=.

=C-n / C-p= are used for creating cursors, and =M-n / M-p= are used for cycling through cursors. The commands that create cursors wrap around; but, the ones that cycle them do not. To skip creating a cursor forward use =C-t= or =grn= and backward =grp=. Finally use =gru= to remove all cursors.

*** Enable =evil-mc= for all buffers

+BEGIN_SRC emacs-lisp

(global-evil-mc-mode 1)

+END_SRC

** Fast switching between buffers

+BEGIN_SRC emacs-lisp

(define-key evil-normal-state-map (kbd "{") 'evil-next-buffer) (define-key evil-normal-state-map (kbd "}") 'evil-prev-buffer)

+END_SRC

** Increment / Decrement numbers

+BEGIN_SRC emacs-lisp

(global-set-key (kbd "C-=") 'evil-numbers/inc-at-pt) (global-set-key (kbd "C--") 'evil-numbers/dec-at-pt) (define-key evil-normal-state-map (kbd "C-=") 'evil-numbers/inc-at-pt) (define-key evil-normal-state-map (kbd "C--") 'evil-numbers/dec-at-pt)

+END_SRC

** Use =j/k= for browsing wrapped lines

+BEGIN_SRC emacs-lisp

(define-key evil-normal-state-map (kbd "j") 'evil-next-visual-line) (define-key evil-normal-state-map (kbd "k") 'evil-previous-visual-line)

+END_SRC

** Paste in Visual Mode

+BEGIN_SRC emacs-lisp

(define-key evil-insert-state-map (kbd "C-v") 'evil-visual-paste)

+END_SRC

** Disable =evil-mode= for some modes Since Emacs is a multi-purpose LISP REPL, there are many modes that are not primarily (or not at all) centered about text-manipulation. For those, it is reasonable to disable =evil-mode=, because it will bring nothing to the table, but might just shadow some keyboard shortcuts.

+BEGIN_SRC emacs-lisp

(mapc (lambda (mode) (evil-set-initial-state mode 'emacs)) '(elfeed-show-mode elfeed-search-mode forge-pullreq-list-mode forge-topic-list-mode dired-mode tide-references-mode image-dired-mode image-dired-thumbnail-mode eww-mode))

+END_SRC

Turning off evil when working in =cider--debug= minor mode:

+BEGIN_SRC emacs-lisp

(defadvice cider--debug-mode (after toggle-evil activate) "Turn off evil-local-mode' when enabling cider--debug-mode', and turn it back on when disabling `cider--debug-mode'." (evil-local-mode (if cider--debug-mode -1 1)))

+END_SRC

** Unbind certain Emacs keybindings in =evil-mode= =M-.= and =M-,= are popular keybindings for "jump to definition" and "back". =evil-mode= by default binds those to rather rarely used functions =evil-repeat-pop-next= and =xref-pop-marker-stack=, for some reason.

+BEGIN_SRC emacs-lisp

(define-key evil-normal-state-map (kbd "M-.") nil) (define-key evil-normal-state-map (kbd "M-,") nil)

+END_SRC

=M-l= and =M-l M-l= is =downcase-word=. This happens a lot by accident for me. And undoing it often undoes a lot more - like deleting whole paragraphs of text. Also, I don't need it, because I'd use evil bindings for that.

+begin_src emacs-lisp

(define-key global-map (kbd "M-l") nil) (define-key evil-insert-state-map (kbd "M-l M-l") nil)

+end_src

=M-k= is =kill-sentence=. That happens by accident, as well. And sometimes, when in insert-mode, it even erases the history. I don't need it, I'd use evil for that.

+begin_src emacs-lisp

(define-key global-map (kbd "M-k") nil) (define-key evil-insert-state-map (kbd "M-k M-k") nil)

+end_src

=TAB= is =evil-jump-forward=: Go to newer position in jump list.

+begin_src emacs-lisp

(define-key global-map (kbd "") nil) (define-key evil-insert-state-map (kbd "") nil) (evil-define-key 'normal org-mode-map (kbd "") #'org-cycle)

+end_src

** Call =ex= by default on visual selection

+BEGIN_SRC emacs-lisp

(setq evil-ex-visual-char-range t)

+END_SRC

Example:

When visually selecting "foo" out of the string "foo foobar", and then calling =:s/o/i/g=, the result would be "fii fiibar" without this setting. With this setting, it will be "fii foobar".

** =evil-escape= https://github.com/syl20bnr/evil-escape

Escape from insert state and everything else.

+BEGIN_SRC emacs-lisp

(setq-default evil-escape-delay 0.2) (setq-default evil-escape-key-sequence "jk") (evil-escape-mode)

+END_SRC

This results in the same feature-set like this vim keybinding:

+BEGIN_SRC vim

"Remap ESC to jk :imap jk

+END_SRC

** Change some Emacs keybindings

With =backward-kill-sentence=, I sometimes shoot myself in the foot. I trigger this shortcut by accident and then all kinds of stuff happens. And =undo-tree-undo= does not always undo the deed for reasons. Anyway, I do not need the Emacs style =backward-kill-sentence=:

+begin_src emacs-lisp

(global-unset-key (kbd "C-x ")) (global-unset-key (kbd "C-x DEL"))

+end_src

+BEGIN_SRC emacs-lisp

(add-hook 'org-mode-hook 'which-key-mode) (add-hook 'cider-mode-hook 'which-key-mode)

+END_SRC

Use =which-key= to show VIM shortcuts, too.

+BEGIN_SRC emacs-lisp

(setq which-key-allow-evil-operators t) (setq which-key-show-operator-state-maps t)

+END_SRC

Basic Configuration

+BEGIN_SRC emacs-lisp

(ac-config-default)

+END_SRC

*** Tabs Set tab width to 2 for all buffers

+BEGIN_SRC emacs-lisp

(setq-default tab-width 2)

+END_SRC

Use 2 spaces instead of a tab.

+BEGIN_SRC emacs-lisp

(setq-default tab-width 2 indent-tabs-mode nil)

+END_SRC

Indentation cannot insert tabs.

+BEGIN_SRC emacs-lisp

(setq-default indent-tabs-mode nil)

+END_SRC

Use 2 spaces instead of tabs for programming languages.

+BEGIN_SRC emacs-lisp

(setq js-indent-level 2)

(setq coffee-tab-width 2)

(setq python-indent 2)

(setq css-indent-offset 2)

(add-hook 'sh-mode-hook (lambda () (setq sh-basic-offset 2 sh-indentation 2)))

(setq web-mode-markup-indent-offset 2)

+END_SRC

*** Syntax Checking (flycheck) :PROPERTIES: :CUSTOM_ID: flycheck :END:

http://www.flycheck.org/

Enable global on the fly syntax checking through =flycheck=.

+BEGIN_SRC emacs-lisp

(add-hook 'after-init-hook #'global-flycheck-mode)

+END_SRC

**** =flycheck-package= https://github.com/purcell/flycheck-package

This library provides a flycheck checker for the metadata in Emacs Lisp files which are intended to be packages. That metadata includes the package description, its dependencies and more.

+begin_src emacs-lisp

(eval-after-load 'flycheck '(flycheck-package-setup))

+end_src

*** Auto-indent with the Return key

+BEGIN_SRC emacs-lisp

(define-key global-map (kbd "RET") 'newline-and-indent)

+END_SRC

*** Highlight matching parenthesis

+BEGIN_SRC emacs-lisp

(show-paren-mode t)

+END_SRC

*** Delete trailing whitespace

Delete trailing whitespace in all modes. Except when editing Markdown, because it uses [[http://daringfireball.net/projects/markdown/syntax#p][two trailing blanks]] as a signal to create a line break.

+BEGIN_SRC emacs-lisp

  (add-hook 'before-save-hook '(lambda()
                                 (when (not (or (derived-mode-p 'markdown-mode)
                                                (derived-mode-p 'org-mode)))
                                  (delete-trailing-whitespace))))

+END_SRC

*** Code Folding

Enable code folding for programming modes with two strategies:

**** 1. VIM style folds

**** 2. Org mode style folds with =outline-minor-mode= :PROPERTIES: :CUSTOM_ID: org-style-folds-with-outline-minor-mode :END:

=outline-minor-mode= is built-in to Emacs. It enables structural editing of hierarchical structures - just as Org mode does, but in any major mode.

Change the shortcuts to be the same as in Org mode:

+BEGIN_SRC emacs-lisp

(add-hook 'prog-mode-hook #'outline-minor-mode)

;; Org mode style keybindings (define-key outline-minor-mode-map (kbd "C-") 'outline-insert-heading) (define-key outline-minor-mode-map (kbd "M-S-") 'outline-demote) (define-key outline-minor-mode-map (kbd "M-S-") 'outline-promote) (define-key outline-minor-mode-map (kbd "C-c C-n") 'outline-next-visible-heading) (define-key outline-minor-mode-map (kbd "C-c C-p") 'outline-previous-visible-heading)

+END_SRC

Leverage the [[https://github.com/tarsius/bicycle][bicycle]] library from tarsius for the ability to cycle visibility of local and global sections:

+BEGIN_SRC emacs-lisp

(define-key outline-minor-mode-map (kbd "C-") 'bicycle-cycle) (define-key outline-minor-mode-map (kbd "") 'bicycle-cycle-global)

+END_SRC

Use the built-in foldout.el to narrow and widen the current subtree:

+BEGIN_SRC emacs-lisp

(require 'foldout) (define-key outline-minor-mode-map (kbd "C-x n s") 'foldout-zoom-subtree) (define-key outline-minor-mode-map (kbd "C-x n w") 'foldout-exit-fold)

+END_SRC

*** Line numbers

Enable =linum-mode= for programming modes. For newer versions of Emacs, use =display-line-numbers-mode=, because it's much faster.

+BEGIN_SRC emacs-lisp

(add-hook 'prog-mode-hook '(lambda () (if (version<= emacs-version "26.0.50") (linum-mode) (display-line-numbers-mode))))

+END_SRC

*** Indenting a buffer

+BEGIN_SRC emacs-lisp

(defun indent-buffer () (interactive) (save-excursion (indent-region (point-min) (point-max) nil)))

+END_SRC

** Ruby

*** Standard linters

For syntax checking to work, installing the command-line linter tools [[https://gitlab.com/yorickpeterse/ruby-lint][ruby-lint]] and [[https://eslint.org/][eslint]] are a premise:

+BEGIN_SRC shell

gem install rubocop ruby-lint npm install -g eslint

+END_SRC

*** Configuration

+BEGIN_SRC emacs-lisp

(setq ruby-indent-level 2) ;; scss-mode blocks Emacs when opening bigger files, so open them with css-mode (add-to-list 'auto-mode-alist '("\.scss?\'" . css-mode))

(add-to-list 'auto-mode-alist '("\.rb?\'" . enh-ruby-mode)) (add-to-list 'auto-mode-alist '("\.rake?\'" . enh-ruby-mode))

+END_SRC

*** =robe-mode=

https://github.com/dgutov/robe

Code navigation, documentation lookup and completion for Ruby

+BEGIN_SRC emacs-lisp

(add-hook 'enh-ruby-mode-hook 'robe-mode) (add-hook 'robe-mode-hook 'ac-robe-setup) (add-to-list 'auto-mode-alist '("\.erb?\'" . robe-mode))

+END_SRC

Start =robe-mode= with =M-x robe-start=.

Shortcuts:

**** =auto-complete= for =robe-mode=

+BEGIN_SRC emacs-lisp

(add-hook 'enh-ruby-mode-hook 'auto-complete-mode)

+END_SRC

**** REPL

+BEGIN_SRC emacs-lisp

(add-hook 'enh-ruby-mode-hook (lambda () (local-set-key (kbd "C-x C-e") 'ruby-send-line)))

+END_SRC

** Clojure *** Cider

https://github.com/clojure-emacs/cider

Cider is short for The "Clojure Interactive Development Environment that Rocks for Emacs". For good reasons, it is the [[http://blog.cognitect.com/blog/2017/1/31/clojure-2018-results][most popular IDE]] for developing Clojure.

**** CIDER REPL Key Bindings

***** Customization

Remove =C-c C-p= (=cider-pprint-eval-last-sexp=) from mode map in favor of using [[org-style-folds-with-outline-minor-mode][Org mode style folding]].

+BEGIN_SRC emacs-lisp

(add-hook 'cider-mode-hook (lambda () (define-key cider-mode-map (kbd "C-c C-p") nil)))

+END_SRC

**** Dependencies

Create a =~/.lein/profiles.clj= file with:

+BEGIN_SRC clojure

{:user {:plugins [[cider/cider-nrepl "0.13.0-SNAPSHOT"]
                  [refactor-nrepl "2.2.0"]]
        :dependencies [[org.clojure/tools.nrepl "0.2.12"]]}}

+END_SRC

**** Emacs configuration

When connecting to a repl, don't pop to the new repl buffer.

+BEGIN_SRC emacs-lisp

(setq cider-repl-pop-to-buffer-on-connect nil)

+END_SRC

*** =clj-refactor=.

https://github.com/clojure-emacs/clj-refactor.el/

A collection of Clojure refactoring functions for Emacs.

+BEGIN_SRC emacs-lisp

(require 'clj-refactor)

(defun my-clojure-mode-hook () (clj-refactor-mode 1) (yas-minor-mode 1) ; for adding require/use/import statements ;; This choice of keybinding leaves cider-macroexpand-1 unbound (cljr-add-keybindings-with-prefix "C-c C-m"))

(add-hook 'clojure-mode-hook #'my-clojure-mode-hook)

+END_SRC

=clj-refactor= enables refactorings like extracting functions (=C-c C-m ef=). Find the list of available refactorings [[https://github.com/clojure-emacs/clj-refactor.el/wiki][here]].

*** Customizations

**** Integrant based applications

[[https://github.com/weavejester/integrant][Integrant]] configures, starts and manages a =system= and exposes a lifecycle for it.

For REPL-driven development this adds one layer of indirection: When starting a service through =lein run= (or bundled in a Docker container), the =system= will already be started by Integrant. Without having a ref to this =system=, we cannot stop it, we can only start new systems. This means that reloading the code will only start new systems, but not be able to halt the old one. The internal code from Integrant relies on spawning a thread after initializing a system through =lein run= and will not return until the process is done. Therefore we cannot retrieve the system when running =lein run=.

When Emacs has a connection to a REPL for an Integrant based application, this snippet actually enables reloading of front and back-ends. The code doesn't use cider internal functions for interacting with the REPL, because not all buffers might be connected (for example the CLJS buffers might not have a dedicated REPL themselves). Instead, it uses common Elisp.

+BEGIN_SRC emacs-lisp

(defun ok-cider-reload-integrant () (interactive) (require 'seq) (save-buffer) (let ((cider-buffer (first (seq-filter '(lambda (buf) (string-match "cider-repl" buf)) (mapcar 'buffer-name (buffer-list)))))) (if cider-buffer (progn (switch-to-buffer cider-buffer) (insert "(in-ns 'dev)(integrant.repl/reset)") (cider-repl-return) (switch-to-buffer (other-buffer))) (message "No Cider buffer!"))))

(add-hook 'clojure-mode-hook '(lambda () (define-key clojure-mode-map (kbd "C-c r") 'ok-cider-reload-integrant)))

+END_SRC

Usage

When you want to reload the =system=, use =C-c r=. It will save your current buffer and reload the =system=.

*** =flycheck-clj-kondo= https://github.com/borkdude/flycheck-clj-kondo

clj-kondo is installed via stow.

+begin_src emacs-lisp

(add-hook 'clojure-mode-hook (lambda () (require 'flycheck-clj-kondo)))

+end_src

** JavaScript

*** =tide-mode=

https://github.com/ananthakumaran/tide

Claim: TypeScript Interactive Development Environment for Emacs. However, also JavaScript development gets big improvements with =tide-mode=.

Tide is an alternative to [[http://ternjs.net/][Tern]] which also has great Emacs integration and which I have happily been using for years. However, tide works even better (in my experience).

For completion to work in a Node.js project, a =jsconfig.json= file like this is required:

+BEGIN_SRC json

{ "compilerOptions": { "target": "es6" }, "exclude": [ "node_modules" ] }

+END_SRC

If no project file is found, it’ll fall back to an inferred configuration.

Tide default shortcuts:

**** Custom shortcuts

+BEGIN_SRC emacs-lisp

(require 'rjsx-mode) (define-key rjsx-mode-map (kbd "C-c C-r") 'tide-rename-symbol) (define-key rjsx-mode-map (kbd "C-c C-d") 'tide-documentation-at-point)

+END_SRC

**** Setup

+BEGIN_SRC emacs-lisp

(defun setup-tide-mode () (interactive) ;; For bigger JS projects and intense tasks like =tide=references= ;; the default of 2s will time out (setq tide-sync-request-timeout 10) (tide-setup) ;; Increase sync request timeout for bigger projects (flycheck-mode +1) (setq flycheck-check-syntax-automatically '(save mode-enabled)) (eldoc-mode +1) (tide-hl-identifier-mode +1))

(add-hook 'rjsx-mode-hook #'setup-tide-mode)

+END_SRC

*** =js-comint= https://github.com/redguardtoo/js-comint

Run a JavaScript interpreter in an inferior process window. **** Enable

+BEGIN_SRC emacs-lisp

(require 'js-comint)

+END_SRC

**** Configure

+BEGIN_SRC emacs-lisp

(add-hook 'rjsx-mode-hook (lambda () (local-set-key (kbd "C-x C-e") 'js-send-last-sexp) (local-set-key (kbd "C-M-x") 'js-send-last-sexp-and-go) (local-set-key (kbd "C-c b") 'js-send-buffer) (local-set-key (kbd "C-c C-b") 'js-send-buffer-and-go) (local-set-key (kbd "C-c l") 'js-load-file-and-go)))

+END_SRC

*** =flycheck-flow=

[[https://flow.org/][Flow]] is a static type checker for JavaScript.

**** Type Inference

Flow uses type inference to find bugs even without type annotations. It precisely tracks the types of variables as they flow through your program.

**** Idiomatic JS

Flow is designed for JavaScript programmers. It understands common JavaScript idioms and very dynamic code.

**** Realtime Feedback

Flow incrementally rechecks your changes as you work, preserving the fast feedback cycle of developing plain JavaScript.

**** Configuration

+BEGIN_SRC elisp

(require 'flycheck-flow) (add-hook 'javascript-mode-hook 'flycheck-mode)

+END_SRC

*** =rjsx-mode=

https://github.com/felipeochoa/rjsx-mode

This mode derives from js2-mode, extending its parser to support JSX syntax according to the official spec. This means you get all of the js2 features plus proper syntax checking and highlighting of JSX code blocks.

+BEGIN_SRC emacs-lisp

(add-to-list 'auto-mode-alist '("components\/.*\.js\'" . rjsx-mode))

+END_SRC

*** General JavaScript configuration

+BEGIN_SRC emacs-lisp

(add-to-list 'auto-mode-alist '("\.js\'" . rjsx-mode)) (add-hook 'js-mode-hook 'js2-minor-mode) (setq js2-highlight-level 3) (setq js-indent-level 2) ;; Semicolons are optional in JS, do not warn about them missing (setq js2-strict-missing-semi-warning nil)

+END_SRC

** Web *** rainbow-mode =rainbow-mode= is a minor mode for Emacs which displays strings representing colors with the color they represent as background.

+BEGIN_SRC emacs-lisp

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

+END_SRC

*** Impatient Mode

https://github.com/netguy204/imp.el

Live JavaScript Coding Emacs/Browser: See your changes in the browser as you type

**** Usage

Enable the web server provided by simple-httpd: =M-x httpd-start=

Publish buffers by enabling the minor mode impatient-mode: =M-x impatient-mode=

And then point your browser to http://localhost:8080/imp/, select a buffer, and watch your changes appear as you type!

*** Process JSON

https://github.com/200ok-ch/counsel-jq

[[https://stedolan.github.io/jq/][jq]] is a lightweight and flexible command-line JSON processor. This loads a counsel wrapper to quickly test queries and traverse a complex JSON structure whilst having live feedback.

Thanks to [[https://github.com/branch14/emacs.d][@branch14]] of 200ok fame for starting with the initial function!

*** web-mode

http://web-mode.org/

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

+BEGIN_SRC emacs-lisp

(add-to-list 'auto-mode-alist '("\.html?\'" . web-mode)) ;; Ruby Templates (add-to-list 'auto-mode-alist '("\.erb?\'" . web-mode)) ;; Handlebars (add-to-list 'auto-mode-alist '("\.hbs?\'" . web-mode)) ;; JSON (add-to-list 'auto-mode-alist '("\.json?\'" . web-mode))

(setq web-mode-enable-current-element-highlight t) (setq web-mode-ac-sources-alist '(("html" . (ac-source-words-in-buffer ac-source-abbrev))))

+END_SRC

** p_slides

[[https://github.com/munen/p_slides][p_slides]] is a static files only, dead simple way, to create semantic slides. The slide content is markdown, embedded in a HTML file. When opening a =presentation.html= file, enable =markdown-mode=.

+BEGIN_SRC emacs-lisp

(add-to-list 'auto-mode-alist '("presentation.html" . markdown-mode))

+END_SRC

** Auto Reload Web Sites

Introducing a custom =browser-reloading-mode=. It's a quick implementation and not a real derived mode.

When enabling =browser-reloading-mode= for a specific buffer, whenever this buffer is saved, a command-line utility =reload_chromium.sh= is called. This in turn is a wrapper around =xdotool= with which a reloading of the Chromium browser is triggered.

This is handy when working in a web environment that doesn't natively support hot-reloading (static web pages, for instance) and the page has too much (dynamic) content to be displayed properly in =impatient-mode=. I'm using it for example when working on a [[https://github.com/munen/p_slides][p_slides]] slide deck.

+BEGIN_SRC emacs-lisp

(defun reload-chromium () (when enable-browser-reloading (shell-command-to-string "reload_chromium.sh")))

(defun browser-reloading-mode () "Finds the open chromium session and reloads the tab" (interactive) ;; When set, disable the local binding and therefore disable the mode (if enable-browser-reloading (setq enable-browser-reloading nil) ;; Otherwise create a local var and set it to True (progn (make-local-variable 'enable-browser-reloading) (setq enable-browser-reloading t))))

;; By default, disable the guard against using reload-chromium (setq enable-browser-reloading nil) (add-hook 'after-save-hook #'reload-chromium)

+END_SRC

** yaml

+BEGIN_SRC emacs-lisp

(require 'yaml-mode)
(add-to-list 'auto-mode-alist '("\\.yml$" . yaml-mode))
(add-hook 'yaml-mode-hook 'visual-line-mode)

+END_SRC

** Markdown

+BEGIN_SRC emacs-lisp

(add-hook 'markdown-mode-hook 'flyspell-mode) (add-hook 'markdown-mode-hook 'outline-minor-mode)

+END_SRC

Unfortunately line breaks are semantic in some versions of markdown (for example Github). So doing automatic line breaks would be harmful. However, this leads to super long lines in many documents which is unreadable. Therefore, always use =visual-line-mode=.

+BEGIN_SRC emacs-lisp

(add-hook 'markdown-mode-hook 'visual-line-mode)

+END_SRC

** Magit :PROPERTIES: :CUSTOM_ID: magit :END:

https://github.com/magit/magit

Magit is an interface to the version control system Git.

*** Guix packages

+begin_src fundamental :noweb-ref packages

"emacs-magit"

+end_src

*** Configuration

Create shortcut for =Magit=.

+BEGIN_SRC emacs-lisp

(global-set-key (kbd "C-x g") 'magit-status)

+END_SRC

Always sign commits with GPG

+BEGIN_SRC emacs-lisp

(setq magit-commit-arguments (quote ("--gpg-sign=137099B38E1FC0E9")))

+END_SRC

**** Start the commit buffer in evil normal mode

+BEGIN_SRC emacs-lisp

(add-hook 'with-editor-mode-hook 'evil-normal-state)

+END_SRC

**** Performance https://magit.vc/manual/magit/Performance.html

+begin_src emacs-lisp

(setq magit-refresh-status-buffer nil) (setq magit-refresh-verbose t)

(with-eval-after-load 'magit (remove-hook 'magit-refs-sections-hook 'magit-insert-tags) (remove-hook 'server-switch-hook 'magit-commit-diffq) (remove-hook 'with-editor-filter-visit-hook 'magit-commit-diff))

+end_src

** Forge https://github.com/magit/forge/

Work with Git forges from the comfort of [[#magit][Magit]].

*** Guix packages

+begin_src fundamental :noweb-ref packages

"emacs-forge"

+end_src

+BEGIN_SRC emacs-lisp

(with-eval-after-load 'magit (require 'forge))

+END_SRC

Add 200ok gitlab instance to list of known forges

+BEGIN_SRC emacs-lisp

(with-eval-after-load 'forge (add-to-list 'forge-alist '("gitlab.200ok.ch" "gitlab.200ok.ch/api/v4" "gitlab.200ok.ch" forge-gitlab-repository)) (add-to-list 'forge-alist '("gitlab.switch.ch" "gitlab.switch.ch/api/v4" "gitlab.switch.ch" forge-gitlab-repository)))

+END_SRC

Show assigned issues and PRs as well as requested reviews directly in the status buffer:

+BEGIN_SRC emacs-lisp

;; TODO: This is not possible in newer versions of magit/forge, ;; anymore, but there are alternatives: ;; https://github.com/magit/forge/issues/676

;; (with-eval-after-load 'magit ;; (magit-add-section-hook 'magit-status-sections-hook 'forge-insert-assigned-issues nil t) ;; (magit-add-section-hook 'magit-status-sections-hook 'forge-insert-assigned-pullreqs nil t) ;; (magit-add-section-hook 'magit-status-sections-hook 'forge-insert-requested-reviews nil t))

+END_SRC

** =magit-delta= https://github.com/dandavison/magit-delta

Provides a minor mode which configures Magit to use [[https://github.com/dandavison/delta][delta]] when displaying diffs.

Enable =magit-delta= when running =magit=.

+begin_src emacs-lisp

(with-eval-after-load 'magit (add-hook 'magit-mode-hook (lambda () (magit-delta-mode +1))))

+end_src

Enable =magit-delta= only for 'regular' diffs, but not when merging huge amounts of stuff, because that'll make Emacs hang. Taken from https://github.com/dandavison/magit-delta/issues/9#issuecomment-795435781.

+begin_src emacs-lisp

(defun magit-delta-toggle () "Toggle magit-delta-mode and refresh magit." (interactive) (progn (call-interactively 'magit-delta-mode) (magit-refresh)))

(defvar nth/magit-delta-point-max 50000) ;; Disable mode if there are too many characters (advice-add 'magit-delta-call-delta-and-convert-ansi-escape-sequences :around (defun nth/magit-delta-colorize-maybe-a (fn &rest args) (if (<= (point-max) nth/magit-delta-point-max) (apply fn args) (magit-delta-mode -1)))) ;; Re-enable mode after `magit-refresh' if there aren't too many characters (add-hook 'magit-post-refresh-hook (defun nth/magit-enable-magit-delta-maybe-h (&rest _args) (when (and (not magit-delta-mode) (<= (point-max) nth/magit-delta-point-max)) (magit-delta-mode +1))))

+end_src

Override the settings (=~/.gitconfig=) for =delta=, because the =line-numbers= feature won't work well with =magit-delta= (see https://github.com/dandavison/magit-delta/issues/13).

+begin_src emacs-lisp

(setq magit-delta-delta-args '("--24-bit-color" "always" "--features" "magit-delta" "--color-only"))

+end_src

** Projectile

https://github.com/bbatsov/projectile

Projectile is a project interaction library. For instance - finding project files (=C-c p f=) or jumping to a new project (=C-c p p=).

*** Configuration

Enable Projectile globally

+BEGIN_SRC emacs-lisp

(projectile-mode +1) (define-key projectile-mode-map (kbd "C-c p") 'projectile-command-map)

+END_SRC

Disable projectile when using TRAMP. Otherwise Tramp will crawl to a halt.

+begin_src emacs-lisp

(defadvice projectile-project-root (around ignore-remote first activate) (unless (file-remote-p default-directory) ad-do-it))

+end_src

** Dumb Jumb

https://github.com/jacktasia/dumb-jump

"Jump to definition" with support for multiple programming languages that favors "just working". This means minimal -- and ideally zero -- configuration with absolutely no stored indexes (TAGS) or persistent background processes.

Dumb Jump uses The Silver Searcher ag, ripgrep rg, or grep to find potential definitions of a function or variable under point. It uses a set of regular expressions based on the file extension, or major-mode, of the current buffer.

+BEGIN_SRC emacs-lisp

(dumb-jump-mode) (setq dumb-jump-selector 'ivy)

+END_SRC

*** Usage

The one important shortcut is =C-M-g= which attempts to jump to the definition of the thing under point.

** Code Styleguides

*** Auto-formatting

Automatically format code for different languages and frameworks.

This implements the interactive function =autoformat= which is a thin wrapper around command-line based code autoformatters which it utilizes through a strategy pattern.

To add a new language/framework, the only required change is to add the respective command-line tool configuration into a separate strategy function. It is trivial to do if the new language/framework has a command-line tool which takes code into =stdin= and formats it to =stdout=.

It's possible to install the dependencies locally, so that the setup doesn't impose dependencies on team members - or they can be installed through the respective packages managers (npm/yarn) to enforce code guidelines.

This requires =prettier=, =@prettier/plugin-ruby= and =prettier-eslint-cli= to be installed:

+BEGIN_SRC shell

npm install -g prettier-eslint-cli prettier @prettier/plugin-ruby

+END_SRC

Linting JavaScript with [[https://eslint.org/][eslint]] happens automatically through [[#flycheck][flycheck]]. eslint just needs to be installed.

+BEGIN_SRC shell

npm install -g eslint

+END_SRC

+BEGIN_SRC emacs-lisp

(defun autoformat () "Automatically format current buffer." (interactive)

(if (derived-mode-p 'clojure-mode)
    (autoformat-clojure-function)
  (let ((eslint-path (concat (projectile-project-root)
                             ".eslintrc.yml"))) ; could be .json or .yml
    (autoformat-with
     (cond ((derived-mode-p 'web-mode) 'autoformat-html-command)
           ((derived-mode-p 'css-mode) 'autoformat-css-command)
           ((derived-mode-p 'nxml-mode) 'autoformat-xml-command)
           ((derived-mode-p 'json-mode) 'autoformat-json-command)
           ((derived-mode-p 'sass-mode) 'autoformat-sass-command)
           ((derived-mode-p 'yaml-mode) 'autoformat-yaml-command)
           ((derived-mode-p 'enh-ruby-mode) 'autoformat-ruby-command)
           ;; JS projects with eslint config
           ((and (file-exists-p eslint-path)
                 (derived-mode-p 'js2-mode))
            'autoformat-prettier-eslint-command)
           ((derived-mode-p 'js2-mode) 'autoformat-javascript-command))))))

(defun autoformat-with (strategy) "Automatically format current buffer using STRATEGY." (let ((p (point)) (s (window-start))) ;; Remember the current position (save-mark-and-excursion ;; Call prettier-eslint binary with the contents of the current ;; buffer (shell-command-on-region (point-min) (point-max) (funcall strategy) ;; Write into a temporary buffer (get-buffer-create "Temp autoformat buffer") ;; Replace the current buffer with the output of ;; the =autoformat strategy= output t ;; If the =autoformat strategy= returns an error, show it in a ;; separate error buffer (get-buffer-create "replace-errors") ;; Automatically show error buffer t)) ;; Return to the previous point and scrolling position (the point ;; was lost, because the whole buffer got replaced. (set-window-start (selected-window) s) (goto-char p)))

(defun autoformat-clojure-function () "Cider function to format Clojure buffer." (indent-buffer) ;; (cider-format-buffer) )

(defun autoformat-ruby-command () "CLI tool to format Ruby." "prettier --parser ruby")

(defun autoformat-javascript-command () "CLI tool to format Javascript." "prettier --parser babel")

(defun autoformat-html-command () "CLI tool to format HTML." "prettier --parser html")

(defun autoformat-css-command () "CLI tool to format CSS." "prettier --parser css")

(defun autoformat-xml-command () "CLI tool to format XML." "xmllint -format -")

(defun autoformat-sass-command () "CLI tool to format SASS." "prettier --parser sass")

(defun autoformat-json-command () "CLI tool to format JSON." "prettier --parser json")

(defun autoformat-yaml-command () "CLI tool to format YAML." "prettier --parser yaml")

(defun autoformat-prettier-eslint-command () "CLI tool to format Javascript with .eslintrc.json configuration." (concat "npx prettier-eslint --stdin --eslint-config-path=" ;; Hand over the path of the current projec (concat (projectile-project-root) ".eslintrc.yml") " --stdin-filepath=" (buffer-file-name) " --parser babel"))

+END_SRC

Shortcut

+BEGIN_SRC emacs-lisp

(setq ok-autoformat-modes (list 'web-mode 'css-mode 'json-mode 'clojure-mode 'sass-mode 'enh-ruby-mode 'yaml-mode 'js2-mode 'rjsx-mode))

(dolist (mode ok-autoformat-modes) (evil-leader/set-key-for-mode mode "f" 'autoformat))

+END_SRC

Demo

[[file:images/demo-ok-autoformat.gif][file:images/demo-ok-autoformat.gif]]

**** Call autoformat on every save - for certain projects

I don't want to =autoformat= for every project, because I might not be the primary owner of the code (that accounts for consulting projects). However, there are projects where I actually do want to run =autoformat= every time. That is on projects with strict formatting requirements.

NB: The overhead of prettier + eslint is about 1.3s on a maxed out X1 Carbon 6th gen.

+BEGIN_SRC emacs-lisp

;; Define list of projects to autoformat (setq ok-autoformat-projects (list "src/200ok/organice"))

(add-hook 'before-save-hook '(lambda() ;; Check if the current directory matches the list of ;; projects that are to be autoformatted. (if (seq-some '(lambda (e) (numberp e)) (mapcar '(lambda (dir) (string-match dir (projectile-project-root))) ok-autoformat-projects) ) (when (or (derived-mode-p 'js2-mode) (derived-mode-p 'css-mode) (derived-mode-p 'sass-mode) (derived-mode-p 'yaml-mode)) (autoformat)))))

+END_SRC

***** Alternative implementation

NB: This could be a good alternative solution. However, scoping to the local directory doesn't work like this. Maybe I'm doing it wrong, maybe dir-locals just shouldn't be used outside of setting variables.

Call autoformat on every save for specific projects

those projects, you can enable =autoformat= by creating a =.dir-locals.el= file in your home directory.

+BEGIN_EXAMPLE emacs-lisp

(("src" (nil . ((eval add-hook 'before-save-hook '(lambda() (autoformat)))))))

+END_EXAMPLE

The first node "src/" is the directory, while the second node is the mode-name, or "nil" to apply to every mode.

*** Editorconfig

[[https://editorconfig.org/][EditorConfig]] helps maintain consistent coding styles for multiple developers working on the same project across various editors and IDEs. I'm an Emacs guy, however, when in an heterogeneous team, it does make sense to adhere to some commonly shared definitions.

With this plugin, if there is an =.editorconfig= in a project, the settings in this file will trump my personal config.

+BEGIN_SRC emacs-lisp

(editorconfig-mode 1)

+END_SRC

** =hcl-mode= https://github.com/purcell/emacs-hcl-mode

Major mode for [[https://github.com/hashicorp/hcl][Hashicorp Configuration Language]]. I use it for [[https://www.terraform.io/][Terraform]].

+begin_src emacs-lisp

(add-to-list 'auto-mode-alist '("\.tf" . hcl-mode))

+end_src

** Drools

+begin_src emacs-lisp

(add-to-list 'auto-mode-alist '("\.drl\'" . java-mode))

+end_src

I installed [[https://github.com/ggerganov/whisper.cpp][whisper.ccp]] manually on the commandline, though whisper.el supports automatic installation.

I benchmarked different models against each other. The "base" model is quite fast and has good results. "tiny" has worse results and starting from "small", the computation time goes way up.

Also, I've benchmarked whisper.cpp streaming vs whisper.el and the latter is far superior.

Note that setting the =whisper-language= to 'auto' implies that one recording can only have one language. Giving it multiple sentences in different languages will not return a happy result.

+begin_src emacs-lisp

(quelpa '(whisper :fetcher git :url "https://github.com/natrys/whisper.el.git"))

(setq whisper-install-directory "~/src" whisper-model "base-q8_0" whisper-language "auto" whisper-use-threads 10 whisper-translate nil)

;; Don't use this locally anymore, because it's too slow for me to run ;; a large model on a machine without appropriate GPU. I'm using ;; ok-whisper, instead. ;; (define-key global-map (kbd "C-x R") 'whisper-run)

+end_src

** Access to whisper recordings in Emacs from anywhere in the OS

With an OS keybinding of =Shift + Alt + r=, I run =emacsclient -c --eval '(start-whisper-recording)= (I use xbindkeys for that).

+begin_src emacs-lisp

(defun start-whisper-recording () "Interactively start a new whisper recording." (interactive) (switch-to-buffer "whisper-recording") (self-insert-command 1) (whisper-run))

+end_src

+begin_src emacs-lisp

(load "~/.emacs.d/ok-whisper") (define-key global-map (kbd "C-x R") 'ok-whisper--record-dwim)

+end_src

Outline-based notes management and organizer. It is an outline-mode for keeping track of everything.

Next to Emacs Org mode, I use organice (https://github.com/200ok-ch/organice/) to manage my Org files on the go and to collaborate with non-Emacs users.

** General config

+BEGIN_SRC emacs-lisp

(setq org-directory "~/Dropbox/org/")

+END_SRC

Configure =org-display-inline-images= width so that they always fit. My screenshots would otherwise overflow, because I'm on a HiDPI display.

+begin_src emacs-lisp

(setq org-image-actual-width 720)

+end_src

** Plain Lists Allow ‘a.’, ‘A.’, ‘a)’ and ‘A) as list elements:

+BEGIN_SRC emacs-lisp

(setq org-list-allow-alphabetical t)

+END_SRC

** Warn about an approaching deadline

The default is 14 days ahead. That's way too much for me. If a task needs a lot of work ahead of the deadline, I'll set a custom reminder date or an additional schedule.

+BEGIN_SRC emacs-lisp

(setq org-deadline-warning-days 3)

+END_SRC

** General configuration

+BEGIN_SRC emacs-lisp

(require 'org)

; languages for org-babel support (org-babel-do-load-languages 'org-babel-load-languages '( (shell . t) (dot . t) (js . t) (ruby . t) ))

(add-hook 'org-mode-hook 'auto-fill-mode) (add-hook 'org-mode-hook 'flyspell-mode)

(evil-leader/set-key "a" 'org-archive-subtree-default)

;; Allow =pdflatex= to use shell-commands. This will allow it to use ;; =pygments= as syntax highlighter for exports to PDF. (setq org-latex-pdf-process '("pdflatex -shell-escape -interaction nonstopmode -output-directory %o %f" "pdflatex -shell-escape -interaction nonstopmode -output-directory %o %f" "pdflatex -shell-escape -interaction nonstopmode -output-directory %o %f")) ;; Alternatively use =xelatex=. Required for documents where I want to use ttf fonts. ;; (setq org-latex-pdf-process ;; '("xelatex -shell-escape -interaction nonstopmode -output-directory %o %f" ;; "xelatex -shell-escape -interaction nonstopmode -output-directory %o %f" ;; "xelatex -shell-escape -interaction nonstopmode -output-directory %o %f"))

;; Include =minted= package for LaTeX exports (add-to-list 'org-latex-packages-alist '("" "minted")) (setq org-latex-listings 'minted)

;; Don’t ask every time when executing a code block. (setq org-confirm-babel-evaluate nil)

+END_SRC

** =imenu=

=imenu= would normally only index two levels - since I run deeply nested documents, go up to six levels.

+BEGIN_SRC emacs-lisp

(setq org-imenu-depth 6)

+END_SRC

When a document is folded and the user searches and finds with =imenu=, the body of the folded header is revealed, so that the search result can actually be seen.

+BEGIN_SRC emacs-lisp

(defun ok-imenu-show-entry () "Reveal content of header." (cond ((and (eq major-mode 'org-mode) (org-at-heading-p)) (org-show-entry) (org-reveal t)) ((bound-and-true-p outline-minor-mode) (outline-show-entry))))

(add-hook 'imenu-after-jump-hook 'ok-imenu-show-entry)

+END_SRC

** KOMA Script export

+BEGIN_SRC emacs-lisp

(require 'ox-latex) (add-to-list 'org-latex-classes '("scrartcl" "\documentclass{scrartcl}" ("\section{%s}" . "\section*{%s}")))

+END_SRC

** Tufte org-mode export

+BEGIN_SRC emacs-lisp

(require 'ox-latex) (add-to-list 'org-latex-classes '("tuftehandout" "\documentclass{tufte-handout} \usepackage{color} \usepackage{amssymb} \usepackage{amsmath} \usepackage{gensymb} \usepackage{nicefrac} \usepackage{units}" ("\section{%s}" . "\section{%s}") ("\subsection{%s}" . "\subsection{%s}") ("\paragraph{%s}" . "\paragraph{%s}") ("\subparagraph{%s}" . "\subparagraph{%s}")))

+END_SRC

** Memoir org mode Export

+begin_src emacs-lisp

(with-eval-after-load 'ox-latex (add-to-list 'org-latex-classes '("memoir" "\documentclass[12pt]{memoir}" ("\chapter{%s}" . "\chapter{%s}") ("\section{%s}" . "\section{%s}") ("\subsection{%s}" . "\subsection{%s}") ("\subsubsection{%s}" . "\subsubsection{%s}") ("\paragraph{%s}" . "\paragraph{%s}") ("\subparagraph{%s}" . "\subparagraph{%s}"))))

+end_src

** Tags

Align tags to the far right of the screen. =-77= would be good for a smaller 80 character terminal.

+begin_src emacs-lisp

 (setq org-tags-column -100)

+end_src

** Capture Templates :PROPERTIES: :END: Set up capture templates for:

Org Capture Templates are explained [[http://orgmode.org/manual/Capture-templates.html][here]], Org Template expansion [[http://orgmode.org/manual/Template-expansion.html#Template-expansion][here.]]

+BEGIN_SRC emacs-lisp

;; Set org-capture inbox
(setq org-default-notes-file (concat org-directory "inbox.org"))
(define-key global-map "\C-cc" 'org-capture)

(setq things-file (expand-file-name "things.org" org-directory))
(setq reference-file (expand-file-name "reference.org" org-directory))
(setq media-file (expand-file-name "media.org" org-directory))

(defun get-domainname (address)
  "Extract TLD (without country) from ADDRESS.
Example: Return '200ok' from 'alain@200ok.ch'."
  (replace-regexp-in-string
   "\-" "_"
   (nth 0
        (split-string (nth 1 (split-string address "@"))
                      "\\."))))

(defun from-name (fromname fromaddress from)
  "Return the first non-empty match for FROMNAME FROMADDRESS and FROM."
  (nth 0
       (seq-filter '(lambda (s)
                      (not (string-empty-p s)))
                   (list fromname fromaddress from))))

(setq org-capture-templates
      `(("t" "Todo" entry (file+olp things-file "Inbox" "Tasks")
         "* TODO %?\n  %U\n  %i\n  %a" :prepend t)
         ("w" "Waiting" entry (file+olp things-file "Waiting")
         "* WAITING %?\n  %U\n  %i\n  %a")
         ;; Creates an expense line for the date of the mail, prompts
         ;; for the amount and currency
         ("e" "Expense" table-line (file+olp things-file "Inbox" "Expenses")
          "|%(org-insert-time-stamp (org-read-date nil t \"%:date\") nil t) | %(from-name \"%:fromname\" \"%:fromaddress\" \"%:from\")| [[%:link][Mail]] | %^{amount} | %^{currency|usd|chf|eur} | | | %^{scope|200ok-alain|200ok-joint|lambda} |")
        ("m" "Mail" entry (file+olp things-file "Inbox" "Mails")
         ;; Creates "* TODO <2019-05-01 Wed> FromName [[mu4e:msgid:uuid][MessageSubject]] :200ok:
         ;; Therefore Emails can be properly:
         ;;   - Used as tasks
         ;;   - Attributed tags
         ;;   - Ordered by priority
         ;;   - Scheduled
         ;;   - etc
         "* TODO %(org-insert-time-stamp (org-read-date nil t \"%:date\") nil t) %(from-name \"%:fromname\" \"%:fromaddress\" \"%:from\") %a \t :%(get-domainname \"%:toaddress\"):")
        ("d" "Daily focus" plain (file+olp things-file "Inbox" "Daily")
         (file "~/.emacs.d/org-templates/daily_focus.org"))
        ("M" "Meeting minutes" plain (file+olp things-file "Inbox" "Tasks")
         (file "~/.emacs.d/org-templates/minutes.org"))
        ("s" "Code Snippet" entry (file+headline "~/src/200ok/knowledge/README.org" "Snippets")
         ;; Prompt for tag and language
         "* %?\t%^g\n#+BEGIN_SRC %^{language}\n%i\n#+END_SRC")
        ("S" "Shopping" entry (file+olp "~/Dropbox/org/shared_with_monika/shared_alain_and_monika.org" "@Shopping")
         "* TODO %?\n  %U\n  %i\n  %a" :prepend t)

        ("l" "Logbook entry" entry (file+olp reference-file "Logbook" ,(format-time-string "%Y"))
         "* %?\n  %U\n  %i\n  %a" :prepend t)
        ;; NOTE: This would result in a cleanlier logbook, but
        ;; organice does not support it, yet.
        ;; ("l" "Logbook entry" entry (file+olp+datetree reference-file "Logbook")
        ;;  "* %?\n  %U\n  %i\n  %a")

        ("p" "password" entry (file+headline "~/Dropbox/org/vault/primary.org.gpg" "Passwords")
         ;; Prompt for name
         "* %^{name}
 :PROPERTIES:
 :username: %^{username}
 :password: %(generate-password-non-interactive)
 :url: %^{url}

 :END:")

        ("u" "URL / Media Inbox" entry (file+olp media-file "Inbox")
                     "* %?\n%U\nURL: \n" :prepend t)
        ;; Legacy style
        ;; ("u" "URL" entry
        ;;  (file+datetree media-file)
        ;;  "* %?\nURL: \nEntered on %U\n")

))

+END_SRC

*** Ensure text from capture-templates end with a newline

If they don't, then the result will look like:

+BEGIN_EXAMPLE

,* Tasks ,* TODO Foo from capture-template This should be on the next line

+END_EXAMPLE

This obviously breaks the structure of the Org file. Here's a fix:

+BEGIN_SRC emacs-lisp

(defun add-newline-at-end-if-none () "Add a newline at the end of the buffer if there isn't any." (save-excursion (save-restriction (goto-char (1- (point-max))) (if (not (looking-at "\n\n")) (progn (goto-char (point-max)) (insert "\n"))))))

(add-hook 'org-capture-before-finalize-hook 'add-newline-at-end-if-none)

+END_SRC

** Structure templates

Enable the =<s TAB= syntax for [[https://orgmode.org/manual/Structure-Templates.html][structure templates]].

+begin_src emacs-lisp

(if (version<= "27.1" emacs-version) (require 'org-tempo))

+end_src

** Pomodoro

A lightweight implementation of the Pomodoro Technique is implemented through customizing Org mode. These are the commands:

It sets the following stats:

Alternatively, if you do not want to manually start a pomodoro, you can hook into the Org mode clocking mechanism. When =ok-pomodoro-auto-clock-in= is set, for every Clock that is started (=C-c C-x C-i=) an automatic Timer is scheduled to 25min. After these 25min are up, a "Time to take a break!" message is played and a pop-up notification is shown.

The timer is not automatically stopped on clocking out, because clocking in should still work on new tasks without resetting the Pomodoro.

The timer can manually be stopped with =M-x org-timer-stop=.

A break can be started with =M-x pomodoro-break=. A pomodoro can also manually be started without clocking in via =M-x pomodoro-start=.

+BEGIN_SRC emacs-lisp

;; Configure primary org pomodoro buffer to which the timers will get ;; attached to. (setq ok-pomodoro-buffer "things.org") (load "~/.emacs.d/org-pomodoro")

+END_SRC

** Keyword sets

I use two workflow sets:

Additionally I sometimes use the keywords PROJECT and AGENDA to denote special bullets that I might tag (schedule/deadline) in the agenda. These keywords give semantics to those bullets.

Note that "|" denotes a semantic state change that is reflected in a different color. Putting the pipe at the end means that all states prior should be shown in the same color.

+BEGIN_SRC emacs-lisp

(setq org-todo-keywords '((sequence "TODO" "|" "DONE") (sequence "PROJECT" "AGENDA" "|" "MINUTES") (sequence "WAITING" "|" "PROGRESS")))

+END_SRC

** Clock Table

*** Clocksum Format

When using a clock table, org will by default sum up the time in perfectly human readable terms like this:

| Headline | Time | |--------------+-----------| | Total time | 1d 1:03 |

For easy calculations (I don't want to parse our hours, weeks and what not), I do prefer that the summation is done only in fractional hours. =org-duration-format= is very powerful, the help is helpful to understand the syntax and options.

+BEGIN_SRC emacs-lisp

(setq org-duration-format '(("h" . t) (special . 2)))

+END_SRC

This will render the same time as above as:

| Headline | Time | |--------------+---------| | Total time | 25.05 |

** GTD

+BEGIN_SRC emacs-lisp

(load-library "find-lisp")

(defun set-org-agenda-files () "Set different org-files to be used in org-agenda." (setq org-agenda-files (flatten-list (list (concat org-directory "things.org") (concat org-directory "reference.org") (concat org-directory "media.org") (concat org-directory "shared_with_monika/shared_alain_and_monika.org") "~/src/200ok/admin/THINGS.org" (find-lisp-find-files "~/src/200ok/admin/pm/" "^pm.*.org$")))))

(set-org-agenda-files)

(global-set-key "\C-cl" 'org-store-link)

(defun things () "Open main 'org-mode' file and start 'org-agenda' for today." (interactive) (find-file (concat org-directory "things.org")) (set-org-agenda-files) (org-agenda-list) (org-agenda-day-view) (shrink-window-if-larger-than-buffer) (other-window 1))

+END_SRC

** Refile Targets

For a proficient GTD workflow, it is important to be able to refile one item from one list easily to another (for example when processing an inbox). Orgmode makes this easy with the refile command =C-c C-w=.

Define where the refiling can happen (the default is to the local buffer):

+BEGIN_SRC emacs-lisp

(setq org-refile-targets (quote ((nil :maxlevel . 9) ;; local buffer (org-agenda-files :maxlevel . 4))))

+END_SRC

** Show "calendar week" in calendar

+BEGIN_SRC emacs-lisp

(setq calendar-week-start-day 1)

(setq calendar-intermonth-text '(propertize (format "%2d" (car (calendar-iso-from-absolute (calendar-absolute-from-gregorian (list month day year))))) 'font-lock-face 'font-lock-warning-face))

(setq calendar-intermonth-header (propertize "CW" 'font-lock-face 'font-lock-keyword-face))

+END_SRC

** Hide empty lines between sub-headers in collapsed view

+BEGIN_SRC emacs-lisp

(setq org-cycle-separator-lines 0)

+END_SRC

** Restclient mode

https://github.com/pashky/restclient.el

HTTP REST client tool for emacs

*** Integration into Org mode

https://github.com/alf/ob-restclient.el

An extension to restclient.el for emacs that provides org-babel support.

+BEGIN_SRC emacs-lisp

(org-babel-do-load-languages 'org-babel-load-languages '((restclient . t)))

+END_SRC

** =org-download= https://github.com/abo-abo/org-download

+begin_src emacs-lisp

 (require 'org-download)

 (add-hook 'dired-mode-hook 'org-download-enable)
 (setq org-download-heading-lvl nil)

+end_src

Don't save org-download images nested according to header, save them flat.

+begin_src emacs-lisp

(setq-default org-download-heading-lvl nil)

+end_src

Save org-download images into the "images" directory.

+begin_src emacs-lisp

(setq-default org-download-image-dir (concat org-directory "images"))

+end_src

** Org habits https://orgmode.org/manual/Tracking-your-habits.html

Show habits outside of today's agenda view:

+begin_src emacs-lisp

(setq org-habit-show-habits-only-for-today nil)

+end_src

** =gptel=

https://github.com/karthink/gptel

+begin_src emacs-lisp

;; (load "~/src/gptel-clone/gptel-openai.el") ;; (load "~/src/gptel-clone/gptel-org.el") ;; (load "~/src/gptel-clone/gptel.el") ;; (load "~/src/gptel-clone/gptel-curl.el") ;; (load "~/src/gptel-clone/gptel-transient.el") ;; (load "~/src/gptel-clone/gptel-kagi.el") ;; (load "~/src/gptel-clone/gptel-ollama.el")

(setq gptel-default-mode 'org-mode) (setq gptel-model "gpt-4-turbo-preview")

;; The chats can have long lines. (add-hook 'gptel-mode-hook 'visual-line-mode)

(gptel-make-ollama "Ollama"
:host "localhost:11434"
:stream t
:models '("codellama"))

(gptel-make-kagi "Kagi"
:key kagi-key)

(global-set-key (kbd "C-c g m") 'gptel-menu) (global-set-key (kbd "C-c g c") 'gptel) (global-set-key (kbd "C-c g s") 'gptel-send) (global-set-key (kbd "C-c g a") 'gptel-add) (global-set-key (kbd "C-c g A") 'gptel-abort)

;; (global-set-key (kbd "M-G") 'gptel-menu) ;; (global-set-key (kbd "M-c") 'gptel) ;; (global-set-key (kbd "M-S") 'gptel-send)

+end_src

** =org-ai= https://github.com/rksm/org-ai

General setup:

+begin_src emacs-lisp

(require 'org-ai) (add-hook 'org-mode-hook #'org-ai-mode)

+end_src

The OpenAI API key is in the =~/.authinfo= file.

Integrate "ai" snippet in Org mode:

+begin_src emacs-lisp

(org-ai-install-yasnippets) (setq org-ai-default-chat-model "gpt-3.5-turbo") ;; (setq org-ai-default-chat-model "gpt-4") (add-hook 'org-mode-hook #'yas-minor-mode)

+end_src

Typing "ai =TAB=" will create a block like this:

+begin_example

+begin_ai

[SYS]: You are a helpful assistant.

[ME]:

+end_ai

+end_example

** Tables

Bring back 'clear cell' shortcut which used to be the default binding, but was removed in the [[https://github.com/bzg/org-mode/blob/main/etc/ORG-NEWS#the-keybinding-for-org-table-blank-field-has-been-removed][9.5 release]].

+begin_src emacs-lisp

(define-key org-mode-map (kbd "C-c SPC") #'org-table-blank-field)

+end_src

** Org Attach

Store a link to a file when attaching it. So after adding an attachment you can just use =C-c C-l= to insert the link.

+begin_src emacs-lisp

(setq org-attach-store-link-p 'attached)

+end_src

** Markdown export

+begin_src emacs-lisp

(eval-after-load "org" '(require 'ox-md nil t))

+end_src

** Better unfold performance https://www.reddit.com/r/orgmode/comments/1drz332/anyone_else_occasionally_experience_being_unable/

Sometimes/quite often, hitting TAB just won't unfold a heading.

+begin_src emacs-lisp

(setq org-fold-core-style 'overlays)

+end_src

** Custom functions *** Show only the next 'n' subheaders

Something similar to org-narrow-to-block, but for a dynamic number of elements. This is useful when looking at a 'Next' entry in a GTD file and not wanting to be overwhelmed by the whole list, for example.

+begin_src emacs-lisp

(defun org-narrow-to-next-n-subheaders (n) "Narrow the buffer to the next N subheaders." (interactive "nNumber of subheaders: ") (save-excursion (org-back-to-heading t) (let ((start (point)) end) (forward-line) (while (and (> n 0) (not (eobp))) (when (org-at-heading-p) (setq n (1- n))) (forward-line)) (setq end (point)) (narrow-to-region start end))))

+end_src

** WIP Notifications / appointment reminders

This is a work-in-progress, but already working.

Important commands are:

+begin_src emacs-lisp

(quelpa '(codeium :fetcher git :url "https://github.com/Exafunction/codeium.el.git")) (add-to-list 'completion-at-point-functions #'codeium-completion-at-point)

+end_src

https://github.com/vedang/pdf-tools, forked from https://github.com/politza/pdf-tools

PDF Tools is, among other things, a replacement of DocView for PDF files. The key difference is that pages are not pre-rendered by e.g. ghostscript and stored in the file-system, but rather created on-demand and stored in memory.

PDF Tools for me is - hands down - the best PDF viewer! It's not an excuse to do even more within Emacs.

** Configuration

When using =evil-mode= and =pdf-tools= and looking at a zoomed PDF, it will blink, because the cursor blinks. This configuration disables this whilst retaining the blinking cursor in other modes.

+BEGIN_SRC emacs-lisp

(evil-set-initial-state 'pdf-view-mode 'emacs) (add-hook 'pdf-view-mode-hook (lambda () (set (make-local-variable 'evil-emacs-state-cursor) (list nil))))

+END_SRC

Elfeed is an extensible web feed reader for Emacs, supporting both Atom and RSS.

** Configuration

+BEGIN_SRC emacs-lisp

;; (require 'elfeed) ;; (require 'elfeed-goodies)

;; (elfeed-goodies/setup)

+END_SRC

Automatic word-wrap for elfeed entries:

+BEGIN_SRC emacs-lisp

;; (add-hook 'elfeed-show-mode-hook 'visual-line-mode)

+END_SRC

Use VIM style scrolling in elfeed entries:

+BEGIN_SRC emacs-lisp

;; (define-key elfeed-show-mode-map (kbd "C-e") 'evil-scroll-line-down) ;; (define-key elfeed-show-mode-map (kbd "C-y") 'evil-scroll-line-up)

+END_SRC

** Define elfeed feeds

+BEGIN_SRC emacs-lisp

;; (load "~/.emacs.d/elfeed-feeds.el")

+END_SRC

Editing text areas in browsers can be quite tedious for the lack of a good editor. Luckily, there's good extensions for both Chrome/Chromium and Firefox to have a live binding to an Emacs session.

There is a good Emacs package called [[https://github.com/alpha22jp/atomic-chrome][Atomic Chrome]] which is similar to [[https://www.emacswiki.org/emacs/Edit_with_Emacs][Edit with Emacs]], but has some advantages as below with the help of websockets:

The name "Atomic Chrome" is a bit misleading, because it actually supports the "GhostText" protocol which allows it to be used with Firefox, as well.

On Firefox, I'm using the [[https://github.com/GhostText/GhostText][GhostText]] addon. On Chromium, I'm using the [[https://github.com/tuvistavie/atomic-chrome][AtomicChrome]] extension. GhostText is also available for Chrome, but it doesn't work for me which is a non-issue, because both plugins work just the same way: Enter a textarea, hit a button, Emacs opens up, type the text, end the session with =C-c C-c=.

+BEGIN_SRC emacs-lisp

(require 'atomic-chrome) ;; Handle if there is an Emacs instance running which has the server already ;; started (ignore-errors ;; Start the server (atomic-chrome-start-server))

+END_SRC

Note: I opened a [[https://github.com/alpha22jp/atomic-chrome/pull/40][PR against AtomicChrome]] which will make the safe-guard obsolete.

Default mode

+BEGIN_SRC emacs-lisp

(setq atomic-chrome-default-major-mode 'markdown-mode)

+END_SRC

Copy to clipboard

Some websites have aggressive JS which triggers when text is entered to a textarea which can lead to bugs in combination with AtomicChrome. There's some websites where I regularly lose the text that's entered. While I'm editing, the textarea is updating, but on =C-c C-c=, Emacs closes and the textarea is empty. For such cases, I'm using this simple workaround: Copy the contents to clipboard just before closing Emacs. So if the contents are lost, I can just paste the text into the textarea. Not a perfect solution, but this happens seldomly enough, that it's good enough for me.

+BEGIN_SRC emacs-lisp

(advice-add 'atomic-chrome-close-current-buffer :before '(lambda() (clipboard-kill-ring-save (point-min) (point-max))))

+END_SRC

Writing and reading mail is inherently a text-based workflow. Yes, there's HTML mails and attachments, but at the core Email is probably /the place/ where many people write and consume the most text. To utilize the best text-processing program available makes a lot of sense.

When combined with other powerful features of Emacs (such as Org mode for organizing mails into projects and todos), processing mails within Emacs not only makes a lot of sense, but becomes a powerhouse.

** mu4e

Emacs has many options for MTAs. I'm using [[http://www.djcbsoftware.nl/code/mu/mu4e/][MU4E]] which is a little similar to using [[http://www.mutt.org/][mutt]] with [[https://notmuchmail.org/][notmuch]]. As SMTP, I'm using the built-in =smtpmail= Emacs package.

*** Installation

MU works on a local Maildir folder. For synchronization [[http://www.offlineimap.org/][offlineimap]] is used. Install:

For MU4E to work, install MU and MU4E:

For starttls to work when sending mail, install gnutls:

**** Guix packages

+begin_src fundamental :noweb-ref packages

"mu"

+end_src

*** General

**** Authentication Tell Emacs where to find the encrypted =.authinfo= file.

+BEGIN_SRC

(setq auth-sources '((:source "~/.authinfo.gpg")))

+END_SRC

**** PDFs

To open PDFs within Mu4e with Emacs, then there's one thing to configure. Mu4e uses =xdg-open= to chose the app to open any mime type.

Configure =xdg-open= to use Emacs in =.local/share/applications/mimeapps.list=:

+BEGIN_EXAMPLE

xdg-mime default emacs.desktop application/pdf

+END_EXAMPLE

*** Configuration

=mu= setup (Initializing the message store):

https://www.djcbsoftware.nl/code/mu/mu4e/Initializing-the-message-store.html

+BEGIN_SRC shell

mu init --my-address=alain.lafon@dispatched.ch --my-address=alain@200ok.ch --my-address=support@200ok.ch --my-address=lafo@zhaw.ch --my-address=alain@zen-tempel.ch --my-address=preek@dispatched.ch --maildir=~/Maildir

+END_SRC

**** Accounts setup

+BEGIN_SRC emacs-lisp

(require 'mu4e)

;; Not required from 1.8.7-2, it's already included ;; (require 'org-mu4e)

(setq send-mail-function 'smtpmail-send-it)

;; Default account on startup (setq user-full-name "Alain M. Lafon" mu4e-sent-folder "/200ok/INBOX.Sent" mu4e-drafts-folder "/200ok/INBOX.Drafts" mu4e-trash-folder "/200ok/INBOX.Trash")

(setq smtpmail-debug-info t message-kill-buffer-on-exit t ;; Custom script to run offlineimap in parallel for multiple ;; accounts as discussed here: ;; http://www.offlineimap.org/configuration/2016/01/29/why-i-m-not-using-maxconnctions.html ;; This halves the time for checking mails for 4 accounts for me ;; (when nothing has to be synched anyway) mu4e-get-mail-command "offlineimap_parallel.sh" mu4e-attachment-dir "~/Dropbox/org/files/inbox")

;; show full addresses in view message (instead of just names) ;; toggle per name with M-RET (setq mu4e-view-show-addresses t)

;; Do not show related messages by default (toggle with =W= works ;; anyway) (setq mu4e-headers-include-related nil)

;; Alternatives are the following, however in first tests they ;; show inferior results ;; (setq mu4e-html2text-command "textutil -stdin -format html -convert txt -stdout") ;; (setq mu4e-html2text-command "html2text -utf8 -width 72") ;; (setq mu4e-html2text-command "w3m -dump -T text/html")

(defvar my-mu4e-account-alist '(("200ok" (user-full-name "Alain M. Lafon") (message-signature "200ok GmbH\nCEO\n\nalain@200ok.ch\n+41 76 405 05 67\nhttps://200ok.ch/\n\nBook an appointment with me: https://200ok.ch/calendar/alain.html \n\norganice is the best way to get stuff done: https://organice.200ok.ch") (message-signature-auto-include t) (mu4e-sent-folder "/200ok/INBOX.Sent") (mu4e-drafts-folder "/200ok/INBOX.Drafts") (mu4e-trash-folder "/200ok/INBOX.Trash") (user-mail-address "alain@200ok.ch") (smtpmail-default-smtp-server "mail.your-server.de") (smtpmail-local-domain "200ok.ch") (smtpmail-smtp-user "munen@200ok.ch") (smtpmail-smtp-server "mail.your-server.de") (smtpmail-stream-type starttls) (smtpmail-smtp-service 587)) ("200ok-support" (user-full-name "200ok Support") (message-signature "200ok GmbH\nCEO\n\nalain@200ok.ch\n+41 76 405 05 67\nhttps://200ok.ch/\n\nBook an appointment with me: https://200ok.ch/calendar/alain.html \n\norganice is the best way to get stuff done: https://organice.200ok.ch") (message-signature-auto-include t) (mu4e-sent-folder "/200ok-support/INBOX.Sent") (mu4e-drafts-folder "/200ok-support/INBOX.Drafts") (mu4e-trash-folder "/200ok-support/INBOX.Trash") (user-mail-address "support@200ok.ch") (smtpmail-default-smtp-server "mail.your-server.de") (smtpmail-local-domain "200ok.ch") (smtpmail-smtp-user "support@200ok.ch") (smtpmail-smtp-server "mail.your-server.de") (smtpmail-stream-type starttls) (smtpmail-smtp-service 587)) ("zen-tempel" (user-full-name "Munen Alain M. Lafon") (message-signature "Lambda Zen Tempel\n\nalain@zen-temple.net\n+41 76 405 05 67\n\nhttps://zen-temple.net/") (message-signature-auto-include t) (mu4e-sent-folder "/zen-tempel/INBOX.Sent") (mu4e-drafts-folder "/zen-tempel/INBOX.Drafts") (mu4e-trash-folder "/zen-tempel/INBOX.Trash") (user-mail-address "alain@zen-tempel.ch") (smtpmail-default-smtp-server "mail.your-server.de") (smtpmail-local-domain "zen-tempel.ch") (smtpmail-smtp-user "alain@zen-tempel.ch") (smtpmail-smtp-server "mail.your-server.de") (smtpmail-stream-type starttls) (smtpmail-smtp-service 587)) ("dispatched" (user-full-name "Alain M. Lafon") (message-signature-auto-include nil) (mu4e-sent-folder "/dispatched/INBOX.Sent") (mu4e-drafts-folder "/dispatched/INBOX.Drafts") (mu4e-trash-folder "/dispatched/INBOX.Trash") (user-mail-address "alain.lafon@dispatched.ch") (smtpmail-default-smtp-server "mail.your-server.de") (smtpmail-local-domain "dispatched.ch") (smtpmail-smtp-user "munen@dispatched.ch") (smtpmail-smtp-server "mail.your-server.de") (smtpmail-stream-type starttls) (smtpmail-smtp-service 587))))

;; Whenever a new mail is to be composed, change all relevant ;; configuration variables to the respective account. This method is ;; taken from the MU4E documentation: ;; http://www.djcbsoftware.nl/code/mu/mu4e/Multiple-accounts.html#Multiple-accounts (defun my-mu4e-set-account () "Set the account for composing a message." (let ((account (if mu4e-compose-parent-message (let ((maildir (mu4e-message-field mu4e-compose-parent-message :maildir))) (string-match "/\(.?\)/" maildir) (match-string 1 maildir)) (completing-read (format "Compose with account: (%s) " (mapconcat #'(lambda (var) (car var)) my-mu4e-account-alist "/")) (mapcar #'(lambda (var) (car var)) my-mu4e-account-alist) nil t nil nil (caar my-mu4e-account-alist)))) (account-vars (cdr (assoc account my-mu4e-account-alist)))) (if account-vars (mapc #'(lambda (var) (set (car var) (cadr var))) account-vars) (error "No email account found"))))

(add-hook 'mu4e-compose-pre-hook 'my-mu4e-set-account)

(add-hook 'mu4e-compose-mode-hook 'visual-line-mode)

(setq mu4e-refile-folder (lambda (msg) (cond ((string-match "^/dispatched." (mu4e-message-field msg :maildir)) "/dispatched/INBOX.Archive") ((string-match "^/zen-tempel." (mu4e-message-field msg :maildir)) "/zen-tempel/INBOX.Archive") ((string-match "^/200ok." (mu4e-message-field msg :maildir)) "/200ok/INBOX.Archive") ((string-match "^/200ok-support." (mu4e-message-field msg :maildir)) "/200ok-support/INBOX.Archive") ((string-match "^/zhaw.*" (mu4e-message-field msg :maildir)) "/zhaw/Archive") ;; everything else goes to /archive (t "/archive"))))

;; Empty the initial bookmark list (setq mu4e-bookmarks '())

;; All archived folders (defvar d-archive "NOT (maildir:/dispatched/INBOX.Archive OR maildir:/zen-tempel/INBOX.Archive OR maildir:/200ok/INBOX.Archive OR maildir:/200ok-support/INBOX.Archive OR maildir:/zhaw/Archive)")

(defvar inbox-folders (string-join '("maildir:/dispatched/INBOX" "maildir:/zen-tempel/INBOX" "maildir:/200ok/INBOX" "maildir:/200ok-support/INBOX") " OR "))

(defvar draft-folders (string-join '("maildir:/dispatched/INBOX.Drafts" "maildir:/zen-tempel/INBOX.Drafts" "maildir:/200ok/INBOX.Drafts" "maildir:/200ok-support/INBOX.Drafts") " OR "))

(defvar spam-folders (string-join '("maildir:/dispatched/INBOX.spambucket" "maildir:/zen-tempel/INBOX.spambucket" "maildir:/200ok/INBOX.spambucket" "maildir:/200ok-support/INBOX.spambucket") " OR "))

(defvar blacklist-folders (string-join '("maildir:/dispatched/INBOX.blacklist" "maildir:/zen-tempel/INBOX.blacklist" "maildir:/200ok/INBOX.blacklist" "maildir:/200ok-support/INBOX.blacklist") " OR "))

;; Re-define all standard bookmarks to not include the spam and ;; blacklist folders for searches (defvar d-spam (format "NOT (%s OR %s)" spam-folders blacklist-folders))

;; Today (let ((today-query (concat d-spam " AND date:today..now"))) (add-to-list 'mu4e-bookmarks `(:name "Today's messages" :query ,today-query :key ?t)))

;; Last 7 days (let ((seven-days-query (concat d-spam " AND date:7d..now"))) (add-to-list 'mu4e-bookmarks `(:name "Last 7 days" :query ,seven-days-query :key ?w)))

;; Flagged (let ((flagged-query (concat d-spam " AND flag:flagged"))) (add-to-list 'mu4e-bookmarks `(:name "Flagged" :query ,flagged-query :key ?f)))

;; Messages with images (let ((images-query (concat d-spam " AND mime:image/*"))) (add-to-list 'mu4e-bookmarks `(:name "Messages with images" :query ,images-query :key ?p)))

;; Spam (add-to-list 'mu4e-bookmarks `(:name "Spam" :query ,spam-folders :key ?S))

;; Blacklisted (add-to-list 'mu4e-bookmarks `(:name "Blacklisted" :query ,blacklist-folders :key ?B))

;; Drafts (add-to-list 'mu4e-bookmarks `(:name "Drafts" :query ,draft-folders :key ?d))

;; Inbox (add-to-list 'mu4e-bookmarks `(:name "Inbox" :query ,inbox-folders :key ?i))

;; Unread messages (let ((unread-query (concat d-spam d-archive " AND (flag:unread OR flag:flagged) AND NOT flag:trashed"))) (add-to-list 'mu4e-bookmarks `(:name "Unread messages" :query ,unread-query :key ?u)))

;; Unread messages, current year (let ((current-year-query (concat d-spam d-archive (concat " (flag:unread OR flag:flagged) AND NOT flag:trashed AND date:" (format-time-string "%Y"))))) (add-to-list 'mu4e-bookmarks `(:name "Unread messages, current year" :query ,current-year-query :key ?U)))

;; Monitoring (add-to-list 'mu4e-bookmarks '(:name "Monitoring" :query "(cron OR monit OR logcheck or UptimeRobot or noreply@linode.com) AND flag:unread" :key ?M))

+END_SRC

**** Use Emacs completion instead of mu completion

+begin_src emacs-lisp

(setq mu4e-read-option-use-builtin nil mu4e-completing-read-function 'completing-read)

+end_src

**** Check for supposed attachments prior to sending them

+begin_src emacs-lisp

(defun ok/message-attachment-present-p () "Return t if a non-gpg attachment is found in the current message." (save-excursion (save-restriction (widen) (goto-char (point-min)) (when (search-forward "<#part type" nil t) t))))

(setq ok/message-attachment-regexp (regexp-opt '("[Ww]e send" "[Ii] send" "attach" "[aA]ngehängt" "[aA]nhang" "[sS]chicke" "angehaengt" "haenge" "hänge")))

(defun ok/message-warn-if-no-attachments () "Check if there is an attachment in the message if I claim it." (when (and (save-excursion (save-restriction (widen) (goto-char (point-min)) (re-search-forward ok/message-attachment-regexp nil t))) (not (ok/message-attachment-present-p))) (unless (y-or-n-p "No attachment. Send the message?") (keyboard-quit))))

(add-hook 'message-send-hook #'ok/message-warn-if-no-attachments)

+end_src

**** Mail autocompletion

For mail completion, only consider emails that have been seen in the last 6 months. This gets rid of legacy mail addresses of people.

+BEGIN_SRC emacs-lisp

(setq mu4e-compose-complete-only-after (format-time-string "%Y-%m-%d" (time-subtract (current-time) (days-to-time 150))))

+END_SRC

**** Enable temp file for faster communication between mu and mu4e

+begin_src emacs-lisp

;; Enable to show debug render times ;; (setq mu4e-headers-report-render-time t) (setq mu4e-mu-allow-temp t)

+end_src

Mini Benchmark (Searching for 'phil'): With temp file: [mu4e] Found 500 matching messages; 0 hidden; search: 142.3 ms (0.28 ms/msg); render: 122.1 ms (0.24 ms/msg) Without temp file: [mu4e] Found 500 matching messages; 0 hidden; search: 181.3 ms (0.36 ms/msg); render: 134.6 ms (0.27 ms/msg)

**** HTML Mails

+BEGIN_SRC emacs-lisp

(require 'mu4e-contrib) (setq mu4e-html2text-command 'mu4e-shr2text) ;;(setq mu4e-html2text-command "iconv -c -t utf-8 | pandoc -f html -t plain") (add-to-list 'mu4e-view-actions '("ViewInBrowser" . mu4e-action-view-in-browser) t)

+END_SRC

Disable colors for HTML mails. HTML mails, especially transactional ones, can be very convoluted. Converting them to text and taking away colors can make them more readable.

+begin_src emacs-lisp

(setq shr-use-colors t)

+end_src

Disable "HTML over plain text" heuristic. This variable officially has this rationale: "Ratio between the length of the html and the plain text part below which mu4e will consider the plain text part to be 'This messages requires html' text bodies. You can neutralize it (always show the text version) by using `most-positive-fixnum'."

This heuristic overwrites the default setting (and configuration) that Plain text should be preferred over HTML!

In my experience, HTML Emails are WAY longer than only 5x the Plain text (Doodle, Airbnb, Meetup, etc), so this will yield me a lot of false positives whereas I have never seen a "This message requires HTML" body.

I wrote an accompanying blog post with further information: https://200ok.ch/posts/2018-10-25_disable_mu4e_html_over_plain_text_heuristic.html

+BEGIN_SRC emacs-lisp

(setq mu4e-view-html-plaintext-ratio-heuristic most-positive-fixnum)

+END_SRC

**** Spellchecking

+BEGIN_SRC emacs-lisp

(add-hook 'mu4e-compose-mode-hook 'flyspell-mode)

+END_SRC

**** Updating mails

**** GPG configuration

+BEGIN_SRC emacs-lisp

(setq mml-secure-openpgp-encrypt-to-self t) (setq mml-secure-openpgp-sign-with-sender t)

+END_SRC

With upgrading to Emacs 27, this broke =mu4e-compose-crypto-reply-plain-policy= set to ='sign=. It always wanted to sign with s/mime whereas I want to sign with gpg. I think this option is obsolete. I'm leaving it here for a moment until I'm sure it will not be needed anymore.

+BEGIN_SRC emacs-lisp

;; (add-hook 'mu4e-compose-mode-hook 'epa-mail-mode) ;; (add-hook 'mu4e-view-mode-hook 'epa-mail-mode)

+END_SRC

**** Handle very long lines

When looking at emails, show them nicely wrapped. That's very helpful when people send mails with very long lines.

Disabling for the moment, because some emails looks worse after it. =visual-line-mode= can always be invoked by =w= in =mu4e-view-mode=.

+BEGIN_SRC emacs-lisp

;; (add-hook 'mu4e-view-mode-hook 'visual-line-mode)

+END_SRC

**** Always reply everyone, not just the sender

+begin_src emacs-lisp

(eval-after-load 'mu4e '(define-key mu4e-compose-minor-mode-map "R" #'mu4e-compose-wide-reply))

+end_src

**** Do not reply to self

+BEGIN_SRC emacs-lisp

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

+END_SRC

**** Store link to message if in header view, not to header query

+BEGIN_SRC emacs-lisp

(setq org-mu4e-link-query-in-headers-mode nil)

+END_SRC

**** Customize header fields

This only adds =:bcc=.

+BEGIN_SRC emacs-lisp

(setq mu4e-view-fields '(:from :to :cc :bcc :subject :flags :date :maildir :mailing-list :tags :attachments :signature :decryption))

+END_SRC

**** Close mu4e without asking

+BEGIN_SRC emacs-lisp

(setq mu4e-confirm-quit nil)

+END_SRC

**** Reminder to keep to three sentences

Rationale: E-mail takes too long to respond to, resulting in continuous inbox overflow for those who receive a lot of it.

http://three.sentenc.es/

+BEGIN_SRC emacs-lisp

(add-hook 'mu4e-compose-mode-hook (defun ok-mu4e-keep-to-three-sentences () (shell-command "notify-send -u critical 'Keep to three sentences.'") (message "Keep to three sentences.")))

+END_SRC

**** Date format

Set a sane ISO 8601 date format.

+begin_src emacs-lisp

(setq mu4e-headers-date-format "%+4Y-%m-%d")

+end_src

**** ~format=flowed~

Setting ~format=flowed~ for non-text-based mail clients which don't respect actual formatting, but let the text "flow" as they please. Relevant RFC: https://tools.ietf.org/html/rfc3676

+BEGIN_QUOTE

What is required is a format which is in all significant ways Text/Plain, and therefore is quite suitable for display as Text/Plain, and yet allows the sender to express to the receiver which lines are quoted and which lines are considered a logical paragraph, and thus eligible to be flowed (wrapped and joined) as appropriate.

+END_QUOTE

**** Configure manually installed mu paths

+BEGIN_SRC emacs-lisp

(setq mu4e-msg2pdf "/usr/local/bin/msg2pdf")

+END_SRC

**** I'm not Borg

mu4e has a feature [[https://www.djcbsoftware.nl/code/mu/mu4e/Other-search-functionality.html][skipping duplicates]] designed for Borg.

While I'm not Borg, being thrown in with the lot brings trouble. I like scanning my Spam folder for false positives. When 'duplicates' are not shown, it'll take me many runs to delete those tasty Bitcoin offers.

An even better solution to the problem would actually be to automatically delete duplicates - maybe with procmail.

+begin_src emacs-lisp

;; Disabled for the moment. ;; (setq mu4e-headers-skip-duplicates t)

+end_src

**** Calendar invitations

https://www.djcbsoftware.nl/code/mu/mu4e/iCalendar.html

Ability to accept or reject mail calendar invitations as well as the ability to add accepted invitations to Org mode (and therefore to the agenda).

+begin_src emacs-lisp

(require 'mu4e-icalendar) (mu4e-icalendar-setup) (setq gnus-icalendar-org-capture-file "~/Dropbox/org/things.org") (setq gnus-icalendar-org-capture-headline '("Calendar")) (gnus-icalendar-org-setup)

+end_src

**** org-mime https://github.com/org-mime/org-mime

org-mime can be used to send HTML email using Org-mode HTML export.

+begin_src emacs-lisp

(require 'org-mime)

+end_src

Usage:

** TODO Use Quoted printable text for outgoing messages to enable automatic line breaks **** If this is successfull, send upstream PR to MU4E https://mathiasbynens.be/notes/gmail-plain-text https://mothereff.in/quoted-printable https://www.gnu.org/software/emacs/manual/html_node/emacs-mime/qp.html Mail filtering Add a header action "Block" which add the Senders Name and From Address to a procmail blacklist.

+BEGIN_SRC emacs-lisp

(defun append-line-to-file (line path) "Append a line to a file behind path" (write-region (concat line "\n") nil path 'append))

(defun mu4e-strategy-from (strategy msg) "If STRATEGY is 'blacklist', then add the from of a message to the procmail. If it is 'whitelist', then whitelist'." (let* ((from (mu4e-message-field msg :from)) (from_name (car (cdr (car from)))) (from_address (car (cdr (cdr (cdr (car from)))))) (path (format "~/.procmail/%s_from.txt" strategy))) ;; Whitelist/Blacklist the senders Name (if from_name (append-line-to-file from_name path)) ;; Whitelist/Blacklist the Email-Address (append-line-to-file from_address path) (shell-command (format "sort -u -o %s %s" path path)) (message "%s: %s" strategy from)))

(defun mu4e-blacklist-subject (msg) "Add the subject of a message to the procmail blacklist" (let* ((subject (mu4e-message-field msg :subject)) (path "~/.procmail/blacklist_subject.txt")) (if subject (append-line-to-file subject path)) (shell-command (format "sort -u -o %s %s" path path))

   (message "Blacklist: %s" subject)))

(add-to-list 'mu4e-headers-actions '("f White 'From:'" . (lambda (msg) (mu4e-strategy-from "whitelist" msg))) t)

(add-to-list 'mu4e-headers-actions '("F Block 'From:'" . (lambda (msg) (mu4e-strategy-from "blacklist" msg))) t)

(add-to-list 'mu4e-headers-actions '("S Block 'Subject:'" . mu4e-blacklist-subject) t)

+END_SRC

*** Rewrite contact information

+BEGIN_SRC emacs-lisp

(defun munen-contact-processor (contact) (cond ((string-match "phil@200ok.ch" contact) ; Phil sometimes dosn't add his name to the reply-to. "Phil Hofmann phil@200ok.ch") ((string-match "d.kuehner@n-pg.de" contact) ; Sanitize NPG encoding "Dominik Kühner d.kuehner@n-pg.de") ((string-match "wiffbubbles@icloud.com" contact) ; Monika writes her name in all caps and I don't want to forward it like that. "Monika Bieri wiffbubbles@icloud.com") ((string-match "bieri.monika@icloud.com" contact) ; Monika writes her name in all caps and I don't want to forward it like that. "Monika Bieri bieri.monika@icloud.com") (t contact)))

(setq mu4e-contact-process-function 'munen-contact-processor)

+END_SRC

** =ido=

=ido= means "Interactively Do Things". =ido= has a completion engine that's sensible to use everywhere. It is built-in and nice and could change a lot of defaults like =find-file= and switching buffers.

It works well while not breaking Emacs defaults.

+BEGIN_SRC emacs-lisp

(ido-mode t) (ido-everywhere t) (setq ido-enable-flex-matching t)

+END_SRC

** Ivy/Counsel/Swiper

https://github.com/abo-abo/swiper

Ivy, a generic completion mechanism for Emacs.

Counsel, a collection of Ivy-enhanced versions of common Emacs commands.

Swiper, an Ivy-enhanced alternative to isearch.

=Ivy= is an interactive interface for completion in Emacs. Therefore it overlaps in functionality with =ido=. While =Ivy= is more powerful, it breaks certain standard functionality. So =ido= is enabled globally by default and for certain tasks, =Ivy= overrides =ido=.

Emacs uses completion mechanism in a variety of contexts: code, menus, commands, variables, functions, etc. Completion entails listing, sorting, filtering, previewing, and applying actions on selected items. When active, =ivy-mode= completes the selection process by narrowing available choices while previewing in the minibuffer. Selecting the final candidate is either through simple keyboard character inputs or through powerful regular expressions.

*** Configuration

+BEGIN_SRC emacs-lisp

(ivy-mode) (setq enable-recursive-minibuffers t) (global-set-key (kbd "") 'ivy-resume) (global-set-key (kbd "C-c SPC") 'complete-symbol)

+END_SRC

Show total amount of matches and the index of the current match

+BEGIN_SRC emacs-lisp

(setq ivy-count-format "(%d/%d) ")

+END_SRC

Wrap to the first result when on the last result and vice versa.

+BEGIN_SRC emacs-lisp

(setq ivy-wrap t)

+END_SRC

Enable =Swiper=

+BEGIN_SRC emacs-lisp

(global-set-key "\C-s" 'swiper)

+END_SRC

Configure =Counsel=

+BEGIN_SRC emacs-lisp

(global-set-key (kbd "C-x b") 'counsel-ibuffer) (global-set-key (kbd "C-h d") 'counsel-describe-function) (global-set-key (kbd "C-h v") 'counsel-describe-variable) ;; Run counsel-ag against the current directory and not against the ;; whole project (global-set-key (kbd "C-c k") '(lambda() (interactive) (counsel-ag "" default-directory nil nil))) (global-set-key (kbd "C-x l") 'counsel-locate) (define-key minibuffer-local-map (kbd "C-r") 'counsel-minibuffer-history)

+END_SRC

Override =C-c C-j= (org-goto) with =counsel-org-goto= which brings super fast fuzzy matching and navigation capabilities for headlines.

+BEGIN_SRC emacs-lisp

(define-key org-mode-map (kbd "C-c C-j") 'counsel-org-goto)

+END_SRC

Override =C-x i= (insert-file) in favor of =counsel-imenu= which brings fuzzy matching to the ability to jump to any indexed position to the already great =[[https://www.gnu.org/software/emacs/manual/html_node/emacs/Imenu.html][imenu]]=.

+BEGIN_SRC emacs-lisp

(global-set-key (kbd "C-x i") 'counsel-imenu)

+END_SRC

Override =find-file= and =dired= in favor of the counsel counter parts.

+BEGIN_SRC emacs-lisp

(global-set-key (kbd "C-x C-f") 'counsel-find-file) (global-set-key (kbd "C-x d") 'counsel-dired)

+END_SRC

Next to counsel, there's also =smex= which is =M-x= combined with =ido=. =smex= has a better sorting algorithm than =Counsel= and having both installed means that we get the =Counsel= interface with =smex= sorting. Best of both worlds.

By default, =counsel-M-x= starts with a =^=. More often than not, this will be in the way of me fuzzy matching a function. Therefore I'll start it with an empty string as argument.

+BEGIN_SRC emacs-lisp

(global-set-key (kbd "M-x") (lambda () (interactive) (counsel-M-x "")))

+END_SRC

Override =insert-char=.

+BEGIN_SRC emacs-lisp

(global-set-key (kbd "C-x 8 RET") 'counsel-unicode-char)

+END_SRC

Make the prompt line selectable (with =C-p=).

+begin_src emacs-lisp

(setq ivy-use-selectable-prompt t)

+end_src

*** Where =Ivy= doesn't work well

**** Overwriting standard Emacs functionality

Some basic features are overwritten when "everything" becomes an =Ivy= search buffer. For example:

**** Disable Swiper where it is broken

Ivy/Swiper cannot search in PDFs. It tries to search in the PDF source code. Therefore I fall back to using isearch within PDFs.

+BEGIN_SRC emacs-lisp

(add-hook 'pdf-view-mode-hook '(lambda() (define-key pdf-view-mode-map "\C-s" 'isearch-forward)))

+END_SRC

When two =dired= buffers are open and files should be copied from one to the other, one can use the =up= and =down= keys to toggle the destination. When this is a search buffer, it will auto complete for all local folders, instead. This is something I do often.

+begin_src emacs-lisp

(add-hook 'dired-mode-hook '(lambda () (ivy-mode -1)))

+end_src

*** Improve other packages with ivy

Projectile completion (Default is =ido=)

+BEGIN_SRC emacs-lisp

(setq projectile-completion-system 'ivy)

+END_SRC

Mu4e "folder" and "from" completion (Default is =ido=)

+BEGIN_SRC emacs-lisp

(setq mu4e-completing-read-function 'ivy-completing-read)

+END_SRC

Synosaurus completion (Default is =ido=)

+BEGIN_SRC emacs-lisp

(setq synosaurus-choose-method 'ivy-read)

+END_SRC

** Obsolete alternatives

I used to use =isearch= instead of =Swiper=.

Replace i-search-(forward|backward) with their respective regexp capable counterparts

+BEGIN_SRC emacs-lisp

;;(global-set-key (kbd "C-s") 'isearch-forward-regexp) ;;(global-set-key (kbd "C-r") 'isearch-backward-regexp)

+END_SRC

+BEGIN_EXAMPLE

machine irc.libera.chat login "munen" password SECRET_PASSWORD

+END_EXAMPLE

This file is automatically read when connecting to servers. It's the same for SMTP servers, for example.

For connecting to IRC, I'm using the built-in package =erc=.

Configure automatic join list

+BEGIN_SRC emacs-lisp

(setq erc-autojoin-channels-alist '(("libera.chat" "#organice" "#200ok" "#emacsconf" "#emacsconf-org" "#lobsters") ;; Deprecate Freenode (rationale https://github.com/200ok-ch/org-parser/pull/48) ;; ("freenode.net" "#200ok" "#emacsconf" "#emacsconf-org" "#lobsters") ;; This does not work, yet. The ;; channels cannot be joined on ;; connecting to bitlbee. bitlbee ;; needs to first connect to the ;; configured accounts (i.e. ;; slack). This could happen in a ;; timeout, or better event ;; oriented on a message that ;; bitlbee sends when connected to ;; the account. ;; '(("localhost" "#internal" "#general")) ))

+END_SRC

By default, connect to Libera.Chat

+begin_src emacs-lisp

(defconst erc-default-server "irc.libera.chat")

+end_src

Do not show join/quit info for lurkers

+BEGIN_SRC emacs-lisp

(setq erc-lurker-hide-list '("JOIN" "PART" "QUIT"))

+END_SRC

Automatically unfold images when links are shared

+BEGIN_SRC emacs-lisp

(require 'erc-image) (add-to-list 'erc-modules 'image) (erc-update-modules)

+END_SRC

Logging

+BEGIN_SRC emacs-lisp

(setq erc-log-channels-directory "~/.erc/logs/") (add-hook 'erc-insert-post-hook 'erc-save-buffer-in-logs)

+END_SRC

Notify when someone is addressing me

+BEGIN_SRC emacs-lisp

(setq erc-pals '("phi|" "branch14")) ;; The quotes around %s are super important to prevent shell injection (add-hook 'erc-text-matched-hook '(lambda(match-type nickuserhost msg) (shell-command-to-string (format "notify-send erc '%s'" msg))))

+END_SRC

This part of the configuration was kindly provided by [[https://github.com/SirPscl/emacs.d#spaceline][SirPscl]].

*** Guix packages

+begin_src fundamental :noweb-ref packages

"emacs-spaceline"

+end_src

+BEGIN_SRC emacs-lisp

(require 'spaceline)

+END_SRC

* Segments ** Flycheck

Slightly simplified flycheck segments for =info=, =warning= and =error=.

+BEGIN_SRC emacs-lisp

;; This is throwing errors when debugging elisp since [2022-12-06 Tue].

; (spaceline-define-segment ph/flycheck-warning-segment ; (if (flycheck-has-current-errors-p) ; (let ((c (cdr (assq 'warning (flycheck-count-errors ; flycheck-current-errors))))) ; (powerline-raw ; (if c (format "%s" c))))))

(spaceline-define-segment ph/flycheck-error-segment (if (flycheck-has-current-errors-p) (let ((c (cdr (assq 'error (flycheck-count-errors flycheck-current-errors))))) (powerline-raw (if c (format "%s" c))))))

(spaceline-define-segment ph/flycheck-info-segment (if (flycheck-has-current-errors-p) (let ((c (cdr (assq 'info (flycheck-count-errors flycheck-current-errors))))) (powerline-raw (if c (format "%s" c))))))

+END_SRC

Default faces for the flycheck segments.

+BEGIN_SRC emacs-lisp

(defface ph/spaceline-flycheck-error-face '((t :inherit 'mode-line :weight bold :foreground "white" :background "dark red")) "Flycheck Error Face" :group 'spaceline)

(defface ph/spaceline-flycheck-warning-face '((t :inherit 'mode-line :weight bold :foreground "white" :background "DarkOrange3")) "Flycheck Warning Face" :group 'spaceline)

(defface ph/spaceline-flycheck-info-face '((t :inherit 'mode-line :weight bold :foreground "white" :background "dark green")) "Flycheck Info Face" :group 'spaceline)

+END_SRC

**** Evil State

Setting the face according to =evil-state=.

+BEGIN_SRC emacs-lisp

(defun ph/spaceline-highlight-face-evil-state () "Set the highlight face depending on the evil state." (if (bound-and-true-p evil-local-mode) (let* ((face (assq evil-state spaceline-evil-state-faces))) (if face (cdr face) (spaceline-highlight-face-default))) (spaceline-highlight-face-default)))

(setq-default spaceline-highlight-face-func 'ph/spaceline-highlight-face-evil-state)

+END_SRC

Set the evil-state segment colors for =operator-state=.

+BEGIN_SRC emacs-lisp

(defface ph/spaceline-evil-operator-face '((t (:background "cornflower blue" :inherit 'spaceline-evil-normal))) "Spaceline Evil Operator State" :group 'spaceline)

(add-to-list 'spaceline-evil-state-faces '(operator . ph/spaceline-evil-operator-face))

+END_SRC

**** Git Branch

+BEGIN_SRC emacs-lisp

(defun ph/git-branch-name () (replace-regexp-in-string "^ Git[:-]" "" vc-mode))

(spaceline-define-segment ph/version-control "Version control information." (when vc-mode (s-trim (concat (ph/git-branch-name)))))

+END_SRC

**** Tramp

Tramp offers the following file name syntax to refer to files on other machines.

+BEGIN_SRC text

/method:host:filename /method:user@host:filename /method:user@host#port:filename

+END_SRC

The following segemnts display the current buffer's =method= and =user@host=.

+BEGIN_SRC emacs-lisp

(spaceline-define-segment ph/remote-method (when (and default-directory (file-remote-p default-directory 'method)) (file-remote-p default-directory 'method)))

(spaceline-define-segment ph/remote-user-and-host (when (and default-directory (or (file-remote-p default-directory 'user) (file-remote-p default-directory 'host))) (concat (file-remote-p default-directory 'user) "@" (file-remote-p default-directory 'host))))

+END_SRC

Default faces for the tramp segments.

+BEGIN_SRC emacs-lisp

(defface ph/spaceline-tramp-user-host-face '((t :inherit 'mode-line :foreground "black" :background "#fce94f")) "Tramp User@Host Face" :group 'spaceline)

(defface ph/spaceline-tramp-method-face '((t :inherit 'mode-line :foreground "black" :background "#ff5d17")) "Tramp Method Face" :group 'spaceline)

+END_SRC

**** Mu4e Context

I'm not using Mu4e contexts, yet, because my configuration started before they were introduced. I'm leaving the segment configuration for the future.

+BEGIN_SRC emacs-lisp

;; (spaceline-define-segment ph/mu4e-context-segment ;; (let ((context (mu4e-context-current))) ;; (when (and context ;; (string-prefix-p "mu4e" (symbol-name major-mode))) ;; (mu4e-context-name context))))

+END_SRC

Face for =mu4e= segemnt.

+BEGIN_SRC emacs-lisp

;; (defface ph/spaceline-mu4e-context-face ;; '((t :inherit 'mode-line ;; :weight bold)) ;; "mu4e face" ;; :group 'spaceline)

+END_SRC

**** Org Timer

I like to set timers, for example through [[file:org-pomodoro.el][org-pomodoro.el]]

+BEGIN_SRC emacs-lisp

(spaceline-define-segment org-timer-left-time "Show the time left in the current org-timer (i.e. a pomodoro)." (ok-pomodoro-remaining-time))

+END_SRC

*** Setup

Setting up the mode-line and order of segements. Compile the modeline with =M-x spaceline-compile=.

+BEGIN_SRC emacs-lisp

(require 'spaceline-config) (spaceline-spacemacs-theme)

;; Otherwise spaceline will be huge in Emacs >= 27.1 ;; (setq powerline-height 1) ;; Since Emacs >= 28.2, it needs to be bigger to stay the same size. (setq powerline-height 22) ;; Since Emacs >= 27.1, there's no need for font trickery. Just use ;; UTF-8. ;; (setq powerline-default-separator 'utf-8)

(spaceline-install 'main '((evil-state :face highlight-face) (buffer-id) ;; (org-timer-left-time) ;; Currently, I show the remaining time in Polybar, not Emacs. ;; (ph/mu4e-context-segment :face 'ph/spaceline-mu4e-context-face) (ph/remote-method :face 'ph/spaceline-tramp-method-face) (ph/remote-user-and-host :face 'ph/spaceline-tramp-user-host-face) (buffer-modified)) '(;;(minor-modes :when active) (projectile-root) (ph/version-control) ;(line-column :when active) ;(buffer-position :when active) (ph/flycheck-info-segment :face 'ph/spaceline-flycheck-info-face :when active) (ph/flycheck-warning-segment :face 'ph/spaceline-flycheck-warning-face :when active) (ph/flycheck-error-segment :face 'ph/spaceline-flycheck-error-face :when active) (line-column) (major-mode)))

+END_SRC

Set mode-line always active (don't hide segments when focus is on a different window).

+BEGIN_SRC emacs-lisp

(defun powerline-selected-window-active () t)

+END_SRC

*** Diminish

Diminish implements hiding or abbreviation of the mode line displays (lighters) of minor-modes.

+BEGIN_SRC emacs-lisp

(eval-after-load "auto-revert" '(diminish 'auto-revert-mode)) (eval-after-load "beacon" '(diminish 'beacon-mode)) (eval-after-load "ivy" '(diminish 'ivy-mode)) (eval-after-load "projectile" '(diminish 'projectile-mode)) (eval-after-load "projectile-rails" '(diminish 'projectile-rails-mode)) (eval-after-load "rainbow-mode" '(diminish 'rainbow-mode)) (eval-after-load "undo-tree" '(diminish 'undo-tree-mode)) (eval-after-load "which-key" '(diminish 'which-key-mode))

+END_SRC

*** Guix Packages

+begin_src fundamental :noweb-ref packages

"emacs-spaceline"

+end_src

** =hide-mode-line=

https://github.com/hlissner/emacs-hide-mode-line

A minor mode that hides (or masks) the mode-line in your current buffer. It can be used to toggle an alternative mode-line, toggle its visibility, or simply disable the mode-line in buffers where it isn't very useful otherwise.

+BEGIN_SRC emacs-lisp

(require 'hide-mode-line)

(add-hook 'pdf-view-mode-hook #'hide-mode-line-mode)

+END_SRC

** =writegood-mode= https://github.com/bnbeckwith/writegood-mode

This is a minor mode to aid in finding common writing problems.

It highlights text based on a set of weasel-words, passive-voice and duplicate words.

** Theraurus https://github.com/hpdeifel/synosaurus/

Synosaurus is a thesaurus front-end with pluggable back-end.

Use the [[http://openthesaurus.de/][openthesaurus.de]] back-end.

+BEGIN_SRC emacs-lisp

(setq synosaurus-backend 'synosaurus-backend-openthesaurus)

(defalias 'thesaurus-openthesaurus-de 'synosaurus-lookup)

+END_SRC

** Flyspell

Emacs has built-in functionality for [[https://www.gnu.org/software/emacs/manual/html_node/emacs/Spelling.html][checking and correcting spelling]] called =ispell.el=. On top of that, there's a built-in minor mode for for on-the-fly spell checking. called =flyspell-mode=.

Flyspell can use multiple back-ends (for example ispell, aspell or hunspell).

*** Configuration

**** Order corrections by likeliness

Do not order not by the default of alphabetical ordering.

+BEGIN_SRC emacs-lisp

(setq flyspell-sort-corrections nil)

+END_SRC

**** Do not print messages for every word

When checking the entire buffer, don't print messages for every word. This is a major performance gain.

+BEGIN_SRC emacs-lisp

(setq flyspell-issue-message-flag nil)

+END_SRC

**** Use =hunspell= with multiple dictionaries

Here in Switzerland, there are four official languages: Swiss German, French, Italian and Romansh. Also, we converse a lot in German and English. Hence, it's a regular occurrence to have one file with multiple languages in them. Especially for these situations it's still to have proper spell checking. Fortunately, Emacs has us covered!

[[https://github.com/hunspell/hunspell][Hunspell]] is a free spell checker and used by LibreOffice, Firefox and Chromium. It allows to set multiple dictionaries - even different dictionaries per language (=aspell=, for example also allows multiple dictionaries, but only for the same language). It also can be used as an =ispell.el= backend.

To use =hunspell=, install it first:

+BEGIN_EXAMPLE

apt install hunspell hunspell-de-de hunspell-en-gb hunspell-en-us hunspell-de-ch-frami

+END_EXAMPLE

+BEGIN_SRC emacs-lisp

(with-eval-after-load "ispell"
  ;; Configure `LANG`, otherwise ispell.el cannot find a 'default
  ;; dictionary' even though multiple dictionaries will be configured
  ;; in next line.
  (setenv "LANG" "en_US")
  (setq ispell-program-name "hunspell")
  ;; Configure German, Swiss German, and two variants of English.
  (setq ispell-dictionary "de_DE,de_CH,en_GB,en_US")
  ;; ispell-set-spellchecker-params has to be called
  ;; before ispell-hunspell-add-multi-dic will work
  (ispell-set-spellchecker-params)
  (ispell-hunspell-add-multi-dic "de_DE,de_CH,en_GB,en_US"))

;; For saving words to the personal dictionary, don't infer it from
;; the locale, otherwise it would save to ~/.hunspell_de_DE.
(setq ispell-personal-dictionary "~/.hunspell_personal")
;; The personal dictionary file has to exist, otherwise hunspell will
;; silently not use it.
(unless (file-exists-p ispell-personal-dictionary)
  (write-region "" nil ispell-personal-dictionary))

+END_SRC

**** Do not loose all spellchecking information after adding one word to a personal dictionary

Advice to re-check the buffer after a word has been added to the dictionary. This has the benefit of the word actually being cleared, but the downside that the whole buffer has to be re-checked which an take some time.

+BEGIN_SRC emacs-lisp

;; (defun flyspell-buffer-after-pdict-save (&rest _) ;; (flyspell-buffer))

;; (advice-add 'ispell-pdict-save :after #'flyspell-buffer-after-pdict-save)

+END_SRC

The proper solution (for which I don't have time now) is to just mark all further occurrences of the word you just saved as correct (without having to recheck the whole buffer).

**** Deprecated =aspell= setup

Alternatively to using =hunspell=, here's an option to switch the dictionary between German and English.

The German dictionary is from [[http://fmg-www.cs.ucla.edu/geoff/ispell-dictionaries.html#German-dicts][here]].

+BEGIN_SRC emacs-lisp

;; (defun flyspell-switch-dictionary() ;; "Switch between German and English dictionaries" ;; (interactive) ;; (let* ((dic ispell-current-dictionary) ;; (change (if (string= dic "deutsch") "english" "deutsch"))) ;; (ispell-change-dictionary change) ;; (message "Dictionary switched from %s to %s" dic change)))

+END_SRC

*** TODO Implement =ispell-pdict-save= with above requirement

My preferred font is "Fira Code Retina". In Debian, the package is called =fonts-firacode=, in Guix it's =font-fira-code=.

+BEGIN_SRC emacs-lisp

(when (eq system-type 'gnu/linux)

    (add-to-list 'default-frame-alist
                 '(font . "Fira Code Retina 9"))

    ;; In the past, this was the name for the same font:

    ;; (add-to-list 'default-frame-alist
    ;;              '(font . "Fira CodeSource Code Pro Retina 9"))
    ;; (set-face-attribute 'default t :font "Fira Code Retina 9")

    ;; This is a great fallback font. It looks very similar and
    ;; comes preinstalled on many systems.

    ;; (add-to-list 'default-frame-alist
    ;;              '(font . "DejaVu Sans Mono 9"))

    ;; Manually setting the font
    ;; (set-frame-font "Fira Code Retina 9")

;; Default Browser
(setq browse-url-browser-function 'browse-url-generic
      browse-url-generic-program "firefox"
      browse-url-new-window-flag t)
(menu-bar-mode -1)
;; enable pdf-tools
(pdf-tools-install))

+END_SRC

*** Guix packages

+begin_src fundamental :noweb-ref packages

"font-fira-code" "fontconfig"

+end_src

* Display Emoji ** Debian Requires the =fonts-symbola= Debian package

+BEGIN_SRC emacs-lisp

;; (set-fontset-font t nil "Symbola" nil 'prepend)

+END_SRC

**** Guix

+BEGIN_SRC emacs-lisp

(set-fontset-font t nil "Noto Color Emoji" nil 'prepend) ;; (set-fontset-font t '(#x1f000 . #x1faff) ;; (font-spec :family "Noto Color Emoji"))

+END_SRC

+begin_src fundamental :noweb-ref packages

"font-google-noto"

+end_src

** macOS

+BEGIN_SRC emacs-lisp

(when (eq system-type 'darwin) (set-frame-font "Menlo 14") ; Use Spotlight to search with M-x locate (setq locate-command "mdfind"))

+END_SRC

+BEGIN_SRC emacs-lisp

(setq custom-safe-themes (quote ("df3e05e16180d77732ceab47a43f2fcdb099714c1c47e91e8089d2fcf5882ea3" "d09467d742f713443c7699a546c0300db1a75fed347e09e3f178ab2f3aa2c617" "8db4b03b9ae654d4a57804286eb3e332725c84d7cdab38463cb6b97d5762ad26" "85c59044bd46f4a0deedc8315ffe23aa46d2a967a81750360fb8600b53519b8a" default)))

+END_SRC

** Configure dark-mode theme and font size

+BEGIN_SRC emacs-lisp

(defun dark-mode () "Default theme and font size. Pendant: (presentation-mode)." (interactive)

(mapcar 'disable-theme custom-enabled-themes)
(set-face-attribute 'default nil :height 150)
;; Themes
;; (set-frame-parameter nil 'background-mode 'dark)

;; Dark, High Contrast <- favorite
(load-theme 'wombat)
(setq frame-background-mode (quote dark))

;; Dark, Low contrast
;; (load-theme 'darktooth)
;; Dark, Lowest contrast
;; (load-theme 'zenburn)
 )

+END_SRC

** Configure light-mode theme and font size

+BEGIN_SRC emacs-lisp

(defun light-mode () "Enables a light theme." (interactive) ;; (set-face-attribute 'default nil :height 100) (mapcar 'disable-theme custom-enabled-themes) (load-theme 'spacemacs-light t))

+END_SRC

+BEGIN_SRC emacs-lisp

(defun presentation-mode () "Presentation friendly theme and font size." (interactive) (load-theme 'leuven t) (mapcar 'disable-theme custom-enabled-themes) (set-face-attribute 'default nil :height 150))

+END_SRC

*** Guix packages

+begin_src fundamental :noweb-ref packages

"emacs-spacemacs-theme"

+end_src

** Enable default theme and font

+BEGIN_SRC emacs-lisp

(add-hook 'server-after-make-frame-hook 'light-mode) (light-mode)

+END_SRC

Note: If I wanted to make a distinction between GUI and terminal modes, this would be reasonable boilerplate:

+begin_example

(add-hook 'after-make-frame-functions (lambda () ;; we want some font only in GUI Emacs (when (display-graphics-p) (set-frame-font "DejaVu Sans Mono 28")))

+end_example

** Time Export Table

Create a customized time table ready for CSV export.

Usage:

+BEGIN_SRC org

+name: ok-timetable

,#+BEGIN_SRC elisp (ok-export-org-timetable "2018-05-09") ,#+END_SRC

+END_SRC

When evaluating the src-block above, it'll yield a table like:

+BEGIN_SRC org

,#+RESULTS: ok-timetable | date | hours | task | |------------+--------+----------------------------------| | 2018-05-09 | 0:02 | #support | | 2018-05-09 | 0:17 | #support | |------------+--------+----------------------------------|

+END_SRC

+BEGIN_SRC emacs-lisp

(require 'seq)

(defun ok-filter-table-by-date (tbl from-date table-row) "Filter a TBL by FROM-DATE which is found in TABLE-ROW." ;; Sort by date (seq-sort '(lambda (e1 e2) (string-lessp (nth table-row e1) (nth table-row e2))) ;; Filter to start with FROM-DATE (seq-filter (lambda (elem) (let ((date-elem (nth table-row elem))) ;; >= (when (or (string-greaterp date-elem from-date) (string-equal date-elem from-date)) elem))) tbl)))

(defun ok-hm-to-hours (worktime) "Casts HH:MM WORKTIME into a floating point number." (condition-case worktime (let* ((time (split-string worktime ":")) (minutes (/ (string-to-number (second time)) 60.0)) (hours (string-to-number (first time)))) (format "%.3f" (+ hours minutes))) (error 0)))

(defun ok-split-hash-and-description (text) "Given a TEXT like '#tag1 #tag2 some description' and return tags and description as a list." ;; The concat is a little hack, so that there's always a minimum ;; description to be found (let ((text (concat text " "))) ;; A hashtag can have numbers, dashes and a-z (if (string-match "\(#[a-z-0-9]+ \)+" text) (let* ((hashtags (match-string 0 text)) ;; Couldn't figure out how to get the description ;; through an elisp regexp, so I'm just reading the ;; remainder of the text after all hashtags here (description (substring text (length hashtags) (length text)))) (list (string-trim hashtags) (string-trim description))))))

(defun ok-find-parent-of-type (elem-type elem) "For a child ELEM, find the closes parent element of type ELEM-TYPE." (let ((parent-elem (org-element-property :parent elem))) (if (eq elem-type (car parent-elem)) parent-elem (ok-find-parent-of-type elem-type parent-elem))))

(defun ok-generate-clock-table () "Generate a list of org elements of type 'clock." (let ((ast (org-element-parse-buffer 'element))) ;; Map a function to all elements of TYPE 'clock which extracts ;; the TITLE, DURATION and DATE of a TODO. (org-element-map ast 'clock (lambda (clock-elem) (let ((val (org-element-property :value clock-elem)) (task (ok-find-parent-of-type 'headline clock-elem)) (hash-and-description (ok-split-hash-and-description (org-element-property :title task)))) `(,(let ((year (org-element-property :year-start val)) (month (org-element-property :month-start val)) (day (org-element-property :day-start val))) (format "%4d-%02d-%02d" year month day )) ,(ok-hm-to-hours (org-element-property :duration clock-elem)) ,(first hash-and-description) ,(second hash-and-description)))))))

(defun ok-export-org-timetable (from-date) "Generate a list from 'org-mode' clock elements starting from FROM-DATE." ;; Concatenate header, element data and footer into one list which ;; will automatically be rendered by org-mode as a table. (append '(("date" "duration" "hashtags" "description")) '(hline) ;; Generate tree of all visible elements within buffer (narrowing ;; works). (ok-filter-table-by-date (ok-generate-clock-table) from-date 0)))

(defun ok-export-table-to (table-name target-path) "Exports the contents of a table called TABLE-NAME to a CSV file at TARGET-PATH." (progn (save-excursion (goto-char (point-min)) (search-forward (concat "RESULTS: " table-name)) (org-cycle) (evil-next-line) (org-table-export target-path "orgtbl-to-csv") (evil-previous-line)) (org-cycle)) (message "Table export completed"))

+END_SRC

** Export Org Mode TODO headers into estimation table

+BEGIN_SRC emacs-lisp

(setq ok-export-org-estimations-file "~/src/200ok/admin/src/org-ok-estimations/org-ok-estimations.el") (unless (file-exists-p ok-export-org-estimations-file) (load-file ok-export-org-estimations-file))

+END_SRC

** General config

*** Don't export the [[https://orgmode.org/manual/Publishing-options.html][html-validation-link]]

+BEGIN_SRC emacs-lisp

(setq org-html-validation-link nil)

+END_SRC

*** Export planning information

+begin_src emacs-lisp

(setq org-export-with-planning t)

+end_src

** Agenda to ICS file

This exports the Org mode Agenda to a ICS file, so that it can be consumed by any calendar application.

+begin_src emacs-lisp

 ;; Setting dummy vars for private variables.
 (setq org-agenda-private-local-path "/tmp/dummy.ics")
 (setq org-agenda-private-remote-path "/sshx:user@host:path/dummy.ics")

 ;; Export the agenda for the next month (default is week)
 (setq org-agenda-span (quote month))

 ;; Override the dummy values from above.
 (if (file-exists-p "~/.emacs.d/private_config.el")
     (load-file "~/.emacs.d/private_config.el"))

 (setq org-agenda-custom-commands
       `(;; Agenda to only show personal GTD files and filter work
         ;; related files which is easy to add with `#+FILETAGS: 200ok`.
         ("c" "Custom agenda, ignore 200ok tag"
          ((agenda ""))
          ((org-agenda-tag-filter-preset '("-200ok"))))
         ;; Define a custom command to save the org agenda to a file
         ("X" agenda "" nil ,(list org-agenda-private-local-path))))

 (defun org-agenda-export-to-ics ()
   (set-org-agenda-files)
   ;; Run all custom agenda commands that have a file argument.
   (org-batch-store-agenda-views)

   ;; Org mode correctly exports TODO keywords as VTODO events in ICS.
   ;; However, some proprietary calendars do not really work with
   ;; standards (looking at you Google), so VTODO is ignored and only
   ;; VEVENT is read.
   (with-current-buffer (find-file-noselect org-agenda-private-local-path)
     (goto-char (point-min))
     (while (re-search-forward "VTODO" nil t)
       (replace-match "VEVENT"))
     (save-buffer))

   ;; Copy the ICS file to a remote server (Tramp paths work).
   (copy-file org-agenda-private-local-path org-agenda-private-remote-path t))

+end_src

Since I'm editing my Org files not just with Emacs, but also with [[https://github.com/200ok-ch/organice/][organice]], I'm doing this on a regular basis in a cron job which runs this code:

+begin_src shell

 #!/bin/bash
 emacs -batch -l ~/.emacs.d/init.el -eval "(org-agenda-export-to-ics)" -kill

 if [[ "$?" != 0 ]]; then
   notify-send -u critical "exporting org agenda failed"
 fi

+end_src

The hourly cron job looks like this:

+begin_example

0 /home/munen/bin/export-org-agenda.sh

+end_example

*** Integration to Google calendar

Google calendar supports subscribing to ICS feeds out of the box.
However, it only updates the feed "every few hours" which seems to
be 12-24h. With this Google AppsScript, this can be configured in
a fine grained way: https://github.com/derekantrican/GAS-ICS-Sync

https://github.com/tarsius/hl-todo

In the past, I've used [[https://github.com/vincekd/comment-tags][comment-tags-mode]]. It's last commit is 2017, while hl-todo is still actively developed in 2022 by tarsius.

=hl-todo= highlights and lists comment tags such as 'TODO', 'FIXME', 'XXX'.

+BEGIN_SRC emacs-lisp

(require 'hl-todo)

(setq hl-todo-keyword-faces '(("TODO" . "#DF5427") ; A concrete TODO with actionable steps ("FIXME" . "#DF5427") ; A non-concrete TODO. We only know something is broken/amiss. ("HACK" . "#DF5427") ; Works, but is a code smell (quick fix). Might break down the line. ("CHECK" . "#CC6437") ; Assumption that needs to be verified. ("NOTE" . "#1FDA9A") ; Use to highlight a regular, but especially important, comment. ("INFO" . "#1FDA9A") ; Use to highlight a regular, but especially important, comment. ))

+END_SRC

Define helpful shortcuts:

+begin_src emacs-lisp

(define-key hl-todo-mode-map (kbd "C-c t p") 'hl-todo-previous) (define-key hl-todo-mode-map (kbd "C-c t n") 'hl-todo-next) (define-key hl-todo-mode-map (kbd "C-c t o") 'hl-todo-occur) (define-key hl-todo-mode-map (kbd "C-c t i") 'hl-todo-insert)

+end_src

Enable =hl-todo= mode where required:

+begin_src emacs-lisp

(add-hook 'prog-mode-hook 'hl-todo-mode) (add-hook 'conf-mode-hook 'hl-todo-mode)

+end_src

** Guix packages

+begin_src fundamental :noweb-ref packages

"emacs-hl-todo"

+end_src

The following packages would be nice, in theory. In practice something is yet amiss, but it might be different in the future. That's why I'm keeping them around and will try them at another time.

** clipmon https://github.com/bburns/clipmon

Proposition: Monitors system clipboard and puts everything in the kill-ring.

Caveat: In theory, I liked the package. However, it seemed to cause racing conditions and crashed Emacs multiple times a day. When this is re-implemented in a non-blocking mode, this would be nice.

+BEGIN_SRC emacs-lisp

;; (add-to-list 'after-init-hook 'clipmon-mode-start)

+END_SRC

*** Comment

Theoretically this is really nice to have functionality. However, I couldn't run it for long. Emacs started freezing a lot on the day when I added this lib. I assume, because clipmon is blocking - and I always run multiple instances of Emacs in parallel. They might be in for a classic racing condition. Might be just another bug.

Modes I probably could use, but haven't tried out, yet.

** =rspec-mode=

Kudos SirPscl

https://github.com/pezra/rspec-mode#usage

** Increase selected region by semantic units

https://github.com/magnars/expand-region.el