以下是我用 Gemini 翻译成中文的文本:
The Emacs widget toolkit
在此博客文章中,我概述了几个月来反复考虑和深入研究 Emacs 显示代码的后果。
在本文中,我将概述 Emacs 窗口工具包的潜在发展方向,制定一个旨在复兴 Emacs 并使其达到一个新用户可以欣赏其作为杰作工程的程度的计划。
但首先,让我们谈谈问题。
“All that glitters is gold”
Emacs 并不丑。虽然有些过时,但这种旧风格反而增添了它一种复古的魅力。
大多数人认为,缺乏动画或其他花哨的功能是 Emacs 吸引人的主要原因,但事实并非如此。我们有相当酷的基于 SVG 的主题,而且 Emacs 具有长期的适用性。 Emacs 并非追求视觉效果,它不像一辆 Bugatti 跑车那样华丽,而更像一辆 Ferrari F50。它可能看起来有些丑陋,但你终将学会欣赏它的内在价值。
所以,如果 Emacs 的问题不在于视觉效果,那么使用 Widget 工具包还有意义吗?
GTK was a mistake
GTK 是一个关于如何不应该设计工具包的案例研究。它的唯一优点是它提供 C-ABI,这意味着您只需要对编程语言进行一些小调整,就能创建链接到系统 GTK 库的程序。这是 GTK 存在的唯一原因。它很便宜而且容易设置。
在 Trolltech 经常戏弄人们并威胁将其 Qt 改为专有软件的情况下,以及随之而来的断裂和基于 C++ 的工具包的断裂,曾经发生过。最初的想法是,GTK 将是 GNU 的原生工具包,一切都将基于 GTK。GNU 网络对象模型环境出现后,情况似乎有所改善。
不幸的是,Gnome 2 最终未能持续存在,以及理性的开发实践、相互的尊重的态度和生产力的日子被我们今天所看到的所取代。
最初名为 Gimp 工具包的 GTK 工具包,是问题的开端和终点。它是人们选择避免设计跨平台原生程序的原因的 alpha 和 omega,并专注于将 Chrome 塞进一个软件包中,以及一些似乎是代码中少数部分的网页。
这个工具包具有一种不寒而栗的能力,可以扔掉完美运行的代码、破坏向后兼容性,并且似乎旧程序在较新的工具包上运行得更糟,这/似乎/不是 GTK 的错。
它是一个动态链接库,几乎没有任何能力运行旧程序。在之前支持并证明在其他领域有价值的特性已经被剥夺了,比如复杂程序。开发团队似乎认为,就像苹果一样,他们能够生产简单程序,这些程序在背景中消失,并且不需要配置,并且他们的设计是唯一您可以看到的。GTK 对不配置的原则立场,与缺乏实际资源来真正地做到不配置的结果,导致 GTK 程序必须不断地追赶,并且不知道下次会删除什么。
那些为 GTK 的政策辩护的人似乎相信了标题党谎言,并且没有深入挖掘。该工具包并非最小化,因为它既没有减少攻击面,也没有提供一个小型且稳定的特征集,可以依靠。有主要版本以一种“吃或扔掉”的方式提供,像 Linux Mint 这样的有识之士选择了软件工程领域的中间手指。
Gnome 和 GTK 并不相同:就像左手袜子傀儡与右手袜子傀儡一样。同样的糟糕主意在两个项目中的都盛行。选择一个,就选择了另一个项目的设计“哲学”。有多个例子表明这种紧密耦合是一个好主意:苹果做得很好,微软也做得很好,而谷歌,尽管有它的缺点和不一致之处,至少会为您提供足够的喘息空间,让您可以遵循或忽略他们的指南。不像 Gnome 一样,也不像 Flathub 一样,以及由此导致的 GTK 程序,除非它们落入伪极简主义的垃圾箱中,否则它们会被视为次要的。
GTK was a mistake for Emacs
现今使用 GTK 似乎仍然是一个好主意。为了更好地理解情况,桌面 Linux 正在转向 Wayland,一种取代 X11 垄断并形成一个寡头市场的协议。这意味着一些以前的方式不再适用。对于终端 UI Emacs 没有任何变化,但对于其 GUI 程序本身,则需要进行调整。
与其一头扎进 Emacs 中,添加条件编译指令并试图解决无法解决的问题,不如利用 GTK,它不仅实现了,而且似乎主导了许多协议。太棒了!
然而,结果却完全不尽如人意。PGTK 版的 Emacs 提供的优势很少,甚至没有可察觉的优势。它仅提供略微改善的字体渲染(与 WebRender 和 Qt 相比仍然不佳),并且修复了剪贴板。这个列表还很长,
这个构建不标准,在X11上无法工作,并且无法禁用相关的错误提示;它更易于出现卡顿,因为UI线程每绘制一次调用会执行更多工作;它无法在KDE Plasma上调整大小,只能最大化和最小化;它有一个长期存在的 bug,会导致崩溃显示服务器时也会导致 Emacs 服务器崩溃;它会向终端输出大量的警告,这些警告既无法修复也不能被忽略;它的主题化能力很差,GTK主题很容易与Emacs的其他部分形成对比;这是一个不断变化的构建,因为它当前将Emacs链接到GTK 3,而不是GTK 4,而第五版本正在紧锣密鼓地开发中。
也许这是因为 Emacs 没有充分利用 GTK?我认为 GTK 的功能实在太少,不足以支持其使用。一个令人信服的理由是 Lucid 工具包看起来有些过时,考虑到 Emacs 的 Chrome 占据的视图比例微乎其微,这让我觉得有点好笑。
Emacs 感觉不像一个图形程序,很大程度上是因为 GTK 提供的功能有限。你仍然更倾向于使用 consult
或 dired
打开文件,因为图形化的“打开文件”对话框似乎遵循一套不稳定的规则。工具栏也鲜少有经过良好设计的图标,并且很少响应对变化的正确操作。customize
界面使用文本形式模拟实际的控件,这主要是因为 GTK 无法提供 Elisp 所需的这种高级控制。试图将 minibuffer 整合到专门设计的 UI 元素中,结果总是出现行为不佳,用户最终又会回到不使用 posframe
工具的习惯。alert
包,旨在提供通知,比用 Elisp 编写几行代码封装 notify-send
更不可靠。键盘事件处理仍然采用自定义方式。没有任何 UI 能够充分利用 GTK 的子集,Emacs 倾向于使用 Vue 和/或 PyQt 用于其显示。Emacs 内置的终端模拟器默认不使用 VTerm,只有通过第三方包才能启用。添加分片视图的最佳方法是模拟它们在文本中。玩 Tetris 的最佳方法不是使用 GTK 的内置功能。
菜单项也未能充分利用GTK的优势。快捷方式的显示方式绕过了GTK的处理方式,导致例如“Ctrl+Shift+F”在Inkscape和“C-M-f”在Emacs之间存在明显的差异。菜单栏不可靠,无法通过全局菜单快捷键(例如在KDE Plasma中)进行控制,而该功能在macOS上似乎运行良好。
诸如平滑滚动和针对特定操作系统(例如)的图形应用程序选择颜色等功能也相对缺失。平滑滚动并不平滑,它只是允许您在文本的非整数部分之间移动,这既不美观,也不实用。 缺乏对替代输入方式的支持,例如触摸屏和游戏手柄。 根本没有任何支持!这引出一个真正的问题,Emacs 从使用 GTK 中获得了什么?
事实上,对于这个问题,答案数量正在减少,除非GTK和Emacs都做出重大改变,否则不太可能改善。如果你当初来此文章是为了了解为什么不能仅仅修复PGTK构建并继续前进,你现在会明白,这根本无法解决问题。一个类似于GTK的工具包根本无法满足Emacs的所有需求。而且,鉴于Elisp提供的深度定制和控制,我认为主流工具包也难以做到这一点。因此,没有Qt移植。
Yes, there are two paths you can go by…
在这一部分,我们将概述两种最有可能的途径,以使 Emacs 更加灵活,并使其更像一个具有 WYSIWYG 功能的图形化程序,而不是一个复杂的文本处理工具。
Why plaintext is king
由于纯文本是 Unix 编程和 Emacs 的主要原因在于其普遍性。标准字符集是 UTF-8(即 Unicode),它在任何地方都能被渲染。交互方式也相对标准化,包括按字符左右移动、理解行、以及向上遍历(Emacs 默认支持,但并非总是如此)、对换行符、制表符和空格的理解。在 monospace 字体下,这些特性共同创造了一个舒适、一致的环境。
任何在 Emacs 中操作纯文本的函数都可以将这些信息反馈给 dired
并进行批量重命名和编辑。如果一个事物有文本表示,就可以编辑该文本表示,然后转换回来,实现“一劳永逸”。
这使得 UTF-8 成为一种通用的数据表示语言。复制、粘贴、剪切、删除等操作,以及大多数键盘的“自插入”行为,允许使用一套可转移的技能直接与大多数数据交互。例如,如果要创建一个在 Emacs 中编辑视频标签的文件(Incidentally, this is on my to-do list),可以通过提供可预测的、普遍的导航,而无需过多思考。这些操作相互补充。学习如何使用 forward-sexp
,也能在 FORTRAN、Algol、C、Rust、Elisp 和文件名等环境中进行。如果某个操作是上下文敏感的,则必须提供教程,尽管从严格意义上讲,最好的学习方式是通过观察——闪烁的光标、行/列布局的 monospace 字体,以及一些其他技巧,为每个人提供一个熟悉的地方。
图形界面没有这种类似的谓词集。如果我想按下特定的按钮,就有一种/某些可能性,图形工具包会提供我可以按键进行操作的按钮,但这些按钮似乎是任意的,只有当我按住 =Alt= 键时才会显现。这些快捷键可能很高效,但大多数时候,它们是基于阻塞操作,因此,如果要运行一个深藏在菜单中的快捷键,需要按住 =Alt= 键并输入一个很长的序列,很可能会错过想要按的按钮。而且,并非所有操作都表示为菜单项。在网络上,你只能希望程序的设计者给你提供足够的提示,为必需的对象赋予焦点能力,并且你不需要长时间按住 =Tab= 键,才能最终按下想要的按钮。这种控制力非常有限。
对于普通人来说,这种差异可能不明显,但对于 Emacs 用户来说,这些差异会累积起来,你就会开始怀念一个更简单的时间,那时一切都是文本缓冲区。但这并不意味着图形工具包本质上就无法提供这种统一的导航方式。毕竟,网格是普遍存在的,六边形坐标系统并不常见,而且如果文本不是 monospace 字体,你所去的地点和你所想去的地点并不总是对应的。
然而,很少有工具包提供足够的键盘和鼠标控制水平,因此你无法通过图形工具包完全复制 Emacs 的功能。更何况,要用控制器导航它们,你实际上是在模拟键盘。
那么,我们需要什么呢?
Uniform navigation verbs
关键原因之一是为什么 Emacs 如此出色,并且其影响力至今可见于 macOS 中,在于其丰富的导航动词。您有前进和后退的概念,也有单词、行、句子和 S-表达式之间的差异。在理论上(尽管并非在实践中),如果用户界面词汇标准,而且大多数情况下确实如此,那么您可以以类似的方式定义这些事物。
诸如 =avy= 之类的软件包允许您随机访问屏幕上的任何内容。它们是终极的导航工具,但并非人们直观使用的工具。这种词汇在 KDE 中仍然存在,例如,使用下划线标记特定事物,以表明可以按下 =Alt + <该键> = 来使用该对象与文本。这是一种有限的导航形式,因为符号交互对象数量是非零的。尽管焦点可以替代点,但在 Emacs 动词中,点可以同时指向多个事物。它可以指向一页、一个文件、一个标识符,或者 minibuffer 中的一个选择… Emacs 中的动词非常有限,因此如果我们想要推动进展并提供足够丰富的系统,我们需要创建等效的东西。
另一个约束是交互必须是自然的。而这正是 Emacs 过去 通过文本模拟许多图形元素发挥作用的原因。网格布局和行/列无处不在。还有组和容器。只需指定一个主要功能按钮(例如,= =)来使交互正常工作,这幸好我们已经有了。也许值得直接触发某些函数,并且必须有一种方法来指定给定时间点绑定哪些函数,但诚然,这些都相对容易地在 Emacs 中分类,这要归功于其 =一切皆函数= 的理念。
这些不仅用于专用模式。LSP 提供了多个交互元素叠加在文本缓冲区上。扩展这些信息,确保尽可能地使其可用,并将这种功能从 LSP 中分离出来,并将其引入主模式,这与 Emacs 的核心价值观相符。这意味着方便的 UI 元素可以引导您避免犯愚蠢的错误,并且在不使您的生活变得更加痛苦,尤其是在滥用绘制预算的情况下,也是一种理想的解决方案。
缓冲区在文本方面似乎效果很好,但缓冲区可以是多形的。这对于 Emacs 的图形 UI 直接工作需要进行必要的更改,但可以推迟。如果代码更改正确地进行,应该对用户透明,只需提高低级绘图 API 的效率,而不会影响高级。这是关键所在,我们不能破坏用户空间。尤其是考虑到 Emacs 的用户对变革持抵制态度。
SVG
这种方法,在某种程度上已经相当普及。你只需将 SVG 添加到缓冲区,并通过操纵对象来布局它们。
但这可能不是我所指的,但如果这种方法足够高效,它/可能/能行。简单地说,大部分的繁重工作,包括布局和渲染矢量图形,都是由我们完成的。这些布局和图形在各个平台上都是一致的,如果经过适当的组织,即使计算成本较高,也能发挥出奇妙的效果。所以,即使有点低效,与 HTML 相比仍然更快。
这种方法的缺点很明显。它仅限于 SVG 的能力。当然,我们可以嵌入大量的媒体,但这些媒体需要复杂的交互。否则,解决所有这些问题的简单方法就是创建一个 JPEG 的布局并显示它,而不是主窗口。
这在原则上不是一个坏主意,但更有可能成功,因为社区已经在朝着这个方向发展。例如,Nicholas Rougier 的出色工作,以及像 =nova= 这样的项目,允许你使用迷你缓冲区做许多有趣的事情(当它决定工作时)。通过将 SVG 嵌入到代码和文本中,可以提供丰富的命令集,从而可以做很多事情。虽然这些都不是理想的,但它们比没有好,并且朝着这个方向已经可以开始。
我的朋友 Divya Ranjan 已经有一个功能齐全的便携式 PDF 阅读器,它比其他任何解决方案都要好,并且依赖于 SVG。扩大在这一领域完成的工作量可能不是一个我们需要一概而论地否认的想法。
然而,也需要认识到这是许多人陷入的常见陷阱。使用浏览器窗口显示文本与使用 SVG 显示缓冲区内容之间的原则区别在于,HTML + JavaScript 在更长的时间内共同演化,而 Elisp + SVG 以及它们早期作为二者的结合,尽管这两个标准在实践上已经相当成熟。
另一个问题是效率方面的问题。SVG 的渲染是一个阻塞过程。Emacs 没有足够可靠的 =async= 运行时,无法让这些效率方面的毛刺逐渐退化到背景中。过度依赖 SVG 来处理一切都可能导致双重困境:SVG 可能会改变,Elisp 库也可能改变。
更根本地来说,这会阻止针对特定应用类型的某些优化。最自然的表达方式可能效率低下,因此像使用带有帽子的NPC作为交通工具这样笨拙的方法是可以预期的。这在个人项目上并非问题,但仅限于个人项目,就无法提供某些默认的质量提升软件包。我很乐意投入大量精力来设计一个 SVG 在 Emacs 中发挥更大作用的未来,如果我确定它不会像使用文本渲染UI元素的原始“黑客”方法一样,导致它几乎从未被使用。
A custom toolkit
目前看来,完全将 Emacs 与任何现有库隔离,并尝试独立发展,可能是一个不错的选择。这并非意味着它必须与文本编辑器耦合,而是 Emacs 也许可以成为下一个 Chrome,如果玩法得当,Electron 程序可能会被 Emacs 程序所取代。
要实现这一点,需要付出大量的努力。
需要彻底扩展 Emacs 的可编程性,充分理解 Emacs Lisp 的优势和劣势。 就像 JavaScript 受到压缩截止日期影响而做出决策一样,Emacs Lisp 同样基于对未来编程不准确的评估。 相比之下,Emacs Lisp 拥有“技术债务”的优势,即由于其历史悠久,大多数不一致之处可以归结为“技术债务”,而实际上修复这些问题既简单,而且如果没有少数“老古董”(讽刺的是)坚持使用默认设置,这些问题早就得到了解决。
Emacs 需要一个低级别的自定义工具包。它需要能够从 Elisp 中绘制,并且绘制得非常好。 我们需要一个与 SVG 类似的东西,虽然不是在语言上,但在目的、丰富度和结构方面。
虽然可能会让人觉得这是不可能的,但历史上已经有许多更雄心勃勃的项目被尝试、实施和发布。问题在于框架和缺乏规划。 有些项目做的事情类似,但没有明确的范围、没有对所需时间和完成日期有概念、也没有可量化的进度报告。 这个项目需要足够优秀,以至于您可以发起募捐活动,邀请来自不同背景的程序员全职捐助。 我是真的这么说的。 我有很多不太雄心勃勃的项目在积压中,我真的需要一个能推动进步的项目。
直接使用SDL以获得跨平台,但极其低级的API。将这些低级API暴露给Elisp,并允许使用Emacs的模块系统进行静态链接和交互。 建立一个基于Elisp的消息传递系统来控制UI。 扩展现有的键盘系统以与各种输入方法(例如Steam Deck控制器)一起使用。 创建一种基于Elisp的最小化QML风格布局定义语言,该语言使用极简的系统进行主题化。 我们不需要/也不需要完整的CSS,但继承面和覆盖层可能是有用的。
理论上,拥有一个低级API,可以将一个想法转化为一个Emacs包,并使其能够独立执行,并非不可能完成的任务。 理论上,Emacs仍然可以占据主导地位,如果能够快速创建一个用于常见任务的GUI。 在Elisp中,这已经很大程度上可行,但像C和C++这样的语言的减少功能和表达力,使得创建功能性的GUI变得困难。 Qt需要Moc才能做到,而GTK则必须模拟一种伪面向对象环境,才能让程序运行。 考虑到这些,我们处于更有利的位置。
There’s still time to change the road you’re on
我做了一些关于 Emacs 代码库的研究,并希望能分享我的发现。不过,内容可能会过于枯燥。这可以看作是 Emacs 控件工具包的宣言,旨在创造一些优秀的东西。
我计划进行更深入的探索,了解底层机制,并尝试通过低级别的编程魔术来插入我们的位置。
我也需要你们的反馈。
如果您正在阅读此文,欢迎向我提出匿名(或非匿名)建议,请发送邮件至:appetrosyan at linux full-stop com
请务必对以下主题发表您的看法:
- 帮助作为 Elisp 程序员的愿望
- 帮助作为 C 程序员的愿望,拥有 SDL 知识
- 帮助进行筹款的愿望
- 帮助撰写文档的愿望和能力
- 关于 Elisp API 应该如何设计的想法
这希望是许多帖子中的第一篇。我真诚地希望能够尽快在我的个人作品集网站上建立评论区,以便您能提供更及时的反馈。总而言之,我希望这段旅程能将我们带到一个美好的地方。
Last Updated:: 27/04/2025, 01:13
Contributors: Aleksandr Petrosyan