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

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

Masimaro
2026-08-30 / 0 评论 / 0 阅读 / 正在检测是否收录...

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

背景知识

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

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

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

+------------+-------------+
|            |    Window   |
|   Window   |     B       |
|      A     +-------------+
|            |    Window   |
|            |     C       |
+------------+-------------+

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

(
 (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 的窗口。

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

├── (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

(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 函数。

(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
0

评论 (0)

取消