emacs-config
emacs-config copied to clipboard
My Emacs configuration in org-babel format
#+SETUPFILE: "~/.emacs.d/org-config.org" #+TITLE: My org-babel based emacs configuration #+LANGUAGE: en #+OPTIONS: H:5 toc:nil creator:nil email:nil author:t timestamp:t tags:nil tex:verbatim #+PROPERTY: header-args :results silent :noweb no-export
Last update: call_last_modified()
This is my emacs configuration file that is loaded with =org-babel-load-file= in the Emacs init file. The intent is to have as much of my Emacs configuration in here as possible. The system works as a literal programming system where with a tangle the elisp code that actually makes up my configuration is extracted automatically and loaded.
This file was created from many separate elisp files and has not been fully sanitized yet. Following the history of that may be interesting to some as well.
This document is irregularly published and lives in different locations:
- [[https://github.com/mrvdb/emacs-config][config sources are available on github]]
- [[https://qua.name/mrb/an-org-babel-based-emacs-configuration][published on my writefreely instance qua.name]]
#+TOC: headlines 4
- Files for configuration Next to this main configuration file (=mrb.org=), two other files are involved in the configuration:
-
=early-init.el= emacs' early initialization file; #+INCLUDE: early-init.el src emacs-lisp
-
=init.el= the main init file, which is used to generate =mrb.el= from =mrb.org=. #+INCLUDE: init.el src emacs-lisp
Ideally these files should be produced by org-babel as well, just like the main configuration, but that generates a dependency because these two files need to be available from the beginning. I am not entirely sure if I need the early-init.el file though.
Small snippet to have last modified timestamp inserted when exporting this file (see line above which has the call to =last_modified=)
#+NAME: last_modified #+BEGIN_SRC sh git log -1 --pretty="format:%ci" ~/.emacs.d/mrb.org #+END_SRC
- Preparing for lift-off The first thing I want to take care of is to make customization possible and stored in a place to my liking. I want to load this first so anything in the configuration I define explicitly overrides it.
#+begin_src emacs-lisp (setq custom-file (concat user-emacs-directory "custom.el")) (load custom-file) #+end_src
The config file =mrb.org= gets opened a lot because I tend to fiddle with this config at least once a day. So, it warrants it's own keybinding, but it will have to wait until [[Key bindings]] before we can use the =bind-key= package.
#+begin_src emacs-lisp (defun mrb/open-config () (interactive) (find-file config-file)) #+end_src
Most of the time the editing process starts at the command line. Because I use emacs in server mode, this needs a little script. The script does two things:
- check if we already have the emacs server, if not, start it;
- treat input from stdin a little bit special.
While we are here, define a snippet which should go in shell scripts. We will repeat this per language at some point.
#+name: tangle-header #+begin_src sh :tangle no [ Org-mode generated this file from a code block, changes will be overwritten ] #+end_src
And edit the file obviously.
#+begin_src sh :exports code :tangle ~/bin/edit :shebang #!/bin/bash
<>
Wrapper for emacs-client usage to handle specific cases:
- no filename given
- stdin editing
- force tty
- adjustment to systemd invocation
ME=$(basename $0)
EC=which emacsclient
SNAME=server
SFILE=$XDG_RUNTIME_DIR/emacs/$SNAME
Default argument needs our socket in any case
ARGS="--socket-name=$SNAME --suppress-output"
Set to false when done
debug() { false $@; }
Do we already have an emacs?
I could let emacsclient do this automatically, but it
is hard-coded to exec emacs --daemon (without socket name)
This goes along an emacs.service file obviously
if ( ! systemctl -q --user is-active emacs );then debug "Emacs systemd service not active...";
# We might have a socket though
if [ -S $SFILE ];then
# The systemd starting has always been a little problematic
# with the environment, so I might have a server running another
# way.
# FIXME: why not /just/ checking for the socket?
#
debug "..but server socket is there, using it..."
else
debug "...and we have no socket either."
notify-send "Starting Emacs server, this might take a while....";
emacs --bg-daemon=$SFILE
#systemctl --user start emacs
fi
fi
Assert that the socket file exists
[ ! -S $SFILE ] || debug "Socket file not found???\n"
Handle special 'stdin' case
if [ $# -ge 1 ] && [ "$1" == "-" ]; then TMPSTDIN="$(mktemp /tmp/emacsstdinXXX)"; cat >"$TMPSTDIN"; # Recurse or call with flags below $ME $TMPSTDIN; rm $TMPSTDIN; exit 0; fi
We're basically expecting X windows, but if not, we good
force tty: call this like 'DISPLAY="" edit ....' see my aliases
if [ $DISPLAY ]; then debug "X-display value seen, using GUI" case $ME in "edit") ARGS=$ARGS' --no-wait' ;; "edit-wait") ARGS=$ARGS' ' ;; "edit-frame") ARGS=$ARGS' --no-wait --create-frame' ;; ,*) echo "Wrong calling name..." ;; esac else # tty always waits, as it is in the terminal, create-frame has no meaning debug "Starting tty emacs" ARGS=$ARGS' -tty' fi
Go with the original arguments
exec $EC ${ARGS} "$@" #+end_src
The above script is the result of trying to get the server based setup of Emacs a little better. The thoughts leading to the above script are included below.
Emacs can run as a /server/ meaning that its process is always active and a specific /client/ program called =emacsclient= can be used to connect to it to do the actual user interaction and let me edit and use the lisp interpreter.
It's far from trivial, for me at least, on how to properly configure this, assuming you have some special wishes. I've certainly spent my time on trying to get it right.
*** Things that I'm sure of I want Here's what I know for sure on what I want:
- I want to have at least one server process for 'default' editing so emacs does not have to start a process for every file; the goal here is to have fast opening of files to edit. This is critical. I'm assuming that for this a server-like-setup is needed.
- Given that I tweak emacs a lot, it must be very easy to either disable the server temporarily or have it restart easily so I can test new changes without shutting me out of emacs completely.
- The previous item also implies that I want to run multiple independent emacs-en next to each other, regardless if they are run as a daemon or not.
- Emacs must work on a non X-windows screen, but extra effort to get that is OK.
- Be able to use '-' as placeholder for =stdin= to be able to pipe information directly into editor
- allow specific handlers, like =mailto:=
*** Things I see potential for, but not sure of Having a server/servers opens a couple of options which I may like, but not sure on how to do or even if I understand them properly
-
given that the socket can be a TCP socket, does that mean I can use emacsclient -some-arg <the IP of the socket> on one machine to use emacs on another machine? How well would that work? This would be awesome to have a small computer which doesn't need an emacs config to use my emacs on another machine, but I suspect I misunderstand how this works
-
having multiple emacs servers could help with specific scenario's for mu4e for example. I'd love to have the mu-server in its own process and just use that from other emacs servers Similarly for long running language servers and or other processes which shouldn't be affected by me clawing words into my keyboard.
- Package Management Package handling, do this early so emacs knows where to find things. Package handling basically means two things:
- getting and installing packages, by default handled by package.el;
- configuring packages, just elisp, but I use the =use-package= macro typically.
As I want to have version controlled package installation that is easily reproducible, =straight.el= seem a logical choice, so let's start by bootstrapping that.
#+begin_src emacs-lisp (defvar bootstrap-version) (setq straight-repository-branch "develop") ; Need this for new org-contrib location (let ((bootstrap-file (expand-file-name "straight/repos/straight.el/bootstrap.el" user-emacs-directory)) (bootstrap-version 5)) (unless (file-exists-p bootstrap-file) (with-current-buffer (url-retrieve-synchronously "https://raw.githubusercontent.com/raxod502/straight.el/develop/install.el" 'silent 'inhibit-cookies) (goto-char (point-max)) (eval-print-last-sexp))) (load bootstrap-file nil 'nomessage)) #+end_src
Now that we have =straight= we can use it to install =use-package=
#+begin_src emacs-lisp (straight-use-package 'use-package) (setq use-package-always-ensure nil ; Make sure this is nil, so we do not use package.el use-package-verbose 'debug ; FIXME: use a debug var for all of config? ) ;; From this point on we should be able to use `use-package (use-package straight :config (setq straight-host-usernames '((github . "mrvdb"))) ; Move to personal information? ;; Make sure packages do not pull in internal org, we pregister org from straight.el (straight-register-package 'org) (straight-register-package 'org-contrib)
<<straight-helperfunctions>>
:custom (straight-use-package-by-default t))
;; FROM THIS POINT use-package should work as intended, i.e. using straight.
;; Need to install dependencies of use-package manually (use-package diminish) (use-package use-package-ensure-system-package) ; because we are in use-package config (use-package bind-key) ; Load early, but see section [[Key bindings]] #+end_src
As emacs lives inside the systemd environment it is not automatically the case that my shell environment is inherited. In many cases, this is not a problem, but for those cases that expect the same environment, I'm making explicit that the enviroment needs to be from the shell. We should do this a soon as =use-package= is useable.
#+begin_src emacs-lisp (use-package exec-path-from-shell :demand t :config (exec-path-from-shell-initialize)) #+end_src
This makes sure that things like TeX and git and other programs run in the same context as they would in my shell. Especially for guix-installed programs this is important.
- Personal information I'd like to arrange 3 things related to personal information:
- register the proper identification to use;
- Make sure the proper authentication information is stored;
- Store this information privately.
So, identification, authorization and encryption.
Seems like a good idea to start configuration with some details about me. The idea being that anything personal goes here and nowhere else. For one, this means my name only appears in this section in this document. Most of the variables I just made up, but some are used in other places too.
#+begin_src emacs-lisp (setq mrb/default-email-address "[email protected]" mrb/default-signature-file "~/Maildir/.signature" mrb/private-addresses '("[email protected]" "[email protected]") mrb/private-signature-file "~/Maildir/.signature-private" mrb/default-private-address (car mrb/private-addresses)
mrb/bcc-address "[email protected]"
user-full-name "Marcel van der Boom"
user-mail-address mrb/default-email-address
user-domain "hsdev.com"
user-organisation "HS-Development BV"
user-gpg-encrypt-key "77DDA1B68D04792A8F85D855235E5C8CF5E8DFFB"
)
#+end_src
** Authorization Several things I use within Emacs need authorization, such as tramp, jabber, erc etc. The authorization can come from several sources; ideally as few as possible. Many packages in Emacs have support for a =.netrc= like mechanism, others want to use the key ring in GNOME. The variables =auth-sources= defines the sources available.
I want to use systems which are desktop independent, so things like the gnome key ring are out because they depend on the gnome environment being present, which I can not guarantee, nor want to related to authentication. The situation which I want to prevent is that if gnome is broken, I can't authenticate to services I need.
I have a gpg-agent daemon configured which manages gpg and ssh keys, protected by a hardware key. Let's make the system as simple as we can for now and just store passwords in the gpg protected store only, i.e. the password-store program.
#+begin_src emacs-lisp ;; Use only password-store (use-package password-store) (use-package auth-source-pass :straight (:type built-in) :init (auth-source-pass-enable) :after password-store :config ;; Make sure it's the only mechanism (setq auth-sources '(password-store) auth-source-gpg-encrypt-to (list user-mail-address))) ;; I like the pass interface, so install that too (use-package pass :after password-store) #+end_src
** Encrypting information I need a way to store some sensitive information without that being published, should I decide some day to push this config somewhere.
When needed, the gpg key is used to encrypt information.
#+begin_src emacs-lisp ;; Use my email-address for encryption (setq-default epa-file-encrypt-to user-mail-address) ;; Make sure we always use this (setq-default epa-file-select-keys nil) #+end_src
For org-mode, there is a way to encrypt sections separately. See [[Encrypting information in org-mode]] for the details on the settings for this.
Next to inline content in org that needs encryption, there is also content that needs encrypting which is more suitable to store in a separate file for several reasons.
- Global Generic settings Section contains global settings which are either generic or I haven't figured out how to classify it under a better section.
Let's begin with the setup of some fundamentals like if we want tabs or spaces, how to select stuff and what system facilities to use.
I'm of the /spaces-no-tabs religion/: #+begin_src emacs-lisp (setq-default indent-tabs-mode nil) ; do not insert tabs when indenting by default (setq tab-width 4) ; 4 spaces by default #+end_src
A the same time, I don't want to be /that spaces guy/ in every project, and I'm willing to use tabs if that is the overwhelming consensus in a file. [[https://www.emacswiki.org/emacs/NoTabs]] has a snippet which guess the style in a file. Call this at the appropriate time.
#+begin_src emacs-lisp (defun mrb/infer-indentation-style () ;; if our source file uses tabs, we use tabs, if spaces spaces, and if ;; neither, we use the current indent-tabs-mode (let ((space-count (how-many "^ " (point-min) (point-max))) (tab-count (how-many "^\t" (point-min) (point-max)))) (if (> space-count tab-count) (setq indent-tabs-mode nil)) (if (> tab-count space-count) (setq indent-tabs-mode t)))) #+end_src
Finally, I want to prevent producing files which have trailing spaces, because they serve no purpose at all. Removing these spaces is less trivial than it seems and I am not pretending to know what the best strategy is. For a while now, the =ws-butler= package has done a great job for me.
#+begin_src emacs-lisp ;; Probably move this to somewhere else ? (use-package ws-butler :diminish :init (ws-butler-global-mode) ; Enable by default ;; List exemptions here ;; FIXME: why is org-capture-mode in here? (add-to-list 'ws-butler-global-exempt-modes 'org-capture-mode) (add-to-list 'ws-butler-global-exempt-modes 'magit-mode))
(setq delete-by-moving-to-trash t ; move files to the trash instead of rm select-enable-clipboard t ; use the clipboard in addition to kill-ring
display-warning-minimum-level 'error large-file-warning-threshold nil find-file-use-truenames nil find-file-compare-truenames t
minibuffer-max-depth nil minibuffer-confirm-incomplete t complex-buffers-menu-p t next-line-add-newlines nil kill-whole-line t
auto-window-vscroll nil)
;; Map backspace to DEL and delete to C-d (if window-system (normal-erase-is-backspace-mode t))
;; Let marks be set when shift arrowing, everybody does this (setq shift-select-mode t) (delete-selection-mode 1)
;; Only require to type 'y' or 'n' instead of 'yes' or 'no' when prompted (defalias 'yes-or-no-p 'y-or-n-p)
;; Use auto revert mode globally ;; This is safe because emacs tracks if the file is saved in the editing buffer ;; and if so, it will not revert to the saved file. (use-package autorevert :diminish auto-revert-mode
:config
(setq auto-revert-interval 0.5 ; Perhaps too often, let's see
global-auto-revert-non-file-buffers t) ; things like dired buffers
(global-auto-revert-mode t))
;; Should this be here? ;; Try to have urls and mailto links clickable everywhere (setq goto-address-url-mouse-face 'default) (define-global-minor-mode global-goto-address-mode goto-address-mode (lambda () (goto-address-mode 1))) (global-goto-address-mode t)
;; Shorten urls for mode specific syntax (defun mrb/shortenurl-at-point () (interactive) (let ((url (thing-at-point 'url)) (bounds (bounds-of-thing-at-point 'url))) (kill-region (car bounds) (cdr bounds))
; Leave url as is, unless mode has specific link syntax
(insert (format (cond ((eq major-mode 'org-mode) "[[%1s][%2s]]")
((eq major-mode 'markdown-mode) "[[%2s][%1s]]")
(t "%1s"))
url
(truncate-string-to-width url 40 nil nil "…")))))
(bind-key "C-c s" 'mrb/shortenurl-at-point) #+end_src
Make life easier if we have sudo, so we can just edit the files and be done with them if possible
#+begin_src emacs-lisp (use-package sudo-save) #+end_src
Using pdf-tools for PDF handling. This is a lot better than docview. I'm a bit annoyed that they are not one package, they are very similar.
#+begin_src emacs-lisp
(use-package pdf-tools
:straight (:host github :repo "vedang/pdf-tools")
:demand t ; prevent configuring while opening pdf files
:after (fullframe)
:magic ("%PDF" . pdf-view-mode)
:hook (pdf-view-mode . (lambda () (cua-mode 0)))
:commands (pdf-info-getannots mrb/pdf-extract-info)
:bind (:map pdf-view-mode-map
("h" . 'pdf-annot-add-highlight-markup-annotation)
("t" . 'pdf-annot-add-text-annotation)
("D" . 'pdf-annot-delete)
("C-s" . 'isearch-forward)
("m" . 'mrb/mailfile)
("q" . 'kill-this-buffer)
([remap pdf-misc-print-document] . 'mrb/pdf-misc-print-pages)
:map pdf-annot-edit-contents-minor-mode-map
("
:config
;; TODO uncommenting the next line helps when rebuilding faild
;(setq pdf-info-epdfinfo-program
; (expand-file-name "server/epdfinfo" (straight--repos-dir "pdf-tools")))
(pdf-tools-install :no-query) ; :no-query auto builds epfinfo when needed
(require 'pdf-annot)
;; Some settings from http://pragmaticemacs.com/emacs/even-more-pdf-tools-tweaks/
(fullframe pdf-view-mode kill-this-buffer)
(setq-default pdf-view-display-size 'fit-page) ;scale to fit page by default
(setq pdf-annot-activate-created-annotations t ; automatically annotate highlights
pdf-view-resize-factor 1.1 ; more fine-grained zooming
pdf-misc-print-program-executable "/usr/bin/lp"
pdf-view-midnight-colors '("#DCDCCC" . "#383838")) ; Not sure what this is
(add-to-list 'revert-without-query ".+\\.pdf")
;; NOTE: assumes lp as printer program!
(defun mrb/pdf-misc-print-pages(filename pages &optional interactive-p)
"Wrapper for `pdf-misc-print-document` to add page selection support"
(interactive (list (pdf-view-buffer-file-name)
(read-string "Page range (empty for all pages): "
(number-to-string (pdf-view-current-page)))
t) pdf-view-mode)
(let ((pdf-misc-print-program-args
(if (not (string-blank-p pages))
(cons (concat "-P " pages) pdf-misc-print-program-args)
pdf-misc-print-program-args)))
(pdf-misc-print-document filename))))
#+end_src
Integrate this with orgmode as well. =org-pdftools= provides a link type, so we can link /into/ pdf documents. =pdf-tools-org= provides utility functions to help export pdf annotations into org mode and vice versa
#+begin_src emacs-lisp (use-package org-pdftools ; this brings in org-noter as well :after (org pdf-tools) :hook (org-mode . org-pdftools-setup-link))
(use-package pdf-tools-org :demand t ; when should this be loaded? goes on pdftools load now :after (org pdf-tools) :straight (:host github :repo "machc/pdf-tools-org")) #+end_src
I tend to compile Emacs with ImageMagick support, let's make sure we use it too.
#+begin_src emacs-lisp (when (fboundp 'imagemagick-register-types) (imagemagick-register-types)) #+end_src
Quick install of package that exports =qrencode-region= function. I use this mainly to send links or package tracking codes to my mobile.
#+begin_src emacs-lisp (use-package qrencode) #+end_src
- Internationalization and multi-language features If anything multi-language should work, UTF-8 encoding is a must, so let's make sure we try to use that everywhere
#+begin_src emacs-lisp (prefer-coding-system 'utf-8) #+end_src
For conveniently editing accented characters like 'é' and 'è' there are quite a few options to reach that result. I have dead characters configured as an option in the operating system, but that is far from ideal, especially when programming. As I hardly need those characters outside of emacs, i can leave that option as needed and optimize Emacs to my needs.
The fallback is C-x 8 C-h which gives specific shortcuts for special characters which are available. For the exotic characters that will do just fine. For the more common characters the C-x 8 prefix is to complex.
After evaluating some systems, the TeX input method suits me the best. I'd like to enable that globally by default, which needs two things:
- enable multi-language editing by default (input-method is only active in a multi-language environment)
- set the default input-method to tex
There is however a problem, the TeX input method assumes the first character / string produced will always be the choice I need, without allowing selecting an alternative. This turns out to be due to =quail-define-package= which determines the way the completions work. The problem is the =DETERMINISTIC= argument of the function, that is set to 't'. (8th argument). While I am at it, I also changed the =FORGET-LAST-SELECTION= (7th argument) to nil, so the last selection is remembered.
For this to work properly we have to define a whole new input-method based on a copy of latin-ltx.el
#+begin_src emacs-lisp ;; No input method will be active by default, so for each mode where ;; it needs to be active we need to activate it (by a hook for example) (defun mrb/set-default-input-method() (interactive) (with-temp-buffer (activate-input-method "TeX")
;; Define a few omissions which I use regularly
(let ((quail-current-package (assoc "TeX" quail-package-alist)))
(quail-define-rules ((append . t))
("\\bitcoin" ?฿)
("\\cmd" ?⌘)
("\\shift" ?⇧)
("\\alt" ?⎇)
("\\option" ?⌥)
("\\return" ?⏎)
("\\tab" ?↹)
("\\backspace" ?⌫)
("\\delete" ?⌦)
("\\plusminus" ?±)
("\\_1" ?₁)
("\\_2" ?₂)))))
;; Set the default language environment and make sure the default ;; input method is activated (add-hook 'set-language-environment-hook 'mrb/set-default-input-method) ;; And now we can set it (set-language-environment "UTF-8")
;; Activate it for all text modes (add-hook 'text-mode-hook 'mrb/set-default-input-method) #+end_src
For even more esoteric characters we have to do some extra work. No font provides all Unicode characters. While I previously had a manual solution, I'm now relying on the package =uni-code-fonts= to do the proper mapping. The first run will be very slow, but the information gets cached.
The problem with applying this function is that we need to be 'done' with our visual initialization or otherwise they'll do nothing (at least that I can see). So, let's group our corrections in a function and call that when we are done with our visual (X) init.
#+begin_src emacs-lisp (use-package unicode-fonts :demand t )
(defun mrb/unicode-font-corrections() (interactive) ;; TODO this fails with recent emacs-28, finds '...' in unicode-fonts pcache file?? ;; TODO it works with emacs-29 but cache state is not save, so it runs on startup always (unicode-fonts-setup) ;; Add a line like the following for each char displaying improperly
;; mu4e uses Ⓟ,Ⓛ and Ⓣ, let's correct all letters
(set-fontset-font t '(?Ⓐ . ?Ⓩ) "Symbola") ; noto line-height too high
) #+end_src
So, when characters do not show properly, the steps to take now are:
- Find a font which has the char
- Map the character(-range) to that font
- Optional: define a convenient way to type the character
To make my life a bit easier, using the =all the icons= package for using special symbols.
#+begin_src emacs-lisp (use-package all-the-icons :if (display-graphic-p)) ; FIXME: when in daemon mode, this does not get loaded #+end_src
The above in itself does nothing. It just makes using the icons easier and consistent. Other packages. With the command =M-x all-the-icons-install-fonts= the latest version of the proper fonts will be installed automaticaly, so run that once in a while.
- Visual Many settings have to do with how the screen looks and behaves in a visual way. Thing like colors, highlighting etc. go fall into this category.
Let's set up some basics first, background dark, some default frame and cursor properties:
#+begin_src emacs-lisp (setq mrb/cursor-type '(bar . 3)) ; Slightly wide bar (setq mrb/cursor-color "DarkOrange") ; in fat orange color
(setq-default frame-background-mode 'dark)
(set-mouse-color "white")
;; Only show cursor in the active window.
(setq-default cursor-in-non-selected-windows nil)
;;Default frame properties frame position, color, etc
(setq default-frame-alist
`((cursor-type . ,mrb/cursor-type )
(height . 60) (width . 100)
(cursor-color . ,mrb/cursor-color)
(internal-border-width . 24)
(mouse-color . "white")))
#+end_src
I'm using the [[https://github.com/arcticicestudio/nord][Nord color palette]]. This palette defines 16 colors.
There is a base16 theme which uses exactly these colors and there are varieties which use the 16 colors and some derivatives of it.
Although I think the 16 color system is a bit simplistic and, for emacs at least, I will need to customize more later on, the main lure of this system is that I can use the same set for many of my programs I use (emacs, xterm, i3, dunst etc.) which sounds attractive. So, I'm starting with the =base16-nord= theme and see where this leaves me.
#+begin_src emacs-lisp (setq custom--inhibit-theme-enable nil) ; This was needed for Emacs 27, but cant recall why (use-package base16-theme :config ;; Do config here and finally load the theme (setq base16-theme-distinct-fringe-background nil base16-theme-highlight-mode-line 'contrast base16-theme-256-color-source "terminal") (load-theme 'base16-nord t)
;; Define our adjustments to faces
;; This should be the only place where we adjust our faces
;; For reference, these are the theme colors
;; :base00 "#2e3440"
;; :base01 "#3b4252"
;; :base02 "#434c5e"
;; :base03 "#4c566a"
;; :base04 "#d8dee9"
;; :base05 "#e5e9f0"
;; :base06 "#eceff4"
;; :base07 "#8fbcbb"
;; :base08 "#88c0d0"
;; :base09 "#81a1c1"
;; :base0A "#5e81ac"
;; :base0B "#bf616a"
;; :base0C "#d08770"
;; :base0D "#ebcb8b"
;; :base0E "#a3be8c"
;; :base0F "#b48ead"
;; FIXME: some of these adjustments come in too early and the package redefines it on loading
;; the only solution for this is to move the visual adjustments in the use-package clauses of
;; the package itself.
(setq base16-adjustments
`((vterm-color-black :foreground "#2e3440")
(vterm-color-red :foreground "#bf616a")
(vterm-color-green :foreground "#a3be8c")
(vterm-color-yellow :foreground "#ebcb8b")
(vterm-color-blue :foreground "#81a1c1")
(vterm-color-cyan :foreground "#88c0c0")
(vterm-color-magenta :foreground "#b48ead")
;; Explicitly define the outline colors
(outline-1 :foreground "#5e81ac")
(outline-2 :foreground "#a3be8c")
(outline-3 :foreground "#b48ead")
(outline-4 :foreground "#81a1c1")
(outline-5 :foreground "#ebcb8b")
(outline-6 :foreground "#8fbcbb")
(outline-7 :foreground "#d08770")
(outline-8 :foreground "#bf616a")
;; Adjustment for non-package faces
;; Comments should not be that dark as base03
(font-lock-comment-face :foreground "#666666")
(button :foreground "#d08770" :weight semi-bold)
(show-paren-match :foreground "#eceff4" :background "#81a1c1"
:inherit nil :weight normal)
(show-paren-mismatch :foreground "#2e3440" :background "#bf616a"
:inherit nil :weight normal)
(region :foreground nil :background "#3b4252"
:inherit nil :weight normal)
(fringe :foreground "#a3be8c")
(hl-line :foreground nil :background "#5e81ac" )
(line-number-current-line :background "#4c566a")
(lazy-highlight :foreground "#434c5e" :background "#5e81ac")
(isearch :foreground "#434c5e" :background ,mrb/cursor-color)
;; Mode-line faces
(mode-line :foreground "#3b4252" :background "#ebcb8b")
(header-line :foreground "#4c566a" :background "#ebcb8b")
;; Magit
(diff-refine-added :background "#003000")
(diff-refine-removed :background "#300000")
(magit-diff-added-highlight :foreground "#a3be8c")
(magit-diff-removed-highlight :foreground "#bf616a")
(magit-diff-added :foreground "#a3be8c")
(magit-diff-removed :foreground "#bf616a")
(magit-diff-hunk-heading-highlight :background "#5e81ac")
(magit-diff-hunk-heading :background "#5e81ac")
;; Elfeed
(elfeed-search-tag-face :foreground "#a3be8c")
(elfeed-search-feed-face :foreground "#ebcb8b")
(elfeed-search-date-face :foreground "#88c0d0")
;; Flyspell
;; flyspell errors/dupes look the same as flycheck-errors/warnings
(flyspell-incorrect :inherit flycheck-error)
(flyspell-duplicate :foreground "#2e3440" :inherit flycheck-warning)
;; Orgmode
(org-headline-done :foreground "#4c566a")
(org-date :foreground "#8fbcbb" :underline nil)
(org-verbatim :foreground "#8fbcbb")
(org-list-dt :foreground "#b48ead")
(org-drawer :height 0.8 :inherit font-lock-comment-face)
(org-code :foreground "Purple" :weight bold)
(org-block :background "#313745")
(org-block-begin-line :foreground "dark gray")
;; User defined faces for org-mode
(mrb/org-todo-keyword-TODO :foreground "#88c0d0" :weight bold)
(mrb/org-todo-keyword-DONE :foreground "#a3be8c" :weight bold)
(mrb/org-todo-keyword-WAITING :foreground "#bf616a" :weight bold)
(mrb/org-todo-keyword-CANCELLED :foreground "#3b4252" :weight bold)
(mrb/org-todo-keyword-BUY :foreground "#d08770" :weight bold)
(mrb/org-todo-keyword-HOWTO :foreground "#5e81ac" :weight bold)
(mrb/org-todo-keyword-INFO :foreground "#ebcb8b" :weight bold)
(mrb/org-todo-keyword-COLLECT :foreground "#a3be8c" :weight bold)
(mrb/org-todo-keyword-SOLVE :foreground "#8fbcbb" :weight bold)
(mrb/org-todo-keyword-READ :foreground "#b48ead" :weight bold)
(mrb/org-todo-keyword-READING :foreground "#bf616a" :weight bold)
(mrb/org-todo-keyword-PLAN :foreground "#d8dee9" :weight bold)
;; Helm selection
(helm-selection :foreground "black" :background "goldenrod2")
(helm-M-x-key :foreground "#bf616a")
;; Mu4e
(mu4e-header-highlight-face :inherit hl-line :underline nil :foreground nil)
(mu4e-context-face :inherit mu4e-link-face)
;; Man pages inside emacs
(Man-overstrike :foreground "#bf616a" :weight bold)
(Man-underline :foreground "#a3be8c" :weight bold)
))
;; Apply our adjustment using the theme function
(base16-theme-set-faces 'base16-nord base16-nord-theme-colors base16-adjustments))
#+end_src
Miscellaneous other visual settings follow.
#+begin_src emacs-lisp ;; no splash screen (setq inhibit-startup-screen t) (setq inhibit-startup-message t) (setq initial-scratch-message nil)
;; check speed consequences of this (setq column-number-mode t)
;; Track changes to buffers (use-package tracking :config (setq tracking-max-mode-line-entries 1 tracking-shorten-buffer-names-p nil) (tracking-mode 1))
;; Mode-line ;; TODO: probably simplify this package, now we do not need evil support (use-package spaceline :straight (:includes spaceline-config))
(use-package spaceline-config :after tracking ;; :requires eyebrowse ;; :requires persp-mode ;; :requires window-numbering :config ;; Define a segment for the tracking package (spaceline-define-segment tracking "Segment to show if buffers need attention, similar to erc-track, but using mcirc tracking library" tracking-mode-line-buffers)
;; We use the spacemacs theme, which gives us the evil state indicator
(spaceline-spacemacs-theme 'tracking)
;; Enable the helm mode
(spaceline-helm-mode))
(use-package mic-paren :config (setq paren-highlight-at-point nil) (paren-activate))
;; Defer fontification, but only if there is input pending (setq jit-lock-defer-time 0)
;; Make underlining nicer (setq underline-minimum-offset 3)
;; Show color of '#RRBBGG texts (use-package rainbow-mode :diminish)
;; Give commands the option to display full-screen (use-package fullframe) #+end_src
** Lines The most important element of an editor is probably how lines of text are displayed. This warrants its own section.
The problem is that 'line' needs clarification because there can be a difference between a logical line and a visual line. It's sometimes important that things stay on one logical line but are displayed over multiple lines. This may be different per mode.
I recently reversed my stance on this and am now enabling visual line mode everywhere (meaning one logical line can be smeared out over multiple display lines. This also enables word-wrap which breaks lines visually between words instead of at random characters.
To help me see the difference between visual lines and logical lines I let emacs display indicators in the fringe and define a function to help me unfill existing paragraphs.
#+begin_src emacs-lisp ;; Set some defaults (setq-default word-wrap t ; why would i want to have no wrapping by word? fill-column 100 truncate-lines nil)
(use-package visual-fill-column :commands (turn-on-visual-fill-column-mode))
;; Similar to mail messages, use vertical bar for wrapped paragraphs (setq visual-line-fringe-indicators '(vertical-bar nil))
;; For all text modes use visual-line-mode (add-hook 'text-mode-hook 'visual-line-mode)
;; From:https://www.emacswiki.org/emacs/UnfillParagraph (defun unfill-paragraph (&optional region) "Takes a multi-line paragraph and makes it into a single line of text." (interactive (progn (barf-if-buffer-read-only) '(t))) (let ((fill-column (point-max)) ;; This would override `fill-column' if it's an integer. (emacs-lisp-docstring-fill-column t)) (fill-paragraph nil region)))
;; Similar to M-q for fill, define M-Q for unfill (bind-key "M-Q" 'unfill-paragraph) #+end_src
Line-numbering is off by default, it tends to slow things down, but if I want it on, I almost always want them relative
#+begin_src emacs-lisp (use-package display-line-numbers :straight (:type built-in) :config (setq display-line-numbers-type 'relative) (display-line-numbers-mode -1)) #+end_src
** Client dependent settings Because most of my usage involves having a long lasting emacs daemon, some settings only come into scope once a client connects to that daemon. Most of these settings have to do with appearance and are related to having X available. Anyways, some settings need to be moved to the event when a client visits the server, so we can still apply these settings transparently.
Note that if this code is evaluated any call to =emacsclient= (be that from external or, more importantly Magit) will try to run this code and magit will fail if there's an error in the next section. Take extra care here. #+begin_src emacs-lisp (defun mrb/run-client-settings(&optional args) (interactive)
(tool-bar-mode -1) ;; No tool-bar
(scroll-bar-mode -1) ;; No scroll-bar
(menu-bar-mode -1) ;; No menu-bar
(tooltip-mode -1) ;; No tooltips
(setq fringe-mode '(14 . 14)) ;; Fringe, left and right for the continuation characters
(set-fringe-mode fringe-mode)
(setq indicate-buffer-boundaries 'left)
(mrb/unicode-font-corrections))
;; Run our client settings after we make a frame This is probably
;; overkill to do on every frame, but it covers the situation where
;; server-visit-hook won't work because we started emacs without a
;; file. This config covers all situations.
(add-hook 'server-after-make-frame-hook 'mrb/run-client-settings)
;; And we need to run those settings now too, when we are not in server mode
(mrb/run-client-settings)
#+end_src
** Context dependent visualization
Next to the information registered in the theme which controls the overall look and feel, there is additional visualization which depend on context dependent factors, such as:
- the user spells a word wrong
- the syntax of a certain phrase is wrong grammatically, depending on what language the user is writing in
- the status of a certain region is changing, dependent on certain rules
- a part of the text could be rendered as non-text, images or formulas for example
*** Flycheck Flycheck is a syntax checking package which seems to have better support than flymake, which is built-in. I've no configuration for flymake specifically, but some packages enable it automatically (elpy for example). Where applicable, I gather the necessary disable actions here first.
Now we can start configuring flycheck
#+begin_src emacs-lisp (use-package flycheck-pos-tip)
(use-package flycheck ;; Use popup to show flycheck message :after flycheck-pos-tip :config (with-eval-after-load 'flycheck (flycheck-pos-tip-mode))) #+end_src
Currently actively configured are:
- javascript :: eslint with a config file
Flyspell is similar to flycheck but for text languages. I'm setting some of the flyspell faces to the flycheck faces so they are consistent
#+begin_src emacs-lisp (use-package flyspell :straight (:type built-in) :config ;; flyspell-duplicate as warnings ;; Do I want this at all?, seems overkill ;; FIXME: this does not seem to help (setq flyspell-mark-duplications-flag nil flyspell-issue-message-flag nil)) #+end_src
Still on the wish-list:
- activate flyspell automatically for all text-modes?
- language detection (English and Dutch mainly)
- check for language pythons, which was troublesome before
- evaluate usage in org source blocks (many checks do not apply)
*** Math rendering Mathematics requires some specific rendering. In simple cases, using the proper unicode symbols may suffice, but for anything other than the most basis equations or formulas, some help is needed. $\LaTeX$ is the defacto standard for this and there's plenty of support for that in emacs.
For $\TeX$ and $\LaTeX$ fragments I use the =texfrag= package. This allows to insert math fragments inside documents.
Example:
$$e^{ix} = cos x + i sinx$$
#+begin_src emacs-lisp (use-package texfrag :diminish :config (setq texfrag-prefix "") (texfrag-global-mode 1)) #+end_src *** Highlighting tags and keywords
In many modes, but especially in orgmode, the use of tags and keywords is abundant. In the set of packages that [[https://github.com/rougier][Nicolas P. Rougier]] has made for emacs, there's also a gem which generates SVG labels from keyword matches.
The use of that package is simple, just create a list of keyword matches in the =svg-tag-tags= variable and match those up with a command to create a tag.
I have at least the following usecases in mind:
- Render my orgmode keywords as tags;
- Use in mu4e to render the tags
- perhaps some FIXME keywords in code, I use that quite a bit.
The first is the easiest, as the format of =org-todo-keyword-faces= is nigh perfect to transform into a list that can be used by svg-tag-mode.
#+begin_src emacs-lisp ;; Trickiest here is to know when org-todo-keyword-faces has been filled (use-package svg-tag-mode :after (org base16-theme) :commands svg-tag-mode ; Possibly redundant? :hook (org-mode . svg-tag-mode)
:config
;; Readymade svg-tag-tags generator from org keywords
(defun mrb/gen-svg-tags (kf)
"Generate a `svg-tag-tags element from a `org-todo-keyword-faces element"
(let ((k (concat "\\(" (car kf) "\\) ")) ; pass keyword, not the space
(f (cdr kf)))
`(,k . ((lambda (tag) (svg-tag-make tag :face ',f :inverse t :margin 0 ))))))
;; This gets initialized the first time svg-tag-mode is called, so then it must have
;; the proper keywords in org-todo-keyword-faces
(setq svg-tag-tags (mapcar 'mrb/gen-svg-tags org-todo-keyword-faces)))
#+end_src
- Buffers and files How do I deal with all those buffers and files?
For starters, make sure that they have unique buffer names so I don't get confused:
#+begin_src emacs-lisp (setq uniquify-buffer-name-style 'forward) #+end_src
For every file-based buffer, I want auto-save to be on, but not in the same location as the file, as that clutters up everything. For that, I add to the list of file-name transforms to have (local) files auto saved in a designated folder)
#+begin_src emacs-lisp (setq auto-save-default t mrb/auto-save-folder (concat user-emacs-directory "auto-save-list/"))
(add-to-list 'auto-save-file-name-transforms (list "\(.+/\)\(.?\)" (expand-file-name "\2" mrb/auto-save-folder)) t) #+end_src
Auto save helps to automatically recover to the latest save point, but I need a bit more. This is done through versioned backup file. The recovery process is then manual though.
#+begin_src emacs-lisp (setq create-lockfiles nil ; just clutters for me, no real use? backup-by-copying t ; otherwise symlinks galore
backup-directory-alist '(
("." . "/tmp/emacs/"))
delete-old-versions t
kept-new-versions 3
kept-old-versions 2
version-control t)
#+end_src
The auto save helps for the minor disasters, my backups help for the major disasters. What else is needed is a 'normal save' but automatically when I forget to do this.
What I am aiming for here is to not have to think about explicitly saving for certain files. Typically when typing stuff in org-mode I just want the stuff saved that I have types so far. For /some/ files, each save is committed if I think the content warrants this (for example if I think going back to an earlier version is a likely event).
#+begin_src emacs-lisp (defconst mrb/idle-timer 15 "Time emacs needs to be idle to trigger the save idle timer")
;; This function does the actual useful thing (defun mrb/save-timer-callback() "Callback function that runs when emacs is idle for `mrb/idle-timer' seconds. Typically we save files here" (org-save-all-org-buffers))
;; Activate the timer ;; The # means: evaluate first ( I keep forgetting this stuff ) (run-with-idle-timer mrb/idle-timer 'always #'mrb/save-timer-callback) #+end_src
Also, save the location in files between sessions.
#+begin_src emacs-lisp ;; Minibuffer prompt is a prompt, don't enter it as text. (setq minibuffer-prompt-properties '(read-only t point-entered minibuffer-avoid-prompt face minibuffer-prompt)) ;; Don't echo keystrokes in the minibuffer, prefix will be hinted on after 2 seconds or so. (setq echo-keystrokes 0)
;; Save places in buffers between sessions (use-package saveplace :straight (:type built-in) :init (setq-default save-place-mode t)) #+end_src
The C-x-w shortcut write a file to a new location (and adjusts the buffer), but leaves the old file lingering. The next defun moves a file in a neater way.
#+begin_src emacs-lisp (defun mrb/move-file (new-location) "Write this file to NEW-LOCATION, and delete the old one." (interactive (list (if buffer-file-name (read-file-name "Move file to: ") (read-file-name "Move file to: " default-directory (expand-file-name (file-name-nondirectory (buffer-name)) default-directory))))) (when (file-exists-p new-location) (delete-file new-location)) (let ((old-location (buffer-file-name))) (write-file new-location t) (when (and old-location (file-exists-p new-location)) (delete-file old-location))))
;; Bind it to the usual shortcut (bind-key "C-x C-w" 'mrb/move-file) (bind-key "C-S-s" 'write-file) #+end_src
For file management itself, dired is the defacto standard It's builtin so =use-package= isn't actually needed but it's nice for consistency.
#+begin_src emacs-lisp (use-package dired :straight (:type built-in) ;; auto-revert global is only for file buffers, so explicit enable is needed :hook (dired-mode-hook . auto-revert-mode) :config (setq dired-bind-jump nil) ; Do not bind to C-x C-j, we do jabber prefix there ) #+end_src
- Modes Customization setting for specific modes. Most of the modes I use have a separate section, so this section is only for other modes.
To determine the default major mode; the mode that is started with before all the magic starts is determined by buffer-file-name. If we have it, the normal routine can be followed. If there is no filename yet, the buffer-name is used to determine which mode is needed.
By looking at the code this may have a side-effect, because the buffer-file-name is given a value. Let's try this and see if it gives any issues. #+begin_src emacs-lisp (setq-default major-mode (lambda () (if buffer-file-name (fundamental-mode) (let ((buffer-file-name (buffer-name))) (set-auto-mode))))) #+end_src
When emacs does not determine the right mode, I sometimes want a mode-string somewhere in the file. Typically that string gets inserted on a line which is commented out in the proper syntax for that mode.
#+begin_src emacs-lisp (defun mrb/buffer-majormode (&optional buffer-or-name) "Returns the major-mode of the specified buffer, or the current buffer if no buffer is specified" (buffer-local-value 'major-mode (if buffer-or-name (get-buffer buffer-or-name) (current-buffer))))
(defun mrb/insert-mode-string() "Inserts a mode string for the current mode at beginning of the current buffer" (interactive) (let ((m (symbol-name (mrb/buffer-majormode)))) (save-excursion (goto-char (point-min)) (insert "-- mode:" (substring m 0 (string-match "-mode" m)) " --") (comment-region (point-min) (point))))) #+end_src
First bring in a bunch of modes that are used regularly, but have no other config than a map to some extensions. =:requires= is set to nil for those packages which aret built in.
#+begin_src emacs-lisp (use-package adoc-mode :mode ("\.asciidoc\'" "\.adoc\'")) (use-package apache-mode :mode "\.htaccess\'") (use-package conf-mode ) (use-package i3wm-config-mode) (use-package css-mode :mode ("\.css\'" "\.mss\'")) (use-package csv-mode :mode "\.csv\'") (use-package diff-mode :mode "\.patch\'") (use-package gnuplot-mode :mode "\.gp\'") (use-package js2-mode :mode "\.js\'") (use-package lua-mode :mode "\.lua\'") (use-package php-mode :mode "\.php\'") (use-package sass-mode :mode "\.sass\'") (use-package markdown-mode :mode "\.md\'") #+end_src
Now, do the other mode related packages which require a bit of configuration. #+begin_src emacs-lisp
(use-package eimp :hook (image-mode . eimp-mode))
(use-package rainbow-mode :hook (conf-mode css-mode))
;; Open scratch buffer by default in the mode we are in at the moment ;; with C-u prefix a mode will be asked to use (use-package scratch) ;; Don't throw away scratch buffer (use-package persistent-scratch :config (persistent-scratch-setup-default))
(use-package nxml-mode :straight (:type built-in) :hook ((nxml-mode . (lambda () (set-input-method nil))) (nxml-mode . turn-off-auto-fill)) :config (setq nxml-heading-element-name-regexp "\|.*" nxml-section-element-name-regexp "\|file\|.+")) #+end_src
- Org-mode Orgmode configuration is probably the largest part of my Emacs configuration, because most of the time I spent in Emacs, when not coding, is spent in org-mode. ** Initialization of Orgmode I loosely follow the GTD method for organizing things and I want a quick shortcut to start my main file.
#+name: gtd-starter #+begin_src emacs-lisp :tangle no (defun mrb/gtd() "Start my GTD system" (interactive) (find-file org-default-notes-file)) #+end_src
We do not have to load the main orgmode location, because we already did that on the main initialization to get org-babel started.
Also make sure we never load org from internal, this can happen when functions were defined in the included org version and not anymore in newer versions. We want an error, not a silent load of the older function.
#+COMMENT: This duplicates the one in init.el, problems? #+begin_src emacs-lisp (use-package org :after selected
:straight (org
:includes (org-agenda org-capture org-crypt org-datetree org-protocol))
:hook ((org-mode . turn-on-visual-line-mode)
(org-mode . turn-on-visual-fill-column-mode)
(org-indent-mode . (lambda () (diminish 'org-indent-mode)))
(org-agenda-mode . (lambda () (hl-line-mode 1))))
:init
<<gtd-starter>>
;; Keybindings specifically for actived regions in orgmode
<<org-mode-selected>>
:mode ("\\.txt\\'" "\\.org\'")
:bind (("C-c g" . 'mrb/gtd)
("C-c a" . 'org-agenda)
("C-c b" . 'org-switchb)
("C-s-s" . 'org-save-all-org-buffers)
("C-c l" . 'org-store-link) ; Is this not bound to C-c l by default
:map org-mode-map
("s-." . 'org-todo)
("s->" . 'mrb/org-cancel-todo)
("C-s-." . 'mrb/force-org-todo)
("C-." . 'org-schedule)
("M-p" . 'org-set-property)
:map org-agenda-mode-map
("s-." . 'org-agenda-todo)
("s->" . 'mrb/org-agenda-cancel-todo)
("C-s-." . 'mrb/force-org-agenda-todo)
("C-." . 'org-agenda-schedule)
("M-p" . 'org-set-property)
("C-x m" . 'mrb/construct-mail))
:config
<<orgmode-keywords>>
<<orgmode-generalconfig>>
;; Category icons I have configured elsewhere.
<<org-agenda-visuals>>
<<orgmode-itemactions>>
<<orgmode-scheduling>>
;; Allow for archiving and refiling in a date organized tree
(use-package org-datetree )
(use-package org-protocol
:config
;; If nothing is specified, create a TODO item
(setq org-protocol-default-template-key "t")))
#+end_src
Gather generic config variables for orgmode in one section
#+name: orgmode-generalconfig #+begin_src emacs-lisp :tangle no (setq org-directory "~/dat/org/" org-metadir (concat org-directory "_orgmeta/")
org-use-fast-todo-selection t
org-use-fast-tag-selection t
org-fast-tag-selection-single-key 'expert
org-enforce-todo-dependencies t ; we do want task dependencies
org-enforce-todo-checkbox-dependencies nil ; but relax checkbox constraints for it
;; We do not do priorities
org-enable-priority-commands nil
;; Agenda settings
org-agenda-files (concat org-directory ".agenda_files")
org-agenda-include-diary t
org-agenda-start-with-log-mode t
org-agenda-todo-ignore-scheduled "future"
org-agenda-ignore-properties '(effort appt category)
org-agenda-todo-ignore-scheduled 'future
org-agenda-log-mode-items '(closed clock state)
org-agenda-skip-deadline-prewarning-if-scheduled t
org-agenda-text-search-extra-files (cons 'agenda-archives (directory-files
(expand-file-name (concat org-metadir)) t ".+\\.org"))
;; Habits
org-habit-show-habits-only-for-today nil
;; Pressing enter on a link should activate it
org-return-follows-link t
org-support-shift-select 'always
;; Auto detect blank line need, this is the default, but I explicitly set thi
;; because it needs to be this or my capturing breaks due to org-capture popup
org-blank-before-new-entry '((heading . auto) (plain-list-item . auto))
org-export-htmlize-output-type 'css
;; Use auto-mode-alist for all file links/types
org-file-apps '((auto-mode . emacs))
org-fontify-done-headline t
org-goto-interface 'outline-path-completion
;; non nil is just direct children, what an ODD name!!!!
org-hierarchical-todo-statistics nil
org-provide-todo-statistics t
org-log-into-drawer t
org-log-redeadline 'note
org-log-reschedule 'time
org-modules '(org-info org-habit
org-inlinetask org-irc
org-toc org-mac-iCal org-mouse
org-tempo)
org-remember-default-headline ""
org-special-ctrl-a/e t
org-stuck-projects '("-inactive/TODO" ("TODO" "WAITING") nil "")
org-track-ordered-property-with-tag nil
org-startup-indented t
org-archive-location (concat org-metadir "archive.org::datetree/")
org-default-notes-file (concat org-directory "GTD.org")
diary-file (concat org-metadir "DIARY"))
#+end_src
** Capturing information I guess 90% of the information I keep in the main orgmode files starts life originally as a captured item. I use it for:
- TODO items;
- BUY items;
- Journal entries;
- Logbook entries;
- Weblinks
The =org-capture-pop-frame= package makes sure all captures are in separate frames.
#+begin_src emacs-lisp (use-package org-capture :diminish
;; Special config for handling tags while capturing
<<org-capture-taghandling>>
:config
;; Configuration of all the capture templates
<<org-capture-templates>>
;; and some shortcuts to run them
<<org-capture-runners>>
(setq org-capture-bookmark nil
bookmark-set-fringe-mark nil))
(use-package org-capture-pop-frame :after org-capture :init (setq ocpf-frame-parameters '((name . "capture") (width . 115) (height . 15) (tool-bar-lines . 0) (menu-bar-lines . 0) (internal-border-width . 5)))) #+end_src
Here are the templates used by org-capture. The /todo/ template is the most used, it is the same as the /link/ template, but does not include a reference to the current context, which is, in most cases, just annoying.
#+name: org-capture-templates #+begin_src emacs-lisp :tangle no (setq org-capture-templates '(("b" "Buy" entry (function mrb/capture-location) "* BUY %? :buy:\n%(mrb/prop-or-nothing :annotation)\n" :prepend t :empty-lines 1) ("l" "Link" entry (function mrb/capture-location) "* TODO %?\n%(mrb/prop-or-nothing :annotation)\n" :prepend t :empty-lines 1) ("t" "Todo" entry (function mrb/capture-location) "* TODO %?\n" :prepend t :empty-lines 1)))
(defun mrb/prop-or-nothing(prop)
"Insert `[:type]: [:property]` when :property has a value, otherwise nothing.
Meant to be used inside org-capture-templates
Example: `%(mrb/prop-or-nothing :annotation)`"
(let ((prop-value (plist-get org-store-link-plist prop))
(type (plist-get org-store-link-plist :type)))
(if (equal prop-value "")
""
(concat (when type (concat type ": ")) prop-value))))
(defun mrb/capture-location()
"This function is meant to be used inside org-capture-templates
to find a file and location where a captured ITEM should
be stored."
;; Open journal file without creating a journal entry This has the
;; side effect that it creates the file and moves TODO items over
;; on first call and leaves the cursor at the end of the file.
(org-journal-new-entry 1)
;; Find the id in this file and go there for the capture
(setq loc (org-find-entry-with-id "new-todo-receiver"))
(when loc
(goto-char loc)))
#+end_src
Define functions for each piece of information captured, so they can be easily bound to keys.
#+name: org-capture-runners #+begin_src emacs-lisp :tangle no :init (defun mrb/capture-todo () "Capture a TODO item" (interactive) (org-capture nil "t"))
(defun mrb/capture-buy () "Capture a BUY item" (interactive) (org-capture nil "b"))
(defun mrb/capture-link () "Capture a TODO item, but link to source when we can" (interactive) (org-capture nil "l")) #+end_src
These capture functions are called from shell scripts in the operating system and have a shortcut key assigned to them. The scripts are produced directly from this document, in a similar way as the main edit script was produced in [[Preparing for lift-off]]
#+begin_src sh :exports code :tangle ~/bin/capture-todo.sh :shebang #!/bin/sh edit-noframe --eval '(mrb/capture-todo)' #+end_src
#+begin_src sh :exports code :tangle ~/bin/capture-buy.sh :shebang #!/bin/sh edit-noframe --eval '(mrb/capture-buy)' #+end_src
By default =C-c C-c= ends the capture, but is normally the shortcut to enter tags, so I define a shortcut to define tags while capturing.
#+name: org-capture-taghandling #+begin_src emacs-lisp :tangle no :bind ((:map org-capture-mode-map ("C-c C-t" . mrb/add-tags-in-capture)))
:init (defun mrb/add-tags-in-capture() (interactive) "Insert tags in a capture window without losing the point" (save-excursion (org-back-to-heading) (org-set-tags-command))) #+end_src
** Workflow Orgmode used a couple of thing which enable you to steer the workflow for items. Item states are the most prominent ones. Org-mode uses keyword definitions to denote states on items. I keep an [[file:org-config.org][Orgmode configuration file]] (=org-config.org)= file which contains the description of the workflow in a format suitable to include directly into orgmode files. The configuration of emacs itself is limited to dressing up this configuration with things less suitable to go into that config file. The configuration here and the org config file should be kept in sync.
Adapt the colors of the states I use a bit:
#+name: orgmode-keywords #+begin_src emacs-lisp :tangle no ;; Define face specs for our keywords, so they can be used in the ;; theme adjustment like standard faces (defface mrb/org-todo-keyword-TODO nil "") (defface mrb/org-todo-keyword-DONE nil "") (defface mrb/org-todo-keyword-WAITING nil "") (defface mrb/org-todo-keyword-CANCELLED nil "") (defface mrb/org-todo-keyword-BUY nil "") (defface mrb/org-todo-keyword-HOWTO nil "") (defface mrb/org-todo-keyword-INFO nil "") (defface mrb/org-todo-keyword-COLLECT nil "") (defface mrb/org-todo-keyword-SOLVE nil "") (defface mrb/org-todo-keyword-READ nil "") (defface mrb/org-todo-keyword-READING nil "") (defface mrb/org-todo-keyword-PLAN nil "")
(setq org-todo-keyword-faces `( ("TODO" . mrb/org-todo-keyword-TODO) ("DONE" . mrb/org-todo-keyword-DONE) ("WAITING" . mrb/org-todo-keyword-WAITING) ("CANCELLED" . mrb/org-todo-keyword-CANCELLED) ("BUY" . mrb/org-todo-keyword-BUY) ("HOWTO" . mrb/org-todo-keyword-HOWTO) ("INFO" . mrb/org-todo-keyword-INFO) ("COLLECT" . mrb/org-todo-keyword-COLLECT) ("SOLVE" . mrb/org-todo-keyword-SOLVE) ("READ" . mrb/org-todo-keyword-READ) ("READING" . mrb/org-todo-keyword-READING) ("PLAN" . mrb/org-todo-keyword-PLAN))) #+end_src
Make sure we keep a clean tag slate when changing tag state. This means that when I move to an active state, remove inactive tags; if something is DONE, remove tags from it and automatically adding a 'buy' tag when a BUY item is created. Note: capturing does not honor this, i.e. when creating a new item.
#+begin_src emacs-lisp (setq org-todo-state-tags-triggers '(('todo ("inactive")) ; remove inactive tags if moved to any active state ('done ("inactive") ("fork")) ; remove tags from any inactive state ("BUY" ("buy" . t)))) ; add buy tag when this is a buying action #+end_src
To keep the TODO list clean we immediately archive the completed entry in the archive. The archiving only occurs when an item enters the 'DONE' state and the item is not marked as a habit.
I'm not sure if this works out in practice without having a confirmation (because we archive the whole sub-tree), so for now, I'm building in the confirmation.
#+begin_src emacs-lisp ;; I need a modified version of org-is-habit, which takes inheritance ;; in to account (defun mrb/org-is-habit-p (&optional pom) "org-is-habit-p taking property inheritance into account" (equalp "habit" (org-with-point-at (or pom (point)) (org-entry-get-with-inheritance "STYLE"))))
(defun mrb/archive-done-item() "Determine if the item went to the DONE/CANCELLED state if so, ask to archive it, but skip habits which have their own logic." (when (not (mrb/org-is-habit-p)) ;; No habit, so we have a candidate (progn ;; Try to use a dialog box to ask for confirmation (setq last-nonmenu-event nil)
;; When a note is going to be added, postpone that Otherwise just
;; run the archiving question
;; FIXME: org-add-note runs through post-command-hook,
;; which is kinda weird, how to i get it to run
;; before the archiving question?
(when (equal org-state "DONE")
(org-archive-subtree-default-with-confirmation)))))
;; Run archive for the item that changed state (add-hook 'org-after-todo-state-change-hook 'mrb/archive-done-item t) #+end_src
In addition to the above I'm experimenting with =org-edna= (Extensible Dependencies 'N' Actions) which has a bit more flexibility. Notably the blocking of tasks is what I want to peruse a bit, because the builtin system hasn't worked for me. I may still use the builtin way to block items, but perhaps under the dynamic control of org-edna instead of manually specifying =order= properties etc.
For now, a basic config and enabling of =org-edna=
#+begin_src emacs-lisp (use-package org-edna :diminish :after org :config (org-edna-mode)) #+end_src ** Marking items as DONE Marking work as completed should be a smooth process to stop getting in the way of doing the actual work. A shortcut is defined to mark items done in the standard way and have an additional shortcut to mark it done should it be blocked.
When an item changes to the DONE state, a question is asked if the item should be archived, to which the normal answer should be 'Yes' to keep the active file as clean as possible.
#+name: orgmode-itemactions #+begin_src emacs-lisp :tangle no ;; Normal flow, but ignore blocking (defun mrb/force-org-todo() (interactive) ;; Disable blocking temporarily (let ((org-inhibit-blocking t)) (org-todo)))
;; Normal flow, but ignore blocking, agenda scope (defun mrb/force-org-agenda-todo() (interactive) ;; Disable blocking temporally (let ((org-inhibit-blocking t)) (org-agenda-todo)))
;; Break flow, cancel directly (defun mrb/org-cancel-todo () (interactive) (org-todo "CANCELLED"))
;; Break flow, cancel directly, agenda scope (defun mrb/org-agenda-cancel-todo () (interactive) (org-agenda-todo "CANCELLED")) #+end_src
The above may be influenced, or even made redundant, by the =org-edna= package configuration. ** Registering creation time of todo items Over time it gets a bit messy in my orgmode files. I can not remember when something was created and thus, by judging the time I didn't do anything with the item, decide if it is still important or not.
So, to help with that I created a little glue to make sure each actionable item gets a =CREATED= property with the date in it on which that item was created. I use the contributed =org-expiry= for that and adjust it a bit.
I want the property to be name 'CREATED' (I don't remember what the org-expiry default name is, but it is different) and the timestamps inserted must not be active, otherwise they'll appear all over the place in the agenda.
#+begin_src emacs-lisp (use-package org-contrib) (use-package org-expiry :init (setq org-expiry-created-property-name "CREATED" org-expiry-inactive-timestamps t)
:config
<<org-expiry-createdtimestampfunction>>
<<org-expiry-createdtimestampusage>>
)
#+end_src
So, to create the timestamp I need a little helper function which actually inserts it, using org-expiry. There is some additional cursor munging to make sure it is used comfortably during editing.
#+name: org-expiry-createdtimestampfunction #+begin_src emacs-lisp :tangle no (defun mrb/insert-created-timestamp() "Insert a CREATED property using org-expiry.el for TODO entries" (org-expiry-insert-created) (org-back-to-heading) (org-end-of-line)) #+end_src
Now that function is used to insert the property when:
- creating a TODO heading, using an advice to =insert-todo-heading=
- capturing an item, but only when it is a TODO item (i.e. has a defined keyword)
#+name: org-expiry-createdtimestampusage #+begin_src emacs-lisp :tangle no (defadvice org-insert-todo-heading (after mrb/created-timestamp-advice activate) "Insert a CREATED property using org-expiry.el for TODO entries" (mrb/insert-created-timestamp))
(ad-activate 'org-insert-todo-heading)
(defadvice org-capture (after mrb/created-timestamp-advice activate) "Insert a CREATED property using org-expiry.el for TODO entries" (when (member (org-get-todo-state) org-todo-keywords-1) (mrb/insert-created-timestamp))) (ad-activate 'org-capture) ;;(ad-deactivate 'org-capture) #+end_src
Related to the above, with some regularity I want to record timestamps, for example for documenting longer during tasks and recording incremental progress or information on them. Orgmode provides the binding '=C-c !=' for this which inserts an inactive date-stamp, optionally including the time as well.
Lastly, there's a mechanism in emacs which can automatically insert time-stamps, based on a placeholder in the file and a format variable. I sometimes use that, so I add it to my before save hook. Typically, the format string is file specific and will be held in a file local variable.
#+begin_src emacs-lisp (add-hook 'before-save-hook 'time-stamp) #+end_src
** Scheduling items Orgmode has a number of provisions to schedule items, either explicitly by setting the SCHEDULE property, inferring a deadline by setting the DEADLINE property, thus scheduling the task in an interval before the deadline expires.
The main key for scheduling will be =C-.= (Control key with dot). A todo is marked to the next state with =M-.= so this makes sense, at least to me. In plain org and in org-agenda mode this key is used most often, but I expect this to be useful in other modes as well. I will try to u use the same keybinding in those modes as well.
I had /schedule-for-today/ functions earlier, but those really just save me from pressing return in a normal =org-schedule= function, so their added value was minimal and I have since deleted them.
** Visual settings Having an attractive screen to look at becomes more important if you use the system all day long. /Attractive/ is rather subjective here. For me it mainly consists of functional things. Anyways, this section groups settings for the visual characteristics of orgmode.
I want to hide the leading stars in the out-liner, and do it exactly in the background color. This is redundant actually in my case, as it is also specified in the org config file that I include. Or rather, it is redundant there, because I want it always to be the case.
#+begin_src emacs-lisp (setq org-hide-leading-stars t) #+end_src
For the collapsed items in the outline orgmode uses the variable =org-ellipsis= to determine what character-sequence should be used to show that the item can be expanded. The variable can contain a string, which will then be used instead of the standard 3 dots, or a face which will then be used to render the standard 3 dots.
#+begin_src emacs-lisp (setq org-ellipsis "…") #+end_src
There are a couple of ways within org to emphasize text inline for bold, /italics/, underlined etc. These are set in the text by enclosing regions with delimiters. I do not want to see these delimiters, but rather render the text. The org-appear package makes this even fancier by showing the characters when inside the marked area, so editing is a lot easier.
#+begin_src emacs-lisp (use-package org-appear :after org :hook (org-mode . org-appear-mode) :config (setq org-hide-emphasis-markers t) ; Is this used by org-appear mode? ) #+end_src
Similar to inline emphasis is the /rewriting/ with pretty entity characters (like '\delta' for example). These characters can be added to the text by adding a '' before a symbol name ('delta' in the example). I make an exception for the sub- and superscript characters. This happens a lot in variable names etc. and I a big annoyance if those get rendered to subscript all the time.
#+begin_src emacs-lisp (setq org-pretty-entities 1) (setq org-pretty-entities-include-sub-superscripts nil) #+end_src
Related to that is the display of links. I want them to be explicit most of the time to avoid confusion, but the 'fancy' display is easier at first:
#+begin_src emacs-lisp (setq org-descriptive-links t) #+end_src
For most of the source blocks I want Emacs to render those blocks in their native mode. This had a serious performance problem in the past, but I think it has been solved recently.
#+begin_src emacs-lisp (setq org-src-fontify-natively t) #+end_src
For the headings at each level a =*= is normally used. As we're in unicode world now, we can do a bit better.
#+begin_src emacs-lisp (use-package org-bullets :hook (org-mode . (lambda () (org-bullets-mode 1)))) #+end_src
The item lists can be made a whole lot more attractive by attaching some icons based on the category an items belongs to. The category assignment itself is done by setting the =CATEGORY= property explicitly on the item or on the file.
#+name: org-agenda-visuals #+begin_src emacs-lisp :tangle no (setq org-agenda-category-icon-alist `( ("Afspraak" ,(concat org-directory "images/stock_new-meeting.png") nil nil :ascent center) ("Blogging" ,(concat org-directory "images/edit.png") nil nil :ascent center) ("Car" ,(concat org-directory "images/car.png") nil nil :ascent center) ("Cobra" ,(concat org-directory "images/car.png") nil nil :ascent center) ("DVD" ,(concat org-directory "images/media-cdrom.png") nil nil :ascent center) ("Emacs" ,(concat org-directory "images/emacs.png") nil nil :ascent center) ("Finance" ,(concat org-directory "images/finance.png") nil nil :ascent center) ("Habitat" ,(concat org-directory "images/house.png") nil nil :ascent center) ("Habit" ,(concat org-directory "images/stock_task-recurring.png") nil nil :ascent center) ("Hobbies" ,(concat org-directory "images/hobbies.png") nil nil :ascent center) ("Partners" ,(concat org-directory "images/partners.png") nil nil :ascent center) ("Personal" ,(concat org-directory "images/personal.png") nil nil :ascent center) ("Task" ,(concat org-directory "images/stock_todo.png") nil nil :ascent center) ("Org" ,(concat org-directory "images/org-mode-unicorn.png") nil nil :ascent center) ("Systeem" ,(concat org-directory "images/systeembeheer.png") nil nil :ascent center) ("Wordpress" ,(concat org-directory "images/wordpress.png") nil nil :ascent center) )) #+end_src
Showing items in the agenda views reacts to a number of settings. In my setup I want blocked tasks hidden, that is the reason for blocking. Hide tasks which are DONE already and a deadline is coming up, no use showing those; the same goes for tasks which are DONE and are scheduled. In short, anything that does not need my attention needs to be hidden.
#+begin_src emacs-lisp (setq org-agenda-dim-blocked-tasks t org-agenda-skip-deadline-if-done t org-agenda-skip-scheduled-if-done t org-agenda-skip-archived-trees nil ) #+end_src
After experimenting with automatic aligning of tags I've come to the conclusion I want to have it manual. For now, I set the tags to align flush right with column 110 but ideally I'd want something more dynamic (in the sense that visual-line mode is also dynamic)
#+begin_src emacs-lisp (setq org-tags-column -95) (defun mrb/org-align-all-tags () "Wrap org-align-tags to be interactive and apply to all" (interactive) (org-align-tags t)) (bind-key "M-"" 'mrb/org-align-all-tags) #+end_src ** Agenda customization Settings which are just applicable for the org-mode agenda view.
#+begin_src emacs-lisp ;; Show properties in agenda view (use-package org-agenda-property :config (setq org-agenda-property-list '("LOCATION" "Responsible")))
;; When done rendering, go to the top (add-hook 'org-agenda-finalize-hook #'(lambda () (goto-char (point-min))) 100) #+end_src
A snippet which resized the agenda to its window whenever its rebuilt by orgmode The 'auto value of the =org-agenda-tags-column= variable right aligns the tags in the agenda view
#+begin_src emacs-lisp (setq org-agenda-tags-column 'auto) (defadvice org-agenda-redo (after fit-windows-for-agenda activate) "Fit the Org Agenda to its buffer." (fit-window-to-buffer) (goto-char (point-min))) #+end_src *** Calendaring Traditionally somewhat related to mail, although mostly through orgmode these days, I want a simple setup to pull in some calendaring information. I run a radicale calendar server somewhere, but having the same info in orgmode makes sense for me.
I have used =org-caldav= but even in just a read only setup it does not work properly. I'm using =ical2orgpy= which is a python script that takes =.ics= input and creates an org-mode file.
I combine =ical2orgpy= with a cron job which pulls in the calendars I am interested into. Adding the produced org-mode files to the =org-agenda-files= variable is enough to get the information into the org-mode agenda.
#+begin_src sh :exports code :tangle ~/bin/ical2org :shebang #!/bin/sh
<>
manually test this script with env -i /bin/sh
Mostly assume nothing is implied, like paths etc.
BASEDIR=/home/mrb/dat/org/_calendars ICAL2ORGPY="/home/mrb/.local/bin/ical2orgpy"
wget uses .netrc to gett auth info
WGET="/usr/bin/wget -O wget.log -O - --quiet"
Base URI for calendar access, perhaps also put in the datafile?
CALBASE="https://calendars.hsdev.com/mrb"
Just go over each one manually
FIXME make this nice with url . file pairs in a loop
cd $BASEDIR
Read destination org file and the calendar id from a file .calendars
while IFS=' ' read -r orgfile calendar_id do $WGET "$CALBASE/$calendar_id" 2>/dev/null | $ICAL2ORGPY - $orgfile done < .calendars #+end_src
** Searching for items Over the years the amount of information I manage with orgmode has grown considerably, ranging from the plain TODO lists, blog posts, calendar information, project files etc. Increasingly I find myself not being able to find things in that pile of information. The solution in [[Grepping files]] helps a lot already but is a generic solution.
To have a more structured solution for org type content, I'm using =org-ql=
#+begin_src emacs-lisp ;; (use-package org-ql) ; Postponing, has wrong repository reference for peg.el #+end_src
One thing I use this for is in my habits file to predefine a search for the relevant items for the habit. ** Babel / Literate programming Specific settings for babel and literate programming within org-mode
#+begin_src emacs-lisp (setq org-babel-load-languages '((awk . t) (calc . t) (css . t) (ditaa . t) (emacs-lisp . t) (gnuplot . t) (haskell . t) (js . t) (lisp . t) (org . t) (plantuml . t) (python . t) (scheme . t) (shell . t) (sql . t)))
;; Activate Babel languages (org-babel-do-load-languages 'org-babel-load-languages org-babel-load-languages) #+end_src
The elements of org babel are blocks of source code. Some of the modes for sources have some helper programs.
Plantuml transforms diagram specifications with a /mini programming language/ into diagrams. It has support for many types of diagram. Ditaa transforms ascii diagrams into graphics.
#+begin_src emacs-lisp (use-package plantuml-mode :after org ; strictly not needed, but i use it mainly from org :config (setq plantuml-jar-path "/usr/share/java/plantuml/plantuml.jar" org-plantuml-jar-path plantuml-jar-path plantuml-default-exec-mode 'jar))
(setq org-ditaa-jar-path "/usr/share/java/ditaa/ditaa-0.11.jar") #+end_src
** Refiling A big part of organizing information and task is shuffling things around. The 'thing' to throw around is a heading and 'refiling' is the term org-mode uses for throwing.
When filing, or capturing we want the items at the bottom of what we are filing it into. The main reason for this is that a large part of the sections that contain items are ordered. Should we file the item at the top, in many cases that would mean it is the most imminent thing to do, which is not the case.
#+begin_src emacs-lisp (setq org-reverse-note-order nil ; File at the bottom of an entry org-refile-allow-creating-parent-nodes 'confirm org-refile-use-outline-path 'file org-outline-path-complete-in-steps nil org-refile-use-cache t) #+end_src
The base list of files I want to be able to refile to are:
- The /active/ file list, i.e. everything in =org-agenda-files=, regardless of these files are currently opened or not;
- All open orgmode based files which, surprisingly, I need to generate myself?
#+begin_src emacs-lisp (setq org-refile-targets '((org-agenda-files :maxlevel . 10)))
;; Borrows from https://yiming.dev/blog/2018/03/02/my-org-refile-workflow/ (defun mrb/org-opened-buffers () "Return the list of org files opened in emacs"
(defun buffer-mode (buf)
(buffer-local-value 'major-mode (get-buffer buf)))
(delq nil
(mapcar (lambda (x)
(if (and (buffer-file-name x) ;-buffer has a file?
(provided-mode-derived-p
(buffer-mode x) 'org-mode))
(buffer-file-name x)))
(buffer-list))))
;; Adjust refile target to include all opened org files
;; Is it bad that we have duplicates here?
(add-to-list 'org-refile-targets
'(mrb/org-opened-buffers :maxlevel . 10))
#+end_src
The type of headers to refile to is, in the default orgmode config, basically everything. I limit it above to a maximum depth of 10, but then still. By using =org-refile-target-verify-function= we can fine-tune the decision whether to use a header as target or not. The following conditions have been implemented:
- The header must /not/ be a DONE item;
- The header needs to have children.
The latter is the most important one and will prevent creating 'gathering' items to tasks themselves.
#+begin_src emacs-lisp (defun mrb/has-DONE-keyword() "Return t when the heading at point has a `DONE' like keyword" (member (nth 2 (org-heading-components)) org-done-keywords))
(defun mrb/verify-refile-target() "Decide if a target header can be used as a refile target Conditions to return t:
-
header must not have one of the DONE keywords
-
it must be a parent of something already" ;; interactive during testing (interactive)
(and ; exclude done keyword headers (not (mrb/has-DONE-keyword)) ; must have a child (save-excursion (org-goto-first-child))))
(setq org-refile-target-verify-function 'mrb/verify-refile-target) #+end_src ** Exporting to other formats Orgmode can export to a variety of formats, I mainly use LaTeX (PDF) and HTML as destination format
#+begin_src emacs-lisp ;; {% raw %} (setq org-export-latex-hyperref-format "\ref{%s}:{%s}" ;; old system org-export-latex-title-command " " ;; new system > 8.0 org-latex-title-command " "
org-export-docbook-xsl-fo-proc-command "fop %i %o"
org-export-docbook-xslt-proc-command "xsltproc --output %o %s %i"
org-export-htmlize-output-type 'css
org-org-htmlized-css-url "orgmode.css"
org-latex-listings 'minted
;; Define a default background color name, this needs to be set through
;; a latex header
org-latex-minted-options '(("bgcolor" "codebg"))
org-export-copy-to-kill-ring 'if-interactive
org-export-htmlized-org-css-url "orgmode.css"
org-export-latex-classes
'(
("article" "\\documentclass[11pt,a4paper,twoside]{article}"
("\\section{%s}" . "\\section*{%s}")
("\\subsection{%s}" . "\\subsection*{%s}")
("\\subsubsection{%s}" . "\\subsubsection*{%s}")
("\\paragraph{%s}" . "\\paragraph*{%s}")
("\\subparagraph{%s}" . "\\subparagraph*{%s}"))
("report" "\\documentclass[11pt]{report}"
("\\part{%s}" . "\\part*{%s}")
("\\chapter{%s}" . "\\chapter*{%s}")
("\\section{%s}" . "\\section*{%s}")
("\\subsection{%s}" . "\\subsection*{%s}")
("\\subsubsection{%s}" . "\\subsubsection*{%s}"))
("book" "\\documentclass[11pt]{book}"
("\\part{%s}" . "\\part*{%s}")
("\\chapter{%s}" . "\\chapter*{%s}")
("\\section{%s}" . "\\section*{%s}")
("\\subsection{%s}" . "\\subsection*{%s}")
("\\subsubsection{%s}" . "\\subsubsection*{%s}"))
("beamer" "\\documentclass{beamer}" org-beamer-sectioning))
org-export-with-tags nil
org-export-with-todo-keywords nil
org-export-backends '(ascii html icalendar latex md odt org texinfo)
)
;; {% endraw %} #+end_src
** Journaling While the standard capture method is useful I found myself not using it very much. Not sure why. It turns out that org-journal is a much better fit for my workflow, especially using the carry-over functionality.
#+begin_src emacs-lisp (use-package org-journal :after (org selected) :commands (org-journal-new-entry) :hook ((org-journal-mode . turn-on-visual-line-mode) (org-journal-mode . turn-on-visual-fill-column-mode)) :bind (("C-c j" . org-journal-new-entry) ("C-s-j" . (lambda () (interactive) (org-journal-new-entry 1))))
;; Keybindings for active regions in org-journal
<<org-journal-mode-selected>>
:init
;; The expand-file-name is needed, which is odd, because for single
;; files this is not needed.
(setq org-journal-dir (expand-file-name (concat org-directory "journal/"))
;; Bring our config to every journal file
org-journal-file-header (concat "#+SETUPFILE: " user-emacs-directory "org-config.org")
;; Match the journal files (FIXME: make this independent of earlier assignments)
org-agenda-file-regexp "^[0-9]+\\.org"
org-journal-file-format "%Y%m%d.org"
;; Put day on top of file, uses `org-journal-date-format`
org-journal-date-format "%A, %e-%m-%Y"
;; Put day on top of file, uses `org-journal-date-format`
org-journal-date-prefix "#+TITLE: "
;; New entries go at the bottom, make sure we are at top level
org-journal-time-format "[%R] "
org-journal-time-prefix "* "
;; Carry over TODO items and items explicitly marked
org-journal-carryover-items "+carryover|+TODO=\"TODO\""
;; and for this to work, we need agenda integration
org-journal-enable-agenda-integration t
;; Remove empty journals after carryover
org-journal-carryover-delete-empty-journal 'always
;; I want to have encryption, but how do TODO items bubble up then in the agenda
org-journal-enable-encryption nil
org-crypt-disable-auto-save t
org-journal-enable-cache t))
#+end_src
Particularly in journaling, but also in org-mode in general, I want to be able to quickly insert screenshots. Rather, images in general but 90% of my use-case is really screenshots.
There's a package =org-attach-screenshot= which matches my use-case 99% so let's use that and worry about extending it to images later on.
#+begin_src emacs-lisp (use-package org-attach-screenshot :bind (("C-c i" . org-attach-screenshot))) #+end_src
The 1% I was referring to above is that the original package exclusively supports org-mode. I patched it, which was trivial to support org-mode and all its derived modes. (org-journal in my case)
** Publishing There's plenty ways to publish orgmode content on the web. I use a couple of them sparingly. For most of them I don't need a permanent configuration. For the ones that I do need a config, there's this section.
*** Writefreely Writefreely is a Write.as derivative and published as open source. This allows me to just kick out a quick orgmode file with a =#+TITLE:= and a =#+DATE:= header and throw it on a blog like site rather quickly.
#+begin_src emacs-lisp ;; Still needed: ;; - post as draft by default, this prevents automatic posting by accident for federated collections ;; - save augment -> publish / update automatically, probably a custom hook on minor mode? ;; - wf: have autorefresh GET parameter which monitors updates and ;; refreshes automatically ;; - set local save dir for the files ;; Make sure the variables of THIS file are loaded before THIS file is loaded (use-package writefreely :after org :config (setq ;; Set api endpoints for my own install writefreely-instance-url "https://qua.name" writefreely-instance-api-endpoint "https://qua.name/api" writefreely-maybe-publish-created-date t writefreely-auth-token (password-store-get "Qua.name/accesstoken"))) #+end_src ** Encrypting information in org-mode I use the /encrypt/ tag for encrypting sections in org-mode (and sometimes my journal). The sections get encrypted and decrypted automatically on saving and opening. This uses the EasyPG library to get to my GPG key.
#+begin_src emacs-lisp (use-package org-crypt
:config
(org-crypt-use-before-save-magic)
(setq org-crypt-tag-matcher "encrypt")
(setq org-crypt-key user-gpg-encrypt-key))
#+end_src
We do not want to inherit this tag automatically, as its behavior is already subsection inclusive. When you encrypt a section, everything below it is considered content of that section and gets encrypted. I also add the value "crypt" as that is the org default, so it won't be inherited by mistake.
#+begin_src emacs-lisp (add-to-list 'org-tags-exclude-from-inheritance '"encrypt") (add-to-list 'org-tags-exclude-from-inheritance '"crypt") #+end_src
** Old configuration Below is what was contained in the old configuration. I will slowly migrate this into more literal sections
#+begin_src emacs-lisp ;; Bit of a leftover from reorganizing bits, do this later (add-to-list 'org-tags-exclude-from-inheritance '"sell")
(defun mrb/is-project-p () "This function returns true if the entry is considered a project. A project is defined to be: - having a TODO keyword itself (why was this again?); - having at least one todo entry, regardless of their state." (let ((has-todokeyword) (has-subtask) (subtree-end (save-excursion (org-end-of-subtree t))) (is-a-task (member (nth 2 (org-heading-components)) org-todo-keywords-1))) (save-excursion (forward-line 1) (while (and (not has-subtask) (< (point) subtree-end) (re-search-forward "^*+ " subtree-end t)) (when (member (org-get-todo-state) org-todo-keywords-1) (setq has-subtask t)))) ;; both subtasks and a keyword on the container need to be present. (and is-a-task has-subtask)))
;; FIXME: testing for tag presence should be easier than a re-search forward ;; FIXME: are we not searching for all 'incomplete' type keywords here?, ;; there must be an org function for that (defun mrb/skip-non-stuck-projects () "Skip trees that are not stuck projects" (let* ((subtree-end (save-excursion (org-end-of-subtree t))) (has-next (save-excursion (forward-line 1) (and (< (point) subtree-end) (re-search-forward "^*+ \(TODO\|BUY\|WAITING\)" subtree-end t))))) (if (and (mrb/is-project-p) (not has-next)) nil ; a stuck project, has subtasks but no next task subtree-end)))
(defun mrb/skip-non-projects () "Skip trees that are not projects" (let* ((subtree-end (save-excursion (org-end-of-subtree t)))) (if (mrb/is-project-p) nil subtree-end)))
(defun mrb/skip-projects () "Skip trees that are projects" (let* ((subtree-end (save-excursion (org-end-of-subtree t)))) (if (mrb/is-project-p) subtree-end nil)))
;; Remove empty property drawers (defun mrb/org-remove-empty-propert-drawers () "*Remove all empty property drawers in current file." (interactive) (unless (eq major-mode 'org-mode) (error "You need to turn on Org mode for this function.")) (save-excursion (goto-char (point-min)) (while (re-search-forward ":PROPERTIES:" nil t) (save-excursion (org-remove-empty-drawer-at "PROPERTIES" (match-beginning 0))))))
(defun mrb/org-remove-redundant-tags () "Remove redundant tags of headlines in current buffer.
A tag is considered redundant if it is local to a headline and inherited by a parent headline." (interactive) (when (eq major-mode 'org-mode) (save-excursion (org-map-entries '(lambda () (let ((alltags (split-string (or (org-entry-get (point) "ALLTAGS") "") ":")) local inherited tag) (dolist (tag alltags) (if (get-text-property 0 'inherited tag) (push tag inherited) (push tag local))) (dolist (tag local) (if (member tag inherited) (org-toggle-tag tag 'off))))) t nil))))
(defvar org-agenda-group-by-property nil "Set this in org-mode agenda views to group tasks by property")
(defun mrb/org-group-bucket-items (prop items) (let ((buckets ())) (dolist (item items) (let* ((marker (get-text-property 0 'org-marker item)) (pvalue (org-entry-get marker prop t)) (cell (assoc pvalue buckets))) (if cell (setcdr cell (cons item (rest cell))) (setq buckets (cons (cons pvalue (list item)) buckets))))) (setq buckets (mapcar (lambda (bucket) (cons (first bucket) (reverse (rest bucket)))) buckets)) (sort buckets (lambda (i1 i2) (string< (first i1) (first i2))))))
(defadvice org-agenda-finalize-entries (around org-group-agenda-finalize
(list &optional nosort))
"Prepare bucketed agenda entry lists"
(if org-agenda-group-by-property
;; bucketed, handle appropriately
(let ((text ""))
(dolist (bucket (mrb/org-group-bucket-items
org-agenda-group-by-property
list))
(let ((header (concat "Property "
org-agenda-group-by-property
" is "
(or (first bucket) "
(defvar mrb/org-my-archive-expiry-days 365 "The number of days after which a completed task should be auto-archived. This can be 0 for immediate, or a floating point value.")
(defun mrb/org-my-archive-done-tasks () (interactive) (save-excursion (goto-char (point-min)) (let ((done-regexp (concat "\* \(" (regexp-opt org-done-keywords) "\) ")) (state-regexp (concat "- State "\(" (regexp-opt org-done-keywords) "\)"\s-\[\([^]\n]+\)\]"))) (while (re-search-forward done-regexp nil t) (let ((end (save-excursion (outline-next-heading) (point))) begin) (goto-char (line-beginning-position)) (setq begin (point)) (when (re-search-forward state-regexp end t) (let ((time-string (match-string 2)) (when-closed (org-parse-time-string time-string))) (if (>= (time-to-number-of-days (time-subtract (current-time) (apply #'encode-time when-closed))) mrb/org-my-archive-expiry-days) (org-archive-subtree)))))))))
(defalias 'archive-done-tasks 'mrb/org-my-archive-done-tasks) #+end_src
- Key and mouse bindings Keyboard bindings are the primary way to interact for me. I have been struggling with consistent keyboard shortcuts and how to integrate them with the other systems on my machine which capture shortcut keys. At this time the following applications capture shortcut keys:
- the awesome window manager captures keys;
- xbindkeys provides a number of key bindings for application dependent operations;
- emacs (and obviously all other applications, but those are largely irrelevant).
- the X-windows server has the kbd extension which has some keyboard related things to configure.
- The linux kernel provides key mapping, so I have to look at that place too (xmodmap)
Because I am daft, here is the notation for the modifiers:
- C - :: control
- s - :: super, meaning the (left) windows key in my configuration
- M - :: meta, meaning the (left) alt key in my configuration
- S - :: shift
To help me out with this when writing about key bindings, the lisp function =key-description= can help out, with a little bit of glue around it:
#+begin_src emacs-lisp (defun mrb/insert-key-description () "Insert a pretty printed representation of a key sequence" (interactive) (insert (key-description (read-key-sequence "Type a key sequence:")))) #+end_src I like the explicit notation where the name of the key is spelled out better, and I'll move all configured keybindings to that eventually.
The right alt and the right
- Terminals, Character encodings and emulation My policy is to use Emacs for as many things and use as little other programs as necessary. All this within reason obviously.
This sections describes how terminals and shells are used from within Emacs. In an ideal situation I won't be needing any other terminal emulator program, other than the ones used directly from Emacs.
** vterm While eshell is the more emacsy solution, it has some limitations which makes me reach for xterm for daily work. However, there's still a good option to benefit from all the emacs configuration in that case and that is the =vterm= package. This is an interface to the =libvterm= library creating a real terminal emulation using emacs for display.
The basic install is easy, but does need an emacs compiled with module support.
#+begin_src emacs-lisp (use-package vterm :if (fboundp'module-load) :config (setq vterm-timer-delay 0)) #+end_src
To benefit from some of the most useful features, there is a little bit of shell configuration involved. The package includes a shell script for that which I am sourcing in my =.zshrc= configuration file.
The =vterm= brings me some advantages, even close to being able to replace xterm for daily use, for example:
- magit is now effectively a terminal package everywhere available
- a terminal is an emacs buffer, so it's easy to have it available anywhere in emacs
- most emacs commands will just work in a predictable way.
The most important downside is that if emacs does weird things, like hang or crash, this will affect all my terminals too, which is unworkable in some situations, so xterm is not going anywhere soon (but eshell is out I think)
** Process handling Sometimes processes get stuck and i want a way to delete those processes easily.
#+begin_src emacs-lisp (defun mrb/delete-process-interactive () "Based on an auto-completed list of process, choose one process to kill" (interactive) (let ((pname (completing-read "Process Name: " (mapcar 'process-name (process-list))))) (delete-process (get-process pname)))) #+end_src
- Completion
There are 2 types of completion:
- Input completion in the minibuffer
- Inline completion in another buffer
I want completion to work as follows:
- completion functions are always bound to a keybinding involving the TAB-key, with as little modifiers as possible;
- completion should always produce something, even if emacs has no special semantic knowledge of the current mode, it should produce /something/ which makes sense;
- completion should be inline whenever possible.
- for each mode, a specialization is ok, if that improves the situation; I expect to have many specializations to improve the auto-complete quality;
- if a completion window must be opened, do this at the same place always and do not mess up other windows.
- Completion should behave somewhat like real-time systems. An answer must be produced within a certain amount of time. If a completion answer takes longer than the amount of type to type it in full, the system has collapsed, so the time needs to be in the order of one third of the typing time.
The next sections deal with the above requirements ** Ad 1. Bind completion always involves TAB-key The package =smart-tab= seems to fit this bill, but the thing that I care about can be achieved fine without it (I only found this out after considerable time using smart-tab).
So, tab tries to indent, which is the main expectation, and if it can't it tries to complete stuff.
#+begin_src emacs-lisp (setq tab-always-indent 'complete) #+end_src
In a standard emacs installation, TAB indents, depending on mode obviously. If indenting would not make sense, a TAB can be inserted or completion could start. The function =completion-at-point= is used in some situations. Ideally the =company-complete= function could take over in many cases. Here's a simplistic approach to get me started:
- if in minibuffer, do completion there like we are used to;
- if cursor is at the end of a symbol, try to complete it with company;
- else, indent according to mode.
This is probably incomplete or wrong even in some cases, but it's a start.
This way, TAB always does completion or indent, unless company-mode is not active.
** Ad 2. Completion should always produce something Not sure if there is anything to do here. ** Ad 3. Inline completion when possible With inline completion I mean without opening a whole new Completions window if not needed.
Content that I want:
- languages: lisp, python, ruby, bash, C/C++ roughly in that order (function and argument completion)
- for all languages, function/method signature shorthands
- speed, slowness is killing here
- prevent minibuffer distractions, put info where my eyes are and that is the cursor in most cases.
- maybe: spelling suggestions
- nick completion in irc channels
Candidates:
- auto-complete :: http://cx4a.org/software/auto-complete/
- company-mode :: http://company-mode.github.io
I had auto-complete installed for a while and this worked fine. I am migrating to company-mode now, as it seems a lot faster and a lot easier to write back-ends for. Also, company mode gets more praise from emacs users, but I recall having problems with it. Anyway, let's enable company-mode everywhere for everything except for some modes which we know lead to problems.
The capf back-end uses 'completion-at-point-functions' as source for candidates, which is what emacs by default does, so I want that in my back-end-lists. This back-end gets added automatically when emacs version is larger than 24.3.50. I'm not sure what the logic is behind that, as there is no mention of it not working before that version? Assuming that there is a good reason, I'm not going to do anything to the backhands list for now.
The default colors of =company-mode= are horrendous, but I have put some corrections by executing a snippet, based on standard color-names. The snippet is here: http://www.emacswiki.org/CompanyMode#toc7
The default keybindings needs changing too; moving the selection up and down is mapped to C-p and C-n, just like moving the cursor in normal text.
Having the completion pop up automatically is annoying in most cases, so I disable that and when the popup is there, don't require a match.
#+begin_src emacs-lisp (use-package company :commands company-mode :diminish company-mode :bind (:map company-active-map ("C-n" . company-select-next) ("C-p" . company-select-previous) :map company-mode-map ;; Remap normal indent-for-tab-command ([remap indent-for-tab-command] . company-indent-or-complete-common))
:config
(setq company-backends '(company-bbdb company-semantic company-clang company-cmake company-capf company-files
(company-dabbrev-code company-gtags company-etags company-keywords)
company-oddmuse)
company-idle-delay 0.0
company-minimum-prefix-length 1
company-require-match 'never
company-global-modes '(not
org-mode org-journal-mode
text-mode
geiser-repl-mode)) ; completion is annoying for the repl cmds, not for the scheme stuff, can we separate this?
;; And this turns it actually on, except for the modes above
(add-hook 'after-init-hook 'global-company-mode))
#+end_src
** Ad 4. Mode specialization There's always exceptions to the rule; with Emacs doubly so. Here's the relevant exceptions for my configuration.
** Ad 5. Open completion window predictably If there is a need to open a Completions window, let's at least do it predictably, so I know what and where to expect it. The =popwin= package make this behavior a bit more predictable by showing it in the same place and making sure I can get rid of it easily.
As popwin is a dependency of which-key, we already have it.
#+begin_src emacs-lisp (use-package popwin :config (popwin-mode 1)) #+end_src
For most of the completion that is needed which is not inline (for which I am using the =company= package, helm seems to be the most powerful solution. I used to use ido, but helm seems a lot easier and more broad.
#+begin_src emacs-lisp (use-package helm :diminish :bind (("C-c M-x" . 'execute-extended-command) ;; Save the default ("M-x" . 'helm-M-x) ;; Replace M-x ("
- Editing control
The multiple cursors package is often used for repetitive editing.
#+begin_src emacs-lisp (use-package multiple-cursors ;; Binding mc/edit-lines because I can't call it from M-x with helm :bind (("C-c e" . 'mc/edit-lines))) #+end_src
** Navigation Navigating pieces of text effectively is probably the best optimization to make in the emacs configuration.
In my browser I use vimium to jump to links with the keyboard. In emacs =avy= does something similar. On pressing the hotkey, all matches of the first char typed are highlighted (frame wide) by a single character. By pressing that character the cursor is place there directly. This makes =within-frame= navigation a 3-key operation, which is considerably faster than anything else.
#+begin_src emacs-lisp ;; Bind super-j globally to jump quickly to anything in view (use-package avy :bind (("s-j" . 'avy-goto-char))) #+end_src In a more general sense, =evil-mode=, the VI emulation layer on top of emacs, is the ultimate navigational package. I have tried this for about 6 months, but it's not for me (yet).
- Remote editing As my default shell is =zsh= tramp does not work out of the box for me. It gets confused by my prompt, so on top of my =.zshrc= file I have the following line:
#+begin_src sh
Immediately bail out if we are coming in with a dumb terminal
which, in my case, mostly means TRAMP is asking for something.
[[ $TERM == "dumb" ]] && unsetopt zle && PS1='$ ' && return #+end_src
Once that is in place files can be opened with =/ssh:/host:/path/to/file= syntax. Surprisingly, the syntax I got used to in the past (i.e. =/host:/path/to/file=) does not work for me anymore.
#+begin_src emacs-lisp (use-package tramp :straight (:type built-in) ; tramp is closely tied to emacs, use builtin :config (setq default-tramp-method "ssh" tramp-syntax 'simplified tramp-terminal-type "tramp")
(eval-after-load 'tramp '(setenv "SHELL" "/bin/bash"))
(add-to-list 'tramp-connection-properties
(list ".*" "locale" "LC_ALL=C")))
#+end_src
- Browser integration My default browser, as set in the OS, should be started automatically on opening =http:= like stuff and =html= files. The only link to the /outside world/ is specifying that =xdg-open= should figure out what program to use.
#+begin_src emacs-lisp (setq browse-url-browser-function 'browse-url-generic) (setq browse-url-generic-program "xdg-open") #+end_src
There's a special place for the =nyxt= browser here. It is particularly interesting because its design was inspired by emacs. The two things that are interesting to me are that it is programmed in common-lisp and thus has all the niceties of that, including an option to connect a REPL to the browser and change the program while it is running. So, let's start with a convenience function to make a connection to a running Nyxt instance.
#+name: nyxt-sly-config #+begin_src emacs-lisp :tangle no (defun mrb/nyxt-connected-p () "Is nyxt connected" (sly-connected-p))
(defun mrb/nyxt () "Connect the the nyxt browser with sly" (interactive)
(unless (sly-connected-p)
(sly-connect "localhost" "4006")))
#+end_src
What other options I need for this is as of yet unclear. Things which come to mind are: controlling window refreshments when something was published (blog postings for example) or monitoring web page changes, capturing information from the current web view buffer to emacs.
Not exactly browser integration, but 'openstreetmap browsing' is close enough. I would like to use this for planning trips, so something like a 'set of view settings' for this package would be nice. This would allow me to gather maps views, augment them with a gpx route and bookmarks. Not sure if gpx waypoints/POI sets would work.
The package as is, is already useful for searching locations and storing links to locations.
#+begin_src emacs-lisp (use-package osm :straight (:host github :repo "minad/osm") :commands (osm osm-mode) :bind (:map osm-mode-map ("q" . (lambda() (interactive) (quit-window t)))) :init ;; Load Org link support (with-eval-after-load 'org (require 'osm-ol)) :config (setq osm-outdoor-url (concat "https://tile.thunderforest.com/outdoors/{%z}/{%x}/{%y}.png?apikey=" (password-store-get "API/tile.thunderforest.com")) map5-base (concat "https://s.map5.nl/map/" (password-store-get "API/map5.nl") "/tiles/") osm-map5-opentopo-url (concat map5-base "opentopo/EPSG900913/{%z}/{%x}/{%y}.jpeg") osm-map5-opensimpletopo-url (concat map5-base "opensimpletopo/EPSG900913/{%z}/{%x}/{%y}.jpeg") osm-map5-openlufo-url (concat map5-base "openlufo/EPSG900913/{%z}/{%x}/{%y}.jpeg")
osm-server-list
`((default
:name "Mapnik"
:description "Standard Mapnik map provided by OpenStreetMap"
:url "https://%s.tile.openstreetmap.org/%z/%x/%y.png"
:group "Standard")
(outdoor :name "Outdoor"
:description "Outdoor focussed maps"
:url ,osm-outdoor-url
:group "Personal")
(opentopo :name "OpenTopo NL"
:description "Map5.nl OpenTopo"
:url ,osm-map5-opentopo-url
:group "Personal")
(opensimpletopo :name "OpenSimpleTopo NL"
:description "Map5.nl OpenSimpleTopo"
:url ,osm-map5-opensimpletopo-url
:group "Personal")
(openlufo :name "OpenLufo NL"
:description "Map5.nl OpenLufo"
:url ,osm-map5-openlufo-url
:group "Personal"))
;; set proper home location, FIXME: read this from private store
osm-home '(51.6441759 4.4377029 17)
osm-copyright nil))
#+end_src
- Messaging and chatting ** Mail This section describes a search for the mail setup I want in Emacs. There are a few mail related packages and I'm unsure at what I want to use; reading the feature list doesn't cut it. So, I'm having a setup where multiple mail handling packages may exist and hopefully one will float to the top as being the one
*** Composing mail Composing mail is often an /out of band/ activity, like creating a tweet or a capture, so I would like to have roughly the same behavior. This is by default provided by compose-mail-other-frame, which in turn calls the right mua to do the job. Oddly enough, it is still required to have =mu4e-compose-in-new-frame= set to actually have another frame, so I'm writing it with mu4e-compose directly.
#+name: mu4e-composeconfig
#+begin_src emacs-lisp :tangle no
;; Keybindings for active regions
<
:bind ("C-x m" . 'mrb/compose-mail)
:config (setq mu4e-compose-format-flowed t mu4e-compose-in-new-frame t mu4e-compose-dont-reply-to-self t mu4e-compose-signature '(with-current-buffer (find-file-noselect message-signature-file) (buffer-string)) mu4e-compose-complete-only-after nil ; no limit on contact completion for now mail-signature t ; use mail-signature file (move to message section?) )
(defun mrb/compose-mail (&optional mailto-url) "Run mail-compose, use mailto URI if it is given." (interactive)
;; If we have a mailto argument, parse and use it
(if (and (stringp mailto-url)
(string-match "\\`mailto:" mailto-url))
(browse-url-mail mailto-url)
;; No mailto, argument, just run the mua
(mu4e-compose-new)))
(defun mrb/mailfile() "Compose mail message from current buffer file, typically a pdf is viewed for my use-case" (interactive) (let* ((file (buffer-file-name)) (mimetype (mm-default-file-type file))) (mu4e-compose-new) (mml-attach-file file mimetype (concat mimetype " attachment") "attachment")))
;; Extend attaching files with extra info insertion
<
To be able to use the =mrb/compose-mail= function as a mailto handler we need to be able to call it from outside of emacs. Let's define a small shell script that does exactly this. The SRC attributes tangle it into my =bin= directory where is will be in the path. In the OS, this script will need to be set as the default mail handler.
#+begin_src sh :exports code :tangle ~/bin/mailto-handler :shebang #!/bin/bash
Emacs mailto URI handler
Make sure mailto: is always prepended to $1
mailto="mailto:${1#mailto:}" mailto=$(printf '%s\n' "$mailto" | sed -e 's/["]/\&/g')
Call the elisp function handling our mailto URI
elisp_expr="(mrb/compose-mail "$mailto")"
Do not create new frame, because mu4e does that already
which is a simpler solution in this case
edit-noframe --eval "$elisp_expr" #+end_src
On top of that, I want a manual capture script which is basically the same as the mailto handler. #+begin_src sh :exports code :tangle ~/bin/capture-mail.sh :shebang #!/bin/sh edit -e '(mrb/compose-mail)' #+end_src
When attaching files, it is sometimes useful to extract some info from the attached file and include it in the message. My main usecase is extracting PDF annotations (mostly for people who cannot process the annotations in their PDF viewer directly.
The way I have implemented this is by advicing the function =mml-insert-empty-tag= which is used by the =mml-attach-file= function. At that point in the code the file and point in the active buffer are known, and we just have to process the extra actions. The advice function extracts the annotations from the pdf file (unconditionally at the moment) and inserts a rendering of them just before inserting the attachment itself.
#+name: extend-attach #+begin_src emacs-lisp :tangle no ;; FIXME: return info, do not use insert directly, so we have more control (defun mrb/pdf-extract-info (file) "Extract and render the pdf annotations in FILE" (mapc (lambda (annot) ;; traverse all annotations (progn (let ((page (cdr (assoc 'page annot))) (highlighted-text (if (pdf-annot-get annot 'markup-edges) (let ((highlighted-text (pdf-info-gettext (pdf-annot-get annot 'page) (pdf-tools-org-edges-to-region (pdf-annot-get annot 'markup-edges)) t file))) (replace-regexp-in-string "\n" " " highlighted-text)) nil)) (note (pdf-annot-get annot 'contents)) (type (pdf-annot-get annot 'type))) ; underline, strike-through etc. ;; Stuff gathered, start rendering (when (or highlighted-text (> (length note) 0)) (insert (format "\n- page %s" page))
(when highlighted-text
(insert (format ": “%s”\n" highlighted-text)))
(if (> (length note) 0)
(insert (format "\n %s\n" note))
(insert "\n" ))))))
(cl-remove-if ; don't process links?
(lambda (annot) (member (pdf-annot-get-type annot) (list 'link)))
(pdf-info-getannots nil (expand-file-name file)))))
(defun mrb/extended-attach-file (name &rest plist)
"Advice :before mml-insert-empty-tag to grab additional info from the attachment."
(let* ((file (plist-get plist 'filename))
(mimetype (if file (mm-default-file-type file) nil)))
(when (string= mimetype "application/pdf")
;; Extract and render annotations
(mrb/pdf-extract-info file))))
(advice-add 'mml-insert-empty-tag ; a lot easier because we already know the file :before 'mrb/extended-attach-file '((name . "extended-attach"))) #+end_src *** Sending mail Sending mail through smtp, using the smtpmail package
#+begin_src emacs-lisp (use-package smtpmail :init (setq smtpmail-default-smtp-server "localhost" smtpmail-service 25) :config (setq smtpmail-local-domain user-domain smtpmail-sendto-domain user-domain ;; User agent style is message mode by default, specific mail packages may override this mail-user-agent 'message-user-agent send-mail-function 'smtpmail-send-it ;This is for mail-mode message-send-mail-function 'message-smtpmail-send-it) ; This is for message-mode
(setq password-cache t) ; default is true, so no need to set this actually
(setq password-cache-expiry 28800)) ; default is 16 seconds, which is ridiculously low
#+end_src
At times we want to send signed and/or encrypted mail
#+begin_src emacs-lisp (use-package mml-sec :straight (:type built-in) :config ;; Use my key to sign messages and make it safe if other keys exist (add-to-list 'mml-secure-openpgp-signers user-gpg-encrypt-key) (setq mml-secure-key-preferences '((OpenPGP (sign) (encrypt (user-mail-address user-gpg-encrypt-key))) (CMS (sign) (encrypt))))
;; Add my bcc address to list of safe addresses in bcc for secure message
(add-to-list 'mml-secure-safe-bcc-list mrb/bcc-address)
;; Always encrypt to self, so I can read my own messages
(setq mml-secure-openpgp-encrypt-to-self `(,user-gpg-encrypt-key)))
#+end_src
*** Generic mail message settings It's not entirely clear which package is exactly responsible for what, but there are a few settings which are related to the =message= package
#+begin_src emacs-lisp (use-package message :demand t ; mu4e won't load otherwise :straight (:type built-in)
:init
(defun mrb/message-mode-hook ()
"This configures message mode and thus other modes which inherit message mode."
;; We have to make sure that the messages we produce are
;; format=flowed compatible. This happens on encoding when sending
;; it. However, during compose we need to make sure that what we
;; offer to encode is suitable for it.
;; Let's do the compose part first No reflowing is possible without
;; having hard newlines, so lets always enable those
(use-hard-newlines t 'always)
;; Use visual line mode and visually break at word boundaries which
;; is the same as the email encoding on send (see below)
(visual-line-mode 1)
(auto-fill-mode 0) ; Makes no sense in visual mode
(setq visual-fill-column-width nil) ; Make sure `fill-column gets used
(setq fill-column 80) ; Not the same as encode column below!
(visual-fill-column-mode 1) ; Show it
;; Next, do the encoding and sending part
;; Set the fill column on encoding (send)
;; Q: where does the 66 come from, surely it could be a little bit more?
(setq fill-flowed-encode-column 66)
;; This enables the f=f encoding
(setq mml-enable-flowed t))
:hook (message-mode . mrb/message-mode-hook)
:config
(setq
;; When citing, remove the senders signature.
;; FIXME: often I want to remove a lot more, like the disclaimer
message-cite-function 'message-cite-original-without-signature
;; Citing/Quoting when replying
message-yank-prefix "> " ; Add '> ' when quoting lines
message-yank-cited-prefix ">" ; Add '>' for cited lines
message-yank-empty-prefix "" ; Add nothing for empty lines
;; Always put one in the Sent folder on sending.
;; FIXME: this does nothing for mu4e!!!
message-default-mail-headers (mapconcat
'identity
`(,(concat "Bcc: " mrb/bcc-address)
"OpenPGP: id=235E5C8CF5E8DFFB"
"OpenPGP: url=https://keys.openpgp.org/vks/v1/by-fingerprint/77DDA1B68D04792A8F85D855235E5C8CF5E8DFFB"
"OpenPGP: preference=signencrypt")
"\n")
;; How to attribute
message-citation-line-format "[%N]:"
message-citation-line-function 'message-insert-formatted-citation-line
message-kill-buffer-on-exit t))
#+end_src
*** Mu4e specific settings After a journey with many different MUAs I seem to be settling on =mu4e=.
#+begin_src emacs-lisp ;; FIXME: check for system packages meson, g++, pkg-config, ;; cmake, libglib2.0-dev, libgmime3.0-dev, libxapian-dev, texinfo ;; (names are debian package names) (use-package mu4e :straight (:type git :host github :repo "djcb/mu" :pre-build (("./autogen.sh") ("ninja" "-C" "build")) :files (:defaults "build/mu4e/*.el") :includes (org-mu4e mu4e-icalendar))
:custom (mu4e-mu-binary (expand-file-name "build/mu/mu" (straight--repos-dir "mu")))
:after (message fullframe selected)
:commands (mu4e mrb/compose-mail mrb/mailfile)
:hook (mu4e-view-mode . turn-on-visual-line-mode)
:bind (:map mu4e-search-minor-mode-map
("/" . mu4e-search)
("s" . nil)
("e" . mu4e-search-edit))
;; Configuration sections
<<mu4e-composeconfig>>
<<mu4e-main-config>>
<<mu4e-headers-config>>
<<mu4e-view-config>>
<<mu4e-packages>>
;; FIXME: move these up
(defun mrb/setsigfile (ctxname)
(concat "~/Maildir/.signature-"
(downcase ctxname)))
(setq
;; Override the default mail user agent with the mu4e specific one
mail-user-agent 'mu4e-user-agent
mu4e-sent-folder "/Sent"
mu4e-drafts-folder "/Drafts"
mu4e-trash-folder "/Trash"
mu4e-attachment-dir "~/Downloads" ; Absolute folder here, not relative to Maildir!
mrb/mu4e-junk-folder "/Junk" ; Added for my specific config
mrb/mu4e-archive-folder "/Archives"
;; Refiling to /Archives/YYYY
mu4e-refile-folder (lambda (msg)
(let ((archive_base (concat mrb/mu4e-archive-folder "/"))
(msgyear (format-time-string "%Y" (mu4e-message-field msg :date))))
(cond
;; Order: most specific to general, end with the 't' case
;; Archive messages per year if it has a date field (basically, always)
(msgyear (concat archive_base msgyear))
;; The default refile location
(t archive_base))))
mu4e-use-fancy-chars t
mu4e-get-mail-command "mbsync quick" ; Just get inbox and important stuff, not everything
mu4e-decryption-policy 'ask
mu4e-update-interval 120 ; we just index, no retrieve, so 2mins is fine
;; Set completing function to standard, so helm works
mu4e-completing-read-function 'completing-read
mu4e-save-multiple-attachments-without-asking t
;; Context definitions
;; Goal 1. use global config, unless private address is used
;; FIXME: Can we use the one that matched?
mu4e-context-policy 'pick-first ; When nothing matches only!
mu4e-compose-context-policy 'nil ; for compose, use current if nothing matches
mail-signature-file mrb/default-signature-file
mu4e-contexts
`(,(let* ((ctxname "Default")
(sigfile (mrb/setsigfile ctxname)))
(make-mu4e-context
:name ctxname
;; Use global config, but restore what was changed in other contexts
:vars `((user-mail-address . ,mrb/default-email-address)
(mail-signature-file . ,sigfile)
(mu4e-compose-signature . (with-current-buffer
(find-file-noselect mail-signature-file)
(buffer-string))))))
,(let* ((ctxname "Private")
(sigfile (mrb/setsigfile ctxname)))
(make-mu4e-context
:name "Private"
:match-func (lambda (msg)
(when msg
(mu4e-message-contact-field-matches
msg `(:to :cc :from) mrb/private-addresses)))
:vars `((user-mail-address . ,mrb/default-private-address)
(mail-signature-file . ,sigfile)
(mu4e-compose-signature . (with-current-buffer
(find-file-noselect mail-signature-file)
(buffer-string))))))
)
)
;; Rendering of html mail is done with shr, but I prefer not to show it at all
(with-eval-after-load "mm-decode"
(add-to-list 'mm-discouraged-alternatives "text/html")
(add-to-list 'mm-discouraged-alternatives "text/richtext"))
(defun mrb/mu4e-shr2text (msg)
"Overridden mu4e-shr2text"
(mu4e~html2text-wrapper
(lambda ()
(let ((shr-inhibit-images nil) ;
(shr-width nil) ; Use whole window width for rendering
(shr-use-colors nil) ; Don't use colors from html, they ugly
(shr-bullet "• ")) ; Original was "* "
(shr-render-region (point-min) (point-max)))) msg))
(setq mu4e-html2text-command 'mrb/mu4e-shr2text))
#+end_src
Regardless, I always want mu4e, so start it in the background
#+begin_src emacs-lisp (mu4e 'background) #+end_src
**** Main screen When mu4e start, it always opens in its main screen, which contains the main operations, visible bookmarks.
#+name: mu4e-main-config #+begin_src emacs-lisp :tangle no :bind (:map mu4e-main-mode-map ("q" . mrb/mu4e-quit) ; just close the menu ("Q" . mu4e-quit) ; really Quit (";" . nil) ; this was too easy to press by mistake ("C-;" . mu4e-context-switch)) :init ;; Claim the whole screen in mu4e main window (defun mrb/mu4e-quit () (interactive) (kill-buffer (current-buffer)))
(fullframe mu4e mrb/mu4e-quit)
:config
(setq mu4e-main-buffer-hide-personal-addresses t
mu4e-main-hide-fully-read nil
mu4e-confirm-quit nil
mu4e-bookmarks (list
'(:name "Unread messages"
:query "flag:unread and not flag:trashed"
:key ?u)
'(:name "INBOX"
:query "(flag:unread or flag:new or maildir:/INBOX) and not (flag:draft or flag:flagged or flag:deleted or maildir:/Trash or flag:trashed)"
:key ?i)
'(:name "TODO list"
:query "flag:flagged and not flag:trashed"
:key ?+)
'(:name "Today's messages"
:query "date:today..now"
:key ?d)
'(:name "Trash"
:query "maildir:/Trash or flag:trashed"
:key ?t)
'(:name "Junk"
:query "maildir:/Junk"
:key ?j)))
#+end_src **** Header view The first view after searching is a list of mail headers. Set config vars for this view and define the visualisation of the marks. I need an extra action to mark mail as junk which, on processing, moves the marked messages to the junk folder
#+name: mu4e-headers-config #+begin_src emacs-lisp :tangle no :bind (:map mu4e-headers-mode-map ("s" . mu4e-headers-mark-for-junk) ("r" . mu4e-compose-reply) ("R" . mu4e-headers-mark-for-refile) ("SPC" . mu4e-headers-mark-for-something) ("X" . (lambda () (interactive) (mu4e-mark-execute-all t)))) :config (setq mu4e-headers-include-related nil ; Use 'W' to toggle interactively mu4e-headers-date-format "%F" ; ISO format yyyy-mm-dd mu4e-headers-results-limit -1 ; For now, needed to have related search results mu4e-headers-visible-lines 20 ; Default of 10 is a bit small mu4e-headers-auto-update nil ; Prevent confusion
;; Make header configuration explicit mu4e-headers-fields '((:flags . 6) (:human-date . 12) (:mailing-list . 10) (:from . 22) (:subject . nil))
;; Define marks, possibly redundant, but I'd like to see them here. mu4e-use-fancy-chars t mu4e-headers-attach-mark '("a" . "📎 ") mu4e-headers-calendar-mark '("c" . "📅") mu4e-headers-draft-mark '("D" . "🚧 ") mu4e-headers-encrypted-mark '("x" . "🔑 ") mu4e-headers-flagged-mark '("F" . "🚩 ") mu4e-headers-list-mark '("s" . "🔈") mu4e-headers-new-mark '("N" . "✨ ") mu4e-headers-passed-mark '("P" . "↪ ") mu4e-headers-personal-mark '("p" . "👨") mu4e-headers-replied-mark '("R" . "↩ ") mu4e-headers-seen-mark '("S" . " ") mu4e-headers-signed-mark '("s" . "🖊 ") mu4e-headers-trashed-mark '("T" . "🗑️") mu4e-headers-unread-mark '("u" . "📩 ") ) ;; Add one specifically for marking as junk (add-to-list 'mu4e-marks '(junk :char ("J" . "💀") :prompt "Mark as junk" :show-target (lambda (dyn-target) "Junk") :ask-target (lambda () mrb/mu4e-junk-folder) :action (lambda (docid msg target) (mu4e--server-move docid (mu4e--mark-check-target target) "+S-N-u"))))
;; Let mu4e define functions for it (mu4e~headers-defun-mark-for junk) (mu4e~view-defun-mark-for junk)
#+end_src
**** Message view After selecting a message in the header view, a message view will open.
#+name: mu4e-view-config #+begin_src emacs-lisp :tangle no :bind (:map mu4e-view-mode-map ("s" . mu4e-view-mark-for-junk) ("r" . mu4e-compose-reply) ("R" . mu4e-view-mark-for-refile) ("X" . (lambda () (interactive) (mu4e~view-in-headers-context (mu4e-mark-execute-all t)))))
:config (setq mu4e-view-use-old nil mu4e-view-show-images t ; This creates connections, possible privacy issue? mu4e-view-image-max-width 800 mu4e-view-image-max-height 600 mu4e-view-show-addresses t) ; Show actual addresses instead of names #+end_src **** Addon packages There are quite a few mu4e packages out there. Let's load them in one group for now, so it will be easy to /disable plugins/ if we need to. #+name: mu4e-packages #+begin_src emacs-lisp :tangle no :config ;; Show an alert when new mails come in, this relies on mu4e being active though (use-package mu4e-alert :hook (after-init . mu4e-alert-enable-notifications) :config ;; Just show subjects, not counts (setq mu4e-alert-email-notification-types '(subjects)) (mu4e-alert-set-default-style 'libnotify))
;; Support icalendar Yes/No/Maybe reactions to calendar invites (use-package mu4e-icalendar :config (mu4e-icalendar-setup))
(use-package mu4e-query-fragments) ; Support reusable query pieces '%junk' etc. (use-package mu4e-jump-to-list) ; Shortcut key 'l' to directly focus on list threads
;; Patch highlighting inside messages (use-package message-view-patch :after (magit) :hook (gnus-part-display . message-view-patch-highlight))
(use-package org-mu4e :after org) #+end_src
*** Sieve Sieve is used on the server to filter our mail and this is accessed by the sieve-manage package. For some reason I can't get the '^M' characters out of the process, so here's a hack for that.
#+begin_src emacs-lisp (use-package sieve :init (defun dos2unix () "Replace DOS eolns CR LF with Unix eolns CR" (interactive) (goto-char (point-min)) (while (search-forward "\r" nil t) (replace-match ""))) :commands sieve-manage :hook (sieve-mode . dos2unix) :config (setq sieve-manage-authenticators '(plain digest-md5 cram-md5 scram-md5 ntlm login))) #+end_src
which basically goes over the whole sieve script and removes the '^M' characters from the buffer ** Elfeed Elfeed handles my RSS feeds, which I specify using an orgmode file =feeds.org=
#+begin_src emacs-lisp (use-package notifications)
(use-package elfeed :after notifications :commands elfeed :bind (("C-c f" . 'elfeed) :map elfeed-show-mode-map ("w" . 'mrb/elfeed-show-toggle-watchlater) ("v" . 'mrb/elfeed-play-with-mpv) :map elfeed-search-mode-map ("w" . 'mrb/elfeed-search-toggle-watchlater)) :init (setf url-queue-timeout 30 elfeed-db-directory "~/.elfeed/")
:config
(defun mrb/elfeed-search-toggle-tag(tag)
(let ((entries (elfeed-search-selected)))
(cl-loop for entry in entries do
(if (elfeed-tagged-p tag entry)
(elfeed-untag entry tag)
(elfeed-tag entry tag)))
(mapc #'elfeed-search-update-entry entries)
(unless (use-region-p) (forward-line))))
(defun mrb/elfeed-search-toggle-watchlater()
(interactive)
(mrb/elfeed-search-toggle-tag 'watchlater))
(defun mrb/elfeed-show-toggle-tag(tag)
(interactive)
(if (elfeed-tagged-p tag elfeed-show-entry)
(elfeed-show-untag tag)
(elfeed-show-tag tag)))
(defun mrb/elfeed-show-toggle-watchlater()
(interactive)
(mrb/elfeed-show-toggle-tag 'watchlater))
;; umpv maintains a playlist, so adding more videos wil automatically queue
(defun mrb/elfeed-play-with-mpv ()
"Play elfeed link in mpv"
(interactive)
(notifications-notify
:title "Elfeed action"
:body "Playing video with MPV"
:app-name "Elfeed")
(start-process "elfeed-mpv" nil
"~/bin/umpv"
(elfeed-entry-link elfeed-show-entry)))
;; New entry hook allows meta information manipulation
;; without directly having to change elfeed-feeds
(add-hook 'elfeed-new-entry-hook
(elfeed-make-tagger :feed-url "youtube\\.com"
:add '(video youtube)))
(add-hook 'elfeed-new-entry-hook
(elfeed-make-tagger :feed-url "vimeo\\.com"
:add '(video vimeo))))
#+end_src
Elfeed allows to interactively subscribe to a feed (defaulting to what is in the clipboard. Managing elfeed-feeds as a variable is kinda clumsy, although very flexible. There is a middle ground which fits me even better. At the cost of the bare-bone flexibility of having the feeds directly in lisp, I'm using an orgmode file to record the feeds i want.
#+begin_src emacs-lisp (use-package elfeed-org :after elfeed :init (setq rmh-elfeed-org-files (list (concat elfeed-db-directory "feeds.org"))) (elfeed-org)) #+end_src ** IRC
I used to run a weechat relay on a vps for IRC. The main reason for this was that the relay allowed multiple clients to be active at the same time an have at least an attempt at saving the state betwee multiple devices.
The major downside is that a special weechat client is needed to make this work, which is not the same as a standard IRC client. This made the choice for clients very limited, but luckily there was an emacs client.
After using it some time and maintaining some local patches; the main program is largely unmaintained, it was time to look for an alternative.
While having the weechat relay is very nice, I also want to have a /"standard"/ IRC configuration where everything is happening in the client, possibly augmented by having an IRC bouncer to maintain persistent connections. [[https://wiki.znc.in/ZNC][ZNC]] might almost have the featureset to do what weechat does, or very closely to it.
ERC seems to be the most used IRC client for emacs and included with it, so it would make sense to use it. I like the simplicity of [[https://github.com/emacs-circe/circe/wiki][circe]] though, so I opted to create a config for that.
#+begin_src emacs-lisp (use-package circe :commands (circe mrb/chat) :config (setq circe-default-nick "mrvdb" circe-network-options `(("libera" :host "chat.hsdev.com" :port 6667 :pass ,(concat "mrb@emacs/libera:" (password-store-get "chat.hsdev.com")) :channels ("#emacs" "#talos-workstation" "#vikings")) ("oftc" :host "chat.hsdev.com" :port 6667 :pass ,(concat "mrb@emacs/oftc:" (password-store-get "chat.hsdev.com")) :channels ("#osm")))
;; Simplify and improve formatting, remove some details
circe-format-say "{nick:10s}: {body}" ; right align nicks at 10th pos
circe-format-self-say "{nick:10s}: {body}"
circe-format-server-part "*** Part: {nick} left {channel}"
circe-format-server-quit-channel "*** Quit: {nick} left {channel}"
circe-format-server-quit "*** Quit: {nick}"
circe-format-server-join "*** Join: {nick}"
circe-format-server-rejoin "*** Re-joined: {nick}"
lui-time-stamp-position 'right-margin
lui-fill-type nil
circe-reduce-lurker-spam t ; dont show notices for non talkers
circe-default-part-message "" ; no parting reason
circe-default-quit-message "") ; no quitting reason
(enable-lui-track) ; show indicator to where has been read
(enable-circe-display-images) ; turn links to images into images
(enable-circe-color-nicks) ; color nicks
;; Faces after enables, so the faces are there
;; FIXME: do not use color names here, but functional names, which we can change centrally
(set-face-attribute 'circe-prompt-face nil :foreground mrb/cursor-color :height 1.3)
(set-face-attribute 'lui-time-stamp-face nil :height 0.8)
(set-face-attribute 'lui-track-bar nil :background "LawnGreen") ; fix this one!
;; Gather buffer local and line UI settings
(defun mrb/lui-setup ()
(setq fringes-outside-margins t ; first margins, then fringes
right-margin-width 7 ; [hh:mm]
word-wrap t ;
wrap-prefix " ") ; line this up with size of nick in `circe-format-say
(company-mode 0)
(setf (cdr (assoc 'continuation fringe-indicator-alist)) nil))
(add-hook 'lui-mode-hook 'mrb/lui-setup)
;; Set the prompt properly on entering the chat mode
(defun mrb/set-circe-prompt ()
(lui-set-prompt (propertize (concat "[" circe-chat-target "]➜ ")
'face 'circe-prompt-face)))
(add-hook 'circe-chat-mode-hook 'mrb/set-circe-prompt)
;; Make a convenient connect function
;; TODO make this re-entrant
(defun mrb/chat()
(interactive)
(circe "libera")
(circe "oftc")))
#+end_src
** Mastodon Not messaging or chatting perse, but a micro blog social network. The 'capture a toot' is hot-keyed through =xbindkeys= like other capture commands.
#+begin_src emacs-lisp ;; Mastodon in emacs (use-package mastodon :config (setq mastodon-instance-url "https://mastodon.nl" mastodon-active-user "mrb" ;; finds mastodon.nl in password-store and expects user field to be present mastodon-auth-source-file 'password-store)
;; Convenience function
(defun mrb/capture-toot()
(interactive)
(mastodon-toot)))
#+end_src
#+begin_src sh :exports code :tangle ~/bin/capture-toot.sh :shebang #!/bin/sh edit-noframe --eval '(mrb/capture-toot)' #+end_src
- Development settings Some settings which aid in development tasks.
Trailing white-space can sneak into files without knowing it. I could enable the display of trailing white-space, but I find that annoying to look at. Instead I just remove it just before saving a file. One solution is to enable a before-save-hook, which would make me the trailing-white-space police in all projects.
Alternatively a package ws-butler exists on github, which solves this exact problem. It implements the removal of white-space on top of the highlight-changes-mode which, at least in theory, would remove only the trailing white-space on changes I have made.
However, this is problematic. It needs a special initialization because I run in server mode (doable), but its global mode is way to global, because it just applies to all buffers, which lead to errors on non-file buffers; for example because they are read-only.
** Generic Let's start with some generic development related settings and packages #+begin_src emacs-lisp (use-package ggtags) ; not used very much
(use-package cmake-mode) ; adjusts auto-mode-alist on load
(use-package yaml-mode) ; used by ansible, magit(?) #+end_src
For most of the languages I use (Bash, python, C, Go, HTML Haskell, Lua), the Language Server Protocol seems to be supported by =lsp-mode=, so I'm setting this up generally here and enable it for each language I'm using if it is supported.
#+begin_src emacs-lisp (use-package lsp-mode :commands (lsp-deferred lsp)
:init
(use-package lsp-ui
:commands lsp-ui-mode)
;; Enable lsp for languages I use
;; TODO move these to their own blocks?
:hook (((c++-mode c-mode
css-mode html-mode js2-mode
python-mode
sh-mode
yaml-mode
go-mode) . lsp-deferred)
(lsp-mode . lsp-enable-which-key-integration))
:config
(setq lsp-enable-snippet nil
lsp-prefer-flymake nil
lsp-ui-doc-position 'bottom
read-process-output-max (* 1024 1024) ; As per https://emacs-lsp.github.io/lsp-mode/page/performance/
lsp-prefer-capf t
lsp-idle-delay 0.5 ; Tuning for frustration
)
)
#+end_src
Make our source look a bit more attractive by enabling =prettify-symbol-mode= in all programming modes
#+begin_src emacs-lisp ;; List of replacements, this needs to be a function to eval in hook scope (defun mrb/prettify-symbols() (interactive) ;; If you want to see the effect: enable prettify-symbols mode (setq prettify-symbols-alist '(("lambda" . ?λ) ("lambda*" . "λ*") ;("map" . ?↦) ; this one is a bit of a pain actually ("->" . ?⟶) ("<-" . ?⟵) ("=>" . ?⟹) ("#t" . ?⟙) ("#f" . ?⟘) ("|>" . ?▷) ("<|" . ?◁) ("->>" . ?↠) ("<=" . ?≤) (">=" . ?≥))) (prettify-symbols-mode))
;; Add hooks for all programming languages, for now ;; TODO: check for conflict with haskell, which has its own system. (add-hook 'prog-mode-hook 'mrb/prettify-symbols) (add-hook 'lisp-interaction-mode-hook 'mrb/prettify-symbols) #+end_src
In general, the syntax highlighting of emacs is sufficient, but is based on defining regular expression to match language contents to color them. There is a better way to do this; based on generating a semantic tree representation of the language in question. There is a tool called [[https://tree-sitter.github.io/][tree-sitter]] which does this.
The main advantages of the methode that tree-sitter uses are:
- the same tree representation is used for all languages, so adding new languages is relatively easy
- using the representation for syntax highlighting is just one application: code folding or semantic extending a region, all of which are available in emacs in other ways, is a lot better using the tree-sitter method.
- it's super fast. fast enough to keep up with typing.
An interface from emacs to tree-sitter luckily exist. Although it is not stable yet, I'm going to enable the highlighting for supported languages.
#+begin_src emacs-lisp (use-package tree-sitter :init (setq tsc-dyn-get-from '(:compilation)) ; dont fetch binaries, ppc64 likely is not there :config ;; Needs to be set before tree-sitter-langs is loaded (setq tree-sitter-langs-grammar-dir (expand-file-name "straight/repos/tree-sitter-langs/" user-emacs-directory)) (use-package tree-sitter-langs :demand t)
;; Enable the mode globally, including highlighting
(global-tree-sitter-mode)
(add-hook 'tree-sitter-after-on-hook #'tree-sitter-hl-mode))
#+end_src
The emacs packages for tree-sitter require tree-sitter version 0.19.3 for now to work as intented and I had to patch tree-sitter-langs to omit /devDependencies/ on building some language grammars. Compilation of the included grammars went fine after that and they will be automatically active.
Now that we have a tree, we can take advantage of that for folding, so let's configure that as well.
#+begin_src emacs-lisp
(use-package ts-fold
:after tree-sitter
:straight (ts-fold :type git :host github :repo "jcs090218/ts-fold")
:bind (("M-S-
Ideally, the folding should be spread out over the major modes and have support for tree-sitter there automatically.
** Reference & documentation While emacs itself has a very nicely documentation system, I still don't understand why this is not interpolated into every other mode and or language.
Example: =C-h-f= in lisp-mode describes the function to me. If I am in a python file, I want it to do exactly the same, but for the python function. There's a page on emacs wiki dealing with this a bit: https://www.emacswiki.org/emacs/Context_sensitive_help
There's a package =ghelp= (/generic help/ I guess) which promises to do what I want, but I can't get it installed properly with =use-package=. That package can use the =helpful= package as back-end which is also helpful on it own, so I'm installing that anyways and replace the internal help commands from emacs with them.
#+begin_src emacs-lisp (use-package helpful :bind (("C-h f" . #'helpful-callable) ("C-h h" . #'helpful-at-point) ; I don't care about the emacs 'hello' file ("C-h v" . #'helpful-variable) ("C-h k" . #'helpful-key)))
#+end_src
Anyways, here's what I have configured related to reference information and documentation
*** Context sensitive Regardless of (programming) language there's certain information that's needed on the spot. These are the function signatures, the specific syntax when typing Math symbols, orgmode oddities etc.
I want that type of information directly at hand. One part of the solution is [[https://www.emacswiki.org/emacs/ElDoc][ElDoc]]. This shows documentation in the echo area for a language. ElDoc is built into Emacs so that's a good start. Let's enable it for the modes that have support for it.
#+begin_src emacs-lisp (dolist (the_mode (list 'emacs-lisp-mode-hook 'lisp-interaction-mode-hook 'ielm-mode-hook 'python-mode)) (add-hook the_mode 'eldoc-mode)) (diminish 'eldoc-mode) #+end_src
The location of the eldoc information in the message area is a bit far away from the editing point. I have looked at eldoc-overlay and others to resolve that, but haven't found a satisfying solution yet.
*** Reference information If the inline documentation presented by ElDoc is not sufficient, I want a way to spawn reference documentation based on the context.
The closest thing I found is =zeal= or =dash= which allow docsets to be searched. There is a GUI and helm-dash is an interface to the docsets for emacs. Typically I install docsets through the GUI or manually (the folder names are sometimes not the same when I use helm-dash to install docsets). This solution gives me at least /access/ to reference information for most of the programming languages I use. The direct access to the sources is missing, or I don't know how to do this yet.
#+begin_src emacs-lisp ;; Unify reference documentation with dash (use-package helm-dash :after helm :commands (helm-dash helm-dash-at-point) :config ;; Make sure docset directory exists (make-directory helm-dash-docsets-path t) (setq dash-docs-enable-debugging nil helm-dash-browser-func 'eww ; Within dash, keep browser links within emacs ;; Use all installed docsets helm-dash-common-docsets (helm-dash-installed-docsets))) #+end_src
Dash show documentation inside emacs, which is my preferred method. There are times however I need the desktop GUI Zeal. For one, it's a lot easier to see which docsets are installed and manage them.
#+begin_src emacs-lisp ;; Global keybinding ot open documentation ;; This should probably be somewhere else (use-package zeal-at-point :bind ("s-d" . zeal-at-point)) #+end_src
*** RFC document reader I often need to consult RFC documents, so not having to leave emacs for that and be able to use all tools on RFC documents is very helpful.
#+begin_src emacs-lisp ;; Special mode to read rfc documents locally (use-package rfc-mode :init (setq rfc-mode-directory (expand-file-name "~/dat/doc/rfc/") rfc-mode-index-path (concat rfc-mode-directory"rfc-index.txt"))) #+end_src
*** Man pages There's a builtin package to read man pages, let's make it explicit.
#+begin_src emacs-lisp (use-package man) #+end_src
*** Epub documents Not often, but increasingly so, reference books are in epub format. So, for that we need a reader package. =nov= seems to be the best option
#+begin_src emacs-lisp (use-package nov :mode ("\.epub" . nov-mode)) #+end_src ** Coding styles Different projects use different coding styles. The ones I need I'll gather here for now.
My personal style will be called =mrb= and basing it on the =linux= style. #+begin_src emacs-lisp
;; Basing it on k&r, no real reason, does it really matter? (c-add-style "mrb" '("k&r" (indent-tabs-mode . nil) (c-basic-offset . 2) (c-cleanup-list . (scope-operator space-before-funcall))))
;; Make my style the default (setq c-default-style '((java-mode . "java") (awk-mode . "awk") (other . "mrb")))
;; EditorConfig support, not used much by the looks of it (use-package editorconfig :diminish :config (editorconfig-mode 1))
#+end_src
** Haskell I am just starting out with haskell, but the two things that are probably needed in any case are a mode to use when editing Haskell source (*.hs files) and the ghc-mod package to help with completion and showing syntax errors.
#+begin_src emacs-lisp (use-package haskell-mode :hook (;(haskell-mode . interactive-haskell) ;; Produces elc load error (haskell-mode . turn-on-haskell-doc) (haskell-mode . haskell-indentation-mode))
:mode "\\.hs\\'"
:config
;; Replace ⇒ with ⇉
(delete '("=>" . "⇒") haskell-font-lock-symbols-alist)
(add-to-list 'haskell-font-lock-symbols-alist '("=>" . "⇉"))
(setq haskell-font-lock-symbols t
haskell-interactive-popup-errors nil
haskell-process-type 'stack-ghci))
#+end_src
Enable the language server for haskell
#+begin_src emacs-lisp (use-package lsp-haskell :after (haskell-mode lsp-mode) :hook ((haskell-mode haskell-literate-mode) . lsp)) #+end_src
Because the haskell environment can be different for each project, we need a way to adjust to that for certain things. One such thing is flycheck which will give false-negatives when checking haskell files if the specific build environment is not taken into account. The package flycheck-haskell automatically adjusts flycheck.
#+begin_src emacs-lisp (use-package flycheck-haskell :after flycheck :hook (flycheck-mode . flycheck-haskell-setup)) #+end_src
For creating web apps I use yesod, which is supported by a number of packages #+begin_src emacs-lisp ;; Yesod's html like files (use-package hamlet-mode) #+end_src ** Go #+begin_src emacs-lisp (use-package go-mode ;; FIXME: godef is fairly heavy on my little machines, make optional? :ensure-system-package godef :config :init ;; Set up before-save hooks to format buffer and add/delete imports. ;; Make sure you don't have other gofmt/goimports hooks enabled. ;; FIXME: would this not add hooks for all files that are saved?? (defun lsp-go-install-save-hooks () (add-hook 'before-save-hook #'lsp-format-buffer t t) (add-hook 'before-save-hook #'lsp-organize-imports t t)) (add-hook 'go-mode-hook #'lsp-go-install-save-hooks)) #+end_src ** Lisp-like There are a number of languages which are lisp-like, not in the least Emacs lisp itself. It makes sense to group the configuration for these languages together. In the first part there will be configuration for all lisp-like languages (like the =paredit= configuration for example). For each specific implementation there may be augmentations to the config, or even a completely separate.
For all of those languages, I want to use paredit which makes editing, longer term, a lot more productive.
#+begin_src emacs-lisp (use-package paredit :hook ((lisp-interaction-mode emacs-lisp-mode eval-expression-mode scheme-mode lisp-mode) . enable-paredit-mode) :diminish) #+end_src
When a mode wants to use paredit, it can use the same constructs as above to enable paredit on it modes.
While we're here, let's do paxedit as well; not sure whey these two package don't merge. It's the same concept. I'm just gonna treat them as one here and enable =paxedit= whenever paredit gets enabled, but I'll be a bit conservative with the keybindings first.
#+begin_src emacs-lisp
(use-package paxedit
:requires paredit
:hook (paredit-mode . paxedit-mode)
:diminish
:bind (("M-
Evaluating an expression with the cursor after it is often used, I'd like being able to do the same when the cursor is in front of such an expression
#+begin_src emacs-lisp ;; The reciprocate of C-x C-e ;; As in: cursor(...eval what is in here...) (defun mrb/eval-next-sexp () (interactive) (save-excursion (forward-sexp) (eval-last-sexp nil))) #+end_src
Not sure how to bind this though. I'd like it close to C-x C-e obviously *** Scheme [[https://www.nongnu.org/geiser][Geiser]] is, among other things, a repl for schemes. I try to use one, mostly for educational purposes. I chose guile because it also claims to support =emacs-lisp= and might be the future vm on which emacs-lisp will run. So, let's have it and use paredit in its REPL.
#+begin_src emacs-lisp ;; Specific implementations depend onn geiser (use-package geiser-guile :straight (:host gitlab :repo "emacs-geiser/guile") :ensure t :after paredit :hook ((geiser-repl-mode . enable-paredit-mode) (geiser-repl-mode . mrb/prettify-symbols))
:config
(setq geiser-active-implementations '(guile)
geiser-guile-binary "/home/mrb/.guix-profile/bin/guile"
geiser-repl-send-on-return-p nil
geiser-default-implementation 'guile) ;chez isnt available for ppc64?
)
#+end_src
While we're in the scheme section, let's configure =guix= and friend, which use scheme heavily
#+begin_src emacs-lisp (use-package guix :config ;; Assuming the Guix checkout is in ~/src/guix. (with-eval-after-load 'geiser-guile (add-to-list 'geiser-guile-load-path "~/dat/src/guix"))
;; guix uses debbugs, but this should probably be closer to mu4e and gnus config
(use-package debbugs)
;; Add yasnippett here, but probably split of later
(use-package yasnippet
:config
(setq yas-snippet-dirs
'("~/dat/src/guix/etc/snippets" ; guix project helpers
"~/.config/emacs/snippets" ; sort of a default
))
(yas-global-mode 1))
)
#+end_src *** Common lisp The defacto standard for a development environment in Emacs for common-lisp is either the =slime= or =sly= package. The latter seems a bit more modern and easier to configure, so trying that one
#+begin_src emacs-lisp (use-package sly :after (paredit) :hook ((sly-mrepl-mode . override-sly-repl-bindings-with-paredit) (sly-mrepl-mode . enable-paredit-mode) (sly-mrepl-mode . sly-mrepl-font-lock-setup)) :commands (mrb/nyxt) :init ;; In the REPL sly takes backspace, I want paredit to have it (defun override-sly-repl-bindings-with-paredit () (define-key sly-mrepl-mode-map (read-kbd-macro paredit-backward-delete-key) nil)) ;; Copy the keywords from lisp mode (defvar sly-mrepl-font-lock-keywords lisp-font-lock-keywords-2)
;; Set them up
(setq sly-mrepl-font-lock-keywords
(cons '(sly-mrepl-font-lock-find-prompt
. 'sly-mrepl-prompt-face)
sly-mrepl-font-lock-keywords))
(defun sly-mrepl-font-lock-setup ()
(setq font-lock-defaults
'(sly-mrepl-font-lock-keywords
;; From lisp-mode.el
nil nil (("+-*/.<>=!?$%_&~^:@" . "w")) nil
(font-lock-syntactic-face-function
. lisp-font-lock-syntactic-face-function))))
:config
(setq inferior-lisp-program "/usr/bin/sbcl"
sly-lisp-implementations '((sbcl ("sbcl" "--core" "/home/mrb/.sbcl/sbcl.core-for-sly")))
sly-net-coding-system 'utf-8-unix)
;; Nyxt exposes a repl we can use
<<nyxt-sly-config>>
;; Correct the REPL prompt
(defun sly-mrepl-font-lock-find-prompt (limit)
;; Rough: (re-search-forward "^\\w*>" limit t)
(let (beg end)
(when (setq beg (text-property-any
(point) limit 'sly-mrepl-prompt-face t))
(setq end (or (text-property-any
beg limit 'sly-mrepl-prompt-face nil)
limit))
(goto-char beg)
(set-match-data (list beg end))
t))))
#+end_src
** Gcode I'm not in the habit of editing gcode, but every now and then I need to look at it and I want it reasonably decent. My use-case is just sending gcode to my 3D-printer which runs Marlin firmware.
Using =define-generic-mode= is more than enough to define something useful for me.
#+begin_src emacs-lisp (use-package generic-x :straight (:type built-in))
(define-generic-mode marlin-gcode-mode
'((";")) ;; ; starts a comment
(apply 'append
(mapcar #'(lambda (s) (list (upcase s) (downcase s) (capitalize s)))
'("keywords?" "exist?")))
'(("\\([GM]\\)[0-9]+" (1 font-lock-function-name-face)) ; code letters
("\\([ABCDEFHIJKLNOPQRSTUVWXYZ]\\)[-]?[0-9]+" (1 font-lock-string-face)) ; parameter letters
("\\([\-+]?[0-9]*\\.[0-9]+\\)" (1 font-lock-constant-face)) ; 9.9 type numbers
("\\([\-+]?[0-9]+\\)" (1 font-lock-constant-face))) ; 999 type numbers
'("\\.gcode\\'")
nil
"Mode for marlin g-code files.")
#+end_src ** Openscad Openscad is a script based 3d parametric drawing program. Obviously I'm editing those scripts in emacs.
#+begin_src emacs-lisp (use-package scad-mode :config (setq scad-keywords '("return" "true" "false" "include"))) #+end_src ** SQL SQL has a number of different dialects I use, depending on the database software in use.
#+begin_src emacs-lisp (setq sql-server "dbserver.hsdev.com" sql-postgres-options '("-P" "pager=off" "-p 5434")) #+end_src ** GIT integration A common factor in all my development is the use of git. For emacs this automatically means Magit. I've used eshell in the past but that config didn't work out.
*** Magit For most, if not all development work (and some other work too) I use git as the revision control system. In emacs that translates to using magit, so let's begin with bringing that in.
Magit forge is a sub-module for magit which helps in managing repositories in forges (notably github and gitlab). This looks very promising, albeit slow.
Features I'm interested in: issue commenting, pull requests
#+begin_src emacs-lisp (use-package magit :demand t :after (org fullframe) :commands magit-status :bind ("C-c m" . magit-status)
:init
(fullframe magit-status magit-mode-quit-window)
:config
(setq magit-last-seen-setup-instructions "1.4.0"
magit-diff-refine-hunk 'all)
;; Enable links to magit fromm org
(use-package orgit)
(use-package forge
:config
(setq forge-add-pullreq-refspec 'ask
forge-pull-notifications t
forge-topic-list-limit '(60 . 0))
;; Only show assigned stuff
(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)))
#+end_src
Most of the magit work is done through its status screen and I would like to see issues and todo's and fixme's and other work that needs to be done in that screen. The forge package does that from a remote forge, like github, but many tasks are hiddenn in the sources already by myself. The magit-todos package scans for those things and show them in the magit screen
#+begin_src emacs-lisp (use-package magit-todos :after (magit) :config (magit-todos-mode 1)) #+end_src
*** Committing automatically I have lost a number of changes in the past because I reverted a file, made a mistake or whatever. Some of these mistakes can be reverted easily if saves are automatically committed
Rather than using an after save hook, there is a minor git-auto-commit mode package which does just what I need.
There is not much to configure for this minor mode. There are a couple of ways to enable it:
- file-local variable (put it in the file to be autocommitted)
- directory-local variable (make a =.dir-locals.el= file); this enables it for all files in the directory
- as a hook
I'm using the first method on relevant files. The disadvantage of this method is that you have to think about it for each file, so perhaps a =.dir-locals.el= is a better solution.
I am considering using a generic hook again to enable the method and either using =git commit --amend= and commit squashing if working on more structured commits. For files that I really do not want autocommit to run I can use a file local variable to disable the hook (or the minor mode)
#+begin_src emacs-lisp (use-package git-auto-commit-mode :diminish "Auto-committed") #+end_src *** Miscellaneous A collection of loosely development related things.
**** Online pastebin services I use github gists sometimes, but rather have more open options as well. These are mostly use to communicate longer pieces of text in chat programs, notably IRC
#+begin_src emacs-lisp (use-package webpaste :config (setq webpaste-provider-priority '("dpaste.org") webpaste-paste-confirmation t webpaste-open-in-browser t)) #+end_src
**** Grepping files Searching for terms in files is a hard problem, but the vast majority of my use-cases can just be solved by searching for terms in the current project root, almost always defined by having a .git directory in a parent directory. So, let's start there and see what else I'll need.
I've chosen =ag= as the engine for /grepping/ the files because it has a helm interface and it's very fast.
#+begin_src emacs-lisp (use-package helm-ag :commands mrb/search :after helm :bind (("C-s-s" . mrb/search)) :config (setq helm-ag-use-agignore t) ; Use the .agignore file if it is there :init (defun mrb/search () (interactive) ;; but do not use the vcs ignore mechanism (setq helm-ag-command-option "--skip-vcs-ignores") (helm-do-ag-project-root))) #+end_src
** Ansible Not really a development setting, but closely related to it, as eventually the programs written need to be deployed. Most of the time, while deploying, ansible plays a role in this and it's now worth having a dedicated configuration for editing the YAML files for it.
For now, just the ansible package, and some access to its documentation will suffice #+begin_src emacs-lisp (use-package ansible :after yaml-mode :init (use-package ansible-doc :hook (yaml-mode . ansible-doc-mode)) :hook (yaml-mode . (lambda () (ansible 1)))) #+end_src
- Finale When we are all done with this, provide it.
#+begin_src emacs-lisp (provide 'mrb) ;;; mrb ends here #+end_src