Henry's Emacs configuration
Table of Contents
- Notes about this file itself (how does this work?)
- Emacs built-ins
- MELPA packages
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.