在 Emacs 中,一切都像服务
摘要
这篇文章解释了 Emacs 如何作为各种服务的客户端,利用其内置的 UI、网络通信和数据管理库,支持了“一切都像服务”在 Emacs 中的理念。
暂无内容
查看缓存全文
缓存时间: 2026/07/10 12:12
# 在 Emacs 中,一切看起来都像服务
来源: http://yummymelon.com/devnull/in-emacs-everything-looks-like-a-service.html
人们常说 Emacs 是一个操作系统(OS)。这并不准确,但它能调度操作系统内核之上的应用程序和工具,这一点倒是容易让人联想到操作系统。下面的示意图更准确地展示了 Emacs 与操作系统及其能力的关系。
img
Emacs 内置了对 OS 系统服务(文件系统、网络等)的访问,加上运行其他程序的能力,使得在 Emacs 内部即兴模拟客户端行为成为常态。正因为如此,Emacs 用户能够通过为其开发的各种客户端模式完成许多计算需求。这为"只生活在 Emacs 中"的说法提供了依据。
在这篇文章中,我们将探讨 Emacs 让你构建客户端的一些方式。读完这篇文章,你很可能也会相信:在 Emacs 看来,一切都是服务。
## 客户端-服务器模型
首先,我们来定义一些概念。
**客户端-服务器模型**(https://en.wikipedia.org/wiki/Client%E2%80%93server_model)是一种常见的计算机交互模式,它将任务分割为资源提供者(服务)和资源请求者(客户端)。客户端向服务器发出**请求**,服务器则返回**响应**,如下图所示。
img
根据实现方式的不同,这次交易(请求 + 响应)可以发生在网络上,也可以是系统本地的。基于网络的客户端-服务器模型在 REST 风格软件架构(https://en.wikipedia.org/wiki/REST)中得到了最充分的阐述。下图的时序图展示了 REST 风格客户端-服务器架构的一种常见实现模式。
img
## Emacs 作为客户端
从上面的图可以看出,客户端通常负责三个关注点:
- **UI:** 用户界面(如果有的话)。
- **客户端边缘:** 负责与服务通信的子系统。对于网络客户端,这就是网络子系统。
- **本地数据库:** 与服务器交换或同步的数据表示。数据的管理方式由实现需求决定。
针对上述关注点,Emacs 提供了众多内置和第三方库来实现客户端。下面列出了一些内置库及其相应的阅读链接:
- UI
- Minibuffers(https://www.gnu.org/software/emacs/manual/html_node/elisp/Minibuffers.html)
- Buffers(https://www.gnu.org/software/emacs/manual/html_node/elisp/Buffers.html)
- Completion(https://www.gnu.org/software/emacs/manual/html_node/elisp/Completion.html)
- Tabulated List Mode(https://www.gnu.org/software/emacs/manual/html_node/elisp/Tabulated-List-Mode.html)
- Variable Pitch Table (vtable)(https://www.gnu.org/software/emacs/manual/html_node/vtable/)
- Transient(https://www.gnu.org/software/emacs/manual/html_node/transient/)
- 客户端边缘
- URL(https://www.gnu.org/software/emacs/manual/html_node/url/)
- Socket (TCP/UDP)(https://www.gnu.org/software/emacs/manual/html_node/elisp/Network.html)
- SMTP(https://www.gnu.org/software/emacs/manual/html_node/smtpmail/)
- 序列化/反序列化
- JSON(https://www.gnu.org/software/emacs/manual/html_node/elisp/Parsing-JSON.html)
- XML(https://www.gnu.org/software/emacs/manual/html_node/elisp/Parsing-HTML_002fXML.html)
- 本地数据库
- 集合
- Association Lists(https://www.gnu.org/software/emacs/manual/html_node/elisp/Association-Lists.html)
- Property Lists(https://www.gnu.org/software/emacs/manual/html_node/elisp/Property-Lists.html)
- Hash Tables(https://www.gnu.org/software/emacs/manual/html_node/elisp/Hash-Tables.html)
- SQLite(https://www.gnu.org/software/emacs/manual/html_node/elisp/Database.html)
需求决定了实现 Emacs 客户端所需的复杂程度。如果某个现有的命令行工具能够完成"重活",那么该工具就可以被重构为可以通过 shell 调用访问的"服务"。
img
## Elisp
上述所有库都通过 Emacs Lisp(Elisp(https://www.gnu.org/software/emacs/manual/html_node/elisp/))编程语言进行访问。Elisp 是一种动态编程语言(https://en.wikipedia.org/wiki/Dynamic_programming_language),允许在运行时进行高度即兴的发挥。这种能力使得可以复杂地调度 Emacs 可用的任何行为,从 Elisp 函数到 shell 命令。
## 示例:wttr.in 客户端
wttr.in(https://github.com/chubin/wttr.in)是一个面向控制台的天气预报网络服务。它支持 JSON 输出(https://github.com/chubin/wttr.in#different-output-formats),因此我们可以构建一个 Emacs `wttr` 命令,该命令会提示输入地点,发起 HTTP 请求,处理 JSON 响应,并在迷你缓冲区中显示结果。
顶级命令 `wttr` 如下所示。
```
1
2
3
4
5
6
7
8
9
10
11
12
13
14
```
```
(defun wttr (location)
"从 `https://wttr.in' 获取 LOCATION 的天气状况,在迷你缓冲区显示。
结果也存储在 `kill-ring' 中。"
(interactive "s地点 (默认: local): ")
(condition-case err
(let* ((url (wttr--request-url location))
(jsondb (fetch-json-as-hash-table url))
(msg (wttr--report-message jsondb)))
(kill-new msg)
(message "%s" msg))
(error (message "ERROR: %s" (cdr err)))))
```
wttr.in 的 URL 由函数 `wttr--request-url` 构造,如下所示。
```
(defun wttr--request-url (location)
"为 LOCATION 构造 wttr.in 的 URL。"
(let* ((base-url (url-generic-parse-url "https://wttr.in"))
(encoded-location (string-replace " " "+" location))
(query (format "/%s?0&format=j1" encoded-location))
(_dummy (setf (url-filename base-url) query)))
(url-recreate-url base-url)))
```
然后我们可以将该 URL 传递给 `fetch-json-as-hash-table`,后者负责获取 URL 并将 JSON 响应解析为 Elisp `hash-table`。
```
1
2
3
4
5
6
7
8
9
10
11
12
13
```
```
(defun fetch-json-as-hash-table (url)
"获取预期返回 JSON 的 URL,并返回 `hash-table'。"
(let ((data-buffer (url-retrieve-synchronously url)))
(if (not data-buffer)
(error "无法从 %s 获取数据" url)
(unwind-protect
(with-current-buffer data-buffer
;; 将光标移动到 HTTP 元数据头部之后
(goto-char url-http-end-of-headers)
;; 将 JSON 缓冲区剩余内容解析为 hash-table
(json-parse-buffer :object-type 'hash-table))
;; 总是杀死下载的网络缓冲区以防内存泄漏
(kill-buffer data-buffer)))))
```
最后,我们可以从 JSON 响应(`jsondb`)中提取所需值,填充要发送到迷你缓冲区的消息。
```
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
```
```
(defun wttr--report-message (jsondb)
"根据 JSONDB 生成天气预报消息。"
(let* ((area-buflist ())
(nearest-area
(wttr--get-first jsondb "nearest_area"))
(area-name
(map-elt (wttr--get-first nearest-area "areaName") "value"))
(region
(map-elt (wttr--get-first nearest-area "region") "value"))
(country
(map-elt (wttr--get-first nearest-area "country") "value"))
(current-condition (wttr--get-first jsondb "current_condition"))
(temp_c (map-elt current-condition "temp_C"))
(temp_f (map-elt current-condition "temp_F"))
(weather-description
(map-elt
(wttr--get-first current-condition "weatherDesc") "value")))
(mapc (lambda (x)
(if (and x (not (string-equal x "")))
(push x area-buflist)))
(list area-name region country))
(format "%s: %s°C, %s°F %s"
(string-join (reverse area-buflist) ", ")
temp_c
temp_f
weather-description)))
```
wttr.el 源码(https://gist.github.com/kickingvegas/40290cef2751c133666eb19921f91eee)
## 结语
至此,希望你已相信本文标题的断言:在 Emacs 中,一切看起来都像服务。此外,Emacs 提供的许多 API 都工作在较高的抽象层次。想想看,`wttr.el` 的代码量只有 67 行(使用 `cloc` 工具计算的结果)。
如果这还嫌多,那么想象另一种实现:将实际的网络请求和 JSON 处理放在一个名为 `weather` 的 Python 脚本中。那么调用它的 Elisp 命令就只是下面这段代码。
```
(defun weather (location)
"调用 weather 脚本处理 LOCATION,并在迷你缓冲区中显示结果。"
(interactive "s地点 (默认: local): ")
(let* ((weather-cmd "weather")
(cmd (if location (format "%s %s" weather-cmd location) weather-cmd))
(result (shell-command-to-string cmd)))
(kill-new result)
(message result)))
```
通过上述实现,shell 命令实际上就成了发送请求的"服务"。
由于 Elisp 是一种动态编程语言,它可以以即兴的方式将 Elisp 库与命令行工具集成在一起。
这种能力对认识到其机会的用户来说极具吸引力。
emacs(http://yummymelon.com/devnull/tag/emacs.html)
相似文章
推荐一下……理解 Emacs 的模式
文章解释了 Emacs 的架构模式,重点介绍了通用缓冲区数据模型和增量补全读取(ICR),并将其比作玫瑰的根与花瓣。文章强调了 Emacs 如何统一界面并通过 Elisp 实现可扩展性。
我可以推荐 Emacs 的创新 UI——eww 吗?
文章推荐使用 Emacs 的 eww 网页浏览器,强调其缺少 JavaScript 的特性改善了许多网站的体验,并指出 Emacs 提供了独特的 UI 创新,如逐张图片调整大小和键盘导航。
Emacs 在流行文化中的出现
一篇博客文章,记录了 Emacs 文本编辑器在电影和电视中的出现,包括《社交网络》、《创:战纪》、《北极冲击》和《硅谷》。
软件界的Emacs化
作者讲述了在终端中阅读 Markdown 的烦恼,并描述了如何使用 Claude 快速构建一个自定义的 macOS Markdown 查看器(MDV.app),展示了 AI 如何让人能够迅速创建个人软件工具。
Emacs 的更多内置功能
一篇博客文章,介绍了Emacs中不太为人所知但实用的内置功能,延续了一个旨在提高原版Emacs功能可发现性的系列。