因为我是用evil的,emacs要是能像在gvim里一样自动切换输入法,就不需要如pyim或者emacs-rime之类package的了。
在windows的gvim里,输入法是可以根据模式自动切换的,按esc进入normal mode就切换为英文;在insert mode中,如果之前是中文,进入insert mode后会保持中文状态。windows的gvim中,测试了rime和微软拼音,都正常切换。不过neovim是不行的。看来没有全盘copy gvim的代码。
实现上,emacs只要原生提供一个输入法切换的函数,在evil切换mode时hook住,切换输入法状态就可以了。
windows中和输入法相关的dll是imm32.dll,它提供了 =ImmSetOpenStatus= 函数进行输入法中英文模式切换。可以用 =LoadLibrary= 动态加载imm32.dll,然后用 =GetProcAddress= 获取到函数地址进行调用。
需要在emacs中实现一个系统级elisp函数进行调用。但是由于lisp代码是在Lisp Thread(mainThread)中被调用,而输入法(gui相关的)是在emacs的windowsThread中调用。而 =ImmSetOpenStatus()= 必须是windowsThread调用才行。
新增emacs自定义的window message WM_EMACS_IME_STATUS,Lisp thread中发WM_EMACS_IME_STATUS消息给window thread。
src/w32term.h
#define WM_EMACS_IME_STATUS (WM_EMACS_START + 26)
#define WM_EMACS_END (WM_EMACS_START + 27)
src/w32fns.c
static HWND w32_main_hwnd;
/* emacs ime status change flag, invoked in mainThread and windowsThread */
static int w32_ime_status_changed = 0;
static int w32_emacs_ime_status = 0;
w32_msg_pump() {
...
/* hwnd =NULL的消息必须在w32_msg_pump()中处理. */
case WM_EMACS_IME_STATUS:
if (!set_ime_open_status_fn) {
break;
}
else {
if (w32_ime_status_changed) {
w32_ime_status_changed = 0;
HIMC context;
context = get_ime_context_fn (w32_main_hwnd);
if (!context) {
break;
}
set_ime_open_status_fn (context, w32_emacs_ime_status);
release_ime_context_fn (w32_main_hwnd, context);
}
}
break;
...
}
static void
w32_createwindow (struct frame *f, int *coords)
{
...
FRAME_W32_WINDOW (f) = hwnd
= CreateWindow (EMACS_CLASS, f->namebuf, f->output_data.w32->dwStyle,
left, top, rect.right - rect.left, rect.bottom - rect.top,
parent_hwnd, NULL, hinst, NULL);
/* 保存输入法相关window的handle*/
w32_main_hwnd = hwnd;
...
}
DEFUN ("w32-set-ime-open-status",
Fw32_set_ime_open_status, Sw32_set_ime_open_status,
1, 1, 0,
doc: /* Set emacs IME open status on Windows. */)
(Lisp_Object status)
{
w32_ime_status_changed = 1;
if (NILP (status)) {
w32_emacs_ime_status = 0;
}
else {
w32_emacs_ime_status = 1;
}
/* 其实post IME_NOTIFY 就可以,里面是什么参数都无所谓的,自定义一个message都可以。 */
PostThreadMessage(dwWindowsThreadId, WM_EMACS_IME_STATUS, 0, 0);
return Qnil;
}
Lisp
(defun emacs-ime-disable ()
(w32-set-ime-open-status nil))
(defun emacs-ime-enable ()
(w32-set-ime-open-status t))
(add-hook 'evil-insert-state-entry-hook 'emacs-ime-enable)
(add-hook 'evil-insert-state-exit-hook 'emacs-ime-disable)