《elisp 每日一练》——Window 树与布局状态恢复

前面的文章中,我们介绍了可以通过 split-window 来创建窗口,也介绍了通过 walk-windows 遍历窗口并且通过 overlay 机制实现了一个简单、直观的快速跳转窗口的功能。今天我们来深入了解背后的原理

背景知识

各位读者如果观察过Emacs执行 split-window 的效果,应该会发现它只会将当前窗口的区域进行划分,将当前窗口区域分为两个部分。这样我们很容易想到一个树的结构,光标所在的位置是树的一个叶子节点,在新建窗口时将该叶子节点作为新的根节点,在下方创建两个新的叶子节点。

其实Emacs底层正是这样做的。我们可以通过 window-tree 来获取当前frame下窗口布局的树结构。

我们用一个简单的例子来描述一下返回的具体结构, 假设我们有如下一个窗口布局

txt 复制代码
+------------+-------------+
|            |    Window   |
|   Window   |     B       |
|      A     +-------------+
|            |    Window   |
|            |     C       |
+------------+-------------+

我们调用 window-tree 得到的结果可能是这样的

emacs-lisp 复制代码
(
 (nil (0 3 84 38)
      #<window 3 on *scratch*>
      (t (42 3 84 38)
	 #<window 8 on *scratch*>
	 #<window 10 on *scratch*>))
 #<window 4 on  *Minibuf-0*>)

从整体上看它分为两个大的部分,第一个部分是一个复杂的结构,而第二个部分是一个window对象,它实际上是 mini-buffer 的窗口。

我们再来看看前面一个大的结构。我们将它转换成一个树状结构

txt 复制代码
├── (nil (0 3 84 38)
│       ├── #<window 3 on *scratch*>
│       └── (t (42 3 84 38)
│               ├── #<window 8 on *scratch*>
│               ├── #<window 10 on *scratch*>
└── #<window 4 on  *Minibuf-0*>

通过这个我们发现,一个根节点实际是当前区域的分割方式和区域的大小,第一个参数为 nil 表示它是水平分割,而 t 表示垂直分割。各位读者请注意,这里的水平和数值的理解,这里并不是在窗口上按水平或者竖直方向画一条线将窗口进行划分,而是表示将新窗口按照水平或者竖直方向进行排列。所以上述结构真正含义其实是:

  • 先通过 nil 将窗口区域划分为水平方向的两个窗口,也就是左右两个
  • 其中一个为window3
  • 将右边区域一分为二,通过 t 表明它应该是竖直方向的两个窗口,也就是在中间水平位置画一条线进行区分

在过去如果想要保存和恢复这个窗口布局,我们需要通过 window-tree 函数保存当前的树结构,恢复时解析每个节点并且调用 split-window 重新根据节点进行。但是从Emacs 24.1 之后提供了两个函数,可以方便的进行保存和后续的恢复: window-state-getwindow-state-put

emacs-lisp 复制代码
(window-state-get &optional WINDOW WRITABLE)

它可以返回指定窗口的布局状态,默认就是root 窗口,也就是当前frame 的主视图区域。

我们可以在关闭时通过 window-state-get 获取root 的布局信息,保存到一个文件中,再下一次启动Emacs 时通过window-state-put 进行恢复窗口。

如果我们只是临时性的保存和恢复当前窗口布局,可以使用 current-window-configurationset-window-configuration 。它们的作用与前面介绍的 save-excursionsave-restrictionwith-current-buffer 类似,如果包含临时性的窗口操作,为了让用户没有察觉或者不改变当前窗口布局,可以使用

练习

好了,我们来实现一个保存和恢复窗口布局的例子,因为我们还没有接触过Emacs文件的操作,所以这里简单的实现一个在Emacs启动期间执行保存和恢复的功能,后续学习了如何读取和保存文件之后我们可以继续完善

因为不涉及到持久化的保存,所以我们可以简单的使用 current-window-configurationset-window-configuration 函数。

emacs-lisp 复制代码
(defvar current-window-state nil)
(defun my/save-current-window-state()
  (interactive)
  (setq current-window-state (current-window-configuration)))

(defun my/restore-current-window-state()
  (interactive)
  (set-window-configuration current-window-state))

我们的练习比较简单,如果读者觉得难度不过瘾,可以考虑通过保存和遍历 window-tree 的方式实现这个功能

小结

本篇我们介绍的函数有:

  • window-tree
  • current-window-configurationset-window-configuration
  • get-window-stateput-window-state
相关推荐
典典分享指南2 天前
Windows 自动化踩坑实录
python·intellij-idea·pyqt·emacs·visual studio
辰辉创聚3 天前
重组细胞因子全解析:定义、分类与应用指南
人工智能·eclipse·emacs·白细胞介素·重组蛋白·重组细胞因子
Masimaro5 天前
《elisp 每日一练》—— 前缀按键
emacs
带娃的IT创业者8 天前
Emacs 31:一场静默而深刻的范式演进
编辑器·文本编辑器·开源软件·emacs·lisp·软件架构
Masimaro10 天前
《elisp 每日一练》——syntax-table
emacs
Masimaro16 天前
《elisp 每日一练》—— overlay 机制
emacs
Masimaro19 天前
《elisp 每日一练》——buffer属性
emacs
2601_958548484 个月前
高频脉冲电源厂家选择:放心供应商筛选策略解析
emacs
AC赳赳老秦4 个月前
投标合规提效:用 OpenClaw 实现标书 / 合同自动审核、关键词校验、格式优化,降低废标风险
开发语言·前端·python·eclipse·emacs·deepseek·openclaw