Emacs Lisp Style Guide Skill
This skill provides expert guidance for writing professional, maintainable Emacs Lisp code following the community-driven Emacs Lisp Style Guide.
When to Use This Skill
Use this skill when:
- Writing Emacs configuration files (~/.emacs.d/init.el)
- Creating Emacs packages or libraries
- Developing major or minor modes
- Writing interactive commands and functions
- Creating keybindings and hooks
- Refactoring existing Emacs Lisp code
- Setting up package metadata and autoloads
- Writing Emacs Lisp macros
- Creating custom variables and faces
Core Principles
1. Source Code Layout
Indentation and Spacing
Use Spaces, Never Tabs:
;; Good - spaces for indentation
(defun my-function (arg1 arg2)
(let ((result (+ arg1 arg2)))
(message "Result: %s" result)))
;; Bad - mixing tabs and spaces (never do this)
Align Function Arguments Vertically:
;; Good - arguments aligned
(defun long-function-name (arg1
arg2
arg3)
(body))
;; Good - first arg on new line, aligned with function name
(defun long-function-name
(arg1 arg2 arg3)
(body))
;; Bad - inconsistent alignment
(defun long-function-name (arg1
arg2
arg3)
(body))
Special Form Indentation:
;; Special forms use 4-space indent for special arguments,
;; 2-space for body
;; Good - if with proper indentation
(if some-condition
(do-something) ; 4 spaces for then-clause
(do-else)) ; 2 spaces for else-clause
;; Good - let bindings
(let ((var1 value1)
(var2 value2)) ; 4 spaces for bindings
(body-form-1) ; 2 spaces for body
(body-form-2))
;; Good - when (no special argument)
(when condition
(do-something)
(do-more))
Maximum Line Length:
;; Aim for 80 characters per line where reasonable
;; Break long lines logically
;; Good
(defun my-function ()
"Short description."
(let ((long-variable-name
(some-function-that-returns-value)))
long-variable-name))
;; Acceptable for long strings
(message "This is a very long message that exceeds 80 characters but is kept on one line for readability")
Parentheses:
;; Good - space between text and opening paren
(defun my-function (arg)
(list arg))
;; Bad - no space after function name
(defun my-function(arg)
(list arg))
;; Good - all closing parens on same line
(defun my-function ()
(when condition
(let ((x 1))
(+ x 2))))
;; Bad - closing parens on separate lines
(defun my-function ()
(when condition
(let ((x 1))
(+ x 2)
)
)
)
Vertical Whitespace:
;; Good - empty line between top-level forms
(defvar my-var1 "value1")
(defvar my-var2 "value2")
(defun my-function ()
"Do something.")
;; Exception: related definitions can be grouped
(defvar my-var1 "value1")
(defvar my-var2 "value2")
(defvar my-var3 "value3")
2. Syntax and Idioms
Prefer Higher-Level Constructs
Use when Instead of Single-Branch if:
;; Good
(when condition
(do-something)
(do-more))
;; Bad
(if condition
(progn
(do-something)
(do-more)))
Use unless for Negated Conditions:
;; Good
(unless condition
(do-something))
;; Bad
(when (not condition)
(do-something))
;; Bad
(if (not condition)
(do-something))
Use not Instead of null:
;; Good - checking for nil/false
(not value)
;; Good - checking for empty list specifically
(null my-list)
;; Bad - using null for general nil check
(null value)
Leverage Comparison Functions:
;; Good - direct comparison
(< 5 x 10)
;; Bad - nested conditions
(and (> x 5) (< x 10))
;; Good - multiple comparisons
(= x y z)
;; Bad - nested comparisons
(and (= x y) (= y z))
Use cond with t as Catch-All:
;; Good
(cond
((eq x 'foo) (handle-foo))
((eq x 'bar) (handle-bar))
(t (handle-default)))
;; Bad - else at the end
(cond
((eq x 'foo) (handle-foo))
((eq x 'bar) (handle-bar))
(else (handle-default)))
Increment and Decrement:
;; Good
(1+ x)
(1- x)
;; Bad
(+ x 1)
(- x 1)
Prefer with-eval-after-load:
;; Good - modern approach
(with-eval-after-load 'company
(setq company-idle-delay 0.2))
;; Acceptable but older
(eval-after-load 'company
'(setq company-idle-delay 0.2))
3. Naming Conventions
General Naming Rules
Use Kebab-Case (Lisp-Case):
;; Good
(defun my-function-name ()
(let ((my-variable 10))
my-variable))
;; Bad - using snake_case
(defun my_function_name ()
(let ((my_variable 10))
my_variable))
;; Bad - using camelCase
(defun myFunctionName ()
(let ((myVariable 10))
myVariable))
Library Name Prefixes:
;; For a library named 'my-package', prefix all public symbols
(defun my-package-do-something ()
"Public function.")
(defvar my-package-option t
"Public variable.")
;; Private functions use double-hyphen
(defun my-package--internal-helper ()
"Private function.")
;; Example from projectile package
(defun projectile-find-file ()
"Public command.")
(defun projectile--get-project-root ()
"Private helper.")
Unused Variables:
;; Good - prefix with underscore
(lambda (x _y _z)
(* x 2))
;; Good - in destructuring
(pcase-let ((`(,first . ,_rest) my-list))
first)
Predicates
Single-Word Predicates:
;; Good - ends with 'p'
(defun evenp (n)
(zerop (mod n 2)))
(defun emptyp (list)
(null list))
Multi-Word Predicates:
;; Good - ends with '-p'
(defun buffer-live-p (buffer)
(and buffer (buffer-name buffer)))
(defun my-package-valid-state-p (state)
(member state '(ready active complete)))
Special Naming
Face Names:
;; Good - no -face suffix
(defface my-package-error
'((t :foreground "red"))
"Face for error messages.")
;; Bad - redundant -face suffix
(defface my-package-error-face
'((t :foreground "red"))
"Face for error messages.")
4. Functions
Lambda Functions
Usage Guidelines:
;; Good - lambda for local use
(mapcar (lambda (x) (* x 2)) '(1 2 3))
;; Bad - lambda in hooks (use named functions)
(add-hook 'emacs-lisp-mode-hook
(lambda () (eldoc-mode 1)))
;; Good - named function in hooks
(defun my-elisp-mode-setup ()
"Setup for Emacs Lisp mode."
(eldoc-mode 1))
(add-hook 'emacs-lisp-mode-hook #'my-elisp-mode-setup)
Never Hard-Quote Lambdas:
;; Bad - hard-quoted lambda
(mapcar '(lambda (x) (* x 2)) list)
;; Good - unquoted lambda
(mapcar (lambda (x) (* x 2)) list)
;; Good - function quote (sharp-quote)
(mapcar #'(lambda (x) (* x 2)) list)
Don't Wrap Existing Functions:
;; Bad - unnecessary wrapper
(add-hook 'prog-mode-hook
(lambda () (linum-mode 1)))
;; Good - direct function reference
(add-hook 'prog-mode-hook #'linum-mode)
Function Quotes
Use Sharp-Quote for Function Names:
;; Good - function quote aids byte-compilation
(mapcar #'upcase '("foo" "bar"))
(add-hook 'text-mode-hook #'turn-on-auto-fill)
(global-set-key (kbd "C-c f") #'find-file)
;; Acceptable but less optimal
(mapcar 'upcase '("foo" "bar"))
;; Bad - unquoted (only works for interactive commands)
(global-set-key (kbd "C-c f") 'find-file)
Parameter Limits
Avoid Too Many Parameters:
;; Bad - too many positional parameters
(defun create-user (name email age address city state zip country)
...)
;; Good - use plist or alist for many parameters
(defun create-user (name email &rest properties)
"Create user with NAME and EMAIL.
PROPERTIES is a plist that may include :age, :address, :city, etc."
(let ((age (plist-get properties :age))
(address (plist-get properties :address)))
...))
;; Good - use cl-defun with keyword arguments
(cl-defun create-user (name email &key age address city state)
"Create user with NAME and EMAIL and optional properties."
...)
5. Macros
When to Use Macros
Only Use Macros When Functions Won't Work:
;; Good - macro needed for special evaluation
(defmacro with-timing (label &rest body)
"Execute BODY and print execution time with LABEL."
(declare (indent 1))
(let ((start (make-symbol "start")))
`(let ((,start (current-time)))
(prog1 (progn ,@body)
(message "%s: %.2fs" ,label
(float-time (time-since ,start)))))))
;; Bad - function would work fine here
(defmacro double (x)
`(* 2 ,x))
;; Good - use function instead
(defun double (x)
(* 2 x))
Macro Design
Extract Examples First:
;; Before writing macro, write example usage:
;;
;; (with-temporary-buffer
;; (insert "content")
;; (buffer-string))
;;
;; Then implement to match desired usage
Use Declare for Metadata:
;; Good - complete macro with declare
(defmacro with-my-mode (buffer &rest body)
"Execute BODY with my-mode enabled in BUFFER."
(declare (indent 1)
(debug (sexp body)))
`(with-current-buffer ,buffer
(my-mode 1)
(unwind-protect
(progn ,@body)
(my-mode -1))))
Prefer Syntax Quoting (Backquote):
;; Good - backquote with unquote
(defmacro my-when (condition &rest body)
`(if ,condition
(progn ,@body)))
;; Bad - manual list construction
(defmacro my-when (condition &rest body)
(list 'if condition
(cons 'progn body)))
6. Documentation
Docstrings
Function Docstrings:
;; Good - complete docstring
(defun my-package-process-file (filename)
"Process the file at FILENAME.
The file is read, processed, and the result is returned.
If FILENAME doesn't exist, signal an error.
Returns the processed content as a string."
(with-temp-buffer
(insert-file-contents filename)
(buffer-string)))
;; Good - document all parameters
(defun my-package-create-entry (name email &optional age)
"Create entry with NAME and EMAIL.
NAME should be a non-empty string.
EMAIL should be a valid email address.
Optional AGE should be a positive integer.
Returns the created entry as a plist."
...)
Variable Docstrings:
;; Good
(defvar my-package-timeout 30
"Timeout in seconds for network operations.
This value is used by all network functions in my-package.
Set to nil to disable timeout.")
;; Good - custom variable with type
(defcustom my-package-auto-save t
"Whether to auto-save files.
When non-nil, files are automatically saved after modifications."
:type 'boolean
:group 'my-package)
First Line Summary:
;; Good - first line is complete sentence
(defun my-function (arg)
"Process ARG and return result.
Additional details about the function behavior.
More explanation if needed."
...)
;; Bad - incomplete first line
(defun my-function (arg)
"Process ARG and
return result."
...)
Comments
Comment Levels:
;;; Section Heading
;;; Used for major sections of the file
;;; Commentary:
;; Package description and usage examples
;;; Code:
;; Top-level comment
;; Used for explanations before functions
(defun my-function ()
;; Comment within function
;; Used for explaining code blocks
(let ((x 10)) ; Margin comment for inline explanation
x))
Comment Best Practices:
;; Good - explains why, not what
(defun my-function (items)
;; Sort items first to optimize lookup performance
(let ((sorted-items (sort items #'string<)))
(process-items sorted-items)))
;; Bad - redundant comment
(defun my-function (items)
;; Sort the items
(sort items #'string<))
;; Good - self-documenting code instead of comments
(defun process-active-buffers ()
(let ((active-buffers (get-active-buffers)))
(mapc #'process-buffer active-buffers)))
;; Bad - needs comments to explain
(defun process ()
;; Get buffers that are active
(let ((bufs (get-bufs)))
;; Process each one
(mapc #'proc bufs)))
Annotation Keywords:
;; TODO: Add support for remote files
;; FIXME: This breaks with Unicode characters
;; OPTIMIZE: Cache results for better performance
;; HACK: Workaround for bug in package.el
;; REVIEW: Is this the best approach?
;; Good - with initials and date
;; TODO(user 2025-01-15): Implement async version
;; FIXME(user 2025-01-15): Handle edge case with empty input
7. Loading and Autoloading
Module Structure
Proper File Footer:
;;; my-package.el --- Short description -*- lexical-binding: t; -*-
;; Copyright (C) 2025 Your Name
;; Author: Your Name <your.email@example.com>
;; Version: 1.0.0
;; Package-Requires: ((emacs "27.1"))
;; Keywords: convenience, tools
;; URL: https://github.com/user/my-package
;;; Commentary:
;; Longer description of what the package does.
;; Usage examples.
;;; Code:
(defun my-package-do-something ()
"Do something useful.")
(provide 'my-package)
;;; my-package.el ends here
Require vs Load
Use require for Dependencies:
;; Good - idempotent, safe
(require 'subr-x)
(require 'cl-lib)
;; Bad - not idempotent
(load "subr-x")
(load-library "cl-lib")
Autoload Cookies
Use Autoload for User-Facing Commands:
;; Good - autoload interactive command
;;;###autoload
(defun my-package-start ()
"Start my-package."
(interactive)
(my-package-mode 1))
;; Good - autoload mode definition
;;;###autoload
(define-minor-mode my-package-mode
"Toggle my-package mode."
:lighter " MyPkg"
(if my-package-mode
(my-package--enable)
(my-package--disable)))
;; Bad - autoloading internal function
;;;###autoload
(defun my-package--internal-helper ()
"Internal helper.")
8. Lexical Binding
Always Enable Lexical Binding:
;;; my-package.el --- Description -*- lexical-binding: t; -*-
;; Lexical binding is faster and more intuitive
;; Always use it for new code
Benefits of Lexical Binding:
;; With lexical binding, closures work correctly
(defun make-counter ()
"Create a counter function."
(let ((count 0))
(lambda ()
(setq count (1+ count))
count)))
;; Usage:
;; (setq counter1 (make-counter))
;; (funcall counter1) => 1
;; (funcall counter1) => 2
9. List Operations
Use dolist for Iteration:
;; Good - clear and idiomatic
(dolist (item items)
(process-item item))
;; Bad - verbose
(mapc (lambda (item)
(process-item item))
items)
Don't Use mapcar for Side Effects:
;; Bad - mapcar creates unused list
(mapcar #'process-item items)
;; Good - dolist for side effects
(dolist (item items)
(process-item item))
;; Good - seq-do for functional style
(seq-do #'process-item items)
10. Interactive Commands
Proper Interactive Specifications:
;; Good - with prompt
(defun my-package-open-file (filename)
"Open FILENAME in my-package."
(interactive "FOpen file: ")
(find-file filename))
;; Good - multiple arguments
(defun my-package-replace (from to)
"Replace FROM with TO in current buffer."
(interactive
(list (read-string "Replace: ")
(read-string "With: ")))
(save-excursion
(goto-char (point-min))
(while (search-forward from nil t)
(replace-match to))))
;; Good - using prefix argument
(defun my-package-insert-n (n)
"Insert N copies of something."
(interactive "p")
(dotimes (_ n)
(insert "text")))
11. Error Handling
Signal Errors Appropriately:
;; Good - descriptive error
(defun my-package-get-user (id)
"Get user by ID."
(or (gethash id my-package-users)
(error "User not found: %s" id)))
;; Good - user-error for user mistakes
(defun my-package-save ()
"Save current data."
(interactive)
(unless (my-package--has-changes-p)
(user-error "No changes to save")))
;; Good - with condition-case
(defun my-package-load-file (filename)
"Load FILENAME safely."
(condition-case err
(with-temp-buffer
(insert-file-contents filename)
(read (current-buffer)))
(file-error
(message "Cannot read file: %s" (error-message-string err))
nil)))
Best Practices Summary
When writing Emacs Lisp code:
- Always enable lexical binding in file header
- Prefix all public symbols with package name
- Use
defcustomfor user-configurable variables - Add autoload cookies to interactive commands
- Write comprehensive docstrings for all public functions
- Prefer built-in functions over reinventing functionality
- Use
declarein macros for indentation and debugging - Handle errors gracefully with informative messages
- Follow naming conventions consistently
- Keep functions focused on single responsibilities
Common Patterns
Package Template
;;; my-package.el --- Brief description -*- lexical-binding: t; -*-
;; Copyright (C) 2025 Your Name
;; Author: Your Name <email@example.com>
;; Version: 1.0.0
;; Package-Requires: ((emacs "27.1"))
;; Keywords: convenience
;; URL: https://github.com/user/my-package
;;; Commentary:
;; Longer description and usage examples.
;;; Code:
(require 'cl-lib)
(defgroup my-package nil
"My package customization group."
:group 'convenience
:prefix "my-package-")
(defcustom my-package-option t
"Enable some option."
:type 'boolean
:group 'my-package)
;;;###autoload
(defun my-package-command ()
"Main command."
(interactive)
(message "Hello from my-package!"))
(provide 'my-package)
;;; my-package.el ends here
Resources
Converted and distributed by TomeVault — claim your Tome and manage your conversions.