27 KiB
Emacs Configuration
- Customize
- Package Management
- Emacs Server
- UI
- Autocompletion
- Org
- Language Integrations
- Tool Integrations
- Backup and Autosave
Customize
Hey, customize, leave my ~/.emacs.d/init.el alone!
(setq custom-file (concat user-emacs-directory "customize.el"))
(load custom-file t)
Package Management
MELPA
Let's be real here, all the good stuff's on MELPA.
(require 'package)
(add-to-list 'package-archives
'("melpa" . "https://melpa.org/packages/"))
Initialization
We now need to run package-initialize to load and activate packages. The documentation advises doing this early in configuration.
(package-initialize)
We also need to fetch the package archives from ELPA and MELPA, unless they have already been fetched:
(unless package-archive-contents
(package-refresh-contents))
use-package
The rest of this config grabs packages via use-package
, so that
needs to be installed:
(when (not (package-installed-p 'use-package))
(package-install 'use-package))
The wanted behaviour for use-package
here is to ensure all used
packages are present.
(require 'use-package-ensure)
(setq use-package-always-ensure t)
Emacs Server
Start an Emacs server if one is not running already:
(require 'server)
(unless (server-running-p)
(server-start))
With this, files opened with emacsclient
in a terminal will open
in Emacs. Most of the time I use Emacs itself for my file browsing
and terminal needs but emacsclient
is still handy from
time-to-time.
UI
The start-up message gets pretty annoying, so disable that.
(setq inhibit-startup-screen t)
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)
It's nice to have an inverse of C-x o
for switching between
windows. other-window
, the function that C-x o
is bound to,
takes an argument COUNT
that determines how many windows it skips
forwards so we can simply pass -1 to other-window
in a lambda and
bind to that:
(global-set-key (kbd "C-x O")
(lambda ()
(interactive)
(other-window -1)))
Font
I find serif fonts much nicer to read, so change the default face to a serif font:
(set-face-attribute 'default nil
:family "Courier 10 Pitch"
:height 100)
I also like a little more line spacing than default, again makes code nicer to read.
(setq-default line-spacing 0.2)
Colour Scheme
Currently using spacemacs-theme
's light variant.
(use-package spacemacs-theme
:defer t)
(load-theme 'spacemacs-light t)
The first tweak I make is changing the borders around the mode line to be two pixels thick and the same colour as the background (of the active mode line that is).
(set-face-attribute 'mode-line nil
:box '(:line-width 2 :color "#e7e5eb" :style nil))
(set-face-attribute 'mode-line-inactive nil
:box '(:line-width 2 :color "#e7e5eb" :style nil))
I also set the right window divider to the same colour as the header background.
(set-face-attribute 'window-divider nil :foreground "#efeae9")
(setq initial-frame-alist '((right-divider-width . 1)))
Opening buffers in current window
There are several places where buffers open in different windows to the currently selected one. I find this behaviour annoying and I don't understand why anyone would like it. display-buffer-alist provides a mechanism for preventing this where there isn't a better way, as seems to be the case with shell and help buffers.
(defun open-in-same-window-p (buffer-name action)
(or (string= (upcase buffer-name) "*SHELL*")
(string= (upcase buffer-name) "*HELP*")))
(setq display-buffer-alist
'((open-in-same-window-p . (display-buffer-same-window . nil))))
Autocompletion
Enable company-mode
globally, and hook it into completion-at-point-functions
.
(use-package company
:config
(add-hook 'after-init-hook 'global-company-mode)
(add-to-list 'company-backends 'company-capf))
And enable ido-mode
everywhere, with flexible matching.
(use-package ido
:config
(setq ido-enable-flex-matching t)
(add-hook 'after-init-hook
(lambda ()
(ido-everywhere)
(ido-mode t))))
Org
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
: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)))
Org is nice for scratch space
(setq initial-major-mode 'org-mode)
(setq initial-scratch-message "")
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)
Asyncronous Execution
ob-async
makes source blocks with the :async
keyword execute
asyncronously, super handy for long-running snippets etc.
(use-package ob-async)
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 and org-roam-db-autosync-mode after init:
(add-hook 'after-init-hook
(lambda ()
(org-roam-mode)
(org-roam-db-autosync-mode)))
Hook it into Ido.
(setq org-roam-completion-system 'ido)
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)))
Workflow States
I like to have IN-PROGRESS
and CANCELLED
workflow states as
well as the standard TODO
and DONE
. Cancelled items also want a
note attached explaining why. All this can be added by setting
org-todo-keywords:
(setq org-todo-keywords
'((sequence "TODO" "IN-PROGRESS" "|" "DONE" "CANCELLED(@)")))
The "|"
separates needs further action states (before it) from
no further action needed states (after it).
I also want to log the date and time when a note is marked as done:
(setq org-log-done 'time)
Agenda
Time to try org-mode's agenda feature again I think. Last time I didn't end up using it much, but I am much more of an Emacs addict now so I do forsee it actually surviving (this will be funny to read in the future if not).
I want to show all TODOs in files in my top-level ~/org
directory, along with those under ~/org/zet/daily
(daily notes)
and any in this config itself. This is done by setting
org-agenda-files.
(setq org-agenda-files
'("~/org" "~/org/zet/daily" "~/.emacs.d/config.org"))
Also I find it really very annoying that the the current window
layout is destroyed when you run org-agenda
. That behaviour is
changed by setting org-agenda-window-setup:
(setq org-agenda-window-setup 'current-window)
Habits
Habit tracking requires the habits org module to be loaded. This is
done by adding the symbol 'habits
to org-modules, if it's not in
there already. I originally didn't have the surrounding unless
,
but it causes problems when re-loading the config using
org-babel-load-file.
(unless (member 'habits org-modules)
(add-to-list 'org-modules 'habits))
Language Integrations
Generic
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)
Language Server Protocol
LSP seems to be the way forward in terms of IDE-like features in
Emacs; grab lsp-mode
and enable lsp-deferred
:
(use-package lsp-mode
:init (setq lsp-keymap-prefix "C-c l")
:commands (lsp lsp-deferred))
lsp-deferred
means that the LSP server will only be started once
a buffer is actually opened, which makes more sense to me.
Also going to give lsp-ui
a shot, which displays a bunch of
information from the language server in the buffer. It looks like
it could be a bit much but we'll see.
(use-package lsp-ui :commands lsp-ui-mode)
For LSP's most excellent autocompletion to work properly
yas-minor-mode must be enabled, so hook that into lsp-mode
:
(add-hook 'lsp-mode-hook
(lambda ()
(yas-minor-mode)))
To enable Ido integration:
(require 'lsp-ido)
Smart Tabs
Indent with tabs and align with spaces. Installing the package here but it's enabled on a per-language basis in the languages' individual config sections.
(use-package smart-tabs-mode)
The package has this really annoying behaviour that it turns
itself off if indent-tabs-mode is nil, even when you just
explicitly turned it on. The solution on the Emacs wiki is to set
indent-tabs-mode to t in a c-mode-common
hook, which is a bit of
a hack, but I tried my own approach and it didn't work for no
apparent reason so I'm just going to do as I'm told.
(add-hook 'c-mode-common-hook
(lambda () (setq indent-tabs-mode t)))
C
For C there is clangd
implementing LSP. Assuming that's
installed and on the PATH
, we can just hook lsp-mode
into the
default mode and there will be much rejoicing.
(add-hook 'c-mode-hook #'lsp-deferred)
As for indenting style, I like BSD-style but with 4-char-wide indents
(add-hook 'c-mode-hook (lambda ()
(c-set-style "bsd")
(setq c-basic-offset 4)))
And we want to enable smart tabs:
(smart-tabs-insinuate 'c)
C++
Essentially the same story as for C.
(add-hook 'c++-mode-hook #'lsp-deferred)
(add-hook 'c++-mode-hook (lambda ()
(c-set-style "bsd")
(setq c-basic-offset 4)))
(smart-tabs-insinuate 'c++)
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
Common Lisp
Use SLIME and Quicklisp for Common Lisp (SBCL).
(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"))
This assumes that SBCL and Quicklisp, along with Quicklisp's SLIME helper, are installed. Once SBCL is installed, Quicklisp and its SLIME helper can be installed by grabbing the installer and loading it with SBCL.
curl -O https://beta.quicklisp.org/quicklisp.lisp
sbcl --load quicklisp.lisp
That will open a REPL with the quicklisp-quickstart
system
loaded. At that REPL, run:
(quicklisp-quickstart:install)
(ql:add-to-init-file)
(ql:quickload "quicklisp-slime-helper")
Source blocks
To enable execution of CL source blocks in Org mode, add 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))))
Scheme and Racket
Geiser is a pretty complete collection of Scheme things for Emacs. Only change from the defaults is to open the REPL in the current window instead of creating a new one.
(use-package geiser
:config
(setq geiser-repl-use-other-window nil))
I commonly use Chez, Guile and Racket so we want the packages for those:
(use-package geiser-chez)
(use-package geiser-guile)
(use-package geiser-racket)
YAML
I don't really like YAML if I'm honest, but it's used a lot so…
(use-package yaml-mode)
Javascript
The first bit of this setup (js2-mode
, js2-refactor
and
xref-js2
) is essentially copied from this Emacs cafe post.
First of all we want to grab js2-mode
and enable it for
javascript buffers. It extends the default js-mode
and builds an
AST which can be used by other packages.
(use-package js2-mode)
(add-to-list 'auto-mode-alist '("\\.js\\'" . js2-mode))
Refactoring
js2-refactor
provides refactoring tools based of said AST, so
enable that and its keybindings:
(use-package js2-refactor)
(add-hook 'js2-mode-hook #'js2-refactor-mode)
(js2r-add-keybindings-with-prefix "C-c C-r")
It provides a kill function with nice semantics for Javascript – we definitely want that instead of the generic kill.
(define-key js2-mode-map (kbd "C-k") #'js2r-kill)
Find references / jump to definition
Then we get to xref-js2
, which adds stuff for jumping to
references and definitions (uses the ag
tool, so that must be
installed in the environment):
(use-package xref-js2)
js-mode
binds M-.
, which conflicts with xref-js2
so we need to unbind that:
(define-key js-mode-map (kbd "M-.") nil)
And hook it up to js2-mode
:
(add-hook 'js2-mode-hook (lambda ()
(add-hook 'xref-backend-functions #'xref-js2-xref-backend nil t)))
Using local tools from NPM
To use tools locally by NPM, there is add-node-modules-path
:
(use-package add-node-modules-path)
(eval-after-load 'js2-mode
'(add-hook 'js2-mode-hook #'add-node-modules-path))
Autoformatting
Prettier seems low-effort to set up :D
(use-package prettier-js)
(add-hook 'js2-mode-hook 'prettier-js-mode)
Prolog
prolog-mode
comes with Emacs, but .pl files are assumed to be
Perl (which I never use, it scares me), so we need to change that.
(add-to-list 'auto-mode-alist '("\\.pl\\'" . prolog-mode))
Java
JDEE provides a whole bunch of tooling for Java. It requires the JDEE server to work properly, which has to be built seperately (this requires JDK and Maven):
cd ~/src
git clone https://github.com/jdee-emacs/jdee-server.git
cd jdee-server
mvn -Dmaven.test.skip=true package
With that built, we can install JDEE, pointing at the built server:
(use-package jdee
:config
(setq jdee-server-dir "~/src/jdee-server/target"))
Dockerfiles
Grab dockerfile-mode
for syntax highlighting etc in Dockerfiles:
(use-package dockerfile-mode)
Zig
zig-mode
provides basic language integration for Zig:
(use-package zig-mode)
There's a language server implementation for Zig so we'll be using
that via lsp-mode
alongside zig-mode
.
(add-hook 'zig-mode-hook #'lsp-deferred)
GLSL
Firstly, glsl-mode
provides basic support:
(use-package glsl-mode)
It's a C-like language, so I want bsd
code style and
smart-tabs
. The former is easy:
(add-hook 'glsl-mode-hook
(lambda ()
(c-set-style "bsd")
(setq c-basic-offset 4)))
Since smart-tabs
doesn't support GLSL out of the box, we need to
add support with smart-tabs-add-language-support. There's an
example of how to use it on Emacs Wiki:
(smart-tabs-add-language-support c++ c++-mode-hook
((c-indent-line . c-basic-offset)
(c-indent-region . c-basic-offset)))
c-indent-line et al will do fine for GLSL too since its syntax is very similar to C's, so adding support for it looks very similar to that example:
(smart-tabs-add-language-support glsl glsl-mode-hook
((c-indent-line . c-basic-offset)
(c-indent-region . c-basic-offset)))
Now that support is added, smart-tabs-insinuate should do its job:
(smart-tabs-insinuate 'glsl)
Tool Integrations
Git
magit
is truly a wonderful creation! Add 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))
By default, magit-status
will open itself in a different window
to the one you open it in. I really don't understand how this is
useful but thankfully this is Emacs so the behaviour can be
tweaked. The default behaviour does make sense for other magit
windows, just not magit-status.
The behviour I want can be achieved by setting
magit-display-buffer-function to something which will open the
buffer in the current window if and only if it's a
magit-status-mode
window.
(setq magit-display-buffer-function
(lambda (buffer)
(display-buffer
buffer
(when (eq (with-current-buffer buffer major-mode)
'magit-status-mode)
'(display-buffer-same-window)))))
Now I'm thinking it I could customise this further as I often am annoyed by diffs opening in a different window but I think I'll leave it at that for now as I'm not sure precisely what behaviour I'd want.
Docker
I use docker quite a lot, unfortunately, so it's nice to be able to
spawn containers etc from Emacs. The docker
package provides a
few nice bits and bobs.
(use-package docker
:bind ("C-c d" . docker))
Build systems
CMake
I hate it, but it's everywhere. cmake-mode
provides basic syntax
highlighting etc.
(use-package cmake-mode)
Meson
Use meson-mode
for syntax highlighting etc in meson.build files.
(use-package meson-mode)
Backup and Autosave
Keep $PWD Tidy
Emacs' default behaviour of dumping temporary files in the current directory is quite a pain, so we want to get it to instead stick them in a dedicated directory somewhere far away.
We can do that for auto-save files by setting the variable auto-save-file-name-transforms:
(make-directory "~/.emacs-tmp/auto-save" t)
(setq auto-save-file-name-transforms '((".*" "~/.emacs-tmp/auto-save" t)))
And for backup files there's backup-directory-alist.
(make-directory "~/.emacs-tmp/backup" t)
(setq backup-directory-alist '(("." . "~/.emacs-tmp/backup")))
Backup by Copying
By default Emacs moves a file to the backup location and then creates a copy in the original location, which apart from being a very strange thing to do also messes up hard links. Setting backup-by-copying changes it to the more obvious behaviour of simply copying the file to the backup location.
(setq backup-by-copying t)