实现了 debounce/throttle 的 timeout 包要加入 Emacs 了

对“拉操作”,这里我补充一个简单的例子和使用 timeout 的缓解方法。

在 Emacs 30 中,Emacs 的 tab-line 新添加了一个选项 tab-line-tabs-buffer-group-function,可以使用项目来实现对 buffer 的分类,下面是我的 tab-line 配置:

;;@@TAB-LINE Windows 顶端显示可方便切换相关 buffer
;; (global-tab-line-mode t)
(setopt tab-line-close-button-show 'non-selected)
;; 使用项目作为 tab line 分组
;; 当前实现的效率不佳
(setopt tab-line-tabs-function 'tab-line-tabs-buffer-groups)
(setopt tab-line-tabs-buffer-group-function
        #'tab-line-tabs-buffer-group-by-project)

我目前不使用 tab-line 的很大原因是 global-tab-line-modetab-line-tabs-buffer-group-by-project 在 Windows 上的效率太低,它内部使用的 project-current 太慢,通过 benchmark-run 测试,单次 project-current 调用开销在 1ms 左右:

(defun tab-line-tabs-buffer-group-by-project (&optional buffer)
  "Group tab buffers by project name."
  (with-current-buffer buffer
    (if-let* ((project (project-current)))
        (project-name project)
      "No project")))

由于 tab-line 的实现机制问题,每当 Emacs 界面需要重新绘制时,它会重新计算所有 buffer 归属的 group,这也就导致 tab-line-tabs-buffer-group-by-project 在 Emacs 每次重绘界面时都需要被调用,而触发 Emacs 重绘实在太过容易和频繁:普通的光标移动命令都能触发。即便在 Emacs 中打开几十个 buffer,你也能够感到明显的操作不跟手。

通过 timeout ,这一问题看似能够在一定程度上得到缓解:

(timeout-throttle 'tab-line-tabs-buffer-group-by-project)

但问题没这么简单,这实际上阻止了 tab-line 从每个 buffer 获取正确的分组信息,因为一次调用后指定的节流时间内的其余调用会直接返回第一次调用的返回结果,从而导致不正确的分组。如果你对 tab-line 的代码进行一些分析,你会发现我们实际上需要节流的是 tab-line-format

(timeout-throttle 'tab-line-format)

但是话又说回来了,这也只是一种“权宜之计”,从稳定的卡顿变成了时不时卡你一下。要用好 timeout 还是需要对性能问题的原因有一定的了解。(也许更好的做法是添加带实际缓存的 advice)。

:innocent: