Skip to main content

Modules

Jotain’s Elisp configuration is split across lisp/init-*.el, one file per concern. Each file is self-contained: a package that only exists to enhance a built-in (e.g. dirvishdired, magitvc, corfucompletion-preview) lives in the same file as the built-in it enhances. There is no “builtins.el” / “third-party.el” split.

Conventions

  • Every module file is named init-<concern>.el under lisp/.
  • Every file starts with a lexical-binding: t cookie.
  • Every file ends with (provide 'init-<concern>).
  • Modules are loaded in order from init.el.
  • use-package-always-ensure is t (set in early-init.el), so every use-package block defaults to “install if missing”. Built-ins must opt out with :ensure nil. Packages provided by Nix use :ensure nil — Nix puts them on load-path, so use-package finds them without touching the network.
  • User options (defcustom variables) are set via setopt, not setq, so the :set callback and :type validation run.

Load order

init.el loads modules in this order:
init-core         GC, encoding, no-littering, sane file-handling defaults
init-keys         Global keymap and window-split helpers
init-ui           Theme, modeline, fonts, frame tweaks, icons, kkp, clipetty
init-help         helpful + built-in help tweaks
init-editing      elec-pair, delsel, whitespace, region tools
init-completion   vertico, marginalia, orderless, consult, corfu, cape
init-navigation   dired, dirvish, project, windmove, winner
init-vc           vc, magit, magit-todos, forge, diff-hl, smerge, ediff
init-prog         prog-mode, treesit, eglot, flymake, eldoc, apheleia, compile
init-project      project + projection + compile-multi
init-ai           claude-code-ide, gptel, mcp
init-shell        eshell, vterm, comint
init-systems      sops, logview, auth-source-1password
init-tracking     keyfreq, wakatime, activity-watch
init-writing      jinx, markdown-mode, denote, pdf-tools
init-org          org, org-modern, capture templates

init-lang-nix
init-lang-rust
init-lang-python
init-lang-web         TS/TSX/CSS/HTML/JSON
init-lang-devops      Dockerfile, terraform, just, ansible
init-lang-data        yaml, csv, sql, jinja2, gnuplot
init-lang-systems     Go, C/C++, CMake, Haskell

Core modules

init-core

GC, encoding, and the sane-defaults baseline. Restores gc-cons-threshold to 16 MiB after the early-init bump; pauses GC entirely while the minibuffer is open; forces UTF-8 everywhere; enables save-place-mode, recentf, savehist, repeat-mode, uniquify, ibuffer as the default buffer list, global-auto-revert-mode. Sets up no-littering to keep var/ and etc/ under the config directory. Also contains the macOS modifier-key fix (Cmd→Meta, Option→Super, right Option untouched for special characters).

init-keys

Global bindings only — per-package bindings live in the relevant use-package block. Unbinds C-z / C-x C-z (no accidental suspend on GUI), binds M-o to other-window, enables windmove-default-keybindings (Shift + arrows), and defines jotain-toggle-window-split on C-x j for rotating a two-window layout.

init-ui

Appearance. Uses modus-operandi-tinted and modus-vivendi-tinted (user-customisable via jotain-theme-light / jotain-theme-dark), with auto-dark-mode flipping between them based on the system appearance. Installs doom-modeline, picks the first available font from jotain-font-preferences (JetBrains Mono Nerd Font → Iosevka Nerd Font → DejaVu Sans Mono), enables display-line-numbers, pixel-scroll-precision-mode, hl-line-mode, show-paren-mode, built-in which-key, nerd-icons integrations for dired/ibuffer/corfu/marginalia, hl-todo, breadcrumb, pulsar, rainbow-delimiters, indent-bars. Also enables global-kkp-mode (Kitty Keyboard Protocol) and clipetty for OSC 52 clipboard support through SSH + tmux.

init-help

Built-in help tweaks (help-window-select) plus helpful for richer describe-* buffers. See Finding Information in Emacs for day-to-day usage.

init-editing

Baseline editing primitives: electric-pair-mode, delete-selection-mode, whitespace-mode, undo settings, region tools.

init-completion

The vertico stack on the minibuffer side, corfu + cape on the in-buffer side, plus the built-in minibuffer tweaks they rely on. All configured in one file because touching one nearly always means touching the others. C-s is deliberately left as isearch-forward; use M-s l for consult-line when you want the consult UI.

init-navigation

dired and dirvish in the same file, along with project, winner-mode, and directional navigation.

init-vc

vc pinned to Git only (other backends are a slow startup tax), magit with refined hunks and worktrees in magit-status, magit-todos, forge (configured with the built-in sqlite backend, emacsql-sqlite-builtin), diff-hl (including diff-hl-flydiff-mode for pre-save indicators), smerge-mode helpers, and ediff with sane window setup.

init-prog

The shared substrate for programming modes: prog-mode hooks, treesit setup, eglot, flymake, eldoc, apheleia for format-on-save, compile. Per-language eglot-ensure hooks live here so all LSP wiring is in one place; per-language mode settings live in init-lang-*.el.

init-project

Two complementary project command systems: projection (.dir-locals.el-backed commands exposed via C-x P, auto-detected from Makefiles/justfiles/Cargo.toml/etc) and compile-multi (per-major-mode named compile commands like “go test”, “pytest file”, “nix flake check”). They overlap but neither fully covers the other.

init-ai

AI assistants. claude-code-ide on C-c C-' for agentic multi-file edits via the Claude Code CLI. gptel on C-c RET (send) / C-c M-RET (menu) with three backends configured: Anthropic (default, claude-sonnet-4), Google Gemini, and a local Ollama instance (no key needed). mcp is loaded after gptel for Model Context Protocol tool use. API keys resolve from the environment (ANTHROPIC_API_KEY / GEMINI_API_KEY) first, then fall back to auth-sourceauth-source-1password (see init-systems) makes that transparent.

init-shell

eshell, vterm, and comint together — shells have their own input handling, history, prompts, and rendering, so grouping them lets the rules be coordinated in one place.

init-systems

Sysadmin tools: SOPS-encrypted file editing, logview for log files, and auth-source-1password as an auth-source backend so magit/forge, gptel, smtpmail, etc. all resolve credentials against the 1Password vault transparently.

init-tracking

Passive usage instrumentation. keyfreq counts command frequency (M-x keyfreq-show), wakatime-mode reports heartbeats to a Wakatime/Wakapi server (guarded by :if on wakatime-cli + WAKATIME_API_KEY, so it is a no-op if either is missing), and activity-watch-mode is installed but off by default — enable with M-x global-activity-watch-mode.

init-writing

Prose editing: jinx for spell-checking, markdown-mode, denote for notes, pdf-tools. Org is big enough to earn its own file.

init-org

org-mode, org-modern, capture templates, agenda, and friends. Binds C-c aorg-agenda, C-c corg-capture, C-c lorg-store-link.

Language modules

Each init-lang-*.el file pins :mode regexes and provides font-lock for a group of related languages. Formatter configuration is centralised through apheleia in init-prog.el; LSP server hooks live in init-prog.el too.
  • init-lang-nixnix-ts-mode. Formatting via apheleia (which ships a nixfmt entry).
  • init-lang-rustrust-ts-mode (built-in) plus Cargo command wrappers.
  • init-lang-pythonpython-ts-mode (built-in), the interpreter set to python3. The LSP server (pyright/basedpyright/ruff-lsp) comes from the project environment, not from this config.
  • init-lang-webtypescript-ts-mode, tsx-ts-mode, CSS, HTML, JSON, and related tree-sitter modes.
  • init-lang-devops — Dockerfile, docker-compose, Terraform (*.tf), gitlab-ci, justfile, Ansible.
  • init-lang-data — YAML, CSV (with csv-align-mode), SQL (sql-indent), Jinja2, gnuplot.
  • init-lang-systems — Go (go-mode), C/C++ (cc-mode), CMake, Haskell.

Adding a new module

  1. Create lisp/init-<concern>.el with the usual Elisp headers and a lexical-binding: t cookie.
  2. End the file with (provide 'init-<concern>).
  3. Add a (require 'init-<concern>) line in init.el at the appropriate point in the load order.
  4. Run just check to confirm the parser is happy, then just compile to catch warnings.
Last modified on April 8, 2026