Emacs 折腾日记(十五)——窗口

news/2025/3/9 10:55:26/文章来源:https://www.cnblogs.com/lanuage/p/18757288

在上一节提到,当前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-windowwith-selected-window。它的作用是在执行语句之后,选择的窗口回到之前选择的窗口。with-selected-windowsave-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-heightwindow-body-height 两个函数,后者返回把 mode-line 和 header line 排除后 的高度

(window-body-height) ;; ==> 53
(window-height) ;; ==> 54

滚动条和 fringe 不包括在窗口的亮度里,window-width 返回窗口的宽度。所以 window-body-widthwindow-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-edgeswindow-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 窗口布局的树状结构。为了说明它的返回值,我们先来举一个例子。

  1. 首先打开emacs,此时看到只有一个窗口,暂时叫它窗口A
  2. 在窗口上垂直分割一个窗口,新生成的窗口叫做窗口B,此时左侧的窗口是A,右侧的是B
  3. 在B窗口上水平分割一个窗口,生成一个新的C窗口

此时应该有3个窗口,它们的布局如下:

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

如果用树来表示这个布局,可以组成这么一颗树

        frame/         \left       right(win A)      / \/   \top   bottomwin 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 等等。

这里的函数实在是太多了,我想暂时不用都记住,现在又有各种大模型,到时候有需求直接使用问就行。或者记住这一个函数,后面要扩展自己去查文档

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/894995.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

解决explorer文件丢失黑屏

下载火绒后会把explorer.exe判断为病毒并隔离; 第一种方法可以用任务管理器找到火绒安装路径的bin目录,运行HpsMain进入火绒的隔离区,找到explorer.exe,点击恢复并加入信任区 第二种方法适用于不小心卸载火绒的情况,有的兄弟可能黑屏后直接删除了火绒,但是这种情况火绒会…

烟火烟雾智能检测摄像机安消系统 守护电动车充电桩安全

电动车充电桩烟火检测系统首先通过高清摄像头对停车场或楼宇充电桩区域进行视频采集。这些摄像头通常覆盖广角区域,具备夜视能力,能够在全天候条件下提供高质量的视频输入。采集的视频数据通过图像去噪、亮度校正等预处理步骤,确保算法输入的稳定性和准确性。近年来,随着电…

AI泥石流智能算法摄像头

山体落石滑坡识别系统 落石泥石流监控摄像机基于YOLOX+RNN的深度学习算法,山体落石滑坡识别系统 落石泥石流监控摄像机通过安装在山区公路沿线的监控摄像机来实现对山体的实时监测。这些摄像机分布在关键位置,如山体易滑坡区域、桥梁附近等,能够24小时不间断地捕捉山体的动态…

ApoorvCTF Rust语言逆向实战

上周参加了国外的ApoorvCTF比赛,看一下老外的比赛跟我们有什么不同,然后我根据国内比赛对比发现,他们考点还是很有意思的,反正都是逆向。上周参加了国外的比赛,名称叫:ApoorvCTF 看一下老外的比赛跟我们有什么不同,然后我根据国内比赛对比发现,他们考点还是很有意思的,…

ACK 通过sls收集pod日志

常见的收集方式 filebeat->kafka->logstash->es阿里云ACK日志收集 ack->sls 这里要说的是我们传统意义上是通过收集pod或者node节点上的 .log日志文件进行收集日志,在ack里面可以不用通过收集.log的方式进行日志收集。具体的操作看下面的步骤1 开始操作吧 1.1 ACK…

GreatSQL5.7 与 8.0 对 DATE 非法值处理方式不同

GreatSQL5.7 与 8.0 对 DATE 非法值处理方式不同 一、问题描述 1. 问题现象 当分别通过LOAD DATA LOCAL INFILE和INSERT导入非法的 DATE 字段数据时,在5.7.21和 8.0.25使用LOAD DATA LOCAL会报一个Warning,数据异常但可以插入成功,而且实际插入的数据跟用户计划插入的不同,…

Hyper-V的安装过程自动化程度较高,但要了解每个安装步骤背后的原理

Hyper-V的安装过程确实具有较高的自动化程度,但了解每个安装步骤背后的原理对于确保安装的成功以及后续的维护和管理至关重要。以下是对Hyper-V安装过程中每个关键步骤及其原理的详细解析:一、准备工作 步骤:检查系统要求、备份数据、关闭不必要的程序。 原理: 系统要求:确…

牛客题解 | 拼凑三角形

牛客题库题解题目 题目链接 题解 题目难度:简单 知识点:数学逻辑 思路: 三角形成立的条件:1.为了更简便的判断三角形是否成立,将三个数由小到大进行排序放入a[3]中,只需要满足a[0]+a[1]&amp;gt;a[2]即可。(两个较小的数相加大于第三个数)。 2.若a[0]+a[1]&amp;…

内网环境部署Deepseek+Dify,构建企业私有化AI应用

0.简介 公司为生产安全和保密,内部的服务器不可连接外部网络,为了可以在内网环境下部署,采用的方案为ollama(Docker)+Dify(Docker Compose),方便内网环境下迁移和备份,下文将介绍部署的全部过程。 1.镜像拉取 镜像拉取为准备工作,因服务器在内网环境,需要先在可以连接外…

牛客题解 | 拍照队形

牛客题库题解题目 题目链接 题解 题目难度:简单难度 知识点:数学逻辑 分析:主要考虑输出格式,由于N=3k+1,那么前K行每行输出2个字符,后k+1行输出1个字符。对于前k行,第一行:第一列输出字符,空格数为m=2*k-1,在输出第二个字符;第二行:先空格1,输出字符,空格m-=2个(…

电动后尾门控制器PLGM

经纬恒润平台化的电动尾门控制器PLGM可为不同的后尾门应用场合提供解决方案,目前已为众多客户配套量产。 经纬恒润平台化的电动尾门控制器PLGM(Power Lift Gate Module,PLGM)可为不同的后尾门应用场合提供解决方案,目前已为众多客户配套量产。主要功能车门电动打开…

CentOS 磁盘扩容lvm(虚拟机环境)

fdisk -l 查看磁盘情况对新增加的硬盘进行分区,使用fdisk命令创建和维护分区表。 fdisk /dev/vda 输入p:查看已分区数量(有两个 /dev/vda1 /dev/vda2) 输入n(new partition):新增加一个分区 输入p(parimary partition):分区类型选择为主分区 输入分区号3(partition …