引言:为什么Lisp是Emacs的灵魂
在软件工具的世界里,大多数编辑器是"被使用"的------你通过菜单、按钮、快捷键向它下达指令,它被动地响应。而Emacs不同,它活着。
Emacs的核心不是用C语言写成的编辑器内核,而是一个Lisp 解释器 。启动Emacs时,你其实是在启动一个运行着Elisp(Emacs Lisp)的虚拟机,编辑器功能只是加载在之上的Lisp程序。这一设计选择,将Emacs与其他编辑器区分开来:你不再只是用户,而是这个环境的共同构建者。
理解这个差异至关重要 :
- VS Code:用TypeScript写插件,插件与核心隔离
- Vim:用Vim脚本配置,但大多数用户只写配置不写插件
- Emacs:你每天按下的每个快捷键,本质上都在执行一段Lisp函数
这意味着:如果你发现某个操作不够高效,你不必等待插件作者更新------你可以当场写几行Lisp,重新定义编辑器的行为。这就是Emacs三十多年来历久弥新的根本原因。
第一部分:Elisp是什么,为什么值得学
1.1 定义与定位
Elisp是Emacs的扩展语言,属于Lisp家族(更准确地说,是Maclisp的后裔)。它的特点:
| 特性 | 说明 |
|---|---|
| 动态类型 | 变量无需声明类型 |
| 自动内存管理 | 垃圾回收,无需手动管理内存 |
| 函数式核心 | 函数是一等公民,支持闭包 |
| 交互式编程 | 每段代码都可以立即执行并看到效果 |
| 深度集成 | 编辑器的每个状态都暴露给Lisp |
1.2 学习Elisp的实际收益
短期 :
- 理解Emacs的配置语法(不再复制粘贴看不懂的配置)
- 可以调整已有插件的行为
- 编写简单的自定义命令
长期 :
- 将Emacs打造成完全符合个人工作流的IDE
- 理解函数式编程的核心思想
- 掌握一种"编程即与系统对话"的思维方式
1.3 为什么Lisp语法值得你放下偏见
Lisp的括号语法常被戏称为"括号语言",但这种设计的背后是深刻的简洁性:代码即数据,数据即代码。在Elisp中,程序的结构和数据的结构完全一致------都是嵌套的列表。这意味着你可以像操作数据一样操作代码,实现其他语言难以企及的元编程能力。
一个简单例子 :你想在Emacs中实现一个功能,自动为选中的文本添加HTML标签。在其他语言中,你需要解析选中范围、操作字符串、替换选区。在Elisp中,你可以写:
elisp
(defun wrap-with-tag (tag)
"Wrap selected region with HTML TAG."
(interactive "sTag name: ")
(let ((start (region-beginning))
(end (region-end)))
(save-excursion
(goto-char end)
(insert (format "</%s>" tag))
(goto-char start)
(insert (format "<%s>" tag)))))
这段代码不是"配置",而是一个程序。你可以把它绑定到快捷键,添加到菜单,甚至让它在保存文件时自动运行。Emacs不区分"编辑器功能"和"用户代码"------它们都是Lisp。

第二部分:Elisp核心语法(15分钟入门)
2.1 S表达式:万物皆列表
Elisp代码由S 表达式 (Symbolic Expression)构成。理解S表达式是理解Lisp的关键。
原子( Atom ) :
elisp
123 ; 数字
"hello" ; 字符串
my-variable ; 符号
t nil ; 真与假
列表( List ) :
elisp
(1 2 3) ; 数字列表
("a" "b" "c") ; 字符串列表
(+ 1 2) ; 函数调用:1+2
(defun foo () (message "hi")) ; 函数定义也是一个列表
关键理解 :在Elisp中,(function arg1 arg2) 的形式既是函数调用,也是一个列表数据结构。括号不是语法噪音------它们是语言的本质。
2.2 基本语法规则
注释 :; 开始单行注释,;; 是更常见的注释风格,;;; 用于文件头注释。
elisp
;; 这是一个注释
(setq x 10) ; 行末注释
求值规则 :
- 原子求值为自身(数字、字符串)
- 符号求值为其绑定的值
- 列表求值为函数调用:第一个元素是函数,其余是参数
elisp
(+ 1 2 3) ; 6
(list 1 2 3) ; (1 2 3) — list是函数,不是语法
'(1 2 3) ; (1 2 3) — 单引号阻止求值
单引号( quote ) :'something 等价于 (quote something),告诉解释器"把这个当作数据,不要执行"。
2.3 变量
全局变量 (使用 setq 或 defvar):
elisp
(setq my-name "张三") ; 设置变量
(defvar my-age 30) ; 定义全局变量(推荐)
(setq my-age 31) ; 修改
局部变量 (使用 let):
elisp
(let ((x 10)
(y 20))
(+ x y)) ; 返回 30,x和y在let外部不可见
变量命名约定 :
- 普通变量:my-variable(使用连字符,这是Lisp风格)
- 自定义变量(用户可配置):my-var
- 内部变量:my--internal-var(双连字符表示私有)
- 常量:my-constant(全大写不是强制要求)
2.4 函数
定义函数 :
elisp
(defun my-add (a b)
"Return the sum of A and B." ; 文档字符串(可选)
(+ a b))
调用函数 :
elisp
(my-add 3 5) ; 返回 8
交互式函数 (可供用户调用的命令):
elisp
(defun my-hello ()
"Say hello in the minibuffer."
(interactive) ; 标记为交互函数
(message "Hello, Emacs!"))
interactive 告诉Emacs:这个函数可以由用户通过 M-x 或快捷键调用。message 在底部状态栏显示信息。
函数返回值 :Elisp中函数自动返回最后一个表达式的值,无需显式 return。
elisp
(defun my-square (x)
(* x x)) ; 返回值是 (* x x) 的结果
2.5 条件与循环
条件判断 :
elisp
(if (> 10 5)
(message "10 is greater")
(message "10 is not greater"))
(cond
((> x 0) "positive")
((< x 0) "negative")
(t "zero")) ; t 相当于 else
循环 :
elisp
;; while 循环
(let ((i 0))
(while (< i 5)
(message "i is %d" i)
(setq i (1+ i))))
;; 列表遍历
(dolist (item '(apple banana orange))
(message "I like %s" item))
;; 数字遍历
(dotimes (i 5)
(message "Number %d" i))
2.6 常用Emacs交互函数
操作缓冲区 :
elisp
(current-buffer) ; 获取当前缓冲区
(other-buffer) ; 切换缓冲区
(with-current-buffer "my-file.txt"
(insert "hello")) ; 在指定缓冲区插入文本
(save-excursion
(goto-char (point-min))
(search-forward "TODO")) ; 在当前位置执行操作,然后恢复位置
操作文本 :
elisp
(point) ; 获取光标位置
(point-min) ; 缓冲区开头位置
(point-max) ; 缓冲区结尾位置
(goto-char (point-min)) ; 移动到开头
(insert "some text") ; 插入文本
(delete-char 5) ; 删除5个字符
(buffer-substring (point) (point-max)) ; 获取从光标到结尾的文本
正则表达式 :
elisp
(re-search-forward "hello") ; 向前搜索
(replace-match "world") ; 替换匹配内容
(delete-matching-lines "^\\s-*$") ; 删除空行

第三部分:实际编写------创建自定义命令
3.1 第一个自定义命令:快速插入日期
elisp
(defun insert-current-date ()
"Insert current date in YYYY-MM-DD format."
(interactive)
(insert (format-time-string "%Y-%m-%d")))
将这段代码放入 ~/.emacs.d/init.el,重启Emacs后执行 M-x insert-current-date,当前日期就被插入到光标位置。
3.2 绑定快捷键
elisp
;; 全局绑定:Ctrl+c d
(global-set-key (kbd "C-c d") 'insert-current-date)
;; 仅在某种模式下绑定
(define-key text-mode-map (kbd "C-c d") 'insert-current-date)
3.3 带参数的命令:智能插入模板
elisp
(defun insert-html-template ()
"Insert a basic HTML5 template."
(interactive)
(let ((title (read-string "Page title: ")))
(insert (format "<!DOCTYPE html>\n<html>\n<head>\n <meta charset=\"UTF-8\">\n <title>%s</title>\n</head>\n<body>\n \n</body>\n</html>
这个命令会:
- 询问用户输入页面标题
- 插入完整HTML5模板
- 将光标自动定位到 <body> 标签内
3.4 辅助函数:代码复用
elisp
(defun my--trim-whitespace (str)
"Remove leading and trailing whitespace from STR."
(replace-regexp-in-string "\\`\\s-+\\|\\s-+\\'" "" str))
(defun my--capitalize-words (str)
"Capitalize each word in STR."
(mapconcat 'capitalize (split-string str " ") " "))
(defun my-format-name (first last)
"Format name as 'LAST, First' with proper capitalization."
(interactive "sFirst name: \nsLast name: ")
(let ((first-clean (my--capitalize-words (my--trim-whitespace first)))
(last-clean (my--capitalize-words (my--trim-whitespace last))))
(message "Formatted: %s, %s" last-clean first-clean)))
注意 my-- 前缀表示这是内部辅助函数,不打算直接暴露给用户。
3.5 实战:批量重命名文件
elisp
(defun rename-files-in-dir (dir pattern replacement)
"Rename files in DIR by replacing PATTERN with REPLACEMENT."
(interactive "DDirectory: \nsReplace pattern: \nsWith: ")
(let ((files (directory-files dir t pattern)))
(dolist (file files)
(when (file-regular-p file)
(let* ((basename (file-name-nondirectory file))
(dirname (file-name-directory file))
(new-basename (replace-regexp-in-string pattern replacement basename))
(new-file (concat dirname new-basename)))
(unless (string= basename new-basename)
(rename-file file new-file)
(message "Renamed: %s -> %s" basename new-basename)))))))
这个命令展示了Elisp操作文件系统的能力,并且可以在Emacs内部完成,无需离开编辑器。

第四部分:理念呼应------代码即财富,配置即程序
4.1 Codigger的"代码即财富"
Codigger提出的"代码即财富"理念,核心在于:代码不是被管理的资产,而是可以被量化、交易、协作的主动财富。这个理念挑战了传统代码托管平台的被动仓库思维。
在Codigger的视角中:
- 每一行代码都有其创造者归属
- 代码片段可以被独立定价和授权
- 协作过程本身就是价值创造
4.2 Emacs的"配置即程序"
Emacs通过三十多年的实践,印证了一个类似的理念:配置文件不是被动的设置列表,而是一个活生生的程序。
当你写一个VS Code的settings.json,你只是在声明"我想要什么"。当你写Emacs的init.el,你在定义编辑器应该做什么。这种差异体现在:
| 方面 | 传统配置 | Emacs 配置 |
|---|---|---|
| 本质 | 数据(JSON/TOML/YAML) | 程序(Lisp代码) |
| 扩展性 | 有限,只能设置已有选项 | 无限,可以定义全新行为 |
| 复用性 | 复制粘贴配置片段 | 调用函数、加载模块 |
| 调试能力 | 查看日志 | 运行、单步、检查变量 |
| 生命周期 | 启动时加载一次 | 可随时重新加载、修改 |
4.3 交汇点:从用户到创造者
Codigger和Emacs共同的洞见是:工具的使用者和创造者之间的界限是可以被打破的。
- Codigger通过"代码即财富"让每个开发者成为自己代码价值的拥有者和交易者
- Emacs通过"配置即程序"让每个用户成为编辑器行为的定义者和扩展者
一个Emacs用户,当他开始写第一行 defun 时,就不再是"用户"了------他是这个编辑器生态的共同构建者。这与Codigger设想的世界是一致的:在这个世界里,每个人不只是消费代码,而是参与代码的价值创造和流转。
4.4 实践建议:从配置者到创造者
如果你认同这个理念,这里有一些路径:
第一阶段(配置使用者) :
- 复制他人的Emacs配置
- 安装现成插件
- 修改已有设置
第二阶段(配置作者) :
- 理解每一行配置的含义
- 编写小的自定义函数
- 将自己的配置整理成模块
第三阶段(插件作者) :
- 将通用功能提取为独立包
- 遵守Elisp包规范
- 分享到MELPA或GitHub
第四阶段(理念实践者) :
- 像Codigger设想的那样,将自己的代码视为可交易的资产
- 授权自己的Emacs扩展
- 参与协作和代码价值网络

第五部分:学习路径与资源
5.1 推荐学习顺序
- 官方入门 :Emacs内置的 C-h i m Emacs Lisp Intro(Info文档)
- 交互实践 :打开 scratch 缓冲区,写代码后用 C-j 或 C-x C-e 执行
- 理解核心概念 :S表达式、作用域、递归(Lisp擅长递归)
- 阅读他人代码 :查看内置函数定义(C-h f 然后点链接)
- 修改现有插件 :比从零开始写更容易
- 贡献开源 :给melpa上的包提PR
5.2 必备资源
官方文档 :
- C-h i 进入Info模式,选择 Emacs Lisp Intro(入门)和 Elisp(完整手册)
- C-h f 查看函数文档
- C-h v 查看变量文档
- C-h k 查看快捷键绑定的命令
必读书目 :
- 《An Introduction to Programming in Emacs Lisp》(官方入门,免费)
- 《GNU Emacs Lisp Reference Manual》(官方手册)
- 《Mastering Emacs》(Mickey Petersen,付费但值得)
在线资源 :
- Emacs Stack Exchange
- r/emacs 和 r/emacs_lisp
- Emacs Wiki(内容旧但很多概念仍有价值)
工具推荐 :
- M-x ielm:Elisp交互式REPL(比 scratch 更适合实验)
- M-x eval-buffer:重新加载整个配置文件
- M-x edebug-defun:调试Elisp函数
- elpaca 或 straight.el:声明式包管理
5.3 常见新手误区
误区 1 :在 scratch里写长函数不保存
解决:scratch 默认不自动保存,使用 C-x C-w 保存为 .el 文件
误区 2 :修改内置函数定义导致不稳定
解决:使用 advice-add 而不是直接重定义;如果必须覆盖,用 defalias 保留原函数
误区 3 :过度使用全局变量
解决:优先使用 let 绑定局部变量;需要状态时使用 defvar-local 或缓冲区局部变量
误区 4 :忽略宏的能力
Elisp的宏系统极其强大,但新手应该先掌握函数。6个月后再学宏。

结论:你的编辑器,你的语言
学习Elisp的意义,不在于掌握另一种编程语言的语法------语法是Lisp中最不重要的部分。真正的意义在于,你开始用程序员的思维来对待你的工具。
当你不再问"Emacs能不能做到X",而是问"我该怎么写Lisp让Emacs做到X"时,一个全新的世界打开了。这个世界的入口,就是第一行 (defun ...)。
Emacs和Codigger共同指向一个未来:工具和它的使用者之间的边界是流动的。在这个未来里,配置不是终点,而是创造力的起点;代码不是被锁在仓库里的资产,而是可以流动、交易、增值的财富。
现在,打开你的Emacs,按下 M-x,输入 ielm,然后写下:
elisp
(message "Hello, world of programmable editors!")
按回车。这不仅仅是一行代码的执行------这是你从一个"用户"转变为一个"创造者"的第一声宣告。
" 任何足够复杂的配置文件都包含一个临时的、非正式、充满 bug 的、缓慢运行的 Lisp 实现。 " ------ 改编自格林斯潘第十定律