Emacs
Decknix provides a modern, batteries-included Emacs experience with 13+ modules, background daemon, and three profile tiers.
Modules
| Module | Description | Profile |
|---|---|---|
| Core | Modus theme, line numbers, better defaults | minimal+ |
| Completion | Vertico, Consult, Corfu, Embark | minimal+ |
| Editing | Smartparens, Crux, Move-text, EditorConfig | minimal+ |
| UI | Which-key, Helpful, Nerd-icons | minimal+ |
| Undo | undo-fu, vundo (visual undo tree) | minimal+ |
| Project | Project management and navigation | minimal+ |
| Welcome | Startup screen with keybinding cheat sheet | standard+ |
| Development | Flycheck, Yasnippet | standard+ |
| Magit | Git interface, Forge (GitHub PRs), code-review | standard+ |
| Treemacs | Project file tree with git integration | standard+ |
| Languages | 30+ language modes with syntax highlighting | standard+ |
| LSP | Eglot, kotlin-ls, jdt-ls, dape (debugging) | full |
| Org-mode | Modern styling, presentations (Olivetti) | full |
| HTTP | REST client, jq integration, org-babel | full |
| Agent Shell | AI agent interface (Augment Code) | full |
Key Bindings — Quick Reference
Navigation & Search
| Key | Action |
|---|---|
C-s | Search in buffer (consult-line) |
C-x b | Switch buffer with preview |
M-s r | Project-wide ripgrep search |
M-y | Browse kill ring |
C-. | Context actions (Embark) |
Git (Magit)
| Key | Action |
|---|---|
C-x g | Magit status |
@ f f | Fetch forge topics (PRs/issues) |
@ c p | Create pull request |
@ l p | List pull requests |
File Tree (Treemacs)
| Key | Action |
|---|---|
C-x t t | Toggle treemacs |
C-x t f | Find current file in tree |
LSP / Code
| Key | Action |
|---|---|
C-c l r | Rename symbol |
C-c l a | Code actions |
C-c l f | Format region |
C-c l F | Format buffer |
C-c l d | Show documentation |
Debugging (dape)
| Key | Action |
|---|---|
C-c d d | Start debugger |
C-c d b | Toggle breakpoint |
C-c d n | Step over |
C-c d s | Step in |
C-c d c | Continue |
Editing
| Key | Action |
|---|---|
C-a | Smart home (Crux) |
C-c d | Duplicate line |
M-up/down | Move line/region |
C-/ | Undo |
C-? | Redo |
C-x u | Visual undo tree (vundo) |
Org-mode
| Key | Action |
|---|---|
F5 or C-c p | Start/stop presentation |
n / p | Next/previous slide |
Languages
30+ languages with syntax highlighting:
| Category | Languages |
|---|---|
| Primary | Kotlin, Java, Scala, SQL, Terraform/HCL, Shell, Nix, Python |
| Data | JSON, YAML, TOML, XML, Markdown |
| Web | HTML, CSS/SCSS/LESS, JavaScript, TypeScript, JSX, Vue, Svelte |
Emacs Daemon
Configured in modules/darwin/emacs.nix, enabled by default on macOS. Runs as a background launchd service — no Dock icon, no Cmd+Tab entry.
ec filename # Open file in Emacs
ec -c -n # New GUI frame
ec -c -n file.txt # Open file in new GUI frame
ec -t file.txt # Open in terminal
emacsclient -c # Create new GUI frame
GUI frames appear in the Dock while open; closing a frame doesn't kill the daemon.
Opening and checking a cold daemon
nix-open emacs # Wait for the launchd daemon to answer, then open a frame
nix-open -s emacs # Quick status check; reports a busy/starting daemon promptly
nix-open -r emacs # Explicitly restart the launchd daemon, wait, then open a frame
Plain nix-open emacs does not restart an already-running daemon. If launchd
has not started it yet, it asks launchd to start it once. It prints elapsed
readiness updates every five seconds, waits as long as startup takes, and can
be cancelled with Ctrl-C without killing the daemon. --wait is redundant for
Emacs; frame creation always waits for server readiness. Only -r kills the
old daemon (losing unsaved in-memory buffers), so use it deliberately. It never
opens a separate Emacs.app instance. A status probe is capped at two seconds;
-s reports an initializing or busy daemon rather than hanging in
emacsclient or calling it stopped.
On startup, surviving ACP brokers are reattached from their bounded event
logs. Re-reading entire 50 MB or larger session transcripts on the Emacs main
thread for each broker can leave the server socket unresponsive for minutes;
a live broker already holds its model context, so automatic reattach skips
that transcript scan and its continuation primer. The broker log restores
visible recent turns. Historical M-p input-ring entries and the transcript-derived asking
flag are not seeded on this fast automatic reattach path; a normal resume
without a live broker still restores them from the transcript. Check the
live conversation itself before relying on its asking badge after restart.
Because of that reattach, closing a session buffer is not the same as
ending it. C-x k (and C-c s D) only detach: the agent keeps running and
comes back on the next start. To end a session for good, discard it, which
stops its broker and then kills the buffer:
| Key / command | Action |
|---|---|
k or a k on a sidebar row | Discard that session |
C-c s k (decknix-agent-session-discard) | Discard this session, or pick several by name |
C-k in the session picker (C-c s s) | Discard the selected sessions (C-SPC marks several) |
C-c s q | Quit this session (also stops its broker) |
a K on a sidebar row | Kill the buffer's process only (the old k) |
Restoring frames and windows after a restart
The daemon records every GUI frame, its tab-bar tabs and the windows in
each tab when it exits (SIGTERM from launchctl kickstart -k runs
kill-emacs-hook), just before the last frame is closed, and every five
minutes. It records file, dired and magit buffers by path and agent-shell
buffers by session id, in ~/.local/state/decknix/emacs-layout.el.
The first GUI frame after a restart asks once:
Restore saved layout (2 frames, 5 tabs, 3 sessions, saved 1m ago)? [y]es with sessions, [l]ayout only, [n]o
y rebuilds the frames and tabs and resumes the sessions through the same
path as the sidebar's Previous restore. l puts a placeholder in each
session window that resumes on RET. A file or directory that has gone
shows *scratch* in its window, and a frame saved on a monitor that is no
longer attached opens wherever macOS places it. Buffers that visit no file
are reopened by their command when decknix-layout-recreate-alist knows
one (by buffer name or major mode): by default the Review Board, the
Session Board, every coordination board (*Board: NAME*, while its
definition exists), the US Pilot board when installed, and *Messages*.
| Key / command | Action |
|---|---|
C-c D w (decknix-layout-save) | Save the layout now |
C-c D W (decknix-layout-restore) | Restore it; C-u restores the run before the last |
decknix-layout-restore-on-start chooses ask (default), notify, t
(restore without asking) or nil. decknix-layout-restore-sessions set to
nil makes a restore use placeholders instead of resuming. The first save
of each run keeps the previous file as emacs-layout.el.prev, so quitting
straight after a restart does not lose the layout worth restoring.
| Option | Default | Description |
|---|---|---|
services.emacs.decknix.enable | true | Enable Emacs daemon |
services.emacs.decknix.package | pkgs.emacs | Emacs package to use |
services.emacs.decknix.additionalPath | [] | Extra PATH entries for daemon |
Module Options Reference
Disabling Modules
{ ... }: {
programs.emacs.decknix.enable = false; # ALL emacs config
programs.emacs.decknix.welcome.enable = false; # Welcome screen
programs.emacs.decknix.magit.enable = false; # Git interface
programs.emacs.decknix.magit.forge.enable = false; # Just Forge
programs.emacs.decknix.completion.enable = false; # Completion stack
programs.emacs.decknix.treemacs.enable = false; # File tree
programs.emacs.decknix.undo.enable = false; # Undo enhancements
programs.emacs.decknix.editing.enable = false; # Editing enhancements
programs.emacs.decknix.development.enable = false; # Flycheck/Yasnippet
programs.emacs.decknix.ui.enable = false; # UI enhancements
programs.emacs.decknix.ui.icons.enable = false; # Just icons
programs.emacs.decknix.org.enable = false; # Org enhancements
programs.emacs.decknix.lsp.enable = false; # LSP/IDE
programs.emacs.decknix.http.enable = false; # REST client
programs.emacs.decknix.languages.enable = false; # All languages
}
Customisation
{ pkgs, ... }: {
# Add your own packages
programs.emacs.extraPackages = epkgs: [ epkgs.evil epkgs.lsp-mode ];
# Add your own config
programs.emacs.extraConfig = ''
(evil-mode 1)
(setq my-custom-variable t)
'';
}
Note: Evil mode (Vim emulation) is not included by default. Add it in your personal config as shown above.