网站首页 > 开源技术 正文
在上一节提到,当前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) ;; ==> #<window 7 on *scratch*>
根据前面对于 optional 后参数的介绍,要填入 horizontal 的值实现竖直切分,需要填充前面的几个参数,如果不给则默认是nil。实际上上面的代码传入的可选参数都是nil,那么我们可以进行如下调用实现竖直分割窗口:
(split-window nil nil 1) ;; ==> #<window 10 on *scratch*>
我们也可以使用 selected-window 来获取当前选中的窗口,当前选中的窗口就是光标所在的窗口
(split-window (selected-window) nil 1) ;; ==> #<window 11 on *scratch*>
在进行实验的时候发现,分割的时候是在当前窗口的基础之上分割的,它是类似于这样的一个过程,它只在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))))))
在执行保存之后,我们查看文件得到的是一个类似于 #<window-configuration> 的字符串,并没有别的内容,在调用 set-window-configuration的时候会报错。
选择窗口
前面提到过可以使用 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)
#<window 3 on *scratch*>
(t
(42 0 84 35)
#<window 7 on *scratch*>
#<window 9 on *scratch*>))
#<window 4 on *Minibuf-0*>)
去除掉minibuffer部分,着重分析一下文本区域的分割
(nil
(0 0 84 35)
#<window 3 on *scratch*>
(t
(42 0 84 35)
#<window 7 on *scratch*>
#<window 9 on *scratch*>))
首先水平分割,占区域大小为 (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) ;; ==> #<buffer *scratch*>
缓冲区对应的窗口也可以用 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 等等。
这里的函数实在是太多了,我想暂时不用都记住,现在又有各种大模型,到时候有需求直接使用问就行。或者记住这一个函数,后面要扩展自己去查文档
- 上一篇: 空间电推进技术概览及评述(连载之四)
- 下一篇: Linux 源代码makefile文件功能解析
猜你喜欢
- 2025-05-02 Linux基础运维篇:Linux系统监控工具(第015课)
- 2025-05-02 CentOS 7安装TCP BBR拥塞算法(centos7开启bbr加速)
- 2025-05-02 Emacs 折腾日记(十八)——改变Emacs的样貌
- 2025-05-02 Linux man 命令使用教程(linux man -k)
- 2025-05-02 qemu linux内核(5.10.209)开发环境搭建
- 2025-05-02 Linux 源代码makefile文件功能解析
- 2025-05-02 空间电推进技术概览及评述(连载之四)
- 2025-05-02 每天LINUX学习:Linux开启VLAN的支持及配置方法
- 2025-05-02 用云存储30分钟快速搭建APP,你信吗?
- 2025-05-02 图文详解SAMA5D2处理器在加载Linux内核前到底做了什么?
你 发表评论:
欢迎- 最近发表
-
- 10款鲜为人知的PHP框架(10款鲜为人知的php框架代码)
- 3分钟搞懂反弹shell(反弹shell的常用命令)
- 计算机专业必须掌握的脚本开发语言—shell
- shell 基本语法(shell基本语法set)
- 学习Shell 教程(shell编程学习)
- 一个有意思的PHP Webshell,利用伪协议执行代码
- Linux入门-shell编程-适合小白(linux shell编程是什么)
- GrayLog开源日志管理平台技术文章合集【共58篇】
- AI大模型 MiniMax 基于 Apache Doris 的日志系统,PB 级秒级查询响应
- 互联网大厂后端必看!手把手教你替换 Spring Boot 中的日志框架
- 标签列表
-
- jdk (81)
- putty (66)
- rufus (78)
- 内网穿透 (89)
- okhttp (70)
- powertoys (74)
- windowsterminal (81)
- netcat (65)
- ghostscript (65)
- veracrypt (65)
- asp.netcore (70)
- wrk (67)
- aspose.words (80)
- itk (80)
- ajaxfileupload.js (66)
- sqlhelper (67)
- express.js (67)
- phpmailer (67)
- xjar (70)
- redisclient (78)
- wakeonlan (66)
- tinygo (85)
- startbbs (72)
- webftp (82)
- vsvim (79)
本文暂时没有评论,来添加一个吧(●'◡'●)