Henry's Emacs configuration

Table of Contents

Notes about this file itself (how does this work?)

This Org-mode file is used to generate my Emacs config via the "tangle" feature. The various source blocks in this file will be written out to init.el whenever the org-babel-tangle function is called.

DONE Work out why setting :tangle yes in the header-args doesn't work

For some reason, despite both the GNU and Orgmode.org manuals telling me it should be possible, I can't just set :tangle yes once at the top of the file and be done with it.

Progress: it does work when :header-args: :tangle yes is put in the properties drawers of the top-level headings, but not when #+PROPERTY: header-args :tangle yes is put at the top of the file.

TODO Auto-run the tangle function when saving this file

Not really sure it's worth it given how little I change this file, but could prevent confusion/annoyance if I ever forget to run org-babel-tangle.

Emacs built-ins

Firstly I configure everything which is internal to Emacs itself (i.e. not using any third-party packages).

Startup

Skip the splash screen and go straight to the scratch buffer:

(setq inhibit-startup-message t)

I flip-flop between Go Mono and Inconsolata for fonts; setting :tangle no on either of the following blocks is all that's needed. I should probably find out how to set fonts other than duplicating the Customize config.

Go Mono, the update of Luxi Mono with better hinting:

(custom-set-faces
 ;; custom-set-faces was added by Custom.
 ;; If you edit it by hand, you could mess it up, so be careful.
 ;; Your init file should contain only one such instance.
 ;; If there is more than one, they won't work right.
 '(default ((t (:family "Go Mono" :slant normal :weight normal :height 110 :width normal)))))

Inconsolata, classic improvement on Consolas:

(custom-set-faces
 ;; custom-set-faces was added by Custom.
 ;; If you edit it by hand, you could mess it up, so be careful.
 ;; Your init file should contain only one such instance.
 ;; If there is more than one, they won't work right.
 '(default ((t (:family "Inconsolata" :slant normal :weight normal :height 120 :width normal)))))

I don't use the scratch buffer for elisp, I use it for notes instead so change the mode from elisp to Org:

(setq initial-major-mode 'org-mode)
(setq initial-scratch-message "\
#+STARTUP: content
Use this for quick notes you can't be bothered to think about filing.
It is automatically saved & restored when quitting/launching emacs.

That said, it is sometimes useful to have an elisp scratch buffer:

#+BEGIN_SRC emacs-lisp

#+END_SRC
")

Global custom functions

Reusable functions, yay!

Buffer local keys

For setting keybindings local to a particular buffer rather than mode, you need this function (local-set-key will work otherwise).

(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)))

Smarter "go to beginning of line"

Instead of the default C-a behaviour, map a function to cycle between column 0 and the start of text indentation.

(defun hjst::cycle-line-start ()
  "Cycle between column 0 and the start of indentation."
  (interactive)
  (let ((orig (point)))
    (back-to-indentation)
    (when (= orig (point))
      (move-beginning-of-line 1))))

(global-set-key (kbd "C-a") 'hjst::cycle-line-start)
(global-set-key (kbd "<home>") 'hjst::cycle-line-start)

Text editing

Spelling

The default Emacs ispell package works well on my system. Emacs will apparently choose aspell above hunspell, and either of them above ispell. With the aspell-en package installed on Fedora, and my locale set to en_GB it all works as expected.

Viper mode

I like being able to enable/disable Viper mode (vi emulation) for those times when I really want vi modal commands for editing some text (mostly I live without them). So far I prefer this to "full time" solutions like Evil, but I'm still experimenting.

(global-set-key (kbd "<f2>") 'toggle-viper-mode)

Line wrapping

I like to enable visual line mode (word-wrapping) for all modes that inherit from text mode (i.e. any kind of prose writing, but leaves code untouched).

(add-hook 'text-mode-hook 'turn-on-visual-line-mode)

Ido mode

For a full explanation of why, see Mickey Petersen's introduction to Ido Mode.

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

rcirc

References:

rcirc has the limitation that it will only open 1 connection per host (not even to different ports). This means that ZNC's method of "open multiple connections each with a different username like henry/networkname" fails. One workaround is to create host aliases in /etc/hosts. Or, to simplify this config mess, connect once and use the ZNC JumpNetwork command.

This is connecting through a local ssh tunnel with autossh like so: autossh -M 0 -f -N -L 34567:localhost:34567 pi, see dotfiles/sshconfig for further details.

The actual server config setq is in ~/.emacs.d/secrets.el.gpg and looks like this:

(setq rcirc-server-alist
      '(
        ("localhost"
         :port 34567
         :encryption tls
         :nick "henry"
         :user-name "henry/network-name"
         :password "totally-super-secure-password")))

Turn on the modeline activity indicators (only load it if/after I run rcirc in the current emacs session).

(eval-after-load "rcirc"
  '(progn
     ;; enable tracking of activity pings as a minor mode
     (rcirc-track-minor-mode 1)))

Adjust a few cosmetic settings for display of lines. The rcirc-fill-flag variable, set to nil, means no padding on wrapped lines: this makes for a more compact display for smaller frames.

(setq rcirc-prompt "»» "
      rcirc-time-format "%H:%M "
      rcirc-fill-flag t)

"Conservative" scrolling means keeping the current line at the bottom of the buffer, which is what you want for irc as new lines appear from the server.

(add-hook 'rcirc-mode-hook
          (lambda ()
            (set (make-local-variable 'scroll-conservatively) 8192)))

For some odd reason visual line mode (i.e. text wrapping at word boundaries) isn't on by default for rcirc buffers, so explicitly set it here.

(add-hook 'rcirc-mode-hook 'turn-on-visual-line-mode)

The default colours in rcirc are garish, so replace some of them with solarized equivalents.

(eval-after-load "rcirc"
  '(progn
     ;; Use solarized blue for my own nick
     (set-face-attribute 'rcirc-my-nick nil :foreground "#268bd2")
     ;; Use solarized gold for other nicks
     (set-face-attribute 'rcirc-other-nick nil :foreground "#b58900")
     ;; Change my nick highlight from bold neon purple to normal solarized red
     (set-face-attribute 'rcirc-nick-in-message-full-line nil :foreground "#cb4b16" :weight 'normal)
     (set-face-attribute 'rcirc-nick-in-message nil :foreground "#cb4b16" :weight 'normal)
     ;; Use solarized green instead of bright red for server messages
     (set-face-attribute 'rcirc-server nil :foreground "#859900")))

Killing an rcirc buffer with C-x k results in a /part, which isn't ideal with ZNC. The following (taken from here) defines a new "detach this buffer then kill it" function, and binds it to C-c C-d:

(eval-after-load "rcirc"
  '(progn  
     (defun rcirc-detach-buffer ()
       (interactive)
       (let ((buffer (current-buffer)))
         (when (and (rcirc-buffer-process)
                    (eq (process-status (rcirc-buffer-process)) 'open))
           (with-rcirc-server-buffer
            (setq rcirc-buffer-alist
                  (rassq-delete-all buffer rcirc-buffer-alist)))
           (rcirc-update-short-buffer-names)
           (if (rcirc-channel-p rcirc-target)
               (rcirc-send-string (rcirc-buffer-process)
                                  (concat "DETACH " rcirc-target))))
         (setq rcirc-target nil)
         (kill-buffer buffer)))

     (define-key rcirc-mode-map [(control c) (control d)] 'rcirc-detach-buffer)))

DONE Split rcirc config into light + heavy bits

The rcirc-track-minor-mode call for instance, should only ever happen after the program is running. Perhaps the static config of the server details could be split out of the hook?

TODO Configure nick colouring?

Details are here, it's simple enough: load a .el file, configure which colours you want to use, done. Seem to be doing ok without it though so far…

Gnus

I'm still experimenting with different Gnus setups; for the moment I'm accessing Gmail directly via IMAP (rather than mirroring locally) and using the search / expire / score mechanics.

I should probably break up & document this messy block sometime:

(setq user-mail-address "henry@hjst.org"
      user-full-name "Henry Todd")

;; ask encryption password once
(setq epa-file-cache-passphrase-for-symmetric-encryption t)

(require 'nnir) ; used for searching mails
;; @see http://gnus.org/manual/gnus_397.html
(setq gnus-select-method
             '(nnimap "gmail"
                      (nnimap-address "imap.gmail.com")
                      (nnimap-server-port 993)
                      (nnimap-stream ssl)
                      (nnir-search-engine imap)
                      ; @see http://www.gnu.org/software/emacs/manual/html_node/gnus/Expiring-Mail.html
                      ;; press 'E' to expire email
                      (nnmail-expiry-target "nnimap+gmail:[Gmail]/Trash")
                      (nnmail-expiry-wait 3)))

(setq gnus-thread-sort-functions
      '((not gnus-thread-sort-by-date)
        (not gnus-thread-sort-by-number)))

; NO 'passive
(setq gnus-use-cache t)

;; Fetch only part of the article if we can.
(setq gnus-read-active-file 'some)

;; Tree view for groups.
(add-hook 'gnus-group-mode-hook 'gnus-topic-mode)

;; Thread mail whenever possible, try to intelligently ignore Subject:
(setq gnus-summary-thread-gathering-function 'gnus-gather-threads-by-references)
(setq gnus-thread-ignore-subject 'fuzzy)
;; Pull in enough old messages to show complete threads
(setq gnus-fetch-old-headers 'some)

;; Also, I prefer to see only the top level message.  If a message has
;; several replies or is part of a thread, only show the first message.
;; 'gnus-thread-ignore-subject' will ignore the subject and
;; look at 'In-Reply-To:' and 'References:' headers.
(setq gnus-thread-hide-subtree t)

(setq message-send-mail-function 'smtpmail-send-it
      smtpmail-starttls-credentials '(("smtp.gmail.com" 587 nil nil))
      smtpmail-auth-credentials (expand-file-name "~/.authinfo")
      smtpmail-default-smtp-server "smtp.gmail.com"
      smtpmail-smtp-server "smtp.gmail.com"
      smtpmail-smtp-service 587
      gnus-ignored-newsgroups "^to\\.\\|^[0-9. ]+\\( \\|$\\)\\|^[\"]\"[#'()]"
      smtpmail-local-domain "raspberrypi"
      ;; gnutls-bin package must be installed for this to work
      starttls-use-gnutls t)

If Emacs is ever killed unceremoniously, Gnus has an auto-save "dribble" file which it uses to track read states, and it asks if you want to load it when next started. Can't see why you'd ever answer "no" to that question, so I override the default behaviour.

(setq gnus-always-read-dribble-file t)

RSS feeds & mailing lists via NNTP

Thank you Lars. Here's the entirety of my Gmane config:

(add-to-list 'gnus-secondary-select-methods '(nntp "news.gmane.org"))

My group subscriptions & read/unread state tracking live in ~/.newsrc and ~/.newsrc.eld. Those are managed by Gnus and I don't touch them. If/when I start Gnus without them on a new machine, I'd need to go through the Server list view for each and mark/unmark my groups again.

DONE Secure my auth credentials

Creds are stored in ~/.authinfo.gpg, which emacs will auto-handle encrypting/decrypting via EasyPG so long as the .gpg extension is present. See the epa-file-cache-passphrase-for-symmetric-encryption variable for more info.

EWW

I'm mostly happy with the Emacs web browser (especially the Instapaper-esque R command), but it defaults to using proportional fonts so I disable that.

(setq shr-use-fonts nil)

Buffer management

I very rarely want to kill a buffer other than the one I'm currently in (and I use IBuffer for that anyway) so remap C-x k to be kill-this-buffer instead of kill-buffer to avoid the prompt (still prompts if buffer contains unsaved changes):

(global-set-key (kbd "C-x k") 'kill-this-buffer)

TRAMP

From the TRAMP wiki page, this apparently speeds things up:

(setq tramp-default-method "ssh")

Secrets

Put passwords or other similarly sensitive info in a auto-encrypt/auto-decrypt GPG file. Gnus does its own version of this with authinfo.

(load-file "~/.emacs.d/secrets.el.gpg")

MELPA packages

Repository & use-package initialisation

This is all done with John Wiegley's use-package. First thing is to define the list of repositories. For now I'm sticking with MELPA and the dedicated Org repo.

(require 'package)
(add-to-list 'package-archives
             '("melpa" . "https://melpa.org/packages/") t)
(add-to-list 'package-archives
             '("org" . "http://orgmode.org/elpa/") t)

Setting package-enable-at-startup to nil disables the autoloading of all packages which would normally happen at init. It's faster to let use-package handle loading of packages as they're needed.

(setq package-enable-at-startup nil)
(package-initialize)

The next bit is cute: it bootstraps use-package if it isn't already installed. This is handy when cloning my dotfiles repo to a new machine, as all I need to do is start emacs and wait while everything installs.

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

I'm using use-package as my sole package installation method, so I set this once here rather than every time calling the function:

(setq use-package-always-ensure t)

Setup is complete so hand over control to use-package:

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

Better defaults

This (Github) is a small collection of preference tweaks I agree with, mostly from the #emacs channel on freenode. The only thing I re-enable is the menu bar, which I do still find useful.

(use-package better-defaults
  :demand t
  :config (menu-bar-mode 1))

Solarized colour theme

It's a toss-up for me which is better: solarised or tango. Currently I'm leaning towards solarised. Check the customisation section of the README for the full details. However, it does need a few tweaks.

Use less garish markers for things like git-gutter & flycheck:

(setq solarized-emphasize-indicators nil)

Keep headings and titles as monospace:

(setq solarized-use-variable-pitch nil)

These make the modelines look flatter and makes the active buffer more obvious:

(setq x-underline-at-descent-line t)
(setq solarized-high-contrast-mode-line t)

Load the light variant by default:

(use-package solarized-theme
  :config (load-theme 'solarized-light t))

Persistent scratch buffer

I sometimes start emacs just to quickly jot down some text; this package lets me treat the scratch buffer as a magically auto-saving place to shove stuff without thinking.

(use-package persistent-scratch
  :config
  (persistent-scratch-setup-default)
  (persistent-scratch-autosave-mode))

Org-mode

Install a more-up-to-date Org mode from the dedicated ELPA repo, which overrides the version bundled with emacs.

(use-package org
  :pin org)

Clean mode

I prefer the default view of org-mode documents to be clean. For longer book-like (rather than outline/list-like) documents the #+STARTUP: feature can be used at the top of the file to customise things.

(setq org-startup-indented t org-hide-leading-stars t)

theme-changer

This configures emacs to switch automatically between a light/dark theme as the sun goes down.

(setq calendar-location-name "Hereford, GB")
(setq calendar-latitude 52.06)
(setq calendar-longitude -2.72)
(use-package theme-changer
  :config (change-theme 'solarized-light 'solarized-dark))

N.B. if you get Wrong type argument: number-or-marker-p errors when initialising this package, check that the calendar-* defs above match your current timezone. You'll get an error if the Emacs calendar location doesn't match your computer's timezone (see manual).

Smex

Smex is an M-x replacement that provides ido style completion. It's also capable of restricting the available commands to those specific to the active major mode (e.g. org-mode or magit).

(use-package smex
  :config (smex-initialize)
  :bind (("M-x" . smex)
         ("M-X" . smex-major-mode-commands)))

simple-mpc

Acts as a lightweight emacs UI for mpc. Mingus is the main alternative, but that implements its own mpd lib in elisp vs simple-mpc's use of the existing mpc binary. I guess if I ever need a Windows client Mingus would be useful? Source is on Github.

(use-package simple-mpc)

TODO Add emacs keybinds for mpc play/pause/next

Magit

Makes working with git repos within emacs a breeze. Just hit C-x g to begin.

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

Git auto-commit

I find this useful for my notes files, it auto-commits & does a git push every time the files are written. I wish magit offered this (it sort of does with its wip options?) but this package does the job. I control it with a .dir-locals.el file that evals git-auto-commit-mode for everything under ~/Notes.

(use-package git-auto-commit-mode
  :init (setq gac-automatically-push-p t))

Simplenote

Turns emacs into a Simplenote client for easy syncing of notes across devices. It maintains a local cache of notes files, and requires manual push via the API (see key bindings below).

Notes specially marked in the API as "markdown formatted" are treated specially; all other notes are assumed to be in Org format (and load org-mode for formatting/editing).

I use the Buffer local keys function defined above to override the standard C-x C-s binding for save-buffer. I also add a binding to assign tags to notes, and a global bind for showing the list of notes.

(use-package simplenote2
  :init
  (setq simplenote2-notes-mode 'org-mode)
  (setq simplenote2-markdown-notes-mode 'markdown-mode)
  (simplenote2-setup)
  :config
  (add-hook 'simplenote2-note-mode-hook
            (lambda ()
              (buffer-local-set-key (kbd "C-x C-s") 'simplenote2-push-buffer)
              (buffer-local-set-key (kbd "C-c C-t") 'simplenote2-add-tag)))
  :bind ("<f4>" . simplenote2-list))

NB: the ~/.emacs.d/secrets.el.gpg file contains the following sensitive config:

(setq simplenote2-email "user@host.com")
(setq simplenote2-password "hunter2")

Jira for Org mode

This lets me pull in + manipulate Jira tickets with Org mode. It's useful when I need to write/markup long ticket descriptions and don't want to use the web UI (and risk losing data if the VPN has gone down). Bit of a pain for day-to-day small stuff though.

Username/password are configured in authinfo like so: machine your-site.atlassian.net login you@example.com password yourPassword port 80. If these aren't present a prompt appears when first connecting; the creds are cached for the life of the emacs session.

To get started open e.g. ~/.org-jira/DAZ.org, run M-x org-jira-get-issue and enter a ticket reference like "ABC-123". When you want to push your changes to Jira, do org-jira-update-issue.

To add a comment, under the existing comments (or at the end of the issue's section) add:

** Comment:
Then type the body of your comment here. This is handy if you want to leave a long, formatted comment with a bunch of copy/pasting.

_NOTE:_ the colon after "Comment" is essential

Then, with the cursor within the comment, run org-jira-update-comment.

There's a YouTube screencast that shows more usage examples. See also the documentation for the org-jira-mode function.

(use-package org-jira
  :init (setq jiralib-url "https://issues.lolacloud.com"))

Python packages

For now at least, I'm avoiding using Elpy because…

  • it's got a lot of moving parts I don't understand
  • the virtualenv integration doesn't work on Windows
  • I can get by just fine with the built-in python.el

YAML

For now the default yaml-mode behaviour seems to be fine, but any required Ansible-specific tweaks would go here.

(use-package yaml-mode)

Markdown

Jason Belvins' markdown mode handles everything here; I've not found a reason to change the defaults yet. I use markdown2 as a local implementation, which is installed on Fedora via the python3-markdown2 package. The tables and fenced-code-blocks extras are enabled to better approximate Github's flavour.

(use-package markdown-mode
  :ensure t
  :commands (markdown-mode gfm-mode)
  :mode (("README\\.md\\'" . gfm-mode)
         ("\\.md\\'" . markdown-mode)
         ("\\.markdown\\'" . markdown-mode))
  :init (setq markdown-command "markdown2-3 --extras tables,fenced-code-blocks"))

js2-mode

The built-in javascript mode is pretty terrible, so I'm trying js2-mode as an alternative.

(use-package js2-mode
  :config
  (add-to-list 'auto-mode-alist '("\\.js\\'" . js2-mode))
  (setq js-indent-level 2))

Web mode

Works very well for editing plain HTML, as well as being smart about things like CSS/JS embedded in HTML docs, as well as templating logic (Mustache, ERB etc.).

(use-package web-mode
  :init
  (add-to-list 'auto-mode-alist '("\\.html?\\'" . web-mode))
  (add-to-list 'auto-mode-alist '("\\.erb\\'" . web-mode))
  (add-to-list 'auto-mode-alist '("\\.as[cp]x\\'" . web-mode))
  (add-to-list 'auto-mode-alist '("\\.mustache\\'" . web-mode))
  :config
  (setq web-mode-markup-indent-offset 2)
  (setq web-mode-css-indent-offset 2)
  (setq web-mode-code-indent-offset 2))

JSON

The default highlighting for JSON documents in emacs isn't great (it's handled by js-mode). This adds a dedicated JSON mode, as well as the handy "display path to the object at point" function (bound to C-c C-p by default). I rebind C-c C-f to json-pretty-print, which is a builtin from Emacs 24.4 onwards.

(use-package json-snatcher)
(use-package json-mode
  :bind ("C-c C-f" . json-pretty-print))

I do still miss Eli Parra's vim-json though.

Author: Henry Todd

Created: 2017-04-08 Sat 09:14

Validate