.emacs.d/config.org

707 lines
20 KiB
Org Mode

#+TITLE: Emacs Configuration
#+AUTHOR: Camden Dixie O'Brien
#+ATTR_LATEX: :float t
* Customize
Hey, customize, leave my ~/.emacs.d/init.el alone!
#+begin_src emacs-lisp
(setq custom-file (concat user-emacs-directory "customize.el"))
(load custom-file t)
#+end_src
* Package Management
** MELPA
Let's be real here, all the good stuff's on MELPA.
#+begin_src emacs-lisp
(require 'package)
(add-to-list 'package-archives
'("melpa" . "https://melpa.org/packages/"))
#+end_src
** Initialization
We now need to run [[help:package-initialize][package-initialize]] to load and activate
packages. The documentation advises doing this early in
configuration.
#+begin_src emacs-lisp
(package-initialize)
#+end_src
We also need to fetch the package archives from ELPA and MELPA,
unless they have already been fetched:
#+begin_src emacs-lisp
(unless package-archive-contents
(package-refresh-contents))
#+end_src
** =use-package=
The rest of this config grabs packages via =use-package=, so that
needs to be installed:
#+begin_src emacs-lisp
(when (not (package-installed-p 'use-package))
(package-install 'use-package))
#+end_src
The wanted behaviour for =use-package= here is to ensure all used
packages are present.
#+begin_src emacs-lisp
(require 'use-package-ensure)
(setq use-package-always-ensure t)
#+end_src
* Emacs Server
Start an Emacs server if one is not running already:
#+begin_src emacs-lisp
(require 'server)
(unless (server-running-p)
(server-start))
#+end_src
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.
#+begin_src emacs-lisp
(setq inhibit-startup-screen t)
#+end_src
I like a little more line spacing than default.
#+begin_src emacs-lisp
(setq-default line-spacing 0.2)
#+end_src
Also, the menu-, tool- and scroll-bar are ugly, take up space and I
don't use them.
#+begin_src emacs-lisp
(menu-bar-mode -1)
(tool-bar-mode -1)
(scroll-bar-mode -1)
#+end_src
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:
#+begin_src emacs-lisp
(global-set-key (kbd "C-x O")
(lambda ()
(interactive)
(other-window -1)))
#+end_src
** Colour Scheme
Currently using =spacemacs-theme='s light variant, but I prefer a pure
white background to the off-white it has by default.
#+begin_src emacs-lisp
(use-package spacemacs-theme
:defer t)
(setq spacemacs-theme-custom-colors
'((bg1 . "#ffffff")
(comment-bg . "#ffffff")))
(load-theme 'spacemacs-light t)
#+end_src
* Autocompletion
Enable =company-mode= globally, and hook it into =completion-at-point-functions=.
#+begin_src emacs-lisp
(use-package company
:config
(add-hook 'after-init-hook 'global-company-mode)
(add-to-list 'company-backends 'company-capf))
#+end_src
And enable =ido-mode= everywhere, with flexible matching.
#+begin_src emacs-lisp
(use-package ido
:config
(setq ido-enable-flex-matching t)
(add-hook 'after-init-hook
(lambda ()
(ido-everywhere)
(ido-mode t))))
#+end_src
* 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.
#+begin_src emacs-lisp
(use-package org
:config
(require 'org-tempo))
#+end_src
A keybinding to add a new heading is super useful
#+begin_src emacs-lisp
(add-hook 'org-mode-hook
(lambda ()
(define-key org-mode-map
(kbd "<C-M-return>")
'org-insert-heading-after-current)))
#+end_src
Org is nice for scratch space
#+begin_src emacs-lisp
(setq initial-major-mode 'org-mode)
(setq initial-scratch-message "")
#+end_src
** Source Blocks
Pressing tab inside a source block should indent appropriately for its
language.
#+begin_src emacs-lisp
(setq org-src-tab-acts-natively t)
#+end_src
=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.
#+begin_src emacs-lisp
(org-babel-do-load-languages
'org-babel-load-languages
'((emacs-lisp . t)
(shell . t)))
#+end_src
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 [[help:org-confirm-babel-evaluate][org-confirm-babel-evaluate]] to
=nil=.
#+begin_src emacs-lisp
(setq org-confirm-babel-evaluate nil)
#+end_src
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 [[help:org-src-window-setup][org-src-window-setup]].
#+begin_src emacs-lisp
(setq org-src-window-setup 'split-window-below)
#+end_src
*** Asyncronous Execution
=ob-async= makes source blocks with the ~:async~ keyword execute
asyncronously, super handy for long-running snippets etc.
#+begin_src emacs-lisp
(use-package ob-async)
#+end_src
** Exporting
I very rarely want a table of contents, as most of my org documents
are pretty short.
#+begin_src emacs-lisp
(setq org-export-with-toc nil)
#+end_src
*** HTML
=htmlize= is needed for decent HTML exporting, but there is no need
for all that stuff at the bottom.
#+begin_src emacs-lisp
(use-package htmlize)
(setq org-html-postamble nil)
#+end_src
*** LaTeX
Use =minted= (LaTeX package) to do syntax highlighting in code blocks:
#+begin_src emacs-lisp
(add-to-list 'org-latex-packages-alist '("" "minted"))
(setq org-latex-listings 'minted)
#+end_src
=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.
#+begin_src emacs-lisp
(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"))
#+end_src
** Roam
A Zettelkasten in org mode? Yes please. It does need =sqlite3=
installed outside of Emacs land.
#+begin_src emacs-lisp
(use-package org-roam)
#+end_src
As stated in [[info:org-roam#Getting Started][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.
#+begin_src emacs-lisp
(make-directory "~/org/zet" t)
(setq org-roam-directory "~/org/zet")
#+end_src
And, also as recommended, we'll start [[help:org-roam-mode][org-roam-mode]] after init:
#+begin_src emacs-lisp
(add-hook 'after-init-hook 'org-roam-mode)
#+end_src
Hook it into Ido.
#+begin_src emacs-lisp
(setq org-roam-completion-system 'ido)
#+end_src
** Default Applications
It's all fun and games until =C-c C-e h o= opens the source code.
#+begin_src emacs-lisp
(setq org-file-apps
'(("html" . "firefox %s")
(auto-mode . emacs)))
#+end_src
** Workflow States
I like to have =DOING= and =DEFERRED= as workflow states as well as
the standard =TODO= and =DONE=, which can be added by setting
[[help:org-todo-keywords][org-todo-keywords]]:
#+begin_src emacs-lisp
(setq org-todo-keywords
'((sequence "TODO" "DOING" "|" "DONE" "DEFERRED")))
#+end_src
The ="|"= separates /needs further action/ states (before it) from
/no further action needed/ states (after it).
* Version Control
** Git
=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).
#+begin_src emacs-lisp
(use-package magit
:bind
("C-x g" . magit-status)
:config
(setq git-commit-summary-max-length 72))
#+end_src
* Language Integrations
** Generic
Generally, 8-character-wide tabs are not my thing.
#+begin_src emacs-lisp
(setq-default tab-width 4)
(setq-default basic-offset 4)
#+end_src
And generally indenting with spaces is more common, so make that
the default:
#+begin_src emacs-lisp
(setq-default indent-tabs-mode nil)
#+end_src
*** 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=:
#+begin_src emacs-lisp
(use-package lsp-mode
:init (setq lsp-keymap-prefix "C-c l")
:commands (lsp lsp-deferred))
#+end_src
=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.
#+begin_src emacs-lisp
(use-package lsp-ui :commands lsp-ui-mode)
#+end_src
Finally, to enable Ido integration:
#+begin_src emacs-lisp
(require 'lsp-ido)
#+end_src
*** 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.
#+begin_src emacs-lisp
(use-package smart-tabs-mode)
#+end_src
The package has this really annoying behaviour that it turns
itself off if [[help:indent-tabs-mode][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.
#+begin_src emacs-lisp
(add-hook 'c-mode-common-hook
(lambda () (setq indent-tabs-mode t)))
#+end_src
** 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.
#+begin_src emacs-lisp
(add-hook 'c-mode-hook #'lsp-deferred)
#+end_src
And we want to enable smart tabs:
#+begin_src emacs-lisp
(smart-tabs-insinuate 'c)
#+end_src
** C++
Essentially the same story as for C.
#+begin_src emacs-lisp
(add-hook 'c++-mode-hook #'lsp-deferred)
(smart-tabs-insinuate 'c++)
#+end_src
** Haskell
My workflow with Haskell is very REPL-based, so I always want
=interactive-haskell-mode= on.
#+begin_src emacs-lisp
(use-package haskell-mode)
(require 'haskell-interactive-mode)
(add-hook 'haskell-mode-hook 'interactive-haskell-mode)
#+end_src
And, of course, that REPL needs to be taking advantage of parallelism!
#+begin_src emacs-lisp
(require 'haskell-process)
(set-variable 'haskell-process-args-ghci
'("-threaded" "+RTS" "-N8" "-RTS"))
#+end_src
** Idris
The only thing to change from the defaults here is to add a more
convenient way to case-split.
#+begin_src emacs-lisp
(use-package idris-mode)
(add-hook 'idris-mode-hook
(lambda ()
(define-key idris-mode-map (kbd "C-c SPC")
'idris-case-split)))
#+end_src
** Rust
I never really use Rust without Cargo, so always turn on the minor
mode for Cargo in Rust buffers.
#+begin_src emacs-lisp
(use-package rust-mode)
(use-package cargo)
(add-hook 'rust-mode-hook 'cargo-minor-mode)
#+end_src
** Lisps
*** Common Lisp
Use SLIME and Quicklisp for Common Lisp (SBCL).
#+begin_src emacs-lisp
(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"))
#+end_src
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 [[https://beta.quicklisp.org/quicklisp.lisp][the installer]] and
loading it with SBCL.
#+begin_src shell :tangle no
curl -O https://beta.quicklisp.org/quicklisp.lisp
sbcl --load quicklisp.lisp
#+end_src
That will open a REPL with the =quicklisp-quickstart= system
loaded. At that REPL, run:
#+begin_src common-lisp :tangle no
(quicklisp-quickstart:install)
(ql:add-to-init-file)
(ql:quickload "quicklisp-slime-helper")
#+end_src
**** Source blocks
To enable execution of CL source blocks in Org mode, add an item
to [[help:org-babel-load-languages][org-babel-load-languages]].
#+begin_src emacs-lisp
(org-babel-do-load-languages
'org-babel-load-languages
'((lisp . t)))
#+end_src
*** 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.
#+begin_src emacs-lisp
(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))))
#+end_src
*** Scheme and Racket
[[https://www.nongnu.org/geiser/][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.
#+begin_src emacs-lisp
(use-package geiser
:config
(setq geiser-repl-use-other-window nil))
#+end_src
I commonly use Chez, Guile and Racket so we want the packages for those:
#+begin_src emacs-lisp
(use-package geiser-chez)
(use-package geiser-guile)
(use-package geiser-racket)
#+end_src
** YAML
I don't really like YAML if I'm honest, but it's used a lot so...
#+begin_src emacs-lisp
(use-package yaml-mode)
#+end_src
** Javascript
The first bit of this setup (=js2-mode=, =js2-refactor= and
=xref-js2=) is essentially copied from [[https://emacs.cafe/emacs/javascript/setup/2017/04/23/emacs-setup-javascript.html][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.
#+begin_src emacs-lisp
(use-package js2-mode)
(add-to-list 'auto-mode-alist '("\\.js\\'" . js2-mode))
#+end_src
*** Refactoring
=js2-refactor= provides refactoring tools based of said AST, so
enable that and its keybindings:
#+begin_src emacs-lisp
(use-package js2-refactor)
(add-hook 'js2-mode-hook #'js2-refactor-mode)
(js2r-add-keybindings-with-prefix "C-c C-r")
#+end_src
It provides a kill function with nice semantics for Javascript --
we definitely want that instead of the generic kill.
#+begin_src emacs-lisp
(define-key js2-mode-map (kbd "C-k") #'js2r-kill)
#+end_src
*** 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):
#+begin_src emacs-lisp
(use-package xref-js2)
#+end_src
=js-mode= binds =M-.=, which conflicts with =xref-js2= so we need to unbind that:
#+begin_src emacs-lisp
(define-key js-mode-map (kbd "M-.") nil)
#+end_src
And hook it up to =js2-mode=:
#+begin_src emacs-lisp
(add-hook 'js2-mode-hook (lambda ()
(add-hook 'xref-backend-functions #'xref-js2-xref-backend nil t)))
#+end_src
*** Using local tools from NPM
To use tools locally by NPM, there is =add-node-modules-path=:
#+begin_src emacs-lisp
(use-package add-node-modules-path)
(eval-after-load 'js2-mode
'(add-hook 'js2-mode-hook #'add-node-modules-path))
#+end_src
*** Autoformatting
[[https://prettier.io/][Prettier]] seems low-effort to set up :D
#+begin_src emacs-lisp
(use-package prettier-js)
(add-hook 'js2-mode-hook 'prettier-js-mode)
#+end_src
** 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.
#+begin_src emacs-lisp
(add-to-list 'auto-mode-alist '("\\.pl\\'" . prolog-mode))
#+end_src
** Java
[[https://github.com/jdee-emacs/jdee][JDEE]] provides a whole bunch of tooling for Java. It requires the
[[https://github.com/jdee-emacs/jdee-server][JDEE server]] to work properly, which has to be built seperately
(this requires JDK and Maven):
#+begin_src shell :tangle no
cd ~/src
git clone https://github.com/jdee-emacs/jdee-server.git
cd jdee-server
mvn -Dmaven.test.skip=true package
#+end_src
With that built, we can install JDEE, pointing at the built server:
#+begin_src emacs-lisp
(use-package jdee
:config
(setq jdee-server-dir "~/src/jdee-server/target"))
#+end_src
** Dockerfiles
Grab =dockerfile-mode= for syntax highlighting etc in Dockerfiles:
#+begin_src emacs-lisp
(use-package dockerfile-mode)
#+end_src
** Zig
=zig-mode= provides basic language integration for Zig:
#+begin_src emacs-lisp
(use-package zig-mode)
#+end_src
There's a language server implementation for Zig so we'll be using
that via =lsp-mode= alongside =zig-mode=.
#+begin_src emacs-lisp
(add-hook 'zig-mode-hook #'lsp-deferred)
#+end_src
* Tool Integrations
** 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.
#+begin_src emacs-lisp
(use-package docker
:bind ("C-c d" . docker))
#+end_src
** Build systems
*** CMake
I hate it, but it's everywhere. =cmake-mode= provides basic syntax
highlighting etc.
#+begin_src emacs-lisp
(use-package cmake-mode)
#+end_src
*** Meson
Use =meson-mode= for syntax highlighting etc in meson.build files.
#+begin_src emacs-lisp
(use-package meson-mode)
#+end_src
* 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
[[help:auto-save-file-name-transforms][auto-save-file-name-transforms]]:
#+begin_src emacs-lisp
(make-directory "~/.emacs-tmp/auto-save" t)
(setq auto-save-file-name-transforms '((".*" "~/.emacs-tmp/auto-save" t)))
#+end_src
And for backup files there's [[help:backup-directory-alist][backup-directory-alist]].
#+begin_src emacs-lisp
(make-directory "~/.emacs-tmp/backup" t)
(setq backup-directory-alist '(("." . "~/.emacs-tmp/backup")))
#+end_src
** 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
[[help:backup-by-copying][backup-by-copying]] changes it to the more obvious behaviour of
simply copying the file to the backup location.
#+begin_src emacs-lisp
(setq backup-by-copying t)
#+end_src