.emacs.d/config.org

18 KiB
Raw Blame History

Emacs Configuration

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)

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)

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

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)

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 after init:

  (add-hook 'after-init-hook 'org-roam-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 DOING and DEFERRED as workflow states as well as the standard TODO and DONE, which can be added by setting org-todo-keywords:

  (setq org-todo-keywords
        '((sequence "TODO" "DOING" "|" "DONE" "DEFERRED")))

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

  (use-package magit
    :bind
    ("C-x g" . magit-status)
    :config
    (setq git-commit-summary-max-length 72))

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)

Finally, to enable Ido integration:

  (require 'lsp-ido)

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)

C++

Essentially the same story as for C

  (add-hook 'c++-mode-hook #'lsp-deferred)

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)

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.

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