分享下折腾emacs 29 的tab-line-mode经验

先说点题外话,emacs 提供了 previous-buffernext-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 的初衷有两个:

  1. 用tab 来将next-buffer/previous-buffer切换前后buffer可视化,用于对最近访问的三五个buffer之间进行切换
  2. *Completions* *Compile-Log*" 等buffer 与正常buffer分到不同的组
  3. 将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-buffernext-buffertab-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即可。 即:

  1. 若当前buffer 是正常buffer,则 switch-to-prev-buffer-skip 判断临时buffer或vterm为skipped ,
  2. 若当前buffer为临时buffer时,则将正常buffer或vterm 判断为skipped
  3. 若当前buffer为vterm ,则将 非vterm buffer判定为skipped

此时通过对 tab-line-tabs-window-buffers 加defadvice,来确保 tab-line-tabs-window-buffers 返回的tab列表与上面的判据一致 即可使用 previous-buffernext-buffertab-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 相关配置链接 https://github.com/jixiuf/vmacs/blob/master/conf/conf-tabs.el

另附上只保留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)
6 个赞