001

Quick Reference

Essential keybindings for everyday editing

Emacs notation: C- means hold Ctrl, M- means hold Alt (Meta). C-x C-f means Ctrl+x, then Ctrl+f. M-x means Alt+x.

001.1 File Operations

Keybinding Command Description
C-x C-f find-file Open or create a file
C-x C-s save-buffer Save the current buffer
C-x C-w write-file Save as (write to a different file)
C-x C-c save-buffers-kill-terminal Quit Emacs (prompts to save)

001.2 Buffer Operations

Keybinding Command Description
C-x b switch-to-buffer Switch to another buffer by name
C-x C-b list-buffers List all open buffers
C-x k kill-buffer Close a buffer

001.3 Window Operations

Keybinding Command Description
C-x 2 split-window-below Split current window horizontally
C-x 3 split-window-right Split current window vertically
C-x 0 delete-window Close the current window
C-x 1 delete-other-windows Keep only the current window
C-x o other-window Switch focus to the next window

001.4 Help System

Emacs has the most comprehensive built-in help system of any editor. The prefix C-h opens the help gateway.

C-h t
help-with-tutorial
Start the interactive tutorial. The best place for absolute beginners.
C-h k
describe-key
Press any key sequence to see what command it runs and its documentation.
C-h f
describe-function
Look up any function by name. Shows docstring, source file, and keybinding.
C-h v
describe-variable
Inspect any variable: current value, documentation, and where it was set.
C-h m
describe-mode
Show all keybindings and docs for every active mode in the current buffer.
M-x
execute-extended-command
Run any command by name. The universal entry point for everything in Emacs.
The help system is self-documenting: C-h C-h shows help about the help system itself. Links in help buffers are clickable, leading you to source code and related documentation.

001.5 Essential Editing

Keybinding Command Description
C-g keyboard-quit Cancel any operation (the panic button)
C-/ undo Undo last change
C-SPC set-mark-command Set mark to begin selection
C-w kill-region Cut (kill) selected text
M-w kill-ring-save Copy selected text
C-y yank Paste (yank) last killed text
C-s isearch-forward Incremental search forward
M-% query-replace Search and replace with confirmation
002

Installation & Setup

Getting Emacs running on every platform

002.1 Package Manager Installation

Every major platform provides Emacs through its package manager. For the best experience, install a version with GUI support (not -nox) even if you plan to use the terminal—GUI Emacs can be launched in terminal mode with emacs -nw.

Debian / Ubuntu
sudo apt install emacs

# Terminal-only variant (no GUI dependencies):
sudo apt install emacs-nox
Fedora
sudo dnf install emacs
Arch Linux
sudo pacman -S emacs
macOS
# Basic install via Homebrew:
brew install emacs

# Recommended: emacs-plus with native compilation
brew tap d12frosted/emacs-plus
brew install emacs-plus@30 --with-native-comp
Windows

Download the installer from the GNU FTP mirror at https://ftp.gnu.org/gnu/emacs/windows/. Choose the emacs-XX.X-installer.exe package. Alternatively, use MSYS2:

pacman -S mingw-w64-x86_64-emacs
Native compilation (Emacs 28+) converts Elisp to machine code, giving 2–5x speed improvements for packages. If your platform supports it, always prefer a build with --with-native-compilation.

002.2 Building from Source

Building from source gives you the latest features and control over compile-time options. This is especially useful on Linux where you may want native JSON parsing, tree-sitter, and native compilation.

# Install build dependencies (Debian/Ubuntu):
sudo apt build-dep emacs
sudo apt install libgccjit-12-dev libtree-sitter-dev

# Clone and build:
git clone https://git.savannah.gnu.org/git/emacs.git
cd emacs
./autogen.sh
./configure --with-native-compilation \
            --with-json \
            --with-tree-sitter \
            --with-mailutils
make -j$(nproc)
sudo make install
Building from source takes 5–15 minutes. Native compilation of bundled Elisp (ahead-of-time) can take considerably longer on first run. You may see *Async-native-compile-log* buffers appear—this is normal.

002.3 Init File Locations

Emacs reads your personal configuration from an init file at startup. The file is Emacs Lisp (Elisp), and its location depends on your Emacs version and OS conventions.

File Path Purpose Version
~/.emacs.d/init.el Traditional init file location All versions
~/.config/emacs/init.el XDG-compliant location Emacs 27+
~/.emacs.d/early-init.el Runs before GUI and package init Emacs 27+
~/.emacs Legacy single-file config (avoid) All versions
Prefer ~/.emacs.d/init.el or ~/.config/emacs/init.el over the legacy ~/.emacs file. A directory-based config lets you organize code into multiple files as your configuration grows.

002.4 Starter Configuration

A minimal, sensible starting configuration that strips Emacs down to a clean editing environment. Place this in your init.el:

;; ── Appearance ──────────────────────────────────
(setq inhibit-startup-message t)       ; Skip the splash screen
(tool-bar-mode -1)                     ; Hide the toolbar
(scroll-bar-mode -1)                   ; Hide scrollbars
(menu-bar-mode -1)                     ; Hide the menu bar
(global-display-line-numbers-mode 1)   ; Show line numbers

;; ── Editing Behavior ────────────────────────────
(electric-pair-mode 1)                 ; Auto-close parens, brackets
(show-paren-mode 1)                    ; Highlight matching parens
(setq-default indent-tabs-mode nil)    ; Spaces, not tabs
(setq-default tab-width 4)            ; 4-space indentation
(delete-selection-mode 1)              ; Typing replaces selection

;; ── File Handling ───────────────────────────────
(recentf-mode 1)                       ; Track recently opened files
(setq make-backup-files nil)           ; No ~ backup files
(setq auto-save-default nil)           ; No #autosave# files
(global-auto-revert-mode 1)           ; Auto-reload changed files

;; ── Completion ──────────────────────────────────
(setq completion-styles '(flex basic)) ; Fuzzy completion (Emacs 27+)
(fido-vertical-mode 1)                ; Vertical minibuffer completion
Starter kits like Doom Emacs, Spacemacs, and Prelude provide pre-built configurations with extensive defaults. They are excellent for getting productive quickly, but learning vanilla Emacs first gives you a deeper understanding of what those kits are doing.
003

Core Concepts

The mental model behind everything in Emacs

Emacs has its own vocabulary for concepts that other editors name differently. Understanding these terms is essential because the entire help system, documentation, and community discourse uses them precisely.

003.1 Buffers

A buffer is the fundamental unit of content in Emacs. It is an in-memory workspace that may or may not correspond to a file on disk. When you open a file, Emacs creates a buffer holding a copy of its contents; edits happen in the buffer, not the file, until you explicitly save.

File-Visiting Buffers

Created by C-x C-f. The buffer name matches the filename. Saving (C-x C-s) writes the buffer back to disk. An asterisk * in the mode line means unsaved changes.

Special Buffers

Names wrapped in asterisks: *scratch* (Elisp playground), *Messages* (log), *Help* (docs), *Completions*. These do not correspond to files.

Buffer-Local Variables

Each buffer can have its own value for any variable. This is how different files get different tab widths, modes, or encoding settings. Use setq-local or make-local-variable.

Every buffer has exactly one major mode that determines its behavior. Use C-x b to switch buffers and C-x C-b to see all open buffers. Emacs can have hundreds of buffers open simultaneously with negligible memory cost.

003.2 Windows

In Emacs terminology, a window is a viewport into a buffer—a rectangular area of the screen that displays buffer contents. This is not what the operating system calls a window (Emacs calls that a frame).

Keybinding Action Description
C-x 2 Split below Create a new window beneath the current one
C-x 3 Split right Create a new window beside the current one
C-x 0 Close window Delete the current window (buffer stays open)
C-x 1 Maximize Delete all other windows, keep current
C-x o Next window Move cursor to the next window
Multiple windows can display the same buffer simultaneously. Each window maintains its own cursor position (point) and scroll position, so you can view two parts of a file side by side.

003.3 Frames

A frame is what your operating system calls a "window"—a top-level GUI container managed by the window manager. Each frame has its own set of Emacs windows, menu bar, and mode line. All frames share the same set of buffers.

Keybinding Command Description
C-x 5 2 make-frame-command Create a new frame
C-x 5 0 delete-frame Delete the current frame
C-x 5 o other-frame Switch to the next frame
Most Emacs users work in a single frame with multiple windows rather than multiple frames. However, multi-monitor setups benefit from frames—one per monitor, all sharing buffers.

003.4 Major Modes

Every buffer has exactly one major mode that defines its primary behavior: syntax highlighting, indentation rules, available commands, and keybindings. Major modes are mutually exclusive—activating one deactivates the previous.

Common Major Modes
emacs-lisp-mode
Editing Elisp files. Provides eval, completion, and debugging support.
python-mode
Python editing with indentation, shell integration, and virtualenv support.
org-mode
Outlining, task management, literate programming, and document export.
fundamental-mode
The bare minimum mode. No syntax highlighting or special behavior.
text-mode
Plain text editing. Parent mode for markdown-mode, rst-mode, and others.
dired-mode
Directory editor. Browse and manipulate files without leaving Emacs.

Emacs selects the major mode automatically based on the filename, file extension, or a special comment (-*- mode: foo -*-) at the top of the file. You can switch manually with M-x python-mode, M-x org-mode, etc.

003.5 Minor Modes

Minor modes are toggleable features that layer on top of the major mode. Multiple minor modes can be active simultaneously. They add functionality like spell checking, auto-saving, line numbering, or auto-completion.

flyspell-mode
On-the-fly spell checking. Underlines misspelled words. Use flyspell-prog-mode to check only comments and strings.
display-line-numbers-mode
Shows line numbers in the left margin. The global- variant enables it everywhere.
eldoc-mode
Shows function signatures and documentation in the echo area as you type. Active by default in Elisp buffers.
electric-pair-mode
Automatically inserts matching delimiters: parentheses, brackets, braces, and quotes.

Enable minor modes per-buffer with M-x flyspell-mode, or enable them automatically via hooks:

;; Enable minor modes via hooks:
(add-hook 'text-mode-hook 'flyspell-mode)
(add-hook 'prog-mode-hook 'display-line-numbers-mode)
(add-hook 'prog-mode-hook 'eldoc-mode)

003.6 The Minibuffer & Echo Area

The minibuffer is the single-line input area at the very bottom of the frame. It is where Emacs prompts you for input: filenames (C-x C-f), command names (M-x), search strings (C-s), and more.

When the minibuffer is not active, the same space serves as the echo area, displaying status messages, errors, and brief notifications. Messages accumulate in the *Messages* buffer for later review.

Minibuffer Essentials
Key Action
TAB Complete the current input (filename, command, variable)
C-g Abort the current minibuffer prompt
M-p / M-n Cycle through previous/next minibuffer history
RET Confirm the current input

003.7 Point, Mark & Region

Emacs uses precise terminology for cursor position and text selection:

Point
The current cursor position. Every buffer has its own point. Functions like (point) return the character offset. Point is always between two characters, not on a character.
Mark
A saved position in the buffer, set with C-SPC (set-mark-command). The mark ring remembers previous mark positions; C-u C-SPC cycles through them.
Region
The text between point and mark. When the region is active (highlighted), commands like C-w (kill) and M-w (copy) operate on it. Toggle visibility with transient-mark-mode.
;; Elisp functions for working with point and mark:
(point)                  ; Current cursor position (integer)
(mark)                   ; Current mark position
(region-beginning)       ; Start of active region
(region-end)             ; End of active region
(buffer-substring        ; Extract text from region
  (region-beginning)
  (region-end))
The kill ring is Emacs's clipboard, but it stores multiple entries. After yanking with C-y, press M-y repeatedly to cycle through previous kills. This is one of Emacs's most productive features once internalized.
004

Movement & Editing

Navigation, kill ring, registers, rectangles, and macros

004.1 Navigation Commands

Emacs navigation is organized by scope—from the smallest unit (character) to the largest (buffer). Each scope has forward and backward variants, and most accept a numeric prefix argument (C-u 5 C-f moves 5 characters forward).

Scope Forward Backward Description
Character C-f C-b Move one character forward / backward
Word M-f M-b Move one word forward / backward
Line (vertical) C-n C-p Move to next / previous line
Line (horizontal) C-e C-a Move to end / beginning of line
Sentence M-e M-a Move to end / beginning of sentence
Paragraph M-} M-{ Move to end / beginning of paragraph
Screen C-v M-v Scroll one screenful down / up
Buffer M-> M-< Jump to end / beginning of buffer
M-g g
Go to a specific line number. Also bound to M-g M-g. Indispensable when following compiler error messages.
C-l
Recenter the screen. Press once to center point, twice to move point to top, three times for bottom. Cycles on repeated presses.
C-u C-SPC
Pop the mark ring—jump back to where you were. Repeat to keep jumping further back in history.
M-g c
Go to a specific character position in the buffer. Useful for debugging byte offsets in binary formats.
The mnemonic pattern: C- prefix operates on characters/lines (small units), M- prefix operates on words/sentences/paragraphs (semantic units). This pattern extends to killing: C-k kills to end of line, M-k kills to end of sentence.

004.2 Kill, Yank & Kill Ring

Emacs uses kill (cut) and yank (paste) instead of the clipboard model. The key difference: killed text accumulates in a kill ring—a list of all previously killed text, not just the most recent. This gives you access to your entire cut history.

Keybinding Command Description
C-k kill-line Kill from point to end of line (line contents, then newline)
C-w kill-region Kill the active region (cut)
M-w kill-ring-save Copy the active region (no deletion)
C-y yank Yank (paste) the most recent kill
M-y yank-pop After C-y, cycle through older kills
M-d kill-word Kill from point to end of current word
M-DEL backward-kill-word Kill from point to start of current word
C-M-k kill-sexp Kill the balanced expression (sexp) after point
M-k kill-sentence Kill to end of sentence
Kill Ring Configuration
;; Prevent duplicate entries in the kill ring
(setq kill-do-not-save-duplicates t)

;; Increase kill ring size (default is 60)
(setq kill-ring-max 120)

;; Integrate with system clipboard
(setq select-enable-clipboard t)
(setq select-enable-primary t)
Consecutive C-k presses merge into a single kill ring entry. Kill a multi-line block by pressing C-k repeatedly—then C-y pastes all those lines as one unit. Any non-kill command between C-k presses breaks the chain.

004.3 Registers

Registers are named slots (single characters: a–z, 0–9) that store positions, text, numbers, window configurations, or rectangles. They persist for the duration of your Emacs session and are lightning-fast to access.

Keybinding Description
C-x r SPC r Save current position (point) in register r
C-x r j r Jump to the position saved in register r
C-x r s r Store the region’s text in register r
C-x r i r Insert the text stored in register r
C-x r w r Store the current window configuration in register r
C-x r f r Store the current frame configuration in register r
C-x r n r Store a number in register r
C-x r + r Increment the number in register r
Registers are ideal for bookmarking positions you jump between frequently. Store your test file location in register t and your source file in register s, then hop between them instantly with C-x r j t and C-x r j s.

004.4 Rectangles

Rectangle commands operate on column-oriented blocks of text—the rectangular region defined by point and mark as opposite corners. This is extremely useful for editing tabular data, aligning columns, or adding prefixes to multiple lines.

Keybinding Command Description
C-x r k kill-rectangle Kill (cut) the rectangle between point and mark
C-x r y yank-rectangle Yank (paste) the last killed rectangle
C-x r o open-rectangle Insert blank space to fill the rectangle, shifting text right
C-x r t string-rectangle Replace each line of the rectangle with a prompted string
C-x r d delete-rectangle Delete the rectangle (without saving to kill ring)
C-x r N rectangle-number-lines Insert line numbers into the rectangle column
Rectangle Example: Adding a Prefix
# Before: (mark at start of "apple", point at start of "cherry")
apple
banana
cherry

# After C-x r t "- " RET:
- apple
- banana
- cherry
C-x SPC activates rectangle-mark-mode, which visually highlights the rectangle as you move point. This makes it much easier to see exactly which columns you are selecting before applying a rectangle command.

004.5 Keyboard Macros

Keyboard macros record a sequence of keystrokes and replay them. They are the fastest way to automate repetitive editing tasks without writing Elisp. Think of them as an instant, disposable script.

Keybinding Description
F3 or C-x ( Start recording a keyboard macro
F4 or C-x ) Stop recording the macro
F4 or C-x e Execute the last recorded macro (press e to repeat)
C-u 10 C-x e Execute the macro 10 times
C-u 0 C-x e Execute the macro until an error occurs (end of buffer, search failure)
C-x C-k n Name the last macro (so it survives recording a new one)
C-x C-k b Bind the last macro to a key sequence
Saving Macros Permanently
;; 1. Name the macro:  M-x name-last-kbd-macro RET my-macro RET
;; 2. Open your init.el
;; 3. M-x insert-kbd-macro RET my-macro RET
;; This inserts something like:
(fset 'my-macro
   (kmacro-lambda-form [?\C-a ?# ?  ?\C-n] 0 "%d"))

;; 4. Optionally bind it:
(global-set-key (kbd "C-c m") 'my-macro)
Design macros to end where the next repetition begins. If your macro processes one line, make sure it ends with point at the start of the next line. This way C-u 0 C-x e cleanly processes all remaining lines until end of buffer.
005

Buffers, Windows & Frames

Splits, winner-mode, tab-bar, shells, and TRAMP

005.1 Window Management

Emacs windows are tiling by nature—every split divides existing space, and windows never overlap. Combined with winner-mode for layout undo, this gives you a window manager inside your editor that rivals dedicated tiling WMs.

Keybinding Command Description
C-x 2 split-window-below Split current window horizontally (top/bottom)
C-x 3 split-window-right Split current window vertically (left/right)
C-x 0 delete-window Close the current window (buffer stays open)
C-x 1 delete-other-windows Maximize current window, closing all others
C-x o other-window Cycle focus to the next window
C-x ^ enlarge-window Make the current window one line taller
C-x } enlarge-window-horizontally Make the current window one column wider
C-x + balance-windows Equalize all window sizes
winner-mode: Layout Undo/Redo
;; Enable winner-mode for window layout history
(winner-mode 1)

;; C-c left  — undo last window configuration change
;; C-c right — redo (return to newer layout)

;; Accidentally hit C-x 1 and lost your splits?
;; Just press C-c left to restore them instantly.
winner-mode is one of the most underrated built-in features. It stores a history of every window layout change. Accidentally closed a split? C-c left brings it back. It costs nothing to enable and saves you constantly.

005.2 tab-bar-mode (Workspaces)

Since Emacs 27, tab-bar-mode provides workspace tabs. Each tab stores an entire window configuration—not a single buffer, but the full layout of splits and their contents. This is the Emacs equivalent of tmux windows or virtual desktops.

Keybinding Description
C-x t 2 Create a new tab (duplicates current layout)
C-x t 0 Close the current tab
C-x t 1 Close all other tabs
C-x t o Switch to the next tab
C-x t RET Switch to a tab by name (with completion)
C-x t r Rename the current tab
tab-bar-mode Configuration
;; Enable the tab bar
(tab-bar-mode 1)

;; Show tab numbers for quick switching
(setq tab-bar-tab-hints t)

;; Switch tabs by number: M-1 through M-9
(setq tab-bar-select-tab-modifiers '(meta))

;; Customize appearance
(setq tab-bar-close-button-show nil)   ; Hide close buttons
(setq tab-bar-new-button-show nil)     ; Hide new-tab button

Project Workspace Pattern

Create one tab per project: “frontend”, “backend”, “docs”. Each tab has its own window layout. Switch with C-x t RET and type the name.

Tab vs. Frame

Tabs live inside a single frame and share the same buffer list. Frames are OS-level windows. Use tabs for context switching within a workflow; use frames for multi-monitor setups.

005.3 Shell Inside Emacs

Running shells inside Emacs is where it truly becomes a terminal multiplexer. You get all the benefits of Emacs editing (search, copy, yank) inside your terminal sessions, plus the ability to manage multiple shells as buffers.

Shell Type Implementation Strengths Limitations
eshell Pure Elisp shell Cross-platform, Lisp piping, Elisp integration No ncurses/TUI apps, different from bash
shell-mode System shell wrapper Familiar bash/zsh, output is a searchable buffer No interactive TUI apps (top, htop)
vterm libvterm terminal emulator Full terminal emulation, runs ncurses apps Requires compilation (libvterm-dev)
eat Pure-Elisp terminal Fast, Eshell-integrated, no compilation needed Newer project, less mature than vterm
Named Shell Buffers (tmux-pane Equivalent)
;; Create named shell sessions (like tmux panes)
(defun spawn-shell (name)
  "Create a shell buffer with a custom NAME."
  (interactive "sShell name: ")
  (shell (format "*shell-%s*" name)))

;; Usage: M-x spawn-shell RET server RET
;; Creates *shell-server* buffer
;; M-x spawn-shell RET build RET
;; Creates *shell-build* buffer
;; Switch between them with C-x b *shell-TAB
vterm Setup
;; Install and configure vterm
(use-package vterm
  :ensure t
  :bind ("C-c t" . vterm)
  :config
  (setq vterm-max-scrollback 10000)
  (setq vterm-shell "/bin/zsh"))

;; Multiple vterm buffers
(use-package multi-vterm
  :ensure t
  :bind (("C-c T" . multi-vterm)
         ("C-c n" . multi-vterm-next)
         ("C-c p" . multi-vterm-prev)))
In eshell, you can pipe output through Elisp functions: ls -la | grep foo | (length (split-string $$ "\n")). You can also redirect to Emacs buffers: cat log.txt > #<buffer my-log>. This blurs the line between shell and editor in ways no other tool can.

005.4 TRAMP (Remote Access)

TRAMP (Transparent Remote Access, Multiple Protocols) lets you edit remote files as if they were local. It works transparently with every Emacs feature—Dired, Magit, eshell, compilation, grep—all operate on remote files without any special configuration.

TRAMP Path Syntax
/ssh:user@host:/path/to/file
Edit a file over SSH. The most common TRAMP usage.
/ssh:user@host|sudo:host:/etc/nginx/nginx.conf
SSH to a host, then sudo to edit a root-owned file. Multi-hop chaining.
/ssh:bird@bastion|ssh:you@internal:/path
Hop through a bastion/jump host to reach an internal server.
/docker:container-name:/app/main.py
Edit files inside a running Docker container.
/sudo::/etc/hosts
Edit a local file as root via sudo (note the double colon).
/ssh:user@host|docker:container:/path
SSH to a remote host, then enter a Docker container on that host.
TRAMP Configuration
;; Speed up TRAMP by using ssh controlmaster
(setq tramp-default-method "ssh")
(setq tramp-ssh-controlmaster-options
      (concat "-o ControlMaster=auto "
              "-o ControlPath=/tmp/ssh-%%r@%%h:%%p "
              "-o ControlPersist=yes"))

;; Speed up remote file operations
(setq tramp-verbose 1)  ; Less logging = faster
(setq remote-file-name-inhibit-cache nil)

;; Open Dired on a remote machine:
;; C-x d /ssh:user@host:/home/user/ RET
TRAMP can feel slow on first connection because it probes the remote system for capabilities. Subsequent operations within the same session are much faster. Set tramp-verbose to 6 temporarily if you need to debug connection issues.
Steve Yegge described wanting an Emacs terminal multiplexer—and it already exists. With vterm for full terminal emulation, tab-bar-mode for workspaces, winner-mode for layout undo, and TRAMP for transparent remote access, Emacs is the multiplexer he dreamed of. You never need to leave.
006

Org Mode

Outlines, TODO, agenda, tables, babel, and export

006.1 Outline Structure

Org mode is built on an outline model. Headings are lines starting with one or more asterisks (*), where the number of asterisks indicates depth. This simple syntax creates a powerful, foldable document hierarchy.

Heading Syntax
* Top-level heading
Some body text goes here.

** Second level
More text under the second level.

*** Third level
Deeply nested content.

** Another second-level heading
Back to the second level.
Keybinding Description
TAB Cycle visibility of the current subtree (folded → children → all)
S-TAB Cycle visibility of the entire buffer globally
M-up / M-down Move the current heading (and its subtree) up or down
M-left / M-right Promote / demote the current heading by one level
M-S-left / M-S-right Promote / demote the entire subtree
M-RET Insert a new heading at the same level
C-c C-n / C-c C-p Jump to next / previous heading
C-c C-u Jump up to the parent heading
Press S-TAB three times from a fully expanded document: first press folds to top-level headings only, second shows all headings, third expands everything. This is the fastest way to get an overview of a large document.

006.2 TODO & Agenda

Org mode turns headings into actionable tasks by adding TODO keywords. Combined with the agenda system, this creates a full personal task manager that scales from a simple to-do list to complex project management.

Task Syntax
* TODO Write documentation
  DEADLINE: <2026-03-15 Sun>

* DONE Complete draft
  CLOSED: [2026-03-08 Sat 14:30]

* TODO [#A] High priority task
  SCHEDULED: <2026-03-10 Mon>

* TODO [#C] Low priority task
Custom TODO Workflows
;; Define custom TODO state sequences
;; | separates "active" states from "done" states
#+TODO: TODO IN-PROGRESS REVIEW | DONE CANCELLED

;; Or in your init.el:
(setq org-todo-keywords
      '((sequence "TODO(t)" "IN-PROGRESS(i)" "REVIEW(r)"
                  "|" "DONE(d)" "CANCELLED(c)")))
Agenda Setup
;; Tell Org where your task files live
(setq org-agenda-files '("~/org/"))

;; Global keybindings (standard convention)
(global-set-key (kbd "C-c a") #'org-agenda)
(global-set-key (kbd "C-c c") #'org-capture)
(global-set-key (kbd "C-c l") #'org-store-link)
Keybinding Agenda View Description
C-c a a Weekly agenda Calendar view with scheduled and deadline tasks
C-c a t TODO list All TODO items across all agenda files
C-c a m Tag match Find headings matching a tag query (e.g., :work:urgent:)
C-c a s Search Full-text search across all agenda files
C-c C-t
Cycle the TODO state of the current heading (TODO → DONE, or through your custom sequence).
C-c C-s
Schedule a task—add a SCHEDULED timestamp. The task appears in the agenda on that date.
C-c C-d
Set a deadline. The agenda shows warnings as the deadline approaches.
C-c C-q
Set tags on a heading. Tags are inherited by child headings and searchable via the agenda.

006.3 Tables

Org mode includes a spreadsheet engine built into its plain-text table syntax. Tables auto-align as you type, and you can add formulas that reference cells by row and column.

Table Syntax & Formulas
| Item    | Qty | Price |  Total |
|---------+-----+-------+--------|
| Apples  |   5 |  1.20 |   6.00 |
| Oranges |   3 |  0.90 |   2.70 |
| Bananas |   8 |  0.50 |   4.00 |
|---------+-----+-------+--------|
| Total   |     |       |  12.70 |
#+TBLFM: $4=$2*$3::@>$4=vsum(@2$4..@-1$4)
Keybinding Description
TAB Move to the next cell (auto-aligns the table)
S-TAB Move to the previous cell
RET Move to the cell below (creates new row if at bottom)
C-c | Convert a region of tab/comma-separated text to a table
C-c C-c Re-align the table and recalculate all formulas
M-left / M-right Move the current column left / right
M-up / M-down Move the current row up / down
Formula syntax: $N references column N, @N references row N. @2$3 means row 2, column 3. vsum(@2$4..@-1$4) sums column 4 from row 2 to the row before the current one. Press C-c ' to edit formulas in a dedicated buffer.

006.4 Code Blocks (Babel)

Org Babel lets you embed executable code blocks in Org documents. You can run code in dozens of languages, capture output, and pass results between blocks. This is literate programming—prose and code woven together.

Code Block Syntax
#+begin_src python :results output
for i in range(5):
    print(f"Line {i}")
#+end_src

#+RESULTS:
: Line 0
: Line 1
: Line 2
: Line 3
: Line 4
Enable Languages for Babel
;; Whitelist the languages you want to execute
(org-babel-do-load-languages
 'org-babel-load-languages
 '((python     . t)
   (shell      . t)
   (emacs-lisp . t)
   (js         . t)
   (sql        . t)
   (C          . t)
   (plantuml   . t)))

;; Don't ask for confirmation before executing (use with care)
(setq org-confirm-babel-evaluate nil)
C-c C-c
Execute the code block at point. Results appear below the block in a #+RESULTS: drawer.
C-c '
Edit the code block in a dedicated buffer with the language’s major mode (syntax highlighting, completion).
:var x=5
Header argument: pass variables into code blocks. Can reference results from other named blocks.
:session
Header argument: run blocks in a persistent session so variables and state carry over between executions.
Babel can execute arbitrary code, including shell commands that modify your system. Set org-confirm-babel-evaluate to nil only for trusted documents. The default prompts before every execution for safety.

006.5 Export

Org mode can export documents to a wide variety of formats through its built-in export dispatcher. Press C-c C-e to open the export menu, then choose your target format.

Keys Format Description
C-c C-e h h HTML Export to a standalone HTML file
C-c C-e h o HTML + open Export to HTML and open in browser
C-c C-e l p PDF (via LaTeX) Export to PDF through LaTeX compilation
C-c C-e m m Markdown Export to Markdown format
C-c C-e t u Plain text (UTF-8) Export to plain text
C-c C-e o o ODT Export to OpenDocument format
Add #+OPTIONS: toc:2 num:t author:t at the top of your Org file to control export settings: table of contents depth, section numbering, and whether to include the author name. See the Org manual for the full list of export options.

006.6 Literate Programming (Tangle)

Tangling extracts code blocks from an Org document and writes them to source files. This inverts the usual relationship: your Org document is the primary artifact, and the source code is generated from it. This is Knuth’s literate programming realized in a modern editor.

Tangle Example
#+PROPERTY: header-args :mkdirp yes

* Configuration
This file generates our application config.

** Main entry point
#+begin_src python :tangle myapp.py
def main():
    print("Hello from tangled source")

if __name__ == "__main__":
    main()
#+end_src

** Utility functions
#+begin_src python :tangle utils.py
def greet(name):
    return f"Hello, {name}!"
#+end_src

# C-c C-v t  —  tangle all blocks to their target files
# Creates myapp.py and utils.py from this single Org document

Literate Emacs Config

Many users write their entire Emacs configuration as an Org file (config.org) and tangle it to init.el. Each section documents why a setting exists, with the code block that implements it. Load with (org-babel-load-file "~/.emacs.d/config.org").

Reproducible Research

Scientists use Org Babel to create papers where the data analysis code, figures, and prose coexist in one document. Re-running C-c C-c on each block regenerates all results from the raw data.

The :noweb header argument enables code block references: <<block-name>> inside a code block expands to the contents of the named block. This lets you compose large programs from documented, reusable pieces—exactly as Knuth envisioned.
007

Configuration & Packages

use-package, straight.el, MELPA, and Elisp configuration

007.1 early-init.el vs init.el

Emacs 27+ introduced early-init.el, which runs before the GUI is initialized and before the package system loads. This is the place for settings that affect startup performance and frame appearance. Everything else goes in init.el, which runs after the GUI and package system are ready.

~/.emacs.d/early-init.el
;; early-init.el — runs BEFORE GUI & package system
;; Use this for frame settings, GC tuning, and disabling UI chrome

;; Boost GC threshold during startup (reset later)
(setq gc-cons-threshold (* 50 1000 1000))

;; Disable package.el at startup (if using straight.el)
(setq package-enable-at-startup nil)

;; Suppress startup screen
(setq inhibit-startup-message t)

;; Remove UI chrome before frame is drawn (faster than disabling after)
(push '(menu-bar-lines . 0) default-frame-alist)
(push '(tool-bar-lines . 0) default-frame-alist)
(push '(vertical-scroll-bars) default-frame-alist)

;; Set default frame size
(push '(width . 120) default-frame-alist)
(push '(height . 45) default-frame-alist)

;; Prevent glimpse of un-styled Emacs by setting bg early
(push '(background-color . "#1e1e2e") default-frame-alist)
File When It Runs What Belongs Here
early-init.el Before GUI & packages Frame defaults, GC threshold, package-enable-at-startup, native-comp settings
init.el After GUI & packages Package config, keybindings, hooks, mode settings, theme loading
custom.el Loaded from init.el Machine-generated settings from M-x customize (keep out of init.el)
Reset the GC threshold after startup to avoid long pauses during editing: (add-hook 'emacs-startup-hook (lambda () (setq gc-cons-threshold (* 2 1000 1000)))). The high threshold during init prevents hundreds of GC pauses while loading packages.

007.2 Package Archives

Emacs ships with GNU ELPA by default, but the most popular third-party packages live on MELPA. Since Emacs 28, NonGNU ELPA provides a curated set of packages that don’t meet GNU copyright assignment requirements but are still widely trusted.

Package Archive Setup
(require 'package)
(setq package-archives
      '(("gnu"    . "https://elpa.gnu.org/packages/")
        ("nongnu" . "https://elpa.nongnu.org/nongnu/")
        ("melpa"  . "https://melpa.org/packages/")))
(package-initialize)

;; Refresh package list if it's empty (first launch)
(unless package-archive-contents
  (package-refresh-contents))

;; Install a package programmatically
(unless (package-installed-p 'magit)
  (package-install 'magit))
Archive Update Frequency Curation Package Count
GNU ELPA Synced with Emacs releases Strict—requires FSF copyright assignment ~400
NonGNU ELPA Rolling Curated, no copyright assignment needed ~200
MELPA Continuous (builds from Git) Community-reviewed recipes ~5,500
MELPA Stable Tagged releases only Same as MELPA, pinned to Git tags ~3,000
Avoid mixing MELPA and MELPA Stable for the same package. If you need stability for specific packages, use (setq package-pinned-packages '((magit . "melpa-stable"))) to pin individual packages while keeping MELPA for everything else.

007.3 use-package (Built-in Since Emacs 29)

use-package is a declarative macro that isolates each package’s configuration into a clean, self-contained block. It handles autoloading, deferred loading, keybinding, and error isolation—so a broken package config won’t prevent Emacs from starting.

Comprehensive use-package Example
(use-package magit
  :ensure t                               ; install from archive if missing
  :defer t                                ; don't load until needed
  :bind (("C-x g"   . magit-status)       ; global keybinding
         ("C-x M-g" . magit-dispatch))    ; secondary binding
  :commands (magit-status magit-blame)     ; autoload these commands
  :init                                    ; runs BEFORE package loads
  (setq magit-display-buffer-function
        #'magit-display-buffer-same-window-except-diff-v1)
  :config                                  ; runs AFTER package loads
  (setq magit-save-repository-buffers 'dontask)
  (setq magit-revision-show-gravatars nil)
  :custom                                  ; set via customize interface
  (magit-diff-refine-hunk 'all "Show word-level diffs")
  :hook (magit-mode . hl-line-mode)        ; add hook
  :diminish magit-auto-revert-mode)        ; hide from modeline
Keyword When It Runs Purpose
:ensure Immediately Install package if missing (t or a package name)
:defer Immediately Delay loading (t for auto, or seconds: :defer 2)
:bind Immediately Create keybindings and autoload the bound commands
:commands Immediately Register autoloads for named commands
:mode Immediately Associate file extensions with this mode
:hook Immediately Add function to a hook (implies deferred loading)
:init Before load Code that runs before the package is loaded
:config After load Code that runs after the package is loaded
:custom After load Set customizable variables with documentation

Deferred Loading

:bind, :commands, :hook, and :mode all implicitly defer loading. The package is only loaded when you first use it. This can reduce startup time from 10+ seconds to under 1 second with dozens of packages.

Error Isolation

If a use-package block throws an error, Emacs logs it and continues loading the rest of your config. Without use-package, a single typo in your init file could leave you with a half-configured editor.

In Emacs 29+, use-package is built in—no installation needed. For Emacs 28 and earlier, add (unless (package-installed-p 'use-package) (package-install 'use-package)) before your first use-package form.

007.4 straight.el (Git-Based Package Management)

straight.el replaces package.el entirely. Instead of downloading tarballs from ELPA/MELPA, it clones Git repositories directly, giving you full version control over every package. You can pin versions, make local edits, and contribute upstream—all from your ~/.emacs.d/.

straight.el Bootstrap
;; In early-init.el: disable package.el
(setq package-enable-at-startup nil)

;; In init.el: bootstrap straight.el
(defvar bootstrap-version)
(let ((bootstrap-file
       (expand-file-name "straight/repos/straight.el/bootstrap.el"
                         user-emacs-directory))
      (bootstrap-version 7))
  (unless (file-exists-p bootstrap-file)
    (with-current-buffer
        (url-retrieve-synchronously
         "https://raw.githubusercontent.com/radian-software/straight.el/develop/install.el"
         'silent 'inhibit-cookies)
      (goto-char (point-max))
      (eval-print-last-sexp)))
  (load bootstrap-file nil 'nomessage))

;; Integrate with use-package
(straight-use-package 'use-package)
(setq straight-use-package-by-default t)

;; Now :ensure uses straight.el instead of package.el
(use-package vertico
  :ensure t         ; clones from Git via straight
  :init (vertico-mode))
Lockfile for Reproducibility
;; Freeze all package versions to a lockfile
M-x straight-freeze-versions

;; Creates ~/.emacs.d/straight/versions/default.el
;; Commit this file to your dotfiles repo

;; On a new machine, after cloning your dotfiles:
M-x straight-thaw-versions
;; Checks out the exact Git commits from the lockfile
Feature package.el straight.el
Source Tarballs from ELPA/MELPA Git clones from any repo
Version pinning Limited (MELPA Stable tags) Exact commit via lockfile
Local edits Overwritten on update Full Git workflow, PRs upstream
Reproducibility Depends on archive state Fully reproducible via lockfile
Speed Faster initial install Slower clone, but builds locally

007.5 Redirecting custom-set-variables

When you use M-x customize, Emacs writes custom-set-variables and custom-set-faces blocks directly into your init.el. This clutters your hand-crafted config with machine-generated code. The solution: redirect it to a separate file.

Isolate Customize Output
;; Send customize output to a separate file
(setq custom-file (expand-file-name "custom.el" user-emacs-directory))

;; Load it if it exists (optional — some people never load it)
(when (file-exists-p custom-file)
  (load custom-file))

;; Now M-x customize writes to ~/.emacs.d/custom.el
;; Your init.el stays clean and version-controlled
Some users never load custom.el and treat M-x customize as a read-only exploration tool. They find the setting they want, then translate it to a setq or use-package :custom form in their init. This keeps the config fully declarative.
008

Elisp Essentials

defun, hooks, advice, and interactive commands

008.1 defun — Function Definition

Functions are the building blocks of Elisp. Every Emacs command, from save-buffer to forward-char, is an Elisp function. You define your own with defun, and they become first-class citizens in the editor—callable from other functions, bindable to keys, and discoverable via C-h f.

Function Anatomy
;; Basic function
(defun greet (name)
  "Greet NAME with a message."       ; docstring (shown by C-h f)
  (message "Hello, %s!" name))

;; Call it:  (greet "Emacs")  →  "Hello, Emacs!"

;; Function with optional and rest args
(defun log-event (level msg &optional timestamp &rest details)
  "Log MSG at LEVEL with optional TIMESTAMP and DETAILS."
  (let ((ts (or timestamp (current-time-string))))
    (message "[%s] %s: %s %s" ts level msg
             (if details (mapconcat #'prin1-to-string details " ") ""))))

;; Lambda (anonymous function)
(mapcar (lambda (x) (* x x)) '(1 2 3 4 5))  ; → (1 4 9 16 25)

;; Sharp-quote for named functions (enables compiler warnings)
(mapcar #'upcase '("hello" "world"))  ; → ("HELLO" "WORLD")
Always include a docstring as the first form after the argument list. It appears in C-h f help buffers and makes your functions self-documenting. Capitalize argument names in the docstring so they stand out: "Greet NAME with a message."

008.2 Variables: setq, let, let*

Elisp has two scoping mechanisms: global variables set with setq and local bindings created with let. Understanding the difference is essential—most configuration uses setq to set global options, while functions use let for temporary values.

Variable Forms
;; setq — set a global/buffer-local variable
(setq fill-column 80)                  ; set for current buffer
(setq-default indent-tabs-mode nil)    ; set default for all buffers
(setq-default tab-width 4)

;; let — parallel binding (all values computed, then bound at once)
(let ((x 10)
      (y 20))
  (+ x y))    ; → 30
;; x and y do NOT exist outside this let block

;; let* — sequential binding (each binding can see previous ones)
(let* ((x 10)
       (y (* x 2))     ; y can reference x
       (z (+ x y)))    ; z can reference both x and y
  z)    ; → 30

;; defvar / defcustom — declare a variable with documentation
(defvar my-project-root "~/code/"
  "Root directory for project searches.")

;; defcustom — declares a user-customizable variable
(defcustom my-greeting "Hello"
  "Default greeting string."
  :type 'string
  :group 'my-package)
Form Scope Use Case
setq Global (or buffer-local) Setting Emacs options and config variables
setq-default Default for all buffers Buffer-local vars like indent-tabs-mode, tab-width
let Lexical block Temporary values in functions
let* Lexical block (sequential) When later bindings depend on earlier ones
defvar Global Declaring package variables with docstrings
defcustom Global (customizable) User-facing options (appear in M-x customize)

008.3 Hooks

Hooks are lists of functions that run at specific events—when a mode activates, when a file is saved, when Emacs starts. They are the primary mechanism for customizing behavior without modifying source code. Almost every major mode defines a hook.

Hook Patterns
;; Add a function to a hook
(add-hook 'python-mode-hook #'display-line-numbers-mode)
(add-hook 'text-mode-hook #'auto-fill-mode)
(add-hook 'prog-mode-hook #'electric-pair-mode)

;; Multiple hooks with a lambda
(add-hook 'before-save-hook
  (lambda ()
    (when (derived-mode-p 'prog-mode)
      (delete-trailing-whitespace))))

;; Remove a hook
(remove-hook 'python-mode-hook #'display-line-numbers-mode)

;; Hook that runs only once (Emacs 29+)
(add-hook 'server-after-make-frame-hook #'my-load-theme nil t)

;; Depth argument — control ordering (lower = earlier)
(add-hook 'prog-mode-hook #'flyspell-prog-mode 90)  ; run late
Hook When It Fires Typical Use
after-init-hook After init.el finishes Final setup, theme loading
emacs-startup-hook After init + command-line processing Resetting GC threshold, startup dashboard
before-save-hook Before writing a file Delete trailing whitespace, format code
after-save-hook After writing a file Run linters, compile, update tags
prog-mode-hook Any programming mode activates Line numbers, electric pairs, folding
text-mode-hook Any text mode activates Auto-fill, spell checking
find-file-hook A file is opened Project-specific settings, auto-revert
kill-buffer-hook A buffer is about to be killed Cleanup, save state
kill-emacs-hook Emacs is shutting down Save history, clean temp files
Always use #'function-name (sharp-quote) with add-hook instead of plain 'function-name. The sharp-quote enables the byte compiler to warn you if the function doesn’t exist, catching typos early. For lambdas, no quoting is needed.

008.4 Advice — Modifying Existing Functions

The advice system lets you modify any function’s behavior without editing its source. You can inject code before, after, or around any function—including built-in C functions. This is one of Emacs’s most powerful extension mechanisms.

Advice Combinators
;; :before — run code BEFORE the original function
(defun my-save-cleanup (&rest _)
  "Delete trailing whitespace before saving."
  (delete-trailing-whitespace))
(advice-add 'save-buffer :before #'my-save-cleanup)

;; :after — run code AFTER the original function
(defun my-find-file-message (&rest _)
  "Report file size after opening."
  (message "Opened %s (%s bytes)" buffer-file-name (buffer-size)))
(advice-add 'find-file :after #'my-find-file-message)

;; :around — WRAP the original function (most powerful)
(defun my-time-advice (orig-fn &rest args)
  "Time how long ORIG-FN takes to execute."
  (let ((start (current-time)))
    (apply orig-fn args)
    (message "Took %.3fs" (float-time (time-since start)))))
(advice-add 'org-export-dispatch :around #'my-time-advice)

;; :override — completely REPLACE the original
(advice-add 'some-broken-fn :override #'my-fixed-version)

;; Remove advice when done
(advice-remove 'save-buffer #'my-save-cleanup)
Combinator Signature Behavior
:before (&rest args) Runs before original; cannot change arguments or return value
:after (&rest args) Runs after original; receives same args, cannot change return
:around (orig-fn &rest args) Wraps original; full control over args and return value
:override (&rest args) Replaces original entirely (original never runs)
:filter-args (args) Transform the argument list before passing to original
:filter-return (return-value) Transform the return value of the original
Use M-x describe-function on any advised function to see all active advice. The help buffer shows each advice function, its combinator, and a link to its definition. Use advice-remove to cleanly detach advice when debugging.

008.5 Interactive Commands

A function becomes an interactive command—callable via M-x and bindable to keys—when you add an (interactive) declaration. The interactive spec tells Emacs how to prompt for arguments when called interactively.

Interactive Functions
;; No arguments — simplest case
(defun my-insert-date ()
  "Insert today's date at point."
  (interactive)
  (insert (format-time-string "%Y-%m-%d")))

;; With a string argument
(defun my-wrap-region (before after)
  "Wrap region with BEFORE and AFTER strings."
  (interactive "sBefore: \nsAfter: ")
  (let ((text (buffer-substring (region-beginning) (region-end))))
    (delete-region (region-beginning) (region-end))
    (insert before text after)))

;; With prefix argument
(defun my-duplicate-line (&optional n)
  "Duplicate the current line N times."
  (interactive "p")
  (let ((line (thing-at-point 'line t)))
    (end-of-line)
    (dotimes (_ (or n 1))
      (newline)
      (insert (string-trim-right line)))))

;; Bind to a key
(global-set-key (kbd "C-c d") #'my-insert-date)
(global-set-key (kbd "C-c D") #'my-duplicate-line)
Code Prompt What It Reads
s String prompt Arbitrary string from minibuffer
n Number prompt Integer from minibuffer
p None (auto) Prefix argument as number (C-u N)
r None (auto) Region start and end as two args
f File prompt Existing file name with completion
F File prompt File name (may not exist yet)
b Buffer prompt Existing buffer name with completion
B Buffer prompt Buffer name (may not exist)
D Directory prompt Directory name with completion
Separate multiple interactive codes with \n: (interactive "sName: \nnAge: ") prompts for a string then a number. The text after each code letter is the prompt string shown to the user.

008.6 Common Patterns & Idioms

These are the recurring patterns you’ll see in virtually every Emacs configuration. Each one is a building block you can compose into more complex behavior.

Essential Elisp Idioms
;; GUI-only configuration
(when (display-graphic-p)
  (set-frame-font "JetBrains Mono 13" nil t)
  (scroll-bar-mode -1))

;; Terminal-only configuration
(unless (display-graphic-p)
  (xterm-mouse-mode 1))

;; Deferred evaluation — run code after a package loads
(with-eval-after-load 'org
  (setq org-startup-indented t)
  (setq org-hide-leading-stars t))

;; Preserve point position during operations
(save-excursion
  (goto-char (point-min))
  (while (search-forward "foo" nil t)
    (replace-match "bar")))

;; Iterate over a list
(dolist (pkg '(magit vertico orderless marginalia))
  (unless (package-installed-p pkg)
    (package-install pkg)))

;; Create a custom keymap prefix
(define-prefix-command 'my-leader-map)
(global-set-key (kbd "C-c l") 'my-leader-map)
(define-key my-leader-map (kbd "g") #'magit-status)
(define-key my-leader-map (kbd "f") #'find-file)
(define-key my-leader-map (kbd "b") #'switch-to-buffer)

;; Conditional platform checks
(when (eq system-type 'darwin)
  (setq mac-option-modifier 'meta))
(when (eq system-type 'gnu/linux)
  (setq x-select-enable-clipboard t))

save-excursion

Saves point, mark, and current buffer, executes the body, then restores them. Essential when your function needs to move around the buffer without disturbing the user’s cursor position.

with-eval-after-load

Defers code until a feature or file is loaded. More efficient than :config for one-off settings. Unlike eval-after-load (deprecated), it wraps the body in a progn automatically.

condition-case

Elisp’s error handling: (condition-case err (risky-operation) (error (message "Failed: %s" err))). Use it to gracefully handle errors in hooks and advice without crashing the editor.

cl-lib

Common Lisp extensions: cl-loop, cl-destructuring-bind, cl-defstruct. Add (require 'cl-lib) and gain powerful iteration, pattern matching, and data structures.

009

Power Workflows

Magit, Dired, project.el, Eglot, and AI integration

009.1 Magit — The Git Porcelain

Magit is widely considered the best Git interface ever built—in any editor, on any platform. It presents Git’s full power through a discoverable, keyboard-driven interface where every operation is a single keystroke from a status buffer. Many developers cite Magit as the sole reason they use Emacs.

Magit Setup
(use-package magit
  :ensure t
  :bind (("C-x g"   . magit-status)
         ("C-x M-g" . magit-dispatch)
         ("C-c M-g" . magit-file-dispatch))
  :config
  (setq magit-save-repository-buffers 'dontask)
  (setq magit-display-buffer-function
        #'magit-display-buffer-same-window-except-diff-v1))
Key Action Key Action
s Stage hunk or file u Unstage hunk or file
c c Commit (opens message buffer) c a Amend last commit
P p Push to remote F p Pull from remote
b b Checkout branch b c Create new branch
l l Log (current branch) l a Log (all branches)
d d Diff (unstaged changes) d s Diff (staged changes)
r i Interactive rebase r e Rebase elsewhere
z z Stash (including untracked) z p Pop stash
k Discard change at point v Reverse change at point
TAB Toggle section visibility ? Show all available commands

Hunk-Level Staging

Move point to any diff hunk in the status buffer and press s to stage just that hunk—not the whole file. Press TAB to expand a file and see individual hunks. This gives you finer control than git add -p with zero friction.

Discoverable Interface

Press ? anywhere in Magit to see all available commands grouped by category. Each transient popup shows its keybindings, arguments (toggle with - prefix flags), and a preview of what will run. You never need to memorize Git flags.

In the commit message buffer, C-c C-c finalizes the commit and C-c C-k aborts. Use M-p and M-n to cycle through previous commit messages. Magit also respects .gitmessage templates and commit hooks.

009.2 Dired — The Directory Editor

Dired turns a directory listing into an editable buffer. You can rename, copy, delete, compress, diff, and search files using the same keybindings you use for text editing. With WDired, you can even rename files by editing the buffer directly—including bulk renames with search-and-replace.

Key Action Description
RET Open Open file or enter directory
^ Parent Go to parent directory
+ Create dir Create a new subdirectory
m Mark Mark file for batch operation
u Unmark Remove mark from file
U Unmark all Remove all marks
d Flag delete Flag file for deletion
x Execute Perform all flagged deletions
C Copy Copy marked files (prompts for destination)
R Rename/Move Rename or move marked files
D Delete Immediately delete marked files
! Shell command Run a shell command on marked files
g Refresh Refresh the directory listing
s Sort toggle Toggle between name and date sorting
WDired — Writable Dired
;; In a Dired buffer:
;; C-x C-q  — Enter WDired mode (filenames become editable text)
;; Now use normal editing: search-replace, multiple-cursors, macros
;; C-c C-c  — Apply all renames
;; C-c C-k  — Abort changes

;; Example: rename all .jpeg to .jpg
;; 1. Open directory in Dired: C-x d ~/photos RET
;; 2. C-x C-q to enter WDired
;; 3. M-% .jpeg RET .jpg RET ! (query-replace all)
;; 4. C-c C-c to apply

;; Useful Dired settings
(setq dired-dwim-target t)     ; suggest other Dired window as target
(setq dired-listing-switches "-alh")  ; human-readable sizes
(setq delete-by-moving-to-trash t)    ; use trash instead of rm
With dired-dwim-target set to t, having two Dired windows side by side makes Copy (C) and Rename (R) automatically suggest the other window’s directory as the target—mimicking a dual-pane file manager like Midnight Commander.

009.3 project.el — Project-Wide Operations

project.el (built-in since Emacs 28) provides project-aware commands under the C-x p prefix. It auto-detects projects by looking for version control roots (.git, .hg) and gives you instant access to project-scoped file finding, grep, compilation, and shell commands.

Keybinding Command Description
C-x p p Switch project Select a known project and choose an action
C-x p f Find file Find any file in the current project
C-x p g Grep Search for a regex across all project files
C-x p r Query-replace Search and replace across the entire project
C-x p d Dired Open Dired at the project root
C-x p e Eshell Open Eshell at the project root
C-x p c Compile Run compile command from the project root
C-x p k Kill buffers Kill all buffers belonging to the project
C-x p b Switch buffer Switch to another buffer in the same project
C-x p s Shell Open a shell at the project root
Project Configuration
;; Remember projects across sessions
(setq project-list-file
      (expand-file-name "projects" user-emacs-directory))

;; Add custom project root markers (beyond just .git)
(setq project-vc-extra-root-markers
      '(".project" "Makefile" "package.json" "Cargo.toml"))

;; Custom action when switching projects
(setq project-switch-commands
      '((project-find-file "Find file" ?f)
        (project-dired "Dired" ?d)
        (project-eshell "Eshell" ?e)
        (magit-project-status "Magit" ?g)
        (project-find-regexp "Grep" ?r)))
Combine project.el with Vertico and Orderless for a fuzzy-finding experience that rivals VS Code’s Ctrl+P. C-x p f then type fragments in any order: comp test rs finds src/compiler/test_parser.rs.

009.4 Eglot & Tree-sitter — Modern IDE Features

Emacs 29 shipped two transformative built-in features: Eglot (an LSP client) and tree-sitter integration (fast, incremental parsing). Together they give Emacs feature parity with VS Code for code intelligence—completions, diagnostics, rename, go-to-definition—without any external plugins.

Eglot Configuration
;; Eglot — built-in LSP client (Emacs 29+)
(use-package eglot
  :hook ((python-ts-mode  . eglot-ensure)
         (rust-ts-mode    . eglot-ensure)
         (js-ts-mode      . eglot-ensure)
         (typescript-ts-mode . eglot-ensure)
         (go-ts-mode      . eglot-ensure)
         (c-ts-mode       . eglot-ensure)
         (c++-ts-mode     . eglot-ensure))
  :config
  (setq eglot-autoshutdown t)    ; shut down server when last buffer closes
  (setq eglot-events-buffer-size 0)  ; disable event logging for performance
  ;; Custom server commands
  (add-to-list 'eglot-server-programs
               '(python-ts-mode . ("pyright-langserver" "--stdio"))))
Keybinding Action Description
M-. Find definition Jump to the definition of the symbol at point
M-? Find references List all references to the symbol at point
C-c C-r Rename Rename symbol across the entire project
C-c C-a Code actions Show available code actions (quick fixes, refactors)
M-, Go back Return to where you were before M-.
C-c C-d Documentation Show hover documentation at point
M-x eglot-format Format Format buffer or region using the language server
M-x flymake-show-diagnostics-buffer Diagnostics Show all errors and warnings in a list
Tree-sitter Setup
;; Tree-sitter — fast incremental parsing (Emacs 29+)
;; Remap traditional modes to tree-sitter variants
(setq major-mode-remap-alist
      '((python-mode     . python-ts-mode)
        (javascript-mode . js-ts-mode)
        (typescript-mode . typescript-ts-mode)
        (json-mode       . json-ts-mode)
        (css-mode        . css-ts-mode)
        (yaml-mode       . yaml-ts-mode)
        (rust-mode       . rust-ts-mode)
        (go-mode         . go-ts-mode)
        (c-mode          . c-ts-mode)
        (c++-mode        . c++-ts-mode)
        (bash-mode       . bash-ts-mode)))

;; Install tree-sitter grammars automatically
(setq treesit-language-source-alist
      '((python "https://github.com/tree-sitter/tree-sitter-python")
        (javascript "https://github.com/tree-sitter/tree-sitter-javascript")
        (typescript "https://github.com/tree-sitter/tree-sitter-typescript"
                    "master" "typescript/src")
        (rust "https://github.com/tree-sitter/tree-sitter-rust")
        (go "https://github.com/tree-sitter/tree-sitter-go")))

;; Install all grammars: M-x treesit-install-language-grammar

Why Tree-sitter?

Traditional Emacs font-lock uses regex-based highlighting, which breaks on complex syntax. Tree-sitter builds a real syntax tree, giving you accurate highlighting, structural navigation (C-M-f/C-M-b move by syntax node), and the foundation for smarter refactoring tools.

Eglot vs LSP-mode

Eglot is minimal and built-in—it does one thing well with zero configuration for supported languages. lsp-mode is a third-party alternative with more features (breadcrumbs, lens, semantic highlighting) but more complexity. Start with Eglot; switch to lsp-mode only if you need its extras.

009.5 AI Integration

Emacs has a growing ecosystem of AI-powered tools that integrate large language models directly into your editing workflow. Unlike IDE plugins that add a chat sidebar, Emacs AI packages integrate at the buffer level—you can send regions, replace text in-place, and pipe AI output through the same tools you use for everything else.

gptel — Multi-Provider LLM Client
;; gptel — supports OpenAI, Anthropic, Ollama, and more
(use-package gptel
  :ensure t
  :bind (("C-c g" . gptel-send)         ; send region or buffer to LLM
         ("C-c G" . gptel))              ; open dedicated chat buffer
  :config
  ;; Configure Claude as a backend
  (gptel-make-anthropic "Claude"
    :stream t
    :key 'gptel-api-key-from-auth-source)

  ;; Configure local Ollama
  (gptel-make-ollama "Ollama"
    :host "localhost:11434"
    :stream t
    :models '(llama3:latest mistral:latest))

  ;; Set default model
  (setq gptel-model 'claude-sonnet-4-20250514
        gptel-backend (gptel-make-anthropic "Claude"
                        :stream t :key 'gptel-api-key-from-auth-source)))
Other AI Packages
;; ellama — Ollama-focused, good for local models
(use-package ellama
  :ensure t
  :init
  (setopt ellama-language "English")
  (require 'llm-ollama)
  (setopt ellama-provider
          (make-llm-ollama :chat-model "llama3:latest"
                           :embedding-model "nomic-embed-text")))

;; copilot.el — GitHub Copilot inline completions
(use-package copilot
  :ensure t
  :hook (prog-mode . copilot-mode)
  :bind (:map copilot-completion-map
              ("TAB"   . copilot-accept-completion)
              ("C-TAB" . copilot-accept-completion-by-word)
              ("C-n"   . copilot-next-completion)
              ("C-p"   . copilot-previous-completion)))

;; Aider.el — AI pair programming
;; Runs aider CLI in a comint buffer
(use-package aider
  :ensure t
  :config
  (setq aider-args '("--model" "claude-sonnet-4-20250514"))
  :bind ("C-c a" . aider-transient-menu))
C-c g (gptel-send)
Send the current region (or buffer, if no region) to the LLM. The response streams directly into your buffer at point.
C-u C-c g
Send with a transient menu: choose the model, set a system prompt, toggle streaming, or select a directive (rewrite, explain, etc.).
C-c G (gptel)
Open a dedicated chat buffer. Supports multi-turn conversations with full Markdown rendering.
gptel-rewrite
Select a region and ask the LLM to rewrite it. The original text is replaced in-place with the LLM’s response.
Store API keys securely using auth-source (Emacs’s built-in credential manager). Add a line to ~/.authinfo.gpg: machine api.anthropic.com login apikey password sk-ant-.... Then gptel retrieves it automatically without hardcoding secrets in your init file.
010

The Emacs Philosophy

Self-documenting, extensibility, and why veterans stay

010.1 The Self-Documenting Editor

Emacs calls itself "the self-documenting editor," and this is not marketing. It is an architectural commitment that has no parallel in any other software. Every function, every variable, every keybinding, every mode—all are introspectable at runtime, from within the editor itself.

Keybinding Command What It Reveals
C-h k describe-key Press any key sequence and see exactly what function it calls, with full documentation
C-h f describe-function Look up any function—its docstring, argument list, and a clickable link to its source code
C-h v describe-variable Inspect any variable’s current value, default value, and documentation
C-h m describe-mode Show every keybinding active in the current buffer, organized by mode
C-h a apropos-command Fuzzy-search all interactive commands by name or pattern
C-h i info Open the Info manual browser—the full documentation system for Emacs and GNU tools
C-h b describe-bindings List every single active keybinding in the current context
From any help buffer, you can click the source link to jump directly into the Elisp code that implements that function. You can read it, modify it, even eval-defun a patched version on the spot. No recompilation. No restart. No other editor comes close to this level of transparency.
;; Discover what C-x C-s does
C-h k C-x C-s
;; => save-buffer: Save current buffer in visited file if modified.

;; From the help buffer, click the source link
;; => You're now reading the Elisp implementation of save-buffer
;; => You can modify it, re-evaluate it, and it takes effect immediately

010.2 Extensibility as a First Principle

Emacs is not a text editor with an extension language bolted on. It is a Lisp interpreter that happens to edit text. The C core provides just enough primitives—buffer management, display rendering, file I/O—and then everything else is implemented in Elisp. The mode line, the minibuffer, the syntax highlighter, the undo system, even self-insert-command (the function that types a character) are all Elisp functions you can inspect and override.

The Advice System
;; Modify ANY function without touching its source code
;; :before advice runs before the original
(defun my/announce-save ()
  "Flash the mode line when saving."
  (message "Saving %s..." (buffer-name)))

(advice-add 'save-buffer :before #'my/announce-save)

;; :around advice wraps the original — you control if/when it runs
(defun my/confirm-kill (orig-fn &rest args)
  "Ask before killing Emacs."
  (when (yes-or-no-p "Really quit Emacs? ")
    (apply orig-fn args)))

(advice-add 'save-buffers-kill-emacs :around #'my/confirm-kill)
Hooks — Customizing Every Mode
;; Hooks fire at specific lifecycle points
;; Every mode has a hook: python-mode-hook, org-mode-hook, etc.
(add-hook 'prog-mode-hook #'display-line-numbers-mode)
(add-hook 'text-mode-hook #'visual-line-mode)
(add-hook 'before-save-hook #'delete-trailing-whitespace)

;; Even buffer creation and killing have hooks
(add-hook 'after-init-hook #'my/startup-dashboard)
(add-hook 'kill-buffer-hook #'my/cleanup-function)
The gap between “user” and “developer” doesn’t exist in Emacs. If you can describe what you want, you can build it. Every user’s init.el is a program. Every customization is code. Every workflow improvement is permanent and shareable.

010.3 The Cathedral of Text

Emacs was first written in 1976. GNU Emacs shipped in 1985. It has been in continuous development for nearly fifty years. Why does it endure when hundreds of editors have come and gone?

Backward Compatibility

Steve Yegge’s Elisp code from 1995 still runs unchanged in Emacs 30. The Emacs maintainers treat backward compatibility as sacrosanct. Your configuration is not disposable—it is an investment that compounds over decades. No other editor has honored this contract for this long.

Customization Without Plugin SDKs

In VS Code, building an extension means learning a separate API, packaging with npm, publishing to a marketplace. In IntelliJ, you navigate a labyrinth of PSI trees and extension points. In Emacs, you open your init file, write some Elisp, and evaluate it. The barrier to customization is trivially low because the extension language is the implementation language.

The Growing Substrate

When a package becomes essential, it gets absorbed into Emacs core. org-mode, eglot, use-package, project.el—all started as community packages and became built-in. The platform grows by accretion, becoming richer without breaking what came before. This is collaborative cathedral-building at its finest.

The "Emacs Pinky"
The famous complaint about Ctrl-heavy keybindings is solved in seconds: remap Caps Lock to Ctrl at the OS level. On Linux: setxkbmap -option ctrl:nocaps. On macOS: System Preferences → Keyboard → Modifier Keys. Problem solved since 1990.
Emacs took being a perfectly open platform more seriously than any other editor. It is not an editor—it is a platform for building the editor you need. The source code is the documentation. The documentation is the interface. The interface is the source code.

010.4 The Multiplexer Dream

Steve Yegge once described Emacs as “a platform written in 1976 for writing software to make you more productive, masquerading as a text editor.” He called it “a sort of hybrid between Windows Notepad, a monolithic-kernel operating system, and the International Space Station.”

Yegge dreamed of building the ultimate terminal multiplexer in Emacs—a workspace system that would unify shells, editors, and tools under a single coherent interface. But look at what already exists:

vterm

A full terminal emulator inside Emacs, powered by libvterm. It handles curses apps, 256 colors, and shell integration. Your terminal lives alongside your code in the same frame.

tab-bar-mode

Workspace tabs at the top of the frame, each with its own set of windows and buffers. Switch contexts with a click or C-x t prefix. Workspaces persist across sessions.

winner-mode

Undo and redo window layout changes with C-c <left> and C-c <right>. Every split, resize, and close is recorded. Your layout has a history, just like your text.

TRAMP

Edit files on remote machines as if they were local. SSH, Docker containers, sudo—all transparent. C-x C-f /ssh:host:/path just works. The remote file is a local buffer.

If Yegge had the time, he’d build an Emacs terminal multiplexer. But look at what’s already there: full terminal emulation, workspace tabs, layout history, remote access—all sharing the same Elisp substrate and keybinding system. The multiplexer isn’t missing. It’s been hiding in plain sight.
;; The multiplexer that already exists
;; Enable workspace tabs
(tab-bar-mode 1)
(setq tab-bar-show 1)  ; show tab bar only when > 1 tab

;; Enable layout undo
(winner-mode 1)

;; Named workspaces
(tab-bar-rename-tab "code")            ; rename current tab
(tab-bar-new-tab)                       ; create new tab
(tab-bar-rename-tab "shell")
(vterm)                                 ; open terminal here

;; Switch workspaces
(tab-bar-switch-to-tab "code")         ; by name
C-x t o                                ; next tab
C-x t p                                ; previous tab

;; Remote access — same keybindings, same experience
C-x C-f /ssh:prod-server:/var/log/app.log
C-x C-f /docker:container-id:/app/src/main.py

010.5 Why Veterans Stay

Ask a 20-year Emacs user why they haven’t switched, and they won’t talk about features. They’ll talk about compound interest. Every keybinding refined, every function written, every workflow automated—it all accumulates in a single init.el that grows with them across jobs, languages, and decades.

Unified Environment

Email (mu4e), notes (Org), Git (Magit), terminal (vterm), files (Dired), web (eww), RSS (elfeed)—all share the same keybindings, the same buffer model, the same Elisp glue. Context-switching cost drops to zero.

Compound Interest

Every workflow refinement persists forever. A snippet you wrote in 2005 still works. A keybinding you added for a job three companies ago still fires. Your configuration is a living document of your entire career.

Full Introspection

When something behaves unexpectedly, you don’t file a bug report and wait. You C-h k the key, read the source, trace the logic, and fix it yourself. Understanding is always one keystroke away.

Stability

Configurations from decades ago still work. The Emacs team does not break your workflow with “bold new visions.” They add. They rarely remove. Your investment is safe.

The Last Tool

Emacs doesn’t try to be the best at any one thing. It tries to be the last tool you’ll ever need to learn. Not because it does everything perfectly, but because it gives you the power to make it do everything your way. The initial learning curve is steep. The return on that investment is lifelong.

Other editors optimize for the first hour. Emacs optimizes for the next thirty years.