# Claude Code setup Source: https://docs.jylhis.com/ai-tools/claude-code Configure Claude Code for your documentation workflow Set up Claude Code to help you write and maintain your Mintlify documentation. ## Prerequisites * Active Claude subscription (Pro, Max, or API access) ## Setup ```bash theme={} npm install -g @anthropic-ai/claude-code ``` In your docs directory, run: ```bash theme={} npx skills add https://mintlify.com/docs ``` This gives Claude Code Mintlify's component reference, writing standards, and workflow guidance. Edit `AGENTS.md` in your project root to add project-specific terminology, style preferences, and content boundaries. ```bash theme={} claude ``` # Cursor setup Source: https://docs.jylhis.com/ai-tools/cursor Configure Cursor for your documentation workflow Set up Cursor to help you write and maintain your Mintlify documentation. ## Prerequisites * Cursor editor installed ## Setup Open the root of your documentation repository where `docs.json` is located. In the integrated terminal, run: ```bash theme={} npx skills add https://mintlify.com/docs ``` This gives Cursor Mintlify's component reference, writing standards, and workflow guidance. Edit `AGENTS.md` in your project root to add project-specific terminology, style preferences, and content boundaries. Open a file and use Cursor's AI features to draft and edit documentation. # Windsurf setup Source: https://docs.jylhis.com/ai-tools/windsurf Configure Windsurf for your documentation workflow Set up Windsurf's Cascade AI assistant to help you write and maintain your Mintlify documentation. ## Prerequisites * Windsurf editor installed ## Setup Open the root of your documentation repository where `docs.json` is located. In the integrated terminal, run: ```bash theme={} npx skills add https://mintlify.com/docs ``` This gives Windsurf Mintlify's component reference, writing standards, and workflow guidance. Edit `AGENTS.md` in your project root to add project-specific terminology, style preferences, and content boundaries. Open a file and use Cascade to draft and edit documentation. # Development Source: https://docs.jylhis.com/development Run the docs site locally Requires Node.js 19+ and the Mintlify CLI. ## Install the CLI ```bash theme={} npm i -g mint ``` ## Preview locally From the repository root (where `docs.json` lives): ```bash theme={} mint dev ``` The site is served at `http://localhost:3000` with hot reload. To pick a different port: ```bash theme={} mint dev --port 3333 ``` ## Pull in project docs The Guides/Knowledge tabs are committed to this repo; project tabs (marchyo, jotain, jstack) are pulled at build time. To preview them locally, enter the devenv shell and run: ```bash theme={} aggregate-docs ``` This clones each project repo, copies its `docs/` directory into this tree, and merges its navigation into `docs.json`. The script is defined in `devenv.nix`. ## Validate links ```bash theme={} mint broken-links ``` ## Deployment CI builds the `docs` branch from `main` daily and on push. Mintlify's GitHub app deploys from the `docs` branch automatically. See `.github/workflows/multirepo-docs.yml`. ## Updating the CLI ```bash theme={} npm i -g mint ``` If a preview misbehaves after an update, clear the cache: ```bash theme={} rm -rf ~/.mintlify ``` # Jylhis Docs Source: https://docs.jylhis.com/index Documentation for marchyo, jotain, and a knowledge base of useful references Personal documentation hub for projects and tools I maintain. ## Projects A modular NixOS configuration flake with sensible defaults. Feature flags, theming, hardware support. A reproducible Emacs configuration managed with Nix Flakes. LSP, modern completion, and a home-manager module. ## Knowledge base A curated reference of modern CLI alternatives to classic Unix tools. ## About this site Built on Mintlify. See the Guides tab for MDX, navigation, and customization docs. # Modules Source: https://docs.jylhis.com/jotain/architecture/modules Elisp modules that make up the Jotain configuration # 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. `dirvish` → `dired`, `magit` → `vc`) 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-.el` under `lisp/`. * Every file starts with a `lexical-binding: t` cookie. * Every file ends with `(provide 'init-)`. * 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, var/ paths, sane file-handling defaults init-keys Global keymap and window-split helpers init-ui Theme, modeline, fonts, frame tweaks, icons init-tabs Workspace tabs via tab-bar-mode init-help helpful + built-in help tweaks init-docs Register the bundled jotain.info with C-h i init-editing elec-pair, delsel, whitespace, region tools, keyfreq init-completion vertico, marginalia, orderless, consult, corfu, cape init-navigation dired, dirvish, project, windmove, winner init-vc vc, vc-jj, magit, majutsu, magit-todos, forge, diff-hl, ediff init-prog prog-mode, treesit, eglot, dape, flymake, eldoc, apheleia, compile init-snippets tempel snippets + eglot-tempel LSP expansion init-project project + projection + compile-multi init-devenv devenv.sh: tasks, processes, env, LSP, MCP init-ai eca, claude-code-ide, gptel, mcp init-shell eshell, comint, ielm init-terminal ghostel terminal + tty integration (kkp, clipetty) init-systems sops, logview, auth-source-1password 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-go init-lang-web TS/TSX/CSS/HTML/JSON init-lang-devops Dockerfile, terraform, just, ansible, bazel init-lang-data yaml, csv, sql, jinja2, gnuplot init-lang-systems C/C++/CUDA, CMake, Meson, Haskell, OCaml, Zig ``` ## 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`. Defines `jotain-var-dir` and the `jotain-var-file` helper, and themes the built-in state-file variables (`recentf-save-file`, `savehist-file`, `save-place-file`, `bookmark-default-file`, `auto-save-list-file-prefix`) to live under `var/`. Other modules use the same helper to keep their own state (keyfreq, logview, forge, transient) in the same place. Also contains the macOS modifier-key fix (`Cmd`→Meta, `Option`→Super, right Option untouched for special characters). Sets `read-extended-command-predicate` to filter `M-x` candidates by major mode; untagged commands are still shown, and typing a full command name always runs it regardless of the filter. ### `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), defines `jotain-toggle-window-split` on `C-x j` for rotating a two-window layout, and (on Emacs 31+) ships a `repeat-map` for the `window-layout-*` frame transforms under `C-x W`. Also registers `which-key-add-key-based-replacements` for the cross-cutting `C-c` / `C-x` prefixes (`C-c n` org-roam, `C-c r` eglot-refactor, `C-c o` combobulate, `C-x P` project, etc.) so the prefix menus surface short noun-phrase labels instead of bare command names. See [Keybindings](/keybindings) for the full namespace map and [Ergonomics](/ergonomics) for the repeat-maps rationale. ### `init-ui` Appearance. Uses the Jylhis design system themes `jylhis-survey-light` (light) and `jylhis-survey-dark` (dark), user-customisable via `jotain-theme-light` / `jotain-theme-dark`, with `auto-dark-mode` flipping between them based on the system appearance. The themes come from the pin in `nix/design-pin.nix`; if that package is unavailable, or a theme it names cannot be loaded, the module degrades to the built-in `modus-operandi` / `modus-vivendi` rather than failing the rest of startup. Installs `doom-modeline`, picks the first available font from `jotain-font-preferences` (BlexMono Nerd Font → 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`. ### `init-help` Built-in help tweaks (`help-window-select`) plus `helpful` for richer `describe-*` buffers. See [Finding Information in Emacs](/finding-information-in-emacs) for day-to-day usage. ### `init-docs` Makes the bundled Jotain Info manual (`jotain.info`, generated by `nix build .#info`) discoverable from `C-h i`. For NixOS / nix-darwin / Home Manager users this is already wired up at the Nix layer: the Emacs wrapper pre-sets `INFOPATH`, so no Elisp is needed — this module is the source-checkout fallback. After `just info`, `result-info/share/info` is added to `Info-directory-list`; `C-h i d m Jotain RET` opens the manual. `JOTAIN_INFO_DIR` overrides the search. ### `init-editing` Baseline editing primitives: `electric-pair-mode`, `delete-selection-mode`, `whitespace-mode`, undo settings, region tools. Also `keyfreq`, which counts command frequency to disk (`M-x keyfreq-show`) — tiny, no daemon, no network, enabled at `after-init`. ### `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 + Jujutsu (other backends are a slow startup tax), `vc-jj` for jj support through built-in `vc`/`project.el` (jj repos are typically colocated with git), `magit` with refined hunks and worktrees in `magit-status`, `majutsu` as a magit-style jj porcelain (`C-c j` status/log, `C-c M-j` dispatch), `magit-todos`, `forge` (configured with the built-in sqlite backend, `emacsql-sqlite-builtin`), `diff-hl` (including `diff-hl-flydiff-mode` for pre-save indicators), and `ediff` with sane window setup (`smerge-mode` needs no config — it self-activates on conflicts with its stock `C-c ^` bindings). The `N/M` modeline counter is backend-aware: a `.jj` directory selects jj probes (`jj diff --git`, an `author_date` revset), otherwise git. For correct jj diff/conflict rendering set `ui.diff-formatter = ":git"` and `ui.conflict-marker-style = "git"` via `jj config edit --user`. ### `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, and beyond that curated list `eglot` auto-starts for any project file whose language server is on the buffer's (envrc-applied) `PATH` — so enabling a language in a project's devenv lights up its LSP with no per-language config. Per-language mode settings live in `init-lang-*.el`. When the `rass` binary (rassumfrassum, shipped in the devenv shell) is on `PATH`, `tsx-ts-mode` / `typescript-ts-mode` / `typescript-mode` buffers are routed through it to run `typescript-language-server` alongside whichever of `eslint-lsp` and `tailwindcss-language-server` are also on `PATH`, and Python buffers run the bundled `rass python` preset (basedpyright + ruff) when both binaries are present; any missing prerequisite makes eglot fall back to its built-in single-server lookup, so projects using `pylsp` / `pyright` / unguarded tsserver keep working unchanged. Also provides `jotain-sonarlint` for on-demand SonarLint analysis via the `sonarlint-ls` language server — SonarCloud connected mode is configured per-project via `eglot-workspace-configuration` in `.dir-locals.el`. Debugging goes through `dape` (Debug Adapter Protocol, `C-x C-a` prefix); the adapter binaries (e.g. `dlv` for Go, `debugpy` for Python) come from the project/host PATH, same as the LSP servers. ### `init-snippets` Template/snippet expansion via `tempel`, chosen over the built-in `skeleton`/`abbrev` and the heavier `yasnippet` because it shares an author with the corfu/cape/vertico stack and plugs straight into `completion-at-point-functions` — snippet names surface in the corfu popup (added buffer-locally ahead of the cape capfs on `prog-mode`/`text-mode`), and `M-+` (`tempel-complete`) / `M-*` (`tempel-insert`) expand them, with `TAB` / `S-TAB` moving between fields. The curated templates themselves live in `templates/jotain.eld` (keyed by major mode — basic for/while/if/function constructs for Emacs Lisp, Python, Rust, Go, C/C++, and TS/TSX/JS), so adding a snippet never touches Elisp. `eglot-tempel` is configured here too, so Tempel also expands the snippets language servers return (e.g. function-argument placeholders); it is armed the moment eglot loads, before the first connection. ### `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-devenv` Wiring for the in-repo [devenv.sh](https://devenv.sh) integration library `lisp/devenv.el` — the one file under `lisp/` that is *not* an `init-*` module: it is a standalone, reusable package (own `devenv-` namespace, no `jotain-` dependencies) that this module merely binds into the configuration. `C-c v` opens a transient with task and script runners (completing-read over `devenv tasks list --json` / `devenv eval scripts`), `devenv test`/`build`/`update`/`gc` through compilation-mode with Nix-trace error matching, a tabulated process dashboard over the devenv process manager (`devenv up`/`down`, per-process start/stop/restart/logs), environment introspection (`devenv eval`/`info`/`search`), and `devenv-reload` (invalidates caches, re-fetches the environment, offers `eglot-reconnect`). `devenv.nix` buffers are routed to the bundled `devenv lsp` server while `nil` keeps serving other Nix files. `devenv-mcp-setup` registers the project's `devenv mcp` stdio server with mcp.el for gptel tool use. Environment loading is the library's native loader (`devenv-env-global-mode`), since direnv/envrc is disabled (see `init-prog`): for a trusted project it sources `devenv print-dev-env` in bash so the shellHook runs, layers `PATH` over the login one, and drops the derivation-only variables the JSON form carries. It defers to envrc where `devenv-env-defer-to-direnv` is left at `t`. See [devenv integration](/usage/devenv) for the user guide. ### `init-ai` AI assistants. `claude-code-ide` on `C-c q` for agentic multi-file edits via the Claude Code CLI. `eca` on `C-c e` is the Editor Code Assistant client (chat, inline completion, rewrite, MCP) talking to a Nix-provided server. `gptel` on `C-c s` (send) / `C-c S` (menu) with four backends configured: OpenRouter (default, OpenAI-compatible aggregator, `anthropic/claude-sonnet-4.6`), direct Anthropic, 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 (`OPENROUTER_API_KEY` / `ANTHROPIC_API_KEY` / `GEMINI_API_KEY`) first, then fall back to `auth-source` — `auth-source-1password` (see `init-systems`) makes that transparent. The eca server's config is generated by the Home Manager module's `services.jotain.eca` submodule (`eca.openrouter.enable` for the default OpenRouter provider from `config/eca/config.json`, `eca.settings` for a freeform merge); the legacy `services.jotain.openrouter.enable` is a renamed alias. API keys for all of these — and any other environment-reading integration — come from the single daemon-wide `services.jotain.environmentFile`, or through Emacs's `auth-source`: `services.jotain.authSources` wires runtime authinfo files (sops/agenix) into `auth-sources`, and `services.jotain.onePassword.enable` puts the `op` CLI on `PATH` for the 1Password backend. Because the eca server reads only its own environment, `jotain-ai-export-api-keys` (see `init-ai`) resolves any missing provider key from auth-source and exports it before each eca session. `services.jotain.claudeCode.enable` supplies the Claude Code CLI for `claude-code-ide`. ### `init-shell` `eshell`, `comint`, and `ielm` together — shells and REPLs have their own input handling, history, prompts, and rendering, so grouping them lets the rules be coordinated in one place. The true PTY terminal emulator lives in `init-terminal`. ### `init-terminal` Both directions of terminal support. Inside Emacs: `ghostel`, a terminal emulator powered by libghostty-vt (the VT engine behind Ghostty) through a native dynamic module — real PTY for tmux/ncurses programs, plus automatic shell integration (OSC 7 directory tracking, OSC 133 prompt navigation) and OSC 52 clipboard. The Nix package builds the module from source and ships it in the package directory; in a source checkout the module is downloaded into the writable `elpa/` dir on first `M-x ghostel`. Emacs inside a terminal: `global-kkp-mode` (Kitty Keyboard Protocol), `clipetty` (OSC 52 clipboard through SSH + tmux), `xterm-mouse-mode`, and `tty-tip-mode` on Emacs 31+ — all no-ops in GUI frames. Ghostel's buffers advertise `TERM=xterm-ghostty`, the same dialect the outer Ghostty terminal uses; the `xterm-ghostty → xterm-256color` alias that supports both lives in `early-init.el` because terminal initialisation runs before `init.el`. ### `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-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 a` → `org-agenda`, `C-c c` → `org-capture`, `C-c l` → `org-store-link`. Also holds the Org Babel layer that makes an `.org` file usable as a notebook: `jotain-org-babel-languages` (a curated set of languages, all backed by `ob-*` libraries Org itself ships), a trust-aware `org-confirm-babel-evaluate` predicate that skips the prompt for files under `org-directory` or inside the current project, inline-image refresh after every run, source-block editing that preserves indentation, and a `C-c b` notebook prefix (`b` run buffer, `e` run subtree, `r` restart session and run buffer, `s` switch to session, `k` clear results, `t` tangle). See [Notebooks with Org Babel](/usage/notebooks). ## 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-nix`** — `nix-ts-mode`. Formatting via apheleia (which ships a `nixfmt` entry). * **`init-lang-rust`** — `rust-ts-mode` (built-in) plus Cargo command wrappers. * **`init-lang-python`** — `python-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-go`** — `go-ts-mode` / `go-mod-ts-mode` / `go-work-ts-mode` (built-in), with a buffer-local gopls workspace config that turns on inlay hints, staticcheck, and the `unusedparams`/`shadow`/`nilness`/`unusedwrite` analyses (eglot wired in `init-prog`). A `major-mode-remap-alist` entry routes the classic `go-mode` to `go-ts-mode`, so a stale autoload or missing grammar can never drop a `.go` file into an unavailable classic mode. Adds `go-tag` (struct-tag editing) and `gotest` (point-aware test/benchmark/coverage runs) under the buffer-local `C-c C-t` prefix. Formatting via apheleia → `goimports`; debugging via dape → `dlv`. All Go tooling (`go`, `gopls`, `goimports`, `dlv`, `gomodifytags`) comes from the project/host PATH. * **`init-lang-web`** — `typescript-ts-mode`, `tsx-ts-mode`, CSS, JSON, and related tree-sitter modes. Plain `.html`/`.htm` uses the built-in `mhtml-ts-mode` (tree-sitter HTML + JS + CSS); `web-mode` is kept for the templating dialects it uniquely handles (`.php`, `.erb`, `.jsp`, `.tpl`, mustache, Django). * **`init-lang-devops`** — Dockerfile, docker-compose, Terraform (`*.tf`), gitlab-ci, justfile, Ansible, Bazel/Starlark (`BUILD`, `WORKSPACE`, `MODULE.bazel`, `*.bzl`, `.bazelrc`, …). A `jotain-docker-backend` defcustom (`'podman` by default, set to `'docker` for classic Docker) switches the runtime command used by `dockerfile-mode`. Bazel buffers format on save via buildifier (centralised through apheleia in `init-prog`). * **`init-lang-data`** — YAML, CSV (with `csv-align-mode`), SQL (`sql-indent`), Jinja2, gnuplot. * **`init-lang-systems`** — C/C++ (`c-ts-mode`/`c++-ts-mode`, routed via `jotain-prog-ts-remaps`; eglot wires `clangd`, apheleia wires `clang-format`, dape uses `codelldb`), CUDA (`.cu`/`.cuh` → `cuda-ts-mode`, defined here on the `cuda` tree-sitter grammar — a C++ superset; via `derived-mode-add-parents` it counts as `c++-ts-mode`, so the full C++ tool stack applies), CMake, Meson, Haskell (`haskell-ts-mode`, derived from `haskell-mode` so eglot's HLS entry matches), OCaml (`neocaml` — `neocaml-mode`/`neocaml-interface-mode` on the `ocaml`/`ocaml-interface` grammars; `tuareg` kept for `.mll`/`.mly`; eglot wires `ocamllsp`, apheleia wires `ocamlformat`), Zig (`zig-ts-mode`, eglot wires `zls`). `cc-mode` is retained for its `auto-mode-alist` decisions and as the grammarless fallback. ## Adding a new module 1. Create `lisp/init-.el` with the usual Elisp headers and a `lexical-binding: t` cookie. 2. End the file with `(provide 'init-)`. 3. Add a `(require 'init-)` line in `init.el` at the appropriate point in the load order. 4. Run `just check` — the `elisp-lint` flake check confirms the parser is happy and `elisp-compile` byte-compiles with warnings as errors. # Nix Build System Source: https://docs.jylhis.com/jotain/architecture/nix-build How Jotain builds Emacs from source using Nix # Nix Build System Jotain uses Nix for reproducible Emacs builds with fine-grained control over compile options. The source of nixpkgs is the revision pinned in `flake.lock` by default; both `default.nix` and `emacs.nix` read it directly via `fetchTarball`, so non-flake `nix-build` consumers get the same pin. Pass `--arg pkgs ''` or override `pkgs` to use a different one. ## Two-Layer Architecture ### emacs.nix The core build expression. It selects a base package per variant — emacs-overlay's `emacs-git`/`emacs-unstable`/`emacs-igc` for the git-based variants (`unstable` is the default), nixpkgs' default `emacs` attribute for `mainline` (the cache-parity canary) — and calls `.override { ... }` with the supported build flags exposed as file-level arguments. **The build matrix is four shipped builds** on × : pgtk/Wayland GUI or terminal-only on Linux, patched NS GUI or terminal-only on Darwin; `emacs.nix` asserts every other GUI configuration away. pgtk honors each backend's advertised scale (Wayland fractional scale) so a fixed point size tracks the system. When `withPgtk = true` (the Linux GUI default) the base selection picks the prebuilt `*-pgtk` sibling (`emacs-unstable-pgtk` etc.) directly instead of overriding `withPgtk` on the non-pgtk base, because the two build identical content under different derivation names and the sibling is the one the cache holds. The [`nix-community/emacs-overlay`](https://github.com/nix-community/emacs-overlay) that supplies the git-based variants is a flake input composed into every overlay consumer in `flake.nix`; `emacs.nix` and `overlay.nix` re-apply it from `flake.lock` when imported standalone. Supported: * **Source variants**: `unstable` (`emacs-unstable`, the Emacs 31 release branch — currently the 31.1-rc1 release candidate; the default), `git` (emacs-overlay's `emacs-git`, current master), `igc` (`emacs-igc`, the feature/igc3 Memory Pool System incremental GC branch), `mainline` (nixpkgs' default `emacs` attribute; parity canary, not a flake output). * **GUIs**: pgtk (pure GTK / Wayland; the only Linux GUI), NS (Cocoa/NeXTstep; the only macOS GUI, patched by default), or no GUI at all (`noGui = true`). * **Compilation**: native compilation (libgccjit AOT, default when the build platform can execute the host), compressed install, C sources for `find-function-C-source`, `srcRepo` (run autoreconf on git-based sources), opt-in `useCcache`. * **Image formats**: WebP (default), optionally ImageMagick. * **Libraries**: tree-sitter, SQLite3, dbus, selinux, gpm, ALSA, ACL, mailutils, systemd, GLib networking. The overlay trims mailutils/gpm/selinux from the builds that are already off binary-cache parity (both terminal-only builds and the Darwin GUI); the Linux pgtk build keeps upstream defaults so it stays byte-identical to the cached `emacs-unstable-pgtk`. * **Darwin patches**: `system-appearance` and `round-undecorated-frame` (plus `adjust-ns-init-colors` on master/32+) fetched from `nix-giant/nix-darwin-emacs`, applied via `overrideAttrs` — **on by default for the NS GUI**, which makes every Darwin GUI build a from-source build. ### Cache-parity invariant `emacs.nix` is written so that every argument default matches the corresponding default in upstream nixpkgs' `make-emacs.nix` (and the explicit args emacs-overlay passes to its prebuilt attrs). As long as that holds, ```nix theme={} import ./emacs.nix {} # mainline (bare-file default) import ./emacs.nix { variant = "unstable"; } # the distribution default import ./emacs.nix { noGui = true; } # any standard override ``` produces the *exact* store path of the matching base attr — `pkgs.emacs(.override { ... })` for mainline, `pkgs.emacs-git`/`emacs-unstable`/`emacs-igc` for the overlay variants — so the `nix-community.cachix.org` (git/unstable/igc, including the shipped `unstable` default), Hydra (mainline), and project `jylhis` binary caches hit and nothing recompiles from source. Only custom `rev` pins and the Darwin patch flags are expected to diverge — those paths run through `overrideAttrs` and intentionally bust the cache. Verify after any change to defaults: ```bash theme={} nix-instantiate --eval --strict -E ' let lock = builtins.fromJSON (builtins.readFile ./flake.lock); n = lock.nodes.${lock.nodes.root.inputs.nixpkgs}.locked; ov = lock.nodes.${lock.nodes.root.inputs.emacs-overlay}.locked; nixpkgs = fetchTarball { url = "https://github.com/${n.owner}/${n.repo}/archive/${n.rev}.tar.gz"; sha256 = n.narHash; }; overlay = fetchTarball { url = "https://github.com/${ov.owner}/${ov.repo}/archive/${ov.rev}.tar.gz"; sha256 = ov.narHash; }; pkgs = import nixpkgs { overlays = [ (import overlay) (import ./overlay.nix) ]; }; in { # jotainEmacs is pgtk on Linux (mk-overlay passes withPgtk = isLinux), # so on this x86_64-linux eval it maps to the emacs-unstable-pgtk sibling. default = pkgs.jotainEmacs.outPath == pkgs.emacs-unstable-pgtk.outPath; mainline = (import ./emacs.nix {}).outPath == pkgs.emacs.outPath; git = (import ./emacs.nix { variant = "git"; }).outPath == pkgs.emacs-git.outPath; unstable = (import ./emacs.nix { variant = "unstable"; }).outPath == pkgs.emacs-unstable.outPath; unstable-pgtk = (import ./emacs.nix { variant = "unstable"; withPgtk = true; }).outPath == pkgs.emacs-unstable-pgtk.outPath; igc = (import ./emacs.nix { variant = "igc"; }).outPath == pkgs.emacs-igc.outPath; }' ``` The override arg set is filtered through `lib.intersectAttrs (lib.functionArgs basePackage.override)`, so only the flags the base `make-emacs.nix` actually defines are forwarded. This keeps the build evaluating when a downstream flake overrides `nixpkgs` with an older release (24.05+): arguments that newer `make-emacs.nix` versions added are dropped rather than throwing "called with unexpected argument". On the pinned unstable every argument is accepted, so the intersection is a no-op and cache parity is unaffected. ### default.nix The non-flake entry point — a thin flake-compat wrapper, not a distribution layer. It reads the pinned `flake-compat` rev from `flake.lock`, evaluates `flake.nix`, and promotes the current system's packages to the top level: plain `nix-build` builds the full distribution (`jotainEmacsPackages`), `nix-build -A emacs` the bare Emacs. Variant builds still target `emacs.nix` directly (e.g. `nix-build emacs.nix --arg withPgtk true`). ### Tree-sitter grammars Grammar bundling lives in `nix/mk-overlay.nix`, not in `default.nix`: the distribution's `withPackages` set includes `epkgs.treesit-grammars.with-all-grammars` (\~275 grammars — a linkFarm over per-grammar derivations, so the full set costs closure size, never build time). Discovery is nixpkgs' own `site-start.el`, which sets `treesit-extra-load-path` to the bundled grammar directory — `early-init.el` deliberately does **no** `TREE_SITTER_DIR` handling (see its comment), and `init-prog.el` propagates `treesit-extra-load-path` to async native-comp workers, which run without site files. ## Key Build Options | Option | Default | Description | | -------------------------------- | -------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `variant` | `"unstable"` | Emacs source variant — `unstable` / `git` / `igc` / `mainline` | | `noGui` | `false` | Terminal only — `--without-x --without-ns` | | `withPgtk` | `isLinux && !noGui` | Pure GTK (Wayland) — `--with-pgtk`; the Linux GUI. Selects the prebuilt `*-pgtk` sibling for cache parity | | `withNS` | `isDarwin && !noGui` | Cocoa/NeXTstep — the macOS GUI | | `withNativeCompilation` | auto | libgccjit AOT compilation | | `withTreeSitter` | `true` | Built-in tree-sitter support | | `withSystemd` | Linux | `--with-systemd` (journal support) | | `withSystemAppearancePatch` | `withNS` | (Darwin) add `ns-system-appearance` hooks — **on by default for the GUI** | | `withRoundUndecoratedFramePatch` | `withNS` | (Darwin) rounded borderless frames — **on by default for the GUI** | | `withAdjustNsInitColorsPatch` | `withNS` | (Darwin) full color palette at init; only exists on the master/32+ patch branch, silently skipped elsewhere | | `rev` / `hash` | `null` | Pin a specific commit for `git` / `unstable` / `igc` variants | | `useCcache` | `false` | Build with `pkgs.ccacheStdenv` (`CCACHE_DIR` wired via `extraConfig`; `ccacheDir` argument, default `/var/cache/ccache`). Only useful for builds already off the cache-parity path (custom `rev`, the Darwin GUI, or `igc` on Darwin); needs a machine-level `extra-sandbox-paths` exception first | See `emacs.nix` for the complete argument list and defaults. ## IGC Variant The `igc` variant builds Emacs's `feature/igc3` branch, which replaces the default mark-and-sweep garbage collector with the [Memory Pool System](https://memory-pool-system.readthedocs.io). The base package is emacs-overlay's `emacs-igc`, which already carries `--with-mps=yes` and the `mps` build input, so no manual steps are needed on Linux — the overlay-pinned revision is a binary-cache hit there. On Darwin, `nix-community.cachix.org` has no prebuilt `emacs-igc`, so `just build-igc` compiles it from source even at the default revision; `just build-igc-ccache` (`useCcache = true`) makes repeat local rebuilds on that platform cheaper once ccache is set up (see the `useCcache` doc comment in `emacs.nix`). ## Git Variants The `git`, `unstable`, and `igc` variants build the revision pinned inside emacs-overlay (updated daily upstream, advanced here by `just update`) and are binary-cache hits from `nix-community.cachix.org`. To pin a different commit, pass `--argstr rev "..."` — `emacs.nix` then fetches it from `https://git.savannah.gnu.org/git/emacs.git` via `fetchgit`; the first build reports the correct hash to pass back via `--argstr hash "sha256-..."`. In that case `postPatch` substitutes the pinned revision into `lisp/loadup.el` so `emacs-repository-get-version` returns the expected value without a `.git` directory in the build tree. On `aarch64-linux`, the overlay's bases include `--enable-check-lisp-object-type` to avoid segfaults. ## nix-on-droid `nixOnDroidModules.default` (from `module-nix-on-droid.nix`) installs Jotain on Android via [nix-on-droid](https://github.com/nix-community/nix-on-droid). Because Android runs headless under proot, the module is a trimmed cousin of `module-system.nix`: it `pkgs.extend`s the overlay and adds a **terminal-only** build (`jotainEmacsPackagesNoGui`, a `noGui = true` Emacs) plus an `emacsclient` `EDITOR`/`VISUAL` wrapper to `environment.packages` and `environment.sessionVariables`. There is no systemd service, launchd agent, `fonts.packages`, or GUI frame. `flake.nix` exposes an example `nixOnDroidConfigurations.default` (aarch64-linux). It activates only on-device or under aarch64 emulation, so `nix flake check` does not realise it (CI is x86\_64); the module itself is eval-checked on x86\_64 via `nix-on-droid-module-eval` in `nix/checks.nix`. ## Overriding nixpkgs A downstream flake can follow a different nixpkgs (release branches **24.05+** through unstable): ```nix theme={} inputs.jotain.inputs.nixpkgs.follows = "nixpkgs"; ``` The version-gated override split (see [Cache-parity invariant](#cache-parity-invariant)) keeps the modules evaluating and building on older releases. The caveat is Emacs version: 24.05's `pkgs.emacs` is Emacs 29 while Jotain's Elisp targets 30/31, so the build succeeds but runtime behaviour is only guaranteed on the pinned unstable. # Overview Source: https://docs.jylhis.com/jotain/architecture/overview How Jotain is structured # Architecture Overview Jotain is organised into distinct layers, each with a clear responsibility. ## File Structure ``` jotain/ ├── early-init.el # Pre-package/GUI initialisation ├── init.el # Entry point — loads modules in order ├── lisp/ # Modular Elisp config (init-*.el, one per concern) ├── emacs.nix # GNU Emacs build from source (cache-parity invariant) ├── default.nix # Non-flake entry point (flake-compat wrapper) ├── overlay.nix # Standalone nixpkgs overlay (wraps nix/mk-overlay.nix) ├── module.nix # Home Manager module for the daemon ├── module-system.nix # Shared NixOS / nix-darwin module ├── module-nix-on-droid.nix # nix-on-droid module (terminal-only build) ├── nix/ │ ├── mk-overlay.nix # Overlay implementation — distribution assembly │ ├── checks.nix # Flake checks (lint, compile, tests, builds) │ ├── use-package.nix # use-package scanner (Elisp → epkgs mapping) │ └── ... # docs, site, extra packages, treefmt helpers ├── devenv.nix # Development shell (tooling only — no Emacs) ├── devenv.yaml # devenv inputs (pinned to flake.lock revs) ├── flake.nix # Flake entry point — source of truth for pins ├── Justfile # Task runner recipes ├── docs/ # Documentation sources (rendered into the site, │ # the Info manual, and the man page; docs.json │ # defines page order for all three) ├── website/ # page.jylhis.com/jotain site shell (assembled by nix build .#site) ├── test/ # ERT tests (every *.el here is loaded by elisp-test) ├── bench/ # Startup-benchmark wrapper init files ├── templates/ # Tempel snippet templates (jotain.eld) ├── config/ # Auxiliary tool config (e.g. eca) ├── scripts/ # Bootstrap helpers (e.g. bootstrap-agent-env.sh) └── journal/ # Development journal entries ``` ## Layers ### Emacs Lisp Layer The Elisp configuration is split into three parts: 1. **`early-init.el`** — loaded before `package.el`, before the first frame, before `init.el`. Handles anything that *must* happen that early: the startup GC threshold, `use-package-always-ensure`, frame chrome defaults, native-comp and eln-cache redirection, terminal aliases. 2. **`init.el`** — tiny entry point. Registers MELPA/NonGNU ELPA as fallback archives, puts `lisp/` on the load-path, points `custom-file` at `var/custom.el` (write-only — never loaded back), and `require`s each module in order. Archive refresh is off the startup path entirely — there is no background warm-up; archives are fetched only when `package-install` finds an empty cache, or on an explicit `M-x package-refresh-contents`. 3. **`lisp/init-*.el`** — one file per concern. See [Modules](/architecture/modules) for the full list and their responsibilities. ### Nix Layer The Nix layer provides reproducible Emacs builds: 1. **`emacs.nix`** — wraps the git-based attrs from [`nix-community/emacs-overlay`](https://github.com/nix-community/emacs-overlay) (`git`/`unstable`/`igc`; `unstable` is the default) or nixpkgs' default `emacs` attribute (`mainline`, the cache-parity canary), with the supported build flags exposed as arguments. The build matrix is deliberately narrow: pgtk/Wayland GUI or terminal-only on Linux, patched NS GUI or terminal-only on Darwin — everything else is asserted away. A cache-parity invariant guarantees the Linux and terminal builds produce the same store paths as the matching prebuilt attrs, so their default-rev builds are cache hits against Hydra and `nix-community.cachix.org`. Custom `rev` pins and the Darwin GUI (whose nix-giant patches are on by default) run through `overrideAttrs` and intentionally rebuild from source. 2. **`default.nix`** — flake-compat wrapper for the default package set. 3. **`module.nix`** — Home Manager module that runs Jotain as a user-session Emacs daemon (`services.jotain`), generates a desktop entry for `emacsclient`, and can install itself as `EDITOR`/`VISUAL`. Supports systemd on Linux and launchd on macOS; `services.jotain.package` swaps in any other Jotain-shaped distribution. ### Development Layer * **`devenv.nix`** — development shell, **tooling only**: there is no `emacs` binary in it (see its top-of-file note). It ships Nix tooling and linters (`nil`, `nixfmt-rfc-style`, `treefmt`, `statix`, `deadnix`), the language servers and CLIs the Elisp config shells out to, the docs toolchain, and the fonts the UI config probes. Build and launch the editor with `just run-built` (see the current-state note in [Launching Emacs](/usage/launching)). * **`Justfile`** — every recipe you run day-to-day: `run-built` / `run-built-debug` (build via Nix, then launch), `check`, `test`, the `build-*` matrix, `fmt`, `update`, `verify`, `clean`, `clean-all`. The direct-launch and in-shell compile recipes (`run`, `debug`, `tty`, `check-elisp`, `compile`, `bench`, …) are disabled stubs; their coverage lives in the `elisp-lint` / `elisp-compile` / `elisp-test` flake checks. * **Pinning** — `flake.lock` is the single source of truth; `default.nix` and `emacs.nix` read it directly via `fetchTarball` so non-flake `nix-build` consumers get the same pin. `devenv.yaml` mirrors the flake's shared input revs (`nixpkgs`, `treefmt-nix`, `emacs-overlay`), and `just update` keeps `devenv.lock` in lockstep. # Compilation Mode Source: https://docs.jylhis.com/jotain/compilation-mode How compilation mode is configured and how to use it for building, linting, and navigating errors AI generated Compilation mode runs a shell command, captures the output, and parses error messages so you can jump directly to the source location. It works for compilers, linters, test runners, and anything else that outputs file/line references. ## Configuration The compile package is configured in `lisp/init-prog.el`: ```elisp theme={} (use-package compile :ensure nil :custom (compilation-scroll-output 'first-error) (compilation-ask-about-save nil) (compilation-always-kill t)) ``` * `compilation-scroll-output` is set to `'first-error` — the compilation buffer scrolls to follow output and stops scrolling when the first error appears. * `compilation-ask-about-save` is disabled — Emacs saves modified buffers before compiling without prompting. * `compilation-always-kill` is enabled — starting a new compilation kills any running one without asking. ## Running a compilation `M-x compile` prompts for a shell command and runs it. `M-x recompile` (or `g` in the compilation buffer) re-runs the last command. For per-language compile commands, `compile-multi` is configured in `lisp/init-project.el` with presets for Go, Python, Haskell, Nix, and Rust: ```elisp theme={} (compile-multi-config '((go-ts-mode . (("go test" . "go test ./...") ("go test current" . "go test .") ("go test -race" . "go test -race ./...") ("go build" . "go build ./...") ("go run" . "go run .") ("go vet" . "go vet ./...") ("golangci-lint" . "golangci-lint run"))) (go-mod-ts-mode . (("go mod tidy" . "go mod tidy") ("go mod download" . "go mod download"))) (python-mode . (("pytest" . "pytest") ("pytest file" . "pytest %file-name%"))) (haskell-mode . (("stack test" . "stack test") ("cabal test" . "cabal test"))) (nix-ts-mode . (("nix flake check" . "nix flake check") ("nix fmt" . "nix fmt"))) (rust-ts-mode . (("cargo test" . "cargo test") ("cargo clippy" . "cargo clippy --all-targets") ("cargo build" . "cargo build"))))) ``` Run `M-x compile-multi` to pick from these. The `consult-compile-multi` integration provides a narrowing UI. ## Navigating errors ### In the compilation buffer | Key | Command | What it does | | --------- | ------------------------------ | ---------------------------------------------- | | `g` | `recompile` | Re-run the last compilation command | | `M-n` | `compilation-next-error` | Move to the next error message | | `M-p` | `compilation-previous-error` | Move to the previous error message | | `RET` | `compile-goto-error` | Jump to the source location at point | | `C-c C-f` | `next-error-follow-minor-mode` | Auto-display source as you move through errors | ### From any buffer The standard `M-g n` (`next-error`) and `M-g p` (`previous-error`) work from any buffer — Emacs tracks the last buffer that produced errors and jumps you there. This works across `compile`, `grep`, `occur`, and any other mode that produces error-like output. `M-g M-n` and `M-g M-p` do the same thing but are easier to type since you can hold Meta throughout. This config also binds `M-g e` to `consult-compile-error` (in `lisp/init-completion.el`), which provides a searchable, narrowable list of all compilation errors via consult. ### Flymake integration For in-buffer diagnostics, `M-n` and `M-p` are bound to `flymake-goto-next-error` and `flymake-goto-prev-error` in `flymake-mode-map` (in `lisp/init-prog.el`). These navigate flymake diagnostics inline without needing a compilation buffer. ## Editing grep results The `wgrep` package (configured in `lisp/init-prog.el`) enables editing grep result buffers in place. The workflow is: 1. `consult-ripgrep` — search across the project 2. `C-c C-o` (`embark-export`) — export results to a grep buffer 3. `C-x C-q` — enter `wgrep` edit mode 4. Edit the results as you would any buffer 5. `C-c C-c` — apply changes to all matched files Starting with Emacs 31, a built-in `grep-edit-mode` will also be available. ## How error parsing works Compilation mode uses `compilation-error-regexp-alist` and `compilation-error-regexp-alist-alist` to parse error output. Emacs ships with dozens of entries for GCC, Java, Ruby, Python, Perl, and many more — no configuration needed for common tools. Each entry has the shape: ``` (SYMBOL REGEXP FILE LINE COLUMN TYPE HYPERLINK HIGHLIGHT...) ``` Where `TYPE` controls severity: 2 = error, 1 = warning, 0 = info. The `TYPE` field can also be a cons cell for conditional severity based on match groups. ### Adding a custom error regexp If you use a tool whose output format is not recognized, you can teach compilation mode about it: ```elisp theme={} (with-eval-after-load 'compile (push '(my-linter "^\\[\\(?:ERROR\\|\\(WARN\\)\\)\\] \\([^:]+\\):\\([0-9]+\\):\\([0-9]+\\)" 2 3 4 (1)) compilation-error-regexp-alist-alist) (push 'my-linter compilation-error-regexp-alist)) ``` ## Useful variables Beyond what this config sets, a few more compilation variables are worth knowing: ```elisp theme={} ;; Skip warnings and info when navigating with next-error. ;; 2 = only errors, 1 = errors + warnings, 0 = everything (default). (setopt compilation-skip-threshold 2) ;; Some languages (e.g. OCaml) use 0-indexed columns. (setq compilation-first-column 0) ;; Auto-close the compilation window on success. (add-hook 'compilation-finish-functions (lambda (buf status) (when (string-match-p "finished" status) (run-at-time 1 nil #'delete-windows-on buf)))) ``` ## The compilation mode family Compilation mode is not just for compilers — it is the infrastructure for any structured output with source locations: * **grep-mode** — `M-x grep`, `M-x rgrep`, `M-x lgrep` all produce compilation-derived buffers with the same navigation. * **occur-mode** — `M-x occur` participates in the same `next-error` infrastructure. * **flymake** — uses compilation-style error navigation under the hood. Any tool that outputs file/line references can plug into this system. Whether you are navigating compiler errors, grep results, test failures, or linter output, the workflow is the same: `M-g n` to jump to the next problem. # early-init.el Source: https://docs.jylhis.com/jotain/configuration/early-init Pre-package, pre-GUI initialisation # early-init.el `early-init.el` is loaded before `package.el` runs, before the first frame is drawn, and before `init.el` is evaluated. Anything that *must* happen that early lives here; everything else lives in `init.el` or one of the `lisp/init-*.el` modules. ## Garbage collection The GC threshold is bumped to "effectively never" during startup; `init-core` restores a 16 MiB runtime value. ```elisp theme={} (setq gc-cons-threshold most-positive-fixnum gc-cons-percentage 0.6) ``` ## Bidirectional text Jotain targets LTR-only usage, so disable the bidirectional reordering and parenthesis algorithms — they otherwise run on every redisplay and are measurable on large files. ```elisp theme={} (setq-default bidi-display-reordering nil bidi-paragraph-direction 'left-to-right) (setopt bidi-inhibit-bpa t) ``` ## Load preference Prefer newer source files to stale `.elc` in batch mode only. Interactive sessions keep `.elc` preference for speed. ```elisp theme={} (setq load-prefer-newer noninteractive) ``` ## package.el and use-package `package.el` stays on and `package-quickstart` is enabled — Nix (and, in dev, devenv) already provides most packages via `load-path`, so startup is fast without any trickery. `use-package` is built in on Emacs 29+, so the only thing we configure before `init.el` loads is imenu support and `use-package-always-ensure`. ```elisp theme={} (setq package-quickstart-file (expand-file-name "var/package-quickstart.el" user-emacs-directory) package-quickstart t) (setq use-package-enable-imenu-support t use-package-always-ensure t) ``` `package-quickstart-file` **must** be pinned here, not in `init.el`: `startup.el` runs its automatic `package-activate-all` between `early-init.el` and `init.el`, and looks up the quickstart file at whatever path `package-quickstart-file` holds at that moment. Leaving it at its default puts the file outside `var/`, and Emacs never finds it on later startups — quickstart ends up paying its refresh + byte-compile cost on every `package-install` without providing any actual speedup. With `use-package-always-ensure` set to `t`, every `use-package` block defaults to "install if missing". Built-ins must opt out with `:ensure nil`. ### Silencing a quickstart false-positive `package-quickstart-refresh` (called after every `package-install` via `package--quickstart-maybe-refresh`) internally re-calls `package-initialize`, which trips the "Unnecessary call to `package-initialize` in init file" warning even though the call is legitimate. Emacs's own code can't distinguish that internal self-call from a user one, so we suppress the warning type directly: ```elisp theme={} (with-eval-after-load 'warnings (add-to-list 'warning-suppress-log-types '(package reinitialization))) ``` ## Frame chrome GUI chrome is disabled in `default-frame-alist` *before* the first frame is drawn — this is much faster than toggling the modes off afterwards. ```elisp theme={} (push '(menu-bar-lines . 0) default-frame-alist) (push '(tool-bar-lines . 0) default-frame-alist) (push '(vertical-scroll-bars) default-frame-alist) (when (featurep 'ns) (push '(ns-transparent-titlebar . t) default-frame-alist) (push '(ns-appearance . dark) default-frame-alist)) (setq inhibit-startup-screen t inhibit-startup-message t inhibit-startup-echo-area-message user-login-name initial-scratch-message nil) ``` ## Byte-compiler noise Silence obsolete-symbol warnings from third-party packages we do not control. Real warnings still surface. ```elisp theme={} (setq byte-compile-warnings '(not obsolete)) ``` ## Font caches Trade a bit of RAM for smoother redisplay. ```elisp theme={} (setq inhibit-compacting-font-caches t) (when (eq system-type 'darwin) (setq ns-use-thin-smoothing t)) ``` ## Native compilation On Emacs 30 with native compilation available, keep the eln-cache out of the config directory, silence the firehose of async warnings during init, and crank native-comp speed. ```elisp theme={} (when (and (fboundp 'native-comp-available-p) (native-comp-available-p)) (setq native-comp-async-report-warnings-errors nil native-comp-speed 2) (when (fboundp 'startup-redirect-eln-cache) (startup-redirect-eln-cache (convert-standard-filename (expand-file-name "var/eln-cache/" user-emacs-directory))))) ``` ## Tree-sitter grammars Nix sets `TREE_SITTER_DIR` to point at the grammar bundle produced by `default.nix`; `early-init.el` picks it up and wires it into `treesit-extra-load-path`. ```elisp theme={} (when-let* ((ts-dir (getenv "TREE_SITTER_DIR"))) (setq treesit-extra-load-path (list ts-dir))) ``` ## Terminal aliases Ghostty advertises `TERM=xterm-ghostty`, but Emacs does not ship `term/xterm-ghostty.el`. Aliasing to `xterm-256color` makes `term/xterm.el` load instead, restoring `modifyOtherKeys` and 24-bit colour. This must be set in `early-init.el` because `tty-run-terminal-initialization` fires before `init.el`. ```elisp theme={} (add-to-list 'term-file-aliases '("xterm-ghostty" . "xterm-256color")) ``` # init.el Source: https://docs.jylhis.com/jotain/configuration/init Main Emacs configuration entry point # init.el `init.el` is deliberately tiny. Its only jobs are: 1. Register MELPA and NonGNU ELPA as fallback archives for anything Nix does not ship. 2. Put `lisp/` on the load-path. 3. Point `custom-file` at `var/custom.el` so `M-x customize` has somewhere to scribble. 4. `require` each module in the right order. Archive refresh is *not* on the startup path — there is no background warm-up at all; archives are fetched only on demand. Everything else — GC, encoding, theme, modeline, keybindings, package configuration — lives in `early-init.el` or one of the `lisp/init-*.el` modules. ## Package archives ```elisp theme={} (require 'package) (add-to-list 'package-archives '("melpa" . "https://melpa.org/packages/") t) (add-to-list 'package-archives '("nongnu" . "https://elpa.nongnu.org/nongnu/") t) ``` Nix provides most packages via `load-path`, so `use-package` finds them without touching the network. The archives above only come into play for packages that Nix does not ship. Registering an archive performs no I/O, and nothing in the config refreshes the archive index at launch. A download happens only when it is actually needed: * `package-install` — including the `:ensure t` path — calls `package--archives-initialize`, which reads the on-disk cache under `elpa/archives/` and downloads only when that cache is empty. * `M-x package-refresh-contents` and `M-x list-packages` refresh when you ask them to. So a fresh Nix deployment, where every `:ensure`d package is already on `load-path`, starts up without contacting MELPA at all. ## Load path ```elisp theme={} (add-to-list 'load-path (expand-file-name "lisp" user-emacs-directory)) ``` ## Custom file ```elisp theme={} (setq custom-file (locate-user-emacs-file "var/custom.el")) ``` `custom.el` is write-only: Jotain never loads it back. The declarative config in git remains the single source of truth; the file exists only so `M-x customize` has somewhere to scribble without touching `init.el`. ## Module loading `init.el` then `require`s each module in order. See [Modules](/architecture/modules) for what each file owns and the full load order. ```elisp theme={} (require 'init-core) ; GC, encoding, var/ paths, sane defaults (require 'init-keys) ; Global keymap and leader-key setup (require 'init-ui) ; Theme, modeline, fonts, frame tweaks (require 'init-tabs) ; Workspace tabs via tab-bar-mode (require 'init-help) ; helpful + built-in help tweaks (require 'init-docs) ; Surface jotain.info under C-h i (require 'init-editing) ; Electric pairs, delsel, whitespace, region tools (require 'init-completion) ; Vertico, marginalia, orderless, consult, corfu (require 'init-navigation) ; Dired + dirvish, project, windmove, winner (require 'init-vc) ; vc + magit + diff-hl + forge (require 'init-prog) ; prog-mode, treesit, eglot, flymake, eldoc, compile (require 'init-snippets) ; tempel snippets + eglot-tempel LSP expansion (require 'init-project) ; project + projection + compile-multi (require 'init-devenv) ; devenv.sh: tasks, processes, env, LSP, MCP (require 'init-ai) ; eca, claude-code-ide, gptel, mcp (require 'init-shell) ; eshell, comint, ielm (require 'init-terminal) ; ghostel terminal + tty integration (kkp, clipetty) (require 'init-systems) ; sops, logview, auth-source-1password (require 'init-writing) ; jinx, markdown-mode, denote, pdf-tools (require 'init-org) ; org, org-modern, capture templates (require 'init-lang-nix) (require 'init-lang-rust) (require 'init-lang-python) (require 'init-lang-go) (require 'init-lang-web) ; TS/TSX/CSS/HTML/JSON/web-mode (require 'init-lang-devops) ; Dockerfile, terraform, just, ansible (require 'init-lang-data) ; yaml, csv, sql, jinja2, gnuplot (require 'init-lang-systems) ; C/C++, CMake, Meson, Haskell, Zig ``` # Package Reference Source: https://docs.jylhis.com/jotain/configuration/package-reference Every Emacs package Jotain ships, with the reason it is included # Package Reference Auto-generated from `;;; @doc` markers immediately above each `(use-package …)` form in `lisp/init-*.el`. To change a description, edit the source file and run `just docs-refresh-packages`. See the module-level narrative under [Architecture → Modules](/architecture/modules) for higher-level grouping. ## `init-core.el` — Core baseline ### `emacs` *(built-in / Nix)* Sane defaults for the bare editor — fill column, dialog/box use, lockfiles, recursive minibuffers, case-insensitive completion. Settings here are the ones that don't deserve their own module. ### `saveplace` *(built-in / Nix)* Persist point per file across sessions. Built-in. The advice below recenters the buffer after restore so you don't reopen on the bottom line. ### `files` *(built-in / Nix)* Built-in file-handling tweaks: no auto-save side files, no backup `~` files, no kill-process confirmation, plus a hook that auto-creates missing parent directories on find-file. ### `repeat` *(built-in / Nix)* Repeat-mode lets you press the trailing key alone after a prefix command (e.g. C-x o o o instead of C-x o C-x o). Built-in, enabled globally. `repeat-exit-timeout' clears the transient map after two idle seconds so the user doesn't have to think about exiting it — the ergonomic "one-shot modifier" pattern. The built-in maps cover window resizing too (`resize-window-repeat-map'), so `C-x ^ ^ v' just works; init-keys.el only adds a map for the Emacs 31 `window-layout-\*' commands. ### `uniquify` *(built-in / Nix)* Disambiguate same-name buffers by directory prefix instead of the default `<2>` suffix. `forward` style mirrors the path. ### `ibuffer` *(built-in / Nix)* Replace `list-buffers` (C-x C-b) with the more capable ibuffer: dired-style filter/mark/operate on buffers. ### `ffap` *(built-in / Nix)* Tame `find-file-at-point` so an unknown hostname doesn't block the editor on a DNS lookup — reject means "treat as not a host". ### `time` *(built-in / Nix)* Built-in `world-clock` for cross-timezone scheduling. Loaded on demand only. ### `exec-path-from-shell` Inherits PATH, MANPATH, and other shell-managed vars from the user's login shell so GUI / launchd / systemd-spawned Emacs matches what the terminal sees. module.nix prepends Nix-store binaries (rg, fd, git, jj, zoxide, coreutils) to the wrapper's PATH — this picks up \~/.nix-profile and user toolchains. ### `autorevert` *(built-in / Nix)* Auto-revert buffers when the underlying file changes on disk — essential for branch switches and external edits. Also covers non-file buffers (dired, magit) so they refresh too. ### `recentf` *(built-in / Nix)* Built-in recently-visited files list. Used by consult-recent-file and the bookmarks UI; state file lives under var/ to keep the repo root clean. ### `savehist` *(built-in / Nix)* Persist minibuffer history (M-p / M-n, vertico ordering, etc.) across sessions. Built-in, enabled globally. ### `bookmark` *(built-in / Nix)* Built-in bookmark store. State file is themed under var/ so it joins the rest of Jotain's persistent state. `save-flag 1` writes on every change so an Emacs crash never loses bookmarks; the fringe glyph is suppressed because it adds visual noise without info. ### `isearch` *(built-in / Nix)* Lazy-count isearch matches in the prompt — "(3/12)" tells you where you are without leaving the search. ### `shr` *(built-in / Nix)* Built-in HTML renderer used by eww, gnus, elfeed. Suppress page colours and proportional fonts so rendered HTML inherits the theme and the user's monospace face — better contrast, predictable layout. ## `init-keys.el` — Global keys ### `emacs` *(built-in / Nix)* Top-level rebindings — disable accidental suspend (C-z and C-x C-z), put other-window on M-o for one-key window switching, bind C-x j to the two-window rotate helper above, and rebind C-g to the DWIM quit so it closes minibuffers from elsewhere. ### `windmove` *(built-in / Nix)* Built-in directional window switching — `Shift-` moves focus between split windows. Ships with Emacs; no reason not to turn it on globally. ## `init-ui.el` — UI: theme, modeline, fonts ### `auto-dark` Flips between `jotain-theme-light` and `jotain-theme-dark` following the system appearance — works on macOS, GNOME, and anything that exposes a dark/light setting. C-c t toggles manually. ### `doom-modeline` A dense, IDE-style modeline with LSP/eglot status, project buffer info, and Nerd Font glyphs. Loaded after init so the primary frame doesn't redraw before fonts are ready. ### `emoji` *(built-in / Nix)* Built-in emoji picker (Emacs 29+): `C-x 8 e e' inserts by name, `C-x 8 e s' searches, `C-x 8 e l' opens the full list, `C-x 8 e d' describes the emoji at point. \`which-key' surfaces the prefix. ### `display-line-numbers` *(built-in / Nix)* Built-in line numbers — only on programming and config buffers, never on prose or org files where they're noise. Honour the \`jotain-line-numbers-in-prog' toggle so users can flip it off without editing this file. ### `pixel-scroll` *(built-in / Nix)* Built-in pixel-precision smooth scrolling. Required for usable trackpad / smooth-mouse scrolling. \`fast-but-imprecise-scrolling' lets large jumps skip exact intermediate fontification — a worthwhile redisplay win on this integrated-GPU Intel machine. ### `hl-line` *(built-in / Nix)* Built-in current-line highlight — on for code and prose, off in shells/dired where it would fight the cursor. ### `paren` *(built-in / Nix)* Built-in matching-paren highlight. Tuned to flash quickly and highlight even when point is just outside the pair. ### `which-key` *(built-in / Nix)* Built-in keybinding cheatsheet. After a prefix key, displays a paged list of completions in the echo area. Discoverability multiplier — a Jotain staple. ### `calendar` *(built-in / Nix)* Built-in calendar. Configured for ISO week numbering and a Monday week start so it agrees with how the rest of Europe thinks about dates. ### `nerd-icons` Provides the Nerd-Font glyph alphabet that the rest of the nerd-icons-\* family draws on. Picks the font family from `jotain-font-preferences` so the icons match the editor face. ### `nerd-icons-corfu` Decorates corfu candidates with a kind-specific glyph in the margin, so completions are scannable at a glance. ### `nerd-icons-completion` Adds Nerd-Font icons to marginalia annotations (file/buffer category icons in completion lists). ### `nerd-icons-ibuffer` Adds Nerd-Font glyphs to ibuffer rows so buffer types are visually distinguished at a glance. ### `hl-todo` Highlights TODO / FIXME / HACK / NOTE / XXX keywords in code with a face that survives theme changes. ### `breadcrumb` Headerline showing project / file / nested function position — the missing "where am I in this file?" indicator built on imenu. ### `pulsar` Pulses a coloured highlight when point jumps a long distance (other-window, xref, consult-line). Tells the eye where the cursor went without staring. ### `rainbow-delimiters` Colourises matching parens by depth in Lisp buffers — almost essential for navigating deeply nested forms. ### `indent-bars` Vertical indent guides for code, treesit-aware so the bars follow real syntactic indentation. Toggle via \`jotain-indent-bars-enabled'. ## `init-tabs.el` — Workspace tabs ### `tab-bar` *(built-in / Nix)* Built-in frame-local tab bar — used as a workspace switcher. Bar hides itself when only one tab exists, so single-window sessions are unaffected. ## `init-help.el` — Help system ### `help` *(built-in / Nix)* Built-in help window. Auto-focus so you can scroll or dismiss with q/n/p without reaching for the mouse. ### `help-at-pt` *(built-in / Nix)* Built-in echo-area tooltips on buttons and links when point lingers — discoverability for the parts of Emacs that aren't plain text. ### `apropos` *(built-in / Nix)* Built-in apropos. Bumped to "search everything" so it surfaces faces, classes, and customs alongside functions and variables. ### `helpful` Replaces the default describe-\* commands with richer buffers that include source, callers, examples, and active keybindings — the single biggest discoverability upgrade in Emacs. ## `init-editing.el` — Editing primitives ### `elec-pair` *(built-in / Nix)* Auto-insert matching delimiters as you type. Built-in. ### `delsel` *(built-in / Nix)* When a region is active, typing replaces it instead of leaving the selection alone. Built-in, on by default in most modern editors — Emacs needs this opt-in. ### `simple` *(built-in / Nix)* Two small built-in editing knobs. `kill-region-dwim' makes C-w with no active region kill a word backwards instead of erroring; `delete-pair-push-mark' leaves a mark on the former pair contents so C-x C-x re-selects them. Both are Emacs 31+, guarded so the config loads on Emacs 30. (The "diff this buffer against its file" action under `d' during `save-some-buffers' is already built in since Emacs 30, so it needs no config here.) ### `whitespace` *(built-in / Nix)* Strip trailing whitespace and stray tabs on save without reformatting the rest of the buffer. Built-in. Skipped during super-save's automatic saves so its except-current-line handling keeps whitespace at point intact. ### `subword` *(built-in / Nix)* Treats CamelCase / snake\_case word parts as separate words for M-f / M-b / M-d. Built-in. Programming-mode only — prose still gets whole-word motion. ### `newcomment` *(built-in / Nix)* Defaults for the built-in comment commands (M-;, C-x C-;, M-j). `comment-multi-line' makes M-j continue inside an open block comment instead of closing/reopening; `extra-line' style puts opening and closing delimiters on their own lines for `comment-region'; `comment-empty-lines' makes `comment-region' treat blank lines the same as content lines; `comment-auto-fill-only-comments' keeps automatic line wrapping (when `auto-fill-mode' is on) confined to comments. C-c ; is an ergonomic alias for `comment-line' — C-; is taken by embark-dwim. Deliberate tradeoff: org-mode shadows C-c ; with \`org-toggle-comment', which is the org equivalent anyway. Per-mode overrides go in the language module via a named hook: (defun my-foo-mode-setup () (setq-local comment-multi-line nil)) (add-hook 'foo-mode-hook #'my-foo-mode-setup) ### `emacs` *(built-in / Nix)* Bindings for the two transpose commands Emacs ships without defaults, so the prose-level family (sentence, paragraph) is reachable alongside the built-in C-t (chars), M-t (words), C-x C-t (lines), and C-M-t (sexps, tree-sitter aware in Emacs 30+). Caveat: transpose-lines works on real newlines, so it gives surprising results under visual-line-mode where wrapped "lines" are visual only. ### `replace` *(built-in / Nix)* Emacs 30 ships `replace-regexp-as-diff' and `multi-file-replace-regexp-as-diff' — run a regex replacement, but see the result as a unified diff first and either apply it as a patch or abort. Worth reaching for on any non-trivial refactor. The dired-marked variant is bound in \`init-navigation.el'. ### `expreg` Treesit-aware semantic region expansion. Smaller, faster successor to expand-region; produces better expansions with much less code now that treesit is everywhere. ### `multiple-cursors` Visual multi-cursor editing. C-> and C-\< select the next/prev occurrence; C-S-c C-S-c selects every occurrence in the buffer. ### `vundo` Visual undo tree on C-x u. Stateless — no .undo-tree side files cluttering the filesystem like undo-tree.el used to leave behind. ### `super-save` Auto-saves buffers when idle and on focus loss, writing the actual file rather than #foo# auto-save side files. Also strips trailing whitespace except on the current line so you don't fight your own cursor. ### `re-builder` *(built-in / Nix)* Make `M-x re-builder' use string syntax — the same form you'd paste into `re-search-forward' — instead of the default \`read' syntax that requires escaping every backslash twice. ## `init-completion.el` — Minibuffer + in-buffer completion ### `minibuffer` *(built-in / Nix)* Built-in minibuffer customisation: detailed annotations and historical sorting (Emacs 30) so frequent commands surface first. ### `orderless` Fuzzy, space-separated, order-independent completion. Pairs with partial-completion (path globbing) so `/u/s/a` matches `/usr/share/applications`. The single most important UX win in the minibuffer. ### `vertico` Vertical, performant minibuffer completion UI. Replaces the default `*Completions*` buffer with an inline list. The whole minibuffer experience hinges on this. ### `vertico-directory` *(built-in / Nix)* Path-savvy editing in vertico — RET enters a candidate directory, DEL/M-DEL delete a path component instead of one character. Bundled with vertico. ### `vertico-multiform` *(built-in / Nix)* Per-category and per-command display modes (grid for files, buffer for line/grep so the preview window has room). Bundled with vertico. ### `vertico-buffer` *(built-in / Nix)* Lets vertico render in a regular buffer instead of the minibuffer — used by vertico-multiform for consult-line and the grep family so candidates have room to breathe. ### `marginalia` Adds annotation columns (file size, mode, docstring, …) to every completion list. Pairs with vertico to make minibuffer choices self-explanatory. ### `consult` Consult provides preview-as-you-go variants of nearly every Emacs lookup: buffer switch, line jump, grep, recent files, imenu, flymake, register store. The big binding table below replaces a dozen built-ins with a single, consistent UI. ### `embark` Right-click for the keyboard. `C-.' (embark-act) on any candidate — file, symbol, region, buffer, command name, URL — opens a menu of actions valid for that thing; `C-;' (embark-dwim) skips the menu and runs the default action (visit the file, browse the URL, jump to the definition). Press `C-h' after `C-.' to turn the menu into a searchable `completing-read' — the best way to discover what applies. Inside the menu `i'/`w' insert or copy the candidate, `A' (embark-act-all) acts on every candidate at once, and `B' (embark-become) re-runs the typed input through another command; `C-u C-.' keeps the minibuffer open for successive actions. \`C-h B' replaces describe-bindings with a paged view. ### `embark-consult` Glue between embark and consult — teaches `embark-export' to turn a consult result list into the right major mode: consult-grep and consult-ripgrep become a grep buffer, consult-line an occur buffer, file candidates a dired buffer, buffer candidates an ibuffer buffer. The grep/ripgrep export is the classic search → `C-c C-o' → wgrep (\`C-x C-q') refactor flow that edits every match in place across all the matched files. ### `avy` Tree-style char/word/line jumping. Bound under M-g so it sits next to the goto family. Multi-frame aware. ### `zoxide` Frecency-ranked directory jump (like the shell zoxide). Adds visited files automatically; M-g z surfaces the most-recent matches first. ### `emacs` *(built-in / Nix)* `tab-always-indent' = t is the stock Emacs default, restored here deliberately: TAB indents the line and does nothing else. `indent-for-tab-command's only call to `completion-at-point' is guarded by (eq tab-always-indent 'complete), so with t that branch is unreachable and TAB provably cannot summon a capf. Completion lives on `C-M-i' instead (`jotain-completion-key'). `tab-first-completion' is inert unless `tab-always-indent' is `complete', so it is irrelevant here and is never set. ### `corfu` In-buffer completion popup — the corfu equivalent of company. The popup appears on its own only in the modes listed by `jotain-completion-auto-modes' (default: programming modes), so prose stays quiet and completes only when asked via `C-M-i'. RET and TAB are unbound in `corfu-map', so Enter always inserts a newline and TAB always indents, even while the popup is showing. `M-n'/`M-p' move, `C-g' dismisses, \`C-M-i' accepts. ### `corfu-history` *(built-in / Nix)* Persists corfu's pick history into savehist so frequent completions float to the top across sessions. Bundled with corfu. ### `cape` Completion-at-point Extensions — extra capf functions (dabbrev, file path, keyword) that feed corfu when the major mode's own capf finds nothing useful. These three go on the *global* `completion-at-point-functions' so they act as fallbacks after the buffer-local, major-mode capfs run. `cape-elisp-symbol' is instead added buffer-locally in Elisp buffers, where it completes symbols even inside comments and docstrings (out there \`elisp-completion-at-point' only fires in code), without polluting completion elsewhere. ## `init-navigation.el` — dired, project, windows ### `dired` *(built-in / Nix)* Built-in directory editor — Jotain's primary file manager. The custom block below tames cross-platform ls quirks (BSD on macOS lacks `--group-directories-first` and `--dired`). `M-s R` previews a regex replacement across the contents of all marked files as a unified diff (Emacs 30's \`dired-do-replace-regexp-as-diff'). ### `dired-x` *(built-in / Nix)* Built-in dired extras — `dired-omit-mode` hides dotfiles and cache directories so dired listings show only the things you actually want to see. ### `dired-async` Async file ops for dired — wraps `dired-do-copy`, `dired-do-rename`, `dired-do-symlink`, `dired-do-hardlink` so they fork into a subprocess instead of blocking the main Emacs. Multi-GB copies no longer freeze the UI; the mode-line shows progress and a message fires on completion. ### `dired-rsync` rsync from dired — bound to `C-c C-r` in `dired-mode-map`. Best for very large transfers or TRAMP sources/destinations: hands marked files to `rsync` in an async shell buffer with live progress. Uses `--progress` (not `--info=progress2`) so stock macOS rsync 2.6.9 still works; noisier output, but portable. ### `ls-lisp` *(built-in / Nix)* Pure-Lisp ls emulation. Fallback for macOS without GNU coreutils — gives us folders-first sorting that BSD ls cannot produce. ### `wdired` *(built-in / Nix)* Built-in writable dired — C-c C-e turns the dired buffer into a regular text buffer where you can rename/chmod files with the usual editing commands. Save to commit changes. ### `diredfl` Pretty colours for dired (font-locks files by type, age, executability). Pure cosmetic, big readability win. ### `dired-narrow` Live filter dired buffers by typing a fragment after `/`. Faster than re-running ls with a glob. ### `dired-subtree` Inline tree expansion in dired — TAB on a directory expands its contents below it instead of opening a new buffer. ### `trashed` Browse the system trash bin from inside Emacs. With `delete-by-moving-to-trash` set in init-core, every dired deletion is recoverable through `M-x trashed`. ### `pretty-sha-path` Shortens `/nix/store/abc123-foo-1.0` to `…foo-1.0` in dired and shell buffers — purely cosmetic, but transformative on a system that's mostly Nix store paths. ### `dirvish` Modern dired front-end with previews, side panels, and miller columns. Overrides plain dired so every `C-x d` benefits. `dirvish-side' (C-c D) already provides the docked side-tree that Emacs 31's new `speedbar-window' offers, so speedbar is intentionally not wired up here. ### `winner` *(built-in / Nix)* Built-in window-layout undo/redo — pairs with the toggle helper below so `C-x 1` becomes a reversible "expand this window" command. ## `init-vc.el` — Version control ### `vc` *(built-in / Nix)* Built-in version control. Pinned to Git + Jujutsu — every other backend is a slow startup tax (probes every visited file's parents) you almost never benefit from. JJ is supplied by `vc-jj' below; without it in this list the backend never gets consulted and project.el won't discover `.jj' roots. ### `vc-git` *(built-in / Nix)* Quick jump to a file git status reports as changed. Runs `git status --porcelain=v1 -z -uall' (-z keeps spaces and non-ASCII paths intact; -uall forces untracked listing regardless of status.showUntrackedFiles) and offers M/A/R/C/U/T/?? entries through `completing-read'. Adapted from Rahul M. Juliato's emacs-solo/switch-git-status-buffer. ### `vc-jj` Jujutsu (jj) backend for built-in `vc' and `project'. Adds the JJ entry pinned in `vc-handled-backends' above, so `C-x v …', the modeline VC state, and `project.el' all light up on jj repos (typically colocated with git). The jj side wants [ui] diff-formatter = ":git" conflict-marker-style = "git" set via `jj config edit --user' so vc/diff-hl/smerge read jj diffs and conflicts in the format they expect. `jotain-switch-jj-status-buffer' (C-x J — not C-x C-j, which stays on its Emacs 28+ default `dired-jump') is the jj twin of the git status jump above: it parses `jj diff --summary -r @' and offers the changed files (deletions omitted — the file is gone) through `completing-read'. The richer interactive view is \`majutsu' (C-c j). ### `magit` *(built-in / Nix)* The Git porcelain. Bound C-x g for status, C-x M-g for global dispatch, C-c g for the file-specific menu. Refined hunks + whitespace-ignoring diffs are turned on globally. ### `majutsu` *(built-in / Nix)* The Jujutsu porcelain — a magit-style interface for jj, sitting alongside magit (jj is normally colocated with git, so both apply). C-c j opens the status/log buffer (`majutsu-log', aliased `majutsu'); C-c M-j opens the top-level transient dispatcher. Provided by Nix (nix/extra-packages.nix), so \`:ensure nil'. ### `magit-todos` Surfaces TODO/FIXME/HACK comments as a section in magit-status. Scan depth pinned to 1 so it stays fast on large repos. ### `forge` PRs, issues, and reviews from GitHub/GitLab/Forgejo inside magit. Uses the Emacs-30 built-in sqlite so no external emacsql binary is needed. Auth via \~/.authinfo.gpg (machine api.github.com login USER^forge password ghp\_…). ### `transient` *(built-in / Nix)* Built-in transient menu system that magit/forge are built on. Themed under var/ so its three state files don't drop at the repo root. ### `diff-hl` Fringe indicators for added/changed/removed lines in the buffer you're editing. `diff-hl-flydiff-mode` updates pre-save so the indicators reflect uncommitted edits, not just the last save. ### `ediff` *(built-in / Nix)* Built-in interactive diff. Configured with `plain` window setup so the control panel doesn't pop a separate frame, plus whitespace-ignoring diffs for less merge noise. ## `init-prog.el` — prog-mode, treesit, eglot ### `prog-mode` *(built-in / Nix)* Built-in `prog-mode` parent. Just turns on the fill-column indicator — hl-line and show-paren live in init-ui so they apply outside programming buffers too. ### `treesit` *(built-in / Nix)* Built-in tree-sitter substrate. Bumped to font-lock level 4 to enable every available syntactic decoration. ### `jit-lock` *(built-in / Nix)* Defer fontification of newly exposed text by a few hundredths of a second so heavy treesit level-4 font-lock never blocks keystrokes or scrolling. Pairs with \`redisplay-skip-fontification-on-input' (set in init-core) — matters more on this slower Intel CPU. ### `treesit-fold` Code folding driven by treesit syntax nodes — folds along functions/classes/blocks instead of indentation guesses. Fringe indicators show fold state. ### `combobulate` *(built-in / Nix)* Structural editing via treesit (move/clone/raise nodes, transpose siblings). Heavy enough to be opt-in per buffer via M-x combobulate-mode or .dir-locals.el. Provided by Nix. ### `eglot` *(built-in / Nix)* Built-in LSP client. Per-language `eglot-ensure` hooks live here so all LSP wiring is visible in one place; per-language mode regexes stay in their `init-lang-*` file. C-c r is the refactor prefix (rename/format/code-actions). Beyond the curated hook list, `jotain-prog--maybe-eglot-ensure' auto-starts eglot for ANY project file whose language server is on the buffer's (envrc-applied) PATH — so enabling `languages.X.enable' in a project's devenv lights up its LSP in Emacs with no per-language config. ### `consult-eglot` Consult-driven workspace symbol search — C-M-. opens an orderless-filtered list of symbols across the LSP workspace. ### `consult-eglot-embark` Embark integration for consult-eglot — gives every workspace symbol an action menu (jump to def, find refs, rename, …). ### `dape` Debug Adapter Protocol client — the debugging counterpart to eglot. Ships adapter configs for dlv (Go), debugpy (Python), codelldb (Rust/C/C++), and more; the adapter binary (e.g. `dlv`) comes from the project/host PATH, same convention as the LSP servers. `C-x C-a` is the prefix (the gud convention); stepping commands carry repeat-maps, so `C-x C-a n n n` keeps stepping. ### `flymake` *(built-in / Nix)* Built-in inline diagnostic display. Indicator chars (! ? ·) and end-of-line message rendering keep diagnostics legible without opening a side window: \`short' shows only the most severe diagnostic per line, so a busy line stays readable. M-n / M-p navigate. ### `eldoc` *(built-in / Nix)* Built-in echo-area documentation. Single-line display plus small idle delay so it feels responsive without flashing while you type. ### `xref` *(built-in / Nix)* Built-in cross-reference engine. Pinned to ripgrep (in the devenv shell) for orders-of-magnitude faster project-wide lookups than default grep. ### `tagref` *(built-in / Nix)* Cross-reference checker for `[tag:x]'/`\[ref:x]' directives. Adds completion, xref navigation (M-. jumps from a ref to its tag, M-? finds references), and `M-x tagref-check' (clickable compilation buffer). Needs the `tagref' CLI on PATH (dev shell / Home Manager wrapper). Provided by Nix (not on MELPA). ### `compile` *(built-in / Nix)* Built-in compile / recompile. Auto-scroll until the first error and skip the "save?" prompt — the annoying defaults that make people reach for projectile or compilation-multi alternatives. ### `editorconfig` *(built-in / Nix)* Honour `.editorconfig` files (indent style/width, line endings, trailing whitespace). Built-in since Emacs 30. ### `apheleia` Async format-on-save through external formatters (ruff, nixfmt, rustfmt, prettier, …). Replaces hand-rolled per-language hooks with one place to look. Per-buffer override safe-local-variable lets `.dir-locals.el` opt out. ### `wgrep` Edit grep / ripgrep result buffers in place; saving propagates edits to every matched file. Powers the project-wide refactor flow: consult-ripgrep → C-c C-o (embark-export) → C-x C-q (wgrep) → edit → C-c C-c. ### `dtrt-indent` Detect indentation width from file contents — saves us from having to special-case every project's tab/space convention. ## `init-snippets.el` — Tempel snippets ### `tempel` Lightweight template/snippet engine from the corfu/cape author. Templates are read from `templates/*.eld' (keyed by major mode); `M-+' completes a snippet by name, `M-*' inserts one interactively, and once fields are active `M-}'/`M-{' or `C-M-n'/`C-M-p' move between them, with `M-RET' to finish. TAB is deliberately not involved: in this configuration TAB indents and nothing else. Set `jotain-completion-snippets' to nil to keep snippet names out of the completion popup; `M-+' and \`M-\*' still work. ### `eglot-tempel` Lets Tempel expand the snippets language servers send back (e.g. function-argument placeholders from rust-analyzer / pyright). \`eglot-tempel-mode' must be enabled before eglot connects, so it is armed the moment eglot loads rather than after the first session. ## `init-project.el` — Per-project commands ### `project` *(built-in / Nix)* Built-in project tracker. Extra root markers below mean a project is recognised when any of these is present, not just on a VCS root. Project list lives under var/. `C-x p P' scans `jotain-repositories-roots' to add+open projects not yet in the known list. ### `projection` `.dir-locals.el`-driven per-project commands (configure, build, test, run, package, install) auto-detected from Makefile/justfile/Cargo.toml/etc and exposed under C-x P. ### `projection-multi` Bridges projection with compile-multi: project-prefix RET picks from every named compile command available in this project. ### `projection-multi-embark` Embark menu for projection-multi entries — pin a command, preview output, etc. ### `compile-multi` Per-major-mode picker for named compile commands ("go test", "pytest file", "nix flake check", …). Complement to projection, kept side by side because neither system fully covers the other. ### `consult-compile-multi` Renders compile-multi pickers through consult — gives you orderless filtering and preview on every "what command should I run?" prompt. ### `compile-multi-nerd-icons` Decorates compile-multi entries with nerd-font glyphs for the command type — purely visual, but a useful at-a-glance hint. ### `compile-multi-embark` Embark actions on compile-multi entries (run, copy, edit command line). Mirrors the projection embark integration. ## `init-devenv.el` — devenv.sh integration ### `devenv` *(built-in / Nix)* Native devenv.sh integration (the in-repo `lisp/devenv.el` library). `C-c v` opens a transient with task and script runners, `devenv test`/`build` through compilation-mode with Nix error matching, a process-manager dashboard (start/stop/restart/logs), and environment introspection (`devenv eval`/`info`/`search`). `devenv-reload` re-runs `devenv print-dev-env` and re-applies the environment buffer-locally via the native loader (`devenv-env-global-mode`), and offers to reconnect eglot servers. devenv.nix buffers are routed to the bundled `devenv lsp` server while `nil` keeps serving other Nix files, and `devenv-mcp-setup` registers the project's `devenv mcp` server with mcp.el so gptel can call its tools. `devenv-allow`/`devenv-revoke` manage devenv 2.1's auto-activation trust database, which also gates the native env loader, and `devenv-modeline-mode` (enabled here) shows the per-buffer status — devenv\[on]/\[off]/\[!] — in the mode line. Everything degrades to a clean error when the `devenv` binary is not on PATH. ## `init-ai.el` — AI assistants ### `claude-code-ide` *(built-in / Nix)* Agentic multi-file editing through the Claude Code CLI. Bound to C-c q (user-reserved space, so no major mode shadows it) so the menu is one key away whenever a refactor needs more context than a single LSP rename can carry. Provided by Nix (manzaltu/claude-code-ide.el is not on MELPA). ### `eca` Editor Code Assistant — AI pair-programming client (chat, inline completion, rewrite, MCP) talking to an external \`eca' server over JSONRPC. The server binary is provided by Nix and found on \$PATH, so nothing is downloaded; provider keys come from the environment, same as gptel. C-c e starts a session and opens the chat. ### `gptel` Conversational LLM front-end with multiple backends. OpenRouter — an OpenAI-compatible aggregator fronting Claude, GPT, Gemini, DeepSeek, Qwen, GLM and more behind one key — is the default; direct Anthropic, Gemini and local Ollama backends stay selectable from the C-c S menu. Bound to C-c s / C-c S for quick send and full menu — user-reserved space, so org-mode and friends can't shadow them. Keys come from the environment first, then auth-source via auth-source-1password. ### `mcp` Model Context Protocol bridge — lets gptel call MCP tools so the LLM can read files, query databases, and act through registered servers. Loaded on demand via its autoloads (\`devenv-mcp-setup', M-x mcp-connect-server) — nothing here forces a load. ## `init-shell.el` — Shells ### `eshell` *(built-in / Nix)* Built-in Lisp-driven shell — works the same on every platform and is the right tool for Emacs-flavoured pipelines (commands as Elisp functions, no subprocess for builtins). ### `comint` *(built-in / Nix)* Built-in REPL substrate (used by python, ielm, sql, etc.). The settings here apply to every comint-derived buffer. ### `ielm` *(built-in / Nix)* Built-in Emacs Lisp REPL. Emacs 31+ can persist input history across sessions like comint/shell already do; point it at a file under \`var/'. Guarded so the config loads on Emacs 30. ## `init-terminal.el` — Terminal support ### `ghostel` Terminal emulator powered by libghostty-vt (the Ghostty VT engine) via a native dynamic module. Replaces vterm: real PTY for tmux/ncurses/TUI programs, plus shell integration (OSC 7 directory tracking, OSC 133 prompt jumping) injected automatically for bash/zsh/fish. The Nix package builds the module from source and ships it in the package directory; in a source checkout the module is downloaded into the writable elpa/ dir on first \`M-x ghostel'. ### `kkp` Kitty Keyboard Protocol — lets terminal Emacs distinguish C-i/TAB, C-m/RET, C-\[/ESC, and pass Shift-modified function keys through. Only loaded in tty and daemon sessions — the daemon arm matters because a daemon can later serve \`emacsclient -nw' frames; pure GUI sessions skip it entirely. ### `clipetty` OSC 52 clipboard integration. Yank/kill in terminal Emacs reaches the system clipboard even through ssh + tmux. ## `init-systems.el` — Sysadmin tools ### `auth-source-1password` Pulls credentials from the 1Password CLI (`op`). Once enabled, every package that uses auth-source — magit/forge, gptel, smtpmail, circe — resolves credentials by host against the 1Password vault transparently. ### `sops` Transparent SOPS encrypt/decrypt for YAML/JSON/env files. C-c C-c saves an encrypted edit; C-c C-d toggles into the editing view; C-c C-k cancels. ### `logview` Major mode for log files — level filtering, timestamp parsing, thread highlighting. Configured for SLF4J (Java/Kotlin) and a custom ROS2 submode. ## `init-tracking.el` — Activity tracking ### `keyfreq` Counts command invocations to disk; `M-x keyfreq-show` ranks the busiest commands so you can spot rebinding opportunities. Tiny, no daemon, no network. ### `wakatime-mode` Heartbeats to a Wakatime / Wakapi server. The `:if` guard makes the whole block a no-op when `wakatime-cli` or WAKATIME\_API\_KEY is missing, so you can install Wakapi later without reconfiguring Emacs. ### `activity-watch-mode` ActivityWatch integration — correlates editor activity with the rest of the machine. Installed but OFF by default; enable with `M-x global-activity-watch-mode` once the AW server is running, or set the option in custom.el to persist. ## `init-writing.el` — Prose & notes ### `text-mode` *(built-in / Nix)* Built-in text-mode tweaks for prose: visual line wrap and hanging indent on wrapped lines. Proportional fonts come from \`mixed-pitch' (below); code modes stay monospaced. ### `mixed-pitch` Proportional fonts for prose via the `variable-pitch' face, while code, tables, and verbatim spans stay monospaced (`fixed-pitch'). This keeps Org tables aligned, where a blanket \`variable-pitch-mode' would leave them ragged. Enabled in every text-mode buffer and opted out of the column-sensitive ones: YAML (see init-lang-data), commit messages, and email. ### `jinx` Just-in-time spell check using enchant — no on-save scan, no per-buffer flyspell setup. `global-jinx-mode' replaces both `flyspell-mode' and `flyspell-prog-mode' in one shot: jinx keys off faces, so in a code buffer it checks only comments and strings and leaves the code alone, no `flyspell-prog-mode' equivalent needed. Enabled from \`emacs-startup' rather than a bag of per-mode hooks so prose *and* code get checked. M-$ corrects the word at point (C-u M-$ walks every misspelling); C-M-\$ switches dictionaries. Data/config buffers that only look like prose opt back out in init-lang-data. ### `markdown-mode` Markdown major mode with native code-block fontification and heading scaling. README.md opens in gfm-mode for the GitHub dialect. ### `denote` Plain-text note system with strict file-naming rules so notes are findable years later. Stored under `jotain-notes-directory` (default `~/Documents/notes`), shared with Org (init-org.el). ### `pdf-tools` In-Emacs PDF viewing — search, annotate, follow links, outline. Needs libpoppler at build time, which the emacs-pgtk / emacs-mac variants in nixpkgs already provide. ## `init-org.el` — Org ### `org` *(built-in / Nix)* Built-in Org — outline, agenda, capture, literate-programming. Treated as a "third-party" package despite being built-in because it's the size and surface area of one. Capture templates and a small bind table live below. ### `org-clock` *(built-in / Nix)* Built-in time tracking with persistence across restarts. Lets you resume an interrupted clock without losing the entry. ### `org-appear` Reveal Org emphasis markers (`*` `_` `/` `~`) only when point is on them — best of both visual worlds: clean reading, easy editing. ### `org-modern` Modern visual styling for Org buffers and agenda — typographic bullets, faux-rendered blocks, agenda decorations. ### `org-roam` Zettelkasten-style note-linking on top of Org. SQLite database auto-syncs in the background; C-c n f / i / c are the three bindings you actually use day-to-day. ## `init-lang-nix.el` — Nix ### `nix-ts-mode` *(built-in / Nix)* Tree-sitter Nix major mode. Nix is the package manager and dev-shell tool the whole config is built around, so it gets a dedicated module even though we don't write much of it daily. Provided by Nix; format-on-save flows through apheleia → nixfmt (configured in init-prog). A blank-line indent rule fills the gap where tree-sitter leaves an empty line at column 0. ## `init-lang-rust.el` — Rust ### `rust-ts-mode` *(built-in / Nix)* Built-in tree-sitter Rust mode. Eglot wires rust-analyzer in init-prog; format-on-save runs rustfmt through apheleia. ## `init-lang-python.el` — Python ### `python` *(built-in / Nix)* Built-in Python mode pinned to its tree-sitter variant so we get the modern parser without any third-party package. The LSP server (pyright/basedpyright/ruff-lsp) comes from the project's own environment, not from this config. ## `init-lang-go.el` — Go ### `go-ts-mode` *(built-in / Nix)* Built-in tree-sitter Go modes: `go-ts-mode` for source files, `go-mod-ts-mode` for go.mod, `go-work-ts-mode` (Emacs 31) for go.work. Eglot wires gopls in init-prog; format-on-save runs goimports through apheleia; dape drives dlv for debugging. All Go tooling (go, gopls, goimports, dlv) comes from the project/host PATH, not from this config. ### `go-tag` Struct-tag editing (`json:"..."', `db:"..."', …). gopls has no equivalent; the underlying `gomodifytags' binary comes from the project environment. Bound under the buffer-local `C-c C-t' Go tooling prefix so it never shadows the global \`C-c t' theme toggle. ### `gotest` Run the Go test or benchmark at point, the current file's tests, or the whole project, with compilation-mode error jumping. Complements the project-wide compile-multi "go test" commands with single-test / single-file runs. Shares the mode-local \`C-c C-t' Go tooling prefix with go-tag. ## `init-lang-web.el` — Web (TypeScript, JS, CSS, HTML) ### `typescript-ts-mode` *(built-in / Nix)* Built-in tree-sitter TypeScript / TSX / JSX modes. Eglot wires typescript-language-server in init-prog. ### `js` *(built-in / Nix)* Built-in JavaScript major mode pinned to its tree-sitter variant. ### `css-mode` *(built-in / Nix)* Built-in CSS / SCSS major mode pinned to the tree-sitter variant. ### `web-mode` One mode for every templating language that mixes HTML with something else: ERB, Mustache, Django, ASP, JSP, PHP. The M-o rebind below stops web-mode-map from shadowing our global other-window binding. ## `init-lang-devops.el` — DevOps (CI, IaC, containers) ### `dockerfile-mode` Dockerfile major mode — syntax highlighting plus build commands (`M-x dockerfile-build-buffer`). The runtime command name comes from `jotain-docker-backend`. ### `docker-compose-mode` YAML-flavoured docker-compose syntax with awareness of compose keywords and service references. ### `terraform-mode` HCL-aware Terraform mode for `.tf` files. Loaded by the mode regex; LSP comes from terraform-ls (configured in init-prog). ### `gitlab-ci-mode` YAML mode tuned for `.gitlab-ci.yml` keywords — includes, rules, jobs. Saves a lot of typo'd job names on CI debugging days. ### `just-mode` Major mode for `Justfile` — the project-aware command runner Jotain itself uses. Pairs with compile-multi for project commands. ### `ansible` Ansible minor mode layered on top of yaml-mode for playbook files. Adds module-name completion and Jinja2 highlighting. ### `bazel` Bazel/Starlark support — major modes for `BUILD`, `WORKSPACE`, `MODULE.bazel`, `REPO.bazel`, `*.bzl`, `.bazelrc`, `.bazelignore` and `.bazeliskrc` (auto-mode-alist comes from the package's own autoloads). `C-c C-f` runs buildifier; format-on-save for the Starlark-family buffers is wired through apheleia in init-prog. ## `init-lang-data.el` — Data formats ### `yaml-mode` YAML major mode (MELPA). Loaded on demand for the dozens of YAML-shaped files in any modern repo (CI, k8s, helm). YAML derives from `text-mode' upstream, so we re-fire `prog-mode-hook' to get the full editor surface (line numbers, flymake, indent guides, …). ### `yaml-ts-mode` *(built-in / Nix)* Built-in tree-sitter YAML mode (Emacs 29+). Same prog-mode hook tweak as `yaml-mode'; kept in its own use-package block so users running the built-in mode aren't forced to install the MELPA `yaml-mode' package by \`use-package-always-ensure'. ### `csv-mode` CSV major mode with column alignment. csv-align-mode renders separators visually so wide files become readable without reflowing the actual bytes. ### `sql-indent` Smart indentation for SQL files — keeps SELECT lists, JOINs, and CTEs aligned without manual whitespace fiddling. ### `jinja2-mode` Jinja2 / Ansible / Saltstack templating syntax. Mode regex covers `.j2`, `.jinja`, and `.jinja2`. ### `gnuplot` Major mode for gnuplot script files (`.plt`). Useful when an analysis pipeline emits its own plotting scripts. ## `init-lang-systems.el` — Systems (C/C++, Haskell, OCaml, Zig) ### `cc-mode` *(built-in / Nix)* Built-in C/C++ mode with a Stroustrup style bias. The header extensions below default to C++ — Jotain assumes that's the more common case in modern repos. C/C++ deliberately stay on cc-mode rather than c-ts-mode/c++-ts-mode — the style setup below is not migrated — and \`jotain-prog-warn-non-ts-exclude' (init-prog.el) records that choice so the tree-sitter gap detector stays quiet about it. ### `cmake-mode` CMake mode for CMakeLists.txt and `.cmake` files. Mode regex covers both file conventions. ### `meson-mode` Meson mode for `meson.build`, `meson_options.txt`, and `meson.options` files. Formatting is configured centrally through apheleia, using the Meson CLI supplied by the dev shell. ### `haskell-mode` Haskell major mode. Loaded on demand only — keeps the rare Haskell editing session from costing every Emacs start. ### `tuareg` Tuareg — the standard OCaml major mode for `.ml`/`.mli` sources (OCaml has no built-in tree-sitter mode). Eglot already maps `tuareg-mode' to `ocamllsp' and apheleia maps it to \`ocamlformat', so the generic eglot auto-start and format-on-save in init-prog pick OCaml up with no extra wiring. All OCaml tooling (ocamllsp, ocamlformat) comes from the project/host PATH, not this config. ### `dune` Dune major mode for the OCaml build system's `dune`, `dune-project`, and `dune-workspace` files — the OCaml counterpart to the CMake/Meson modes above. ### `zig-ts-mode` Tree-sitter Zig mode (MELPA). Eglot wires zls in init-prog; format-on-save runs \`zig fmt' through apheleia. # Packages Source: https://docs.jylhis.com/jotain/configuration/packages How packages are managed in Jotain # Packages Jotain is a "use-package + Nix" configuration: Nix provides most packages on `load-path`, so `use-package` finds them without touching the network; anything Nix does not ship falls through to MELPA at install time. ## Provenance * **Built-in to Emacs 30+** — always opt out of installation with `:ensure nil`. * **Provided by Nix** — the distribution (`nix/mk-overlay.nix`) scans the `use-package` declarations and maps them to the emacs-overlay package set; archive-absent packages (e.g. `claude-code-ide`, `combobulate`) are built in `nix/extra-packages.nix`. These also use `:ensure nil` because Nix already put them on the load path. * **MELPA / NonGNU ELPA fallback** — every other `use-package` block. `use-package-always-ensure` is `t` (set in `early-init.el`), so these default to "install if missing" without any extra boilerplate. Archives are registered in `init.el`: ```elisp theme={} (add-to-list 'package-archives '("melpa" . "https://melpa.org/packages/") t) (add-to-list 'package-archives '("nongnu" . "https://elpa.nongnu.org/nongnu/") t) ``` ## Tree-sitter grammars When built with `default.nix` (the full distribution — `just build` and friends), Jotain ships *every* tree-sitter grammar in nixpkgs via `emacsPackagesFor emacs |> withPackages (epkgs: [ epkgs.treesit-grammars.with-all-grammars ])`. The grammar bundle is exposed through `TREE_SITTER_DIR`; `early-init.el` picks that up and adds it to `treesit-extra-load-path` so every built-in `*-ts-mode` finds its grammar out of the box. ## Packages that live in Nix `nix/extra-packages.nix` defines the Elisp packages that are not on any archive (MELPA, GNU ELPA, NonGNU ELPA), so Nix builds them directly via `trivialBuild` from pinned revisions and puts them on `load-path`: * **`ghostel`** — Ghostty terminal integration. * **`jylhis-emacs-themes`** — the design-system themes; their revision comes from `nix/design-pin.nix`, the single pin shared with the website's vendored CSS. * **`claude-code-ide`** — fetched from `manzaltu/claude-code-ide.el`, built with `websocket` and `web-server` as runtime dependencies (`transient` is built in to Emacs 30+). * **`combobulate`** — fetched from `mickeynp/combobulate`; all its dependencies (`seq`, `map`, `treesit`) are built in to Emacs 30+. * **`majutsu`** — Magit-style porcelain for Jujutsu (jj), wired in `init-vc.el`. * **`tagref`** — Emacs integration for the `tagref` cross-reference checker. All are consumed by `use-package ... :ensure nil` blocks in the relevant `init-*.el` module and wired into the distribution by `nix/mk-overlay.nix`. ## The devenv shell The devenv shell provides **tooling only** — linters, language servers, the docs chain, fonts. Emacs itself is *not* in the shell (see the top-of-file note in `devenv.nix`); build and launch the editor with `just run-built`. ## Adding a new package Most of the time you just need a `use-package` block in the appropriate `init-*.el` module — `:ensure t` is implicit thanks to `use-package-always-ensure`: ```elisp theme={} (use-package some-package :mode "\\.ext\\'" :custom (some-package-option t) :config ;; configuration here ) ``` If the package is not on any archive, add it to `nix/extra-packages.nix` with `trivialBuild` so it is built from a pinned GitHub revision and available on `load-path`, then declare it with `:ensure nil` in the module. One invariant to know: the Nix side (`nix/use-package.nix`) **parses** the Elisp — it does not evaluate it. `use-package` forms must stay top-level (not wrapped in `when`/`let`/macros) and not `:disabled`, or the package silently drops out of the Nix-provided set even though the Elisp still works interactively. # Ergonomics Source: https://docs.jylhis.com/jotain/ergonomics One-shot modifiers, repeat-maps, and reducing chord strain in Jotain # Ergonomics Long Emacs sessions punish the modifier-holding fingers. Protesilaos Stavrou's [keyboard ergonomics writing](https://protesilaos.com/keeb/) argues for replacing **held** modifier chords with **one-shot modifiers** — tap-then-press — so that the only fingers under load are the ones actually typing characters. Most of Prot's solution lives in keyboard firmware (split keyboards, QMK one-shot mods, thumb clusters). The Emacs side of the same idea is **`repeat-mode`** plus a small set of `repeat-map`s. This page explains what Jotain enables out of the box, how to extend it, and what to look at next if you want to push further. ## Repeat-mode: the Emacs-native one-shot `repeat-mode` is built into Emacs 28+ and enabled globally in `init-core.el`. After running a command tagged with a `repeat-map`, Emacs installs that map as a transient overlay: the *bare* trailing keys keep running related commands until you press anything else (or 2 s pass — configured via `repeat-exit-timeout`). The everyday wins are all built-in: | Type once | Then keep typing | What happens | | --------- | ---------------- | ------------------------------- | | `M-o` | `o o o` | Cycle through windows | | `C-/` | `/ / /` | Undo, undo, undo | | `C-x →` | `→ →` | Cycle through buffers | | `M-g n` | `n n n` | Jump through compilation errors | | `C-x o` | `o O` | `other-window` forward/back | | `C-x ^` | `^ ^ v` | Resize the window, then correct | These come from Emacs itself; Jotain just turns the mode on. Run `M-x describe-repeat-maps` to see every active map at any moment. ## Jotain-specific maps Window resizing is covered out of the box too: Emacs ships `resize-window-repeat-map`, so `C-x ^ ^ ^ v` overshoots vertically then corrects, and `C-x } } } { {` widens then narrows horizontally — all without re-pressing `C-x`. `shrink-window` has no default key binding, so `v` only activates *inside* the repeat overlay (after another entry command has already fired). The one map Jotain adds itself (in `lisp/init-keys.el`, on Emacs 31+) is `jotain-window-layout-repeat-map` under the `C-x W` prefix: it repeats the new `window-layout-*` frame transforms, so `C-x W r r r` keeps rotating the whole window tree. The 2-second `repeat-exit-timeout` (in `init-core.el`) clears any repeat overlay automatically — no `C-g` needed. ## Extending repeat-maps To add your own, drop a `defvar-keymap` into `lisp/init-keys.el` (or the module owning the commands) and tag it with `:repeat t`. Every command bound in the map gets `repeat-map` set automatically. For example, paragraph navigation: ```elisp theme={} (defvar-keymap my-paragraph-repeat-map :repeat t "{" #'backward-paragraph "}" #'forward-paragraph) ``` Now `M-{ { {` walks backward through paragraphs without holding Meta. ## Pinky-stretch escape hatches Jotain already takes A few existing bindings are quiet ergonomic wins: * **`M-o` → `other-window`** (instead of `C-x o`) — one chord, no prefix sequence, both hands involved. * **`C-z` and `C-x C-z` disabled** — removes a foot-gun and frees `C-z` for personal use. * **`windmove-default-keybindings`** — `Shift-` to move directionally between windows, no chord required. * **macOS `Cmd` → Meta** (`init-core.el`) — Meta lives under the thumb, not the pinky. ## Beyond Emacs: the rest of Prot's advice Most of Prot's recommendations are outside Emacs's reach: * **Caps Lock → Ctrl** is fine, but reinforces left-pinky overuse. Prefer a keyboard with thumb-reachable Ctrl (or remap *both* sides). * **Split keyboards** (Prot uses an Iris) let each hand stay in its natural posture and put modifiers under the thumbs. * **Firmware one-shot mods** (QMK, ZMK) let any key act as a modifier for a single subsequent press. This is the hardware analogue of `repeat-mode`. These are out of scope for an Emacs config, but worth knowing about when the in-editor wins above stop being enough. ## Optional: `modifier-bar-mode` Emacs 30 ships `modifier-bar-mode`, a tool-bar widget that lets you tap `Ctrl`/`Meta`/`Super`/`Hyper` and have it apply to the next keypress — a clickable one-shot modifier. Jotain disables the tool-bar entirely in `early-init.el` for a clean UI, so this is **off by default**. To enable it for yourself, add to your `custom.el` or a personal init fragment: ```elisp theme={} (tool-bar-mode 1) (modifier-bar-mode 1) ``` ## Further reading * [Irreal: "Emacs Keyboard Ergonomics"](https://irreal.org/blog/?p=13788) — the post that prompted this page. * [Protesilaos's mechanical keyboards index](https://protesilaos.com/keeb/) — RSI background, Iris choice, QMK config. * `M-x describe-repeat-maps` — list every repeat-map currently active. * `(info "(emacs) Repeating")` — the official Emacs manual section. # Finding Information in Emacs Source: https://docs.jylhis.com/jotain/finding-information-in-emacs How to use Emacs' built-in help system, Info reader, and introspection tools AI generated Also covers programmatic access for scripts and AI agents via `emacs --batch`. ## The self-documenting editor The starting point is always `C-h` (the Help prefix). If you remember nothing else, remember `C-h C-h`, it shows you all the help commands. ## 1. The Help system (`C-h`) `C-h` is the gateway to everything. Press it and wait, `which-key` will show you all available subcommands. Or press `C-h C-h` for the interactive help menu. ### Quick orientation `C-h C-h` (or `C-h ?`) runs `help-for-help`, which shows all available help commands. Basically the meta-help. `C-h C-q` toggles a cheat sheet of common commands via `help-quick-toggle`. ### The essential eight These are the commands you'll use 90% of the time. `C-h f` (`describe-function`) gives you documentation for a function, with completion. `C-h v` (`describe-variable`) shows documentation and the current value of a variable. `C-h k` (`describe-key`) tells you what command a key sequence runs, with full docs. `C-h o` (`describe-symbol`) gives you everything about a symbol, the function docs, variable docs, and face docs all combined. `C-h m` (`describe-mode`) describes the current major mode and all active minor modes. `C-h b` (`describe-bindings`) lists every active keybinding in the current buffer. `C-h x` (`describe-command`) is like `C-h f` but restricted to interactive commands. And `C-h w` (`where-is`) tells you which key(s) run a given command. ### More help commands Beyond those eight, there is a whole collection of help commands worth knowing about. `C-h c` (`describe-key-briefly`) shows the command name for a key in the echo area without opening a popup. `C-h a` (`apropos-command`) finds commands matching a pattern. `C-h d` (`apropos-documentation`) searches documentation strings. `C-h i` (`info`) opens the Info manual browser. `C-h r` (`info-emacs-manual`) jumps directly to the Emacs manual. `C-h R` (`info-display-manual`) lets you choose a manual by name. `C-h S` (`info-lookup-symbol`) looks up a symbol in the language-appropriate manual. `C-h F` (`Info-goto-emacs-command-node`) finds a command in the Emacs manual. `C-h K` (`Info-goto-emacs-key-command-node`) finds a key in the Emacs manual. For general orientation: `C-h t` (`help-with-tutorial`) runs the interactive Emacs tutorial, `C-h p` (`finder-by-keyword`) browses packages by category, `C-h P` (`describe-package`) gives documentation for a specific package, `C-h l` (`view-lossage`) shows the last 300 keystrokes so you can replay what you just did, and `C-h e` (`view-echo-area-messages`) shows the message history. There is also `C-h .` (`display-local-help`) for showing help-echo text at point, `C-h n` (`view-emacs-news`) for release notes, and the internationalization helpers: `C-h C` (`describe-coding-system`), `C-h I` (`describe-input-method`), and `C-h L` (`describe-language-environment`). ### Built-in documents Emacs ships with several standalone documents accessible directly from the help system. `C-h t` is the interactive tutorial available in 20+ languages. `C-h n` (or `C-h C-n`) shows release notes for the current version. `C-h C-f` opens the FAQ, `C-h C-p` opens known issues and workarounds, `C-h C-d` explains how to debug Emacs itself, and `C-h C-a` shows version, license, and credits. There is also `C-h h` which opens the HELLO file, showing character set display samples in many languages. ### Navigating Help buffers When a `*Help*` buffer is open, there are some useful keys for navigating within it. `TAB` and `S-TAB` jump between hyperlinks, `RET` follows the hyperlink at point, `s` jumps to the source code of the described item, `i` looks it up in the Info manual, `I` looks it up specifically in the Elisp Reference Manual, and `c` opens customization for a variable or face. For history navigation: `l` goes back, `r` goes forward, `n` and `p` move between help pages, and `q` quits the help window. The `s` key deserves special attention. After `C-h f some-function`, pressing `s` takes you directly to the source code definition. This is the fastest path from "what does it do?" to "how does it work?" ## 2. The describe family Emacs has 39 `describe-*` commands. The essential eight are covered above. Here are the rest, grouped by what they inspect. ### Code `describe-function` shows the docstring, arglist, type specifier, keybindings, source file, and obsolescence info. `describe-variable` shows the value, docstring, buffer-local status, safe-local-variable status, and custom type. `describe-symbol` combines everything: function docs, variable docs, and face docs for the same symbol. `describe-command` is like `describe-function` but limited to interactive commands. ### Keys and bindings `describe-key` gives full docs for the command bound to a key sequence. `describe-key-briefly` just shows the command name in the echo area. `describe-bindings` lists all active keybindings, covering minor mode, major mode, and global bindings. `describe-keymap` shows all bindings in a specific named keymap. `describe-prefix-bindings` shows what follows after a prefix key, so for example `C-x C-h` shows everything available after `C-x`. This works with any prefix: `C-c C-h`, `M-g C-h`, and so on. `describe-personal-keybindings` shows your custom `bind-key` and `use-package :bind` overrides. ### Modes `describe-mode` shows the current major mode plus all active minor modes with docs. `describe-minor-mode` gives documentation for a specific minor mode. `describe-minor-mode-from-indicator` lets you identify a minor mode from its mode-line lighter, and `describe-minor-mode-from-symbol` does the same by symbol name. ### Text and display This group covers character-level inspection. `describe-char` tells you everything about the character at point: unicode, properties, overlays, face, and font. `describe-text-properties` shows all text properties at point. `describe-face` gives face attributes like foreground, background, font family, and weight. `describe-font` shows low-level font metrics. `describe-fontset` reveals which fonts cover which character ranges. `describe-syntax` shows the syntax table, meaning which characters are treated as words, punctuation, and so on. `describe-categories` shows the character category table. `describe-char-fold-equivalences` shows case-fold equivalences for a character. `describe-buffer-case-table` shows buffer case conversion rules. `describe-current-display-table` shows how characters render. ### International `describe-coding-system` gives encoding details (UTF-8, latin-1, etc.), `describe-current-coding-system` shows the active encoding for the current buffer or process, `describe-input-method` shows how an input method maps keystrokes to characters, `describe-language-environment` covers language support including charsets, encodings, input methods, and samples, and `describe-character-set` shows characters in a specific charset. ### Packages, themes, widgets `describe-package` shows package metadata, dependencies, status, and description. `describe-theme` gives color theme documentation and face assignments. `describe-widget` shows widget and button properties at point. `describe-icon` shows icon properties. ## 3. Apropos, finding what you don't know the name of When you don't know the exact name, apropos searches by pattern. There are 9 apropos commands, each searching different things. `apropos-command` (`C-h a`) searches names of interactive commands. `apropos-documentation` (`C-h d`) searches the documentation strings of all symbols. `apropos-user-option` (available from the menu) searches names of `defcustom` variables. Then there are the `M-x` variants: `apropos` searches names of functions, variables, and faces. `apropos-function` searches function names including non-interactive ones. `apropos-variable` searches all variable names. `apropos-value` searches values of variables and function definitions. `apropos-library` finds symbols defined in a specific library file. And `apropos-local-variable` and `apropos-local-value` search buffer-local variables and their values. ### Pattern syntax A single word like `"buffer"` matches anything with "buffer" in the name. Multiple words like `"buffer read only"` match symbols containing at least two of those words. If the pattern contains `^$*+?.\[`, it's treated as a regular expression. There is also a synonym system, so "find" also matches "open" and "edit" (configurable via `apropos-synonyms`). ### Examples `C-h a font` finds all commands with "font" in their name. `C-h d overlay` finds all symbols whose docstring mentions "overlay". `M-x apropos-library RET python` shows everything defined in python.el. `M-x apropos-value RET "utf-8"` finds all variables whose value contains "utf-8". ### Reading apropos output Each result shows the symbol name (which is clickable), type indicators (`c` for command, `f` for function, `m` for macro, `v` for variable, `u` for user option, `a` for face), key bindings if any, and the first line of the docstring. Click any symbol name to see its full documentation. ## 4. The Info reader, structured manuals Info is the primary documentation format for GNU software. Emacs ships with the complete Emacs Manual and the Emacs Lisp Reference Manual, plus 30+ subsystem manuals. ### Opening Info `C-h i` opens the top-level directory with all manuals. `C-h r` jumps directly to the GNU Emacs Manual. `C-h R` lets you choose a specific manual by name. `C-h F` finds a command's section in the manual. `C-h K` finds a key's section in the manual. `C-h S` looks up a symbol in the language-appropriate manual. ### Navigation In the Info reader, `n` and `p` move to the next and previous node at the same level, `u` goes up to the parent node. `]` and `[` move in reading order, going into children. `m` selects a menu item by name with completion, `f` follows a cross-reference by name. `l` and `r` handle history (back and forward). `SPC` and `DEL` scroll, `.` and `b` go to the beginning of the current node, `d` goes to the top-level Info directory, and `q` quits. ### Searching There are several ways to search within Info. `i` searches the manual's index for a topic, which is the best approach for finding specific items. `,` jumps to the next index match. `I` is a virtual index, searching all index entries with a regexp. `s` does full-text search within the current manual. And `M-x info-apropos` searches indices across all installed Info manuals. Index search (`i`) is better than text search (`s`) because the index is curated, it points to the most relevant section for each topic. Text search finds every mention, which is mostly noise. ### `info-lookup-symbol` (`C-h S`) This one is underused but quite powerful. It's language-aware: in an Elisp buffer, it searches the Elisp Reference Manual index. In a C buffer, it searches the C library manual. In a Python buffer, it searches the Python manual (if installed). So in an Elisp buffer with cursor on `mapcar`, you do `C-h S mapcar RET` and it opens the Elisp manual at the mapcar definition. ### Available manuals Type `C-h R TAB` to see all available manuals. A typical Emacs 30 installation includes Emacs, Elisp, Org, Gnus, Tramp, CC Mode, Eglot, Flymake, use-package, ERT, Calc, Ediff, Info, ERC, Eshell, and many more. ### The Jotain manual Jotain ships its own Info manual (`(jotain)`) alongside the bundled Emacs manuals. `C-h i d m Jotain RET` opens it; it covers installation, the module layout, the Nix build layer, and the full Nix module option reference. `M-x consult-info` includes it in cross-manual search. For NixOS / nix-darwin / Home Manager users the manual is pre-wired via `INFOPATH`; for source checkouts, run `just info` to build `result-info/` and `init-docs.el` picks it up automatically. ## 5. Automatic and contextual help These features show information without you explicitly asking for it. ### ElDoc ElDoc displays function signatures and variable types in the echo area as you type. It's enabled globally by default via `global-eldoc-mode`. So while typing `(mapcar |)`, the echo area shows `mapcar: (FUNCTION SEQUENCE)`. Key settings: `eldoc-idle-delay` controls seconds before showing (default 0.5), `eldoc-echo-area-use-multiline-p` controls multi-line display, and `M-x eldoc-doc-buffer` shows full docs in a dedicated buffer. ### which-key After pressing a prefix key like `C-x`, if you pause, a popup shows all available continuations with descriptions. This is built into Emacs 30. `which-key-idle-delay` controls the seconds before the popup appears (default 1.0), and `which-key-popup-type` controls whether it displays as a minibuffer, side-window, or frame. There are also useful commands: `M-x which-key-show-major-mode` shows all major mode bindings, and `M-x which-key-show-minor-mode-keymap` shows bindings for a specific minor mode. ### Completion annotations When using `M-x`, `C-h f`, or `C-h v` with completion, each candidate shows a type indicator (`c` for command, `f` for function, `m` for macro, `v` for variable, `u` for user option, `a` for face) and the first line of its docstring. This turns completion into a discovery tool, you type a partial name and browse what exists. Jotain narrows the `M-x` candidate list via `read-extended-command-predicate`, set in `init-core` to `command-completion-default-include-p`. A command opts in by listing applicable modes after the `interactive` argument descriptor — `(interactive nil foo-mode)` for a no-argument command, `(interactive "p" foo-mode bar-mode)` for one that reads a prefix argument and applies in either mode — and then only appears in `M-x` when one of those modes is active. Untagged commands are always shown, and typing a full command name still runs it whether or not the filter would hide it. Two stricter predicates ship with Emacs if you want fewer candidates: `command-completion-using-modes-and-keymaps-p` (only commands tied to active modes or active keybindings) and `command-completion-using-modes-p` (only commands explicitly tagged for a mode). ### Help at point `C-h .` (`display-local-help`) shows the `help-echo` text property at point. Many UI elements like buttons, links, and mode-line indicators have this property. ## 6. Source code navigation When documentation isn't enough, read the source. ### find-function family `find-function` opens the source definition of a function, and `find-function-other-window` does the same in another window. `find-variable` and `find-variable-other-window` do the same for variables. `find-face-definition` finds face source definitions. `find-library` opens a library file by name. `find-function-on-key` finds the source of the command bound to a key. `find-function-at-point` and `find-variable-at-point` find the source of whatever symbol or variable is at point. As a shortcut from Help: in any `*Help*` buffer, press `s` to jump to source. ### xref (cross-reference framework) `M-.` (`xref-find-definitions`) jumps to the definition of the symbol at point. `M-?` (`xref-find-references`) finds all references to the symbol at point. `M-,` (`xref-go-back`) goes back after jumping, and `C-M-,` (`xref-go-forward`) goes forward. `M-x xref-find-apropos` finds definitions matching a regexp. xref works across languages. It uses etags, Eglot/LSP, or grep depending on what's available. ## 7. Shortdoc, function groups by topic `M-x shortdoc` shows common functions organized by topic with live examples and expected output. Available groups in Emacs 30 include: `string` (string manipulation), `list` (list operations), `alist` (association lists), `sequence` (generic sequences with `seq-map`, `seq-filter`, `seq-reduce`, etc.), `number` (arithmetic), `vector`, `regexp` (regular expressions), `buffer` (buffer manipulation), `process` (process management), `file-name` (filename operations), `file` (file I/O), `hash-table`, `keymaps`, `text-properties`, `overlay`, `symbol`, `comparison`, and `map` (the map library). Each entry shows the function signature, a brief description, and a live example. ## 8. Customization browser `M-x customize` is not just for configuration, it's also a documentation browser. Every `defcustom` variable shows its type, valid values, default, and a full description. `customize` shows top-level customization groups. `customize-browse` gives you a hierarchical tree of all option groups. `customize-group` shows all options in a specific group. `customize-variable` gives a single variable with full docs and type. `customize-face` shows a face with preview and attribute editor. `customize-apropos` finds options matching a pattern, and there are also `customize-apropos-faces` and `customize-apropos-groups` for faces and groups specifically. `customize-browse` is particularly useful for discovery. It lets you explore what Emacs can do by browsing option hierarchies. Categories like `programming`, `faces`, `editing`, and `convenience` reveal features you never knew existed. ## 9. Search-based discovery When you're looking for a needle in a haystack, search. ### In the current buffer `C-s` (`isearch-forward`) does incremental search, `C-r` (`isearch-backward`) does reverse incremental search, `C-M-s` (`isearch-forward-regexp`) does regexp search, and `M-x occur` shows all lines matching a regexp in a clickable buffer. ### Across files `M-x grep` runs grep with results in a clickable buffer. `M-x rgrep` does recursive grep with directory and file filtering. `M-x lgrep` greps in a single directory. `C-x p g` (`project-find-regexp`) searches across the current project, and `C-x p f` (`project-find-file`) finds files by name in the project. ### Across buffers `multi-occur` runs `occur` across multiple buffers, and `multi-occur-in-matching-buffers` runs `occur` in buffers matching a name regexp. ## 10. External documentation inside Emacs ### Unix manual pages `M-x man` views a Unix man page (requires external `man` command), and `M-x woman` does the same without external dependencies, the name stands for "WithOut Man". Both give hyperlinked, formatted output inside Emacs. ### Web browsing `M-x eww` opens a URL or searches the web inside Emacs. `M-x eww-open-file` opens a local HTML file. ### Package discovery `C-h p` (`finder-by-keyword`) browses packages by category keyword. `M-x list-packages` shows a tabulated list of all available packages. `C-h P` (`describe-package`) gives full documentation for a specific package. ## 11. Runtime introspection ### Evaluate any expression `M-:` (`eval-expression`) lets you evaluate any Elisp expression and see the result. This is the most powerful introspection tool, you can query anything. ```elisp theme={} ;; What mode is this buffer in? M-: major-mode RET ;; What's the value of a variable? M-: tab-width RET ;; What key runs a command? M-: (where-is-internal 'save-buffer) RET ;; What command is on a key? M-: (key-binding (kbd "C-x C-s")) RET ``` ### Debugging as discovery `debug-on-entry` lets you step through a function, seeing every call and return value. `debug-on-variable-change` breaks when a variable changes, so you can find who's mutating state. `trace-function` logs every call to a function with arguments and return values. `edebug-defun` instruments a function for step-by-step execution with breakpoints. And `profiler-start` / `profiler-report` give you CPU and memory profiling to find what's slow. ### Observing recent activity `C-h l` (`view-lossage`) shows the last 300 keystrokes and commands. `C-h e` (`view-echo-area-messages`) shows the message log with recent `message` calls. ## 12. Elisp introspection functions These are the building blocks. Use them in `M-:`, in `*scratch*`, or in batch mode. ### Documentation ```elisp theme={} ;; Get a function's docstring (documentation 'mapcar t) ;; Get a variable's docstring (documentation-property 'tab-width 'variable-documentation) ;; Get the arglist of a function (help-function-arglist 'mapcar) ;; → (FUNCTION SEQUENCE) ``` ### Finding where things come from ```elisp theme={} ;; Which file defines a function? (symbol-file 'python-mode 'defun) ;; → "python" ;; Which file defines a variable? (symbol-file 'tab-width 'defvar) ;; Is this a C primitive or Elisp? (subrp (symbol-function 'forward-char)) ;; → t (C) (subrp (symbol-function 'forward-word)) ;; → nil (Elisp) ;; Is it native-compiled? (subr-native-elisp-p (symbol-function 'forward-word)) ;; Where is a library file? (locate-library "org") ;; → "/path/to/org.elc" ``` ### Discovering API surfaces ```elisp theme={} ;; Find all functions with a prefix (apropos-internal "^font-lock-" #'functionp) ;; Find all commands with a prefix (apropos-internal "^describe-" #'commandp) ;; Find all user options in a prefix (apropos-internal "^org-" #'custom-variable-p) ``` ### Key binding introspection ```elisp theme={} ;; What command is on a key? (key-binding (kbd "C-x C-f")) ;; → find-file ;; What keys run a command? (where-is-internal 'find-file) ;; → ([24 6] [open] ...) ;; Human-readable: (mapconcat #'key-description (where-is-internal 'find-file) ", ") ;; → "C-x C-f, , ..." ``` ### Symbol properties ```elisp theme={} ;; The property list reveals metadata the docs don't mention: (symbol-plist 'font-lock-mode) ;; Properties like: safe-local-variable, permanent-local, risky-local-variable, ;; side-effect-free, byte-compile, compiler-macro ;; Check if a variable is safe as a file-local variable (get 'tab-width 'safe-local-variable) ;; Get the customize type (shows all valid values) (require 'cus-edit) (custom-variable-type 'font-lock-maximum-decoration) ``` ### Feature and capability testing ```elisp theme={} ;; Does this Emacs have native compilation? (featurep 'native-compile) ;; Does it have tree-sitter? (featurep 'treesit) ;; What features are loaded? features ;; → (rmc iso-transl tooltip ...) ;; Build configuration system-configuration-options emacs-version system-type ``` ### Simulating a file open ```elisp theme={} ;; What mode + minor modes activate for a .py file? (with-temp-buffer (setq buffer-file-name "/tmp/test.py") (set-auto-mode) (cons major-mode (seq-filter (lambda (m) (and (boundp m) (symbol-value m))) minor-mode-list))) ``` ## 13. Accessing documentation from outside Emacs For scripts, CI, and AI agents. Two approaches: batch mode (standalone) and emacsclient (persistent). ### `emacs --batch --eval` This starts a fresh Emacs, evaluates code, and exits. No GUI, no user config, no persistent state. ```bash theme={} # Get a function's documentation emacs --batch --eval '(princ (documentation '\''mapcar t))' 2>/dev/null # Find what command a key runs emacs --batch --eval '(princ (key-binding (kbd "C-x C-f")))' 2>/dev/null # Find what keys run a command emacs --batch --eval '(princ (mapconcat #'\''key-description (where-is-internal '\''find-file) ", "))' 2>/dev/null # List all functions matching a prefix emacs --batch --eval '(princ (mapconcat #'\''symbol-name (apropos-internal "^font-lock-" #'\''functionp) "\n"))' 2>/dev/null # Get a function's arglist emacs --batch --eval '(princ (help-function-arglist '\''make-overlay))' 2>/dev/null # Find where a function is defined emacs --batch --eval '(princ (symbol-file '\''python-mode '\''defun))' 2>/dev/null # Check if a feature exists emacs --batch --eval '(princ (featurep '\''native-compile))' 2>/dev/null # Get build configuration emacs --batch --eval '(princ system-configuration-options)' 2>/dev/null # Simulate opening a file and list active modes emacs --batch --eval '(progn (with-temp-buffer (setq buffer-file-name "/tmp/test.py") (set-auto-mode) (let ((modes (list major-mode))) (dolist (m minor-mode-list) (when (and (boundp m) (symbol-value m)) (push m modes))) (princ (mapconcat #'\''symbol-name modes "\n")))))' 2>/dev/null # Load a library first, then query emacs --batch --eval '(progn (require '\''auth-source) (princ (documentation '\''auth-source-search t)))' 2>/dev/null ``` ### Temp file pattern for large output When output would be too large for stdout, write to a file: ```bash theme={} emacs --batch --eval '(with-temp-file "/tmp/emacs-result.txt" (dolist (fn (apropos-internal "^overlay-" #'\''functionp)) (insert (format "%s: %s\n\n" fn (documentation fn)))))' 2>/dev/null # Then read it cat /tmp/emacs-result.txt ``` ### `emacsclient --eval` This connects to a running Emacs server. It's faster since there is no cold start, and it has access to user config and loaded state. ```bash theme={} # Requires: M-x server-start or emacs --daemon first emacsclient --eval '(documentation '\''mapcar t)' emacsclient --eval '(buffer-list)' ``` ### Comparison `emacs --batch --eval` takes about 0.5-1.0s to start (cold), doesn't require a running server, has no state persistence between calls, doesn't load user config, and is best for scripts, CI, and standalone queries. `emacsclient --eval` starts in about 0.1s (warm), requires a running server, shares state with the running Emacs, has user config loaded, and is best for interactive workflows where you want to leverage existing state. ### Limitations of batch mode There is no state between calls, so each invocation is a fresh process. If you need to chain operations, use `(progn ...)`. There is no GUI, so functions that require interactive input will silently fail. The cold start adds about 0.5-1.0s overhead per call, so for many queries it's better to use one `emacs --batch` invocation with multiple operations. Output goes to stdout/stderr, use `princ` for stdout and `2>/dev/null` to suppress startup messages. Libraries are not loaded by default, so use `(require 'library)` or `--load file.el` for non-default libraries. ## 14. What ships with Emacs ### On-disk reference materials `C-h t` is the interactive tutorial available in 20+ languages. `C-h n` shows the release notes (stored in `etc/NEWS` through `etc/NEWS.30`). `C-h C-f` is the FAQ. `C-h C-p` covers known issues and workarounds. `C-h C-d` explains how to debug Emacs with GDB. `C-h C-a` shows version, license, and credits. `C-h h` shows character set samples in many scripts. And there are printable quick-reference cards in `etc/refcards/`. ### The source code as documentation Emacs Lisp source files serve as documentation through three mechanisms. Commentary sections at the top of each file explain the module's purpose and design: ```elisp theme={} ;;; Commentary: ;; A grab-bag of basic Emacs commands not specifically related to some ;; major mode or to file-handling. ``` Find them with `M-x find-library` then search for `;;; Commentary:`. Docstrings on every function and variable are accessible via `C-h f` and `C-h v`, or programmatically via `(documentation 'symbol t)`. Defcustom declarations show the type, valid values, and group for every user option: ```elisp theme={} (defcustom make-backup-files t "Non-nil means make a backup of a file the first time it is saved. This can be done by renaming the file or by copying. ..." :type 'boolean :group 'backup) ``` ### Key source directories The `lisp/` directory contains the Elisp standard library (1,587 `.el` files). Under it, `lisp/progmodes/` has programming language modes like `python.el` and `js.el`, `lisp/emacs-lisp/` has the Elisp language infrastructure (byte-compiler, cl-lib, seq, map), and `lisp/net/` has networking code (Tramp, EWW, URL). `lisp/org/` contains Org mode. The `src/` directory has the C core, covering the Lisp interpreter, display engine, and buffer management. `doc/emacs/` has the Emacs Manual texinfo sources, `doc/lispref/` has the Elisp Reference texinfo sources, and `doc/misc/` has 70+ subsystem manual texinfo sources. `etc/NEWS*` has the version-by-version changelog. ## Quick reference: "How do I find out about...?" What a function does: `C-h f function-name`. What a variable controls: `C-h v variable-name`. What a key does: `C-h k` then press the key. What key runs a command: `C-h w command-name`. What this mode provides: `C-h m`. All keybindings right now: `C-h b`. Everything about a symbol: `C-h o symbol-name`. Functions you can't name: `C-h a partial-name` or `C-h d keyword`. Read the manual on a topic: `C-h i` then `i topic`. Find a command in the manual: `C-h F command-name`. How a function is implemented: `C-h f function-name` then `s` to jump to source. What functions exist for X: `M-x shortdoc` or `M-: (apropos-internal "^prefix-" #'functionp)`. What options exist for X: `M-x customize-group` or `M-x customize-apropos`. What packages do X: `C-h p` or `M-x list-packages`. What changed in this version: `C-h n`. A Unix command's manual: `M-x man` or `M-x woman`. What just happened: `C-h l` for keystrokes or `C-h e` for messages. From a script or AI agent: `emacs --batch --eval '(princ (documentation '\''fn t))' 2>/dev/null`. # Inspiration & Resources Source: https://docs.jylhis.com/jotain/inspiration Links to Emacs builds, Nix resources, and related projects ## Emacs Source: git://git.sv.gnu.org/emacs.git ## Linux * [https://github.com/NixOS/nixpkgs/tree/master/pkgs/applications/editors](https://github.com/NixOS/nixpkgs/tree/master/pkgs/applications/editors) * [https://github.com/nix-community/emacs-overlay](https://github.com/nix-community/emacs-overlay) ## MacOS * [https://github.com/d12frosted/homebrew-emacs-plus](https://github.com/d12frosted/homebrew-emacs-plus) * [https://github.com/caldwell/build-emacs/tree/master](https://github.com/caldwell/build-emacs/tree/master) * [https://github.com/sban/emacs](https://github.com/sban/emacs) ## Android * [https://mstempl.netlify.app/post/emacs-on-android/](https://mstempl.netlify.app/post/emacs-on-android/) * [https://github.com/nix-community/nix-on-droid/tree/master/pkgs](https://github.com/nix-community/nix-on-droid/tree/master/pkgs) * [https://www.gnu.org/software/emacs/manual/html\_node/emacs/Android.html](https://www.gnu.org/software/emacs/manual/html_node/emacs/Android.html) * [https://f-droid.org/packages/org.gnu.emacs/](https://f-droid.org/packages/org.gnu.emacs/) * [https://sourceforge.net/projects/android-ports-for-gnu-emacs/files/](https://sourceforge.net/projects/android-ports-for-gnu-emacs/files/) * [https://github.com/mariusk/android-with-emacs](https://github.com/mariusk/android-with-emacs) * [https://github.com/zakkak/android-emacs-toolkit](https://github.com/zakkak/android-emacs-toolkit) ## Nix * [https://wiki.nixos.org/wiki/Emacs](https://wiki.nixos.org/wiki/Emacs) ## Keybindings — considered and not adopted * [general.el to conquer Emacs keybindings](https://bicycleforyourmind.com/generaldotel-to-conquer-emacs-keybindings) — argues for a leader-key system built on [`general.el`](https://github.com/noctuid/general.el). Researched against the built-in alternatives and **rejected** for Jotain: `general.el`'s primary differentiator is `:states` for evil-mode integration, which Jotain does not use. Emacs 30's `keymap-set`, built-in `which-key`, and use-package `:bind` already cover the article's discoverability and leader-key goals without a new dependency. The article's spirit — memorable, categorised, self-documenting prefixes — is instead realised through `which-key-add-key-based-replacements` registered in `init-keys.el`, with the per-package bindings staying colocated with their `use-package` blocks. See [Keybindings](/keybindings) for the resulting namespace map. # Installation Source: https://docs.jylhis.com/jotain/installation How to build and install Jotain # Installation Jotain uses [Nix](https://nixos.org/) to build Emacs from source, driven by a `Justfile` task runner. Development assumes a [devenv](https://devenv.sh) shell — enter it with `devenv shell` (no `.envrc` is tracked; direnv users can create their own). ## Prerequisites * [Nix package manager](https://nixos.org/download) with `nix-build` available * [just](https://github.com/casey/just) task runner (provided by the devenv shell) * [devenv](https://devenv.sh) (optional but recommended for development) All `just` recipes assume the devenv shell is active. If you do not use direnv, prefix any command with `devenv shell --`, e.g. `devenv shell -- just check`. ## Building Emacs The default build targets the current system and includes every tree-sitter grammar from nixpkgs: ```bash theme={} just build ``` This runs plain `nix-build`, which evaluates `default.nix` — a thin flake-compat wrapper around `flake.nix` — and builds `packages..default`: the full `jotainEmacsPackages` distribution (Emacs + use-package-scanned packages + tree-sitter grammars + the Info manual) assembled by `nix/mk-overlay.nix`. The distribution's Emacs is the emacs-overlay `unstable` variant (the Emacs 31 release branch); thanks to the cache-parity invariant in `emacs.nix` its store path matches [`nix-community/emacs-overlay`](https://github.com/nix-community/emacs-overlay)'s prebuilt `emacs-unstable`, so the base Emacs is a binary-cache hit from `nix-community.cachix.org` (and the `mainline` variant from Hydra) — nothing recompiles from source. ### Build Matrix Jotain ships exactly four builds — × : on Linux a pgtk/Wayland GUI and a terminal-only build; on Darwin a patched NS/Cocoa GUI (nix-giant system-appearance and round-undecorated-frame patches applied by default — always built from source) and a terminal-only build. X11/Lucid/GTK3-x11/Motif/Athena and the macport fork are not supported; `emacs.nix` asserts those configurations away. ```bash theme={} just build # full distribution: Emacs 31 (unstable) + every grammar (default) just build-nox-full # full terminal-only distribution (same attr the nix-on-droid module ships) just build-bare # bare Emacs from emacs.nix — the platform's matrix GUI just build-nox # --without-x --without-ns (terminal only, bare) just build-git # bleeding-edge master from git.savannah.gnu.org just build-igc # feature/igc3 MPS incremental GC branch just build-igc-ccache # igc via ccacheStdenv (for from-source platforms; see emacs.nix) just build-android # bare aarch64-linux nox — kept for cache-parity testing ``` Or call `nix-build` directly — `default.nix` exposes the flake packages, and the bare-Emacs builds target `emacs.nix` with any argument the file accepts: ```bash theme={} nix-build # full distribution nix-build -A emacs # bare Emacs only nix-build emacs.nix --arg withNativeCompilation false # no native-comp nix-build emacs.nix --arg variant '"git"' # master nix-build emacs.nix --arg variant '"igc"' # MPS GC branch ``` `git`/`unstable`/`igc` build the revision pinned by emacs-overlay and are binary-cache hits on Linux and for the terminal-only Darwin build. The Darwin GUI is patched by default and therefore always compiles from source. Only when pinning a custom commit via `--argstr rev "..."` does the first build fail and report the expected hash to pass back via `--argstr hash "sha256-..."`. ## Installing from a Consumer Flake Jotain exposes Home Manager, NixOS, and nix-darwin modules. They all install the cache-friendly `emacs.nix` build by default: ```nix theme={} { imports = [ inputs.jotain.homeManagerModules.default ]; services.jotain.enable = true; } ``` `services.jotain.package` swaps in any other Jotain-shaped distribution — for example the terminal-only build: ```nix theme={} { imports = [ inputs.jotain.homeManagerModules.default ]; services.jotain = { enable = true; package = inputs.jotain.packages.${system}.emacs-nox; }; } ``` ### nix-on-droid (Android) Jotain also ships a [nix-on-droid](https://github.com/nix-community/nix-on-droid) module for running Emacs on Android (Termux/proot). Android is headless under proot, so the module installs a **terminal-only** (`-nw`) Emacs into `environment.packages` and wires `EDITOR`/`VISUAL` to an `emacsclient` wrapper — there is no systemd daemon, launchd agent, or GUI frame. ```nix theme={} { imports = [ jotain.nixOnDroidModules.default ]; services.jotain.enable = true; } ``` Switch it in with `nix-on-droid switch --flake .#default`. See `nixOnDroidConfigurations.default` in Jotain's `flake.nix` for a complete example wiring. Like the NixOS / nix-darwin module (`module-system.nix`), this module installs the **curated Jotain Emacs package** — Jotain's Emacs packages, tree-sitter grammars, themes, and Info manual are on the `load-path` — but it does not install Jotain's own `early-init.el` / `init.el` / `lisp/`. To have Emacs boot the full Jotain configuration, point it at the config with `--init-directory` (the way `just run-built` does) or layer the Home Manager module through nix-on-droid's `home-manager.config`, which installs the config into a writable `~/.config/emacs`. A bare `--init-directory` into the read-only Nix store will not work, because Jotain writes `var/`, `elpa/`, and `eln-cache/` under `user-emacs-directory`. ### Overriding nixpkgs Downstream flakes may pin a different nixpkgs (release branches **24.05+** through unstable) by following the input: ```nix theme={} inputs.jotain.inputs.nixpkgs.follows = "nixpkgs"; ``` `emacs.nix` gates the Emacs build flags that newer `make-emacs.nix` versions introduced, so the modules still evaluate and build on older releases. Note that on 24.05 `pkgs.emacs` is Emacs 29 while Jotain's Elisp targets Emacs 30/31 — the build succeeds, but full runtime behaviour is only guaranteed on the pinned unstable. ## Running Jotain is designed to be launched out of its own checkout via `--init-directory`, so it never touches `~/.emacs.d`. ```bash theme={} just run-built # build for this platform, then launch result/bin/emacs just run-built-debug # same, with --debug-init and debug-on-error ``` The devenv shell provides tooling only — Emacs itself is **not** in the shell, and the direct-launch recipes (`just run`, `just debug`, `just tty`, …) are disabled stubs. See the current-state note in [Launching Emacs](/usage/launching) for details and the daemon + client pattern. # Introduction Source: https://docs.jylhis.com/jotain/introduction Jotain — a custom GNU Emacs configuration built from scratch # Jotain **Jotain** is a custom GNU Emacs configuration built from scratch. The name is Finnish for "something." The project includes: * A modular Emacs Lisp configuration organised by concern (`lisp/init-*.el`) * A Nix expression (`emacs.nix`) for building GNU Emacs from source, with every upstream build flag exposed and `pkgs.emacs` (nixpkgs) binary-cache parity as an invariant; git-based variants come from [`nix-community/emacs-overlay`](https://github.com/nix-community/emacs-overlay) * A distribution (`nix/mk-overlay.nix`, exposed via `flake.nix` and the `default.nix` flake-compat shim) that ships Emacs together with all \~275 tree-sitter grammars from nixpkgs * A development shell (`devenv.nix`) with linters, language servers, and docs tooling (Emacs itself is not in the shell — launch it with `just run-built`) * A home-manager service (`module.nix`) for running Jotain as a user daemon with `emacsclient` * A `Justfile` task runner covering check, build, launch (`just run-built`), pins, and cleanup ## Why Jotain? Jotain takes a different approach from framework-based Emacs configurations like Doom or Spacemacs. Instead of layering on top of an opinionated framework, Jotain is built from scratch with: * **Nix-based builds** — reproducible Emacs from source with precise control over compile options, and binary-cache parity with `pkgs.emacs` (nixpkgs) for the default configuration. * **Modular Elisp** — one file per concern (`init-ui.el`, `init-completion.el`, `init-vc.el`, …). A package that only exists to enhance a built-in (e.g. `dirvish` → `dired`, `magit` → `vc`) lives in the same file as the built-in it enhances — there is no "builtins.el" / "third-party.el" split. * **Modern Emacs** — an Emacs 31 configuration (default build: Emacs 31.1 via emacs-overlay; floor: `emacs "30.1"`), with native compilation, tree-sitter modes, lexical binding throughout, and `use-package` built-in. * **`setopt` by default** — user options (`defcustom`) are set via `setopt` so `:set` callbacks and type validation actually run. ## Source Code Jotain is open source and available on [GitHub](https://github.com/Jylhis/jotain). # Keybindings Source: https://docs.jylhis.com/jotain/keybindings The Jotain keybinding namespace, organised by prefix Jotain follows Emacs conventions: only **global** rebindings live in [`lisp/init-keys.el`](https://github.com/Jylhis/jotain/blob/main/lisp/init-keys.el); per-package bindings stay colocated with the package's `use-package` block. [`which-key`](https://github.com/justbur/emacs-which-key) is enabled by default, so after any prefix key a paged menu of available continuations appears in the echo area. The prefix labels listed below are registered via `which-key-add-key-based-replacements` in `init-keys.el`, so they show up in those menus instead of the bare command names. ## `C-c` — user namespace Reserved by Emacs convention for user bindings. Jotain claims the following: | Key | Label | Command | | --------- | ------------------------- | --------------------------------------------------------------------------------- | | `C-c a` | org-agenda | `org-agenda` | | `C-c c` | org-capture | `org-capture` | | `C-c d` | dirvish | `dirvish` | | `C-c D` | dirvish-side | `dirvish-side` | | `C-c e` | eca | `eca` | | `C-c g` | magit-file | `magit-file-dispatch` | | `C-c h` | consult-history | `consult-history` | | `C-c i` | consult-info | `consult-info` | | `C-c j` | jujutsu | `majutsu-log` | | `C-c M-j` | jujutsu-dispatch | `majutsu-dispatch` | | `C-c k` | consult-kmacro | `consult-kmacro` | | `C-c l` | org-store-link | `org-store-link` | | `C-c m` | — | `consult-man` | | `C-c n` | **org-roam** prefix | `n f` find, `n i` insert, `n c` capture | | `C-c o` | **combobulate** prefix | structural editing dispatch | | `C-c q` | claude-code-ide | `claude-code-ide-menu` | | `C-c r` | **eglot-refactor** prefix | `r r` rename, `r f` format, `r a` actions, `r o` organize-imports, `r q` quickfix | | `C-c s` | gptel-send | `gptel-send` | | `C-c S` | gptel-menu | `gptel-menu` | | `C-c t` | toggle-theme | `auto-dark-toggle-appearance` | | `C-c v` | devenv | `devenv` transient — tasks, scripts, processes, env | | `C-c M-x` | consult-mode-command | `consult-mode-command` | ## `C-x` — control namespace Built-in Emacs prefix extended with a handful of Jotain additions: | Key | Label | Command | | --------- | ------------------------------- | -------------------------------------------------------------------------- | | `C-x C-a` | **dape (debug)** prefix | `dape-global-map` — Debug Adapter Protocol: start, step, breakpoints, REPL | | `C-x b` | — | `consult-buffer` | | `C-x g` | magit-status | `magit-status` | | `C-x G` | git-status-file | `jotain-switch-git-status-buffer` | | `C-x M-g` | magit-dispatch | `magit-dispatch` | | `C-x j` | rotate-window-split | `jotain-toggle-window-split` | | `C-x J` | jj-status-file | `jotain-switch-jj-status-buffer` | | `C-x p b` | — | `consult-project-buffer` | | `C-x r b` | — | `consult-bookmark` | | `C-x t b` | — | `consult-buffer-other-tab` | | `C-x u` | vundo | `vundo` | | `C-x 1` | — | `jotain-nav-toggle-delete-other-windows` | | `C-x P` | **project (projection)** prefix | `projection-map` — multi-language project commands | ## Other global bindings Single-keystroke globals that don't fit a `C-c` / `C-x` namespace — window switching, spell-check, region growth, and multi-cursor. | Key | Command | | ------------- | ----------------------------------------------------- | | `M-o` | `other-window` | | `M-$` | `jinx-correct` | | `C-M-$` | `jinx-languages` | | `C-=` | `expreg-expand` | | `C->` | `mc/mark-next-like-this` | | `C-<` | `mc/mark-previous-like-this` | | `C-S-c C-S-c` | `mc/mark-all-like-this` | | `C-h .` | `helpful-at-point` (via `[remap display-local-help]`) | | `C-.` | `embark-act` | | `C-;` | `embark-dwim` | | `C-h B` | `embark-bindings` | ## Disabled by default | Key | Reason | | --------- | --------------------------------------------------------------------- | | `C-z` | Accidentally suspending Emacs from a GUI frame is never what you want | | `C-x C-z` | Same | ## Mode-local overrides A few major modes rebind `C-c` leaves locally: * `dired-mode`: `C-c C-e` → `wdired-change-to-wdired-mode` (toggle writable dired) * `sops-mode`: `C-c C-c` save, `C-c C-k` cancel, `C-c C-d` edit * `embark` exports: `C-c C-o` → `embark-export` * `org-mode`: `C-c b` is an Org Babel notebook prefix — `b` run buffer, `e` run subtree, `r` restart session and run buffer, `s` switch to session, `k` clear results, `t` tangle. Single blocks stay on `C-c C-c`, and Org's own `C-c C-v` babel map is untouched. See [Notebooks with Org Babel](/usage/notebooks). ## Embark action menu `C-.` (`embark-act`) opens a keyboard-driven "right-click" menu of the actions that apply to the thing at point — a minibuffer candidate, a file name, a buffer, a URL, an Elisp symbol, the active region. `C-;` (`embark-dwim`) skips the menu and runs the default action. The keys below are pressed *after* `C-.`, inside the action menu: | Key | Action | | ----- | ----------------------------------------------------------------------------------------------------------------------------------------------- | | `C-h` | Searchable `completing-read` of every applicable action — the discovery entry point | | `E` | `embark-export` — promote the candidate set to a buffer (grep for ripgrep/grep, occur for `consult-line`, dired for files, ibuffer for buffers) | | `A` | `embark-act-all` — run one action on *every* current candidate | | `B` | `embark-become` — re-run the input you typed through a different command | | `i` | Insert the candidate into the buffer you came from | | `w` | Copy the candidate to the kill-ring | Two habits worth forming: * **Search → edit.** `consult-ripgrep` → `C-. E` exports to a grep buffer, then `C-x C-q` (`wgrep`) makes it editable so you can rewrite every match in place and save across all files at once. * **Keep the session alive.** Acting on a candidate exits the minibuffer by default; call it with a prefix (`C-u C-.`) to stay in the minibuffer for successive actions — handy for deleting or renaming several files in a row. Because Embark resolves candidate paths through `project.el`, the full file-action arsenal (`w` copy path, `r` rename, `d` delete, `j` jump to dired) works directly on the project-relative candidates of `project-find-file` without a dired detour. `embark-become`'s file map already offers `p` for `project-find-file`, so `C-. B p` re-runs a plain `C-x C-f` input through the project finder. ## Window navigation Shift + arrow keys move focus between split windows, courtesy of the built-in `windmove-default-keybindings`. `M-o` cycles through windows linearly; `C-x j` rotates a two-window layout between horizontal and vertical splits. ## Why no leader-key framework? The article ["general.el to conquer Emacs keybindings"](https://bicycleforyourmind.com/generaldotel-to-conquer-emacs-keybindings) makes a persuasive case for adopting [`general.el`](https://github.com/noctuid/general.el) as a unified keybinding layer. Jotain considered and rejected the migration: `general.el`'s primary differentiator is `:states` for evil-mode integration, which Jotain does not use, and Emacs 30's `keymap-set` plus built-in `which-key` plus use-package `:bind` already cover the article's discoverability and leader-key ergonomics without an extra dependency. The *spirit* of the article — memorable, categorised, self-documenting prefixes — is realised here through the `which-key-add-key-based-replacements` table in `init-keys.el`. See [Inspiration & Resources](/inspiration) for the full rationale. # Quickstart Source: https://docs.jylhis.com/jotain/quickstart Get up and running with Jotain in minutes # Quickstart ## Clone the repository ```bash theme={} git clone https://github.com/Jylhis/jotain.git cd jotain ``` ## Enter the development shell Jotain ships a [devenv](https://devenv.sh) shell that provides the tooling — `just`, `nil`, `nixfmt-rfc-style`, `treefmt`, Nix linters, the language servers the config shells out to, and the fonts the UI config looks up. Emacs itself is **not** in the shell (see [Launching Emacs](/usage/launching)); it is built via Nix on demand. With `direnv` installed the shell activates automatically; otherwise enter it manually: ```bash theme={} devenv shell ``` ## Build and run Emacs with the Jotain configuration ```bash theme={} just run-built # build for the current platform, then launch result/bin/emacs just run-built-debug # same, with --debug-init and debug-on-error ``` `just run-built` builds the full distribution (Emacs 31 + packages + tree-sitter grammars — usually a binary-cache pull, not a compile) and launches `./result/bin/emacs` with `--init-directory=`, so it never touches `~/.emacs.d`. The first launch bootstraps any package not provided by Nix from MELPA; subsequent launches load from the package cache. The direct-launch recipes (`just run`, `just debug`, `just tty`, `just daemon`, `just client`, `just client-tty`, `just quick`) are currently disabled stubs — see the note in [Launching Emacs](/usage/launching), which also covers the daemon + client pattern and the `emd` / `em` / `emg` shell aliases via `services.jotain.shellAliases`. ## Sanity-check the configuration ```bash theme={} just check # nix flake check: package builds, Nix linting, docs checks, # elisp paren check, byte-compile (warnings as errors), ERT tests just test # build only the elisp-test flake check (ERT tests under test/) just fmt # nix fmt — treefmt with nixfmt-rfc-style, deadnix, statix ``` ## Clean build artifacts ```bash theme={} just clean # .elc files, autosaves, eln-cache, result/ just clean-all # also elpa/, var/, .dev-home/ — forces a full re-fetch ``` ## Update pinned sources `flake.lock` is the single source of truth for input revs; `devenv.yaml` mirrors the shared ones (`nixpkgs`, `treefmt-nix`, `emacs-overlay`). ```bash theme={} just update # update every flake input, then sync devenv.yaml + devenv.lock just verify # assert flake.lock and devenv.lock agree on every shared input ``` # Language Support Source: https://docs.jylhis.com/jotain/reference/language-support Which editor features Jotain wires up for each supported language # Language Support Jotain gives every programming language the same **baseline** the moment its buffer opens — no language server required: * **Completion** — in-buffer completion via corfu + cape (dabbrev, file, keyword). * **Diagnostics** — on-the-fly checking via flymake. * **Documentation** — echo-area docs via eldoc. * **Navigation** — xref, imenu, tree-sitter code folding, and a breadcrumb header line. * **Formatting on save** — via apheleia, where a formatter is configured (below). * **Editing hygiene** — editorconfig, indentation detection (dtrt-indent), and snippets (tempel). When a language server is on the project's `PATH` (Jotain resolves servers from the project's own devenv/environment, not from this config), eglot starts automatically and **upgrades** completion, documentation, navigation, and diagnostics to semantic LSP, and adds go-to-definition into dependencies, find-references, rename, code actions, and workspace-symbol search. Debugging is provided by dape for the languages with a DAP adapter below. The table lists the language-specific wiring on top of that baseline. It is the *declared* standard from the language registry; the live, ground-truth matrix (actual mode routing and which servers are bundled/available) is produced by `nix build .#lang-eval-matrix`, and a live end-to-end LSP probe by `nix build .#lang-eval-live`. | Language | Major mode | Tree-sitter | Language server | Formatter | Debug (DAP) | Snippets | Inlay | Live-probed | | ---------------- | ------------------------- | ----------- | ------------------------------------------------------------ | -------------- | ----------- | -------- | ----- | ----------- | | Nix | `nix-ts-mode` | ✓ | `nixd` / `nil` | `nixfmt` | — | — | ✓ | ✓ | | Python | `python-ts-mode` | ✓ | `basedpyright-langserver` / `pyright-langserver` / `pylsp` ᵉ | `ruff` | `debugpy` | ✓ | ✓ | ✓ | | Rust | `rust-ts-mode` | ✓ | `rust-analyzer` | `rustfmt` | `codelldb` | ✓ | ✓ | — | | Go | `go-ts-mode` | ✓ | `gopls` ᵉ | `goimports` | `dlv` | ✓ | ✓ | ✓ | | TypeScript | `typescript-ts-mode` | ✓ | `typescript-language-server` ᵉ | `prettier` | — | ✓ | ✓ | ✓ | | TSX | `tsx-ts-mode` | ✓ | `typescript-language-server` ᵉ | `prettier` | — | ✓ | ✓ | — | | JavaScript | `js-ts-mode` | ✓ | `typescript-language-server` ᵉ | `prettier` | — | ✓ | — | — | | CSS / SCSS | `css-ts-mode` | ✓ | `vscode-css-language-server` | `prettier` | — | — | — | — | | C | `c-ts-mode` | ✓ | `clangd` ᵉ | `clang-format` | `codelldb` | ✓ | ✓ | — | | C++ | `c++-ts-mode` | ✓ | `clangd` ᵉ | `clang-format` | `codelldb` | ✓ | ✓ | — | | CUDA | `cuda-ts-mode` | ✓ | `clangd` ᵉ | `clang-format` | `codelldb` | ✓ | ✓ | — | | Bash / shell | `bash-ts-mode` | ✓ | `bash-language-server` | `shfmt` | — | — | — | ✓ | | Dockerfile | `dockerfile-ts-mode` | ✓ | `docker-langserver` ᵉ | — | — | — | — | ✓ | | YAML | `yaml-ts-mode` | ✓ | `yaml-language-server` | `prettier` | — | — | — | ✓ | | JSON | `json-ts-mode` | ✓ | `vscode-json-language-server` | `prettier` | — | — | — | — | | TOML | `toml-ts-mode` | ✓ | `taplo` | — | — | — | — | — | | Zig | `zig-ts-mode` | ✓ | `zls` | `zig` | — | — | ✓ | — | | OCaml | `neocaml-mode` | ✓ | `ocamllsp` ᵉ | `ocamlformat` | — | — | ✓ | — | | Haskell | `haskell-ts-mode` | ✓ | `haskell-language-server-wrapper` | — | — | — | ✓ | — | | Terraform | `terraform-mode` | — | `terraform-ls` | — | — | — | — | — | | CMake | `cmake-ts-mode` | ✓ | `cmake-language-server` | — | — | — | — | — | | Meson | `meson-mode` | — | — | `meson` | — | — | — | — | | QML | `qml-ts-mode` | ✓ | `qmlls` ᵉ | `qmlformat` | — | — | — | — | | LikeC4 | `likec4-mode` | — | `likec4-lsp` / `likec4` ᵉ | — | — | — | — | — | | HTML | `mhtml-ts-mode` | ✓ | `vscode-html-language-server` / `html-languageserver` ᵉ | — | — | — | — | — | | CSV | `csv-mode` | — | — | — | — | — | — | — | | SQL | `sql-mode` | — | — | — | — | — | — | — | | Dune | `dune-mode` | — | — | — | — | — | — | — | | Bazel / Starlark | `bazel-build-mode` | — | — | `buildifier` | — | — | — | — | | Structurizr DSL | `jotain-structurizr-mode` | — | — | — | — | — | — | — | | Jinja2 | `jinja2-mode` | — | — | — | — | — | — | — | | Just | `just-ts-mode` | ✓ | — | — | — | — | — | — | Notes: **ᵉ** marks a server set by an explicit `eglot-server-programs` override in `init-prog.el` (the rest are eglot's built-in defaults). **Live-probed** ✓ marks the curated subset checked end-to-end by `.#lang-eval-live`. Tree-sitter ✓ means a grammar backs the major mode (all grammars ship with the distribution). This page is generated from `etc/lang-eval/jotain-lang-registry.el` — edit the registry, not this file, then run `just docs-refresh-lang-matrix`. # Setting Variables in Emacs Lisp Source: https://docs.jylhis.com/jotain/setting-variables Quick reference for choosing the right way to set variables in your init file ## Use `setopt` for user options (defcustom) Introduced in **Emacs 29**. Preferred for all `defcustom` variables in your init file. ```elisp theme={} (setopt display-line-numbers-type 'relative) (setopt copilot-idle-delay 0.5) ``` **Why:** `setopt` runs the `:set` callback defined by `defcustom` and validates the value against its `:type`. Using `setq` on a `defcustom` variable bypasses both, which can silently break packages that depend on the setter to update internal state. **Caveat:** slower than `setq` because it goes through the Customize machinery. Don't use it for regular variables in hot paths. **Works in early-init.el:** `setopt` is autoloaded (via `loaddefs.el`, which is part of the Emacs dump), so it is available during `early-init.el`. The first use triggers loading `cus-edit.el` slightly earlier than normal — negligible overhead for a few variables. ## Use `setq` for regular variables The standard way to set any variable. Use for internal/non-customizable variables. ```elisp theme={} (setq gc-cons-threshold (* 50 1000 1000)) (setq x 1 y 2) ``` **Note:** `setq` sets the current binding (lexical or dynamic). Inside a `let`, it modifies the local binding, not the global one. ## Use `setq-default` for buffer-local defaults Sets the default value for buffer-local variables. The value applies to buffers that haven't set their own local value. ```elisp theme={} (setq-default indent-tabs-mode nil) (setq-default fill-column 80) ``` ## Use `setq-local` for buffer-local values Sets a variable's value in the current buffer only. ```elisp theme={} (add-hook 'org-mode-hook (lambda () (setq-local fill-column 100))) ``` ## Use `set` when the variable name is computed A function (not a special form) -- evaluates its first argument to get the symbol to set. ```elisp theme={} (set 'my-var 42) ; equivalent to (setq my-var 42) (set (intern name) value) ; dynamic symbol lookup ``` Rarely needed in init files. ## Quick decision table | Variable kind | Use | | -------------------------- | -------------- | | `defcustom` user option | `setopt` | | Regular variable | `setq` | | Buffer-local default | `setq-default` | | Buffer-local (this buffer) | `setq-local` | | Dynamic symbol name | `set` | ## Sources * [GNU Elisp Manual -- Setting Variables](https://www.gnu.org/software/emacs/manual/html_node/elisp/Setting-Variables.html) * [Emacs Redux -- Goodbye setq, Hello setopt](https://emacsredux.com/blog/2025/04/06/goodbye-setq-hello-setopt/) # AI Screenshots Source: https://docs.jylhis.com/jotain/usage/ai-screenshot Let an AI assistant capture the Emacs frame and see how the config renders — interactively via MCP or headless under Xvfb # AI Screenshots Jotain can render the current frame to an image file so an AI assistant (or you) can *see* how Emacs looks. The capture uses the built-in `x-export-frames`, which draws the frame straight to PNG/SVG/PDF on cairo builds — no external screenshot tool, no window-manager wrangling. The technique follows [Automating Emacs Screenshots (Emacs Redux)][article-1]. Two entry points share the same code path: an interactive command for live sessions, and a headless `just` recipe for terminal-only environments such as CI or a cloud agent container. ## `M-x jotain-screenshot` Captures the selected frame and writes it to `var/screenshots/.png` (the path is echoed and pushed onto the kill ring). From Lisp, both the output file and the format are parameters: ```elisp theme={} (jotain-screenshot) ; var/screenshots/.png (jotain-screenshot "/tmp/frame.svg" 'svg) ``` The command needs a graphical frame on a cairo build (the default Linux X11 build; pgtk also carries cairo). It signals a `user-error` on tty frames and on builds without `x-export-frames` — `noGui` (nix-on-droid) and the macOS NS port. ## The `emacs_screenshot` MCP tool `init-ai.el` registers the command as a custom MCP tool with [claude-code-ide][article-2], so a Claude Code session attached to Emacs (`C-c q`) can call `emacs_screenshot`, get back the image path, and read the file to see the frame. For the tool to be served, `claude-code-ide-enable-mcp-server` must be non-nil — Jotain now sets it in the `claude-code-ide` block. This starts a small local MCP server per Claude session (loopback only); it also enables claude-code-ide's built-in Emacs tools for the session. A typical exchange: ask Claude *"take a screenshot and check the mode line"* — it calls `emacs_screenshot`, then views the returned PNG. ## Headless: `just screenshot` For environments with no display at all — CI, a cloud agent container, an SSH box — the `screenshot` recipe builds Emacs via Nix, launches it with this repo's config under a virtual X server (Xvfb, 1920×1080), captures the frame once startup settles, and exits: ```bash theme={} just screenshot # → var/screenshots/headless.png just screenshot /tmp/frame.png # explicit output path ``` `xvfb-run` comes from the devenv shell (Linux only — Xvfb is X11, and the macOS ports lack `x-export-frames` anyway). Exit status is 0 on success, 2 if init or the capture failed, non-zero from `timeout` if the run wedged. Caveats: * **First run is slow.** It pays a full `nix-build` (binary-cache pull of a multi-GB closure) plus the MELPA bootstrap of every package during init, and needs network for both. The recipe wraps the run in `timeout 600`; raise it if a cold cache needs more. * **Fonts may differ.** If fontconfig doesn't see the devenv-shell fonts, Xvfb falls back to whatever is available — the capture still succeeds, just less faithful to your desktop. * `var/screenshots/` lives under `var/`, so it is gitignored and wiped by `just clean-all`. [article-1]: https://emacsredux.com/blog/2026/07/03/automating-emacs-screenshots/ [article-2]: https://github.com/manzaltu/claude-code-ide.el # devenv integration Source: https://docs.jylhis.com/jotain/usage/devenv Run tasks, manage processes, and introspect devenv.sh environments from Emacs # devenv integration Jotain ships a native integration with [devenv](https://devenv.sh), the Nix-based developer environment tool. The library lives in `lisp/devenv.el` (standalone and reusable — no Jotain dependencies) and is wired in by `init-devenv.el`. Everything is driven through the `devenv` CLI and degrades to a clean error when the binary is not on `PATH`. devenv 2.1+ is recommended; every invocation sets `AI_AGENT=1` so devenv's TUI stays out of the machine-readable output. ## Entry point: `C-c v` `C-c v` (or `M-x devenv`) opens a transient covering the whole surface: | Key | Command | What it does | | --------------- | ----------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- | | `t` | `devenv-task-run` | Pick a task (annotated from `devenv tasks list --json`) and run it; prefix arg selects the dependency mode (`before`/`single`/`after`/`all`) | | `s` | `devenv-script-run` | Pick a script defined in `devenv.nix` (annotated with its description) and run it | | `T` | `devenv-test` | `devenv test` (enterTest, with processes started/stopped around it) | | `b` | `devenv-build` | `devenv build`, optionally for specific attributes | | `u` / `d` | `devenv-up` / `devenv-down` | Start/stop the project's processes; `up` streams into a live buffer (or detaches, see `devenv-up-detached`); prefix arg picks a subset | | `p` | `devenv-processes` | Process dashboard (below) | | `l` | `devenv-processes-logs` | Tail one process's logs | | `i` / `/` / `:` | `devenv-info` / `devenv-search` / `devenv-eval` | Introspection: environment summary, package/option search, evaluate any config attribute to pretty-printed JSON | | `r` | `devenv-reload` | Refresh the environment (below) | | `e` | `devenv-env-global-mode` | Toggle the native environment loader (below) | | `m` | `devenv-modeline-mode` | Toggle the mode-line status segment (below) | | `a` / `x` | `devenv-allow` / `devenv-revoke` | Trust / untrust the project for auto-activation (below) | | `M` | `devenv-mcp-setup` | Register the project's MCP server with mcp.el | | `U` / `G` | `devenv-update` / `devenv-gc` | Update `devenv.lock` inputs / garbage-collect old generations | | `$` | `devenv-show-log` | Every CLI invocation is recorded in `*devenv log*` | The `-c` (`--clean`) and `-r` (`--refresh-eval-cache`) infixes apply to `devenv-test` and `devenv-build`. Long-running commands (test, build, tasks, update, gc) run in compilation buffers with Nix trace matching, so `M-g M-n` jumps to the `file.nix:LINE:COL` locations in evaluation errors. ## Process dashboard `devenv-processes` opens a tabulated view of the devenv process manager. Keys: `s` start, `k` stop, `r` restart, `l`/`RET` logs, `u` up, `d` down, `g` refresh. Data comes from `devenv processes list` (JSON when available, plain table on older devenv), fetched asynchronously so the UI never blocks. ## Environment loading and reload Environment loading is done by the library's **native loader** (`devenv-env-global-mode`, enabled by `init-devenv`; direnv/envrc is disabled in `init-prog`). Every buffer inside a trusted devenv project carries the project environment buffer-locally, so eglot servers, apheleia formatters, `M-x compile`, and the ghostel terminal all see devenv-provided tools with no extra configuration. The goal is parity with a terminal `devenv shell`, which takes more than reading `devenv print-dev-env`: * **`devenv-env-loader` = `shell` (default)** sources the printed dev-env script in bash, shellHook included, and diffs the resulting environment against Emacs's own. Hook-only values therefore reach Emacs too: `LOCALE_ARCHIVE`, `MANPATH`, `CARGO_INSTALL_ROOT`, `$CARGO_HOME/bin` on `PATH`, and anything `enterShell` exports. Because sourcing runs project code it is limited to projects trusted with `devenv allow` (see below). * **`devenv-env-loader` = `json`** parses `devenv print-dev-env --json` instead. No project code runs, but the shellHook's effects are missing. This is also the automatic fallback when sourcing is unavailable or fails. * **`PATH` and `XDG_DATA_DIRS` are layered**, not replaced (`devenv-env-layered-variables`), matching the `nix_saved_PATH` restore in devenv's own script. Without this, everything outside the project profile disappears from `exec-path` — the `devenv` CLI included, which then breaks `C-c v` inside the very projects it manages. * **Derivation-only variables are dropped** (`devenv-env-never-applied-variables`): nix's own `ignoreVars` plus the stdenv build variables devenv's shellHook unsets. The JSON form is the raw *build* environment, so applying it verbatim sets `TMPDIR`/`NIX_BUILD_TOP` to the build sandbox's `/build`, which does not exist on the host. That breaks every tool needing a temp directory: rust-analyzer's proc-macro server dies with `proc-macro server did not respond with data`, and cargo build scripts fail. While a fetch is in flight, `eglot-ensure` is deferred and replayed once the environment lands, so language servers never start with the wrong `PATH`. If the fetch fails, the deferred buffers are released without connecting (starting a server with the login environment would be the wrong toolchain) and the message says how many are without an LSP; `M-x eglot` or `C-c v r` retries. After changing `devenv.nix`, run `devenv-reload` (`C-c v r`). It invalidates the library's caches, re-fetches the environment, and — because eglot snapshots the environment when a server starts — offers to `eglot-reconnect` every server serving the project so language servers pick up new `PATH` entries. Where direnv is in charge instead, `envrc` + `use devenv` is an equally good substrate: set `devenv-env-defer-to-direnv` back to `t` and the native loader stays out of any project with a `.envrc`, delegating reload to `envrc-reload-all`. ## Auto-activation trust (`devenv allow` / `revoke`) devenv 2.1+ gates shell auto-activation behind a per-project trust database (`~/.local/share/devenv/allowed`), managed with `devenv allow` and `devenv revoke`. The integration honours the same contract: the native loader (`devenv-env-global-mode`) consults the internal `devenv hook-should-activate` check and **never evaluates an untrusted devenv.nix automatically** — exactly like the zsh/fish/nu shell hooks. The same gate decides whether the environment may be obtained by sourcing (which runs the project's shellHook); an untrusted or unverifiable project falls back to the JSON loader. `devenv-allow` (`C-c v a`) trusts the project and immediately activates the loader in its buffers; `devenv-revoke` (`C-c v x`) untrusts it and drops the native environment. On devenv versions without the trust model (\< 2.1) the loader behaves as before. Note that the direnv/envrc path has its own consent gate (`envrc-allow` → `direnv allow`), independent of devenv's database. ## Mode-line status `devenv-modeline-mode` (global, enabled by `init-devenv` at startup) adds a status segment to the mode line for buffers inside a devenv project: * **`devenv[on]`** — the buffer's environment carries the devenv shell (loaded by envrc/direnv or the native loader; detected via `DEVENV_PROFILE`). * **`devenv[off]`** — devenv project, environment not loaded. * **`devenv[!]`** — auto-activation trust denied; run `devenv-allow`. Outside devenv projects the segment is empty. `mouse-1` on the segment opens the `devenv` transient. The redisplay path is subprocess-free: trust is probed once per project in the background from `find-file-hook` and cached. ## LSP for devenv.nix devenv 2.0+ ships `devenv lsp`, a bundled nixd preconfigured with the project's devenv module options (completion, hover docs for `devenv.nix` options). `init-devenv` routes `devenv.nix` and `devenv.local.nix` buffers to it while `nil` keeps serving every other Nix file — the routing entry delegates non-devenv buffers to whatever contact was registered before it. ## MCP server for the AI stack devenv 2.0+ ships `devenv mcp`, a stdio MCP server exposing nixpkgs package search, devenv option search, and process control (list/status/logs/start/stop/restart). `devenv-mcp-setup` (`C-c v M`) registers it with [mcp.el](https://github.com/lizqwerscott/mcp.el) under a per-project name (`devenv-`); start it from `M-x mcp-hub` and run `M-x gptel-mcp-connect` to hand its tools to gptel. `devenv-mcp-remove` unregisters it. ## Customization The `devenv` customization group covers the executable name (`devenv-executable`), the quiet-mode environment (`devenv-extra-env`), global CLI flags (`devenv-global-arguments`), cache TTL and command timeout, detached `up` (`devenv-up-detached`), and the dashboard's log line count (`devenv-processes-log-lines`). The native loader adds: how the environment is obtained (`devenv-env-loader`), whether direnv keeps ownership (`devenv-env-defer-to-direnv`), extra variables to drop (`devenv-env-ignored-variables`, filtered on top of the built-in `devenv-env-never-applied-variables`), which variables extend rather than replace the global value (`devenv-env-layered-variables`), and the shell bookkeeping ignored when diffing a sourced environment (`devenv-env-shell-ignored-variables`). # Launching Emacs Source: https://docs.jylhis.com/jotain/usage/launching Pick the right launch pattern for the task — one-shot, daemon + client, or `-Q -nw` quick edit # Launching Emacs **Current state:** the devenv shell provides tooling only — there is no `emacs` binary in it (see the top-of-file note in `devenv.nix`). The direct-launch recipes (`just run`, `just debug`, `just tty`, `just daemon`, `just client`, `just client-tty`, `just quick`) are `[DISABLED]` stubs that print a notice and exit 0. The supported launch path is **`just run-built`**: build Emacs via Nix, then launch it with this configuration. Other docs pages that mention launching point back at this note. Jotain supports three launch patterns. Pick the one that matches the task — they're complementary, not alternatives. ## One-shot A fresh `emacs` process every time. Loads the full Jotain config; pays package-init cost on every launch. ```bash theme={} just run-built # build for this platform, then launch result/bin/emacs just run-built-debug # same, with --debug-init and debug-on-error ``` `just run-built` auto-detects the platform, builds the full distribution (`just build`; on aarch64-linux the full terminal-only distribution via `just build-nox-full`), and launches `./result/bin/emacs --init-directory=`, so it never touches `~/.emacs.d`. The first invocation pays a `nix build` (usually a binary-cache pull); reruns launch straight from `./result`. Best for sanity checks and trying out config changes. Avoid for day-to-day editing — the startup cost adds up. ## Daemon + client Start Emacs once as a server; reuse it via lightweight clients. The first frame is slow; every frame after is instant. ### In the repo (development) The `just daemon` / `just client` / `just client-tty` recipes are disabled (no Emacs in the dev shell — see the note above). The manual equivalent uses the Nix-built binary: ```bash theme={} just build # or let a previous `just run-built` leave ./result behind ./result/bin/emacs --fg-daemon --init-directory="$PWD" # foreground daemon ./result/bin/emacsclient -c # graphical client frame ./result/bin/emacsclient -t # terminal client frame ``` The daemon blocks; run it in another terminal (or under `tmux`) and connect with `emacsclient` from elsewhere. Launching with `--init-directory=` keeps it isolated from `~/.emacs.d`. Stop the daemon with `C-c` in its terminal, or from any client: ```bash theme={} ./result/bin/emacsclient -e '(kill-emacs)' ``` ### As an installed user (home-manager) The `services.jotain` module ships: * A systemd user service (Linux) or launchd agent (macOS) that runs `emacs --fg-daemon`. Enabled via `services.jotain.startWithUserSession`. * `jotain-editor` — `emacsclient --tty` with a `-nw` fallback. Set as `$EDITOR` when `services.jotain.defaultEditor = true`. * `jotain-visual` — `emacsclient --create-frame`. Set as `$VISUAL`. * `jotctl {start|stop|status|restart|logs}` — manage the daemon itself (wraps `launchctl` on macOS, the `jotain` systemd user service on Linux). Always on `PATH` when the module is enabled. * A `.desktop` entry (`jotain-client.desktop`) for launching a GUI client from your application menu, enabled by `services.jotain.client.enable`. See *Module Options* in the appendix for the full option reference. ### Shell aliases Inspired by [Rahul Juliato's launching-emacs-terminal post][article-1], the module can install short aliases for the daemon and clients: ```nix theme={} services.jotain = { enable = true; shellAliases.enable = true; # shellAliases.prefix = "j"; # for "jemd"/"jem"/"jemg" }; ``` | Alias | Expands to | | ----- | ------------------------------------- | | `emd` | `emacs --fg-daemon` (wrapped) | | `em` | `jotain-editor` (`emacsclient --tty`) | | `emg` | `jotain-visual` (`emacsclient -c`) | The aliases are written to `programs.bash.shellAliases`, `programs.zsh.shellAliases`, and `programs.fish.shellAliases` — pick up whichever shell home-manager already manages. ### Managing the daemon (`jotctl`) `emd` starts a *foreground* daemon; to manage the supervised daemon (the launchd agent on macOS, the `jotain` systemd user service on Linux) use `jotctl`, which is always on `PATH` when the module is enabled — independent of `shellAliases.enable`: | Command | macOS (launchd) | Linux (systemd --user) | | ---------------- | --------------------------------- | --------------------------------- | | `jotctl status` | `launchctl print …` (default) | `systemctl --user status jotain` | | `jotctl start` | `launchctl bootstrap` / kickstart | `systemctl --user start jotain` | | `jotctl stop` | `launchctl bootout` | `systemctl --user stop jotain` | | `jotctl restart` | `launchctl kickstart -k` | `systemctl --user restart jotain` | | `jotctl logs` | launchd state + note¹ | `journalctl --user -u jotain -f` | ¹ The launchd agent sets no `StandardOutPath`, so macOS doesn't capture the daemon's stdout; `jotctl logs` prints the launchd state instead. ## Quick edit (`-Q -nw`) When you need a stripped-down Emacs — editing `/etc/hosts` over SSH, fixing a typo in a 200 MB log file, demonstrating something with no muscle-memory key bindings — bypass Jotain entirely (the `just quick` recipe is disabled; use any `emacs` on the host, or the Nix-built one): ```bash theme={} ./result/bin/emacs -Q -nw --eval "(load-theme 'wombat t)" ``` This is the pattern [Rahul Juliato describes in basic-emacs-alias][article-2]. `-Q` skips `early-init.el`, `init.el`, site-lisp, and `~/.emacs.d/`; `-nw` keeps it in the current terminal; the wombat theme line is the one concession to readability on dark terminals. To get the same effect anywhere, add an alias to your shell: ```bash theme={} alias eq='emacs -Q -nw --eval "(load-theme '\''wombat t)"' ``` ## Which one to use | Situation | Use | | ------------------------------------ | ---------------------- | | First-time setup, sanity checks | `just run-built` | | Day-to-day editing, fast frame opens | daemon + `em`/`emg` | | Sysadmin SSH session, ad-hoc edit | `emacs -Q -nw` alias | | Debugging a config regression | `just run-built-debug` | [article-1]: https://rahuljuliato.com/posts/launching-emacs-terminal [article-2]: https://rahuljuliato.com/posts/basic-emacs-alias # Notebooks with Org Babel Source: https://docs.jylhis.com/jotain/usage/notebooks Use Org mode as a literate notebook — run code blocks, keep a session, get plots inline, tangle to source # Notebooks with Org Babel Org Babel turns an ordinary `.org` file into a notebook: prose, executable source blocks, and their captured output in one plain-text document that diffs, greps, and merges like any other file. Jotain configures it in `lisp/init-org.el` for that workflow specifically — languages preloaded, evaluation prompt made unobtrusive, plots redisplayed after every run. Nothing here is a separate mode. You are in `org-mode` the whole time. ## The loop Write a block, put point in it, press `C-c C-c`: ```org theme={} #+begin_src python print(sum(range(10))) #+end_src #+RESULTS: : 45 ``` The `#+RESULTS:` block is written back into the buffer, so results are part of the document and survive a restart. `C-c C-c` on a block that already has results replaces them. Type `