name: emacs-variables description: Use when inspecting or modifying Emacs variables - provides elisp patterns for variable handling, customization, and state management
Emacs Variables Skill
Display Guidelines
E-ink Monitor Compatibility: User uses an e-ink monitor that only supports black, white, and grey.
- NEVER use colors (
:foreground "red",:foreground "cyan", etc.) - Use
:weight bold,:weight light,:slant italic,:underline tfor differentiation - Standard Emacs faces (font-lock-*-face) are acceptable as they adapt to themes
Variable Basics
Define variable
(defvar my-var "default" "Documentation string.")
(defvar my-var nil) ; define without overwriting
Set variable
(setq my-var "value")
(setq var1 "val1" var2 "val2") ; multiple
Get variable value
my-var ; just use the symbol
(symbol-value 'my-var) ; programmatic access
Buffer-Local Variables
Make buffer-local
(make-local-variable 'my-var)
(setq-local my-var "buffer-specific")
Define as buffer-local by default
(defvar-local my-buffer-var nil "Always buffer-local.")
Get value in other buffer
(buffer-local-value 'my-var other-buffer)
Check if buffer-local
(local-variable-p 'my-var)
(local-variable-if-set-p 'my-var)
User Options (Customizable)
Define custom option
(defcustom my-option "default"
"Documentation for option."
:type 'string
:group 'my-group)
Common :type values
:type 'boolean
:type 'string
:type 'integer
:type '(choice (const nil) (string :tag "Custom"))
:type '(repeat string)
Variable Inspection
Check if bound
(boundp 'my-var) ; is it defined?
Describe variable
(describe-variable 'my-var) ; interactive
Get documentation
(documentation-property 'my-var 'variable-documentation)
Let Bindings
Local binding
(let ((x 1)
(y 2))
(+ x y)) ; x, y only exist here
Sequential binding
(let* ((x 1)
(y (+ x 1))) ; y can use x
y)
Dynamic Binding
Temporarily change variable
(let ((some-global-var "temporary"))
;; functions called here see temporary value
(do-something))
;; original value restored
Common pattern
(let ((inhibit-read-only t))
;; can modify read-only buffer here
(erase-buffer))
Lexical Binding and Closures
IMPORTANT: Emacs has two binding modes - dynamic (default in older code) and lexical.
Literate-elisp files and files without lexical-binding: t use dynamic binding.
The Problem: Closures in Dynamic Binding
In dynamic binding, lambdas don't capture variables - they look them up at runtime:
;; BROKEN in dynamic binding - tn is void at call time
(defun make-broken-fn (name)
(let ((tn name))
(lambda () (message "Name: %s" tn)))) ; tn looked up when called, not defined!
Solution: Use lexical-let
lexical-let (from cl-lib) creates true lexical closures even in dynamic binding mode:
(require 'cl-lib)
;; WORKS - lexical-let captures variables properly
(defun make-working-fn (name)
(lexical-let ((tn name))
(lambda () (message "Name: %s" tn)))) ; tn captured at definition time
When to Use lexical-let
Use lexical-let when:
- Creating closures/lambdas that reference outer variables
- Building callbacks or handler functions dynamically
- Any lambda that will be called later and needs captured state
;; Creating multiple closures that each capture different values
(defun make-toggler (tag-name)
"Create a function that toggles TAG-NAME."
(lexical-let ((tn tag-name))
(lambda ()
(interactive)
(toggle-tag tn))))
;; Creating a description function for transient menu
(defun make-description (tag desc)
"Create a description function for TAG with DESC."
(lexical-let ((t tag) (d desc))
(lambda ()
(format "[%s] %s" (if (selected-p t) "X" " ") d))))
Alternative: Enable Lexical Binding
For new files, prefer enabling lexical binding in the file header:
;;; my-file.el --- Description -*- lexical-binding: t; -*-
Then regular let creates closures correctly:
;; Works with lexical-binding: t
(defun make-fn (name)
(let ((tn name))
(lambda () (message "Name: %s" tn))))
Hash Tables
Create hash table
(make-hash-table :test 'equal)
Access hash table
(gethash key table)
(gethash key table default)
(puthash key value table)
(remhash key table)
Iterate hash table
(maphash (lambda (key value)
;; process key, value
)
table)
Property Lists
Get property
(plist-get '(:a 1 :b 2) :a) ; => 1
Put property
(plist-put plist :key value)
In symbol properties
(get 'my-symbol 'property)
(put 'my-symbol 'property value)
Common Patterns
Safe variable access
(when (boundp 'maybe-var)
(symbol-value 'maybe-var))
Toggle boolean
(setq my-flag (not my-flag))
Increment/modify
(cl-incf counter)
(cl-decf counter)
(push item my-list)
(pop my-list)
Environment variables
(getenv "PATH")
(setenv "MY_VAR" "value")
Mode-line variables
;; For e-ink: use weight/slant, not colors
(setq my-mode-line-string
(propertize " [Status]"
'face '(:weight bold)
'help-echo "Tooltip text"))
Hooks
Add to hook
(add-hook 'some-mode-hook #'my-function)
(add-hook 'some-mode-hook #'my-function nil t) ; buffer-local
Remove from hook
(remove-hook 'some-mode-hook #'my-function)
Run hooks
(run-hooks 'my-hook)
(run-hook-with-args 'my-hook arg1 arg2)