Elisp入门:让编辑器听懂你的话

引言:为什么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>

这个命令会:

  1. 询问用户输入页面标题
  2. 插入完整HTML5模板
  3. 将光标自动定位到 <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 推荐学习顺序

  1. 官方入门 :Emacs内置的 C-h i m Emacs Lisp Intro(Info文档)
  2. 交互实践 :打开 scratch 缓冲区,写代码后用 C-j 或 C-x C-e 执行
  3. 理解核心概念 :S表达式、作用域、递归(Lisp擅长递归)
  4. 阅读他人代码 :查看内置函数定义(C-h f 然后点链接)
  5. 修改现有插件 :比从零开始写更容易
  6. 贡献开源 :给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 实现。 " ------ 改编自格林斯潘第十定律

相关推荐
刺心疯3 小时前
VScode集成openClaw使用OpenClaw Node for VS Code插件(右键没有openClaw)
vscode·编辑器
硬汉嵌入式4 小时前
Clion 2026.1发布,集成AI,支持导入VSCode项目,支持TCP DAP调试等
vscode·编辑器·clion
nongcunqq4 小时前
离线下载 vscode 插件 chrome 插件 docker镜像
ide·vscode·编辑器
笨笨饿19 小时前
26_为什么工程上必须使用拉普拉斯变换
c语言·开发语言·人工智能·嵌入式硬件·机器学习·编辑器·概率论
H@Z*rTE|i1 天前
vscode 安装配置claudeCode 配置美团免费模型LongCat-Flash-Thinking-2601的每天500000token 保姆级教程
ide·vscode·编辑器
浔川python社1 天前
浔川代码编辑器 v4.0 升级版 公告
编辑器
笨笨饿1 天前
博客目录框架
c语言·开发语言·arm开发·git·嵌入式硬件·神经网络·编辑器
@BruceYan@1 天前
VSCode Insiders 添加 OpenAI 兼容模型
ide·vscode·编辑器·copilot·自定义模型