《elisp 每日一练》—— overlay 机制

在之前的文章中,我们利用 walk-windowscompleting-read 实现了一个可以跳转到任意窗口的程序。但是我们通过 buffer + window 的方式进行显示,如果buffer不重名还好,用户可以根据buffer名称来选择,而当buffer有重名,用户没办法直观的看出哪个项对应哪个窗口。如果读者尝试过配置自己的Emacs,在管理窗口方面一定绕不开 ace-window这个插件,它提供了一个非常优雅高效的方式。今天将介绍overlay机制,并且利用该机制实现一个类似的跳转功能。

背景知识介绍

对于buffer的显示效果,Emacs提供了两种改变的机制,一种是通过设置 Text properties 来改变文本的字号、字体大小等属性,是 buffer 文本的一部分,会随文本一起复制、保存。另一个就是 Overlay 机制,它只影响显示,不进入 undo 列表,也不随文本复制。

对于 Overlay 官方给它的定位是:

txt 复制代码
You can use overlays to alter the appearance of a buffer's text on the screen, for the sake of presentation features. An overlay is an object that belongs to a particular buffer, and has a specified beginning and end.

你可以将它想象成一个贴在buffer上的便利贴。如果要使用的话,我们首先要创建一个便利贴、然后决定便利贴的位置,最后在不需要时销毁它。并且Emacs还提供了一些操作它的函数。下面我们来列举一些重要的函数。

创建可以使用函数 make-overlay,它的原形如下:

emacs-lisp 复制代码
(make-overlay BEG END &optional BUFFER FRONT-ADVANCE REAR-ADVANCE)

函数的相关参数如下:

  • BEGEND : 指定overlay起始位置和终点位置
  • BUFFER : 指定叠加层属于哪个缓冲区, 默认为当前缓冲区
  • FRONT-ADVANCEREAR-ADVANCE 表示当开头或者结尾被插入新字符时,overlay 该如何变化

我们来了解一下最后两个参数的含义。我们还是将它想象成一个便利贴。假设便利贴我们从头到尾写满了文字,这个时候我们想要在便利贴开始位置或者结束位置写一个新的内容,该怎么办? FRONT-ADVANCEREAR-ADVANCE 就是来处理这种情况的。一种办法是丢失这些内容,纸张的大小不变,这个时候对应着 FRONT-ADVANCE 为非 nilREAR-ADVANCEnil。还有一种办法就是纸张大小改变,我们在前面和后面插入内容,此时对应着 FRONT-ADVANCEnilREAR-ADVANCE 为非 nil

关于属性,我们有 overlay-putoverlay-get 来设置或者获取属性。

面对一个陌生的,不是我们创建的 overlay,我们可以通过 overlay-startoverlay-end 来获取它的起始和结束位置

另外我们可以通过 overlays-atoverlays-in 来获取区域内的所有 overlay

我们常用的属性有:

  • face 设置样式,包括背景色、前景色、字体等
  • display : 替换overlay中的的显示内容
  • priority : 重叠时的 z序
  • window : 限定只在指定窗口生效

overlay 实际是对于 buffer 的修改,所以默认情况下,如果多个窗口都显示同一个buffer,那么每个窗口都会显示这样的内容,我们可以通过 window 属性,限定只在指定的窗口中显示

例如我们可以尝试写下如下代码,来创造一个显示内容为 "标记" 的黄低红色的标记,并且通过 window 属性指定只在当前窗口显示

emacs-lisp 复制代码
(let ((overlay (make-overlay (point-min) (+ 10 (point-min)))))
  (overlay-put overlay 'window (selected-window))
  (overlay-put overlay 'face '(:background "red" :foreground "yellow"))
  (overlay-put overlay 'display "A"))

练习

到此我们差不多已经掌握了 overlay 的绝大部分用法了。可以开始我们的改造行动了。

我们的思路仍然是先遍历,然后给每个窗口打上标签,并且记录到一个列表中,列表中每项的内容为 (LABLE . window) 后续根据用户输入的字母或者数字来找到窗口并跳转。

emacs-lisp 复制代码
(defvar my/all-window-list '()
  "保存窗口字典的列表")

(defun my/show-overlay(label win)
  "在左上角创建显示字母的 overlay"
  (with-selected-window win
    (let* ((pos (window-start))
	   (ol (make-overlay pos (+ 10 pos) (window-buffer win))))
      (overlay-put ol 'display label)
      (overlay-put ol 'face '(:foreground "red" :weight bold :background "yellow"))
      (overlay-put ol 'window win)
      ol))) ;; 返回 ol 用于清理

(defun my/jump-to-selected-window()
  "根据用户按下的字母标签进行跳转"
  (interactive)
  (let*((chars (mapcar #'identity "abcdefghijklmnopqrstuvwxyz")) ;; 构建一个字母的字典
	(overlays '()) ;; 保存所有的overlay 方便后面删除
	(index 0)) ;; 存储当前使用的字母的索引
    (walk-windows (lambda(win)
		    (when (< index (length chars))
		      (let* ((ch (nth index chars))
			    (ol (my/show-overlay (string ch) win)))
			(when ol
			  (push ol overlays)
			  (push (cons ch win) my/all-window-list)))
		      (setq index (1+ index)))))
    ;; 读取用户按键并跳转
    (unwind-protect
	(let* ((char (read-char "选择窗口: "))
	       (win (cdr (assoc char my/all-window-list))))
	  (if win
	      (select-window win)
	    (message "没有选择对应的窗口")))
      ;;清理资源
      (mapc #'delete-overlay overlays)
      (setq overlays nil))))

上述代码中使用了一个新的宏 unwind-protect。它的作用与try...catch 中的 finally 类似,它的定义如下:

emacs-lisp 复制代码
(unwind-protect body-form cleanup-forms...)

它主要执行body-form 中的内容,并且保证会执行 cleanup-forms 中的清理代码。

总结

本节我们学习了如何给buffer 加上 overlay ,用来展示一些信息。本节介绍的函数有

  • make-overlay
  • overlay-putoverlay-get
  • overlay-startoverlay-end
  • overlays-atoverlays-in
相关推荐
Masimaro2 天前
《elisp 每日一练》——minibuffer输入读取和set-transient-map 临时热键状态机
emacs
Masimaro5 天前
《elisp 每日一练》——Window 树与布局状态恢复
emacs
典典分享指南7 天前
Windows 自动化踩坑实录
python·intellij-idea·pyqt·emacs·visual studio
辰辉创聚8 天前
重组细胞因子全解析:定义、分类与应用指南
人工智能·eclipse·emacs·白细胞介素·重组蛋白·重组细胞因子
Masimaro10 天前
《elisp 每日一练》—— 前缀按键
emacs
带娃的IT创业者13 天前
Emacs 31:一场静默而深刻的范式演进
编辑器·文本编辑器·开源软件·emacs·lisp·软件架构
Masimaro15 天前
《elisp 每日一练》——syntax-table
emacs
Masimaro24 天前
《elisp 每日一练》——buffer属性
emacs
2601_958548484 个月前
高频脉冲电源厂家选择:放心供应商筛选策略解析
emacs