Jump to Table of Contents Pop Out Sidebar

buffer 管理和 ibuffer 的介绍与使用

More details about this document
Create Date:
Publish Date:
Update Date:
2024-04-21 23:14
Creator:
Emacs 29.2 (Org mode 9.6.15)
License:
This work is licensed under CC BY-SA 4.0

本文并不是用来介绍 buffer 相关的 elisp 接口函数的,而是介绍如何使用 emacs 提供的功能来使用和管理 buffer。首先我会介绍 buffer-menu 的使用,随后我会介绍更强大的 ibuffer,它已经内置到 emacs 里面了。

1. buffer 概念及其基本操作

在这一节中,我首先根据 emacs manual[1]来介绍一下 buffer 是什么,随后介绍一些常见的 buffer 操作。

我们所有的编辑操作都是在 buffer 中完成的,当我们使用 emacs 打开一个文件时,文件中的内容就会放入 buffer 中,当我们对 buffer 执行保存操作时,buffer 中的内容就会写入文件中。buffer 也不一定要与文件绑定,我们可以创建空的 buffer,在必要的时候可以使用命令来将其保存在文件中。

在 emacs 中,同一时间只能选中一个 buffer,它被叫做当前 buffer(current buffer),它就像是键盘焦点一样,键盘只能在获得焦点的窗口中进行输入。

下面我会根据 emacs manual 的内容来介绍一些简单的 buffer 操作,我们了解一下就行了,其中一些命令我也用的不多。

1.1. 选择 buffer

在没有改键的 emacs 中,下面的按键分别对应的命令是:

  • C-x b buffer RET ,选择或创建一个 buffer, switch-to-buffer
  • C-x 4 b buffer RET , 在另一 window 中选择或创建 buffer, switch-to-buffer-other-window
  • C-x 5 b buffer RET , 在另一 frame 中选择或创建 buffer, switch-to-buffer-other-frame
  • C-x LEFT C-x RIGHT ,选择 buffer list 中的上一或下一 buffer, previous-buffernext-buffer

上面这几个命令我用的最多的还是 switch-to-buffer ,更准确地说是 ido-switch-buffer (它提供了显示 buffer 的 minibuffer ,比 emacs 原本的功能更直观一点)。buffer 多起来之后 previous-buffernext-buffer 就不太实用了。至于在其他 window 或 frame 中切换 buffer,我感觉还是直接移到 window 内再选择 buffer 更方便。

1.2. 列出 buffer

通过调用 C-x C-blist-buffer ),emacs 会为我们弹出一个叫做 *Buffer List* 的 buffer 菜单,菜单中的每一行会显示各 buffer 的名字,大小和 major mode。下面是效果图:

1.PNG

在这个 buffer 内,我们可以执行一些 buffer 的管理操作,我会在下一节进行简单的介绍。

1.3. 关闭 buffer

emacs 中对这类操作的用词是 kill buffers ,但直译为杀死 buffer 感觉不太好。

C-x k buffer RETkill-buffer 可以关闭一个指定的 buffer,在关闭该 buffer 后,另一 buffer 就会成为当前 buffer。如果被关闭的 buffer 未保存的话,emacs 会询问你是否在关闭前将 buffer 内容保存到文件中。

kill-some-buffers 会拿着 buffer-list 中的 buffer 一个个问你要不要删除,如果确定删除那就对 buffer 执行 kill-buffer 操作。

kill-matching-buffers 会要求你输入一个正则表达式,并对名字匹配正则表达式的 buffer 执行关闭操作。它会像 kill-some-buffers 一样拿匹配的 buffer 一个个问你是否关闭。

clean-buffer-list 用于关闭长时间未使用的 buffer,要控制它的行为的话,可以通过阅读该函数的文档来了解。

除了 kill-buffer (实际上我用的是 ido-kill-buffer )外,其他的命令我基本上没有用过。

1.4. 创建间接 buffer

所谓间接 buffer(indirect buffer)指的是和原 buffer 共享文本的 buffer,但除了文本一致外,它们的其他属性是分开的,也就是说间接 buffer 可以使用不同的 major mode,不同的 narrow,等等。当我们关闭原 buffer 时,它的间接 buffer 也会被关闭,但是关闭间接 buffer 不会影响到原 buffer。

make-indirect-buffer 可用来创建间接 buffer。 clone-indirect-buffer 使用当前 buffer 来创建间接 buffer。 C-x 4 c 对应的 clone-indirect-buffer-other-window 在另一 windows 打开根据当前 buffer 创建的间接 buffer。

出于懒惰的考虑,上面这些命令的效果我就不演示了,自己试一试也不麻烦。在浏览许多 emacs 中的 elisp 源代码文件时,在比较大的文件中,我们时不时可以看到使用 ^L 字符进行分页的操作。分页也许是出于功能分模块放置的考虑,不过代码的作者可能是对不同分页使用了间接 buffer,然后在不同间接 buffer 中使用 narrow-to-page 命令,这样一来就实现了“逻辑分页”的功能,将一个 elisp 文件看作多个 elisp 文件。

我之前也设想过将一个文件根据函数分为多页,每页一个函数,然后根据函数名而不是文件内容查找来进行页面跳转。但是当时没有了解到 emacs 提供的 narrow 功能,以及提供的间接 buffer 功能。有时间了我去简单实践一下,或者去找找已有的最佳实践。

1.5. 其他杂项

  • C-x C-q 可以控制当前 buffer 是否只读(read-only)
  • rename-buffer 可以更改当前 buffer 的名字
  • rename-uniquely 类似于 rename-buffer ,但是会在必要的时候在名字后面添加 <num> 来避免冲突
  • view-buffer RET buffer RET 在 buffer 中开启 view-mode

这些命令都没什么好说的,其中 C-x C-q 我用的很频繁,这是为了避免在阅读 elisp 源代码时不小心修改。也许我应该考虑使用代码实现源代码自动只读,而不是每次都按一次 C-x C-q

还有一件事,如果不同目录下的文件有相同的名字,emacs 会采取一些办法来区分这两个 buffer。默认的办法是在 buffer 名字前面加上后缀,比如我在 foo 和 bar 目录下都有 index.txt 文件,那 emacs 会将它们显示为 index.txt<foo>index.txt<bar> 。如果目录更加复杂,那么 emacs 也会使用更长的后缀以达到区分的效果。

2.PNG

我们可以通过一些变量来控制 emacs 的同名 buffer 修饰规则。 uniquify-buffer-name-style 共有 5 个选项,它们对应的显示如下:

  • forward: bar/mumble/name and quux/mumble/name
  • reverse: name\mumble\bar and name\mumble\quux
  • post-forward: name|bar/mumble and name|quux/mumble
  • post-forward-angle-brackets: name<bar/mumble> and name<quux/mumble>
  • nil: name and name<2>

我比较喜欢用 reverse 风格,它将多出的信息添加在 buffer 名字后面。

除了 name-style 外,还有其他的选项可用:

  • uniquify-after-kill-buffer-p ,当关闭 buffer 时是否刷新名字,它默认为 t
  • uniquify-ignore-buffers-re ,一个正则表达式,满足该表达式的 buffer 不会被修饰
  • uniquify-separator ,修饰内容与 buffer 名字之间的间隔符,如果不指定则使用默认分隔

我使用的配置代码如下,这是从 redguardtoo 的 emacs 配置中抄过来的,他的配置可以在 github 上找到

;; Nicer naming of buffers for files with identical names
(setq uniquify-buffer-name-style 'reverse)
(setq uniquify-separator " • ")
(setq uniquify-after-kill-buffer-p t)
(setq uniquify-ignore-buffers-re "^\\*")

想要进一步了解 buffer 修饰相关内容,可以查看 uniquify.el 源代码。

2. 使用 buffer-menu 来管理 buffer

所谓的 buffer-menu 就是我们上面提到的 C-x C-b 呼出的 *Buffer List* ,该 buffer 提供了一些快捷键来供我们方便地管理多个 buffer。除了说 buffer-menu 外,我们还可以使用 bs-show 来管理 buffer,相比于 buffer-menu,它提供了更多可扩展的功能。

在按下 C-x C-b 或调用 buffer-menu (或 list-buffers )后,我们就进入了 *Buffer List* 中,按下 ? 可以查看使用帮助。下面我就帮助中的内容来介绍一下 buffer-menu 的功能。

2.1. 打开光标所在 buffer

将光标移到某一行再按下 RET 就可以在 *Buffer List* 所在 windows 打开该 buffer 了,除了回车键外,我们也可以使用 oC-ov12 ,它们的功能如下:

  • RET ,在当前 window 打开 buffer
  • o ,在另一 window 打开 buffer
  • C-o ,和 o 类似,但是光标不移动到打开的 buffer 中
  • V ,使用 view-mode 打开 buffer
  • v ,打开所有有标记的 buffer,使用多个 window 垂直分屏显示,标记需要使用 m
  • 1 ,在当前 frame 中打开 buffer,使用整个 frame 的空间
  • 2 ,在一个 window 中打开 buffer,在另一 window 中显示先前的 current buffer
  • t ,以 tags-mode 打开 buffer,它应该是用来阅读 tags 文件的

老实说,我只用过 RET 切换 buffer,其他的爱用不用吧。 buffer-menu 提供了在其他 window 中打开 buffer 的功能,但是我没感觉平时用过多少。

2.2. 对 buffer 中内容的搜索

  • M-s a C-s ,在标记的 buffer 中使用增量式文本搜索
  • M-s a C-M-s ,在标记的 buffer 中使用增量式正则搜索
  • M-s a C-o ,在 buffer 中显示匹配的行,也就是多文件版的 occur

很不幸,在阅读帮助文档前我还不知道有多 buffer 搜索这回事,我一般都直接使用 git grep 进行项目内文件搜索。感兴趣的同学可以试试。使用 M-s a C-o 可以实现多文件快速修改,也许可以拿来重构用。

总之,它和一般的搜索没什么区别,除了多了 M-s a 这个前缀外。标记 buffer 同样是使用 m

2.3. 对 buffer 的标记

  • m ,仅标记 buffer
  • s ,标记 buffer 将执行保存操作
  • C-k ,标记 buffer 将被删除,然后将光标下移
  • kd ,和 C-k 一样
  • C-d ,标记 buffer 将被删除,然后将光标上移
  • u ,取消光标所在 buffer 的标记,光标下移
  • U ,取消所有的标记
  • DEL ,取消标记,光标上移
  • M-DEL ,取消所有的某一类标记
  • x ,对标记为 save 的 buffer 执行保存操作,对标记为 delete 的 buffer 执行关闭操作

这一部分就是对多个 buffer 的管理,可以批量保存,批量关闭。但是不能批量标记,这个我们会在 ibuffer 中讲到。

这些选项同样我也没用过,我只是一个一个关 buffer 罢了,save 操作直接交给 ⛤lazy-cat⛤ 的 auto-save 了。

2.4. 其他杂项

  • ~ ,取消 buffer 的 modify 标记,就当 buffer 没有被修改过
  • % ,切换 buffer 的 read-only 状态
  • g ,更新 buffer list
  • T ,切换是否只显示文件 buffer
  • b ,将光标所在 buffer 放到 buffer list 的最后位置
  • q ,回到 current buffer

同样,我也没用过这些,更新通过多次调用 list-buffers ,只读切换通过 C-x C-q ,其他的就没用过了。

综上,其实这 emacs 自带的 buffer 管理平时我也没用多少,如果不读文档的话我还真不知道有这么多功能,借此机会整理一下也是个不错的休闲时光。那个多 buffer 搜索是个挺厉害的功能,也许我会在本文的最后稍微演示一下。

buffer-menu 也支持一定的自定义,可以通过浏览 buffer-menu.el 来了解。由于本文的重点不在配置 buffer-menu,这一部分我就略过了,有兴趣的读者可以去试试。

3. 使用 bs-show 来管理 buffer

正如上面所说,相比于 list-buffersbs-show 提供了可自定义的一些选项,快捷键和 buffer-menu 有些区别(下面会提到)。它的配置主要通过 bs-customize 来进行。界面如下:(考虑到我也用不上这东西,这里我就简单讲讲了)

3.PNG

它有一个控制外观的 subgroup Bs Appearance

4.PNG

3.1. 打开 buffer

  • RET ,和 buffer-menu 一致
  • oC-obuffer-menu 一致
  • ! ,和 buffer-menu1 一致
  • F ,在新 frame 中打开 buffer
  • v ,对光标所在 buffer 打开并开启 view-mode
  • t ,和 buffer-menu 一致

3.2. 标记 buffer

  • m ,一致
  • u ,一致
  • DEL ,一致
  • U ,一致
  • s ,直接保存当前位置 buffer
  • k ,直接关闭当前位置 buffer
  • C-d ,同 k ,但随后光标上移

3.3. 杂项

  • b ,将当前位置 buffer 放到末尾
  • ~ ,移除 modify 标志
  • % ,切换当前位置 buffer 的 read-only 属性
  • + ,总是在 bs-show 中显示该 buffer
  • M ,切换 buffer 在 bs-show 中的显示属性,分 -+nothing 三档
  • g ,更新 buffer list
  • q ,退出 bs-show

3.4. 显示设置

  • a ,切换是否显示特殊 buffer (比如带 * 号的)
  • c ,切换可用的显示配置
  • C ,让用户选择配置并应用
  • S ,切换不同规则来对 buffer 进行排序

相比于 buffer-menubs-show 提供了多种排列 buffer 的选择,以及自定义排序的能力,但是它没有提供多 buffer 搜索的能力。我基本上用不到它,这里我也说不出什么东西来。

配置上感觉没啥好说的。。。。。。跟着 customize 界面的注释就大概知道啥意思了。接下来我们开始介绍 ibuffer,这也是本文的重头戏。

4. 使用 ibuffer 来管理 buffer

ibuffer 相当于 buffer-menu 的超级强化版,除了 buffer-menu 的一些基本功能外,它还提供了给 buffer 分组的功能,等等。这一节中我会介绍 ibuffer 的使用,在下一节中我会介绍 ibuffer 的简单配置方法。同样,我们还是先从它提供的快捷键开始说起,它所提供的快捷键集合远大于 buffer-menubs-show 。讲完它所有的功能的意义可能不是很大,毕竟日常用不了这么多,但是提供一个速查中文文本对我而言还是有必要的。

在开始之前,请将 C-x C-b 使用以下代码设置为 ibuffer

(global-unset-key (kbd "C-x C-b"))
(global-set-key (kbd "C-x C-b") 'ibuffer)

需要说明的是, *Help* buffer 中列出的快捷键并没有包括 ibuffer 的全部快捷键,要了解 ibuffer 的全部功能请阅读源代码。

4.1. 标记 buffer

  • m ,标记光标所在 buffer
  • d ,标记光标所在 buffer 为将删除
  • t ,取消所有标记,并标记所有未标记的 buffer
  • u ,取消光标所在 buffer 标记
  • U ,取消所有标记
  • DEL ,取消上面一个 buffer 的标记
  • M-DEL ,取消所有特定标记(需要输入标记字符,不输入则去掉所有标记)

上面的标记方式和 buffer-menu 中的没有太大区别,下面的就是 ibuffer 中特有的批量标记功能了:

  • * c ,将某种标记全部替换为另一种标记(需要输入待替换标记字符和替换标记字符,普通标记是 >
  • * M ,根据 major mode 对 buffer 进行标记(需要选择一个 major mode)
  • * u ,标记所有未保存的 buffer(用了 auto-save 估计用不上这种方法了)
  • * m ,标记所谓修改过的 buffer (同上)
  • * s ,标记所有以 * 开头的 buffer (也就是一些临时 buffer ,比如 *message*
  • * e ,标记所有拥有关联文件但暂时不存在的 buffer
  • * r ,标记所有 read-only 的 buffer
  • * / ,标记是 dired-mode 的 buffer
  • * h ,标记所有 help-modeapropos-mode 的 buffer
  • . ,标记比 ibuffer-old-time 更老的 buffer,它的值是 72 小时

第三种标记方式是根据正则匹配,它们包括:

  • % n ,根据 buffer name 进行标记,名字要匹配正则
  • % m ,根据 major mode 进行标记,major mode 名字要匹配正则
  • % f ,根据文件名进行标记,文件名匹配正则,对绝对路径
  • % g ,根据文件内容进行标记,内容匹配正则
  • % L ,标记所有被锁定的 buffer

老实说,上面的大部分我都用不上,记住最基本的,以及根据名字匹配的差不多就够了,我觉得大概只需要下面这些:

  • m d t u U
  • * c * M * r * s
  • % n % m % f % g

4.2. 对所标记 buffer 的操作

  • S ,保存标记的 buffer
  • A ,在 frame 中观察标记的 buffer,以垂直方式分屏
  • H ,在另一 frame 中打开该 buffer
  • V ,对标记 buffer 进行 revert 操作(将文件内容重新写入 buffer,相当于取消保存前的所有编辑,用了 auto-save 就基本上不用管了)
  • T ,切换标记 buffer 的 read-only 状态
  • L ,切换标记 buffer 的锁定状态
  • D ,关闭标记 buffer
  • x ,关闭所有标记为删除的 buffer
  • k ,将当前位置的 buffer 从 Ibuffer 中移除,但是不关闭该 buffer

上面的一些操作可在 buffer-menu 找到对应的,下面是一些查找替换操作:

  • M-s a C-s ,在标记 buffer 中进行增量搜索
  • M-s a C-M-s ,在标记 buffer 中进行增量正则搜索
  • O ,在标记 buffer 中进行 Occur 操作
  • r ,在标记 buffer 中进行正则替换
  • Q ,在标记 buffer 中进行字符串逐个替换
  • I ,在标记 buffer 中进行正则逐个替换

下面的这些就是 ibuffer 中独有的了,和命令的执行有关:

  • X ,将标记 buffer 的内容通过管道传给命令行
  • N ,使用命令行输出内容替换标记 buffer 的内容
  • ! ,使用标记 buffer 的内容作为命令行参数调用命令
  • E ,在标记的 buffer 中执行一条 elisp 表达式
  • W ,和 E 类似,但是在执行时会对被执行 buffer 进行观察

对于 buffer 的打开操作也没什么好记的, A 对应于 buffer-menu 中的 v 。对于查找与替换,ibuffer 中多出了替换的功能。对于执行操作,我感觉在 windows 上可能只用得上 EW 。你可以试试在 *Ibuffer* 中标记所有 buffer 后执行 (sit-for 1.0) ,emacs 会以每秒一个 buffer 的速度为你显示所有的 buffer ,并在结束后显示 buffer 总数。 EW 相当于使用某个表达式对 buffer 进行了选择性的遍历。

4.3. 对 buffer 的筛选

所谓筛选就是按照选出某些满足条件的 buffer,ibuffer 提供了多种多样的筛选方式,各筛选器还可以进行逻辑组合来得到组合筛选器。

  • / RET ,根据 major mode 进行筛选,它包括所有的 major mode
  • / m ,根据当前所有 buffer 中使用的 major mode 进行筛选,也就是 major mode in use
  • / M ,根据派生 mode 进行筛选
  • / n ,根据 buffer name 进行筛选
  • / c ,根据 buffer 内容进行筛选(c for contents)
  • / b ,根据文件基础名字(不带扩展名)进行筛选
  • / F ,根据目录名进行筛选(不含文件名)
  • / f ,根据文件名进行筛选(绝对路径名)
  • / . ,根据扩展名进行筛选
  • / i ,筛选处于 modified 的 buffer
  • / e ,根据 elisp 表达式进行筛选,该表达式的求值环境为对应 buffer
  • / > ,根据 buffer 大小筛选,要大于某一大小
  • / < ,根据 buffer 大小筛选,要小于某一大小
  • / * ,筛选特殊的 buffer(一般以 * 开头)
  • / v ,根据 buffer 是否有关联文件进行筛选,要有对应文件

上面的选项是 ibuffer 提供的不同类型的筛选器,记不住也没关系,我们可以使用 / SPC 来从中选择一种,它会提示你各筛选器的具体作用。下面的选项是对筛选器的操作:

  • / s ,保存当前筛选器,并给它一个名字
  • / r ,选择一个筛选器,将它作为当前筛选器
  • / a ,选择一个筛选器,并添加到当前筛选中
  • / & ,使用 and 连接当前的两个筛选器
  • / | ,使用 or 连接当前的两个筛选器
  • / p ,移除最近添加的筛选器,比如 a b 得到 b
  • / ! ,对当前筛选器取反,由 a 得到 [not a]
  • / d ,拆开组合筛选器,比如 [and a b c] 得到 a b c
  • / / ,移除当前所有筛选器

通过筛选器,我们可以对 buffer 进行分类了。具体的分组需要通过下面的分组命令。

4.4. 对 buffer 进行分组

  • / g ,根据当前筛选器创建一个组,创建后 ibuffer 中会将它归为一组中
  • / P ,移除最近添加的组,类似于弹栈
  • TAB ,移动到下一个筛选组,组间光标移动
  • M-p ,移动到上一个筛选组,组间光标移动
  • / \ ,移除所有的筛选组
  • / S ,保存当前的筛选组到文件中,这样下次就可以复用当前的分组方案了
  • / R ,使用之前保存的某个筛选组,从文件中获取
  • / X ,删除之前保存的筛选组

进行分组后,不同种类的 buffer 就在不同的组中了,具体效果差不多是这样。没有分组的 buffer 就位于 [default] 分组中:

5.PNG

4.5. 对 buffer 进行排序

  • , ,切换排序方式,具体多少种可以自己去试试
  • s i ,对当前的排序反序
  • s a ,按字典序排序,就是字符码排序,比如 A < B < a < b 等等
  • s f ,根据文件名排序
  • s v ,根据 buffer 的最近访问时间排序
  • s s ,根据 buffer 大小排序
  • s m ,根据 major mode 排序

排序基本上通过 , 切换就差不多了,感觉没太大用。。。

4.6. 杂项

  • g ,更新 *Ibuffer 内容
  • ` ,改变当前显示格式,用了就知道咋回事了
  • SPC ,向下移动一行
  • C-p ,向上移动一行
  • b ,将光标所在 buffer 放到最后
  • RET 和 buffer-menu 一致
  • oC-o 和 buffer-menu 一致
  • = ,比较 buffer 内容与对应文件内容,需要 diff 命令,windows 不弄个 diff 用不了

好了,到了这里关于 ibuffer 的操作就介绍的差不多了,下面我们介绍一下配置方法。ibuffer 应该可以当作一个简单的项目资源浏览器来用。

5. 配置 ibuffer

在上一节中,通过一些实践你应该基本了解了 ibuffer 的使用,这一节我们来介绍一下 ibuffer 的配置,在下一节我们介绍一下 ibuffer-vc 。说是配置方法介绍,其实也就是读了下 ibuffer.el 以及 customize 界面中的 option。

想要打开 ibuffer 对应的 customize 界面,可以使用 ibuffer-customize 命令。其中有非常多的选项,下面我们一个一个的来介绍。我尽量保证覆盖所有的选项。(这一节可能会很长)

我们开始吧。下面涉及到的文件有 ibuf-ext.el,ibuffer.el。

综上,我们就介绍完了 ibuffer 提供的选项,我可能漏掉了一些,不过这些大概够用了。

下面是我使用的 ibuffer 配置代码,其中的某些选项完全没必要设置。希望对你有所帮助:

;;; init-ibuffer.el config ibuffer -*- lexical-binding:t; -*-
(require 'ibuffer)

;; use C-x C-b call ibuffer command
(global-unset-key (kbd "C-x C-b"))
;; don't open other window when in a Ibuffer
(defun init-ibuffer-ibuffer ()
  (interactive)
  (if (string= (buffer-name) "*Ibuffer*")
      (ibuffer-update nil t)
    (ibuffer)))
(global-set-key (kbd "C-x C-b") 'init-ibuffer-ibuffer)

;; ibuffer formats
;; see ibuffer.el
(setq ibuffer-formats '((mark modified read-only locked
                              " " (name 18 18 :left :elide)
			      " " (size 9 -1 :right)
			      " " (mode 16 16 :left :elide) " " filename-and-process)
			(mark " " (name 16 -1) " " filename)))

;; ibuffer fontification-alist
;; just copy from ibuffer.el
(setq ibuffer-fontification-alist
      '((10 buffer-read-only font-lock-constant-face)
	(15 (and buffer-file-name
		 (string-match ibuffer-compressed-file-name-regexp
			       buffer-file-name))
	    font-lock-doc-face)
	(20 (string-match "^\\*" (buffer-name)) font-lock-keyword-face)
	(25 (and (string-match "^ " (buffer-name))
		 (null buffer-file-name))
	    italic)
	(30 (memq major-mode ibuffer-help-buffer-modes) font-lock-comment-face)
	(35 (derived-mode-p 'dired-mode) font-lock-function-name-face)
	(40 (and (boundp 'emacs-lock-mode) emacs-lock-mode) ibuffer-locked-buffer)))

;; don't use custom save
(setq ibuffer-save-with-custom nil)

;; saved filters
(let (it)
  ;; add push here
  (push '("el"
	 (or
	  (file-extension . "el")
	  (used-mode . emacs-lisp-mode)))
	it)
  (push '("cl"
	 (or
	  (used-mode . lisp-mode)
	  (file-extension . "lisp")))
	it)
  (push '("esrc"
	 (or
	  (file-extension . "el")
	  (directory . "emacs/.*/lisp")))
	it)
  (push '("org"
	 (or
	  (file-extension . "org")
	  (used-mode . org-mode)))
	it)
  ;; set option to it
  (setq ibuffer-saved-filters it)
  )

;; saved groups
(let (it)
  ;;add push here
  (push '("default"
	 ("clisp"
	  (saved . "cl"))
	 ("src"
	  (saved . "esrc"))
	 ("elisp"
	  (saved . "el"))
	 ("org"
	  (saved . "org")))
	it)
  ;;set option to it
  (setq ibuffer-saved-filter-groups it)
  )

;; switch to default group when start ibuffer
(defun init-ibuffer-use-default-group ()
  (and (not ibuffer-filter-groups) ;; not use group
       (assoc "default" ibuffer-saved-filter-groups)
       (ibuffer-switch-to-saved-filter-groups "default")))
(add-hook 'ibuffer-hook 'init-ibuffer-use-default-group)

;; functions used to save filter and group config code
(defmacro init-ibuffer-generate-saver (name var)
  `(defun ,name (name)
     (interactive
      (if (null ,var)
	  (error "No item saved")
	(list (completing-read "get group: " ,var nil t))))
     (insert (concat "(push '"
		     (pp-to-string (assoc name ,var))
		     " it)"))))
(init-ibuffer-generate-saver init-ibuffer-filter ibuffer-saved-filters)
(init-ibuffer-generate-saver init-ibuffer-group ibuffer-saved-filter-groups)

;; preds and regexps that buffer not show
(let (it)
  ;; add push here

  (setq ibuffer-never-show-predicates it)
  )

;; preds and regexps that buffer show
(let (it)
  ;; add push here

  (setq ibuffer-always-show-predicates it)
  )

;; major mode never mark by content

(let (it)
  ;; add push here
  (push 'dired-mode it)
  (setq ibuffer-never-search-content-mode it)
  )

;; don't show empty filter groups
(setq ibuffer-show-empty-filter-groups nil)

;; use other windows ibuffer
(setq ibuffer-use-other-window t)

;; use full size ibuffer
(setq ibuffer-default-shrink-to-minimum-size nil)

;; don't show summary
(setq ibuffer-display-summary nil)

;; enable cycle movement
(setq ibuffer-movement-cycle t)

;; the ibuffer-vc
(require 'ibuffer-vc)

(provide 'init-ibuffer)

在这一段代码中,我做了一些额外的配置操作:

以上就是完整的配置,下一节我们来介绍一下 ibuffer 的扩展方法,以及 ibuffer-vc 这个插件。

注意,添加 filter 和 group 建议先在 *Ibuffer 中创建好后使用我定义的函数进行插入,而不是手写。

6. ibuffer 的扩展

这一节我们来介绍一下 ibuffer 提供的标准扩展方式,顺带简单介绍下 ibuffer 的实现细节。网上关于 ibuffer 的资料有点少,下面都是参考 ibuffer 实现源代码。

ibuffer 的实现位于三个文件中,它们分别是 ibuf-macs.el,ibuf-ext.el 和 ibuffer.el。其中 ibuffer 可看作一个巨大的接口文件,它里面定义了一系列可在 ibuffer-mode 中使用的快捷键,以及其他的一些接口命令。ibuf-macs 中定义了一些 ANSI Common Lisp 中的宏,比如 aif awhen 之类的,有兴趣的同学可以读读这本书,以及另一本叫做 On Lisp 的书。ibuf-ext 可看作 ibuffer 的“下层实现”。

通过 define-ibuffer-column 我们可以定义一些列,就像 ibuffer-formats 注释中允许使用的那些一样。这个宏的用法可以参考 ibuffer.el,在 ibuffer.el 中搜索 define-ibuffer-column 即可。

通过 define-ibuffer-sorter 可以定义排序函数,定义后就可以和像 major-modealphabetic 等 sorter 一样使用了,使用例子可以在 ibuf-ext.el 中搜索 define-ibuffer-sorter 找到。

通过 define-ibuffer-op 可以定义一些操作,就像 *Ibuffer 中的 W E ! O 等操作一样的操作。可以在 ibuf-ext.el 中搜索 define-ibuffer-op 找到使用例子。

通过 define-ibuffer-filter 可以定义 filter 修饰符,默认的修饰符有 mode used-mode name starred-name 等等。可以在 ibuf-ext.el 中搜索 define-ibuffer-filter 找到使用例。

要想控制 ibuffer 的行为,可以去 ibuffer.el 中找到一些相关的函数,由于实在是太多了,而且我也没有什么探索的欲望了,关于 ibuffer 扩展的介绍就到这里吧。

接下来我们介绍一下 ibuffer-vc[2] 这个插件。它的作者是大名鼎鼎的 Steve Purcell,MELPA 的维护者。通过这个小插件我们也可以学习一下 ibuffer 的扩展方法。这个插件不过百余行,读起来应该是比较容易的。

根据 readme 的内容,这个插件提供了以下功能:

ibuffer-vc 的主要功能是提供了依据仓库的分组,以及一些 sorter,filter 和 column。它最主要的函数是 ibuffer-vc-set-filter-groups-by-vc-root ,在 *Ibuffer 中使用它就会使用项目区分的分组。为了能够显示版本管理状态,我们需要修改一下 ibuffer-formats:

(setq ibuffer-formats
      '((mark modified read-only vc-status-mini " "
              (name 18 18 :left :elide)
              " "
              (size 9 -1 :right)
              " "
              (mode 16 16 :left :elide)
              " "
              (vc-status 16 16 :left)
              " "
              vc-relative-file)))

这样就可以显示类似于 git status 的状态标识了。

为了能够快速切换到根据项目区分的分组,我们可以添加快捷键来快速打开 vc-group,这里我就不贴代码了,毕竟个人都有自己习惯的快捷键。

最后来张图看下效果吧:

6.PNG

需要说明的是,调用 ibuffer-vc-set-filter-groups-by-vc-root 只是根据当前文件的项目所属来生成 group,如果新加入了其他项目的文件,那么需要重新生成 group,这样就会打乱当前的 group,我建议使用 ibuffer-vc-generate-filter-groups-by-vc-root 来生成 group,再与原先的 group 组合生成新 group。也就是这样做:

(defun init-ibuffer-group-by-vc-and-default ()
  (interactive)
  (let* ((vc-res (ibuffer-vc-generate-filter-groups-by-vc-root))
	 (new-group (append vc-res (cdr (assoc "default" ibuffer-saved-filter-groups)))))
    (setq ibuffer-filter-groups new-group)
    (let ((ibuf (get-buffer "*Ibuffer*")))
      (when ibuf
        (with-current-buffer ibuf
          (pop-to-buffer ibuf)
          (ibuffer-update nil t))))))

我的完整配置在这里[3],有兴趣的同学可以参考一下。

7. 后记

我大概一年前就看到过有人推荐使用 ibuffer,比如这个视频[4]。 但是我也懒得去用,毕竟那个时候也没有什么 buffer 管理的概念,要管理文件的话,IDE 的侧边栏已经够用了。现在看来 ibuffer 的功能还是挺强大的,但我也不知道花在这上面的时间到底值不值(笑)。

References

[1]
https://www.gnu.org/software/emacs/manual/html_node/emacs/Buffers.html
[2]
https://github.com/purcell/ibuffer-vc/blob/master/ibuffer-vc.el
[3]
https://github.com/include-yy/yy-emacs/blob/master/site-lisp/config/init-ibuffer.el
[4]
https://protesilaos.com/codelog/2020-04-02-emacs-intro-ibuffer/