先说点题外话,emacs 提供了 previous-buffer
与next-buffer
两个命令
分别绑定在 C-x left
C-x right
上, 在用tab 之前我一直是用这两个命令在最近访问过的buffer之间切换的。
并在这两个命令的基础上,对其做了简单封装,用于切换时,跳过 *Completions*
*Compile-Log*"
这类比较烦人的的buffer。
这两天研究 tab-line-mode 的时候了 发现了switch-to-prev-buffer-skip
这个变量,它的值可以是一个函数,形如
(defun vmacs-switch-to-prev-buffer-skip(win buf bury-or-kill)
nil)
(setq switch-to-prev-buffer-skip #'vmacs-switch-to-prev-buffer-skip)
这个函数返回非nil
值时,用next-buffer
previous-buffer
进行切换buffer时 便可将buf
参数对应的buffer 跳过.
言归正传: 而我使用tab 的初衷有两个:
- 用tab 来将
next-buffer
/previous-buffer
切换前后buffer可视化,用于对最近访问的三五个buffer之间进行切换 - 将
*Completions*
*Compile-Log*"
等buffer 与正常buffer分到不同的组 - 将vterm 分到同一个tab组里, 这个组里只显示vterm buffer,将能通过命令快速在相邻的tab页间进行切换
之前用了一段时间的 awesome-tab 和centaur-tab ,它们的分组功能可以满足我上面的第2、3条,但没法满足第1条
因为执行 next-buffer/previous-buffer
时,buffer的顺序是按 (buffer-list)
中的顺序来的,
awesome-tab/centaur-tab 对buffer 进行分组,各组中buffer顺序无法与(buffer-list)
完全一致,所以无法满足第1个要求
所幸 awesome-tab/centaur-tab
都提供了 在相邻tab间进行切换的命令,与next-buffer/previous-buffer
行为类似,
于是上面第1条变成了 如何让最近访问过的buffer 对应的tab彼此相邻 , 为此也曾为awesome-tab/centaur-tab 提过issue ,
如希望将新建的tab 放在当前tab 之前或之后,而不是放到整个列表的末尾。
还有就是当使用代码跳转或使用ivy/helm跳转时,希望跳转前后的两个buffer能挪到相邻的位置
centaur-tabs 有如下配置能实现诸如此类的功能。
(setq centaur-tabs-adjust-buffer-order 'left)
(centaur-tabs-enable-buffer-reordering)
emacs 自带了tab-bar-mode tab-line-mode 后 一直想研究下能否用它们实现类似的功能,这样又能少安装一个包了。
折腾了几天tab-line-mode后,发现基本能达到我的要求。
首先默认 tab-line-mode不进行分组 此时其tab的排序就是按(buffer-list)
的顺序排的 ,最近访问的buffer通常排在整个tab列表最后。
无论是通过代码跳转到一个buffer,还是使用consult-buffer/helm/ivy 切换到其他buffer,都会将你最近访问过的两个buffer
放到tab列表末尾,只有通过 previous-buffer
与next-buffer
或tab-line-switch-to-prev-tab
tab-line-switch-to-next-tab
进行的切换,才不会调整tab/buffer的顺序。 若是上面4条命令也会调整顺序,那你通过这4条命令只能在最末尾的两个buffer间来回切换。
要求1、2、3 可以认为将所有buffer 分成3组,
一组正常的buffer,
一组 为*Completions*
*Compile-Log*"
等临时性的buffer
一组为vterm
但是一旦对tab 进行分组后 又会出现tab顺序无法保持与(buffer-list)
顺序一致的情况。
不过我们可以通过对上面提到的 switch-to-prev-buffer-skip
进行定制后,来实现。
我们只需要保证 switch-to-prev-buffer-skip 判定为skipped 的buffer 恰好都不是当前分组内的buffer即可。
即:
- 若当前buffer 是正常buffer,则 switch-to-prev-buffer-skip 判断临时buffer或vterm为skipped ,
- 若当前buffer为临时buffer时,则将正常buffer或vterm 判断为skipped
- 若当前buffer为vterm ,则将 非vterm buffer判定为skipped
此时通过对 tab-line-tabs-window-buffers 加defadvice,来确保 tab-line-tabs-window-buffers 返回的tab列表与上面的判据一致
即可使用 previous-buffer
与next-buffer
或tab-line-switch-to-prev-tab
tab-line-switch-to-next-tab
切换前后相邻的tab.
代码如下:
(global-tab-line-mode t)
(global-set-key (kbd "s-C-M-k") 'previous-buffer) ;H-k default C-x left
(global-set-key (kbd "s-C-M-j") 'next-buffer) ;H-j default C-x right
(setq tab-line-new-button-show nil) ;; do not show add-new button
(setq tab-line-close-button-show nil) ;; do not show close button
(setq tab-line-separator (propertize " ▶" 'face '(foreground-color . "cyan")))
(setq switch-to-prev-buffer-skip #'vmacs-switch-to-prev-buffer-skip)
;; switch-to-prev-buffer 与 switch-to-next-buffer 时 skip 特定的buffers
;;而 tab-line-switch-to-prev/next-tab 恰好使用了上面两个函数
(defun vmacs-switch-to-prev-buffer-skip(win buf bury-or-kill)
(when (member this-command '(next-buffer previous-buffer
tab-line-switch-to-prev-tab
tab-line-switch-to-next-tab))
(cond
((vmacs-tab-vterm-p) ;当前buffer是vterm
(not (vmacs-tab-vterm-p buf))) ;若buf 不是vterm,则skip
((vmacs-boring-buffer-p (current-buffer))
(not (vmacs-boring-buffer-p buf)))
(t ;当前buffer是正常buffer
(or (vmacs-boring-buffer-p buf) ;若buf 是boring buf 或vterm,则跳过
(vmacs-tab-vterm-p buf))))))
(defadvice tab-line-tabs-window-buffers (around skip-buffer activate)
"Return a list of tabs that should be displayed in the tab line
but skip uninterested buffers."
(let ((buffers ad-do-it))
(cond
((vmacs-tab-vterm-p) ;当前buffer是vterm
;; 只返回vterm buffer 作为当前tab group 的tab
(setq ad-return-value (seq-filter #'vmacs-tab-vterm-p buffers)))
((vmacs-boring-buffer-p (current-buffer))
(setq ad-return-value (seq-filter #'vmacs-boring-buffer-p buffers)))
(t
;; skip boring buffer 及vterm
(setq buffers (seq-remove #'vmacs-boring-buffer-p buffers))
(setq ad-return-value (seq-remove #'vmacs-tab-vterm-p buffers))))))
(defun vmacs-tab-vterm-p(&optional buf)
(eq (buffer-local-value 'major-mode (or buf (current-buffer))) 'vterm-mode))
(defun vmacs-boring-buffer-p(&optional buf)
(string-match-p (rx (or
"\*Async-native-compile-log\*"
"magit"
"\*company-documentation\*"
"\*eaf" "\*eldoc" "\*Launch " "*dap-"
"*EGLOT " "\*Flymake log\*"
"\*gopls::stderr\*" "\*gopls\*"
"\*Compile-Log\*" "*Backtrace*"
"*Package-Lint*" "\*sdcv\*" "\*tramp"
"\*lsp-log\*" "\*tramp" "\*Ibuffer\*"
"\*Help\*" "\*ccls" "\*vc"
"\*xref" "\*Warnings*" "\*Http"
"\*Async Shell Command\*"
"\*Shell Command Output\*"
"\*Calculator\*" "\*Calc "
"\*Flycheck error messages\*"
"\*Gofmt Errors\*"
"\*Ediff" "\*sdcv\*"
"\*Org PDF LaTex Output\*"
"\*Org Export"
"\*osx-dictionary\*" "\*Messages\*"
))
(buffer-name buf)))
另附上我tab 相关配置链接
另附上只保留10个打开的文件的办法
;; 最多打开10个文件
(defun vmacs-prevent-open-too-much-files()
(let* ((buffers (tab-line-tabs-window-buffers))
(buffer-save-without-query t)
(len (length buffers))
(max 9) (i 0))
(dolist (buffer buffers)
(when (and (< i (- len max)) (>= len max))
(when (buffer-live-p buffer)
(with-current-buffer buffer
(when (buffer-file-name buffer) (basic-save-buffer))
(kill-buffer buffer))))
(setq i (1+ i)))))
(add-hook 'find-file-hook #'vmacs-prevent-open-too-much-files)