请测试buffer local 输入法管理机器人

;;; 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")
(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"))
;; to update input method status variable:
;; in your window manager(global hotkey manager), bind command below to a input method toggle key
;; emacsclient -e '(imbot--toggle-im-state)'
(defun imbot--toggle-im-state ()
(interactive)
(with-selected-window (selected-window)
    (if (imbot--im-active-p)
        (imbot--deactivate-im)
        (imbot--activate-im))
    ;; if emacs is under focus, update input method state for current buffer
    (imbot--save-state)))

(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"))
;; CAUTION: disable imbot-mode before looking up key definition start with imbot--prefix-override-keys
(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)
    (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-hook ()
;; for command that changes buffer, the change won't happen in pre-command-hook
(setq imbot--last-buffer (current-buffer)))

(defun imbot--post-command-hook ()
;; restore im state after prefix sequence completed
(when (and (not imbot--prefix-override) this-command)
    (setq imbot--prefix-override t)
    (imbot--maybe-activate-im))
;; save im state for previous buffer and restore im state in current buffer
(when (not (eq imbot--last-buffer (current-buffer)))
    (when (buffer-live-p imbot--last-buffer)
    (with-current-buffer imbot--last-buffer
        (imbot--save-state)))
    (unless (minibufferp)
    (imbot--maybe-activate-im))))

(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-hook)
(add-hook 'post-command-hook #'imbot--post-command-hook)
(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-hook)
(remove-hook 'post-command-hook #'imbot--post-command-hook)
(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 个赞

@manateelazycat 猫哥,实现了你的做到三点就足够了:

配置好 smart-input-source 后,用户可以通过 toggle-input-method 命令手动切换输入法状态
自动记住Buffer的输入法local状态
激活输入法,但是不输入中文时,用户可以使用任何Emacs快捷键而不被输入法吃掉

这三个核心功能实现,既能利用系统默认输入法,又能实现pyim/emacs-rime这些包的优点。 特别感谢 @goumao

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

(defvar imbot--emacs-in-focus nil)
(defun imbot--emacs-set-focus ()
(set imbot--emacs-in-focus t))
(defun imbot--emacs-unset-focus ()
(set imbot--emacs-in-focus nil))

;; 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")
(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"))
;; to update input method status variable:
;; in your window manager(global hotkey manager), bind command below to a input method toggle key
;; emacsclient -e '(imbot--toggle-im-state)'
(defun imbot--toggle-im-state ()
(interactive)
(with-selected-window (selected-window)
    (if (imbot--im-active-p)
        (imbot--deactivate-im)
        (imbot--activate-im))
    ;; if emacs is under focus, update input method state for current buffer
    (when imbot--emacs-in-focus
    (imbot--save-state))))

(defun imbot--deactivate-im ()
(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"))
;; CAUTION: disable imbot-mode before looking up key definition start with imbot--prefix-override-keys
(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--save-state)
    (imbot--deactivate-im)
    ;; Restore the prefix arg
    (setq prefix-arg arg)
    (prefix-command-preserve-state)
    (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-hook ()
;; for command that changes buffer, the change won't happen in pre-command-hook
(setq imbot--last-buffer (current-buffer)))

(defun imbot--post-command-hook ()
;; restore im state after prefix sequence completed
(when (and (not imbot--prefix-override) this-command)
    (setq imbot--prefix-override t)
    (imbot--maybe-activate-im))
;; save im state for previous buffer and restore im state in current buffer
(when (not (eq imbot--last-buffer (current-buffer)))
    (when (buffer-live-p imbot--last-buffer)
    (with-current-buffer imbot--last-buffer
        (imbot--save-state)))
    (unless (minibufferp)
    (imbot--maybe-activate-im))))

(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-hook)
(add-hook 'post-command-hook #'imbot--post-command-hook)
(add-hook 'focus-out-hook #'imbot--save-state)
(add-hook 'focus-in-hook #'imbot--maybe-activate-im)
(add-hook 'focus-out-hook #'imbot--unset-focus)
(add-hook 'focus-in-hook #'imbot--set-focus)
(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-hook)
(remove-hook 'post-command-hook #'imbot--post-command-hook)
(remove-hook 'focus-out-hook #'imbot--save-state)
(remove-hook 'focus-in-hook #'imbot--maybe-activate-im)
(remove-hook 'focus-out-hook #'imbot--unset-focus)
(remove-hook 'focus-in-hook #'imbot--set-focus)
(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
;;; imbot.el --- an input method management bot: automatic system input method switch -*- lexical-binding: t; -*-
(require 'subr-x)

(defvar imbot--emacs-in-focus nil)
(defun imbot--set-focus ()
(setq imbot--emacs-in-focus t))
(defun imbot--unset-focus ()
(setq imbot--emacs-in-focus nil))

;; 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")
(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"))
;; to update input method status variable:
;; in your window manager(global hotkey manager), bind command below to a input method toggle key
;; emacsclient -e '(imbot--toggle-im-state)'
(defun imbot--toggle-im-state ()
(interactive)
(with-selected-window (selected-window)
    (if (imbot--im-active-p)
        (imbot--deactivate-im)
        (imbot--activate-im))
    ;; if emacs is under focus, update input method state for current buffer
    (when imbot--emacs-in-focus
    (imbot--save-state))))

(defun imbot--deactivate-im ()
(call-process imbot-command nil nil nil "-c"))
(defun imbot--maybe-activate-im ()
(if imbot--im-active
    (progn
        (imbot--activate-im)
        (set-cursor-color imbot--im-active-cursor-color))
    (imbot--deactivate-im)
    (set-cursor-color imbot--im-inactive-cursor-color)))

(defvar imbot--prefix-override-keys
'("C-c" "C-x" "C-h"))
;; CAUTION: disable imbot-mode before looking up key definition start with imbot--prefix-override-keys
(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--save-state)
    (imbot--deactivate-im)
    ;; Restore the prefix arg
    (setq prefix-arg arg)
    (prefix-command-preserve-state)
    (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-hook ()
;; for command that changes buffer, the change won't happen in pre-command-hook
(setq imbot--last-buffer (current-buffer)))

(defun imbot--post-command-hook ()
;; restore im state after prefix sequence completed
(when (and (not imbot--prefix-override) this-command)
    (setq imbot--prefix-override t)
    (imbot--maybe-activate-im))
;; save im state for previous buffer and restore im state in current buffer
(when (not (eq imbot--last-buffer (current-buffer)))
    (when (buffer-live-p imbot--last-buffer)
    (with-current-buffer imbot--last-buffer
        (imbot--save-state)))
    (unless (minibufferp)
    (imbot--maybe-activate-im))))

(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-hook)
(add-hook 'post-command-hook #'imbot--post-command-hook)
(add-hook 'focus-out-hook #'imbot--save-state)
(add-hook 'focus-in-hook #'imbot--maybe-activate-im)
(add-hook 'focus-out-hook #'imbot--unset-focus)
(add-hook 'focus-in-hook #'imbot--set-focus)
(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-hook)
(remove-hook 'post-command-hook #'imbot--post-command-hook)
(remove-hook 'focus-out-hook #'imbot--save-state)
(remove-hook 'focus-in-hook #'imbot--maybe-activate-im)
(remove-hook 'focus-out-hook #'imbot--unset-focus)
(remove-hook 'focus-in-hook #'imbot--set-focus)
(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

单独做个package吧,应该很多人会用。

我现在写完包都直接放site-lisp,site-lisp里得有二十多个文件 :joy: 分出去维护很麻烦,又没啥人用,我就懒得整了

等把inline mode也偷过来,再弄个package :grin: