从最开始使用外置输入法到使用内置输入法 pyim ,越来越感受到内置输入法的便利,其中最让人中毒的就是通过探针函数的设置,在不同的位置或模式自动切换中英文,几乎不用手工切换,打字真如行云流水一般。

pyim 是一款非常优秀的内置输入法,是它坚定了我从外置输入法转为内置输入法的决心。但是这是一款从拼音起家的输入法,虽然早已加入了对五笔等方案的支持,但对五笔远没有像拼音方案那样进行优化,所以用起来总感觉有点儿不尽如意。

我在系统中用的是 rime 输入法, pyim 也可以把 rime 作为后端进行调用,经过一段时间的试用,发现 pyim 对 rime 的调用不太完全,而且 pyim 用自家的缓存对 rime 取出的候选词进行干预,这样就无法保持与外置 rime 输入法的一致体验了。

正在此时, emacs-china 上有大佬发布了一新的内置输入法 emacs-rime 。严格的说,这不是一个输入法,而是相当于是 rime 输入法在 emacs 里的一个前端,所有的输入都原封不动的发送给 rime 后端,由 rime 处理完后把数据再返回并在 emacs 里展现。

经过几天的试用和调整,今天终于决定把输入法切换到 emacs-rime 了,理由有两点:

  1. 几乎与外置输入法一致的输入体验;
  2. 谦虚而活跃的作者,作者曾经连续花几个小时和我一起定位一个 issue 。

配置如下 (doom-emacs 配置格式) :

 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
30
31
32
33
34
35
36
37
;; packages.el
(package! liberime
  :recipe (:host github :repo "DogLooksGood/liberime" :files ("CMakeLists.txt" "Makefile" "src" "*.el")))
(package! rime
  :recipe (:host github :repo "DogLooksGood/emacs-rime" :files ("rime.el")))

;; config.el
(use-package! liberime-config
  :init
  (setenv "RIME_PATH" "~/repos/librime")
  (setq liberime-shared-data-dir (file-truename "~/Library/Rime")
        liberime-user-data-dir (file-truename "~/.local/liberime"))
  :hook
  ('after-init . (lambda ()
                   (when (fboundp 'liberime-sync-user-data)
                     (liberime-sync))))
  ('liberime-after-start . (lambda ()
                            (liberime-select-schema "wubi86_jidian"))))

(use-package! rime
  :after liberime-config
  :after-call after-find-file pre-command-hook
  :custom
  (default-input-method "rime")
  (rime-show-candidate 'posframe)
  :config
  (defadvice! +rime--posframe-display-result-a (args)
    "给 `rime--posframe-display-result' 传入的字符串加一个全角空
格,以解决 `posframe' 偶尔吃字的问题。"
    :filter-args #'rime--posframe-display-result
    (cl-destructuring-bind (result) args
      (let ((newresult (if (string-blank-p result)
                           result
                         (concat result " "))))
        (list newresult))))

  (load! "+rime-probe-english"))

pyim 里的探针函数非常好用, emacs-rime 里也提供了几个,我从 pyim 里移植了一个觉得好用的,另外又写了两个自用的探针,代码如下:

 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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
;;; +rime-probe-english.el -*- lexical-binding: t; -*-

;;
;; Some functions copied from `pyim', thanks for tumashu@github.com .
;;
(defun +rime--char-before-to-string (num)
  "得到光标前第 `num' 个字符,并将其转换为字符串。"
  (let* ((point (point))
         (point-before (- point num)))
    (when (and (> point-before 0)
               (char-before point-before))
      (char-to-string (char-before point-before)))))

(defun +rime--string-match-p (regexp string &optional start)
  "与 `string-match-p' 类似,如果 REGEXP 和 STRING 是非字符串时,
不会报错。"
  (and (stringp regexp)
       (stringp string)
       (string-match-p regexp string start)))

(defun +rime--probe-auto-english ()
  "激活这个探针函数后,使用下面的规则自动切换中英文输入:

1. 当前字符为英文字符(不包括空格)时,输入下一个字符为英文字符
2. 当前字符为中文字符或输入字符为行首字符时,输入的字符为中文字符
3. 以单个空格为界,自动切换中文和英文字符
   即,形如 `我使用 emacs 编辑此函数' 的句子全程自动切换中英输入法
"
  (let ((str-before-1 (+rime--char-before-to-string 0))
        (str-before-2 (+rime--char-before-to-string 1)))
    (unless (string= (buffer-name) " *temp*")
      (if (> (point) (save-excursion (back-to-indentation)
                                     (point)))
          (or (if (+rime--string-match-p " " str-before-1)
                  (+rime--string-match-p "\\cc" str-before-2)
                (not (+rime--string-match-p "\\cc" str-before-1))))))))

(defun +rime--beancount-p ()
  "当前为`beancount-mode',且光标在注释或字符串当中。"
  (when (derived-mode-p 'beancount-mode)
    (not (or (nth 3 (syntax-ppss))
             (nth 4 (syntax-ppss))))))

(defun +rime--evil-mode-p ()
  "检测当前是否在 `evil' 模式下。"
  (or (evil-normal-state-p)
      (evil-visual-state-p)
      (evil-motion-state-p)
      (evil-operator-state-p)))

(defun +rime-english-prober()
  "自定义英文输入探针函数,用于在不同mode下使用不同的探针列表"
  (let ((use-en (or (button-at (point))
                    (+rime--evil-mode-p))))
    (if (derived-mode-p 'telega-chat-mode)
        (setq use-en (or use-en
                         (+rime--probe-auto-english)))
      (when (derived-mode-p 'text-mode)
        (setq use-en (or use-en
                         (+rime--probe-auto-english))))
      (when (derived-mode-p 'prog-mode 'conf-mode)
        (setq use-en (or use-en
                         (rime--after-alphabet-char-p))))
      (setq use-en (or use-en
                       (rime--prog-in-code-p)
                       (+rime--beancount-p))))
    use-en))

(setq rime-disable-predicates '(+rime-english-prober))

刚刚给作者提了点小建议,得到了非常积极的响应,非常感谢 DogLooksGood@github.com 的贡献。 emacs-rime 还在不断完善中,希望 emacs-rime 越做越好,造福更多的 emacer 。

另外,再次感谢 pyim 和 posframe 的大神 tumashu@github.com ,造了那么多优秀的轮子。