imbot.el --- an input method management bot

菜鸟的 smart-input-source.el 先上一个预览版 :-)

;;; imbot.el --- an input method management bot: automatic system input method switch -*- lexical-binding: t; -*-
(require 'subr-x)

;; Input source specific cursor color
(defvar imbot--im-inactive-cursor-color "white")
(defvar imbot--im-active-cursor-color "green")
(defface imbot--inline-face '()
"inline overlay."
:group 'imbot)
(set-face-attribute
'imbot--inline-face nil
:foreground (face-attribute 'font-lock-constant-face :foreground)
:inverse-video t)

(defvar imbot-command "fcitx-remote")
(defvar imbot--im-active nil "buffer local input method state")
;; (message "im %s" imbot--im-active)
(make-variable-buffer-local 'imbot--im-active)
(defun imbot--im-active-p ()
(let ((output
        (let (deactivate-mark)
        (with-temp-buffer
            (call-process imbot-command nil t)
            (buffer-string)))))
    (char-equal
    (aref output 0) ?2)))
(defun imbot--save-state ()
(setq imbot--im-active (imbot--im-active-p)))
(defun imbot--activate-im ()
(call-process imbot-command nil nil nil "-o"))
(defun imbot--deactivate-im ()
(imbot--save-state)
(call-process imbot-command nil nil nil "-c"))
(defun imbot--maybe-activate-im ()
(if imbot--im-active
    (imbot--activate-im)
    (imbot--deactivate-im)))

(defvar imbot--prefix-override-keys
'("C-c" "C-x" "C-h"))
(defvar imbot--prefix-override-map-alist nil)
(let ((keymap (make-sparse-keymap)))
(dolist (prefix
        imbot--prefix-override-keys)
    (define-key keymap (kbd prefix)
    #'imbot--prefix-override-handler))
(setq imbot--prefix-override-map-alist
        `((imbot--prefix-override . ,keymap))))

(defvar imbot--prefix-override nil "imbot prefix override state")
(defvar imbot--last-buffer nil)

(defun imbot--prefix-override-handler (arg)
"Prefix key handler with ARG."
(interactive "P")
(let* ((keys (this-command-keys)))
    ;; temporarily disable prefix override
    (setq imbot--prefix-override nil)
    (imbot--deactivate-im)
    ;; Restore the prefix arg
    (setq prefix-arg arg)
    (prefix-command-preserve-state)
    ;; (message "1 this command %s" this-command)
    (setq this-command nil)
    ;; Push the key back on the event queue
    (setq unread-command-events
        (append (mapcar (lambda (e) `(t . ,e)) (listify-key-sequence keys))
                unread-command-events))))

(defun imbot--pre-command-switcher ()
(setq imbot--last-buffer (current-buffer)))

(defun imbot--post-command-switcher ()
;; (message "2 this command %s" this-command)
(cond
    ;; restore im state after prefix sequence completed 
    ((and (not imbot--prefix-override) this-command)
    (setq imbot--prefix-override t)
    (imbot--maybe-activate-im))
    ;; save im state on buffer switch and restore im state
    ((not (eq imbot--last-buffer (current-buffer)))
    (when (buffer-live-p imbot--last-buffer)
    (with-current-buffer imbot--last-buffer
        (imbot--save-state)))
    (imbot--maybe-activate-im))
    (t nil)))

(defun imbot--prefix-override-enable (&optional args)
(add-to-list 'emulation-mode-map-alists 'imbot--prefix-override-map-alist))

(defun imbot--prefix-override-disable (&optional args)
(setq emulation-mode-map-alists
        (delq 'imbot--prefix-override-map-alist
            emulation-mode-map-alists)))

(defvar imbot--prefix-reinstate-triggers
'(evil-local-mode yas-minor-mode eaf-mode)
"modes mess with `emulation-mode-map-alists")

(defun imbot-mode-enable ()
(when (and (boundp 'evil-mode) evil-mode)
    (add-hook 'evil-insert-state-exit-hook #'imbot--deactivate-im)
    (add-hook 'evil-emacs-state-exit-hook #'imbot--deactivate-im)
    (add-hook 'evil-insert-state-entry-hook #'imbot--maybe-activate-im)
    (add-hook 'evil-emacs-state-entry-hook #'imbot--maybe-activate-im))
(add-hook 'pre-command-hook #'imbot--pre-command-switcher)
(add-hook 'post-command-hook #'imbot--post-command-switcher)
(add-hook 'focus-out-hook #'imbot--save-state)
(add-hook 'focus-in-hook #'imbot--maybe-activate-im)
(add-hook 'minibuffer-setup-hook #'imbot--deactivate-im)
(imbot--prefix-override-enable)
(setq imbot--prefix-override t)
(dolist (trigger imbot--prefix-reinstate-triggers)
    (advice-add trigger :after #'imbot--prefix-override-enable)))

(defun imbot-mode-disable ()
(imbot--deactivate-im)
(when (and (boundp 'evil-mode) evil-mode)
    (remove-hook 'evil-insert-state-exit-hook #'imbot--deactivate-im)
    (remove-hook 'evil-emacs-state-exit-hook #'imbot--deactivate-im)
    (remove-hook 'evil-insert-state-entry-hook #'imbot--maybe-activate-im)
    (remove-hook 'evil-emacs-state-entry-hook #'imbot--maybe-activate-im))
(remove-hook 'pre-command-hook #'imbot--pre-command-switcher)
(remove-hook 'post-command-hook #'imbot--post-command-switcher)
(remove-hook 'focus-out-hook #'imbot--save-state)
(remove-hook 'focus-in-hook #'imbot--maybe-activate-im)
(remove-hook 'minibuffer-setup-hook #'imbot--deactivate-im)
(imbot--prefix-override-disable)
(dolist (trigger imbot--prefix-reinstate-triggers)
    (advice-remove trigger #'imbot--prefix-override-enable)))

(define-minor-mode imbot-mode
"input method manage bot"
:global t
:init-value nil
(if imbot-mode
    (imbot-mode-enable)
    (imbot-mode-disable)))

(provide 'imbot)
;;; imbot.el ends here

下一步问题:

  1. 在buffer变化时执行一行代码,保存输入法状态现在是用 pre-command hook和post command hook,但是有可能buffer切换不是因为commandh导致的
  2. 操作系统切换输入法快捷键绑定到一个脚本,当前窗口是emacs,执行切换时用 emacsclient改变输入法状态变量
  3. 加入 inline mode