16 KiB
Emacs Configuration
Shout out to Harry R. Schwartz; A whole bunch of this config (including the idea of embeddeding the lot in an Org document) is yanked from his dotfiles repo.
The rest of this config grabs packages via use-package
, so that
needs to be set up to install them if they aren't already.
(require 'use-package-ensure)
(setq use-package-always-ensure t)
UI
The start-up message gets pretty annoying, so disable that.
(setq inhibit-startup-screen t)
I like a little more line spacing than default.
(setq-default line-spacing 0.2)
Also, the menu-, tool- and scroll-bar are ugly, take up space and I don't use them.
(menu-bar-mode -1)
(tool-bar-mode -1)
(scroll-bar-mode -1)
Colour Scheme
Currently using spacemacs-theme
's light variant, but I prefer a pure
white background to the off-white it has by default.
(use-package spacemacs-theme
:defer t)
(setq spacemacs-theme-custom-colors
'((bg1 . "#ffffff")
(comment-bg . "#ffffff")))
(load-theme 'spacemacs-light t)
Org-mode
I use a couple non-standard bits and pieces, but not a whole
bunch. I really like the <s
to insert a source block thing (which
was deprecated); org-tempo
brings that back.
(use-package org
:ensure org-plus-contrib
:config
(require 'org-tempo))
A keybinding to add a new heading is super useful
(add-hook 'org-mode-hook
(lambda ()
(define-key org-mode-map
(kbd "<C-M-return>")
'org-insert-heading-after-current)))
Source Blocks
Pressing tab inside a source block should indent appropriately for its language.
(setq org-src-tab-acts-natively t)
babel
lets us evaluate Org documents containing source blocks!
I've left the enabling of this for most languages to the section
for that language, but I'll add Emacs Lisp and shell here.
(org-babel-do-load-languages
'org-babel-load-languages
'((emacs-lisp . t)
(shell . t)))
By default trying to execute a source block prompts you, which is
super annoying since I'm realistically not going to try to run any
code from Org documents I haven't written, so that needs
disabling. You can do that by setting org-confirm-babel-evaluate to
nil
.
(setq org-confirm-babel-evaluate nil)
Another annoying thing that happens by default is the clobbering of the window layout when you open a source block. You can change that by setting org-src-window-setup.
(setq org-src-window-setup 'split-window-below)
Exporting
I very rarely want a table of contents, as most of my org documents are pretty short.
(setq org-export-with-toc nil)
HTML
htmlize
is needed for decent HTML exporting, but there is no need
for all that stuff at the bottom.
(use-package htmlize)
(setq org-html-postamble nil)
LaTeX
Use minted
(LaTeX package) to do syntax highlighting in code blocks:
(add-to-list 'org-latex-packages-alist '("" "minted"))
(setq org-latex-listings 'minted)
minted
actually calls pygments
through the shell, which pdflatex
doesn't like; you have to tell it not to worry, and that everything is
going to be OK.
(setq org-latex-pdf-process
'("xelatex -shell-escape -interaction nonstopmode -output-directory %o %f"
"xelatex -shell-escape -interaction nonstopmode -output-directory %o %f"
"xelatex -shell-escape -interaction nonstopmode -output-directory %o %f"))
Roam
A Zettelkasten in org mode? Yes please. It does need sqlite3
installed outside of Emacs land.
(use-package org-roam)
As stated in the manual, org-roam needs to know where notes are
stored. I'm going to go with ~/org/zet
, as its nice having all
org documents under ~/org
, but also we need to distinguish
zettels from other org stuff.
(make-directory "~/org/zet" t)
(setq org-roam-directory "~/org/zet")
And, also as recommended, we'll start org-roam-mode after init:
(add-hook 'after-init-hook 'org-roam-mode)
Default Applications
It's all fun and games until C-c C-e h o
opens the source code.
(setq org-file-apps
'(("html" . "firefox %s")
(auto-mode . emacs)))
Start up
Org is better suited as scratch space than Funamental, I'd say.
(setq initial-major-mode 'org-mode)
(setq initial-scratch-message "")
Magit
magit
is truly a wonderful creation! Only deviations from defaults
here are a keybinding for magit-status
and a maximum length for the
summary line of commit messages (after which the excess is
highlighted).
(use-package magit
:bind
("C-x g" . magit-status)
:config
(setq git-commit-summary-max-length 72))
Language Integrations
Generally, 8-character-wide tabs are not my thing.
(setq-default tab-width 4)
(setq-default basic-offset 4)
And generally indenting with spaces is more common, so make that the default:
(setq-default indent-tabs-mode nil)
C
For C, I like to indent with tabs and align with spaces: this
behaviour is provided by smart-tabs-mode
.
(use-package smart-tabs-mode)
(smart-tabs-insinuate 'c)
I'll generally format my code in BSD style but I also use
clang-format
a lot, so I have a keybinding to run that.
(setq c-default-style "bsd")
(use-package clang-format)
(add-hook 'c-mode-hook
(lambda ()
(define-key c-mode-map (kbd "C-M-f")
'clang-format-buffer)))
Meson is my build system of choice for C, but I also use CMake a lot.
(use-package meson-mode)
(use-package cmake-mode)
Haskell
My workflow with Haskell is very REPL-based, so I always want
interactive-haskell-mode
on.
(use-package haskell-mode)
(require 'haskell-interactive-mode)
(add-hook 'haskell-mode-hook 'interactive-haskell-mode)
And, of course, that REPL needs to be taking advantage of parallelism!
(require 'haskell-process)
(set-variable 'haskell-process-args-ghci
'("-threaded" "+RTS" "-N8" "-RTS"))
Idris
The only thing to change from the defaults here is to add a more convenient way to case-split.
(use-package idris-mode)
(add-hook 'idris-mode-hook
(lambda ()
(define-key idris-mode-map (kbd "C-c SPC")
'idris-case-split)))
Rust
I never really use Rust without Cargo, so always turn on the minor mode for Cargo in Rust buffers.
(use-package rust-mode)
(use-package cargo)
(add-hook 'rust-mode-hook 'cargo-minor-mode)
Lisps
Racket
Get racket-mode
for some Racket-specific things, like searching documentation
(use-package racket-mode)
Common Lisp
Use SLIME and Quicklisp for Common Lisp (SBCL), with a convenient
binding for slime-selector
(use-package slime)
(setq inferior-lisp-program "sbcl")
(global-set-key (kbd "C-c s") 'slime-selector)
(load (expand-file-name "~/quicklisp/slime-helper.el"))
And we also want to enable execution of CL source blocks in Org mode, which we do by adding an item to org-babel-load-languages.
(org-babel-do-load-languages
'org-babel-load-languages
'((lisp . t)))
Paredit
paredit
is generally very useful for balancing parenthesis so we
want that turned on for all the lisps. Additionally, it's nice to have
an entire expression highlighted when the cursor is on one of its
enclosing parens.
(use-package paredit)
(setq lispy-mode-hooks
'(emacs-lisp-mode-hook
lisp-mode-hook
racket-mode-hook
scheme-mode-hook
slime-repl-mode-hook))
(dolist (hook lispy-mode-hooks)
(add-hook hook (lambda ()
(setq show-paren-style 'expression)
(paredit-mode))))
YAML
I don't really like YAML if I'm honest, but it's used a lot so…
(use-package yaml-mode)
Desktop
EXWM
One must fulfil the meme of doing everything with Emacs… still got a lot of tweaking to do here before I'm happy.
(use-package exwm
:config
(require 'exwm-config)
(exwm-config-default))
One annoying thing here is that the default config turns on
ido-mode
, which I don't really like. The solution is to not use
the default config, but for the time being:
(add-hook 'after-init-hook
(lambda () (ido-mode nil)))
Multi-monitor
Multi-monitor support is provided in exwm-randr
:
(require 'exwm-randr)
(exwm-randr-enable)
When I have my laptop connected to a monitor I want the built-in display to turn off, but turn back on when it's disconnected. Turns out this is a total pain.
To start with we need a function to tell whether a monitor's
attached. exwm-randr
provides exwm-randr–get-monitors, but its
result is not – as I'd expect – a list of monitors, but instead a
rather complicated mess that is (as far as I can tell)
undocumented. Rather than trying to figure out what was going on
there, I opted for the search-in-shell-command-output route
(defun hdmi-connected-p ()
(string-match-p "HDMI-2 connected"
(shell-command-to-string "xrandr")))
With that defined, an exwm-randr-screen-change-hook can then be added to turn the built-in display on and off appropriately.
(add-hook 'exwm-randr-screen-change-hook
(lambda ()
(let ((xrandr-command
(if (hdmi-connected-p)
"xrandr --output eDP-1 --off --output HDMI-2 --auto"
"xrandr --output eDP-1 --auto")))
(start-process-shell-command "xrandr" nil xrandr-command))))
Mode Line
Clock
The time is a useful thing to know… and 12-hour clock is for losers.
(setq display-time-24hr-format t)
(display-time-mode 1)
Battery
Also useful to know, but only on a laptop… once I'm using this configuration on Mandarax as well I'll probably have to conditionally disable it.
(display-battery-mode 1)
Passwords
This was a little more work than I expected… password-store
provides a nice interface to pass
, but annoyingly appears to
depend on f
without declaring so.
(use-package password-store)
(use-package f)
However, in order for it to actually work, EasyPG had to be configured to use loopback for pinentry.
(setq epa-pinentry-mode 'loopback)
gpg-agent
also had to be configured to allow loopback for
pinentry – this was done by adding allow-loopback-pinentry
to
gpg-agent.conf.
With that all working, all that remains is to add a convenient
keybinding for getting a password, which is done by the function
password-store-copy
. C-c C-p
does conflict with some modal
bindings, but I think that's fine as most of the time I'll need a
password it'll be in some X window anyway… and besides, M-x
pass-co RET
isn't bad for when it does happen to conflict.
(global-set-key (kbd "C-c C-p") 'password-store-copy)
Music Player
MPD is clearly the correct way to build a music player, so all that's needed is a client for it… after having played around with a couple of them, Mingus is the one I've like the most.
(use-package mingus)
Currently using mu4e
for mail. Not sure whether this is my 'final'
set up, I might give notmuch
a try at some point.
mu4e
is a bit annoying as it's bundled along with mu
rather than
being loaded from ELPA or MELPA, so it can't be loaded with
use-package
. Indeed, how to load it depends on how mu
was
packaged. On NixOS at least, mu4e
gets put in a place where Emacs
is able to find it, so it just needs to be require
'd.
(require 'mu4e)
I'm sure this will break at some point; when it does, probably makes sense to do something like:
(let ((uname-output (shell-command-to-string "uname -a")))
(cond ((string-match-p "NixOS" uname-output) nil)
...))
(require 'mu4e)
To get the correct address by default:
(setq user-mail-address "cdo@wip.sh")
And to avoid being tickled:
(setq mail-host-address "hactar")
Automatic updating
For updating through mu4e
to actually work, mu4e-get-mail-command
needs to be set to offlineimap
. New mail can be then fetched
regularly with mu4e-update-mail-and-index using run-with-timer.
(setq mu4e-get-mail-command "offlineimap")
(run-with-timer 0 120 (lambda ()
(mu4e-update-mail-and-index t)))
Mode-line alert
mu4e-alert
provides a convenient little icon that shows up
whenever mu4e
has unread mail.
(use-package mu4e-alert
:config
(add-hook 'after-init-hook
#'mu4e-alert-enable-mode-line-display))
Sending with sendmail
I have msmtp
set up so use that to send mail.
(setq send-mail-function 'sendmail-send-it)