Dennis' Emacs configuration file

This is my configuration file for emacs. This file is written using org-mode, which makes it a lot easier to keep track of changes in different sections. I'm not providing the source file—it wouldn't work on anyone else's machine anyways—, but on my quest of creating the perfect editor, I found it very useful to dig through webpages like these and be inspired by some of the awesome things the disciples of the Church of Emacs can come up with. Consider this a way of me giving back to the community.

The "real" init.el

This file is being loaded as the last line of my "real" init.el, which also sets up the package manager.

The real init.el looks like this:

;;; init.el --- Dennis Ogbe's emacs initialization file

;; Copyright (c) 2010-2017 Dennis Ogbe


;; Author: Dennis Ogbe <do@ogbe.net>
;; Maintainer: Dennis Ogbe <do@ogbe.net>
;; Created: 2 Apr 2017

;; Keywords: configuration, initialization
;; Homepage: https://ogbe.net

;; This file is not part of GNU Emacs.

;;; Commentary:

;; This file sets up `package.el', `org-mode', `dash', and `use-package' and then loads
;; the rest of my configuration, which is distributed across a selection of
;; org-mode files

;;; Code:

;; We enable or disable one of or several "layers", similar to how spacemacs
;; handles its configuration. Defaults to
(defvar do.layers/enabled-layers
  '(minimal faces org terminal snippets mode-line completion
    cpp latex octave scheme julia python matlab misc blog mail)
  "A list of the enabled Layers for this configuration.

Currently available layers:

- minimal: The minimum viable configuration
- faces: A few tricks with faces, mainly for graphical Emacs
- org: `org-mode' customizations
- terminal: settings for the terminal environment (`ansi-term')
- snippets: `yasnippet' configuration
- completion: `company-mode' configuration
- mode-line: Custom mode-line
- blog: Blogging setup
- mail: E-Mail configuration

- octave: GNU Octave settings
- cpp: C and C++ programming environment
- matlab: MATLAB programming environment
- julia: Julia programming environment
- python: Python programming environment
- scheme: Scheme programming environment
- latex: All things LaTeX (using AUCTeX)
- misc: Miscellaneous programming and text environments")

;; We enable one of our themes using this variable
(defvar do.theme/enabled-theme
  'dark
  "The choice of theme. The choices are:

- dark: Based on the internal `wombat-theme'
- light: Based on john2x's plan9-theme
- nil: No theme customizations at all")

;; set up the load path and package.el
(require 'package)
(setq package-archives
      '(("gnu" . "https://elpa.gnu.org/packages/")
        ("melpa" . "https://melpa.org/packages/")
        ("elpy" . "https://jorgenschaefer.github.io/packages/")
        ("org" . "http://orgmode.org/elpa/")))
(package-initialize)

;; We can customize the startup for a different machine by incluing elisp in
;; the special file "~/.emacs-site.el". This is loaded before any packages are
;; loaded and any variables set here might be overwritten. However, this can be
;; used to specify different themes or different layers for different machines,
;; for example.
(let ((site-file "~/.emacs-site.el"))
  (when (file-exists-p site-file)
    (load site-file)))

;; Bootstrap `use-package'
(unless (package-installed-p 'use-package)
  (package-refresh-contents)
  (package-install 'use-package))

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

;; refresh package.el
;; (package-refresh-contents)
;; there are numerous bug involving the ELPA signing key with the id
;; 474F05837FBDEF9B. I am not sure what exactly the issue is here, but calling
;; `use-package' on these two packages here seems to do the trick. I don't like
;; this.
;; (setq package-check-signature nil)
;; (use-package queue :ensure t :demand)
;; (use-package spinner :ensure t :demand)

;; load dash, which gives us neat tricks like the threading macro
(use-package dash
  :ensure t
  :demand
  :config
  (dash-enable-font-lock))

;; load org-mode using use-package. This is the only package for which
;; additional configuration is in settings.org by itself
(use-package org
  :init ; loaded before package is initialized
  ;; The background color of my org source code blocks needs to be defined before
  ;; org is loaded.
  (defface org-block-begin-line
    '((t (:foreground "#99968b" :background "#303030" :box (:style released-button))))
    "Face used for the line delimiting the begin of source blocks.")
  (defface org-block-end-line
    '((t (:foreground "#99968b" :background "#303030" :box (:style released-button))))
    "Face used for the line delimiting the end of source blocks.")
  :demand
  :ensure org-plus-contrib ; make sure to load org from orgmode.org
  :pin "org")

;; exec path
(setq exec-path (append exec-path `(,(concat (file-name-as-directory (getenv "HOME")) "bin" ))))

;; Moved the custom.el stuff into its own file called ~/.emacs.d/customize.el
(setq custom-file (concat user-emacs-directory
                          (file-name-as-directory "lisp")
                          "customize.el"))
(load custom-file)

;; Normally, I could use the =system-name= variable to get the current
;; hostname, but it seems to return the value of =hostname -f=, which is not
;; what I want. Therefore, I find the hostname manually by calling
;; =shell-command-to-string= and stripping some whitespace. This will probably
;; /not/ work on windows. Also, unfortunately, this hast to be defined before
;; tangling, since this is the variable we're checking while tangling
(let ((hn-executable "hostname"))
  (when (executable-find hn-executable)
    (setq hostname (->> hn-executable
                        (shell-command-to-string)
                        (replace-regexp-in-string "[ \t\n]*\\'" "")
                        (replace-regexp-in-string "\\`[ \t\n]*" "")))))

;; ansi-term directory tracking breaks when "system-name" evaluates to
;; "ava.Home". This is a dirty fix, but it works for now. Maybe I can
;; investigate further into this one day.
(setq system-name hostname)

;; finally, call org-babel to load the settings files for the individual
;; layers. The files get tangled first.
(dolist (layer do.layers/enabled-layers)
  (-> layer
       (symbol-name)
       (concat ".org")
       ((lambda (fn)
          (concat user-emacs-directory
                  (file-name-as-directory "config")
                  fn)))
       (org-babel-load-file)))

;;; init.el ends here

The important part is the very last line, where I call org-babel to load the settings file.

This file also takes care of loading my M-x customize... settings. I generally try to avoid using this, I think it's cleaner to write out all of my settings in settings.org, and every once in a while, I move some settings from customize.el in here.

These little helper functions let me access and reload my configuration whenever I want to make any changes.

(defun reload-settings ()
  (interactive)
  (org-babel-load-file "~/.emacs.d/settings.org"))
(defun settings ()
  (interactive)
  (find-file "~/.emacs.d/settings.org"))

With that out of the way, let's get started.

Security

Inspired by glyph's great post Your Text Editor is Malware. We need to fix some of emacs' security settings.

First, we need to make sure to actually check any TLS connections we're making against my local trust roots.

(setq tls-checktrust t)
(setq gnutls-verify-error t)

Now we need to change the value of tls-program to make sure gnutls knows where my root certs are.

(let ((trustfile "/etc/ssl/cert.pem"))
  (setq tls-program
        `(,(format  "gnutls-cli --x509cafile %s -p %%p %%h" trustfile)
          ,(format "openssl s_client -connect %%h:%%p -CAfile %s -no_ssl2 -ign_eof" trustfile)))
  (setq gnutls-trustfiles (list trustfile)))

Like mentioned in the post, the following snippet can be used to test whether my local root certs contain any bad certificates:

(let ((bad-hosts
       (loop for bad
             in `("https://wrong.host.badssl.com/"
                  "https://self-signed.badssl.com/")
             if (condition-case e
                    (url-retrieve
                     bad (lambda (retrieved) t))
                  (error nil))
             collect bad)))
  (if bad-hosts
      (error (format "tls misconfigured; retrieved %s ok"
                     bad-hosts))
    (url-retrieve "https://badssl.com"
                  (lambda (retrieved) t))))

Appearance, UI Elements, and Emacs server

First, start a server, and then set some important appearance settings. Note that some of the settings (especially the color theme) are still in the "real" init.el.

Server

Start the server first, but only if emacs is not currently running.

(load "server")
(unless (server-running-p) (server-start))

When connected to server using emacsclient, we want to kill the client using C-x k, the way it seems natural.

(add-hook 'server-switch-hook
          (lambda ()
            (when (current-local-map)
              (use-local-map (copy-keymap (current-local-map))))
            (local-set-key (kbd "C-x k") 'server-edit)))

Appearance

Theme & Faces

(load-theme 'wombat)
(set-face-attribute 'default nil :height 94)
(set-face-attribute 'fringe nil :background "#2d2d2d")
(set-face-attribute 'default nil :family "Source Code Pro")
(set-face-attribute 'font-lock-comment-face nil :slant 'italic)
(set-face-attribute 'font-lock-comment-face nil :weight 'semibold)
(set-fontset-font "fontset-default" 'unicode "DejaVu Sans Mono for Powerline")

UI Elements

Get rid of all those fancy UI elements that we don't need.

(menu-bar-mode -1)
(when (display-graphic-p)
  (tool-bar-mode -1)
  (scroll-bar-mode -1))

Remove the strange white line between two fringes.

(set-face-attribute 'vertical-border nil :foreground (face-attribute 'fringe :background))

Welcome Screen

We also want to get rid of the splash screen and start in the home directory.

(setq inhibit-startup-message t)
(setq inhibit-splash-screen t)
(setq initial-scratch-message nil)
(setq initial-buffer-choice "~/")

Window Geometry

Let's set the height and width of the window. The last line gets rid of the ugly bright white line when splitting a window.

(add-to-list 'default-frame-alist '(height . 40))
(add-to-list 'default-frame-alist '(width . 90))

Scrolling

This setting was probably what made me switch. I HATE the normal way emacs scrolls. This lets it scroll like in vim.

(setq scroll-step            1
      scroll-conservatively  10000)

Autosave and Backup files

It's annoying when autosave files are in the same directory. Too much clutter. Instead, let's save them somewhere in ~/.emacs.d/.

(defvar backup-dir (expand-file-name "~/.emacs.d/emacs_backup/"))
(defvar autosave-dir (expand-file-name "~/.emacs.d/autosave/"))
(setq backup-directory-alist (list (cons ".*" backup-dir)))
(setq auto-save-list-file-prefix autosave-dir)
(setq auto-save-file-name-transforms `((".*" ,autosave-dir t)))
(setq tramp-backup-directory-alist backup-directory-alist)
(setq tramp-auto-save-directory autosave-dir)

Window movement

Navigate through windows and frames using Shift-<Arrow>

(require 'framemove)
(windmove-default-keybindings)
(setq framemove-hook-into-windmove t)

Debugging

Set some keys to make debugging in GUD easier

(global-set-key (kbd "<f7>") 'gud-cont)
(global-set-key (kbd "<f6>") 'gud-step)
(global-set-key (kbd "<f5>") 'gud-next)
(global-set-key (kbd "<f8>") 'gud-finish)

Trailing whitespace

We want to show trailing whitespace. Trailing whitespace is the devil.

(require 'whitespace)
(setq-default show-trailing-whitespace t)

Don't show trailing whitespace in some modes

…but sometimes (especially in read-only buffers that I don't control), this gets annoying. Which is why we can add this small function to any hook that we want:

(defun no-trailing-whitespace ()
  (setq show-trailing-whitespace nil))

We already know two places to add it: the minibuffer and eww.

(add-hook 'minibuffer-setup-hook
          'no-trailing-whitespace)
(add-hook 'eww-mode-hook
          'no-trailing-whitespace)
(add-hook 'ielm-mode-hook
          'no-trailing-whitespace)
(add-hook 'gdb-mode-hook
          'no-trailing-whitespace)
(add-hook 'help-mode-hook
          'no-trailing-whitespace)

Dired

General Settings

The first two lines of this tell dired to stop asking me whether I want to recursively delete or copy, since I never respond to that question with "No".

The last line enables "Do What I Mean" mode for dired: If I'm in a split frame with two dired buffers, the default target to copy (and rename) will be the other window.

(setq dired-recursive-copies 'always)
(setq dired-recursive-deletes 'always)
(setq dired-dwim-target t)
(setq dired-listing-switches "-alh")

Automatically revert dired buffers

I also want dired to automatically revert, but to be quiet about it. The first line actually enables auto-revert for any buffers.

(global-auto-revert-mode 1)
(setq global-auto-revert-non-file-buffers t)
(setq auto-revert-verbose nil)

Less verbosity

We can show file details using the ( and ) keys.

(require 'dired-details)
(setq dired-details-hidden-string "")
(dired-details-install)

We want to omit the . and .. files, which are shown in dired by default.

(setq-default dired-omit-mode t)
(setq-default dired-omit-verbose nil)
(setq-default dired-omit-files "^\\.$\\|^\\.\\.$")

Some files we need to show regardless

;;(setq dired-omit-extensions (remove ".bin" dired-omit-extensions))

Opening files

This is mostly stolen from here. Uses nohup to spawn child processes without annoying new buffers. First, we define a list of default programs.

(require 'dired-x)
(require 'dired-aux)

(setq dired-guess-shell-alist-user
      '(("\\.pdf\\'" "evince")
        ("\\.\\(?:djvu\\|eps\\)\\'" "zathura")
        ("\\.\\(?:jpg\\|jpeg\\|png\\|gif\\|xpm\\)\\'" "eog")
        ("\\.\\(?:xcf\\)\\'" "gimp")
        ("\\.\\(?:csv\\|odt\\|ods\\)\\'" "libreoffice")
        ("\\.\\(?:mp4\\|mp3\\|mkv\\|avi\\|flv\\|ogv\\)\\(?:\\.part\\)?\\'"
         "vlc")
        ("\\.html?\\'" "firefox")))

Now, define a new function to start a process in the background.

(defvar dired-filelist-cmd
  '(("vlc" "-L")))

(defun dired-start-process (cmd &optional file-list)
  (interactive
   (let ((files (dired-get-marked-files
                 t current-prefix-arg)))
     (list
      (dired-read-shell-command "Open with: "
                                current-prefix-arg files)
      files)))
  (let (list-switch)
    (start-process
     cmd nil shell-file-name
     shell-command-switch
     (format
      "nohup 1>/dev/null 2>/dev/null %s \"%s\""
      (if (and (> (length file-list) 1)
               (setq list-switch
                     (cadr (assoc cmd dired-filelist-cmd))))
          (format "%s %s" cmd list-switch)
        cmd)
      (mapconcat #'expand-file-name file-list "\" \"")))))

At last, we remap two standard keys: We want !, which defaults to dired-do-shell-command, to run the old dired-do-aync-shell-command and use it for things like a quick unzip or unrar x or something like that. The old & shall be remapped to dired-start-process.

(define-key dired-mode-map "!" 'dired-do-async-shell-command)
(define-key dired-mode-map "&" 'dired-start-process)

Async

We can use async to copy files asynchronously.

(autoload 'dired-async-mode "dired-async.el" nil t)
(dired-async-mode 1)

Line Numbers

First, we customize the format that line-numbers are displayed with. We also want the current line to be highlighted.

(require 'linum)
(set-face-attribute 'linum nil
                    :background (face-attribute 'default :background)
                    :foreground (face-attribute 'font-lock-comment-face :foreground))
(defface linum-current-line-face
  `((t :background "gray30" :foreground "gold"))
  "Face for the currently active Line number")
(defvar my-linum-current-line-number 0)
(defun get-linum-format-string ()
  (setq-local my-linum-format-string
              (let ((w (length (number-to-string
                                (count-lines (point-min) (point-max))))))
                (concat " %" (number-to-string w) "d "))))
(add-hook 'linum-before-numbering-hook 'get-linum-format-string)
(defun my-linum-format (line-number)
  (propertize (format my-linum-format-string line-number) 'face
              (if (eq line-number my-linum-current-line-number)
                  'linum-current-line-face
                'linum)))
(setq linum-format 'my-linum-format)
(defadvice linum-update (around my-linum-update)
  (let ((my-linum-current-line-number (line-number-at-pos)))
    ad-do-it))
(ad-activate 'linum-update)

Next, we configure the looks of relative-line-numbers-mode.

(require 'relative-line-numbers)
(set-face-attribute 'relative-line-numbers-current-line nil
                    :background "gray30" :foreground "gold")
(setq relative-line-numbers-motion-function 'forward-visible-line)
(setq relative-line-numbers-format
      '(lambda (offset)
         (concat " " (number-to-string (abs offset)) " ")))

Toggle line numbers (num) or relative line numbers (rnum) in a safe manner by turning the other off in case it is on.

(defun num ()
  (interactive)
  (if (bound-and-true-p relative-line-numbers-mode)
      (relative-line-numbers-mode 'toggle))
  (linum-mode 'toggle))
(defun rnum ()
  (interactive)
  (if (bound-and-true-p linum-mode)
      (linum-mode 'toggle))
  (relative-line-numbers-mode 'toggle))

Fringe

Enable the fringe to show git diffs and extra information about the buffer. Apparently Git-Gutter breaks things, so I will disable that for now.

(fringe-mode '(4 . 4))
;;(when (window-system)
  ;;(require 'git-gutter-fringe))
;;(global-git-gutter-mode +1)

Mode Line

Powerline general settings

Using powerline to emulate the sleek look of spacemacs, but with the wombat colors. The wavy separators look really cool!

(column-number-mode 1)
(require 'powerline)
(if (display-graphic-p)
    (progn
      (setq powerline-default-separator 'wave)
      (setq powerline-height 25)))
(setq powerline-default-separator-dir '(right . left))

Powerline theme

My custom theme consists of three parts: First, I have a bunch of different faces that switch between an "inactive mode" and "active mode", based on whether a window is active or not. Second, I define a few functions that take care of displaying some information about the buffer. Third, I use the functions provided in powerline.el to dynamically set the mode-line-format variable. In doing so, I'm using a few cool unicode characters and some characters from the powerline fonts.

Faces

We first define some extra faces for the mode line.

;; first reset the faces that already exist
(set-face-attribute 'mode-line nil
                    :foreground (face-attribute 'default :foreground)
                    :family "Fira Sans"
                    :weight 'bold
                    :background (face-attribute 'fringe :background))
(set-face-attribute 'mode-line-inactive nil
                    :foreground (face-attribute 'font-lock-comment-face :foreground)
                    :background (face-attribute 'fringe :background)
                    :family "Fira Sans"
                    :weight 'bold
                    :box `(:line-width -2 :color ,(face-attribute 'fringe :background)))
(set-face-attribute 'powerline-active1 nil
                    :background "gray30")
(set-face-attribute 'powerline-inactive1 nil
                    :background (face-attribute 'default :background)
                    :box `(:line-width -2 :color ,(face-attribute 'fringe :background)))

;; these next faces are for the status indicator
;; read-only buffer
(make-face 'mode-line-read-only-face)
(make-face 'mode-line-read-only-inactive-face)
(set-face-attribute 'mode-line-read-only-face nil
                    :foreground (face-attribute 'default :foreground)
                    :inherit 'mode-line)
(set-face-attribute 'mode-line-read-only-inactive-face nil
                    :foreground (face-attribute 'default :foreground)
                    :inherit 'mode-line-inactive)

;; modified buffer
(make-face 'mode-line-modified-face)
(make-face 'mode-line-modified-inactive-face)
(set-face-attribute 'mode-line-modified-face nil
                    :foreground (face-attribute 'default :background)
                    :background "#e5786d"
                    :inherit 'mode-line)
(set-face-attribute 'mode-line-modified-inactive-face nil
                    :foreground (face-attribute 'default :background)
                    :background "#e5786d"
                    :inherit 'mode-line-inactive)

;; unmodified buffer
(make-face 'mode-line-unmodified-face)
(make-face 'mode-line-unmodified-inactive-face)
(set-face-attribute 'mode-line-unmodified-face nil
                    :foreground (face-attribute 'font-lock-comment-face :foreground)
                    :inherit 'mode-line)
(set-face-attribute 'mode-line-unmodified-inactive-face nil
                    :foreground (face-attribute 'font-lock-comment-face :foreground)
                    :inherit 'mode-line-inactive)

;; the remote indicator
(make-face 'mode-line-remote-face)
(make-face 'mode-line-remote-inactive-face)
(set-face-attribute 'mode-line-remote-face nil
                    :foreground (face-attribute 'font-lock-comment-face :foreground)
                    :background (face-attribute 'default :background)
                    :inherit 'mode-line)
(set-face-attribute 'mode-line-remote-inactive-face nil
                    :foreground (face-attribute 'font-lock-comment-face :foreground)
                    :background (face-attribute 'default :background)
                    :inherit 'mode-line-inactive)

;; the current file name
(make-face 'mode-line-filename-face)
(make-face 'mode-line-filename-inactive-face)
(set-face-attribute 'mode-line-filename-face nil
                    :foreground (face-attribute 'font-lock-type-face :foreground)
                    :background (face-attribute 'default :background)
                    :inherit 'mode-line)
(set-face-attribute 'mode-line-filename-inactive-face nil
                    :foreground (face-attribute 'font-lock-comment-face :foreground)
                    :background (face-attribute 'default :background)
                    :inherit 'mode-line-inactive)

;; the major mode name
(make-face 'mode-line-major-mode-face)
(make-face 'mode-line-major-mode-inactive-face)
(set-face-attribute 'mode-line-major-mode-face nil
                    :foreground (face-attribute 'default :foreground)
                    :inherit 'powerline-active1)
(set-face-attribute 'mode-line-major-mode-inactive-face nil
                    :box `(:line-width -2 :color ,(face-attribute 'fringe :background))
                    :foreground (face-attribute 'font-lock-comment-face :foreground)
                    :inherit 'powerline-inactive1)

;; the minor mode name
(make-face 'mode-line-minor-mode-face)
(make-face 'mode-line-minor-mode-inactive-face)
(set-face-attribute 'mode-line-minor-mode-face nil
                    :foreground (face-attribute 'font-lock-comment-face :foreground)
                    :inherit 'powerline-active1)
(set-face-attribute 'mode-line-minor-mode-inactive-face nil
                    :box `(:line-width -2 :color ,(face-attribute 'fringe :background))
                    :foreground (face-attribute 'powerline-inactive1 :background)
                    :inherit 'powerline-inactive1)

;; the position face
(make-face 'mode-line-position-face)
(make-face 'mode-line-position-inactive-face)
(set-face-attribute 'mode-line-position-face nil
                    :background (face-attribute 'default :background)
                    :inherit 'mode-line)
(set-face-attribute 'mode-line-position-inactive-face nil
                    :foreground (face-attribute 'font-lock-comment-face :foreground)
                    :background (face-attribute 'default :background)
                    :inherit 'mode-line-inactive)

;; the 80col warning face
(make-face 'mode-line-80col-face)
(make-face 'mode-line-80col-inactive-face)
(set-face-attribute 'mode-line-80col-face nil
                    :background "#e5786d"
                    :foreground (face-attribute 'default :background)
                    :inherit 'mode-line)
(set-face-attribute 'mode-line-80col-inactive-face nil
                    :foreground (face-attribute 'font-lock-comment-face :foreground)
                    :background (face-attribute 'default :background)
                    :inherit 'mode-line-inactive)

;; the buffer percentage face
(make-face 'mode-line-percentage-face)
(make-face 'mode-line-percentage-inactive-face)
(set-face-attribute 'mode-line-percentage-face nil
                    :foreground (face-attribute 'font-lock-comment-face :foreground)
                    :inherit 'mode-line)
(set-face-attribute 'mode-line-percentage-inactive-face nil
                    :foreground (face-attribute 'font-lock-comment-face :foreground)
                    :inherit 'mode-line-inactive)

;; the directory face
(make-face 'mode-line-shell-dir-face)
(make-face 'mode-line-shell-dir-inactive-face)
(set-face-attribute 'mode-line-shell-dir-face nil
                    :foreground (face-attribute 'font-lock-comment-face :foreground)
                    :inherit 'powerline-active1)
(set-face-attribute 'mode-line-shell-dir-inactive-face nil
                    :foreground (face-attribute 'font-lock-comment-face :foreground)
                    :inherit 'powerline-inactive1)

Helper functions

  • shorten-directory

    Now we want to define Amit's shorten-directory function. This will be used when in an ansi-term buffer.

    (defun shorten-directory (dir max-length)
      "Show up to `max-length' characters of a directory name `dir'."
      (let ((path (reverse (split-string (abbreviate-file-name dir) "/")))
            (output ""))
        (when (and path (equal "" (car path)))
          (setq path (cdr path)))
        (while (and path (< (length output) (- max-length 4)))
          (setq output (concat (car path) "/" output))
          (setq path (cdr path)))
        (when path
          (setq output (concat ".../" output)))
        output))
    
  • dennis-powerline-narrow

    This detects the current state of narrowing. It is a slight modification of the original function powerline-narrow from powerline.el.

    (defpowerline dennis-powerline-narrow
      (let (real-point-min real-point-max)
        (save-excursion
          (save-restriction
            (widen)
            (setq real-point-min (point-min) real-point-max (point-max))))
        (when (or (/= real-point-min (point-min))
                  (/= real-point-max (point-max)))
          (propertize (concat (char-to-string #x2691) " Narrow")
                      'mouse-face 'mode-line-highlight
                      'help-echo "mouse-1: Remove narrowing from the current buffer"
                      'local-map (make-mode-line-mouse-map
                                  'mouse-1 'mode-line-widen)))))
    
  • dennis-powerline-vc

    This is a modification of the original powerline-vc function. It correctly displays the right glyphs using emacs' VC interface.

    (defpowerline dennis-powerline-vc
      (when (and (buffer-file-name (current-buffer)) vc-mode)
        (if window-system
            (let ((backend (vc-backend (buffer-file-name (current-buffer)))))
              (when backend
                (format "%s %s: %s"
                        (char-to-string #xe0a0)
                        backend
                        (vc-working-revision (buffer-file-name (current-buffer)) backend)))))))
    

mode-line-format

Now we can set the mode-line-format variable to what we want.

(setq-default
 mode-line-format
 '("%e"
   (:eval
    (let* ((active (powerline-selected-window-active))

           ;; toggle faces between active and inactive
           (mode-line (if active 'mode-line 'mode-line-inactive))
           (face1 (if active 'powerline-active1 'powerline-inactive1))
           (face2 (if active 'powerline-active2 'powerline-inactive2))
           (read-only-face (if active 'mode-line-read-only-face 'mode-line-read-only-inactive-face))
           (modified-face (if active 'mode-line-modified-face 'mode-line-modified-inactive-face))
           (unmodified-face (if active 'mode-line-unmodified-face 'mode-line-unmodified-inactive-face))
           (position-face (if active 'mode-line-position-face 'mode-line-position-inactive-face))
           (80col-face (if active 'mode-line-80col-face 'mode-line-80col-inactive-face))
           (major-mode-face (if active 'mode-line-major-mode-face 'mode-line-major-mode-inactive-face))
           (minor-mode-face (if active 'mode-line-minor-mode-face 'mode-line-minor-mode-inactive-face))
           (filename-face (if active 'mode-line-filename-face 'mode-line-filename-inactive-face))
           (percentage-face (if active 'mode-line-percentage-face 'mode-line-percentage-inactive-face))
           (remote-face (if active 'mode-line-remote-face 'mode-line-remote-inactive-face))
           (shell-dir-face (if active 'mode-line-shell-dir-face 'mode-line-shell-dir-inactive-face))

           ;; get the separators
           (separator-left (intern (format "powerline-%s-%s"
                                           (powerline-current-separator)
                                           (car powerline-default-separator-dir))))
           (separator-right (intern (format "powerline-%s-%s"
                                            (powerline-current-separator)
                                            (cdr powerline-default-separator-dir))))

           ;; the right side
           (rhs (list
                 (dennis-powerline-vc minor-mode-face 'r)
                 (funcall separator-right face1 position-face)
                 (powerline-raw " " position-face)
                 (powerline-raw (char-to-string #xe0a1) position-face)
                 (powerline-raw " " position-face)
                 (powerline-raw "%4l" position-face 'r)
                 ;; display a warning if we go above 80 columns
                 (if (>= (current-column) 80)
                     (funcall separator-right position-face 80col-face)
                   (powerline-raw (char-to-string #x2502) position-face))
                 (if (>= (current-column) 80)
                     (powerline-raw "%3c" 80col-face 'l)
                   (powerline-raw "%3c" position-face 'l))
                 (if (>= (current-column) 80)
                     (powerline-raw " " 80col-face)
                   (powerline-raw " " position-face))
                 (if (>= (current-column) 80)
                     (funcall separator-left 80col-face percentage-face)
                   (funcall separator-left position-face percentage-face))
                 (powerline-raw " " percentage-face)
                 (powerline-raw "%6p" percentage-face 'r)))

           ;; the left side
           (lhs (list
                 ;; this is the modified status indicator
                 (cond (buffer-read-only
                        (powerline-raw "  " read-only-face))
                       ((buffer-modified-p)
                        ;; do not light up when in an interactive buffer. Set
                        ;; ML-INTERACTIVE? in hooks for interactive buffers.
                        (if (not (bound-and-true-p ml-interactive?))
                            (powerline-raw "  " modified-face)
                          (powerline-raw "  " unmodified-face)))
                       ((not (buffer-modified-p))
                        (powerline-raw "  " unmodified-face)))
                 (cond (buffer-read-only
                        (powerline-raw (concat (char-to-string #xe0a2) " ") read-only-face 'l))
                       ((buffer-modified-p)
                        (if (not (bound-and-true-p ml-interactive?))
                            (powerline-raw (concat (char-to-string #x2621) " ") modified-face 'l)
                          (powerline-raw (concat (char-to-string #x259e) " ") unmodified-face 'l)))
                       ((not (buffer-modified-p))
                        (powerline-raw (concat (char-to-string #x26c1) " ") unmodified-face 'l)))
                 (cond (buffer-read-only
                        (funcall separator-right read-only-face filename-face))
                       ((buffer-modified-p)
                        (if (not (bound-and-true-p ml-interactive?))
                            (funcall separator-right modified-face filename-face)
                          (funcall separator-right unmodified-face filename-face)))
                       ((not (buffer-modified-p))
                        (funcall separator-right unmodified-face filename-face)))
                 ;; remote indicator
                 (when (file-remote-p default-directory)
                   (powerline-raw (concat " " (char-to-string #x211b)) remote-face))
                 ;; filename and mode info
                 (powerline-buffer-id filename-face 'l)
                 (powerline-raw " " filename-face)
                 (funcall separator-left filename-face major-mode-face)
                 ;; do not need mode info when in ansi-term
                 (unless (bound-and-true-p show-dir-in-mode-line?)
                   (powerline-major-mode major-mode-face 'l))
                 (unless (bound-and-true-p show-dir-in-mode-line?)
                   (powerline-process major-mode-face 'l))
                 ;; show a flag if in line mode in terminal
                 (when (and (bound-and-true-p show-dir-in-mode-line?) (term-in-line-mode))
                   (powerline-raw (concat (char-to-string #x2691) " Line") major-mode-face))
                 (powerline-raw " " major-mode-face)
                 ;; little trick to move the directory name to the mode line
                 ;; when inside of emacs set SHOW-DIR-IN-MODE-LINE? to enable
                 (if (bound-and-true-p show-dir-in-mode-line?)
                     (when (not (file-remote-p default-directory))
                       (powerline-raw (shorten-directory default-directory 45)
                                      shell-dir-face))
                   (powerline-minor-modes minor-mode-face 'l))
                 (unless (bound-and-true-p show-dir-in-mode-line?)
                   (dennis-powerline-narrow major-mode-face 'l)))))

      ;; concatenate it all together
      (concat (powerline-render lhs)
              (powerline-fill face1 (powerline-width rhs))
              (powerline-render rhs))))))

Toggle Mode Line

Sometimes we don't want a mode-line. Use M-x hidden-mode-line to activate.

(defvar-local hidden-mode-line-mode nil)
(define-minor-mode hidden-mode-line-mode
  "Minor mode to hide the mode-line in the current buffer."
  :init-value nil
  :global t
  :variable hidden-mode-line-mode
  :group 'editing-basics
  (if hidden-mode-line-mode
      (setq hide-mode-line mode-line-format
            mode-line-format nil)
    (setq mode-line-format hide-mode-line
          hide-mode-line nil))
  (force-mode-line-update)
  ;; Apparently force-mode-line-update is not always enough to
  ;; redisplay the mode-line
  (redraw-display)
  (when (and (called-interactively-p 'interactive)
             hidden-mode-line-mode)
    (run-with-idle-timer
     0 nil 'message
     (concat "Hidden Mode Line Mode enabled.  "
             "Use M-x hidden-mode-line-mode to make the mode-line appear."))))

If I ever feel like hiding the mode-line by default, I could do something like this: (this is currently not enabled)

(add-hook 'after-change-major-mode-hook 'hidden-mode-line-mode)

Cursor

Highlight current line

Globally enable this, turn off when not needed.

(global-hl-line-mode 1)
(make-variable-buffer-local 'global-hl-line-mode)

Blink forever

(setq blink-cursor-blinks 0)

Change cursor based on reading or writing

Always reset the cursor color and the faces of the highlight line. Also, use vertical bar if we're in edit mode, and use box if in read-only mode, (almost like in gvim…)

(defun dennis-set-cursor ()
  (set-cursor-color "gold") ;; set cursor color to gold
  (set-face-attribute 'region nil :background "gold" :foreground "black")
  (set-face-background 'hl-line "gray30")
  (set-face-foreground 'highlight nil)
  (set-face-underline 'hl-line nil)
  (cond
   (buffer-read-only
    (setq cursor-type 'box))
   (t
    (setq cursor-type 'bar)))
  ;; red cursor for overwrite mode
  (when overwrite-mode
    (set-cursor-color "red")))
(dennis-set-cursor)

This needs to run after every command, since some modes screw with the cursor.

(add-hook 'post-command-hook 'dennis-set-cursor)

Keys

Use Emacs keybindings everywhere

To use emacs (GNU Readline) keybindings in other apps like Chrome and Firefox, we need to run this little command. This only works in GNOME.

gsettings set \
  org.gnome.desktop.interface \
  gtk-key-theme "Emacs"

Change a key for current buffer only

(defun buffer-local-set-key (key func)
      (interactive "KSet key on this buffer: \naCommand: ")
      (let ((name (format "%s-magic" (buffer-name))))
        (eval
         `(define-minor-mode ,(intern name)
            "Automagically built minor mode to define buffer-local keys."))
        (let* ((mapname (format "%s-map" name))
               (map (intern mapname)))
          (unless (boundp (intern mapname))
            (set map (make-sparse-keymap)))
          (eval
           `(define-key ,map ,key func)))
        (funcall (intern name) t)))

To undo, use

(buffer-local-set-key key nil)

Spaces and tabs

Spaces instead of tabs

Tabs are evil! I want spaces instead of tabs, and want exactly 2 spaces instead of a tab. Note to self: Apparently emacs is smart enough to not do this in Python, which is a good thing.

(setq-default indent-tabs-mode nil)
(setq-default tab-width 2)
(setq-default tab-stop-list (number-sequence 2 120 2))
(setq c-basic-indent 2)
(setq sh-basic-offset 2)

Exceptions

Makefiles

Makefiles are not so friendly when it comes to spaces.

(defun my-tabs-makefile-hook ()
  (setq indent-tabs-mode t))
(add-hook 'makefile-mode-hook 'my-tabs-makefile-hook)

Parentheses

Show parens

This mode highlights the mathing parenthesis on point.

(show-paren-mode 1)
(setq show-paren-delay 0)

Highlight Parens

This mode highlights the parentheses surrounding the point in shades of red. It works quite well for c-based languages and bash and other things, which is why I enable it globally.

(define-globalized-minor-mode global-highlight-parentheses-mode
  highlight-parentheses-mode
  (lambda ()
    (highlight-parentheses-mode t)))
(global-highlight-parentheses-mode t)

Rainbow delimiters for LISP

But for lisp like languages, I want to witness the full power of colorful rainbow-delimiters! I will even set them to pastel versions of the rainbow colors stolen from this wallpaper.

(require 'rainbow-delimiters)

(set-face-attribute 'rainbow-delimiters-depth-1-face nil
                    :foreground "#78c5d6")
(set-face-attribute 'rainbow-delimiters-depth-2-face nil
                    :foreground "#bf62a6")
(set-face-attribute 'rainbow-delimiters-depth-3-face nil
                    :foreground "#459ba8")
(set-face-attribute 'rainbow-delimiters-depth-4-face nil
                    :foreground "#e868a2")
(set-face-attribute 'rainbow-delimiters-depth-5-face nil
                    :foreground "#79c267")
(set-face-attribute 'rainbow-delimiters-depth-6-face nil
                    :foreground "#f28c33")
(set-face-attribute 'rainbow-delimiters-depth-7-face nil
                    :foreground "#c5d647")
(set-face-attribute 'rainbow-delimiters-depth-8-face nil
                    :foreground "#f5d63d")
(set-face-attribute 'rainbow-delimiters-depth-9-face nil
                    :foreground "#78c5d6")

We also want to make unmatched parens stand out more:

(set-face-attribute 'rainbow-delimiters-unmatched-face nil
                    :foreground 'unspecified
                    :inherit 'show-paren-mismatch
                    :strike-through t)

Now we just need to adjust the hook for lisp-like languages. Possibly have to add clojure, if I ever want to mess with that.

(add-hook 'emacs-lisp-mode-hook 'rainbow-delimiters-mode)
(add-hook 'lisp-mode-hook 'rainbow-delimiters-mode)

Insert closing parens automagically

(electric-pair-mode 1)

Paredit

This will definitely take some getting used to…

(autoload 'enable-paredit-mode "paredit" "Turn on pseudo-structural editing of Lisp code." t)
(defun turn-on-paredit ()
  "Turn on paredit and disable electric-pair-mode, since that is obsolete with paredit."
  (enable-paredit-mode))
(add-hook 'emacs-lisp-mode-hook 'turn-on-paredit)

Paredit overwrites the C-j binding to eval-last-sexp. This needs to be undone.

(eval-after-load 'paredit
   #'(define-key paredit-mode-map (kbd "C-j") nil))

Ibuffer

Combining some Ibuffer tips from here and here. Ibuffer lets me filter the list of all currently open buffers.

Filter groups

My filter groups are defined below.

(require 'ibuffer)
(setq my-ibuffer-filter-group-name "my-filters")
(setq ibuffer-saved-filter-groups
      (list (nreverse
             `(("Directories" (mode . dired-mode))
               ("Magit" (name . "^\\*magit.*$"))
               ("Org" (mode . org-mode))
               ("Shell" (or (mode . term-mode)
                            (mode . eshell-mode)
                            (mode . shell-mode)))
               ("IRC" (mode . erc-mode))
               ("Global" (name . "^\\*.*\\*$"))
               ("Interactive" (or (mode . matlab-shell-mode)
                                  (mode . inferior-julia-shell-mode)
                                  (mode . inferior-python-mode)
                                  (mode . inferior-octave-mode)
                                  (mode . inferior-lisp-mode)
                                  (mode . inferior-scheme-mode)
                                  (mode . ielm-mode)))
               ,my-ibuffer-filter-group-name))))

Reverse the order of the filter groups. Kind of confusing: Since I'm reversing the order of the groups above, this snippet ensures that the groups are ordered in the way they are written above, with the "Default" group on top. This advice might need to be ported to the new advice system soon.

(defadvice ibuffer-generate-filter-groups
    (after reverse-ibuffer-groups () activate)
  (setq ad-return-value (nreverse ad-return-value)))

Other settings

Only show groups that have active buffers

(setq ibuffer-show-empty-filter-groups nil)

Don't show the summary or headline

(setq ibuffer-display-summary nil)

Display more characters in the buffer name column, also convert sizes to a human-readable format. Stolen from the wiki.

;; Use human readable Size column instead of original one
(define-ibuffer-column size-h
  (:name "Size" :inline t)
  (cond
   ((> (buffer-size) 1000000) (format "%7.1fM" (/ (buffer-size) 1000000.0)))
   ((> (buffer-size) 100000) (format "%7.0fk" (/ (buffer-size) 1000.0)))
   ((> (buffer-size) 1000) (format "%7.1fk" (/ (buffer-size) 1000.0)))
   (t (format "%8d" (buffer-size)))))

;; Modify the default ibuffer-formats
(setq ibuffer-formats
      '((mark modified read-only " "
              (name 40 60 :left :elide)
              " "
              (size-h 9 -1 :right)
              " "
              (mode 16 16 :left :elide)
              " "
              filename-and-process)))

Hooks

Automagically keep the buffer list up to date, enable the filter groups defined above, disable trailing whitespace

(defun my-ibuffer-hooks ()
  (ibuffer-auto-mode 1)
  (ibuffer-switch-to-saved-filter-groups my-ibuffer-filter-group-name)
  (no-trailing-whitespace))
(add-hook 'ibuffer-mode-hook 'my-ibuffer-hooks)

Alias/keybinding - enable instead of list-buffers

(defalias 'list-buffers 'ibuffer)
(global-set-key (kbd "C-x C-b") 'ibuffer)

Terminal Emulator

I'm using ansi-term, because it's the closest thing to a real terminal I can get. RIP tmux. We had a good run.

Choice of shell to launch

Also disables the query of which shell to run. Go fish! We need to make sure that we get the right path, though.

(defvar my-term-shell "/usr/bin/zsh")
(defadvice ansi-term (before force-bash)
  (interactive (list my-term-shell)))
(ad-activate 'ansi-term)

Close on exit

Don't linger around when I don't need you anymore.

(defadvice term-sentinel (around my-advice-term-sentinel (proc msg))
  (if (memq (process-status proc) '(signal exit))
      (let ((buffer (process-buffer proc)))
        ad-do-it
        (kill-buffer buffer))
    ad-do-it))
(ad-activate 'term-sentinel)

Mode toggle

We can either be in char mode, which is more like a traditional terminal emulator, or in line mode, which is more akin th M-x shell behavior. Toggle with C-x C-j.

(defun term-toggle-mode ()
  (interactive)
  (if (term-in-line-mode)
      (term-char-mode)
    (term-line-mode)))

Terminal hooks

(defun my-term-hook ()
  (goto-address-mode)
  (local-set-key "\C-c\C-j" 'term-toggle-mode) ;; toggle line/char mode
  (local-set-key "\C-c\C-k" 'term-toggle-mode)
  (setq global-hl-line-mode nil)
  (setq term-buffer-maximum-size 10000)
  (setq-local ml-interactive? t) ;; for mode line
  (setq-local show-dir-in-mode-line? t) ;; also mode linec'
  (setq show-trailing-whitespace nil)
  ;; disable company in favor of shell completion
  (company-mode -1))
(add-hook 'term-mode-hook 'my-term-hook)

Alias

Run the terminal by doing M-x sh

(defalias 'sh 'ansi-term)

Org-Mode

Org-mode might the emacs killer app.

General settings

Some general settings for org.

(require 'org)
(setq-default
 org-return-follows-link t
 org-image-actual-width '(400)
 org-highlight-latex-and-related '(latex script entities))

Syntax highlighting in Code Blocks

Very important if your config file is a .org document… Also, add native <tab> behavior in source blocks.

(setq
 org-src-fontify-natively t
 org-src-tab-acts-natively t)

We also want this in LaTeX output!

(setq org-latex-listings 'minted)

HTML Export

For HTML output, we want to be able to set a custom stylesheet.

(setq org-html-htmlize-output-type 'css)

The following code reads some CSS and adds it as a string to the HTML header that will be embedded in every .org file that is exported.

When exporting an org-file to my blog, this variable will be overridden and instead I will point to the relevant .css files. For "standalone" HTML files, I want to embed the CSS into the HTML header.

(setq org-html-head
      (with-temp-buffer
        (let ((csstidy "csstidy")
              (csstidy-args " --template=highest --silent=true")
              (css-dir (file-name-as-directory "~/repos/blog/res"))
              (css-files '("code.css" "main.css")))
          (insert "<style type=\"text/css\">\n")
          (dolist (file css-files)
            (insert (shell-command-to-string
                     (concat csstidy " " css-dir file csstidy-args))))
          (insert "</style>")
          (buffer-string))))

org-latex settings

For some reason, explained here, we need to run pdflatex with the -shell-escape flag. This is getting too compilcated, so we're just going to run latexmk from org.

(setq org-latex-pdf-process (list "latexmk -f -pdf %f"))

If we want to export to a documentclass other than article, we need to add its structure to the following list. To use that class, we can then put the following in the org-mode file header: #+LATEX_CLASS: <class>. To add options to the class, we add the following: #+LATEX_CLASS_OPTIONS: [op1, op2,...].

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

Default Applications

Org opens PDFs by default in gv… change that to evince. Also open HTML in Chrome.

(setq org-file-apps '((auto-mode . emacs)
                      ("\\.x?html?\\'" . "firefox %s")
                      ("\\.pdf\\'" . "evince \"%s\"")
                      ("\\.pdf::\\([0-9]+\\)\\'" . "evince \"%s\" -p %1")
                      ("\\.pdf.xoj" . "xournal %s")))

org-babel

Babel lets the user run code inside an org-mode document. I feel like this can come in handy one day.

Active Babel languages

(org-babel-do-load-languages
 'org-babel-load-languages
 '(
   (python . t)
   (emacs-lisp . t)
   (matlab . t)
   (octave . t)
   (latex . t)
   (js . t)
   (sh . t)
   (C . t)
   (ditaa . t)
   ))

org-babel settings

(setq org-babel-matlab-with-emacs-link nil)
(setq org-confirm-babel-evaluate nil)
(setq org-export-babel-evaluate nil) ;; can be bad for long simulations
;;; display/update images in the buffer after I evaluate
(add-hook 'org-babel-after-execute-hook 'org-display-inline-images 'append)

ditaa

Ditaa is a small tool that takes ASCII art and renders it as neat diagrams. Combined with org, I can sketch out ideas directly in an org buffer using the emacs Artist mode.

(setq org-ditaa-jar-path "~/.emacs.d/random/ditaa/ditaa0_9.jar")

org-present

Use with M-x org-present. More info here. We configure this by adding functionality to the hooks. For this, we first need a few helper functions. Some of them are similar to the ones in the "writing font" subsection.

This snippet adds some code to replace the plain old "-" or "+" bullets with a nicer unicode symbol, although I do have a feeling that the teardown function is a little too dirty. It works for now.

(setq my-org-present-prettify-bullets-keywords
      '(;; the regular "- " bullet
        ("^ *\\- " ;; match line start followed by 0 or more spaces followed by "-" followed by a space
         (0 (progn (put-text-property
                    (match-beginning 0) (match-end 0)
                    'display (concat ;; add spaces if necessary
                              (make-string (- (match-end 0) (match-beginning 0) 2) 32)
                              (char-to-string #x25B8) " ") ;; triangle
                    nil))))
        ;; the "+" bullet. add more as needed.
        ("^ *\\+ "
         (0 (progn (put-text-property
                    (match-beginning 0) (match-end 0)
                    'display (concat
                              (make-string (- (match-end 0) (match-beginning 0) 2) 32)
                              (char-to-string #x2022) " ") ;; bullet
                    nil))))))

(defun my-org-present-prettify-bullets-setup ()
  (font-lock-add-keywords nil
                          my-org-present-prettify-bullets-keywords))
(defun my-org-present-prettify-bullets-teardown ()
  (font-lock-remove-keywords nil
                             my-org-present-prettify-bullets-keywords)
  (remove-text-properties (point-min) (point-max) '(display nil))
  (revert-buffer t t));; dirty, but works for now.

Next, we mess with some faces. TODO: maybe change face in code blocks to monospace (as of right now, I do not see an easy way to do this, since all of the font-lock faces inherit from default. Maybe I can make the font-lock faces inherit from a different font, of course buffer locally)

(defun my-org-present-faces-setup ()
  (let ((heading-height 450)
        (heading-fam "Fira Sans")
        (text-fam "Fira Sans")
        (spacing 0.4))
    (make-local-variable 'org-present-face-cookie-list)
    (setq org-present-face-cookie-list nil)
    ;; remap the heading face
    (add-to-list 'org-present-face-cookie-list
                 (face-remap-add-relative 'org-level-1
                                          :family heading-fam
                                          :height heading-height
                                          :weight 'bold))
    ;; remap the default face
    (add-to-list 'org-present-face-cookie-list
                 (face-remap-add-relative 'default
                                          :family text-fam))
    ;; disable grey bars in code blocks
    (add-to-list 'org-present-face-cookie-list
                 (face-remap-add-relative 'org-block-begin-line
                                          :background (face-attribute 'default :background)))
    (add-to-list 'org-present-face-cookie-list
                 (face-remap-add-relative 'org-block-end-line
                                          :background (face-attribute 'default :background)))
    ;; add some spacing between lines
    (setq-local line-spacing spacing)))

(defun my-org-present-faces-teardown ()
  ;; restore the modified faces
  (dolist (cookie org-present-face-cookie-list)
    (face-remap-remove-relative cookie))
  ;; restore the spacing
  (setq-local line-spacing nil))

The following snippet finally defines the setup and teardown hooks for org-present-mode.

(defun my-org-present-setup ()
  ;; do not want cursor or hl-line
  (make-variable-buffer-local 'post-command-hook)
  (remove-hook 'post-command-hook 'dennis-set-cursor)
  (setq global-hl-line-mode nil)
  ;; make it work with wireless presenter
  (buffer-local-set-key (kbd "<next>") 'org-present-next)
  (buffer-local-set-key (kbd "<prior>") 'org-present-prev)
  ;; change other things to make it look like a presentation
  (org-display-inline-images)
  (org-present-hide-cursor)
  (org-indent-mode)
  (my-org-present-faces-setup)
  (hidden-mode-line-mode)
  (org-present-big)
  (fringe-mode '(0 . 0))
  (my-org-present-prettify-bullets-setup)
  (org-present-read-only))

(defun my-org-present-teardown ()
  (add-hook 'post-command-hook 'dennis-set-cursor)
  (setq global-hl-line-mode t)
  (buffer-local-set-key (kbd "<next>") nil)
  (buffer-local-set-key (kbd "<prior>") nil)
  (org-remove-inline-images)
  (org-present-show-cursor)
  (org-indent-mode -1)
  (my-org-present-faces-teardown)
  (hidden-mode-line-mode -1)
  (org-present-small)
  (fringe-mode '(4 . 4))
  (org-present-read-write)
  (my-org-present-prettify-bullets-teardown))

(add-hook 'org-present-mode-hook 'my-org-present-setup)
(add-hook 'org-present-mode-quit-hook 'my-org-present-teardown)

Windmove and org-mode clashes

Make windmove (and framemove) play nice with org.

(add-hook 'org-shiftup-final-hook 'windmove-up)
(add-hook 'org-shiftleft-final-hook 'windmove-left)
(add-hook 'org-shiftdown-final-hook 'windmove-down)
(add-hook 'org-shiftright-final-hook 'windmove-right)

YaSnippet and org

backtab is already used in org-mode. Let's use C-<tab> instead.

(define-key org-mode-map (kbd "C-<tab>") 'yas-expand)

Flycheck

Flycheck works out-of-the-box, but I'll probably customize it some day.

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

Python

My Python REPL of choice is IPython.

IPython

By default, we want ipython to be our main python REPL. The code block below configures python-mode for that.

(when (executable-find "ipython")
  (setq
   python-shell-interpreter "ipython3"
   python-shell-interpreter-args "--simple-prompt -i"
   python-shell-prompt-regexp "In \\[[0-9]+\\]: "
   python-shell-prompt-output-regexp "Out\\[[0-9]+\\]: "
   python-shell-completion-setup-code
   "from IPython.core.completerlib import module_completion"
   python-shell-completion-module-string-code
   "';'.join(module_completion('''%s'''))\n"
   python-shell-completion-string-code
   "';'.join(get_ipython().Completer.all_completions('''%s'''))\n"))

(add-hook 'inferior-python-mode-hook 'no-trailing-whitespace)
(add-hook 'inferior-python-mode-hook
          '(lambda ()
             (setq-local ml-interactive? t)))

Sometimes, I want to be able to run a python2 shell as my inferior python.

(defun run-python2 ()
  (interactive ())
  (run-python "ipython2" nil t))

elpy

Apparently, elpy is the way to go when I want to do Python development in emacs.

(elpy-enable)
(setq elpy-rpc-backend "jedi")

Elpy automatically activates highlight-identation-mode, which is quite ugly in my opinion. It also uses flymake instead of flycheck by default. So let's only activate the modules we actually want.

(setq elpy-modules
      (quote
       (elpy-module-company
        elpy-module-eldoc
        elpy-module-pyvenv
        elpy-module-yasnippet
        elpy-module-sane-defaults)))

MATLAB

MATLAB integration in emacs is superb. A good introduction is here.

Load matlab-mode

This is the only package that does not work when I install it from MELPA. Maybe I can fix that at some point.

;;(add-to-list 'load-path "~/.emacs.d/lisp/matlab-emacs")
;;(require 'matlab-load)
(require 'matlab)
;;(require 'matlab-shell)
(setq matlab-shell-enable-gud-flag t)
(matlab-cedet-setup)
(require 'mlint)

General settings

Note that I used to modify my MATLAB search path using command line switches here, but the better way to do this is to let MATLAB itself do it. To change the search path, I can call the addpath command, and I make any changes permanent by calling savepath. Note that this is not very good for multi-user setups, but whatever.

The last line binds execution of the current line to the ever useful C-c C-c.

(setq my-matlab-version "R2016b")
(setq matlab-indent-function t)
;; (define-key matlab-shell-mode-map (kbd "TAB") nil) ;; we're using company
(setq matlab-shell-command
      (concat (file-name-as-directory "/data/MATLAB")
        my-matlab-version "/bin/matlab"))
(setq matlab-shell-command-switches
      '("-nodesktop"
        "-nosplash"))
(define-key matlab-mode-map (kbd "C-c C-c") 'matlab-shell-run-region-or-line)

Enable for .m files

(add-to-list
 'auto-mode-alist
 '("\\.m$" . matlab-mode))

Running MATLAB

MATLAB does not play nice with my shell of choice, fish. It looks like MATLAB uses shell commands internally that assume a POSIX-compliant shell. This can be fixed by setting the environment variable $SHELL before launching MATLAB. $SHELL gets passed on to MATLAB from emacs. We need to therefore make sure that emacs passes /bin/bash as $SHELL to MATLAB.

(defun run-matlab ()
  "Execute MATLAB-SHELL with POSIX-compliant SHELL env variable"
  (interactive)
  (let ((posix-shell-name "/bin/bash")
        (original-shell-env-var (getenv "SHELL")))
    ;; need to disable DRI3 according to red hat bug #1357571
    (setenv "LIBGL_DRI3_DISABLE" "true")
    ;; if $SHELL is bash, just launch matlab
    (if (string= original-shell-env-var posix-shell-name)
        (matlab-shell)
      ;; else set the correct $SHELL, launch matlab, and revert changes
      (setenv "SHELL" posix-shell-name)
      (matlab-shell)
      (setenv "SHELL" original-shell-env-var))))

(defun run-matlab-latest ()
  "Run the latest version of matlab."
  (interactive)
  (let ((matlab-shell-command "/data/MATLAB/R2016a/bin/matlab"))
    (run-matlab)))

We also want to have a legacy alias to launch matlab-shell and to run a line MATLAB code.

(defalias 'mshell 'run-matlab)
(defalias 'mrun 'matlab-shell-run-region-or-line)

Other customizations

(setq-default matlab-function t)
(setq-default matlab-highlight-cross-function-variables t)
(setq-default matlab-functions-have-end t)
(setq-default matlab-fill-code nil)
(setq-default matlab-vers-on-startup nil)
(add-to-list 'matlab-keyword-list "cvx_begin")
(add-to-list 'matlab-keyword-list "cvx_end")

mlint

This section configures mlint to check my MATLAB syntax after every save. We first need to point it at the right binary and set some general settings.

(setq-default mlint-program
              (concat (file-name-as-directory "/data/MATLAB")
                      my-matlab-version
                      "/bin/glnxa64/mlint"))
(setq-default matlab-show-mlint-warnings t)
(setq-default mlint-verbose nil)

In order to try to make mlint behave a little more like flycheck, I want to add a function similar to mlint-show-warning to my post-command-hook. It gets added to the hook in the "Hooks" section below.

(defun mlint-show-warning-continuous ()
  "Show the warning for the current mark.
This is intended to be run after every command. It only prints a
message if there is a error at point."
  (let ((n (linemark-at-point (point) mlint-mark-group)))
    (when n
      (message (oref n warning)))))

Hooks

First, the hook for the MATLAB shell.

(defun my-matlab-shell-hooks ()
  (setq global-hl-line-mode nil)
  (setq-local ml-interactive? t) ;; for mode line
  (setq show-trailing-whitespace nil))
(add-hook 'matlab-shell-mode-hook 'my-matlab-shell-hooks)

Now the hook for matlab-mode. Mostly setting up mlint.

(defun my-matlab-mode-hooks ()
  (flycheck-mode -1)
  (mlint-minor-mode 1)
  (add-hook 'post-command-hook 'mlint-show-warning-continuous)
  (setq-local company-backends (remove 'company-capf my-company-backends)))
(add-hook 'matlab-mode-hook 'my-matlab-mode-hooks)

Automatic completions with company-mode

Using company-mode. First, some general settings.

(require 'company)
(setq company-tooltip-align-annotations t)
(setq company-selection-wrap-around t)
(setq company-tooltip-flip-when-above t)
(setq company-idle-delay 0.0)
(add-hook 'after-init-hook 'global-company-mode)

company-dabbrev

Some settings to fix capitalization in company-dabbrev-code.

(require 'company-dabbrev)
(require 'company-dabbrev-code)
(setq company-dabbrev-code-everywhere t)
(setq company-dabbrev-code-ignore-case nil)
(setq company-dabbrev-ignore-case nil)
(add-to-list 'company-dabbrev-code-modes 'julia-mode)
(add-to-list 'company-dabbrev-code-modes 'matlab-mode)
(add-to-list 'company-dabbrev-code-modes 'matlab-shell-mode)

Keybindings

We want to enable company when tab is pressed and cycle through suggestions on subsequent pressings of tab.

(define-key company-active-map [tab] 'company-complete-common-or-cycle)
(define-key company-active-map (kbd "TAB") 'company-complete-common-or-cycle)

Backends

(require 'company-auctex)
(require 'company-math)
(require 'company-c-headers)
(add-to-list 'company-c-headers-path-system "/usr/include/c++/5.2.0/")

(defvar my-company-backends nil
  "A list of my company backends")
(setq my-company-backends
      '(company-auctex-labels
        company-auctex-bibs
        (company-auctex-macros company-auctex-symbols company-auctex-environments)
        (company-math-symbols-latex company-math-symbols-unicode)
        company-ispell
        (company-semantic
         company-clang company-c-headers)
        ;;company-matlab-shell
        company-bbdb
        company-elisp
        ac-js2-company
        company-nxml
        company-css
        company-eclim
        company-xcode
        company-cmake
        company-capf
        (company-dabbrev-code company-gtags company-etags company-keywords)
        company-oddmuse
        company-files
        company-dabbrev
        company-yasnippet))
(setq company-backends my-company-backends)

Enable yasnippet suggestions with company

From here. Modified a little for simplifications. We first define a function that enables the yasnippet backend together with all of the other backends.

(defun add-yas-to-company-backends (backend)
  "Add yasnippet suggestions to the `company-mode' backend BACKEND.

  Returns an alist of (BACKEND :with company-yasnippet)."
  (if (or (eq backend 'company-yasnippet)
          (and (listp backend) (member 'company-yasnippet backend)))
      ;; do nothing
      backend
    ;; else append :with company-yasnippet
    (append (if (consp backend) backend (list backend))
            '(:with company-yasnippet))))
;; set the backends if wanted---not right now
;; (setq company-backends (mapcar #'add-yas-to-company-backends my-company-backends))

Company and elpy

Elpy configures its own company backend. It sets some settings that are unneccessary. This happens in the eply-module-company functions, which we will redefine.

(defun my-elpy-module-company (command &rest args)
  "Prepare company for elpy, without being too intrusive."
  (pcase command
    (`global-init
     (require 'company)
     (elpy-modules-remove-modeline-lighter 'company-mode)
     (define-key company-active-map (kbd "C-d")
       'company-show-doc-buffer))
    (`buffer-init
     (set (make-local-variable 'company-backends)
          ;; to enable yasnippet suggestions, use the following instead:
          ;; (cons '(elpy-company-backend :with company-yasnippet)
          (cons '(elpy-company-backend)
                (mapcar #'identity ;; if want yasnippet, change here
                        (delq 'company-semantic
                              (delq 'company-ropemacs
                                    (delq 'company-capf
                                          my-company-backends))))))
     (company-mode 1))
    (`buffer-stop
     (company-mode -1)
     (kill-local-variable 'company-backends))))

(advice-add 'elpy-module-company :override #'my-elpy-module-company)

Completion on <tab>

The following functions define the global behavior of the <tab> key. Modified from here. We want to

  1. Check if in minibuffer, if yes, use minibuffer-complete.
  2. Check if there are snippets, if yes, execute the snippet
  3. Check for company-completions, if yes, enter company.
  4. If there are no snippets or completions, indent the line.
(defun check-expansion ()
  (save-excursion
    (if (looking-at "\\_>") t
      (backward-char 1)
      (if (looking-at "\\.") t
        (backward-char 1)
        (if (looking-at "->") t nil)))))

(defun do-yas-expand ()
  (let ((yas-fallback-behavior 'return-nil))
    (yas-expand)))

(defun tab-indent-or-complete ()
  (interactive)
  (if (minibufferp)
      (minibuffer-complete)
    (if (or (not yas-minor-mode) ;; xxx change this to point to right var
            (null (when (looking-at "\\_>") (do-yas-expand))))
        (if (check-expansion)
            (company-complete-common)
          (indent-for-tab-command)))))

(define-key prog-mode-map [tab] 'tab-indent-or-complete)
(define-key prog-mode-map (kbd "TAB") 'tab-indent-or-complete)

Theme

Finally, we want to set the colors for company-mode to align more with the wombat colorscheme.

(set-face-attribute 'company-tooltip nil
                    :foreground (face-attribute 'font-lock-comment-face :foreground)
                    :background (face-attribute 'fringe :background))
(set-face-attribute 'company-tooltip-selection nil
                    :background "gray30"
                    :slant 'italic
                    :weight 'semibold)

(set-face-attribute 'company-tooltip-common nil
                    :foreground (face-attribute 'font-lock-builtin-face :foreground))
(set-face-attribute 'company-tooltip-common-selection nil
                    :foreground (face-attribute 'font-lock-builtin-face :foreground)
                    :slant 'italic
                    :weight 'semibold)

(set-face-attribute 'company-tooltip-annotation nil
                    :foreground (face-attribute 'font-lock-comment-face :foreground))

(set-face-attribute 'company-scrollbar-fg nil
                    :background (face-attribute 'font-lock-comment-face :foreground))
(set-face-attribute 'company-scrollbar-bg nil
                    :background (face-attribute 'fringe :background))

(set-face-attribute 'company-preview-common nil
                    :foreground (face-attribute 'font-lock-builtin-face :foreground)
                    :background "SlateBlue4")
(set-face-attribute 'company-preview nil
                    :foreground (face-attribute 'font-lock-builtin-face :foreground)
                    :background "SlateBlue4")
(set-face-attribute 'company-preview-search nil
                    :background "SlateBlue1")

Snippets

Using yasnippet. My custom snippets are under ~/.emacs.d/snippets/<mode-name>.

I currently have the following custom snippets:

  • latex-mode
    • ieq
    • ieqs
    • mat
    • eqref
    • lr—this one is cool.
  • various copies of latex snippets for org-mode

Enable it.

(require 'yasnippet)
(yas-global-mode 1)

Keybindings

Unbind the tab key, since I'm also using auto-complete. We want to use Shift-tab to expand snippets.

(define-key yas-minor-mode-map (kbd "<tab>") nil)
(define-key yas-minor-mode-map (kbd "TAB") nil)
(define-key yas-minor-mode-map (kbd "<backtab>") 'yas-expand)

Popup settings

Want to use a text-based popup, not a GTK-based. I don't like it that much.

(require 'popup)
(define-key popup-menu-keymap (kbd "M-n") 'popup-next)
(define-key popup-menu-keymap (kbd "TAB") 'popup-next)
(define-key popup-menu-keymap (kbd "<tab>") 'popup-next)
(define-key popup-menu-keymap (kbd "<backtab>") 'popup-previous)
(define-key popup-menu-keymap (kbd "M-p") 'popup-previous)
(defun yas-popup-isearch-prompt (prompt choices &optional display-fn)
  (when (featurep 'popup)
    (popup-menu*
     (mapcar
      (lambda (choice)
        (popup-make-item
         (or (and display-fn (funcall display-fn choice))
             choice)
         :value choice))
      choices)
     :prompt prompt
     ;; start isearch mode immediately
     :isearch t)))

The popup color theme has to be adjusted as well.

(set-face-attribute 'popup-face nil
                    :foreground (face-attribute 'font-lock-comment-face :foreground)
                    :background (face-attribute 'fringe :background))

(set-face-attribute 'popup-menu-mouse-face nil
                    :foreground (face-attribute 'font-lock-comment-face :foreground)
                    :background (face-attribute 'fringe :background))

(set-face-attribute 'popup-menu-selection-face nil
                    :background "gray30"
                    :foreground (face-attribute 'font-lock-builtin-face :foreground))

(set-face-attribute 'popup-menu-summary-face nil
                    :foreground (face-attribute 'font-lock-builtin-face :foreground))

(set-face-attribute 'popup-summary-face nil
                    :foreground (face-attribute 'font-lock-builtin-face :foreground))

(set-face-attribute 'popup-scroll-bar-foreground-face nil
                    :background (face-attribute 'font-lock-comment-face :foreground))
(set-face-attribute 'popup-scroll-bar-background-face nil
                    :background (face-attribute 'fringe :background))

This sets the available popup methods:

(setq yas-prompt-functions '(yas-popup-isearch-prompt yas-ido-prompt yas-no-prompt))

SLiME

For if I ever learn Lisp…

LISP REPL choice

Set to SBCL right now.

(setq inferior-lisp-program "sbcl")
(setq slime-contribs '(slime-fancy))

LISP REPL hooks

(defun my-lisp-repl-hooks ()
  (setq global-hl-line-mode nil)
  (setq-local ml-interactive? t) ;; for mode line
  (setq show-trailing-whitespace nil))
(add-hook 'slime-repl-mode-hook 'my-lisp-repl-hooks)
(add-hook 'inferior-lisp-mode-hook 'my-lisp-repl-hooks)
(add-hook 'ielm-mode-hook 'my-lisp-repl-hooks)

Magit

Slowly but surely starting to use Magit. I heard it was good.

(global-set-key (kbd "C-x g") 'magit-status)
(setq magit-push-always-verify nil)

Writing font

Serif font for text

I want to be able to toggle a variable-width "writing font" with serifs for a buffer in which I'm mainly composing text. This is particularly comfortable when editing \(\LaTeX\) or org-mode source files.

First, whenever we turn on the serif font, there could be faces where we want to preserve the monospace font (usually all of the markup commands and math blocks). These should be listed in the variable serif-preserve-monospace-list.

(defvar serif-preserve-default-list nil
  "A list holding the faces that preserve the default family and
  height when TOGGLE-SERIF is used.")
(setq serif-preserve-default-list
      '(;; LaTeX markup
        font-latex-math-face
        font-latex-sedate-face
        font-latex-warning-face
        ;; org markup
        org-latex-and-related
        org-meta-line
        org-verbatim
        org-block-begin-line
        ;; mail
        mu4e-header-key-face
        mu4e-header-value-face
        mu4e-link-face
        mu4e-contact-face
        mu4e-compose-separator-face
        mu4e-compose-header-face
        message-header-name
        message-header-to
        message-header-cc
        message-header-newsgroups
        message-header-xheader
        message-header-subject
        message-header-other
        ;; syntax highlighting using font-lock
        font-lock-builtin-face
        font-lock-comment-delimiter-face
        font-lock-comment-face
        font-lock-constant-face
        font-lock-doc-face
        font-lock-function-name-face
        font-lock-keyword-face
        font-lock-negation-char-face
        font-lock-preprocessor-face
        font-lock-regexp-grouping-backslash
        font-lock-regexp-grouping-construct
        font-lock-string-face
        font-lock-type-face
        font-lock-variable-name-face
        font-lock-warning-face))

The following interactive function can then be used to toggle the faces of the current buffer to a mix of serif and monospace fonts.

(defun toggle-serif (&optional show-echo)
  "Change the default face of the current buffer to use a serif family."
  (interactive)
  (when (display-graphic-p)  ;; this is only for graphical emacs
    ;; the serif font familiy and height, save the default attributes
    (let ((serif-fam "Source Serif Pro")
          (serif-height (round (* 1.2 (face-attribute 'default :height))))
          (default-fam (face-attribute 'default :family))
          (default-height (face-attribute 'default :height)))
      (if (not (bound-and-true-p default-cookie))
          (progn (make-local-variable 'default-cookie)
                 (make-local-variable 'preserve-default-cookies-list)
                 (setq preserve-default-cookies-list nil)
                 ;; remap default face to serif
                 (setq default-cookie
                       (face-remap-add-relative
                        'default :family serif-fam :height serif-height))
                 ;; keep previously defined monospace fonts the same
                 (dolist (face serif-preserve-default-list)
                   (add-to-list 'preserve-default-cookies-list
                                (face-remap-add-relative
                                 face :family default-fam :height default-height)))
                 (when show-echo (message "Turned on serif writing font.")))
        ;; else undo changes
        (progn (face-remap-remove-relative default-cookie)
               (dolist (cookie preserve-default-cookies-list)
                 (face-remap-remove-relative cookie))
               (setq default-cookie nil)
               (setq preserve-default-cookies-list nil)
               (when show-echo (message "Restored default fonts.")))))))

Sans-serif font for code

Sometimes I just want to write code with a variable-width Sans Serif font. This function is very similar to the one above.

(defun sans-font ()
  (interactive)
  (when (display-graphic-p)
    (let ((sans-fam "CMU Bright")
          (sans-height (round (* 1.1 (face-attribute 'default :height)))))
      (if (not (bound-and-true-p default-cookie-sans))
          ;; remap default face to serif
          (progn (make-local-variable 'default-cookie-sans)
                 (setq default-cookie-sans
                       (face-remap-add-relative
                        'default :family sans-fam :height sans-height :weight 'bold)))
        ;; else undo changes
        (progn (face-remap-remove-relative default-cookie-sans)
               (setq default-cookie-sans nil)
               (message "Restored default fonts."))))))

HiDPI

This is a very low-tech solution to the HiDPI problem on the linux desktop. Simply scale the default face and fringe.

(defvar dennis-hidpi-scale-factor 2
  "The scale factor for my global HiDPI minor mode")

(define-minor-mode dennis-hidpi-mode
  "Toggle HiDPI minor mode."
  :lighter " HD"
  :global t
  (if (bound-and-true-p dennis-hidpi-mode)
      ;; on toggle, scale the fonts and change powerline
      (let ((dennis-hidpi-height (* 100 dennis-hidpi-scale-factor)))
        (setq dennis-hidpi-prev-height (face-attribute 'default :height)
              dennis-hidpi-prev-powerline-height powerline-height
              dennis-hidpi-prev-powerline-separator powerline-default-separator
              dennis-hidpi-prev-powerline-separator-dir powerline-default-separator-dir)
        (set-face-attribute 'default nil :height dennis-hidpi-height)
        (setq powerline-height  (+ 5 (frame-char-height))
              powerline-default-separator 'contour
              powerline-default-separator-dir '(right . left))
        (fringe-mode '(12 . 12)))
    ;; else undo the changes
    (set-face-attribute 'default nil :height dennis-hidpi-prev-height)
    (setq powerline-height dennis-hidpi-prev-powerline-height
          powerline-default-separator dennis-hidpi-prev-powerline-separator
          powerline-default-separator-dir dennis-hidpi-prev-powerline-separator-dir)
    (fringe-mode '(4 . 4))))

;; set a quick alias
(defalias 'hd 'dennis-hidpi-mode)

LaTeX / AUCTeX

AuCTeX is the best LaTeX editing suite of all time.

Load Packages

(load "auctex.el" nil t t)
(load "preview.el" nil t t)
(require 'texmathp)

General settings

We generally want to autosave and output to PDF. Last line autosaves on compilation with C-c C-c.

(setq TeX-auto-save t)
(setq TeX-parse-self t)
(setq-default TeX-master nil)
(setq TeX-PDF-mode t)
(setq reftex-plug-into-AUCTeX t)
(setq TeX-save-query nil)
(setq-default TeX-command-extra-options "--shell-escape")
(setq TeX-error-overview-open-after-TeX-run t)
(setq TeX-electric-math '("$" . "$"))
(setq TeX-electric-sub-and-superscript t)
;; fix for company completion
(define-key TeX-mode-map (kbd "TAB") 'tab-indent-or-complete)
(define-key TeX-mode-map [tab] 'tab-indent-or-complete)

The section headers should be rendered using the Computer Modern font

(require 'font-latex)
(set-face-attribute 'font-latex-sectioning-5-face nil :family "CMU Bright")
(setq font-latex-fontify-sectioning 1.2)
(font-latex-update-sectioning-faces)

Fix auto-fill with inline math

More info here.

(setq-default LaTeX-fill-break-at-separators (quote (\\\( \\\[ \\\])))

SyncTeX

(setq TeX-source-correlate-method 'synctex)
(setq TeX-source-correlate-start-server t)
(add-hook 'LaTeX-mode-hook 'TeX-source-correlate-mode)

Hooks

First line adds hook from previous section. The last hook we add sets the default face for prose to a serif font.

(add-hook 'LaTeX-mode-hook 'visual-line-mode)
(add-hook 'LaTeX-mode-hook 'flyspell-mode)
(add-hook 'LaTeX-mode-hook 'LaTeX-math-mode)
(add-hook 'LaTeX-mode-hook 'turn-on-reftex)

Latexmk

(require 'auctex-latexmk)
(auctex-latexmk-setup)

XeLaTeX

(add-to-list 'TeX-command-list '("XeLaTeX" "%`xelatex %(extraopts)%(mode)%' %t" TeX-run-TeX nil t))

Spell checking

Set-up

(require 'ispell)
(setq-default ispell-program-name "hunspell")
(setq ispell-really-hunspell t)

Dictionaries

(setq ispell-dictionary-base-alist
      '(("en_US"
         "[a-zA-Z]" "[^a-zA-Z]" "[']" nil
         ("-d" "en_US" "-i" "iso-8859-1") nil iso-8859-1)
        ("en_GB"
         "[a-zA-Z]" "[^a-zA-Z]" "[']" nil
         ("-d" "en_GB" "-i" "iso-8859-1") nil iso-8859-1)
        ("de_DE"
         "[a-zäöüßA-ZÄÖÜ]" "[^a-zäöüßA-ZÄÖÜ]" "[']" nil
         ("-d" "de_DE" "-i" "iso-8859-1") nil iso-8859-1)))

Activate a specific Dictionary after loading

(eval-after-load "ispell"
  (progn
    (setq ispell-dictionary "en_US")
    (setq ispell-silently-savep t))) ; save personal dict without confirmation

Emacs and IEEEeqnarray

We want emacs to play nice with the IEEEtran and IEEEtrantools packages. We also define some keyboard shortcuts. Stefan Moser's typeset_equations comes with a few keyboard bindings, but rather than using the recommended fset... commands to insert a new IEEEeqnarray, I want to insert them using snippets.

Snippets

The two snippets I defined are ieq.yasnippet to insert a regular IEEEeqnarray with argument {rCl} and ieqs.yasnippet to insert the starred version. The recommended keybindings are also defined within the snippet: I'm binding C-c i to insert the regular array nad C-c o to insert the starred array.

The ieq snippet looks like this:

# -*- mode: snippet -*-
# contributor: Dennis Ogbe <dogbe@purdue.edu>
# key: ieq
# group: math
# name: \begin{IEEEeqnarray}{rCl} ... \end{IEEEeqnarray}
# binding: C-c i
# --
\begin{IEEEeqnarray}{${1:rCl}}
\label{${2:"waiting for reftex-label call..."$(unless yas/modified-p (reftex-label nil 'dont-insert))}}
$0
\end{IEEEeqnarray}

Keybindings

I will however, add the commands to insert and remove line breaks. The following block describes those operations.

(fset 'linebreak
      [?\\ ?n ?o ?n ?u ?m ?b ?e ?r ?\\ ?\\ tab return ?& ?& ?  right ?\\ ?> tab])

(fset 'linebreakwithoutspace
      [?\\ ?n ?o ?n ?u ?m ?b ?e ?r ?\\ ?\\ tab return ?& ?& ?  right tab])

(fset 'unlinebreak
      [?\C-\M-s ?\\ ?n ?o ?n ?u ?m ?b ?e ?r C-left left ?\C-  ?\C-\M-s ?& ?& right left ?\C-w])

(fset 'IEEEmulticol
      [?\C-a tab ?\\ ?I ?E ?E ?E ?e ?q ?n ?a ?r ?r ?a ?y ?m ?u ?l ?t ?i ?c ?o ?l ?{ ?3 ?} ?{
             ?l ?} ?{ return tab escape ?x ?i ?s ?e ?a ?r ?c ?h ?- ?f ?o ?r ?w ?a ?r ?d return
             ?& left ?} ?\\ ?n ?o ?n ?u ?m ?b ?e ?r ?\\ ?\\ ?\\ ?q ?u ?a ?d return tab])

Now add the keybindings for them to the LaTeX hook.

(add-hook 'LaTeX-mode-hook
          '(lambda ()
             (define-key LaTeX-mode-map "\C-cb" 'linebreak)
             (define-key LaTeX-mode-map "\C-cn" 'linebreakwithoutspace)
             (define-key LaTeX-mode-map "\C-c\C-b" 'unlinebreak)
             (define-key LaTeX-mode-map "\C-cm" 'IEEEmulticol)))

LaTeX environments

Make AuCTeX aware of the IEEEeqnarray as a math environment.

(TeX-add-style-hook "latex"
                    (lambda ()
                      (LaTeX-add-environments
                       '("IEEEeqnarray" LaTex-env-label)
                       '("IEEEeqnarray*"))))
(add-hook 'LaTeX-mode-hook
          '(lambda ()
             (add-to-list 'font-latex-math-environments "IEEEeqnarray")
             (add-to-list 'font-latex-math-environments "IEEEeqnarray*")
             ;; need this  for electric subscripts
             (setq texmathp-tex-commands
                   '(("IEEEeqnarray" env-on)
                     ("IEEEeqnarray*" env-on)))
             (texmathp-compile)))

ispell and reftex

We also want to make ispell and reftex aware of IEEEeqnarray. The variable ispell-tex-skip-alists contains two lists with patterns for ispell to skip. Since Moser's proposed list is longer than the default emacs one, we'll just copy the whole list.

(setq ispell-tex-skip-alists
      '((("%\\[" . "%\\]")
         ;; All the standard LaTeX keywords from L. Lamport's guide:
         ;; \cite, \hspace, \hspace*, \hyphenation, \include, \includeonly, \input,
         ;; \label, \nocite, \rule (in ispell - rest included here)
         ("\\\\addcontentsline"              ispell-tex-arg-end 2)
         ("\\\\psfrag"                       ispell-tex-arg-end-psfrag 0)
         ("\\\\add\\(tocontents\\|vspace\\)" ispell-tex-arg-end)
         ("\\\\\\([aA]lph\\|arabic\\)"       ispell-tex-arg-end)
         ("\\\\author"                       ispell-tex-arg-end)
         ("\\\\bibliographystyle"            ispell-tex-arg-end)
         ("\\\\bibliography"                 ispell-tex-arg-end)
         ("\\\\bstctlcite"                   ispell-tex-arg-end)
         ("\\\\eqref"                        ispell-tex-arg-end)
         ("\\\\thref"                        ispell-tex-arg-end)
         ("\\\\label"                        ispell-tex-arg-end)
         ("\\\\makebox"                      ispell-tex-arg-end 0)
         ("\\\\document\\(class\\|style\\)" .
          "\\\\begin[ \t\n]*{[ \t\n]*document[ \t\n]*}"))
        (;; delimited with \begin.  In ispell: displaymath, eqnarray, eqnarray*,
         ;; equation, minipage, picture, tabular, tabular* (ispell)
         ("\\(figure\\|table\\)\\*?"  ispell-tex-arg-end 0)
         ("list"                      ispell-tex-arg-end 2)
         ("IEEEeqnarray\\*?". "\\\\end[ \t\n]*{[ \t\n]*IEEEeqnarray\\*?[ \t\n]*}")
         ("IEEEeqnarraybox[tm]?\\*?". "\\\\end[ \t\n]*{[ \t\n]*IEEEeqnarraybox[tm]?\\*?[ \t\n]*}")
         ("program"         . "\\\\end[ \t\n]*{[ \t\n]*program[ \t\n]*}")
         ("verbatim\\*?"    . "\\\\end[ \t\n]*{[ \t\n]*verbatim\\*?[ \t\n]*}")
         ("gather\\*?"      . "\\\\end[ \t\n]*{[ \t\n]*gather\\*?[ \t\n]*}"))))

The variable reftex-label-alist is also empty by default, so let's use the one from typeset_equations:

(setq reftex-label-alist
      '(
        (nil              ?e "eq:"   "~\\eqref{%s}" nil nil)
        ("IEEEeqnarray"   ?e "eq:"   "~\\eqref{%s}" t   nil)          ;; add this line!
        ("IEEEeqnarray*"  ?e "eq:"   "~\\eqref{%s}" t   nil)          ;; add this line!
        ("lemma"          ?l "lem:"  "~\\ref{%s}"   t   ("lemma" "lem."))
        ("theorem"        ?h "thm:"  "~\\ref{%s}"   t   ("theorem" "th." "thm."))
        ("corollary"      ?c "cor:"  "~\\ref{%s}"   t   ("corollary" "cor."))
        ("conjecture"     ?j "conj:" "~\\ref{%s}"   t   ("conjecture" "conj."))
        ("proposition"    ?p "prop:" "~\\ref{%s}"   t   ("proposition" "prop."))
        ("claim"          ?m "clm:"  "~\\ref{%s}"   t   ("claim" "cl."))
        ("definition"     ?d "def:"  "~\\ref{%s}"   t   ("definition" "def."))
        ("remark"         ?r "rem:"  "~\\ref{%s}"   t   ("remark" "rem."))
        ("example"        ?x "ex:"   "~\\ref{%s}"   t   ("example" "ex."))
        ("exercise"       ?x "ex:"   "~\\ref{%s}"   t   ("exercise" "exer." "exerc." "ex."))
        ("enumerate"      ?i "item:" "~\\ref{%s}"   t   ("part"))
        ))

Julia

General Settings

Some settings for julia-mode.

(setq julia-indent-offset 4)

Font Locking

Mathematical operators are being font-locked in matlab.el. This is nice, let's steal it.

(font-lock-add-keywords
 'julia-mode
 `((,(let ((OR "\\|"))
       (concat "\\(" ;; stolen `matlab.el' operators first
               "[<>!]=?" OR
               "\\.[/*^']" OR
               "==" OR
               "=>" OR
               "\\<xor\\>" OR
               "[-+*\\/^&|$]=?" OR ;; this has to come before next (updating operators)
               "[-!^&|*+\\/~:]" OR
               ;; more extra julia operators follow
               "[%$]" OR
               ;; bitwise operators
               ">>>" OR
               ">>" OR
               "<<" OR
               ">>>=" OR ">>" OR "<<" OR
               ;; comparison
               "[<>!]=?" OR
               "\\)"))
   1 font-lock-type-face)))

Let's add some font-locking support for a few constants!

(font-lock-add-keywords
 'julia-mode
 '(("\\<\\(pi\\|π\\|im\\|^>>\\)\\>"
    1 font-lock-constant-face)))

Julia Shell

I'm currently developing julia-shell-mode, which aims to mirror the functionality of matlab.el, but for julia. This loads the custom library. If development takes off and it's good code, I hope to re-integrate this into julia-mode one day.

(add-to-list 'load-path "~/.emacs.d/lisp/julia-shell")
(load-library "julia-shell")

(defun my-julia-shell-hooks ()
  (setq-local ml-interactive? t)
  (setq global-hl-line-mode nil)
  (setq show-trailing-whitespace nil)
  (company-mode -1))
(add-hook 'inferior-julia-shell-mode-hook 'my-julia-shell-hooks)

Based on the new and improved julia-shell-mode, we add some functionality to julia-mode.

;; some changes for the OG julia-mode
(defun my-julia-mode-hooks ()
  (require 'julia-shell))
(add-hook 'julia-mode-hook 'my-julia-mode-hooks)
(define-key julia-mode-map (kbd "C-c C-c") 'julia-shell-run-region-or-line)
(define-key julia-mode-map (kbd "C-c C-s") 'julia-shell-save-and-go)
(define-key julia-mode-map [tab] 'julia-latexsub-or-indent)
(define-key julia-mode-map (kbd "TAB") 'julia-latexsub-or-indent)

Hy

Hy is lisp running in the Python interpreter! That means all of the power of matplotlib, numpy, and scipy with the power of lisp!

(require 'hy-mode)
(add-hook 'hy-mode-hook 'paredit-mode)
(add-hook 'hy-mode-hook 'rainbow-delimiters-mode)

CEDET

(require 'cc-mode)
(require 'semantic)
(global-semanticdb-minor-mode)
(global-semantic-idle-scheduler-mode)
(global-semantic-highlight-func-mode)
(semantic-mode 1)
(semantic-add-system-include "/usr/include/boost" 'c++-mode)

Projects

In some projects, flycheck needs some information about the projects include path. Since we want to use EDE for C and C++ projects anyways, we can grab the include path from there.

(require 'ede)
(global-ede-mode t)
(require 'flycheck-cedet)

(defun dennis-flycheck-get-ede-includes ()
  (interactive)
  "Check if the current file is part of an EDE project.
If yes, set up `flycheck-clang-include-path'"
  (make-variable-buffer-local 'flycheck-clang-include-path)
  (let* ((rel-includes
          (flycheck-cedet-get-cpp-includes "" (buffer-file-name)))
         (dirname (when rel-includes
                    (ede-cpp-root-project-root default-directory))))
    (when rel-includes
      (when (string-match "\\(.*\\)/$" dirname)
        (setq dirname (substring dirname (match-beginning 1) (match-end 1))))
      (setq incl-paths
            (mapcar '(lambda (arg) (concat dirname arg))
                    rel-includes))
      (setq flycheck-clang-include-path
            (append flycheck-clang-include-path incl-paths)))))

(add-hook 'c-mode-common-hook 'dennis-flycheck-get-ede-includes)

We can now list our current EDE projects. This is not being exported.

Highlighting symbols

Use highlight-symbol with a custom face and a low timeout value.

(require 'highlight-symbol)
;; do not wait, I have no patience
(setq highlight-symbol-idle-delay 0)
(set-face-attribute 'highlight-symbol-face nil
                    :background (face-attribute 'hl-line :background))
;; add to relevant hooks
(add-hook 'prog-mode-hook 'highlight-symbol-mode)
(add-hook 'matlab-mode-hook 'highlight-symbol-mode)
(add-hook 'julia-mode-hook 'highlight-symbol-mode)

Filetypes

Here, I put different settings for specific filetypes.

Markdown

File extensions

(require 'markdown-mode)
(add-to-list 'auto-mode-alist '("\\.text\\'" . markdown-mode))
(add-to-list 'auto-mode-alist '("\\.markdown\\'" . markdown-mode))
(add-to-list 'auto-mode-alist '("\\.md\\'" . markdown-mode))

Hooks

(add-hook 'markdown-mode-hook (lambda () (auto-fill-mode t)))
(add-hook 'markdown-mode-hook 'flyspell-mode)

Keymappings

Similar to org-mode, <backtab> is already in use. I'll use <C-tab> for snippet expansion instead then.

(define-key markdown-mode-map (kbd "<C-tab>") 'yas-expand)

C

C++ style comments in C

(add-hook 'c-mode-hook (lambda () (setq comment-start "//"
                                        comment-end   "")))

Preprocessor macros

(require 'preproc-font-lock)
(preproc-font-lock-global-mode 1)
(make-face 'my-cpp-preproc-macro-face)
(set-face-attribute 'my-cpp-preproc-macro-face nil
                    :background (face-attribute 'default :background)
                    :foreground (face-attribute 'font-lock-constant-face :foreground)
                    :underline nil
                    :slant 'italic
                    :weight 'bold)
(setq preproc-font-lock-preprocessor-background-face 'my-cpp-preproc-macro-face)

HTML

When editing HTML, I want to have the proper syntax highligting according to what kind of block I'm in. web-mode does this.

(require 'web-mode)
(add-to-list 'auto-mode-alist '("\\.html?\\'" . web-mode))
(defun my-web-mode-hook ()
  "Hooks for Web mode."
  (setq web-mode-markup-indent-offset 2)
  (setq web-mode-css-indent-offset 2))
(add-hook 'web-mode-hook  'my-web-mode-hook)

JavaScript

Activate js2-mode instead of standard js-mode.

(add-to-list 'auto-mode-alist '("\\.js\\'" . js2-mode))
(add-to-list 'interpreter-mode-alist '("node" . js2-mode))
(add-hook 'js2-mode-hook 'ac-js2-mode)

Some general settings for js2-mode.

(setq
 js2-basic-offset 2
 js2-highlight-level 3)

We can run a nodejs REPL locally or over TRAMP, and it works out-of-the-box!

(defalias 'run-node 'nodejs-repl)

Misc. settings

Some one-liners and other settings go in here.

Only y/n questions, not yes/no

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

Disable ad-handle-definition warnings

(setq ad-redefinition-action 'accept)

Use SSH for TRAMP

I have to do this because my default shell, fish, does not agree with the TRAMP commands when using the default "scp" mode. This should be a reason to switch back to bash or zsh…

(setq tramp-default-method "scp")

Unique buffer names

(setq uniquify-buffer-name-style 'forward)

Stop pasting at the mouse click point

Emacs' default setting first moves point to the click location and then yanks. This is annoying, and we rather want it to yank at the current position of point.

(setq mouse-yank-at-point t)

Open file as root

Inspired by this, but using the regular find-file instead of ido-find-file.

(defun sudo-open ()
  "Like `find-file', but with root rights using TRAMP"
  (interactive)
  (let ((file (read-file-name "Open as root: ")))
    (unless (file-writable-p file)
      (find-file (concat "/sudo:root@localhost:" file)))))
;; bind to a key
(global-set-key (kbd "C-x F") 'sudo-open)

Auto-fill-mode

(setq-default fill-column 79)

DocView

Make DocView suck a little less. I still prefer evince.

(eval-after-load 'doc-view
  '(defun doc-view-buffer-message () "rendering pdf..."))
(setq doc-view-continuous t)
(add-hook 'doc-view-mode-hook 'auto-revert-mode)

Inferior Octave

Sometimes, I want to run octave. My custom octaverc for the emacs inferior mode looks like this:

source("~/.octaverc")

I can use this file to load some emacs-specific settings for octave, although right now, it does not do very much. The rest of my configuration for octave looks like this:

(setq
 inferior-octave-startup-file "~/.emacs.d/random/emacs-octaverc"
 inferior-octave-startup-args '("-q"))
(setq
 octave-blink-matching-block t
 octave-block-offset 4
 octave-continuation-offset 4
 octave-continuation-string "\\"
 octave-send-echo-input t
 octave-send-line-auto-forward t)
(add-hook 'inferior-octave-mode-hook
          (lambda ()
            (turn-on-font-lock)
            (setq-local ml-interactive? t)
            (define-key inferior-octave-mode-map [up]
              'comint-previous-input)
            (define-key inferior-octave-mode-map [down]
              'comint-next-input)))

Keybindings in tmux

When launching terminal emacs in tmux, some keys do not work. This hint from the archwiki is supposed to help. To make this smarter, we add some logic to toggle the key-translation-map on a frame-by-frame basis.

;; handle tmux's xterm-keys
;; put the following line in your ~/.tmux.conf:
;;   setw -g xterm-keys on

;; first, define the tmux-translation-map
(setq tmux-translation-map
      (let ((x 2)
            (tkey "")
            (km (make-sparse-keymap)))
        (while (<= x 8)
          (when (= x 2) (setq tkey "S-")) ;; shift
          (when (= x 3) (setq tkey "M-")) ;; alt
          (when (= x 4) (setq tkey "M-S-")) ;; alt + shift
          (when (= x 5) (setq tkey "C-")) ;; ctrl
          (when (= x 6) (setq tkey "C-S-")) ;; ctrl + shift
          (when (= x 7) (setq tkey "C-M-")) ;; ctrl + alt
          (when (= x 8) (setq tkey "C-M-S-")) ;; ctrl + alt + shift
          ;; arrows
          (define-key km (kbd (format "M-[ 1 ; %d A" x)) (kbd (format "%s<up>" tkey))) ;; uparrow
          (define-key km (kbd (format "M-[ 1 ; %d B" x)) (kbd (format "%s<down>" tkey))) ;; downarrow
          (define-key km (kbd (format "M-[ 1 ; %d C" x)) (kbd (format "%s<right>" tkey))) ;; rightarrow
          (define-key km (kbd (format "M-[ 1 ; %d D" x)) (kbd (format "%s<left>" tkey))) ;; leftarrow
          ;; special keys
          (define-key km (kbd (format "M-[ 1 ; %d H" x)) (kbd (format "%s<home>" tkey))) ;; home
          (define-key km (kbd (format "M-[ 1 ; %d F" x)) (kbd (format "%s<end>" tkey))) ;; end
          (define-key km (kbd (format "M-[ 5 ; %d ~" x)) (kbd (format "%s<prior>" tkey))) ;; pgup
          (define-key km (kbd (format "M-[ 6 ; %d ~" x)) (kbd (format "%s<next>" tkey))) ;; pgdown
          (define-key km (kbd (format "M-[ 2 ; %d ~" x)) (kbd (format "%s<delete>" tkey))) ;; insert
          (define-key km (kbd (format "M-[ 3 ; %d ~" x)) (kbd (format "%s<delete>" tkey))) ;; delete
          ;; function keys
          (define-key km (kbd (format "M-[ 1 ; %d P" x)) (kbd (format "%s<f1>" tkey)))
          (define-key km (kbd (format "M-[ 1 ; %d Q" x)) (kbd (format "%s<f2>" tkey)))
          (define-key km (kbd (format "M-[ 1 ; %d R" x)) (kbd (format "%s<f3>" tkey)))
          (define-key km (kbd (format "M-[ 1 ; %d S" x)) (kbd (format "%s<f4>" tkey)))
          (define-key km (kbd (format "M-[ 15 ; %d ~" x)) (kbd (format "%s<f5>" tkey)))
          (define-key km (kbd (format "M-[ 17 ; %d ~" x)) (kbd (format "%s<f6>" tkey)))
          (define-key km (kbd (format "M-[ 18 ; %d ~" x)) (kbd (format "%s<f7>" tkey)))
          (define-key km (kbd (format "M-[ 19 ; %d ~" x)) (kbd (format "%s<f8>" tkey)))
          (define-key km (kbd (format "M-[ 20 ; %d ~" x)) (kbd (format "%s<f9>" tkey)))
          (define-key km (kbd (format "M-[ 21 ; %d ~" x)) (kbd (format "%s<f10>" tkey)))
          (define-key km (kbd (format "M-[ 23 ; %d ~" x)) (kbd (format "%s<f11>" tkey)))
          (define-key km (kbd (format "M-[ 24 ; %d ~" x)) (kbd (format "%s<f12>" tkey)))
          (define-key km (kbd (format "M-[ 25 ; %d ~" x)) (kbd (format "%s<f13>" tkey)))
          (define-key km (kbd (format "M-[ 26 ; %d ~" x)) (kbd (format "%s<f14>" tkey)))
          (define-key km (kbd (format "M-[ 28 ; %d ~" x)) (kbd (format "%s<f15>" tkey)))
          (define-key km (kbd (format "M-[ 29 ; %d ~" x)) (kbd (format "%s<f16>" tkey)))
          (define-key km (kbd (format "M-[ 31 ; %d ~" x)) (kbd (format "%s<f17>" tkey)))
          (define-key km (kbd (format "M-[ 32 ; %d ~" x)) (kbd (format "%s<f18>" tkey)))
          (define-key km (kbd (format "M-[ 33 ; %d ~" x)) (kbd (format "%s<f19>" tkey)))
          (define-key km (kbd (format "M-[ 34 ; %d ~" x)) (kbd (format "%s<f20>" tkey)))
          ;; continue loop
          (setq x (+ x 1)))
        ;; return the keymap
        km))

;; this is the default key-translation-map
(setq default-translation-map (copy-keymap key-translation-map))

;; define a function which turns on the correct keymap on frame focus and add
;; to focus-in-hook FIXME find the right way to call automatically
(defun dennis-set-translation-map ()
  "Toggle the `key-translation-map' on frame focus."
  (interactive)
  (if (getenv "TMUX" (selected-frame)) ;; check env of current frame!
      (setq key-translation-map tmux-translation-map)
    (setq key-translation-map default-translation-map)))

Moving to beginning of line

Spotted here. Using C-a to toggle between going to the true beginning of the line and the beginning of the indented line. Goes to the true beginning of line first.

(defun beginning-of-line-or-indentation ()
  "move to beginning of line, or indentation"
  (interactive)
  (if (bolp)
      (back-to-indentation)
    (beginning-of-line)))
(global-set-key [remap move-beginning-of-line]
                'beginning-of-line-or-indentation)

Eshell

Eshell is interesting. It could be a great replacement for bash if I'm ever forced to use a windows machine. Also, it's just kind of cool to have sort of a lisp/shell hybrid thingy.

Hooks

First, let's define some hooks.

(require 'eshell)
(defun my-eshell-hook ()
  (eshell-read-aliases-list)
  (setq global-hl-line-mode nil)
  (setq show-trailing-whitespace nil)
  (setq-local ml-interactive? t) ;; for mode line
  (eshell/addpath "~/bin"))
(add-hook 'eshell-mode-hook 'my-eshell-hook)

General settings

Inspired by Howard's eshell config.

This next block defines some programs for which eshell launches a comint buffer. These are mostly curses programs.

(add-hook 'eshell-mode-hook
   (lambda ()
     (add-to-list 'eshell-visual-commands "ssh")
     (add-to-list 'eshell-visual-commands "htop")
     (add-to-list 'eshell-visual-commands "ncmpcpp")
     (add-to-list 'eshell-visual-commands "tail")))

Disable the banner message.

(setq eshell-banner-message "")

Clear the screen using the (regular?) clear command. Stolen from this old list post.

(defun eshell/clear ()
  "Deletes the contents of eshell buffer, except the last prompt"
  (save-excursion
    (goto-char eshell-last-output-end)
    (let ((lines (count-lines 1 (point)))
          (inhibit-read-only t))
      (beginning-of-line)
      (let ((pos (point)))
        (if (bobp)
            (if (interactive-p)
                (error "Buffer too short to truncate"))
          (delete-region (point-min) (point)))))))

Prompt

This is mimicking my fish shell prompt. Without the git indicator for now, but that should be easy to code up.

First, we want to make sure that the home directory is always replaced

(defun pwd-replace-home (pwd)
  "Replace home in PWD with tilde (~) character."
  (interactive)
  (let* ((home (expand-file-name (getenv "HOME")))
         (home-len (length home)))
    (if (and
         (>= (length pwd) home-len)
         (equal home (substring pwd 0 home-len)))
        (concat "~" (substring pwd home-len))
      pwd)))

Now the prompt function itself. Note that this setup should be modified somehow to play nice with tramp. I should decide based on (file-remote-p) whether I'm editing a remote file. If that's the case, take the default-directory string and either split it into "real" hostname and directory or truncate the /ssh:<hostname> part out of it if I can get the "real" hostname from somewhere else. Emacs will always have a small project for you…

;; a macro to propertize, stolen from emacswiki.
(defmacro with-face (str &rest properties)
  `(propertize ,str 'face (list ,@properties)))
;; the prompt function
(defun dennis-eshell-prompt ()
  (let*
      ;; need some string lengths to fill the screen with spaces
      ((spc-cnt (length hostname))
       (dir (pwd-replace-home (eshell/pwd)))
       (offset (if (display-graphic-p)
                   2 3)) ;; need extra offset in terminal
       (fill-size (- (window-body-width) (+ spc-cnt (length dir) offset))))
    ;; put together the prompt
    (concat
     "\n"
     (with-face hostname :foreground "cornflower blue" :weight "bold")
     (with-face "|" :foreground "red" :weight "bold")
     (with-face dir :foreground "gold" :weight "extra-bold")
     (make-string fill-size 32) ;; go to the right side
     ;; the return status indicator
     (if (equal eshell-last-command-status 0)
         (with-face (char-to-string #x2713) :foreground "green" :weight "bold")
       (with-face (char-to-string #x2717) :foreground "red" :weight "bold"))
     "\n"
     (make-string spc-cnt 32)
     (with-face "|" :foreground "red" :weight "bold")
     (with-face (char-to-string #x25b8) :foreground (face-attribute 'default :foreground))
     " ")))
;; set the prompt regexp for eshell to recognize what is prompt and what not.
(setq
 eshell-prompt-function 'dennis-eshell-prompt
 eshell-prompt-regexp (concat "\s*|" (char-to-string #x25b8) " ")) ;; \s* captures whitespace

Shell-mode

Use bash when invoking M-x shell.

(setq explicit-shell-file-name "/bin/bash")
(add-hook 'shell-mode-hook 'no-trailing-whitespace)
(add-hook 'shell-mode-hook '(lambda () (setq-local ml-interactive? t)))

Scheme

(setq scheme-program-name "guile")
(add-hook 'inferior-scheme-mode-hook 'no-trailing-whitespace)
(add-hook 'inferior-scheme-mode-hook '(lambda () (setq-local ml-interactive? t)))
;; geiser
(setq geiser-repl-use-other-window nil)
(setq geiser-active-implementations '(guile))
(add-hook 'geiser-repl-mode-hook 'no-trailing-whitespace)
(add-hook 'geiser-repl-mode-hook 'turn-on-paredit)
(add-hook 'geiser-repl-mode-hook '(lambda () (setq-local ml-interactive? t)))
;; scheme-mode
(add-hook 'scheme-mode-hook 'turn-on-paredit)
(add-hook 'scheme-mode-hook 'turn-on-geiser-mode)

Google this!

Take the current region and send it as search query to google.

(defun google ()
  "Googles a query or region if any."
  (interactive)
  (browse-url
   (concat
    "http://www.google.com/search?ie=utf-8&oe=utf-8&q="
    (if mark-active
        (buffer-substring (region-beginning) (region-end))
      (read-string "Google: ")))))

Show line numbers while using goto-line

Shamelessly stolen from here.

(defun goto-line-with-feedback ()
  "Show line numbers temporarily, while prompting for the line number input"
  (interactive)
  (let ((line-numbers-off-p (not linum-mode)))
    (unwind-protect
        (progn (when line-numbers-off-p
                 (linum-mode 1))
               (call-interactively 'goto-line))
      (when line-numbers-off-p
        (linum-mode -1)))))
(global-set-key [remap goto-line] 'goto-line-with-feedback)

Fireplace

(add-hook 'fireplace-mode-hook 'no-trailing-whitespace)

Delete trailing whitespace before saving a file

(add-hook 'before-save-hook 'delete-trailing-whitespace)

Fill line from point to end with some character

(defvar fill-to-end-with-char-column fill-column
  "The value that `fill-to-end' fills the column until.")

(defun fill-to-end-with-char (char)
  "Fill the current line with CHAR from point until the column
  including `fill-to-end-with-char-column'.

When called with a prefix argument, show a prompt asking for the
character to fill with. The default character to fill is '-'."
  (interactive
   (if current-prefix-arg
       (list (let ((input))
               (while (not (= (length input) 1))
                 (setq input (read-string "Fill with character: ")))
               input))
     (list "-")))
  (save-excursion
    (let* ((cur-point (point))
           (cur-point-on-line (- cur-point (point-at-bol)))
           (str-len (- fill-to-end-with-char-column cur-point-on-line))
           (str (make-string str-len (string-to-char char))))
      (goto-char cur-point)
      (insert-string str))))

Blogging using org-mode

This website is generated using the publishing features of org-mode and some custom Emacs Lisp, which is defined in this section. In coming up with this, I was inspired by other simple org-mode blogs here, here and especially here.

The publishing uses the Org HTML export backend a lot, so to start off, we require it here, along with the RSS publishing backend.

(require 'ox-html)
(require 'ox-rss)
(setq org-export-html-coding-system 'utf-8-unix)
(setq org-html-viewport nil)

Next, we define some functions and variables that will be used by org-publish. First, let's define the website headers, footers, and make sure that the exported HTML points to the right style sheets.

(setq my-blog-extra-head
      (concat
       "<link rel='stylesheet' href='/../res/code.css' />\n"
       "<link rel='stylesheet' href='/../res/main.css' />"))

(setq my-blog-header-file "~/repos/blog/header.html")
(defun my-blog-header (arg)
  (with-temp-buffer
    (insert-file-contents my-blog-header-file)
    (buffer-string)))

(setq my-blog-footer
      "<hr />\n
<p><span style=\"float: left;\"><a href= \"/blog.xml\">RSS</a></span>
License: <a href= \"https://creativecommons.org/licenses/by-sa/4.0/\">CC BY-SA 4.0</a></p>\n
<p><a href= \"/contact.html\"> Contact</a></p>\n")

I'd also like to export drawers out to HTML; this idea is ripped directly from here.

(defun my-blog-org-export-format-drawer (name content)
  (concat "<div class=\"drawer " (downcase name) "\">\n"
    "<h6>" (capitalize name) "</h6>\n"
    content
    "\n</div>"))

MathJax usually recommends to use their CDN to load their JavaScript code, but I want to use a version that sits on my server.

(setq my-blog-local-mathjax
      '((path "/res/mj/MathJax.js?config=TeX-AMS-MML_HTMLorMML")
        (scale "100") (align "center") (indent "2em") (tagside "right")
        (mathml nil)))

Now we'll get to some of the customizations I've bolted on Org's publishing features. In it's standard configuration, the sitemap generator produces a plain, kind of boring looking list of posts, which was inadequate for me. After hacking on the sitemap generation function for a little while, I came up with the following solution: When I write a blog post, I enclose the "preview" part of the post in #+BEGIN_PREVIEW...#+END_PREVIEW tags, which my (very simple) parser then inserts into the sitemap page.

(defun my-blog-get-preview (file)
  "The comments in FILE have to be on their own lines, prefereably before and after paragraphs."
  (with-temp-buffer
    (insert-file-contents file)
    (goto-char (point-min))
    (let ((beg (+ 1 (re-search-forward "^#\\+BEGIN_PREVIEW$")))
          (end (progn (re-search-forward "^#\\+END_PREVIEW$")
                      (match-beginning 0))))
      (buffer-substring beg end))))

(defun my-blog-sitemap (project &optional sitemap-filename)
  "Generate the sitemap for my blog."
  (let* ((project-plist (cdr project))
         (dir (file-name-as-directory
               (plist-get project-plist :base-directory)))
         (localdir (file-name-directory dir))
         (exclude-regexp (plist-get project-plist :exclude))
         (files (nreverse
                 (org-publish-get-base-files project exclude-regexp)))
         (sitemap-filename (concat dir (or sitemap-filename "sitemap.org")))
         (sitemap-sans-extension
          (plist-get project-plist :sitemap-sans-extension))
         (visiting (find-buffer-visiting sitemap-filename))
         file sitemap-buffer)
    (with-current-buffer
        (let ((org-inhibit-startup t))
          (setq sitemap-buffer
                (or visiting (find-file sitemap-filename))))
      (erase-buffer)
      ;; loop through all of the files in the project
      (while (setq file (pop files))
        (let ((fn (file-name-nondirectory file))
              (link ;; changed this to fix links. see postprocessor.
               (file-relative-name file (file-name-as-directory
                                         (expand-file-name (concat (file-name-as-directory dir) "..")))))
              (oldlocal localdir))
          (when sitemap-sans-extension
            (setq link (file-name-sans-extension link)))
          ;; sitemap shouldn't list itself
          (unless (equal (file-truename sitemap-filename)
                         (file-truename file))
            (let (;; get the title and date of the current file
                  (title (org-publish-format-file-entry "%t" file project-plist))
                  (date (org-publish-format-file-entry "%d" file project-plist))
                  ;; get the preview section from the current file
                  (preview (my-blog-get-preview file))
                  (regexp "\\(.*\\)\\[\\([^][]+\\)\\]\\(.*\\)"))
              ;; insert a horizontal line before every post, kill the first one
              ;; before saving
              (insert "-----\n")
              (cond ((string-match-p regexp title)
                     (string-match regexp title)
                     ;; insert every post as headline
                     (insert (concat"* " (match-string 1 title)
                                    "[[file:" link "]["
                                    (match-string 2 title)
                                    "]]" (match-string 3 title) "\n")))
                    (t (insert (concat "* [[file:" link "][" title "]]\n"))))
              ;; add properties for `ox-rss.el' here
              (let ((rss-permalink (concat (file-name-sans-extension link) ".html"))
                    (rss-pubdate (format-time-string
                                  (car org-time-stamp-formats)
                                  (org-publish-find-date file))))
                (org-set-property "RSS_PERMALINK" rss-permalink)
                (org-set-property "PUBDATE" rss-pubdate))
              ;; insert the date, preview, & read more link
              (insert (concat date "\n\n"))
              (insert preview)
              (insert (concat "[[file:" link "][Read More...]]\n"))))))
      ;; kill the first hrule to make this look OK
      (goto-char (point-min))
      (let ((kill-whole-line t)) (kill-line))
      (save-buffer))
    (or visiting (kill-buffer sitemap-buffer))))

Next I define some pre-and postprocessors that run during the publishing process. They are used to move around some files before and after publishing.

(setq my-blog-emacs-config-name "emacsconfig.org")
(setq my-blog-process-emacs-config t)

(defun my-blog-pages-preprocessor (project-plist)
  "Move a fresh version of the settings.org file to the pages directory."
  (when my-blog-process-emacs-config
    (let* ((cfg-file (expand-file-name (concat (file-name-as-directory user-emacs-directory)
                                               "settings.org")))
           (destdir (file-name-as-directory (plist-get project-plist :base-directory)))
           (cfg-file-dest (expand-file-name (concat destdir my-blog-emacs-config-name))))
      (copy-file cfg-file cfg-file-dest t))))

(defun my-blog-pages-postprocessor (project-plist)
  (message "In the pages postprocessor."))

(defun my-blog-articles-preprocessor (project-plist)
  (message "In the articles preprocessor."))

(defun my-blog-articles-postprocessor (project-plist)
  "Massage the sitemap file and move it up one directory.

for this to work, we have already fixed the creation of the
relative link in the sitemap-publish function"
  (let* ((sitemap-fn (concat (file-name-sans-extension (plist-get project-plist :sitemap-filename)) ".html"))
         (sitemap-olddir (plist-get project-plist :publishing-directory))
         (sitemap-newdir (expand-file-name (concat (file-name-as-directory sitemap-olddir) "..")))
         (sitemap-oldfile (expand-file-name sitemap-fn sitemap-olddir))
         (sitemap-newfile (expand-file-name (concat (file-name-as-directory sitemap-newdir) sitemap-fn))))
    (with-temp-buffer
      (goto-char (point-min))
      (insert-file-contents sitemap-oldfile)
      ;; massage the sitemap if wanted

      ;; delete the old file and write the correct one
      (delete-file sitemap-oldfile)
      (write-file sitemap-newfile))))

The next preprocessor runs CSSTidy on the site's CSS.

(defun my-blog-minify-css (project-plist)
  (let* ((csstidy "csstidy")
         (csstidy-args " --template=highest --silent=true")
         (css-dir (expand-file-name (plist-get project-plist :publishing-directory)))
         (css-files (directory-files css-dir t "^.*\\.css$")))
    (dolist (file css-files)
      (with-temp-buffer
        (insert (shell-command-to-string (concat csstidy " " file csstidy-args)))
        (write-file file)))))

Most of the publishing settings are defined in org-publish-project-alist.

(setq org-publish-project-alist
      `(("blog"
         :components ("blog-articles", "blog-pages", "blog-rss", "blog-res", "blog-images", "blog-dl"))
        ("blog-articles"
         :base-directory "~/repos/blog/blog/"
         :base-extension "org"
         :publishing-directory "~/repos/blog/www/blog/"
         :publishing-function org-html-publish-to-html
         :preparation-function my-blog-articles-preprocessor
         :completion-function my-blog-articles-postprocessor
         :htmlized-source t ;; this enables htmlize, which means that I can use css for code!

         :with-author t
         :with-creator nil
         :with-date t

         :headline-level 4
         :section-numbers nil
         :with-toc nil
         :with-drawers t
         :with-sub-superscript nil ;; important!!

         ;; the following removes extra headers from HTML output -- important!
         :html-link-home "/"
         :html-head nil ;; cleans up anything that would have been in there.
         :html-head-extra ,my-blog-extra-head
         :html-head-include-default-style nil
         :html-head-include-scripts nil
         :html-viewport nil

         :html-format-drawer-function my-blog-org-export-format-drawer
         :html-home/up-format ""
         :html-mathjax-options ,my-blog-local-mathjax
         :html-mathjax-template "<script type=\"text/javascript\" src=\"%PATH\"></script>"
         :html-footnotes-section "<div id='footnotes'><!--%s-->%s</div>"
         :html-link-up ""
         :html-link-home ""
         :html-preamble my-blog-header
         :html-postamble ,my-blog-footer

         ;; sitemap - list of blog articles
         :auto-sitemap t
         :sitemap-filename "blog.org"
         :sitemap-title "Blog"
         ;; custom sitemap generator function
         :sitemap-function my-blog-sitemap
         :sitemap-sort-files anti-chronologically
         :sitemap-date-format "Published: %a %b %d %Y")
        ("blog-pages"
         :base-directory "~/repos/blog/pages/"
         :base-extension "org"
         :publishing-directory "~/repos/blog/www/"
         :publishing-function org-html-publish-to-html
         :preparation-function my-blog-pages-preprocessor
         :completion-function my-blog-pages-postprocessor
         :htmlized-source t

         :with-author t
         :with-creator nil
         :with-date t

         :headline-level 4
         :section-numbers nil
         :with-toc nil
         :with-drawers t
         :with-sub-superscript nil ;; important!!
         :html-viewport nil ;; hasn't worked yet

         ;; the following removes extra headers from HTML output -- important!
         :html-link-home "/"
         :html-head nil ;; cleans up anything that would have been in there.
         :html-head-extra ,my-blog-extra-head
         :html-head-include-default-style nil
         :html-head-include-scripts nil

         :html-format-drawer-function my-blog-org-export-format-drawer
         :html-home/up-format ""
         :html-mathjax-options ,my-blog-local-mathjax
         :html-mathjax-template "<script type=\"text/javascript\" src=\"%PATH\"></script>"
         :html-footnotes-section "<div id='footnotes'><!--%s-->%s</div>"
         :html-link-up ""
         :html-link-home ""

         :html-preamble my-blog-header
         :html-postamble ,my-blog-footer)
        ("blog-rss"
         :base-directory "~/repos/blog/blog/"
         :base-extension "org"
         :publishing-directory "~/repos/blog/www/"
         :publishing-function org-rss-publish-to-rss

         :html-link-home "https://ogbe.net/"
         :html-link-use-abs-url t

         :title "Dennis Ogbe"
         :rss-image-url "https://ogbe.net/img/feed-icon-28x28.png"
         :section-numbers nil
         :exclude ".*"
         :include ("blog.org")
         :table-of-contents nil)
        ("blog-res"
         :base-directory "~/repos/blog/res/"
         :base-extension ".*"
         :publishing-directory "~/repos/blog/www/res/"
         :publishing-function org-publish-attachment
         :completion-function my-blog-minify-css)
        ("blog-images"
         :base-directory "~/repos/blog/img/"
         :base-extension ".*"
         :publishing-directory "~/repos/blog/www/img/"
         :publishing-function org-publish-attachment
         :recursive t)
        ("blog-dl"
         :base-directory "~/repos/blog/dl/"
         :base-extension ".*"
         :publishing-directory "~/repos/blog/www/dl/"
         :publishing-function org-publish-attachment
         :Recursive t)))

Finally, define a small template for new blog posts.

(add-to-list 'org-structure-template-alist
             '("b" "#+TITLE: ?
#+AUTHOR: Dennis Ogbe
#+EMAIL: do@ogbe.net
#+DATE:
#+STARTUP: showall
#+STARTUP: inlineimages
#+BEGIN_PREVIEW\n\n#+END_PREVIEW\n"))

Email settings - mu4e

These are my settings for the email client mu4e. Some of the code blocks are not visible, since I don't want to put potentially personal information on the interwebs.

Load mu4e

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

Aliases

(defalias 'email 'mu4e)
(defalias 'em 'mu4e)
(defalias 'encrypt-email 'mml-secure-message-encrypt-pgpmime)
(defalias 'sign-mail 'mml-secure-message-sign-pgpmime)

General Settings

Collecting Mail

I originally used OfflineIMAP to sync mail from my mail servers to my local Maildir. This worked for a while, but unfortuantely OfflineIMAP is quite slow and buggy. During my big email overhauling, I ended up switching to the much faster (because it's written in C) mbsync. Every 2 minutes it syncs from and to my mail server.

(setq
 mu4e-get-mail-command "mbsync -aqV"
 mu4e-change-filenames-when-moving t
 mu4e-update-interval 120)

Sending Mail

I'm sending mail using msmtp, but just using msmtp as a replacement for sendmail has one major flaw: it is not asynchronous! Large messages will halt Emacs for a few uncomfortable seconds. Of course, this can be fixed with a few tricky lines of elisp.

I implemented a quick-and-dirty queuing system by saving messages to disk first instead of sending them. After a message is "sent" (or better: "put in the queue"), message-sent-hook calls function which starts asynchronous msmtp processes. Errors during transmission are being caught by a process sentinel that offers some options.

We first set some variables. Make sure that mail-queue-dir exists. This is where our queue will be.

(require 'sendmail)
(setq mail-queue-dir "~/.emacs.d/mail_queue")
(setq mail-queue-program "msmtp")
(setq sendmail-program "msmtp")

Now, some helper functions. The only interactive function is M-x mail-queue, which checks on the status of the queue.

(defun mail-queue ()
  "Inform about the number of messages in the queue.

Ask to flush if queue non-empty."
  (interactive)
  (let ((queue-len (length(cdr (cdr (directory-files mail-queue-dir))))))
    (if (zerop queue-len)
        (message "No messages in queue.")
      (when (y-or-n-p (format "%d messages in the queue. Flush? " queue-len))
        (mail-queue-flush)))))

(defun mail-queue-load-msg (hash)
  "Return the message list for the message HASH"
  (let ((fn (concat (file-name-as-directory mail-queue-dir) hash)))
    (when (file-exists-p fn)
      (read (with-temp-buffer (insert-file-contents fn) (buffer-string))))))

(defun mail-queue-delete-mail (hash)
  "Delete msg HASH from queue."
  (delete-file
   (concat (file-name-as-directory mail-queue-dir) hash)))

(defun mail-queue-get-from-alist (key al)
  "Return KEY from alist AL."
  (car (cdr (assoc key al))))

(defun mail-queue-flush ()
  "Attempt to send all messages currently in the queue."
  (let ((queue (cdr (cdr (directory-files mail-queue-dir)))))
    (dolist (msg queue)
      (mail-queue-send-mail msg))))

The interface to message-mode is mail-queue-save-msg-to-disk, which inserts a message in the queue.

(defun mail-queue-save-msg-to-disk ()
  "Save the message to queue folder on disk"
  ;; first part taken from `message-send-mail-with-sendmail'
  (let ((case-fold-search t))
    (save-restriction
      (message-narrow-to-headers)
      (setq resend-to-addresses (message-fetch-field "resent-to")))
    ;; Change header-delimiter to be what sendmail expects.
    (goto-char (point-min))
    (re-search-forward
     (concat "^" (regexp-quote mail-header-separator) "\n"))
    (replace-match "\n")
    (backward-char 1)
    (setq delimline (point-marker))
    (run-hooks 'message-send-mail-hook)
    ;; Insert an extra newline if we need it to work around
    ;; Sun's bug that swallows newlines.
    (goto-char (1+ delimline))
    (when (eval message-mailer-swallows-blank-line)
      (newline))
    (let* ((coding-system-for-write message-send-coding-system)
           (arglist (append
                     (list "-oi")
                     message-sendmail-extra-arguments
                     ;; Always specify who from,
                     ;; since some systems have broken sendmails.
                     ;; But some systems are more broken with -f, so
                     ;; we'll let users override this.
                     (and (null message-sendmail-f-is-evil)
                          (list "-f" (message-sendmail-envelope-from)))
                     ;; These mean "report errors by mail"
                     ;; and "deliver in background".
                     (if (null message-interactive) '("-oem" "-odb"))
                     ;; Get the addresses from the message
                     ;; unless this is a resend.
                     ;; We must not do that for a resend
                     ;; because we would find the original addresses.
                     ;; For a resend, include the specific addresses.
                     (if resend-to-addresses
                         (list resend-to-addresses)
                       '("-t"))))
           ;; Save the message and its arguments to sendmail as a file in
           ;; `mail-queue-dir'.
           ;; The filename is the SHA256 hash of the
           ;; message. We're saving an alist containing some info about message
           (msg-body (buffer-substring-no-properties delimline (point-max)))
           (msg-header (buffer-substring-no-properties (point-min) delimline))
           (msg-hash (secure-hash 'sha256 (concat msg-header msg-body)))
           (msg-alist (list (cons 'args (list arglist))
                            (cons 'body (list msg-body))
                            (cons 'header (list msg-header))
                            (cons 'hash (list msg-hash)))))
      (with-temp-buffer
        (goto-char (point-min))
        (insert (prin1-to-string msg-alist))
        (write-file (concat (file-name-as-directory mail-queue-dir) msg-hash))))))

To send a mail, we call mail-queue-send-mail with the SHA256 hash of the message (which is also the filename) argument. This will prep the message for msmtp and pass it on to mail-queue-deliver-mail, which starts an asynchronous msmtp subprocess.

(defun mail-queue-send-mail (hash)
  "Prep the message HASH for delivery and pass to `mail-queue-deliver-mail'."
  ;; load the message
  (let* ((msg-alist (mail-queue-load-msg hash))
         (args (mail-queue-get-from-alist 'args msg-alist))
         (body (mail-queue-get-from-alist 'body msg-alist))
         (header (mail-queue-get-from-alist 'header msg-alist))
         (saved-hash (mail-queue-get-from-alist 'hash msg-alist))
         (computed-hash (secure-hash 'sha256 (concat header body))))
    ;; check hash, if everything checks out, attempt to deliver
    (if (string= saved-hash computed-hash)
        (mail-queue-deliver-mail args (concat header body) hash)
      ;; if hashes don't check out, something bad happened. Inspect body, but
      ;; delete msg --FIXME use `error' instead
      (message "Hash mismatch, expect: %s. got: %s, deleting"
               (substring-no-properties saved-hash 0 16)
               (substring-no-properties computed-hash 0 16))
      (pop-to-buffer (get-buffer-create "Mail hash error"))
      (erase-buffer)
      (insert body)
      (mail-queue-delete-mail hash))))

(defun mail-queue-deliver-mail (sendmail-args msg hash)
  "Attempt to Send the msg HASH using sendmail using the arguments SENDMAIL-ARGS.

The message MSG is the *full* message, a concatenation of header and body."
  (let* ((pname (concat "msmtp: " (substring-no-properties hash 0 16)))
         (errbuf (get-buffer-create pname))
         (sendmail-proc nil))
    ;; Insert the hash of the message in the buffer to identify it on the
    ;; callback
    (with-current-buffer errbuf
      (erase-buffer)
      (goto-char (point-min))
      (insert (concat hash "\n")))
    ;; start the sendmail process
    (setq sendmail-proc
          (apply #'start-process
                 (append (list pname errbuf mail-queue-program) sendmail-args)))
    (set-process-sentinel
     sendmail-proc
     ;; The callback function. Executed when sendmail finishes.
     (lambda (process event)
       (let* ((ret (process-exit-status process))
              (errbuf (process-buffer process))
              (err-str nil)
              (hash-len 64)
              ;; first HASH-LEN chars are hash of the message.
              (hash (with-current-buffer errbuf
                      (buffer-substring-no-properties (point-min) (+ hash-len 1))))
              (hash16 (substring-no-properties hash 0 16)))
         (unwind-protect
             (progn (if (zerop ret) ;; success: tell me and remove the message from the queue
                        (progn (message "Successfully sent %s" hash16)
                               (mail-queue-delete-mail hash))
                      ;; else something went wrong
                      (pop-to-buffer errbuf)
                      (setq err-str(format "Sending of %s failed with exit code %d" hash16 ret))
                      (goto-char (point-max))
                      (insert (concat "\n" err-str))
                      (read-only-mode)
                      (mail-queue-unsuccessful hash)))
           ;; always kill error buffer
           (when (bufferp errbuf)
             (kill-buffer errbuf))))))
    ;; give the message to sendmail
    (process-send-string sendmail-proc msg)
    (process-send-eof sendmail-proc)))

If there is an error during the transmission, the callback defined in mail-queue-deliver-mail will then use the following two functions to query the user about how to continue.

(defun mail-queue-unsuccessful (hash)
  "Sending the mail HASH was unsuccessful. Query what to do next.

Query in the minibuffer the following 3 options:
  1) inspect message in new buffer
  2) delete message
  3) keep the message in the queue"
  (let* ((hash16 (substring-no-properties hash 0 16))
         (ret (mail-queue-fail-choices hash16)))
    (cond ((eq ret 'inspect) ;; use body of message in new message
           (let ((msg-alist (mail-queue-load-msg hash))
                 (buf (get-buffer-create "mail contents")))
             (pop-to-buffer buf)
             (insert (mail-queue-get-from-alist 'body msg-alist)))
           ;; delete old
           (mail-queue-delete-mail hash)
           (message "Deleted %s." hash16))
          ((eq ret 'delete) ;; delete the message
           (mail-queue-delete-mail hash)
           (message "Deleted %s." hash16))
          ((eq ret 'keep) ;; keep message in queue
           (message "Keeeping %s in queue." hash16)))))

(defun mail-queue-fail-choices (hash16)
  "Present User with choices for what to do next.

Return either DELETE, KEEP, or INSPECT, based on selection."
  (let ((correct-regexp "[dDkKiI]")
        (input "")
        (prompt (format
                 "Sending %s failed. [d]elete, [k]eep, [i]nspect body?: "
                 hash16)))
    (message "%s" prompt)
    (while (not (string-match correct-regexp input))
      (setq input (read-string prompt)))
    (cond ((string-match "[dD]" input) 'delete)
          ((string-match "[kK]" input) 'keep)
          ((string-match "[iI]" input) 'inspect))))

All done! Now, let's tell message-mode to use our newly defined system.

(setq message-send-mail-function 'mail-queue-save-msg-to-disk)
(add-hook 'message-sent-hook 'mail-queue-flush)
(setq mu4e-sent-messages-bahavior 'sent)

Use Imagemagick for images

(when (fboundp 'imagemagick-register-types)
  (imagemagick-register-types))

Spell check when composing

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

Misc. Settings

(setq
 mu4e-attachment-dir "~/Downloads"
 mu4e-show-images t
 mu4e-view-show-images t
 mu4e-compose-signature-auto-include nil
 mu4e-view-show-addresses 't
 message-kill-buffer-on-exit t
 mu4e-decryption-policy t
 mail-user-agent 'mu4e-user-agent
 mu4e-compose-dont-reply-to-self t
 mu4e-hide-index-messages t)
(add-hook 'mu4e-org-mode-hook 'no-trailing-whitespace)

No autocompletion during compose

(defun turn-off-company ()
  (company-mode -1))
(add-hook 'mu4e-compose-mode-hook 'turn-off-company)

Keys

Instead of quitting mu4e when q is pressed, I want it to keep running and just go back to the previously used buffer.

(define-key mu4e-main-mode-map "q" 'previous-buffer)

RET to open URLS:

(define-key mu4e-view-mode-map (kbd "RET") 'mu4e~view-browse-url-from-binding)

Appearance

Fancy characters

(setq mu4e-use-fancy-chars t)
(setq mu4e-headers-unread-mark    (purecopy '("u" . "✉")))

Disable underlines

(set-face-underline 'mu4e-header-highlight-face nil)

Do not show trailing whitespace

(defun no_whitespace ()
  (setq show-trailing-whitespace nil))
(add-hook 'mu4e-main-mode-hook 'no_whitespace)
(add-hook 'mu4e-compose-mode-hook 'no_whitespace)
(add-hook 'mu4e-view-mode-hook 'no_whitespace)
(add-hook 'mu4e-headers-mode-hook 'no_whitespace)

HTML Viewing

When we encounter an HTML message, we want to render it using shr. I personally prefer using the mu4e-action-view-as-pdf action, but I'm having a hard time using that action as my mu4e-html2text command. For now, I'll try to make the shr2text rendering a little more bearable.

(setq
 mu4e-view-prefer-html nil ;; always prefer plaintext
 mu4e-html2text-command 'mu4e-shr2text ;; render within emacs
 shr-color-visible-luminance-min 80 ;; better for dark themes
 mu4e-msg2pdf "/usr/bin/msg2pdf") ;; this is nice
(add-hook 'mu4e-view-mode-hook
          (lambda ()
            ;; try to emulate some of the eww key-bindings
            (local-set-key (kbd "<tab>") 'shr-next-link)
            (local-set-key (kbd "<backtab>") 'shr-previous-link)))

We also want to add another action to our MSGV actions. This lets us render a message in a web browser.

(add-to-list 'mu4e-view-actions
  '("ViewInBrowser" . mu4e-action-view-in-browser) t)

Serifs in message view

I want to display plaintext emails with a nice serif font, but this messes HTML rendering using shr up, we have to be a little nuanced here.

;; small wrapper fn
(defun mail-serif ()
  "Toggle serif font without making noise"
  (toggle-serif nil))
;; always compose in plain text
(add-hook 'mu4e-compose-mode-hook 'mail-serif)
;; don't use serif font when message is html
(defun mail-serif-if-plaintext ()
  "Toggle serif font if the email only has an HTML body."
  (let ((has-txt? (mu4e-field-at-point :body-txt)))
    (when has-txt? (mail-serif))))
(add-hook 'mu4e-view-mode-hook 'mail-serif-if-plaintext)

visual-line-mode

(add-hook 'mu4e-view-mode-hook '(lambda () (visual-line-mode 1)))

Accounts

Before the great mail overhaul of 2016, I had a bunch of tricks and hacks to coordinate different mailboxes (A personal Gmail mailbox and a work/school mailbox). All of this has changed and with the advent of my personal mail server, so this section has slimmed down significantly.

Address list

I need to have a list of addresses in mu4e-user-mail-address-list, so that I don't reply to myself. This code block is not exported to HTML to not expose all of my addresses to the WWW.

;; my default address. In this source block so I can show off some code later on
(setq my-default-mail-address "do@ogbe.net")

;; our main "from" addresses. Handle SMTP relaying at the server.
(setq my-mail-from-addrs
      `(,my-default-mail-address "dogbe@purdue.edu" "ogbe.dennis@gmail.com"))

;; these two need to be in here to not reply to ourselves.
(setq mu4e-user-mail-address-list
      (append '("dogbe@ecn.purdue.edu" "daden91@gmail.com")
              my-mail-from-addrs))

;; since we possibly want to reply to messages sent to a certain address with
;; a different one, we construct this hash table
(setq my-reply-to-table (make-hash-table :test 'equal))
(mapcar (lambda (x) (puthash x x my-reply-to-table))
        (cons "daden91@gmail.com" my-mail-from-addrs))
; this is where the money is: map @ecn.purdue addresses to @purdue.
(puthash "dogbe@ecn.purdue.edu" "dogbe@purdue.edu" my-reply-to-table)
(puthash "hknexec@ecn.purdue.edu" "dogbe@purdue.edu" my-reply-to-table)
(puthash "hkn@ecn.purdue.edu" "dogbe@purdue.edu" my-reply-to-table)
(puthash "hkn-list@ecn.purdue.edu" "dogbe@purdue.edu" my-reply-to-table)
(puthash "cloudradio-list@ecn.purdue.edu" "dogbe@purdue.edu" my-reply-to-table)

Set account dymamically

We want to reply to emails using the address the original email was sent to. I'm using two variables here: my-mail-from-addrs holds all of the adresses that I want to be able to use in the "From" field when composing. my-reply-to-table holds a hash table which is used when I'm replying to a message. This might be a little convoluted, but I want to be able to have a customized mapping between the email address that was adressed by someone to the email address that I reply with. This is easier with an example: Let's say that I have some sort of email forwarding going on and I routinely receive messages addressed to foo@mail.bar.xyz. However, when replying to these messages, I want to use the adress baz@bar.xzy. In order to achieve this mapping, I just add it to my table: (puthash "foo@mail.bar.xyz" "baz@bar.xzy" my-reply-to-table).

(defun my-mu4e-set-from-addr ()
  "Set the FROM address when composing a message."
  (if (not mu4e-compose-parent-message)
      ;; pick an address
      (setq user-mail-address
            (ido-completing-read "From: " my-mail-from-addrs))
    ;; else reply with same email address: search through address mapping table
    ;; and change user-mail-address if found
    (let ((addr))
      (maphash
       (lambda (k v)
         (when (mu4e-message-contact-field-matches mu4e-compose-parent-message :to k)
           (setq addr v)))
       my-reply-to-table)
      ;; if nothing matches, use default
      (if addr
          (setq user-mail-address addr)
        (setq user-mail-address my-default-mail-address)))))
(add-hook 'mu4e-compose-pre-hook 'my-mu4e-set-from-addr)

Maildir & Account settings

These variables need to be set in order for things to work out correctly. This is a lot less configuration (and a lot cleaner) than what I had to do before.

(setq
 mu4e-sent-folder "/Sent"
 mu4e-drafts-folder "/Drafts"
 mu4e-trash-folder "/Trash"
 ;; my-default-mail-address holds the mail address that I want to use as
 ;; default. Just a placeholder to remove from HTML output
 user-mail-address my-default-mail-address
 user-full-name "Dennis Ogbe")

Dired to attach files

Make the gnus-dired-mail-buffers function also work on message-mode derived modes, such as mu4e-compose-mode to attach file, just mark in dired and C-c RET C-a

(require 'gnus-dired)
(defun gnus-dired-mail-buffers ()
  "Return a list of active message buffers."
  (let (buffers)
    (save-current-buffer
      (dolist (buffer (buffer-list t))
  (set-buffer buffer)
  (when (and (derived-mode-p 'message-mode)
    (null message-sent-message-via))
    (push (buffer-name buffer) buffers))))
    (nreverse buffers)))
(setq gnus-dired-mail-mode 'mu4e-user-agent)
(add-hook 'dired-mode-hook 'turn-on-gnus-dired-mode)

Notifications

Use notify-send to alert me when there are new messages, but do so asynchronously and without showing any messages in the echo area.

(defun display-mail-notification ()
  (start-process ;; start a subprocess in the background
   "mail_notify" nil
   shell-file-name
   shell-command-switch
   (concat
    "nohup 1>/dev/null 2>/dev/null "
    "~/repos/scripts/mail_notify "
    (number-to-string mu4e-update-interval))))
(add-hook 'mu4e-index-updated-hook 'display-mail-notification)

The script that is called from this command looks like this:

#!/bin/bash

# timestamp of previous mail sync
NBACK=$(date +%s --date="$1 sec ago")
# number of messages after timestamp
NMAIL=$(mu find flag:unread maildir:/INBOX --after=$NBACK 2>/dev/null | wc -l)

if [ $NMAIL -eq 1 ]
then
    aplay "$HOME/.emacs.d/mail.wav" &
    notify-send --icon=emblem-mail "1 new message"
elif [ $NMAIL -gt 1 ]
then
    aplay "$HOME/.emacs.d/mail.wav" &
    notify-send --icon=emblem-mail "$NMAIL new messages"
fi

Bookmarks

The button to press after 'b' is defined after the '?' below.

(setq mu4e-bookmarks
      `(;; bu
        ("maildir:/INBOX flag:unread date:today"
         "unread mail from today" ?s)
        ;; b2
        ("date:2d..now AND maildir:/INBOX"
         "last 2 days" ?2)
        ;; bt
        ("date:today AND maildir:/INBOX"
         "today" ?t)
        ;; bu
        ("flag:unread"
         "all unread" ?u)
        ;; ba
        ("maildir:/*"
         "all mail" ?a)
        ;; bo
        ("maildir:/Sent"
         "outgoing mail" ?o)))

Crypto

The reply to an encrypted email should always be encrypted.

(defun my-auto-encrypt ()
  (let ((msg mu4e-compose-parent-message))
    (when msg
      (when (member 'encrypted (mu4e-message-field msg :flags))
        (mml-secure-message-encrypt-pgpmime)))))
(add-hook 'mu4e-compose-mode-hook 'my-auto-encrypt)

A small helper function to decrypt PGP/Inline.

(defun decrypt-inline-pgp ()
  "Decrypt a PGP MESSAGE block in the current buffer."
  (interactive)
  (save-excursion
    (let* ((pm (point-max))
           (beg (progn (re-search-forward "^-----BEGIN PGP MESSAGE-----$" pm t 1)
                       (match-beginning 0)))
           (end (re-search-forward "^-----END PGP MESSAGE-----$" pm t 1)))
      (if (and beg end)
          (epa-decrypt-region beg end)
        (message "No encrypted region found.")))))

Load at emacs startup

Finally, we want to enable mu4e at startup.

(mu4e t)

RSS License: CC BY-SA 4.0

Contact