Claude-skill-registry emacs-lisp
git clone https://github.com/majiayu000/claude-skill-registry
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/emacs-lisp" ~/.claude/skills/majiayu000-claude-skill-registry-emacs-lisp && rm -rf "$T"
skills/data/emacs-lisp/SKILL.mdEmacs 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
Instead of Single-Branch when
:if
;; Good (when condition (do-something) (do-more)) ;; Bad (if condition (progn (do-something) (do-more)))
Use
for Negated Conditions:unless
;; Good (unless condition (do-something)) ;; Bad (when (not condition) (do-something)) ;; Bad (if (not condition) (do-something))
Use
Instead of not
: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
with cond
as Catch-All:t
;; 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
for Dependencies:require
;; 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
for Iteration:dolist
;; Good - clear and idiomatic (dolist (item items) (process-item item)) ;; Bad - verbose (mapc (lambda (item) (process-item item)) items)
Don't Use
for Side Effects:mapcar
;; 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
for user-configurable variablesdefcustom - Add autoload cookies to interactive commands
- Write comprehensive docstrings for all public functions
- Prefer built-in functions over reinventing functionality
- Use
in macros for indentation and debuggingdeclare - 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