原文:https://mp.weixin.qq.com/s/_zXZOlYtcA0BaQ82rUQIXQ
在决定使用 Emacs 作为 RaySystem 的前端交互,我把 Emacs 视为一种“前端框架”。Emacs 中的 Major Modes 是控制缓冲区行为的核心机制,在本文中,通过创建一个 Major Modes,实现了一个简单的站点列表管理界面。
❝
感兴趣的小伙伴,欢迎关注本号,同时 star 支持这个项目:GitHub - maxiee/RaySystem 本文代码位于 RaySystem 仓库的
emacs
目录下。❞
Emacs Major Mode
Major Modes 的核心作用在于根据文件类型提供相应的编辑功能,使编辑体验与内容类型匹配。不同类型的文件会启用不同的功能,如代码文件通常会提供语法高亮和自动缩进,而文本文件可能只提供基本编辑功能。
Major Modes 也可以与具体文件无关,编程带有某一类功能的应用,比如 Elfeed RSS 资讯阅读器,或者 2048 小游戏。
Ray Info Site Mode
在本文中,我们将实现如下图的站点管理功能:
这是一个可交互的界面,通过上下选择条目,并支持以下快捷键:
按键 | 作用 | 命令 |
---|---|---|
c |
创建新站点 | ray-info-site-add-command |
e |
编辑当前站点 | ray-info-site-edit-command |
d |
删除站点 | ray-info-site-delete-command |
q |
退出当前模式 | kill-current-buffer |
可以看到,每个按键都与一个函数相关联。
创建 Major Mode
通过以下代码创建 Major Mode:
;;;###autoload(defun ray-info-site () "Open the Ray Info Site management buffer." (interactive) (switch-to-buffer (get-buffer-create "*Ray Info Site*")) (ray-info-site-mode))(define-derived-mode ray-info-site-mode tabulated-list-mode "Ray-Info-Site" "Major mode for viewing and managing a list of websites." (setq tabulated-list-format [("Name" 20 t) ("URL" 40 t)]) (setq tabulated-list-padding 2) (setq tabulated-list-sort-key (cons "Name" nil)) (hl-line-mode 1) (ray-info-site-refresh))
其中,先看 ray-info-site
:
-
定义了一个命令,用于打开主模式的初始化
-
;;;###autoload
是一个固定写法,用于 Emacs 对包的 autoload 机制,是一种加速初始化的优化 -
首先我们创建了一个名为
"*Ray Info Site*"
的 Buffer,并切换过去 -
然后调用
ray-info-site-mode
进入这个主模式
再看 ray-info-site-mode
:
-
主模式是支持继承的,
ray-info-site-mode
继承子tabulated-list-mode
,这是一种以分列方式展示里列表的模式,我们在下一节中介绍。 -
接下来是对
tabulated-list-mode
的一系列设置,比如都有哪些列,每列多么宽,如何排序。 -
(hl-line-mode 1)
高亮当前行,ListView 的选中效果通过它来实现。 -
最后通过
(ray-info-site-refresh)
来加载数据。
tabulated-list-mode
「tabulated-list-mode」 是 Emacs 中一种用于显示和管理表格数据的主要模式,适合用于开发交互式工具。它提供了一种直观的方式,将多列数据以表格的形式显示,并允许用户通过键盘或鼠标与数据进行交互。
主要特性
-
支持以行和列的形式组织和呈现数据。
-
每一行代表一个条目,每一列代表该条目的属性值。
-
提供了内置的排序功能,用户可以通过点击列标题或快捷键快速对表格数据进行排序。
-
支持绑定键盘快捷键,为每个条目添加交互行为,例如打开、编辑或删除条目。
核心概念
列描述符
列描述符是表格的核心部分,用来定义表格的列结构。每列都有一个名称、宽度以及可选的对齐方式。定义列描述符的典型形式为:
(setq tabulated-list-format [("列名1" 宽度1 对齐1) ("列名2" 宽度2 对齐2)])
-
「列名」:列的标题,将显示在表格的顶部。
-
「宽度」:列的宽度,以字符数表示。
-
「对齐」:列内容的对齐方式,取值可以为
left
、center
或right
。
数据项
数据项是表格中的每一行,它由一个唯一标识符(id
)和一个属性值列表(values
)组成。定义数据项的典型形式为:
(setq tabulated-list-entries '((id1 ["值1" "值2"]) (id2 ["值3" "值4"])))
更新显示
每次更新表格时,需要调用 tabulated-list-mode
的更新函数:
(tabulated-list-init-header) ; 初始化表头(tabulated-list-print) ; 刷新表格显示
使用场景
-
「管理文件或进程」:如 dired-mode 的文件管理视图或进程列表工具。
-
「显示搜索结果」:可将多列搜索结果呈现为表格,供用户进一步筛选或操作。
-
「开发自定义工具」:任何需要以表格形式显示数据的工具都可以基于此模式实现。
优点
-
简化了表格显示的实现,开发者无需手动处理复杂的布局逻辑。
-
支持交互式操作和动态更新,扩展性强。
-
内置排序功能,提升用户体验。
缺点
-
表格的外观较为简陋,受限于 Emacs 的文本显示能力,难以满足复杂 UI 的需求。
-
学习曲线相对较高,初学者可能需要一定时间掌握其概念和用法。
注册快捷键
Ray Info Site Mode 支持一系列快捷键来实现交互。
在 Emacs Lisp 中,当使用 define-derived-mode
定义主模式时,符合 -map
后缀命名规范的键映射会自动与模式关联。具体来说,键映射的名称必须为 <模式名称>-map
。
键映射应该在模式定义之前定义。当激活该模式时,Emacs 会自动将这个键映射应用到对应的缓冲区中。
键盘映射的代码如下:
(defvar ray-info-site-mode-map (let ((map (make-sparse-keymap))) (define-key map (kbd "c") 'ray-info-site-add-command) (define-key map (kbd "e") 'ray-info-site-edit-command) (define-key map (kbd "d") 'ray-info-site-delete-command) (define-key map (kbd "q") 'kill-current-buffer) map) "Keymap for `ray-info-site-mode'.")
站点数据结构
整个模式围绕站点列表这个数据结构进行展示和 CRUD,数据结构的定义如下:
(defvar ray-info-site-list '((:name "Google" :url "https://www.google.com") (:name "Emacs" :url "https://www.gnu.org/software/emacs/") (:name "Example" :url "https://example.com")) "List of Websites, each element is a plist: (:name NAME :url URL).")
列表展示
结合前面对 tabulated-list-mode
的讲解,要展示这个数据结构,分为两步:
-
将上面的数据结构转换为
tabulated-list-mode
的 Entry 格式,我们选择使用 site item 作为 id,在列表中存放每一列的数据。 -
再通过
(tabulated-list-print t)
函数触发 UI 刷新
具体代码如下:
(defun ray-info-site--build-entries () "Build `'tabulated-list-entries' from `ray-info-site-list'." (mapcar (lambda (site) (let ((name (plist-get site :name)) (url (plist-get site :url))) (list site (vector name url)))) ray-info-site-list))(defun ray-info-site-refresh () "Refresh the website list in the buffer." (interactive) (setq tabulated-list-entries (ray-info-site--build-entries)) (tabulated-list-print t))
创建条目
ray-info-site-add-command
命令负责创建新条目,它也分为两个函数:
-
一个函数处理命令行交互,通过
read-string
读取用户输入 -
一个函数用于执行数据结构的添加,同时触发 UI 刷新
(defun ray-info-site-add-site (name url) "Add a new site with NAME and URL to `ray-info-site-list'." (push (list :name name :url url) ray-info-site-list) (ray-info-site-refresh))(defun ray-info-site-add-command () "Command to add a new site." (interactive) (let ((name (read-string "Enter site name: ")) (url (read-string "Enter site URL: "))) ;; 未来扩展点:可在此增加一轮对频道(Channel)的选择或者创建交互 (ray-info-site-add-site name url)))
删除条目
ray-info-site-delete-command
命令负责删除光标所在的站点,它的实现略复杂一些:
-
首先通过
ray-info-site--current-site
获取当前选中站点 -
弹出一个
yes or no
的二次确认 -
如果确定,调用
ray-info-site-delete-site
进行站点删除。
具体代码实现如下:
;; 先获取 entry,entry 的 id 就是 site(defun ray-info-site--current-site () "Return the site data of the current line." (let* ((entry (tabulated-list-get-entry)) (site (tabulated-list-get-id))) site))(defun ray-info-site-delete-site (site) "Delete SITE from `ray-info-site-list'." (setq ray-info-site-list (delq site ray-info-site-list)) (ray-info-site-refresh))(defun ray-info-site-delete-command () "Command to delete the current site." (interactive) (let ((site (ray-info-site--current-site))) (unless site (error "No site selected")) (when (y-or-n-p (format "Delete site '%s'?" (plist-get site :name))) (ray-info-site-delete-site site))))
总结
我把 Emacs 视为一种“前端框架”,我这次的开发中,明显感受到开发交互的便捷性。
比如,通过 tabulated-list-mode
我能以极低的成本开发出一个 ListView,这让我不被 UI 分心,从而将精力都集中于逻辑与功能上。
通过 read-string
即可读取用户输入。要知道,在任何一个前端框架中,都需要通过 Dialog Modal 来接受用户输入,然后还要去处理异步,当 UI 开发变成一个“事情”,就分掉了开发者的精力。
在传统 UI 开发中,为了实现 CRUD,要么要在 UI 中设计布局,摆放按钮,要么,按照在现代化移动应用的开发模式中,要为 ListView 设置各种手势,比如长按弹出一个菜单,或者在列表卡片 Item 右边设置三个点,点击点弹出菜单。现如今,在 keymap 之下,这些烦人的事情都省掉了。
当然,Emacs 的这种简洁是以复杂的学习曲线为代价的。但是,RaySystem 是我设计给我自己用的系统,而我自己完全能够接受这样的学习曲线,同时,我自己也乐于使用这样的交互方式。我不喜欢现代化的交互,我更加喜欢这种传统、复古的交互方式。
综合来看,实现一个 Major Mode 还是非常容易上手的,甚至比流行的前端开发框架还要简单。这也增强了我使用 Emacs 作为 RaySystem 前端的信心。
AI 点评
批判性思考与深度洞见
1. 「Emacs 作为“前端框架”的定位是否合理?」
作者将 Emacs 定位为“前端框架”,本质上是基于 Emacs 的可扩展性和强大的交互定制能力。这种类比具有启发性,但也存在局限:
-
「合理性」:Emacs 的 Major Mode 确实能够通过键盘驱动的交互方式,高效完成特定任务的界面开发。这种框架式的模块化设计类似于现代前端框架中的组件化开发,同时依托于 Emacs 的内置功能如
tabulated-list-mode
,快速实现了常见的列表交互。 -
「局限性」:与现代前端框架(如 React 或 Vue)相比,Emacs 的交互和布局能力更接近于“命令行界面”,缺乏视觉化、动态化的表现力。这种“简洁”的前端框架定位在开发者工具或内部工具的场景中是合理的,但在更广泛的用户界面需求下显得局促。
2. 「简化 UI 的优劣权衡」
作者强调通过键盘驱动交互和 tabulated-list-mode
等工具,省略了许多传统 UI 开发中的复杂逻辑。这种方式确实降低了开发成本,但也隐含着问题:
-
「优点」:开发者可以专注于核心逻辑,而不被复杂的 UI 布局和异步交互分散注意力。在以开发者为核心的使用场景中(如内部工具或个人项目),这种模式高效且直观。
-
「缺点」:依赖键盘驱动的交互方式对普通用户不够友好,尤其是在移动端和触控设备普及的背景下。这种设计需要用户熟悉 Emacs 的操作模式,且对非开发者的学习成本极高。
此外,作者提到的“简化对话框和异步逻辑处理”的优势,可能在面对复杂交互(如多层级依赖或实时反馈)时,反而成为一种瓶颈。
3. 「交互模式的适用性与主观偏好」
作者明确表示喜欢这种“传统、复古的交互方式”,并基于此构建了 RaySystem 的前端。这种设计显然是为满足作者个人需求而定制的,但值得讨论的是:
-
「面向个人的合理性」:作者对 Emacs 的掌握程度和习惯偏好决定了这种设计是适合的。开发者通过这种模式可以实现“为自己而设计”的极简系统。
-
「面向大众的局限性」:如果 RaySystem 日后计划推广至更广泛的用户群体,这种复古交互模式可能需要重新评估。即使功能强大,过高的学习曲线可能导致用户流失,且现代用户更习惯于直观的图形化界面。
4. 「技术实现与扩展性的探讨」
本文在技术实现上展示了 Emacs 的灵活性和强大功能,但以下问题值得深思:
-
「可扩展性」:当前实现以
ray-info-site-list
作为核心数据结构,CRUD 操作较为简单。若未来需要添加更多复杂功能(如分类、标签或批量操作),是否需要重新设计数据模型?能否以更通用的方式封装这些逻辑? -
「性能与并发」:虽然 Emacs 本质上是单线程运行的,但若 RaySystem 的规模扩大到处理数百甚至数千条数据,是否需要引入异步机制来提升性能?Emacs 的
tabulated-list-mode
能否胜任更大规模的数据管理需求?
5. 「对现代化前端开发框架的类比是否公平?」
作者在文中对比了 Emacs 的开发体验与现代前端框架,但这种对比并不完全公平:
-
「抽象层级不同」:现代前端框架需要面对更广泛的设备支持和复杂的用户体验需求,而 Emacs 是高度专业化、面向开发者的工具。这种差异导致两者的开发复杂性和定位本质不同。
-
「功能覆盖面不同」:现代框架不仅支持数据绑定和交互,还涵盖了动画、响应式设计等领域。而 Emacs 的功能集中于文本和键盘交互,难以应对复杂 UI 的需求。
6. 「关于“省去复杂 UI 布局”的深层思考」
作者认为 Emacs 的键盘驱动交互能够“省去复杂布局逻辑”。这一观点在开发工具场景下有一定道理,但忽略了现代 UI 的发展趋势:用户体验不仅仅是简化开发,更在于创造高效且直观的使用方式。键盘驱动固然适合开发者,但更广泛的用户可能更依赖鼠标、触控甚至语音交互。
深度洞见
-
「从个人化工具到普适系统的转变」
RaySystem 的设计显然高度个人化,这种开发哲学和 Emacs 的核心理念契合。然而,若作者期望 RaySystem 发展成更广泛适用的系统,必须考虑交互方式的适配性。例如,可以保留键盘驱动的核心交互,同时引入图形化界面以降低用户门槛。 -
「数据模型的可扩展性与系统复杂度」
当前的站点列表管理设计简单直观,但未来若需要支持更多维度的操作(如站点分组、标签或版本控制),可能需要重新设计数据模型或引入数据库支持(如 SQLite)。同时,这种设计是否能平衡复杂性与性能,也是值得提前考虑的。 -
「对 Emacs 的局限性认知」
Emacs 的核心优势在于灵活性和扩展性,但其单线程、有限的图形能力以及与现代用户习惯的脱节,限制了它作为现代应用前端框架的潜力。如果 RaySystem 的开发计划超越个人工具,这种局限性将成为系统发展的关键瓶颈。 -
「学习曲线与目标用户」
作者明确表示自己能接受 Emacs 的学习曲线,但如果 RaySystem 的目标用户群体扩展到非技术背景的用户,这种交互设计可能需要彻底重构。作者可以考虑在 Emacs 核心之外,开发基于浏览器或跨平台 GUI 的前端版本,以弥合不同用户群体的使用习惯。
综上,本文展示了作者在 Emacs 平台上的深厚技术功底和独特思考。但未来若希望 RaySystem 不止服务于个人,还需在数据模型、交互方式、平台扩展等方面更进一步。
本文转自 https://mp.weixin.qq.com/s/_zXZOlYtcA0BaQ82rUQIXQ,如有侵权,请联系删除。