emacs-rime 中的断言(pyim 中称为探针)是相当实用的功能,配置的好的话可以在输入过程中做到完全自动切换中英文。

我希望在配置中不同的 mode 中能有不同的断言,因此写了个判断函数,根据不同的 mode 计算不同的断言函数的值,然后集中返回;另外还自定义了几种自己需要的断言,如:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
(defun +rime-predicate-button-at-point-p ()
  "Determines whether the point is a button.

\"Button\" means that positon is not editable."
  (button-at (point)))

(defun +rime-predicate-beancount-p ()
  "Predicate input state in `beancount-mode.'

Determines whether current buffer's `major-mode' is
`beancount-mode', and the cursor is at the beginning of the
line."
  (when (derived-mode-p 'beancount-mode)
    (not (or (nth 3 (syntax-ppss))
             (nth 4 (syntax-ppss))))))

(defun +rime-predicate-multi-mode-english-p()
  "Using different predicates in different modes."
  (if (derived-mode-p 'telega-chat-mode
                      'text-mode)
      (rime-predicate-auto-english-p)
    (or (rime-predicate-after-alphabet-char-p)
        (rime-predicate-prog-in-code-p)
        (+rime-predicate-beancount-p))))

(setq rime-disable-predicates '(rime-predicate-evil-mode-p
                                +rime-predicate-button-at-point-p
                                rime-predicate-punctuation-line-begin-p
                                +rime-predicate-multi-mode-english-p))

从中可以看到对 `telega-chat-mode` 和 `text-mode` 两种模式使用了不同的断言逻辑,实际应用满足了我的需求。

如果使用的 mode 很多,每种 mode 又需要不同的断言组合,那就需要对判断函数增加逻辑。可以预想,很有可能会发展成类似于

(if 条件 1 (逻辑 1) (if 条件 2 (逻辑 2) (if 条件 3 (逻辑 3) (……))))

的形式。

恰好这时在 Emacs China 论坛 上看到关于 buffer-local 变量的帖子,以前对 `setq-default` 和 `setq-local` 一头雾水,这次经过回帖的解释,总算明白了 buffer-local 变量的用处。

对于我用断言的情况,不正是 buffer-local 变量的使用场景吗?把之前配置里 17 行以后的代码修改一下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
(setq-default rime-disable-predicates
              '(+rime-predicate-button-at-point-p
                rime-predicate-evil-mode-p
                rime-predicate-punctuation-line-begin-p
                rime-predicate-after-alphabet-char-p
                rime-predicate-prog-in-code-p
                +rime-predicate-beancount-p))

(add-hook 'telega-chat-mode-hook
          (lambda() (setq-local rime-disable-predicates
                                '(+rime-predicate-button-at-point-p
                                  rime-predicate-evil-mode-p
                                  rime-predicate-punctuation-line-begin-p
                                  rime-predicate-auto-english-p))))

(add-hook 'text-mode-hook
          (lambda() (setq-local rime-disable-predicates
                                '(+rime-predicate-button-at-point-p
                                  rime-predicate-evil-mode-p
                                  rime-predicate-punctuation-line-begin-p
                                  rime-predicate-auto-english-p))))

也就是先用 `setq-default` 设置一个大部分 mode 都用的默认断言组合,其次对需要单独设置的 mode 用 `setq-local` 设置 buffer-local 断言组合,通过 `add-hook` 使其在进入对应 mode 时设置生效。

这种方式配置代码行数可能会增多,但是逻辑简单,无论多少个不同需求的 mode ,只需要依此类推逐个 `setq-loca` 设置就好了,不容易出错。

最后安利一下 `doom-emacs` ,提供了很多简化代码的宏,如上述两个 `setq-loca` 在 `doom-emacs` 里就可以写为:

1
2
3
4
5
6
(add-hook! (telega-chat-mode text-mode)
  (setq-local rime-disable-predicates
              '(+rime-predicate-button-at-point-p
                rime-predicate-evil-mode-p
                rime-predicate-punctuation-line-begin-p
                rime-predicate-auto-english-p)))

搞定,简单吧?

注: 最新的断言配置在 github 仓库 ,里面的逻辑稍微复杂点,谨供参考。