大家好,欢迎来到IT知识分享网。
在上一节提到,当前buffer不一定是当前显示在屏幕上的那个buffer,想要修改显示的buffer,可以使用窗口相关的api。这节来介绍一些窗口的操作。
窗口是屏幕上用于显示一个缓冲区 的部分。和它要区分开来的一个概念是 frame。frame 是 Emacs 能够使用屏幕的 部分。可以用窗口的观点来看 frame 和窗口,一个 frame 里可以容纳多个(至 少一个)窗口,而 Emacs 可以有多个 frame。不知道各位读者是否学习过MFC或者QT,这里的窗口就是MFC中的View,而frame则是整个界面框架,包括菜单栏工具栏、标题栏、状态栏等等部分。而窗口仅仅是最中间显示buffer的那一部分。
分割窗口
刚启动时,emacs 都是只有一个 frame 一个窗口。多个窗口都是用分割窗口的函 数生成的。分割窗口的内建函数是split-window。这个函数的参数如下:
(split-window &optional window size horizontal)
这个函数的功能是把当前或者指定窗口进行分割,默认分割方式是水平分割,可 以将参数中的 horizontal 设置为 non-nil 的值,变成垂直分割。如果不指定 大小,则分割后两个窗口的大小是一样的。分割后的两个窗口里的缓冲区是同 一个缓冲区。使用这个函数后,光标仍然在原窗口,而返回的新窗口对象:
(split-window) ;; ==> #
根据前面对于 optional 后参数的介绍,要填入 horizontal 的值实现竖直切分,需要填充前面的几个参数,如果不给则默认是nil。实际上上面的代码传入的可选参数都是nil,那么我们可以进行如下调用实现竖直分割窗口:
(split-window nil nil 1) ;; ==> #
我们也可以使用 selected-window 来获取当前选中的窗口,当前选中的窗口就是光标所在的窗口
(split-window (selected-window) nil 1) ;; ==> #
在进行实验的时候发现,分割的时候是在当前窗口的基础之上分割的,它是类似于这样的一个过程,它只在Win1所在的窗口区域进行划分,除非改变当前窗口。
+---------------+ +---------------+ | | | | | | win1 | | win1 | win2 | | | --> | | | | | | | | | | | | | +---------------+ +---------------+ | v +---------------+ +---------------+ | 4 | 5 | | | | | | | | win2 | | win1 | win2 | |--------| | <-- |-------| | | win3 | | | win3 | | | | | | | | +---------------+ +---------------+
可以看成是这样一种结构
(win1) -> (win1 win2) -> ((win1 win3) win2) -> (((win4 win5) win3) win2)
删除窗口
如果要让一个窗口不显示在屏幕上,要使用 delete-window 函数。如果没有指定 参数,删除的窗口是当前选中的窗口,如果指定了参数,删除的是这个参数对应 的窗口。删除的窗口多出来的空间会自动加到它的邻接的窗口中。如果要删除除 了当前窗口之外的窗口,可以用 delete-other-windows 函数。
当一个窗口不可见之后,这个窗口对象也就消失了
(setq foo (selected-window)) (delete-window foo) (windowp foo) ;; ==> t (window-live-p foo) ;; ==> nil (delete-other-windows foo) ;; ==> error, 因为先删除foo所对应的窗口,现在已经无法找到这个窗口了,所以这里删除它以外的会报错
窗口配置
窗口配置(window configuration) 包含了 frame 中所有窗口的位置信息:窗口 大小,显示的缓冲区,缓冲区中光标的位置和 mark,还有 fringe,滚动条等等。 用
current-window-configuration 得到当前窗口配置,用 set-window-configuration 来还原。
(setq foo (selected-window)) (split-window foo nil t) (split-window) (setq wc (current-window-configuration)) (delete-other-windows foo) (set-window-configuration wc)
我们一行一行的执行上述代码,会发现调用 delete-other-windows 删除之前的窗口之后再次调用 set-window-configuration 会恢复上次保存的结果。看到这里各位读者是否有这么一个想法:利用这两个函数实现一个自动保存和恢复窗口结构的功能呢?
但是经过测试,
current-window-configuration 得到的对象并不能持久化的保存的到文件中,即使写到文件中,读取的时候也会报错。下面是我的测试代码
(setq workspace-file-path "~/.session") ;; 保存窗口的配置 (defun my/save-current-workspace () (with-temp-file workspace-file-path (print (current-window-configuration) (current-buffer)))) ;; 加载窗口的配置 (defun my/load-current-workspace () (when (file-exists-p workspace-file-path) (with-temp-buffer (insert-file-contents workspace-file-path) (set-window-configuration (read (current-buffer))))))
在执行保存之后,我们查看文件得到的是一个类似于 #
选择窗口
前面提到过可以使用 selected-window 来获取当前光标所在的窗口。
我们可以使用 select-window 来选择某个窗口作为当前窗口。使用 other-window 来选择另外的窗口。该函数是一个在不同窗口之间快速跳转的一个函数,它按照窗口创建的时间的逆序进行排序,根据传入的整数参数来决定跳转到第几个窗口。
(progn (setq foo (selected-window)) (message "Original window: %S" foo) (other-window 1) (message "Current window: %S" (selected-window)) (select-window foo) (message "Back to original window: %S" foo))
这里有两个特殊的宏 save-selected-window 和 with-selected-window。它的作用是在执行语句之后,选择的窗口回到之前选择的窗口。with-selected-window 和 save-selected-window 几乎相同, 只不过 save-selected-window 选择了其它窗口。这两个宏不会保存窗口的位置 信息,如果执行语句结束后,保存的窗口已经消失,则会选择最后一个选择的窗口
(save-selected-window (select-window (next-window)) (goto-char (point-min)))
上述代码会选择另一个窗口并将光标移动到缓冲的开始位置。
当前 frame 里所有的窗口可以用 window-list 函数得到。可以用 next-window 来得到在 window-list 里排在某个 window 之后的窗口。对应的用 previous-window 得到排在某个 window 之前的窗口
walk-windows 可以遍历窗口,相当于 (mapc proc (window-list))。 get-window-with-predicate 用于查找符合某个条件的窗口
窗口大小信息
窗口是一个长方形区域,所以窗口的大小信息包括它的高度和宽度。用来度量窗 口大小的单位都是以字符数来表示,所以窗口高度为 45 指的是这个窗口可以容 纳 45 行字符,宽度为 140 是指窗口一行可以显示 140 个字符
mode line 和 header line 都包含在窗口的高度里,所以有 window-height 和 window-body-height 两个函数,后者返回把 mode-line 和 header line 排除后 的高度
(window-body-height) ;; ==> 53 (window-height) ;; ==> 54
滚动条和 fringe 不包括在窗口的亮度里,window-width 返回窗口的宽度。所以 window-body-width 和 window-width 返回的结果一样
(window-body-width) ;; ==> 234 (window-width) ;; ==> 234
也可以用 window-edges 返回各个顶点的坐标信息。window-edges 返回的区域包含了 滚动条、fringe、mode line、header line 在内,如果单纯的想要返回文本所在区域可以使用 window-inside-edges
(window-edges);; ==> (0 0 238 54) (window-inside-edges) ;; ==> (1 0 236 54)
如果需要的话也可以得到用像素表示的窗口位置信息,这里用到的函数是 window-pixel-edges 和 window-inside-pixel-edges
(window-pixel-edges) ;; ==> (0 0 1908 922) (window-inside-pixel-edges) ;; ==> (8 0 1884 905)
到目前为止,我们有了手段可以遍历窗口以及获取窗口的坐标,那么利用这些数据就可以做到记录和恢复之前的窗口布局了。
我最开始的思路是采用 walk-windows 来遍历窗口,并且使用 window-pixel-edges 来记录每个窗口的区域。但是这么做有一些问题无法解决:首先还原的时候创建窗口只能采用 split-window,而 split-window 是基于之前的窗口来创建的,walk-windows 无法反映出这种层级关系。另外就是emacs 中没有函数来设置窗口左上角的坐标,我们只能通过函数来改变窗口的宽和高,窗口的位置在使用 split-window 创建的时候已经决定了。所以我们需要一种能表示层级关系的结构来存储窗口的信息。
这个时候就要引入 window-tree 函数了。这个函数可以返回当前 frame 窗口布局的树状结构。为了说明它的返回值,我们先来举一个例子。
- 首先打开emacs,此时看到只有一个窗口,暂时叫它窗口A
- 在窗口上垂直分割一个窗口,新生成的窗口叫做窗口B,此时左侧的窗口是A,右侧的是B
- 在B窗口上水平分割一个窗口,生成一个新的C窗口
此时应该有3个窗口,它们的布局如下:
+---------------+ | | | | A | B | | |----| | | C | | | | +---------------+
如果用树来表示这个布局,可以组成这么一颗树
frame / \ left right (win A) / \ / \ top bottom win B win C
对于叶子节点来说,window-tree 返回的数据形式是 (DIR EDGES CHILD1 CHILD2 …) 各部分代表的含义如下:
- DIR,表示分割类型,t表示竖直分割,nil表示水平分割
- EDGES, 表示窗口区域的坐标,格式为 (LEFT TOP RIGHT BOTTOM),以字符为单位
- CHILDREN, 子节点列表,可以是分支节点或叶子节点
而叶子节点是一个窗口对象。
上面的窗口布局,使用 window-tree 得到的结果如下
( (nil (0 0 84 35) #
(t (42 0 84 35) #
#
)) #
)
去除掉minibuffer部分,着重分析一下文本区域的分割
(nil (0 0 84 35) #
(t (42 0 84 35) #
#
))
首先水平分割,占区域大小为 (0 0 84 35)。此时上面一个部分是 win3。下半部分右进行了分割。下半部分采用竖直方式进行分割,占区域为 (42 0 84 35)。这个部分有两个子窗口win7 和 win9。
感觉分割的顺序与我们的直觉相悖。但是仔细想想好像又能产生之前那种结果
(42 0) +---------------+ | | | | win3 | win7 | | |------- | | | win9 | | | | +---------------+ (84 35)
我们可以写下如下代码来进行这个结构的解析
(defun my-current-window-configuration () ;; pai chu minibuffer de shu ju (my-window-tree-to-list (car (window-tree)))) (defun my-window-tree-to-list (tree) (if (windowp tree) 'win (let ((dir (car tree)) (children (cddr tree))) (list (if dir 'vertical 'horizontal) (if dir (my-window-height (car children)) (my-window-width (car children))) (my-window-tree-to-list (car children)) (if (> (length children) 2) (my-window-tree-to-list (cons dir (cons nil (cdr children)))) (my-window-tree-to-list (cadr children))))))) (defun my-window-height (win) (if (windowp win) (window-height win) (let ((edge (cadr win))) (- (nth 3 edge) (nth 1 edge))))) (defun my-window-width (win) (if (windowp win) (window-width win) (let (edge (cadr win)) (- (nth 2 edge) (car edge)))))
根据这个结构编写一个还原的功能
(defun my-list-to-window-tree (conf) (when (listp conf) (let (newwin) (setq newwin (split-window nil (cadr conf) (eq (car conf) 'horizontal))) (my-list-to-window-tree (nth 2 conf)) (select-window newwin) (my-list-to-window-tree (nth 3 conf))))) (defun my-set-window-configuration (winconf) (delete-other-windows) (my-list-to-window-tree winconf))
可以使用如下代码进行调用
(setq foo (my-current-window-configuration)) ;; do something (my-set-window-configuration foo)
窗口对应的缓冲区
窗口对应的缓冲区可以用 window-buffer 函数得到:
(window-buffer) ;; ==> #
缓冲区对应的窗口也可以用 get-buffer-window 得到。如果有多个窗口显示同一 个缓冲区,那这个函数只能返回其中的一个,由window-list 决定。如果要得到 所有的窗口,可以用 get-buffer-window-list
(get-buffer-window (get-buffer "*scratch*")) (get-buffer-window-list (get-buffer "*scratch*"))
让某个窗口显示某个缓冲区可以用 set-window-buffer 函数。 让一个缓冲区可见可以用 display-buffer。默认的行为是当缓冲区已经显示在某个窗口中时,如果不是当前选中窗口,则返回那个窗口,如果是当前选中窗口, 且如果传递的 not-this-window 参数为 non-nil 时,会新建一个窗口,显示缓 冲区。如果没有任何窗口显示这个缓冲区,则新建一个窗口显示缓冲区,并返回 这个窗口。
display-buffer 是一个比较高级的命令,用户可以通过一些变量来改 变这个命令的行为。比如控制显示的 pop-up-windows,
display-buffer-reuse-frames,pop-up-frames,控制新建窗口高度的 split-height-threshold,even-window-heights,控制显示的 frame 的
special-display-buffer-names,special-display-regexps, special-display-function,控制是否应该显示在当前选中窗口 same-window-buffer-names,same-window-regexps 等等。
这里的函数实在是太多了,我想暂时不用都记住,现在又有各种大模型,到时候有需求直接使用问就行。或者记住这一个函数,后面要扩展自己去查文档
免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://haidsoft.com/176103.html