Skip to content

Jassob/.emacs.d

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Jassob’s Emacs configuration

About this file

This file is intended to be the configurable point of my Emacs configuration.

The complete configuration is very much inspired by Sacha Chua’s Emacs Configuration, especially org-mode, with some stolen bits and pieces from both Spacemacs/Spaceline and hlissner’s Emacs config.

This file generates my config by tangling all source code blocks in the text to a single elisp-file.

Setup

There is a Makefile that tangles everything that needs to be tangled.

  1. Clone this repository to /.emacs.d/ (backup your own configuration first!)
  2. Run make all in =~/.emacs.d/
  3. Profit!

Emacs initialisation

Before doing anything else, let us disable the garbage collector during setup, this saves us some time and we’re not likely to run in to memory issues while setting everything up.

(setq gc-cons-threshold 64000000)
(add-hook 'after-init-hook
          ;; restore after startup
          #'(lambda () (setq gc-cons-threshold 800000)))

Editing system files

When I edit system configuration files I might accidentally find files as my regular user, even though I need sudo rights to edit them. This advice makes ido-find-file automatically open it through TRAMP as sudo if it is not writable by my user.

(defadvice find-file (after find-file-sudo activate)
  "Find file as root if necessary."
  (when (and buffer-file-name
             (not (file-writable-p buffer-file-name))
             (y-or-n-p "Do you want to edit as sudo?"))
    (find-alternate-file (concat "/sudo:root@localhost:" buffer-file-name))))

Packages

First we need to activate the package system and add the melpa repository, if it is not already added.

(unless (executable-find "nix")
  (require 'package)
  (setq package-user-dir (expand-file-name "elpa" packages-dir)
        package-archives '(("gnu"   . "https://elpa.gnu.org/packages/")
                           ("melpa" . "https://melpa.org/packages/")))
  (package-initialize))

;; We've already enabled the package system.
(setq package-enable-at-startup nil)

Make sure to reload the list of packages by calling M-x package-refresh-contents when adding melpa for the first time.

I use use-package for keeping track of my packages, customising them and load them lazily.

(unless (or (executable-find "nix")
           (package-installed-p 'use-package))
  (package-refresh-contents)
  (package-install 'use-package))

Make sure that use-package is available for the byte-compiler.

(eval-when-compile
  (require 'use-package))
(require 'bind-key)

If we’re running on NixOS (or another system where Nix is available), don’t let use-package download and install packages by itself.

(when (executable-find "nix")
  ;; Simple dummy ensure function, it does nothing other than
  ;; requiring the package.
  (setq use-package-ensure-function
        (lambda (pkg ensure state) (require pkg))))
(setq use-package-verbose t)

Delight is a nice package for hiding stuff in my modeline.

(use-package delight :ensure t)

Encoding system

We would like Emacs to prefer UTF8 when reading ambiguous bit strings.

(prefer-coding-system 'utf-8)
(when (display-graphic-p)
  (setq x-select-request-type '(UTF8_STRING COMPOUND_TEXT TEXT STRING)))

Buffer and window navigation

Switching between windows can be a bit of a pain, just cycling through them with C-x o is not really good enough. I therefore use windmove and buffer-move to navigate amongst my buffers and I have them bound to, in my opinion at least, sensible keybindings.

(bind-keys ("<S-left>"  . windmove-left)
           ("C-x <left>"  . windmove-left)
           ("<S-up>"    . windmove-up)
           ("C-x <up>"    . windmove-up)
           ("<S-down>"  . windmove-down)
           ("C-x <down>"  . windmove-down)
           ("<S-right>" . windmove-right)
           ("C-x <right>" . windmove-right))

Sometimes it is nice to be able to shrink and enlarge windows easily and that is why I’ve bounded them to simple keys:

(bind-keys ("S-C-<left>"  . shrink-window-horizontally)
           ("S-C-<right>" . enlarge-window-horizontally)
           ("S-C-<down>"  . shrink-window)
           ("S-C-<up>"    . enlarge-window))

However, sometimes C-x o is exactly what we need, but then we can extend it with ace-window to make it prompt us for a window when there is a choice.

(use-package ace-window :ensure t :bind (("C-x o" . ace-window)))

Dired

Dired is file manager and browser built into Emacs and it is efficient enough. By default it shows every file, even hidden ones, which results in a lot of noise.

(require 'dired-x)
(setq dired-omit-files "^\\...+$")
(add-hook 'dired-mode-hook (lambda () (dired-omit-mode 1)))
(define-key dired-mode-map (kbd "C-c h") 'dired-omit-mode)

Now, pressing C-c h will hide all hidden files in Dired.

Parsing to terminal keys correctly

Sometimes my terminal sends garbled keys to Emacs, to make Emacs understand the terminal I extend the input-decode-map.

How to add more keys:

  1. In the scratch buffer: C-q $COMBINATION
  2. Add the binding to input-decode-map.
  3. Profit!

Credits: This fine answer on Emacs stack exchange

;; shift + arrow keys
(define-key input-decode-map "\[1;2D" [S-left])
(define-key input-decode-map "\[1;2A" [S-up])
(define-key input-decode-map "\[1;2C" [S-right])
(define-key input-decode-map "\[1;2B" [S-down])

;; ctrl + arrow keys
(define-key input-decode-map "\[1;5D" [C-left])
(define-key input-decode-map "\[1;5A" [C-up])
(define-key input-decode-map "\[1;5C" [C-right])
(define-key input-decode-map "\[1;5B" [C-down])

;; meta + arrow keys
(define-key input-decode-map "\[1;3D" [M-left])
(define-key input-decode-map "\[1;3A" [M-up])
(define-key input-decode-map "\[1;3C" [M-right])
(define-key input-decode-map "\[1;3B" [M-down])

;; shift + ctrl + arrow keys
(define-key input-decode-map "\[1;6D" [S-M-left])
(define-key input-decode-map "\[1;6A" [S-M-up])
(define-key input-decode-map "\[1;6C" [S-M-right])
(define-key input-decode-map "\[1;6B" [S-M-down])

;; shift + meta + arrow keys
(define-key input-decode-map "\[1;4D" [S-M-left])
(define-key input-decode-map "\[1;4A" [S-M-up])
(define-key input-decode-map "\[1;4C" [S-M-right])
(define-key input-decode-map "\[1;4B" [S-M-down])

Interacting with X clipboard in terminal

When I run Emacs in terminal mode I still want to be able to copy stuff to the X clipboard. xclip is a package that does just that.

(use-package xclip :ensure t :init (xclip-mode t))

Backup files

Emacs should not create backup files in the current directory.

(setq backup-directory-alist `(("" . ,(concat user-emacs-directory "backups"))))

Coding settings

There are some common things I want to use for all every programming language I code in. For instance I would like error checking and auto-completion when it exists and line indicators that shows if a line is modified, added or removed.

Worth noting is that I turn off Transient Mark mode in Common-Code minor mode, because I like to use the tag stack for navigation (i.e activating a mark = temporarily “bookmark” current position, popping marker stack = jumping to last “bookmark). Besides the original behaviour (i.e beginning a region) is still available on C-SPC C-SPC.

(if (>= emacs-major-version 26)
    (add-hook 'prog-mode-hook 'display-line-numbers-mode)
  (progn
    (add-hook 'prog-mode-hook 'linum-mode)))
(add-hook 'prog-mode-hook (lambda () (transient-mark-mode -1)))
(setq company-idle-delay nil)

Quite a few of the programming languages I work with supports LSP, Microsoft’s Language Server Protocol, and wiring it prog-mode makes sense.

;; LSP mode is loaded whenever #'lsp or #'lsp-deferred is called
(use-package lsp-mode :ensure t :commands (lsp-deferred)
  :hook (prog-mode . lsp-deferred)
  :bind (:map lsp-signature-mode-map ("M-n" . nil))
  :config (define-key lsp-mode-map (kbd "C-c l")
            (lookup-key lsp-mode-map (kbd "s-l"))))

(use-package yasnippet :after lsp-mode :hook (lsp-mode . yas-minor-mode) :ensure t)
(use-package lsp-ui :after lsp-mode :ensure t
  :hook (lsp-mode . lsp-ui-mode)
  :bind (:map lsp-mode-map ("C-x C-h ." . lsp-ui-doc-show))
  :config (setq lsp-ui-flycheck-enable t
                lsp-ui-doc-enable nil
                lsp-ui-doc-use-child-frame nil
                lsp-ui-sideline-enable nil))

I want trailing white space to be removed automatically before saving.

(add-hook 'prog-mode-hook
          (lambda () (add-hook 'before-save-hook #'delete-trailing-whitespace)))

Other minor modes I want to have active in programming modes:

;; Folding of outline
(use-package hs-minor-mode :hook prog-mode :delight :bind (("C-<tab>" . #'hs-toggle-hiding)))

;; Automatically revert file when changed outside of Emacs
(use-package autorevert :delight auto-revert-mode :hook (prog-mode . auto-revert-mode))

(use-package subword-mode :hook prog-mode :delight)
(use-package "whitespace" :hook (prog-mode . whitespace-mode) :delight
  :config
  ;; Make the whitespace style really dark and light on the eyes
  ;; (for dark themes)
  (set-face-attribute 'whitespace-space nil :background nil :foreground "gray30")
  (set-face-attribute 'whitespace-hspace nil :background nil :foreground "gray30")
  (set-face-attribute 'whitespace-indentation nil :background nil :foreground "gray30")
  (set-face-attribute 'whitespace-newline nil :background nil :foreground "gray30")
  (set-face-attribute 'whitespace-tab nil :background nil :foreground "gray30")
  (setq whitespace-style
        '(face tabs tab-mark spaces space-mark trailing missing-newline-at-eof indentation empty lines-tail))
  )

(use-package hl-line-mode :hook prog-mode :delight)
(use-package hl-todo :ensure t :delight :hook hl-todo-mode)

(use-package column-number-mode :hook prog-mode :delight)

(use-package projectile :ensure t :delight
  :bind (("C-c p" . #'projectile-command-map)
         ("M-p" . #'projectile-command-map))
  :config (projectile-mode))

(use-package flycheck :ensure t :hook (prog-mode . flycheck-mode))

;; Hide modes in modeline
(use-package delight :ensure t)

(use-package company :ensure t :delight :hook (prog-mode . company-mode)
  :bind (:map prog-mode-map ("C-c RET" . company-complete)))

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

(use-package multiple-cursors :ensure t
  :bind (:map prog-mode-map
              ("C-S-c C-S-c" . mc/edit-lines)
              ("M-n" . mc/mark-next-symbol-like-this)
              ("M-p" . mc/mark-previous-symbol-like-this)
              ("C-c M->" . mc/mark-next-like-this)
              ("C-c M-<" . mc/mark-previous-like-this)
              ("C-," . mc/mark-pop)
              ("M-<mouse-1>" . mc/add-cursor-on-click)))

Formatting

I want to be able to easily run a formatter on my code since I don’t want to have to think about coding style.

(defun format-with-command (command)
  "Runs COMMAND on buffer and replaces its content with the output.

Used for integrating formatters that do not have elisp packages."
  (save-excursion
    (shell-command-on-region
     (point-min)      ;; starting point in buffer
     (point-max)      ;; ending point in buffer
     command          ;; command to run in buffer
     (current-buffer) ;; output buffer
     t                ;; replace contents
     (concat "*Custom formatter " command " Error Buffer*")
     t)))              ;; show error buffer

(defun format-with-command-query ()
  "Queries the user for a command to use as a formatter"
  (interactive)
  (format-with-command (read-from-minibuffer "Formatter command: ")))
(defun format-code (ask?)
  "Format buffer using formatter in assoc-list prog-mode-formatters..

prog-mode-formatters is an assoc-list on the form 'major-mode
. formatting-call' and formatting-call is invoked with '(funcall).'
"
  (interactive "P")
  (if (or (not (boundp 'prog-mode-formatters))
          ask?)
      (format-with-command-query)
    (let ((formatter (assoc major-mode prog-mode-formatters)))
      (if (eq nil formatter)
          (format-with-command-query)
        (funcall (cdr formatter))))))

;; Bind it to our formatting key-binding
(define-key prog-mode-map (kbd "C-c C-f") 'format-code)

For C and Java we want to use Clang-format for formatting, Go and Rust will continue to use their respective *fmt binaries.

(use-package clang-format :commands 'clang-format-buffer :ensure t)

(defvar prog-mode-formatters '((c-mode . clang-format-buffer)
                               (java-mode . clang-format-buffer)
                               (go-mode . gofmt)
                               (rust-mode . rust-format-buffer))
  "Alist containing major-mode and formatter pairs.")

Version control

I mostly use Git to handle my version control and while it certainly got somewhat of a steep learning curve and a few rough edges here and there I mostly find it intuitive.

To help me manage my Git repositories I use the fantastic package magit, which is a Git frontend to Emacs and one of the few Git frontends I really like.

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

Git-gutter+ is a package that shows a line’s status (added, modifid or deleted) in a file that is version controlled by Git.

(use-package git-gutter :ensure t :diminish t
  :hook (prog-mode . git-gutter-mode)
  :bind (:map prog-mode-map
              ;; Navigate on hunks
              ("C-x v n"   . git-gutter:next-hunk)
              ("C-x v p"   . git-gutter:previous-hunk)
              ;; Act on hunks
              ("C-x v =" . git-gutter:popup-hunk)
              ("C-x v r"   . git-gutter:revert-hunk)
              ("C-x v t"   . git-gutter:stage-hunk)
              ("C-x v U"   . git-gutter:update-all-windows)))

To help me interact with my Github repositories I use forge.

(use-package forge :after magit :ensure t)

Rust

I want to format Rust buffers on save:

(use-package rust-mode :ensure t :hook (rust-mode . lsp-deferred)
  :init (setq rust-format-on-save t
              rust-format-show-buffer nil    ;; don't popup error buffer
              rust-format-goto-problem nil)) ;; don't move point to error

Golang

This configuration sets up a Go mode where common-code minor mode is enabled together with a plethora of other useful stuff, such as linter and formatters etc.

(use-package go-mode :mode "\\.go\\'" :defer t
  :hook ((go-mode . lsp-deferred)
         (go-mode . (lambda ()
                     (add-hook 'before-save-hook #'lsp-format-buffer t t)
                     (add-hook 'before-save-hook #'lsp-organize-imports t t))))
  :bind (:map go-mode-map
              ("C-c C-f" . format-code) ;; otherwise binds to go-goto* functions
              ("C-c C-k" . godoc)))

I want to be able to run tests directly from within Emacs.

(use-package gotest :ensure t :after go-mode :config (setq go-test-verbose t)
  :bind (:map go-mode-map
              ("C-c C-t t" . go-test-current-test)
              ("C-c C-t f" . go-test-current-file)
              ("C-c C-t p" . go-test-current-project)))

Typescript / Javascript

(use-package typescript-mode :ensure t :mode ("\\.tsx?\\'") :hook ((typescript-mode . prettier-js-mode)))

(defun local/prettier-use-local-plugins()
  "Search project root for prettier plugins."
  (when projectile-mode
    (let ((arg (concat "--plugin-search-dir=" (projectile-project-root))))
      (setq-local prettier-js-args (append prettier-js-args `(,arg))))))

(use-package prettier-js :ensure t  :hook (prettier-js-mode . (lambda () (local/prettier-use-local-plugins))))

;; Enable formatting of typescript files with prettier
(add-to-list 'prog-mode-formatters '(typescript-mode . prettier-js))

Nix

When I edit nix files I want to have some syntax highlighting.

(use-package nix-mode :ensure t :mode "\\.nix\\'")
(add-to-list 'prog-mode-formatters '(nix-mode . nix-mode-format))

Java

I want Emacs to automatically download lsp-java and install the Eclipse project’s language server.

(use-package lsp-java :ensure t :hook (java-mode . lsp-deferred))

Personal customization

Give my setup a personal touch.

(setq user-full-name "Jacob Jonsson"
  user-mail-address "[email protected]")

I don’t like to type more than necessary, so why do I need to type 1-2 extra letters when the first letter is enough?

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

I’ve seen the splash screen enough times now, please don’t show it to me anymore.

(setq inhibit-splash-screen t)

Now that I’m trying out Dvorak (Svorak A5) these changes makes the transition between key layouts easier.

;; Bind C-z to C-x
(global-set-key (kbd "C-z") ctl-x-map)

;; Bind C-h to previous-line since C-p is no longer on the same half
;; of the keyboard
(global-set-key (kbd "C-x C-h") help-map)
(global-set-key (kbd "C-h") 'previous-line)

When modifying a file Emacs creates a hidden lock symlink pointing to the modified file. This is probably nice when you don’t want to accidentally open an unsaved and modified file in another Emacs instance, but it also breaks tools that watches file modifications in a directory. Therefore I choose to disable it.

(setq create-lockfiles nil)

There are sometimes when I need to interact with external programs. For instance I sometimes like to open URL’s in a more capable browser than EWW (even though it is very good!).

Firefox is currently my driver of choice.

(setq browse-url-browser-function 'browse-url-firefox
      browse-url-new-window-flag  t)

Visual appearance

It is great that you can start out learning Emacs like a normal person, using the mouse and navigating through the menu and tool bar. However, on a smaller screen I find it a waste of screen space.

(tool-bar-mode -1)
(menu-bar-mode -1)
(scroll-bar-mode -1)

I really like the gruvbox-dark theme. It’s a dark theme with good contrast and stuff.

;; Load theme
(use-package gruvbox-theme :ensure t
  :config (load-theme 'gruvbox-dark-hard t))

The fonts in font-preferences are the preferred fonts that I use on my system, in descending order. The first font that is available will be set as the main font for Emacs.

(use-package cl-lib :ensure t)
(defun font-existsp (font)
  "Check to see if the named FONT is available."
  (if (null (x-list-fonts font)) nil t))

(defun font-avail (fonts)
  "Finds the available fonts."
  (cl-remove-if-not 'font-existsp fonts))

(defvar font-preferences
  '( "Iosevka"
     "Hasklig"
     "Inconsolata"
     "Fira Code"
     "Source Code Pro"
     "PragmataPro"))

(unless (eq window-system nil)
  (let ((fonts (font-avail font-preferences)))
    (unless (null fonts) (progn
        (set-face-attribute 'default nil :font (car fonts))
        (set-face-attribute 'default nil :weight 'medium)))))

When using Hasklig we can have some degree of ligature support and this is configured below.

(use-package ligature
  :load-path "/home/jassob/.emacs.d/site-lisp/ligature.el"
  :config
  ;; Enable the "www" ligature in every possible major mode
  (ligature-set-ligatures 't '("www"))
  ;; Enable traditional ligature support in eww-mode, if the
  ;; `variable-pitch' face supports it
  (ligature-set-ligatures 'eww-mode '("ff" "fi" "ffi"))
  ;; Enable all Cascadia Code ligatures in programming modes
  (ligature-set-ligatures 'prog-mode '("|||>" "<|||" "<==>" "<!--" "####" "~~>" "***" "||=" "||>"
                                       ":::" "::=" "=:=" "===" "==>" "=!=" "=>>" "=<<" "=/=" "!=="
                                       "!!." ">=>" ">>=" ">>>" ">>-" ">->" "->>" "-->" "---" "-<<"
                                       "<~~" "<~>" "<*>" "<||" "<|>" "<$>" "<==" "<=>" "<=<" "<->"
                                       "<--" "<-<" "<<=" "<<-" "<<<" "<+>" "</>" "###" "#_(" "..<"
                                       "..." "+++" "/==" "///" "_|_" "www" "&&" "^=" "~~" "~@" "~="
                                       "~>" "~-" "**" "*>" "*/" "||" "|}" "|]" "|=" "|>" "|-" "{|"
                                       "[|" "]#" "::" ":=" ":>" ":<" "$>" "==" "=>" "!=" "!!" ">:"
                                       ">=" ">>" ">-" "-~" "-|" "->" "--" "-<" "<~" "<*" "<|" "<:"
                                       "<$" "<=" "<>" "<-" "<<" "<+" "</" "#{" "#[" "#:" "#=" "#!"
                                       "##" "#(" "#?" "#_" "%%" ".=" ".-" ".." ".?" "+>" "++" "?:"
                                       "?=" "?." "??" ";;" "/*" "/=" "/>" "//" "__" "~~" "(*" "*)"
                                       "\\\\" "://"))
  ;; Enables ligature checks globally in all buffers. You can also do it
  ;; per mode with `ligature-mode'.
  (global-ligature-mode t))

Counsel / Ivy

After having run with ido and smex for a while I wanted to try out Helm and while it worked quite satisfactorily I thought that it deviated too much from vanilla Emacs experience. My hope is that Ivy and counsel will be a more discrete mix.

;; Enable ivy on completion-read
(use-package ivy :ensure t :init (ivy-mode t))
;; Replace common functions with ivy-versions
(use-package counsel :ensure t
  :bind (("M-x" . counsel-M-x)
         ("C-c g" . counsel-git))
  :config (counsel-mode t))
(use-package amx :after counsel :ensure t)
;; Enable ivy powered search/occur function
(use-package swiper :ensure t :bind ("C-s" . swiper))

I sometimes use Imenu to quickly navigate inside the current file.

(define-key global-map (kbd "M-g M-m") #'imenu)

Org configuration

Begin the conditional loading:

(with-eval-after-load 'org

I prefer to have my org files in my ~/personal

(setq org-directory (file-name-as-directory (expand-file-name "~/personal"))
      org-default-notes-file (concat org-directory "organizer.org"))

To keep track of my notes and tasks I add some states that my notes and tasks could be in. For instance in my reading file, items could be READ, READING or WANT-TO-READ. The letters inside the parantheses defines keyboard shortcuts that can be used for selecting the state of the item. The special characters @ and ! defines how logging should be performed. Changing the state of an item to a state with a @ prompts you for a note and ! tells org that it should automatically log timestamp of the state change.

(setq org-todo-keywords
      '((sequence "IDEAS(i)" "TODO(t)" "URGENT(u@/!)"
                  "IN-PROGRESS(p!/@)" "WAITING(w@/@)"
                  "|" "DONE(d@)" "CANCELLED(c@)")
        (sequence "WANT-TO-READ(@)" "READING(!)" "|" "READ(@)")))

Many GTD-apps organize the tasks into projects and contexts, this is of course doable inside Org mode as well.

(setq org-tag-alist '(("@work" . ?w) ("@study" . ?s) ("@coding" . ?c)
                      ("@reading" . ?r) ("@home" . ?h)))

When I use org-gcal to synchronize my calendar with Emacs I want those files to end up in my calendar.

(setq org-agenda-files (list org-directory (concat org-directory "/calendar")))

My files

This is the structure of org files that I want to have and try to maintain.

#<<org-files>>

organizer.orgMain org file, used for org-capture and tasks etc
people.orgPeople-related tasks
journal.org.gpgJournal entries (encrypted)
studies.orgChalmers-related tasks
reading.orgOrg file for book notes
watching.orgOrg file for stuff I’d like to watch

Org Capture templates

I want to start using org-capture to quickly add tasks and notes and organize them in my life.

Quick legend of the template escape codes:

  • %^{PROMPT} - Org will prompt me with “PROMPT: ” and the input will replace the occurrance of %^{Task} in the template,
  • %? - Org will put the cursor here so I can edit the capture before refiling it,
  • %i - Org will insert the marked region from before the capture here,
  • %a - Org will insert an annotation here (,
  • %U - Org will insert an inactive timestamp here,
  • %l - Org will insert a literal link here,
(with-eval-after-load 'org
  (setq org-capture-templates
        `(("t" "Tasks" entry (file+headline ,org-default-notes-file "Inbox")
           "* TODO %^{Task}\nCaptured %<%Y-%m-%d %H:%M> %a\n%?\n\n%i\n")

          ("i" "Interrupting task" entry
           (file+headline ,org-default-notes-file "Inbox")
           "* IN-PROGRESS %^{Task}\n" :clock-in)

          ("j" "Journal entry" plain
           (file+datetree ,(concat org-directory "journal.org.gpg"))
           "%K - %a\n%i\n%?\n")

          ("J" "Journal entry with date" plain
           (file+datetree+prompt ,(concat org-directory "journal.org.gpg"))
           "%K - %a\n%i\n%?\n")

          ("B" "Book" entry
           (file+headline ,(concat org-directory "reading.org") "Books")
           "* WANT-TO-READ %^{Title}  %^g\n\n%i%?\n\n*Author(s)*: %^{Author}\n*Review on:* %^t\n%a %U\n")

          ("A" "Article" entry
           (file+headline ,(concat org-directory "reading.org") "Articles")
           "* WANT-TO-READ %^{Title}  %^g\n\n*Author(s)*: %^{Author}\n\n*Abstract*: %i%?\n\n[[%l][Link to paper]]\n")

          ("p" "Blog post" entry
           (file+headline ,(concat org-directory "reading.org") "Blog entries")
           "* WANT-TO-READ %^{Title}  %^g\n\n%i\n\n*Author(s)*: %^{Author}\n\n[[%l][Link to blog post]]\n")

          ("l" "Bookmark" entry
           (file+headline ,(concat org-directory "bookmarks.org") "Captured entries")
           "* [[%^{Link}][%^{Title}]]\n\n%i%?\n")

          ("n" "Notes" entry (file+datetree ,org-default-notes-file) "* %?\n\n%i\n%U\n")

          ;; Org protocol handlers
          ("pp" "Protocol Blog post" entry
           (file+headline ,(concat org-directory "reading.org") "Blog entries")
           "* WANT-TO-READ %:description  %^g\n\n%i\n\n*Author(s)*: %^{Author}\n\n[[%l][Link to blog post]]\n")

          ("c" "Protocol selection" entry (file+headline ,org-default-notes-file "Inbox")
           "* [[%:link][%:description]] \n\n#+BEGIN_QUOTE\n%i\n#+END_QUOTE\n\n%?\n\nCaptured: %U\n")))

  (bind-key "C-M-r" 'org-capture))

I then want to be able to capture stuff from the web using org-protocol.

(require 'org-protocol)
(setq org-protocol-protocol-alist org-protocol-protocol-alist-default)

Publishing

I want to be able to view my org documents so that I can see my progress and what I’ve got left to do and so on. Org publish works rather well for this scenario, even though I probably would like do some automation on when it does the publishing.

(with-eval-after-load 'org
  (require 'ox-html)
  (setq org-publish-project-alist
        `(("html"
           :base-directory ,org-directory
           :base-extension "org"
           :publishing-directory "/ssh:jassob:/var/www/org"
           :recursive t
           :publishing-function org-html-publish-to-html)

          ("org-static"
           :base-directory ,org-directory
           :base-extension "css\\|js\\|png\\|jpg\\|gif\\|pdf\\|mp3\\|ogg\\|swf"
           :publishing-directory "/ssh:jassob:/var/www/org"
           :recursive t
           :publishing-function org-publish-attachment)

          ("archive"
           :base-directory ,org-directory
           :base-extension "org_archive"
           :publishing-directory "/ssh:jassob:/var/www/org/archive"
           :publishing-function org-html-publish-to-html)

          ("web"
           :base-directory ,(concat org-directory "web/")
           :base-extension "org"
           :publishing-directory "/ssh:jassob:/var/www/"
           :publishing-function org-html-publish-to-html)

          ("jassob" :components ("html" "archive" "org-static" "web"))
          ("all" :components ("jassob"))))

  (defun local/publish-jassob ()
    "Publishes \"jassob\" project"
    (interactive)
    (org-publish "jassob" t))

  (defun local/publish-chalmers ()
    "Publishes \"chalmers\" project"
    (interactive)
    (org-publish "chalmers" t))

  (defun local/publish-web ()
    "Publishes \"web\" project"
    (interactive)
    (org-publish "web" t)))

Wrapping up

End conditional loading for org config

)

EVIL mode

I’m experimenting with EVIL mode in an attempt to learn Vi(m) keybindings. Since I am used to quitting and escaping stuff by C-g I want EVIL to transition to normal mode when I press C-g.

(defun evil-keyboard-quit ()
  "Keyboard quit and force normal state."
  (interactive)
  (and evil-mode (evil-force-normal-state))
  (keyboard-quit))

When EVIL is loaded I therefore want bind C-g to evil-keyboard-quit.

(use-package evil :commands 'evil-mode
  :bind
  (:map evil-normal-state-map   ("C-g" . #'evil-keyboard-quit))
  (:map evil-motion-state-map   ("C-g" . #'evil-keyboard-quit))
  (:map evil-motion-state-map   ("C-g" . #'evil-keyboard-quit))
  (:map evil-insert-state-map   ("C-g" . #'evil-keyboard-quit))
  (:map evil-window-map         ("C-g" . #'evil-keyboard-quit))
  (:map evil-operator-state-map ("C-g" . #'evil-keyboard-quit)))

Key stroke reminders

To help me remember my commands I use which-key, which displays a popup showing all the keybindings belonging to a prefix key.

(use-package which-key :delight t :ensure t :config (setq which-key-idle-delay 2.0))

(which-key-mode t)

Tree-like file history

I find Emacs default undo behaviour rather intuitive (of course a redo is just an undo of your last undo!), but I like being able to visualise the timeline of my file. Enters undo-tree-mode!

;; Display local file history as tree of edits
(use-package undo-tree :ensure t :delight
  :config
  (setq undo-tree-visualizer-timestamps t
        undo-tree-visualizer-diff t)
  (global-undo-tree-mode))

Distraction free writing

Sometimes I just want to have my code (or whatever I’m currently reading or writing) presented to me without any other distractions and this is where writeroom-mode (found here) comes in to play.

(use-package writeroom-mode :ensure t
  :init (setq writeroom-width 120)
  (add-hook 'writeroom-mode-hook (lambda () (display-line-numbers-mode -1)))
  :bind (:map writeroom-mode-map
              ("C-c C-w <" . #'writeroom-decrease-width)
              ("C-c C-w >" . #'writeroom-increase-width)
              ("C-c C-w =" . #'writeroom-adjust-width)
              ("s-?" . nil)
              ("C-c C-w SPC" . #'writeroom-toggle-mode-line))
        (:map global-map
              ("C-c C-M-w" . #'writeroom-mode)))

Mail configuration

Mu4e is a great mail user agent for Emacs, which lets us read, manage and compose mail.

Mu4e requires that the mail indexer mu is installed (and is usually installed in the same package). For Ubuntu/Debian distros the package is installed with:

sudo apt install mu4e

After that we need to make Emacs able to find mu4e package:

(add-to-list 'load-path "/usr/local/share/emacs/site-lisp/mu4e/")

Now let’s continue to set up my maildir directory (which is populated by the isync configuration in https://github.com/Jassob/dotfiles/blob/master/nix/.config/nixpkgs/home.nix#L342) and some other settings:

(with-eval-after-load 'mu4e
  (setq mu4e-maildir (expand-file-name "~/.mail/")
        mu4e-get-mail-command "mbsync -a"

        ;; prefer plain-text mail
        mu4e-view-prefer-html nil
        ;; enable inline images
        mu4e-view-show-images t
        ;; show full addresses in view message (instead of just names)
        ;; toggle per name with M-RET
        mu4e-view-show-addresses 't

        mu4e-update-interval nil
        mu4e-headers-auto-update t

        ;; every new email composition gets its own frame
        mu4e-compose-in-new-frame t
        mu4e-compose-signature-auto-include nil
        mu4e-compose-format-flowed t
        mu4e-compose-dont-reply-to-self t
        mu4e-compose-context-policy 'always-ask

        mu4e-context-policy 'pick-first

        message-kill-buffer-on-exit t

        ;; important to get mu4e to play with mbsync
        mu4e-change-filenames-when-moving t
        ;; don't save message to Sent Messages, IMAP takes care of this
        mu4e-sent-messages-behavior 'delete

        mu4e-confirm-quit nil)

  ;; to view selected message in the browser, no signin, just html mail
  (add-to-list 'mu4e-view-actions '("ViewInBrowser" . mu4e-action-view-in-browser) t)

  ;; use imagemagick, if available
  (when (fboundp 'imagemagick-register-types)
    (imagemagick-register-types))

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

  ;; increase luminesence for dark themes
  (setq shr-color-visible-luminance-min 80))

org-mu4e is a package that let’s us format the messages in org-mode and then convert them to HTML.

(with-eval-after-load 'mu4e
  (require 'org-mu4e)
  ;; convert org mode to HTML automatically
  (setq org-mu4e-convert-to-html t
        ;;store link to message if in header view, not to header query
        org-mu4e-link-query-in-headers-mode nil))

When we compose new messages we want to let the recepient’s email reader break the lines and not force our line width upon them. We also want to enable the spell checker.

(with-eval-after-load 'mu4e
  (require 'org-mime)
  (add-hook 'mu4e-compose-mode-hook
            (lambda ()
              (visual-line-mode)
              (org-mu4e-compose-org-mode)
              (use-hard-newlines -1)
              (flyspell-mode))))

Both my mail accounts are rather similar, so I’ll define a function to reduce the repetition needed to define them:

(defun my/make-mu4e-context (name email-address maildir &rest vars)
    "Create a mu4e context called NAME based in MAILDIR and uses
EMAIL-ADDRESS.

- NAME is shown when switching between different contexts and the first letter is also the shortcut.
- EMAIL-ADDRESS is used for authenticating with both IMAP and SMTP servers.
- MAILDIR is the subdirectory inside mu4e-maildir that contains the mail for this account, it should start with a /.
"
    (make-mu4e-context
          :name name
          :enter-func (lambda () (mu4e-message (concat "Entering context " name)))
          :leave-func (lambda () (mu4e-message (concat "Leaving context " name)))
          :match-func (lambda (msg)
                        (when msg
                          (mu4e-message-contact-field-matches msg '(:from :to :cc :bcc) user-mail-address)))
          :vars `((user-mail-address  . ,email-address)
                  (mu4e-sent-folder   . ,(concat maildir "/sent"))
                  (mu4e-drafts-folder . ,(concat maildir "/sent"))
                  (mu4e-trash-folder  . ,(concat maildir "/trash"))
                  (mu4e-refile-folder . ,(concat maildir "/all"))
                  (mu4e-maildir-shortcuts . ((,(concat maildir "/Inbox")   . ?i)
                                             (,(concat maildir "/all")     . ?a)
                                             (,(concat maildir "/starred") . ?s)))
                  (mu4e-bookmarks . ((,(concat "flag:unread AND NOT flag:trashed maildir:" maildir "*") "Unread messages" ?u)
                                     ("flag:unread AND NOT flag:trashed" "All unread messages" ?a)
                                     ("flag:flagged AND NOT flag:trashed" "Starred messages" ?s)
                                     ("date:today..now AND NOT flag:trashed" "Today's messages" ?t)
                                     ("date:7d..now AND NOT flag:trashed" "Last 7 days" ?7)
                                     ("flag:trashed" "Deleted messages" ?d)
                                     ("mime:image/*" "Messages with images" ?i)))
                  (message-send-mail-function . smtpmail-send-it)
                  (smtpmail-queue-dir . ,(concat mu4e-maildir maildir "/queue/cur"))
                  (smtpmail-smtp-user . ,email-address)
                  (smtpmail-starttls-credentials . (("smtp.gmail.com" 587 nil nil)))
                  (smtpmail-auth-credentials . (expand-file-name "~/.authinfo.gpg"))
                  (smtpmail-default-smtp-server . "smtp.gmail.com")
                  (smtpmail-smtp-server . "smtp.gmail.com")
                  (smtpmail-smtp-service . 587)
                  (smtpmail-debug-info . t)
                  (smtpmail-debug-verbose . t))))

Now let’s add the accounts:

(with-eval-after-load 'mu4e
  (require 'smtpmail)
  (setq smtpmail-queue-mail nil)

  (setq mu4e-contexts
        (list
         (my/make-mu4e-context "personal" my/personal-email-address "/personal")
         (my/make-mu4e-context "work" my/work-email-address "/work"))))

Reading and editing documents

I want to be able to read PDF documents with pdf-tools.

(use-package pdf-tools :ensure t :init (pdf-loader-install) :config (setq pdf-view-continuous nil))

Eshell

I found Eshell smart display on the Eshell article on Mastering Emacs and I think it is pretty neat! It lets me review a failing command and edit the command line to fix the error.

(require 'eshell)
(require 'em-smart)
(setq eshell-where-to-jump 'begin)
(setq eshell-review-quick-commands nil)
(setq eshell-smart-space-goes-to-end t)

Add linters and formatters to common-code-mode

Variable buffer-file-name could be used together with a asynch shell command.

Add work configuration

Add work config where browse-url opens links in Chrome etc.

Check out byte-compiled errors

When the code is byte-compiled there are some stuff that is not found, might be worth investigating whether (eval-and-compile) works better.

Integrate better with Nix

This is very interesting, maybe something I might take advantage of myself?

Move common-code-mode into prog-mode