在 Emacs 中,一切看起来像一个服务
一个常见的说法是 Emacs 是一个操作系统 (OS)。这并不正确,但使之与操作系统进行比较的是它在操作系统内核层以上编排应用程序和工具的能力。下面的图示揭示了 Emacs 与操作系统及其功能之间的更真实的关系。Emacs 内置访问操作系统服务(文件系统、网络等),以及运行其他程序的能力,使得在其中即兴发挥客户行为成为常态。因此,Emacs 用户能够从为其开发的不同客户端模式中满足许多计算需求。这使得 “只在 Emacs 中生活” 的概念得到了认可。在这篇文章中,我们将探讨 Emacs 如何让你构建客户的方式。在这篇文章结束时,你可能会相信在 Emacs 内部,一切看起来都像一个服务。 客户端-服务器模型 首先,我们提供一些定义。客户端-服务器模型是一种常见的计算机交互模式,其中任务在资源提供者(服务)和资源请求者(客户端)之间进行划分。客户端向服务器发出请求,服务器则返回响应,如下图所示。根据实现方式,事务(请求 + 响应)可以通过网络发生,也可以局限于系统本地。使用网络的客户端-服务器模型在 REST 风格的软件架构中得到了最详细的阐述。下面的顺序图展示了一种 REST 风格的客户端-服务器架构的常见实现模式。 Emacs 作为客户端 从上面的图示来看,客户端通常负责三个方面: 用户界面 (UI):用户界面(如果有的话)。 客户端边缘:负责与服务进行通信的子系统。对于网络客户端,这是网络子系统。 本地数据库:与服务器交换或同步的数据表示。如何管理这些数据取决于实现要求。 对于上述关注点,Emacs 提供了众多内置和第三方库,可以实现客户端。下面列出了一些内置库及其相应链接以供进一步阅读: UI 迷你缓冲区 缓冲区 补全 列表模式 可变音调表 (vtable) 短暂 客户端边缘 URL 套接字 (TCP/UDP) SMTP 序列化/反序列化 JSON XML 本地数据库 集合 关联列表 属性列表 哈希表 SQLite 要求决定了实现 Emacs 客户端所需的复杂性程度。如果存在可以进行 "重负荷 " 的现有命令行工具,该工具可以重新构造为可以通过终端调用访问的 "服务"。 Elisp 上述所有库都是通过 Emacs Lisp (Elisp) 编程语言访问的。Elisp 是一种动态编程语言,允许在运行时进行高度的即兴发挥。这一能力使得可以复杂地编排可用于 Emacs 的任何行为,从 Elisp 函数到 shell 命令。 示例 wttr.in 客户端 wttr.in 是一个面向控制台的天气预报 Web 服务。它支持 JSON 输出,因此我们可以构建一个 Emacs wttr 命令,该命令会提示输入位置,发出 HTTP 请求,处理 JSON 响应并在迷你缓冲区中显示结果。顶部命令 wttr 如下所示。 ```elisp (defun wttr (location) "在迷你缓冲区中显示 LOCATION 的天气状况,来自 `https://wttr.in'。结果也存储在 `kill-ring' 中。" (interactive "sWhere (default: 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 构建的。 ```elisp (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 哈希表的重担。 ```elisp (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 缓冲区解析为哈希表 (json-parse-buffer :object-type 'hash-table)) ;; 始终杀死下载的网络缓冲区以防止内存泄漏 (kill-buffer data-buffer))))) ```
本站免费、广告极少。如果觉得有帮助,可以请我们喝杯咖啡 —— 任何金额都对持续运营有实际帮助。
☕请我喝杯咖啡