Emacs 中使用 smart-input-source 配合 Rime 进行流畅地中英文输入


Emacs 中文输入有不少方案,既可以用外置的输入法(比如 Rime, sogou 等)进行输入,还可以用内置的输入法(比如 pyim )利用 Elisp 完成中文输入。作为中文用户,我们很难避免同时输入中英文(比如这篇博文到目前为止就有不少英文字符了),那么选用一个能自动进行流畅地中英文自动切换的输入方案能够极大地提升输入体验。

Rime 是比较成熟的开源中文输入法,但在 Emacs 中使用有几个问题:

  1. 在输入过程中得按 Shift 来切换中英文输入状态,这会导致整体的输入流程比较卡顿;
  2. 如果当前处于中文输入状态,敲 Emacs 命令的时候依然会以中文输入,比如执行 C-x b 切换 buffer 时,其中的 b 依然会被 Rime 当作中文输入来处理;

smart-input-source (简称 sis )就是为了解决这些问题应运而生的,但当前的文档还不是很完善,导致上手有一定的困难。我在入坑的过程中进行了一些摸索,这里对它进行一些补充,希望对其他 Emacs 都有会有帮助。

/img/2022-09-11-emacs-rime-smart-input-source.gif
通过 sis 来自动切换中英文输入

概念

input source
输入源。 sis 定义了两个 input source ,一个叫 english 用来输入英文,另一个叫 other 用来输入其他,对我们来说就是中文;
input source manager
也叫 ISM ,用来切换不同的 input source ,比如 ibus 就是 Linux 上的一种 ISM

功能

sis 的功能通过 local & global minor modes 两种方式提供,有以下几种 global minor modes :

  • sis-global-cursor-color-mode 根据当前所处的 input source 为 english or other ,以不同的颜色显示光标
  • sis-global-context-mode 根据当前 buffer 光标处的上下文,智能地通过上下文来决定应该使用哪种 input source (对中英文混合输入至关重要) context 在 sis 中表示使用 english or other 输入法这个“上下文”。
  • sis-global-respect-mode 进入 minibuffer 、敲命令(比如 C-x b )时临时进入英文模式,回到原 buffer 后又会恢复之前的 input source
  • sis-global-inline-mode 这个不知道是啥功能,暂时不使用

配置

  • sis-ism-lazyman-config 函数用于设置两个 input sources (第一个和第二个参数)以及 ISM ISM 参数可以参考 sis README 进行设置,在我我这里是 ibus 。 两个 input sources 参数具体应该填啥,可以通过 M-x sis-get 命令来得到当前系统的输入法对应的字符串,把它的输出复制过来即可。 最终我的系统上的设置参数是这样的: (sis-ism-lazyman-config "xkb:us::eng" "rime" 'ibus) ;另外,这句配置得优先于下边的开启 modes 设置,否则 sis 无法工作,目前还不知道为啥。
  • 开启自己感兴趣的 modes ,我只要 context mode & respect mode 即可。

context mode 设置

context mode 主要有 2 个配置变量:

  • sis-context-hooks and sis-context-detectors

    前者 sis-context-hooks 控制需要在哪些 hooks 里添加 sis-context 这个 context 探测函数。而探测函数实现逻辑中( sis--context-guess )需要依靠 sis-context-detectors 来判断当前应该用 english 还是 other input source 。

    默认提供的 sis-context-detectors 不是很符合我个人的需求,我希望的用法是:

    1. 在输入中文的过程中,如果输入了一个空格或者英文字符,那么就切换到 english input source
    2. 在输入英文的过程中,需要有机制能够切换回中文。我觉得用 一个空格+一个逗号 来表示即可,一方面比较好按,另一方面不容易与正常的输入序列冲突(本来用两个空格更好按,但较容易出现冲突)。
  • sis-context-triggers, a list of list 用于控制哪些 functions 前后应该进行探测 context 。 每一个 list item 的格式为 (FN PRE-FN-DETECTOR POST-FN-DETECTOR) ,利用 elisp around advice 机制实现。它使得在 FN 执行之前,执行 PRE-FN-DETECTOR 来探测 context ;执行完 FN 之后,再通过 POST-FN-DETECTOR 来判断。两个 detectors 设置起来比较繁琐,可以把不关心的 detector 设置为 nil 即可。 这个与 sis-context-detectors 有何区别呢?我猜测它应该适用于本来没有提供 hooks 变量的场景(不然直接定制 sis-context-hooks 即可)。

看代码过程中看到的一些内部函数的功能,也顺便记录一下:

  • (sis--context-guess) 探测光标处的 context
  • (sis--context-line) 探测当前行的 context

其他

  • M-x sis-log-mode 用于开关日志打印(往 *Message* buffer 输出日志信息),方便 debugging 。

原理

虽然目前我还没有全部看完 sis 的代码,但如果我没理解错,它在 Linux 上的原理就是通过 ibus engine "rime" or ibus engine "xkb:us::eng" 来达到切换系统的输入法。

配置代码汇总

最终我的配置代码如下:

(sis-ism-lazyman-config "xkb:us::eng" "rime" 'ibus)

(defun w/sis--guess-context-by-prev-chars (backward-chars forward-chars)
  "Detect the context based on the 2 chars before the point.

It has a side effect of deleting the previous whitespace if
there is a whitespace/newline and a comma before the point."
  (when (and (>= (point) 3)
             sis-context-mode
             (memq major-mode '(org-mode)))
    (let ((prev (preceding-char))
          (pprev (char-before (1- (point)))))
      (cond
       ((and (or (char-equal ?  pprev) (char-equal 10 pprev)) ; a whitespace or newline
             (char-equal ?, prev))
        (delete-char -1)                ; side effect: delete the second whitespace
        'other)
       ((string-match-p "[[:ascii:]]" (char-to-string (preceding-char)))
        'english)
       (t 'other)))))

(setq sis-context-detectors '(w/sis--guess-context-by-prev-chars))

(setq sis-context-hooks '(post-command-hook)) ; may hurt performance

(sis-global-respect-mode t)
(sis-global-context-mode t)

这里 context 探测功能的使用方法是:

  1. 输入中文的过程中,输入一个空格就会切换到英文输入状态
  2. 输入英文的过程中,输入空格和逗号,或者换行加逗号,就能回到中文输入状态

它通过往 post-command-hook 加 hook 来实现的,性能上可能会有问题(不过目前我还没遇到);另外,由于 post-command-hook 比较关键,如果某个 hook 执行出错,它会被 elisp 直接移除。

对功能的影响就是,如果 w/sis--guess-context-by-prev-chars 出错了,会导致自动中英文切换功能失效。遇到这种情况不要慌,执行 M-x sis-global-context-mode 两次 来让 sis 把 hook 重新加上就好了。

P.S. 这篇文章就是在 sis 的帮助下写的,我对目前的效果还比较满意 :)

Emacs  Rime  Linux 

也可以看看

comments powered by Disqus