Emacs Lisp is a dialect of the Lisp programming language primarily used for extending and customizing the GNU Emacs text editor. It is dynamically typed, features automatic memory management, and provides a comprehensive set of functions for manipulating buffers, windows, keymaps, and other editor constructs. Emacs Lisp code can be loaded interactively or at startup to modify almost every aspect of Emacs behavior.
1 History
1.1 Origins in TECO and early Lisp
The earliest extensible text editors, such as TECO (Text Editor and Corrector), allowed users to write command macros to automate editing tasks. In the late 1970s, Richard Stallman and others at the MIT AI Lab began developing Emacs (Editor MACroS) as a set of macros for TECO. As Emacs grew, the need for a more powerful and expressive extension language became apparent. The Lisp programming language, known for its symbolic computation and flexible syntax, was chosen as the basis for this extension language.
1.2 Development of GNU Emacs
In 1984, Richard Stallman launched the GNU Project with the goal of creating a free Unix-like operating system. As part of this effort, he rewrote Emacs from scratch. The new editor, GNU Emacs, used a Lisp dialect called Emacs Lisp as its extension language. Early versions of GNU Emacs (released starting in 1985) included a Lisp interpreter that allowed users to write custom commands and modify editor behavior without recompiling the C core. This design established Emacs Lisp as a central component of GNU Emacs.
1.3 Standardization and Evolution
Over the decades, Emacs Lisp has evolved alongside GNU Emacs. While no formal standard analogous to Common Lisp or Scheme exists for Emacs Lisp, the language is defined by the implementation in the GNU Emacs source code. Major releases have introduced new features such as lexical binding (Emacs 24), native compilation (Emacs 29), and improved concurrency mechanisms. The community maintains a comprehensive reference manual and encourages backward compatibility.
2 Syntax and Semantics
2.1 Basic Syntax
2.1.1 S-expressions and Parentheses
Emacs Lisp code is written as symbolic expressions (S-expressions). An S-expression can be an atom (e.g., a number, a symbol, a string) or a list enclosed in parentheses. Lists are the primary data structure for both code and data; a function call is a list where the first element is the function name and the remaining elements are arguments. For example, (+ 1 2) is a list that evaluates to the sum of 1 and 2.
2.1.2 Comments and Whitespace
Comments in Emacs Lisp begin with a semicolon (;) and extend to the end of the line. Multi-line comments are not natively supported, but string literals or blocks of commented lines are used instead. Whitespace (spaces, tabs, newlines) is insignificant for syntax except for separating tokens. Indentation is conventionally used to improve readability.
2.1.3 Quoting and Evaluation
The Lisp evaluator processes S-expressions: atoms (except symbols) evaluate to themselves; symbols evaluate to their current variable binding; and lists are evaluated as function calls. The quote operator ' (or the quote special form) prevents evaluation. For example, '(a b c) evaluates to the list (a b c) without treating a, b, or c as variables.
2.2 Data Types
2.2.1 Numbers
Emacs Lisp supports integers and floating-point numbers. Integers are fixnums (usually 64-bit) or bignums as needed. Floating-point numbers follow IEEE double precision. Arithmetic operations such as +, -, *, / are provided, along with comparison predicates (=, <, etc.). Common mathematical functions (sin, cos, sqrt, etc.) are also available.
2.2.2 Strings
Strings are sequences of characters enclosed in double quotes. They support a comprehensive set of operations including concatenation (concat), substring extraction (substring), case conversion (upcase, downcase), and regular expression matching. Strings in Emacs Lisp are mutable (characters can be changed via aset), but doing so may be inefficient for long strings.
2.2.3 Symbols
Symbols are atomic objects used as names for variables, functions, and keys. They can be interned (stored in an obarray for fast lookup) or uninterned. Symbols have property lists (plists) that allow arbitrary key-value pairs to be attached. For example, the symbol font-lock-keywords may carry properties such as :group.
2.2.4 Lists and Cons Cells
Lists are built from cons cells, which are pairs of pointers (car and cdr). A proper list is either the empty list nil or a cons cell whose cdr points to another list. Operations for constructing lists (list, cons), accessing elements (car, cdr, nth), and modifying them (setcar, setcdr) are common. Lists can represent both data (e.g., (&optional nocolor)) and Lisp programs.
2.2.5 Vectors and Hash Tables
Vectors are fixed-length arrays of objects, created with (vector ...). Elements can be accessed and modified by index using aref and aset. Hash tables provide key-value storage with fast lookup. They are created with (make-hash-table) and support operations like gethash, puthash, and maphash.
2.2.6 Buffers and Windows
Buffers represent text containers within Emacs. They have attributes like name, mode, point (cursor position), and mark. Windows are viewports onto buffers. Emacs Lisp provides functions to create, select, and manipulate buffers (with-current-buffer, get-buffer-create) and windows (split-window, selected-window). Buffers and windows are first-class objects, though they are not printable.
2.2.7 Process and Marker Objects
Process objects represent subprocesses spawned by Emacs (e.g., a shell or compiler). They support input/output via sentinels and filters. Markers are special pointers into a buffer that adjust automatically when text is inserted or deleted. They are used to track positions across editing operations.
2.2.8 Custom Types (Closures, Font-Lock, etc.)
Emacs Lisp allows the creation of custom data types using record types (cl-defstruct or record). Common custom types include closures (lexical functions that capture variables), font-lock patterns (used for syntax highlighting), and keymap structures. These are implemented as vectors or lists internally.
3 Programming Constructs
3.1 Variables and Scoping
3.1.1 Dynamic vs. Lexical Binding
Historically, Emacs Lisp used only dynamic (indefinite) scoping: a variable’s binding is determined by the calling context at runtime. Since Emacs 24, lexical binding (where the variable’s scope is determined by the source code structure) is available by adding a file-local variable lexical-binding: t. Lexical binding improves performance and enables closures. Many modern packages use lexical binding.
3.1.2 Buffer-Local Variables
Some variables can have different values in different buffers. For example, fill-column may be 70 in one buffer and 80 in another. Buffer-local variables are created using make-local-variable or setq-local. Major modes often set buffer-local variables to configure behavior for a specific file type.
3.1.3 Frame-Local Variables
A frame is a GUI window or terminal frame. Frame-local variables allow different values per frame, affecting aspects like font size or cursor color. They are less commonly used than buffer-local variables but are supported via make-variable-frame-local.
3.2 Functions
3.2.1 Defining Functions (defun)
Functions are defined with defun, which takes a name, an argument list, an optional docstring, and a body. Example: (defun my-add (a b) "Add two numbers." (+ a b)). Functions can have optional (&optional), rest (&rest), and keyword arguments (via &key in combination with cl-defun).
3.2.2 Lambda Expressions
Anonymous functions are created with lambda. A lambda form is a list starting with lambda, followed by an argument list and body. Lambdas can be assigned to variables or passed as arguments. Example: (setq my-func (lambda (x) (* x x))).
3.2.3 Interactivity and Commands
A function becomes an interactive command when its docstring or the interactive specifier is given. The (interactive) special form (placed as the first form in the body) tells Emacs that the function can be called by the user (e.g., via M-x). It may include a code string to specify how to read arguments from the user.
3.2.4 Advice and Advising Functions
Advice allows modifying existing functions without redefining them. The modern interface uses advice-add and advice-remove, with before, after, around, and other types of advice. For example, (advice-add 'save-buffer :before 'my-before-save) runs a custom function before saving.
3.3 Macros
3.3.1 Defining Macros (defmacro)
Macros are functions that transform code before evaluation. They are defined with defmacro, similar to defun but returning a new S-expression. Macros allow syntactic abstraction, such as creating new control structures. Example: (defmacro my-when (test &rest body) "If TEST is non-nil, evaluate BODY." (list 'if test (cons 'progn body))).
3.3.2 Backquote and Comma
The backquote (` `) simplifies macro construction. Within a backquoted expression, a comma (,) unquotes the following form so it is evaluated. ,@ splices a list. Example: (defmacro my-when (test &rest body) (if ,test (progn ,@body)))``.
3.3.3 Common Macro Patterns
Common macros in Emacs Lisp include with-current-buffer, save-excursion, dolist, and dotimes. These provide convenience around common patterns like saving point position or iterating over a list.
3.4 Conditionals and Loops
3.4.1 if, cond, and when
The if special form has the shape (if test then-form else-form?). cond is a multi-branch conditional: (cond (test1 body1) (test2 body2) ... (t default)). when and unless are macros that combine if with progn for a single branch. and and or are short-circuit logical operators.
3.4.2 while, dolist, and dotimes
The while loop repeats a body while a condition is non-nil: (while condition body...). dolist iterates over list elements: (dolist (var list &optional result) body...). dotimes iterates from 0 to an integer: (dotimes (var count &optional result) body...). All three are macros that expand into while loops.
3.4.3 Recursion and Tail Call Optimization
Emacs Lisp supports recursion, but it does not guarantee tail call optimization. Deep recursion may cause a stack overflow. Iterative constructs or explicit trampolining are preferred for indefinite repetition. Some libraries (e.g., cl-lib) provide loop macros for iteration.
4 Editor Integration
4.1 Buffers and Text Manipulation
4.1.1 Buffer Basics (point, mark, region)
Every buffer has a current position called point (an integer counting characters from the beginning of the buffer). The mark is another position; together they define the region. Functions like point, mark, region-beginning, and region-end retrieve these values. The region is highlighted when the mark is active.
4.1.2 Inserting and Deleting Text
Text is inserted with insert (inserts a string) or insert-char. Deletion functions include delete-region (deletes characters between two positions), kill-region (deletes and copies to the kill ring), and delete-char (deletes a character at point). Many editing commands operate on the current buffer.
4.1.3 Searching and Replacing
Emacs Lisp provides string search (string-match, search-forward), regular expression search (re-search-forward, re-search-backward), and replacement functions (replace-match). The query-replace family of commands is interactive. Searching can be performed using re-search-forward which moves point and returns the matched position or nil.
4.1.4 Syntax Tables and Font Lock
Syntax tables define how Emacs parses characters (e.g., which are word constituents, parentheses, whitespace). Font Lock is a system for syntax highlighting: major modes assign faces to tokens based on regular expressions in font-lock-keywords. Packages can add patterns to fontify code, comments, and strings.
4.2 Windows and Frames
4.2.1 Window Configuration and Splitting
Windows display buffers. Functions split-window-below, split-window-right, and delete-window rearrange windows. The window configuration (size, arrangement) can be saved and restored with current-window-configuration and set-window-configuration.
4.2.2 Displaying Buffers
The function display-buffer shows a buffer in a window, obeying display actions specified in display-buffer-alist. The pop-to-buffer function selects the buffer. Buffer display can be customized to show buffers in specific windows, frames, or tabs.
4.2.3 Frame Management
Frames are top-level windows (GUI windows or TTY screens). Functions like make-frame, delete-frame, and select-frame manage them. Frame parameters control appearance (title, position, size, font). Emacs can run in a single frame (TTY) or multiple frames (GUI).
4.3 Keymaps and Key Bindings
4.3.1 Global and Local Keymaps
Keymaps map key sequences to commands. The global keymap (global-map) applies in all buffers. Major and minor modes have their own keymaps. When a key is pressed, Emacs searches the minor mode keymaps, then the local (major mode) keymap, then the global keymap.
4.3.2 Defining Key Sequences
Key sequences can be defined with define-key or keymap-set. Example: (define-key global-map (kbd "C-c m") 'my-command). Key sequences can include modifiers like C- (Control), M- (Meta/Alt), S- (Shift), and function keys.
4.3.3 Minor Modes and Major Modes
Minor modes are optional features that can be toggled on/off; they often provide their own keymap (e.g., auto-fill-mode, flyspell-mode). Major modes define syntax highlighting, indentation, and key bindings for a specific file type (e.g., python-mode, c-mode). Both use keymaps to override global bindings.
4.4 Hooks and Mode Hooks
Hooks are lists of functions that are called at specific points, such as after a buffer is loaded or before saving. Major modes define a mode hook (e.g., python-mode-hook) that is run when the mode is activated. Users add functions to hooks with add-hook. Common hooks include after-init-hook, find-file-hook, and kill-emacs-hook.
4.5 Customizing User Interface
4.5.1 Minibuffer and Completion
The minibuffer is a one-line input area at the bottom of the frame. It reads user input for commands like M-x, file prompts, and more. Completion (tab completion) is provided by functions like completing-read and read-buffer. The minibuffer keymap and various completion styles (e.g., Icomplete, Vertico) enhance usability.
4.5.2 Mode Line and Header Line
The mode line displays information about the current buffer (mode, file name, position, etc.). It can be customized via the mode-line-format variable. The header line is an optional extra line above the buffer area, often used for column headers or navigation.
4.5.3 Toolbars and Menus
GUI frames can display a toolbar (icon bar) with buttons for common actions. The toolbar is defined by tool-bar-map. Menus are built using menu-bar-mode and easy-menu-define. Emacs Lisp allows creating custom menus with submenus, keyboard shortcuts, and enable/disable conditions.
5 Development and Tooling
5.1 Byte Compilation and Native Compilation
5.1.1 Byte-Compiler (byte-compile)
Emacs Lisp source files can be byte-compiled into .elc files. The byte-compiler (byte-compile-file) produces faster-loading code. Byte-compiled files use a compact virtual machine instruction set. The byte compiler also checks for errors, unused variables, and potential inefficiencies.
5.1.2 Native Compilation (libgccjit)
Since Emacs 28, native compilation (using libgccjit) translates Emacs Lisp functions directly to machine code, significantly improving execution speed. It is enabled with native-compile and native-comp-driver. The resulting .eln files are stored in a cache directory. Native compilation can be done eagerly for all packages or just-in-time.
5.1.3 Performance Considerations
Byte-compiled code is generally faster than interpreted code, but native compilation offers the best performance. Memory allocation, frequent garbage collections, and inefficient algorithms can still cause slowdowns. Profiling (see 5.2.2) is used to identify bottlenecks.
5.2 Debugging Emacs Lisp
5.2.1 edebug and debug-on-error
The primary interactive debugger is Edebug, which can step through function calls, set breakpoints, and inspect variables. It is entered by instrumenting a function with edebug-defun. The variable debug-on-error causes the debugger to pop up on any error. The backtrace buffer shows the call stack.
5.2.2 Profiling with profiler
The built-in profiler (profiler-start, profiler-report) collects data on CPU time and memory allocation per function. It produces a profile report that shows which functions consume the most resources. Useful for performance tuning.
5.2.3 Edebug and Tracing
Edebug provides watchpoints (edebug-visit-eval) and tracing (edebug-trace). Tracing prints execution information to the *trace-output* buffer. For simpler debugging, message inserts text into the *Messages* buffer. The ert unit test framework also integrates with debugging.
5.3 Package Management
5.3.1 ELPA and MELPA
ELPA (Emacs Lisp Package Archive) is the official repository maintained by the GNU project. MELPA (Milkypostman’s Emacs Lisp Package Archive) is a large community archive with many third-party packages. Both are accessed via package-archives. Other repositories include GNU ELPA devel and NonGNU ELPA.
5.3.2 package.el and use-package
Package installation is managed by package.el (built-in). Commands like package-install, list-packages, and package-refresh-contents are used. The use-package macro provides a declarative way to configure packages, autoloads, and key bindings. It integrates with package.el and quelpa.
5.3.3 Package Development and .el Files
Packages are typically single .el files or multi-file bundles with a .tar archive. Conventions include a proper header (Package-Requires, Version, Keywords), a Commentary section, and a Change Log. The flymake and checkdoc tools help ensure code quality.
5.4 Documentation Standards
5.4.1 Docstrings and Info Manuals
Every defined function, variable, and macro should have a docstring (a string literal immediately after the name/argument list). Docstrings are displayed by describe-function and describe-variable. Complex libraries often generate Info manuals using texinfo source files.
5.4.2 Commentary Sections
In package .el files, the Commentary section (after the initial header and before the code) describes the purpose, usage, and dependencies. It is displayed by describe-package. It should be clear and concise.
5.4.3 Compiler Warnings and linting
The byte compiler emits warnings for potential issues (unused variables, missing docstrings, undefined functions). Tools like checkdoc enforce style and documentation conventions. Additional linters include elisp-lint and relint for regular expression checking.
6 Common Libraries and Extensions
6.1 cl-lib (Common Lisp Emulation)
cl-lib provides Common Lisp–inspired features such as cl-loop, cl-destructuring-bind, cl-flet, cl-symbol-macrolet, and type predicates (cl-typep). It is the recommended successor to the deprecated cl package. It is widely used for more expressive loop constructs and data structures.
6.2 seq (Sequence Operations)
The seq library defines generic functions for sequences (lists, vectors, strings, and cons cells) including seq-map, seq-filter, seq-reduce, seq-take, and seq-into. It works with any sequence type and is extensible via cl-defgeneric.
6.3 map (Mapping Over Sequences)
The map library provides functions that operate on association lists, hash tables, and alists. Functions include map-elt, map-put!, map-keys, and map-values. It abstracts over different key-value storage types.
6.4 subr-x (Helper Macros)
subr-x offers convenience macros and functions: when-let, if-let, thread-first, thread-last, and-let*, and hash-table-keys. These simplify common patterns like threading expressions through function calls and handling optional variables.
6.5 eieio (Object-Oriented Programming)
EIEIO (Enhanced Implementation of Emacs Interpreted Objects) provides an object-oriented system with classes, methods, and inheritance. It supports defclass, defmethod, and make-instance. EIEIO is used by some packages for structured data and polymorphism, though cl-structs (cl-defstruct) are more common.
6.6 generator (Generators and Iteration)
The generator library implements Python-style generators using the iter macro. A generator function can yield values lazily using (iter-yield value). It enables efficient iteration over infinite or large sequences without constructing the entire sequence in memory.