emacs-elpa-diffs
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[elpa] externals/pyim 2f1aaaa9a7 41/41: Merge pull request #442 from tum


From: ELPA Syncer
Subject: [elpa] externals/pyim 2f1aaaa9a7 41/41: Merge pull request #442 from tumashu/struct
Date: Sat, 4 Jun 2022 09:57:49 -0400 (EDT)

branch: externals/pyim
commit 2f1aaaa9a7f0e6640e5bafb66857a9a0f49e80f8
Merge: 872c7139fe 1b767c669e
Author: tumashu <tumashu@163.com>
Commit: GitHub <noreply@github.com>

    Merge pull request #442 from tumashu/struct
    
    使用 cl-defgeneric and cl-defmethod 重构代码。
---
 README.org           |   2 -
 pyim-autoselector.el |   8 +-
 pyim-candidates.el   | 488 ++++++++++++++++++++--------------------
 pyim-cloudim.el      |  48 ++--
 pyim-codes.el        |  36 ++-
 pyim-cregexp.el      |  26 +--
 pyim-cstring.el      | 158 +++++++------
 pyim-dcache.el       |   3 +-
 pyim-imobjs.el       |  36 ++-
 pyim-liberime.el     |  85 ++++---
 pyim-outcome.el      |  13 +-
 pyim-page.el         | 569 +++++++++++++++++++++++-----------------------
 pyim-preview.el      |  50 +++--
 pyim-probe.el        |  16 +-
 pyim-process.el      |  50 ++---
 pyim-scheme.el       | 115 +++++++---
 pyim.el              |  20 +-
 tests/pyim-tests.el  | 623 +++++++++++++++++++++++++++++++++++++++++++++------
 18 files changed, 1456 insertions(+), 890 deletions(-)

diff --git a/README.org b/README.org
index 558ca39d18..d278dc124e 100644
--- a/README.org
+++ b/README.org
@@ -227,8 +227,6 @@ pyim 的选词框默认使用 *双行显示* 的样式,在一些特殊的情
 (setq pyim-page-style 'one-line)
 #+end_example
 
-注:用户可以添加函数 pyim-page-style:STYLENAME 来定义自己的选词框格式。
-
 ** 设置模糊音
 可以通过设置 `pyim-pinyin-fuzzy-alist' 变量来自定义模糊音。
 
diff --git a/pyim-autoselector.el b/pyim-autoselector.el
index 973d6b3a6f..74d183d7b1 100644
--- a/pyim-autoselector.el
+++ b/pyim-autoselector.el
@@ -40,13 +40,12 @@
 
 比如:五笔等型码输入法,重码率很低,90%以上的情况都是选择第一个词
 条,自动选择可以减少按空格强制选词的机会。"
-  (let* ((scheme-name (pyim-scheme-name))
-         (class (pyim-scheme-get-option scheme-name :class))
-         (n (pyim-scheme-get-option scheme-name :code-split-length))
+  (let* ((scheme (pyim-scheme-current))
+         (n (pyim-scheme-xingma-code-split-length scheme))
          (entered (pyim-process-get-entered 'point-before))
          (candidates (pyim-process-get-candidates))
          (last-candidates (pyim-process-get-last-candidates)))
-    (when (eq class 'xingma)
+    (when (pyim-scheme-xingma-p scheme)
       (cond
        ((and (= (length entered) n)
              (= (length candidates) 1)
@@ -64,6 +63,7 @@
         '(:select last))
        (t nil)))))
 
+(cl-pushnew #'pyim-autoselector-xingma pyim-process-autoselector)
 
 ;; * Footer
 (provide 'pyim-autoselector)
diff --git a/pyim-candidates.el b/pyim-candidates.el
index 0aa0321f8b..53440ed15e 100644
--- a/pyim-candidates.el
+++ b/pyim-candidates.el
@@ -50,13 +50,10 @@
   "上一轮备选词条列表,这个变量主要用于 autoselector 机制.")
 
 (defvar pyim-candidate-position nil
-  "当前选择的词条在 ‘pyim-candidates’ 中的位置.
+  "当前选择的词条在 `pyim-candidates’ 中的位置.
 
 细节信息请参考 `pyim-page-refresh' 的 docstring.")
 
-(defvar pyim-candidates-cloud-search-function nil
-  "`pyim-candidates-cloud-search' 调用的函数.")
-
 (pyim-register-local-variables
  '(pyim-candidates pyim-candidate-position))
 
@@ -65,181 +62,155 @@
   "对 CANDIDATES 进行排序。"
   (pyim-dcache-call-api 'sort-words candidates))
 
-(defun pyim-candidates-create (imobjs scheme-name &optional async)
-  "按照 SCHEME-NAME 对应的输入法方案, 从输入法内部对象列表:
-IMOBJS 获得候选词条。"
-  (when imobjs
-    (let ((class (pyim-scheme-get-option scheme-name :class)))
-      (when class
-        (funcall (intern (format "pyim-candidates-create:%S" class))
-                 imobjs scheme-name async)))))
-
-(defun pyim-candidates-get-chief (scheme-name &optional personal-words 
common-words)
-  "选取第一位候选词。"
-  (let ((class (pyim-scheme-get-option scheme-name :class)))
-    (cond ((equal class 'xingma)
-           (or
-            ;; 如果从公共词库里面获取到的第一个词条是汉字,就选择它。
-            (when (= (length (car common-words)) 1)
-              (car common-words))
-            ;; 从个人词库里面按排列的先后顺序,获取一个汉字。
-            (cl-find-if
-             (lambda (word)
-               (= (length word) 1))
-             personal-words)))
-          (t (or
-              ;; 最近输入的10个不同的词中出现一次以上。
-              (cl-find-if
-               (lambda (word)
-                 (> (or (car (pyim-dcache-get word 
'(iword2count-recent-10-words))) 0) 1))
-               personal-words)
-              ;; 最近输入的50个不同的词中出现过三次以上。
-              (cl-find-if
-               (lambda (word)
-                 (> (or (car (pyim-dcache-get word 
'(iword2count-recent-50-words))) 0) 3))
-               personal-words)
-              ;; 个人词条中的第一个词。
-              (car personal-words))))))
-
-(defun pyim-candidates-create:xingma (imobjs scheme-name &optional async)
-  "`pyim-candidates-create' 处理五笔仓颉等形码输入法的函数."
-  (unless async
-    (let (result)
-      (dolist (imobj imobjs)
-        (let* ((codes (pyim-codes-create imobj scheme-name))
-               (last-code (car (last codes)))
-               (other-codes (remove last-code codes))
-               output prefix)
-
-          ;; 如果 wubi/aaaa -> 工 㠭;wubi/bbbb -> 子 子子孙孙;wubi/cccc 又 叕;
-          ;; 用户输入为: aaaabbbbcccc
-
-          ;; 那么:
-          ;; 1. codes       =>   ("wubi/aaaa" "wubi/bbbb" "wubi/cccc")
-          ;; 2. last-code   =>   "wubi/cccc"
-          ;; 3. other-codes =>   ("wubi/aaaa" "wubi/bbbb")
-          ;; 4. prefix      =>   工子
-          (when other-codes
-            (setq prefix (mapconcat
-                          (lambda (code)
-                            (pyim-candidates-get-chief
-                             scheme-name
-                             (pyim-dcache-get code '(icode2word))
-                             (pyim-dcache-get code '(code2word))))
-                          other-codes "")))
-
-          ;; 5. output => 工子又 工子叕
-          (setq output
-                ;; NOTE: 下面这种策略是否合理?
-                ;; 1. 第一个词选择公共词库中的第一个词。
-                ;; 2. 剩下的分成常用字和词,常用字优先排,字和词各按 count 大小排序。
-                (let* ((personal-words (pyim-dcache-get last-code 
'(icode2word)))
-                       (personal-words (pyim-candidates-sort personal-words))
-                       (common-words (pyim-dcache-get last-code '(code2word)))
-                       (chief-word (pyim-candidates-get-chief scheme-name 
personal-words common-words))
-                       (common-words (pyim-candidates-sort common-words))
-                       (other-words (pyim-dcache-get last-code 
'(shortcode2word))))
-                  (mapcar (lambda (word)
-                            (concat prefix word))
-                          `(,chief-word
-                            ,@personal-words
-                            ,@common-words
-                            ,@other-words))))
-          (setq output (remove "" (or output (list prefix))))
-          (setq result (append result output))))
-      (when (car result)
-        (delete-dups result)))))
-
-(defun pyim-candidates-create:quanpin (imobjs scheme-name &optional async)
-  "`pyim-candidates-create' 处理全拼输入法的函数."
-  (if async
-      ;; 构建一个搜索中文的正则表达式, 然后使用这个正则表达式在当前 buffer 中搜
-      ;; 索词条。
-      (let ((str (string-join (pyim-codes-create (car imobjs) scheme-name))))
-        (if (< (length str) 1)
-            pyim-candidates
-          ;; NOTE: 让第一个词保持不变是不是合理,有待进一步的观察。
-          `(,(car pyim-candidates)
-            ,@(pyim-candidates-cloud-search str 'quanpin)
-            ,@(pyim-candidates-search-buffer
-               (pyim-cregexp-build str 3 t))
-            ,@(cdr pyim-candidates))))
-    ;; 这段代码主要实现以下功能:假如用户输入 nihaomazheshi, 但词库里面找不到对
-    ;; 应的词条,那么输入法自动用 nihaoma 和 zheshi 的第一个词条:"你好吗" 和 "
-    ;; 这是" 连接成一个新的字符串 "你好吗这是" 做为第一个候选词。
-    (let* ((candidates (pyim-candidates-create-quanpin imobjs scheme-name))
-           (n (length (car candidates)))
-           output)
-      (push (car candidates) output)
-      (while (and (> n 0)
-                  (car (setq imobjs
-                             (mapcar (lambda (imobj)
-                                       (nthcdr n imobj))
-                                     imobjs))))
-        (let ((candidates (pyim-candidates-create-quanpin imobjs scheme-name)))
-          (push (car (pyim-candidates-create-quanpin imobjs scheme-name t)) 
output)
-          (setq n (length (car candidates)))))
-      (append (pyim-subconcat (nreverse output) "")
-              candidates))))
-
-(defun pyim-candidates-cloud-search (string scheme-name)
-  "云搜索 STRING, 返回候选词条列表."
-  (ignore-errors
-    (funcall pyim-candidates-cloud-search-function string scheme-name)))
-
-(defun pyim-candidates-search-buffer (regexp)
-  "在当前 buffer 中使用 REGEXP 搜索词条。"
-  (when (not (input-pending-p)) ;只有在用户输入停顿的时候才搜索 buffer.
-    (save-excursion
-      (let ((counts (make-hash-table :test #'equal))
-            (time-limit 0.1)
-            words)
-        (goto-char (point-min))
-        (pyim-time-limit-while (and (not (input-pending-p)) ;如果用户继续输入,就停止 
buffer 搜索。
-                                    (re-search-forward regexp nil t)) 
time-limit
-          (let* ((match (match-string-no-properties 0))
-                 (word (propertize match :comment "(Buf)")))
-            ;; NOTE: 单个汉字我觉得不值得收集。
-            (when (>= (length word) 2)
-              (if (member word words)
-                  (cl-incf (gethash word counts))
-                (push word words)
-                (puthash word 1 counts)))))
-        (sort words (lambda (a b)
-                      (> (or (gethash a counts) 0)
-                         (or (gethash b counts) 0))))))))
-
-(defun pyim-candidates-create-quanpin (imobjs scheme-name &optional 
fast-search)
-  "`pyim-candidates-create:quanpin' 内部使用的函数。"
-  (let (;; Let indent beautiful.
-        jianpin-words znabc-words
-        personal-words common-words
-        pinyin-chars-1 pinyin-chars-2
-        chief-word)
-    ;; 智能ABC模式,得到尽可能的拼音组合,查询这些组合,得到的词条做为联想词。
-    (let ((codes (mapcar (lambda (x)
-                           (pyim-subconcat x "-"))
-                         (mapcar (lambda (imobj)
-                                   (pyim-codes-create imobj scheme-name))
-                                 imobjs))))
-      (setq znabc-words
-            (pyim-zip (mapcar #'pyim-dcache-get
-                              (pyim-zip codes))
-                      fast-search)))
-
-    ;; 假如输入 "nih" ,那么搜索 code 为 "n-h" 的词条,然后筛选出所有拼音匹配
-    ;; "ni-h" 或者 "ni[^-]*-h" 的词条。
-    (when (and pyim-enable-shortcode
-               (> (length (car imobjs)) 1))
+(cl-defgeneric pyim-candidates-get-chief (scheme &optional personal-words 
common-words)
+  "PYIM 输入法第一位候选词的获取策略。")
+
+(cl-defmethod pyim-candidates-get-chief ((_scheme pyim-scheme-xingma)
+                                         &optional personal-words common-words)
+  "五笔仓颉等形码输入法第一位候选词的选择策略。"
+  (or
+   ;; 如果从公共词库里面获取到的第一个词条是汉字,就选择它。
+   (when (= (length (car common-words)) 1)
+     (car common-words))
+   ;; 从个人词库里面按排列的先后顺序,获取一个汉字。
+   (cl-find-if
+    (lambda (word)
+      (= (length word) 1))
+    personal-words)))
+
+(cl-defmethod pyim-candidates-get-chief ((_scheme pyim-scheme-quanpin)
+                                         &optional personal-words 
_common-words)
+  "PYIM 输入法第一位候选词的获取通用策略。"
+  (or
+   ;; 最近输入的10个不同的词中出现一次以上。
+   (cl-find-if
+    (lambda (word)
+      (> (or (car (pyim-dcache-get word '(iword2count-recent-10-words))) 0) 1))
+    personal-words)
+   ;; 最近输入的50个不同的词中出现过三次以上。
+   (cl-find-if
+    (lambda (word)
+      (> (or (car (pyim-dcache-get word '(iword2count-recent-50-words))) 0) 3))
+    personal-words)
+   ;; 个人词条中的第一个词。
+   (car personal-words)))
+
+(cl-defgeneric pyim-candidates-create (imobjs scheme)
+  "按照 SCHEME, 从 IMOBJS 获得候选词条。")
+
+(cl-defmethod pyim-candidates-create (imobjs (scheme pyim-scheme-xingma))
+  "按照 SCHEME, 从 IMOBJS 获得候选词条,用于五笔仓颉等形码输入法。"
+  (let (result)
+    (dolist (imobj imobjs)
+      (let* ((codes (pyim-codes-create imobj scheme))
+             (last-code (car (last codes)))
+             (other-codes (remove last-code codes))
+             output prefix)
+
+        ;; 如果 wubi/aaaa -> 工 㠭;wubi/bbbb -> 子 子子孙孙;wubi/cccc 又 叕;
+        ;; 用户输入为: aaaabbbbcccc
+
+        ;; 那么:
+        ;; 1. codes       =>   ("wubi/aaaa" "wubi/bbbb" "wubi/cccc")
+        ;; 2. last-code   =>   "wubi/cccc"
+        ;; 3. other-codes =>   ("wubi/aaaa" "wubi/bbbb")
+        ;; 4. prefix      =>   工子
+        (when other-codes
+          (setq prefix (mapconcat
+                        (lambda (code)
+                          (pyim-candidates-get-chief
+                           scheme
+                           (pyim-dcache-get code '(icode2word))
+                           (pyim-dcache-get code '(code2word))))
+                        other-codes "")))
+
+        ;; 5. output => 工子又 工子叕
+        (setq output
+              (let* ((personal-words (pyim-dcache-get last-code '(icode2word)))
+                     (personal-words (pyim-candidates-sort personal-words))
+                     (common-words (pyim-dcache-get last-code '(code2word)))
+                     (chief-word (pyim-candidates-get-chief scheme 
personal-words common-words))
+                     (common-words (pyim-candidates-sort common-words))
+                     (other-words (pyim-dcache-get last-code 
'(shortcode2word))))
+                (mapcar (lambda (word)
+                          (concat prefix word))
+                        `(,chief-word
+                          ,@personal-words
+                          ,@common-words
+                          ,@other-words))))
+        (setq output (remove "" (or output (list prefix))))
+        (setq result (append result output))))
+    (when (car result)
+      (delete-dups result))))
+
+(cl-defmethod pyim-candidates-create (imobjs (scheme pyim-scheme-quanpin))
+  "按照 SCHEME, 从 IMOBJS 获得候选词条,用于全拼输入法。"
+  ;; 这段代码主要实现以下功能:假如用户输入 nihaomazheshi, 但词库里面找不到对
+  ;; 应的词条,那么输入法自动用 nihaoma 和 zheshi 的第一个词条:"你好吗" 和 "
+  ;; 这是" 连接成一个新的字符串 "你好吗这是" 做为第一个候选词。
+  (let* ((candidates (pyim-candidates-quanpin imobjs scheme))
+         (n (length (car candidates)))
+         output)
+    (push (car candidates) output)
+    (while (and (> n 0)
+                (car (setq imobjs
+                           (mapcar (lambda (imobj)
+                                     (nthcdr n imobj))
+                                   imobjs))))
+      (let ((candidates (pyim-candidates-quanpin imobjs scheme)))
+        (push (car (pyim-candidates-quanpin imobjs scheme t)) output)
+        (setq n (length (car candidates)))))
+    (append (pyim-subconcat (nreverse output) "")
+            candidates)))
+
+(defun pyim-candidates-quanpin (imobjs scheme &optional fast-search)
+  "用于全拼输入法的 `pyim-candidates-create' 方法内部使用的函数。"
+  (let* ((znabc-words (pyim-candidates-znabc-words imobjs scheme fast-search))
+         (jianpin-words (pyim-candidates-jianpin-words imobjs scheme 
fast-search))
+         (dcache-words (pyim-candidates-dcache-quanpin-words imobjs scheme 
fast-search))
+         (personal-words (pyim-candidates-sort (nth 0 dcache-words)))
+         (chief-word (pyim-candidates-get-chief scheme personal-words))
+         (common-words (nth 1 dcache-words))
+         (pinyin-chars-1 (nth 2 dcache-words))
+         (pinyin-chars-2 (nth 3 dcache-words))
+         (words `( :chief-word ,chief-word
+                   :personal-words ,@personal-words
+                   :jianpin-words ,@jianpin-words
+                   :common-words ,@common-words
+                   :znabc-words ,@znabc-words
+                   :pinyin-chars-1 ,@pinyin-chars-1
+                   :pinyin-chars-2 ,@pinyin-chars-2)))
+    (when pyim-debug (print words))
+    (delete-dups (cl-remove-if-not #'stringp words))))
+
+(defun pyim-candidates-znabc-words (imobjs scheme &optional fast-search)
+  "智能ABC模式,得到尽可能的拼音组合,查询这些组合,得到的词条做为联想词。"
+  (let ((codes (mapcar (lambda (x)
+                         (pyim-subconcat x "-"))
+                       (mapcar (lambda (imobj)
+                                 (pyim-codes-create imobj scheme))
+                               imobjs))))
+    (pyim-zip (mapcar #'pyim-dcache-get
+                      (pyim-zip codes))
+              fast-search)))
+
+(defun pyim-candidates-jianpin-words (imobjs scheme &optional fast-search)
+  "获取简拼词语。
+
+ 假如输入 \"nih\" ,那么搜索 code 为 \"n-h\" 的词条,然后筛选出所
+ 有拼音匹配\"ni-h\" 或者 \"ni[^-]*-h\" 的词条。"
+  (when (and pyim-enable-shortcode
+             (> (length (car imobjs)) 1))
+    (let (jianpin-words)
       (dolist (imobj imobjs)
         (let* ((w (pyim-dcache-get
-                   (string-join (pyim-codes-create imobj scheme-name 1) "-")
+                   (string-join (pyim-codes-create imobj scheme 1) "-")
                    '(ishortcode2word)))
                (regexp1 (string-join
-                         (pyim-codes-create imobj scheme-name)
+                         (pyim-codes-create imobj scheme)
                          "-"))
                (regexp2 (string-join
-                         (pyim-codes-create imobj scheme-name)
+                         (pyim-codes-create imobj scheme)
                          "[^-]*-"))
                (w1 (cl-remove-if-not
                     (lambda (cstr)
@@ -251,97 +222,118 @@ IMOBJS 获得候选词条。"
                     (lambda (cstr)
                       (string-match-p regexp1 (pyim-cstring-to-pinyin cstr nil 
"-")))
                     w1)))
-          (push (append w2 w1) jianpin-words))))
+          (push (delete-dups (append w2 w1)) jianpin-words)))
+      (pyim-zip (nreverse jianpin-words) fast-search))))
 
-    ;; 获取个人词条,词库词条和第一汉字列表。
+(defun pyim-candidates-dcache-quanpin-words (imobjs scheme &optional 
fast-search pymap-chars-num)
+  "从 dcache 获取个人词条,词库词条和第一汉字列表。"
+  (let (personal-words common-words pinyin-chars-1 pinyin-chars-2)
     (dolist (imobj imobjs)
-      (let* (;; 个人词条
-             (w1 (pyim-dcache-get
-                  (string-join (pyim-codes-create imobj scheme-name) "-")
-                  (if pyim-enable-shortcode
-                      '(icode2word ishortcode2word)
-                    '(icode2word))))
-             ;; 词库词条
-             (w2 (pyim-dcache-get
-                  (string-join (pyim-codes-create imobj scheme-name) "-")
-                  (if pyim-enable-shortcode
-                      '(code2word shortcode2word)
-                    '(code2word))))
+      (let* ((w1 (pyim-candidates-quanpin-personal-words imobj scheme))
+             (w2 (pyim-candidates-quanpin-common-words imobj scheme))
              ;; 第一个汉字
-             (w3 (pyim-dcache-get
-                  (car (pyim-codes-create imobj scheme-name))))
+             (w3 (pyim-candidates-quanpin-chars imobj scheme))
              ;; 如果 w3 找不到第一个拼音对应的汉字,那就进一步使用
              ;; `pyim-pymap-py2cchar-get' 来查找,这个函数支持声母搜索。可以得到
              ;; 更多的词条。
              (w4 (unless w3
-                   (mapcar #'char-to-string
-                           (pyim-zip
-                            (mapcar (lambda (x)
-                                      ;; NOTE: 
这里只取最常用的汉字,太多的汉字会带来后续处理压力,可能拖慢输入法。不过
-                                      ;; 这个结论只是猜测。
-                                      (car (split-string x "|")))
-                                    (pyim-pymap-py2cchar-get
-                                     (car (pyim-codes-create imobj 
scheme-name)))))))))
+                   (pyim-candidates-pymap-chars
+                    (car (pyim-codes-create imobj scheme)) pymap-chars-num))))
         (push w1 personal-words)
         (push w2 common-words)
         (push w3 pinyin-chars-1)
         (push w4 pinyin-chars-2)))
-
-    (setq jianpin-words (pyim-zip (nreverse jianpin-words) fast-search))
     (setq personal-words (pyim-zip (nreverse personal-words) fast-search))
     (setq common-words (pyim-zip (nreverse common-words) fast-search))
     (setq pinyin-chars-1 (pyim-zip (nreverse pinyin-chars-1) fast-search))
     (setq pinyin-chars-2 (pyim-zip (nreverse pinyin-chars-2) fast-search))
+    (list personal-words common-words pinyin-chars-1 pinyin-chars-2)))
+
+(defun pyim-candidates-quanpin-personal-words (imobj scheme)
+  (pyim-dcache-get
+   (string-join (pyim-codes-create imobj scheme) "-")
+   (if pyim-enable-shortcode
+       '(icode2word ishortcode2word)
+     '(icode2word))))
+
+(defun pyim-candidates-quanpin-common-words (imobj scheme)
+  (pyim-dcache-get
+   (string-join (pyim-codes-create imobj scheme) "-")
+   (if pyim-enable-shortcode
+       '(code2word shortcode2word)
+     '(code2word))))
+
+(defun pyim-candidates-quanpin-chars (imobj scheme)
+  (pyim-dcache-get
+   (car (pyim-codes-create imobj scheme))
+   '(icode2word code2word)))
+
+(defun pyim-candidates-pymap-chars (pinyin &optional num)
+  "从 pymap 表获取汉字。"
+  (let ((chars (mapcar #'char-to-string
+                       (pyim-zip
+                        (mapcar (lambda (x)
+                                  ;; NOTE: 这里只取最常用的汉字,太多的汉字会带
+                                  ;; 来后续处理压力,可能拖慢输入法。不过这个结
+                                  ;; 论只是猜测。
+                                  (car (split-string x "|")))
+                                (pyim-pymap-py2cchar-get pinyin))))))
+    (cl-subseq chars 0 num)))
+
+(cl-defgeneric pyim-candidates-create-async (imobjs scheme)
+  "按照 SCHEME, 使用异步的方式从 IMOBJS 获得候选词条。")
+
+(cl-defmethod pyim-candidates-create-async (_imobjs _scheme)
+  "按照 SCHEME, 使用异步的方式从 IMOBJS 获得候选词条。"
+  nil)
+
+(cl-defmethod pyim-candidates-create-async (imobjs (scheme 
pyim-scheme-quanpin))
+  "按照 SCHEME, 用异步的方式从 IMOBJS 获得候选词条,用于全拼输入法。"
+  ;; 构建一个搜索中文的正则表达式, 然后使用这个正则表达式在当前 buffer 中搜
+  ;; 索词条。
+  (let ((str (string-join (pyim-codes-create (car imobjs) scheme))))
+    (if (< (length str) 1)
+        pyim-candidates
+      ;; NOTE: 让第一个词保持不变是不是合理,有待进一步的观察。
+      `(,(car pyim-candidates)
+        ,@(pyim-candidates-cloud-search str scheme)
+        ,@(pyim-candidates-search-buffer
+           (pyim-cregexp-build str 3 t))
+        ,@(cdr pyim-candidates)))))
+
+(cl-defgeneric pyim-candidates-cloud-search (string scheme)
+  "云搜索 STRING, 返回候选词条列表.")
+
+(cl-defmethod pyim-candidates-cloud-search (_string _scheme)
+  "云搜索 STRING, 返回候选词条列表."
+  nil)
+
+(defun pyim-candidates-search-buffer (regexp)
+  "在当前 buffer 中使用 REGEXP 搜索词条。"
+  (when (not (input-pending-p)) ;只有在用户输入停顿的时候才搜索 buffer.
+    (save-excursion
+      (let ((counts (make-hash-table :test #'equal))
+            (time-limit 0.1)
+            words)
+        (goto-char (point-min))
+        (pyim-time-limit-while (and (not (input-pending-p)) ;如果用户继续输入,就停止 
buffer 搜索。
+                                    (re-search-forward regexp nil t)) 
time-limit
+          (let* ((match (match-string-no-properties 0))
+                 (word (propertize match :comment "(buf)")))
+            ;; NOTE: 单个汉字我觉得不值得收集。
+            (when (>= (length word) 2)
+              (if (member word words)
+                  (cl-incf (gethash word counts))
+                (push word words)
+                (puthash word 1 counts)))))
+        (sort words (lambda (a b)
+                      (> (or (gethash a counts) 0)
+                         (or (gethash b counts) 0))))))))
 
-    ;; 个人词条排序:使用词频信息对个人词库得到的候选词排序,第一个词条的位置
-    ;; 比较特殊,不参与排序,具体原因请参考 `pyim-page-select-word' 中的
-    ;; comment.
-    (setq personal-words (pyim-candidates-sort personal-words))
-    (setq chief-word (pyim-candidates-get-chief scheme-name personal-words))
-
-    ;; 调试输出
-    (when pyim-debug
-      (print (list :imobjs imobjs
-                   :chief-word chief-word
-                   :personal-words personal-words
-                   :common-words common-words
-                   :jianpin-words jianpin-words
-                   :znabc-words znabc-words
-                   :pinyin-chars-1
-                   (cl-subseq pinyin-chars-1
-                              0 (min (length pinyin-chars-1) 5))
-                   :pinyin-chars-2
-                   (cl-subseq pinyin-chars-2
-                              0 (min (length pinyin-chars-2) 5)))))
-
-    (delete-dups
-     (delq nil
-           `(,chief-word
-             ,@personal-words
-             ,@jianpin-words
-             ,@common-words
-             ,@znabc-words
-             ,@pinyin-chars-1
-             ,@pinyin-chars-2
-             )))))
-
-(defun pyim-candidates-create:shuangpin (imobjs scheme-name &optional async)
-  "`pyim-candidates-create' 处理双拼输入法的函数."
-  (if async
-      ;; 构建一个搜索中文的正则表达式, 然后使用这个正则表达式在当前 buffer 中搜
-      ;; 索词条。
-      (let ((str (string-join (pyim-codes-create (car imobjs) scheme-name))))
-        (if (< (length str) 1)
-            pyim-candidates
-          ;; NOTE: 让第一个词保持不变是不是合理,有待进一步的观察。
-          `(,(car pyim-candidates)
-            ,@(pyim-candidates-search-buffer
-               ;; 按照 pyim 的内部设计,这里得到的 str 其实是全拼,所以要按照全
-               ;; 拼的规则来生成 cregexp.
-               (let ((pyim-default-scheme 'quanpin))
-                 (pyim-cregexp-build str 3 t)))
-            ,@(cdr pyim-candidates))))
-    (pyim-candidates-create:quanpin imobjs 'quanpin async)))
+(cl-defmethod pyim-candidates-create-async (_imobjs (_scheme 
pyim-scheme-shuangpin))
+  "按照 SCHEME, 用异步的方式从 IMOBJS 获得候选词条,用于双拼输入法。"
+  (let ((pyim-default-scheme 'quanpin))
+    (cl-call-next-method)))
 
 ;; * Footer
 (provide 'pyim-candidates)
diff --git a/pyim-cloudim.el b/pyim-cloudim.el
index b08c573a45..e9e1192102 100644
--- a/pyim-cloudim.el
+++ b/pyim-cloudim.el
@@ -42,26 +42,17 @@
           (const :tag "Use baidu cloud input method." baidu)
           (const :tag "Use google cloud input method." google)))
 
-(defun pyim-cloudim (string scheme-name)
-  "使用云输入法引擎搜索 STRING 获取词条列表.
-云输入法由 `pyim-cloudim' 设置。"
-  (when (and pyim-cloudim (symbolp pyim-cloudim))
-    (let ((func (intern (format "pyim-cloudim:%s" pyim-cloudim))))
-      (when (functionp func)
-        (funcall func string scheme-name)))))
-
-(setq pyim-candidates-cloud-search-function #'pyim-cloudim)
-
-(defun pyim-cloudim:baidu (string scheme-name)
+(cl-defmethod pyim-candidates-cloud-search
+  (string (_scheme pyim-scheme-quanpin)
+          &context (pyim-cloudim (eql baidu)))
   "使用 baidu 云输入法引擎搜索 STRING, 获取词条列表。"
-  (when (equal scheme-name 'quanpin)
-    (let ((buffer (pyim-cloudim-url-retrieve-sync
-                   (format "https://olime.baidu.com/py?py=%s"; string)
-                   t nil 0.2)))
-      (when (bufferp buffer)
-        (with-current-buffer buffer
-          (prog1 (pyim-cloudim-parse-baidu-buffer)
-            (kill-buffer)))))))
+  (let ((buffer (pyim-cloudim-url-retrieve-sync
+                 (format "https://olime.baidu.com/py?py=%s"; string)
+                 t nil 0.2)))
+    (when (bufferp buffer)
+      (with-current-buffer buffer
+        (prog1 (pyim-cloudim-parse-baidu-buffer)
+          (kill-buffer))))))
 
 (defun pyim-cloudim-url-retrieve-sync (url &optional silent inhibit-cookies 
timeout)
   "Pyim 版本的 `url-retrieve-synchronously'.
@@ -134,16 +125,17 @@
     (when (> (length word) 0)
       (list (propertize word :comment "(云)")))))
 
-(defun pyim-cloudim:google (string scheme-name)
+(cl-defmethod pyim-candidates-cloud-search
+  (string (_scheme pyim-scheme-quanpin)
+          &context (pyim-cloudim (eql google)))
   "使用 google 云输入法引擎搜索 STRING, 获取词条列表。"
-  (when (eq scheme-name 'quanpin)
-    (let ((buffer (pyim-cloudim-url-retrieve-sync
-                   (format 
"https://www.google.cn/inputtools/request?ime=pinyin&text=%s"; string)
-                   t nil 0.2)))
-      (when (bufferp buffer)
-        (with-current-buffer buffer
-          (prog1 (pyim-cloudim-parse-google-buffer)
-            (kill-buffer)))))))
+  (let ((buffer (pyim-cloudim-url-retrieve-sync
+                 (format 
"https://www.google.cn/inputtools/request?ime=pinyin&text=%s"; string)
+                 t nil 0.3)))
+    (when (bufferp buffer)
+      (with-current-buffer buffer
+        (prog1 (pyim-cloudim-parse-google-buffer)
+          (kill-buffer))))))
 
 (defun pyim-cloudim-parse-google-buffer ()
   "解析 `pyim-cloudim-url-retrieve-sync' 返回的 google buffer."
diff --git a/pyim-codes.el b/pyim-codes.el
index a7476b3bff..7c0bc0a277 100644
--- a/pyim-codes.el
+++ b/pyim-codes.el
@@ -32,25 +32,23 @@
 (require 'pyim-imobjs)
 (require 'pyim-dcache)
 
-(defun pyim-codes-create (imobj scheme-name &optional first-n)
-  "按照 SCHEME-NAME 对应的输入法方案,从一个 IMOBJ 创建一个列表 codes, 这个列表
-包含一个或者多个 code 字符串,这些 code 字符串用于从词库中搜索词条."
-  (let ((class (pyim-scheme-get-option scheme-name :class)))
-    (when class
-      (funcall (intern (format "pyim-codes-create:%S" class))
-               imobj scheme-name first-n))))
-
-(defun pyim-codes-create:quanpin (imobj _scheme-name &optional first-n)
+(cl-defgeneric pyim-codes-create (imobj scheme &optional first-n)
+  "按照 SCHEME 对应的输入法方案,从一个 IMOBJ 创建一个列表 codes.
+
+这个列表包含一个或者多个 code 字符串,这些 code 字符串用于从词库
+中搜索词条.")
+
+(cl-defmethod pyim-codes-create (imobj (_scheme pyim-scheme-quanpin) &optional 
first-n)
   "从IMOBJ 创建一个 code 列表:codes.
 
 列表 codes 中包含一个或者多个 code 字符串,这些 code 字符串用于从
 词库中搜索相关词条。
 
-    (pyim-codes-create '((\"w\" \"o\" \"w\" \"o\")
-                         (\"\" \"ai\" \"\" \"ai\")
-                         (\"m\" \"ei\" \"m\" \"ei\")
-                         (\"n\"  \"v\" \"n\"  \"v\"))
-                       'quanpin)
+    (pyim-codes-create (quote ((\"w\" \"o\" \"w\" \"o\")
+                               (\"\" \"ai\" \"\" \"ai\")
+                               (\"m\" \"ei\" \"m\" \"ei\")
+                               (\"n\"  \"v\" \"n\"  \"v\")))
+                       (pyim-scheme-get (quote quanpin)))
 
 结果为:
 
@@ -64,12 +62,10 @@
          py)))
    imobj))
 
-(defun pyim-codes-create:shuangpin (imobj _scheme-name &optional first-n)
-  (pyim-codes-create:quanpin imobj 'quanpin first-n))
-
-(defun pyim-codes-create:xingma (imobj scheme-name &optional first-n)
-  (when scheme-name
-    (let ((code-prefix (pyim-scheme-get-option scheme-name :code-prefix)))
+(cl-defmethod pyim-codes-create (imobj (scheme pyim-scheme-xingma) &optional 
first-n)
+  "用于处理形码输入法的 `pyim-codes-create' 方法。"
+  (when scheme
+    (let ((code-prefix (pyim-scheme-code-prefix scheme)))
       (mapcar
        (lambda (x)
          (concat (or code-prefix "")
diff --git a/pyim-cregexp.el b/pyim-cregexp.el
index 540f2ca513..9140b94ade 100644
--- a/pyim-cregexp.el
+++ b/pyim-cregexp.el
@@ -111,9 +111,8 @@ regexp, 所以搜索单字的时候一般可以搜到生僻字,但搜索句子
 
 (defun pyim-cregexp-build-1 (str &optional char-level-num chinese-only)
   (let* ((num (pyim-cregexp-char-level-num char-level-num))
-         (scheme-name (pyim-scheme-name))
-         (class (pyim-scheme-get-option scheme-name :class))
-         (code-prefix (pyim-scheme-get-option scheme-name :code-prefix))
+         (scheme (pyim-scheme-current))
+         (code-prefix (pyim-scheme-code-prefix scheme))
          (sep "#####&&&&#####")
          (lst (remove "" (split-string
                           (replace-regexp-in-string
@@ -121,22 +120,23 @@ regexp, 所以搜索单字的时候一般可以搜到生僻字,但搜索句子
                           sep))))
     ;; 确保 pyim 词库加载
     (pyim-dcache-init-variables)
-    ;; pyim 暂时只支持全拼和双拼搜索
-    (when (not (member class '(quanpin shuangpin xingma)))
-      (setq scheme-name pyim-cregexp-fallback-scheme))
+    (unless (or (pyim-scheme-quanpin-p scheme)
+                (pyim-scheme-shuangpin-p scheme)
+                (pyim-scheme-xingma-p scheme))
+      (setq scheme (pyim-scheme-get pyim-cregexp-fallback-scheme)))
     (mapconcat
      (lambda (string)
        (if (or (pyim-string-match-p "[^a-z']+" string)
                (equal string ""))
            string
          (let* ((string1 (replace-regexp-in-string "'" "" string))
-                (imobjs (pyim-imobjs-create string1 scheme-name))
+                (imobjs (pyim-imobjs-create string1 scheme))
                 (regexp-list
                  (mapcar
                   (lambda (imobj)
-                    (if (eq class 'xingma)
-                        (pyim-cregexp-build:xingma imobj nil nil nil 
code-prefix)
-                      (pyim-cregexp-build:quanpin imobj nil nil nil num)))
+                    (if (pyim-scheme-xingma-p scheme)
+                        (pyim-cregexp-build-xingma imobj nil nil nil 
code-prefix)
+                      (pyim-cregexp-build-quanpin imobj nil nil nil num)))
                   imobjs))
                 (regexp
                  (when regexp-list
@@ -152,7 +152,7 @@ regexp, 所以搜索单字的时候一般可以搜到生僻字,但搜索句子
            (format "\\(?:%s\\)" regexp))))
      lst "")))
 
-(defun pyim-cregexp-build:quanpin (imobj &optional match-beginning
+(defun pyim-cregexp-build-quanpin (imobj &optional match-beginning
                                          first-equal all-equal char-level-num)
   "从 IMOBJ 创建一个搜索中文的 regexp."
   (let* ((num (pyim-cregexp-char-level-num char-level-num))
@@ -184,7 +184,7 @@ regexp, 所以搜索单字的时候一般可以搜到生僻字,但搜索句子
     (unless (equal regexp "")
       (concat (if match-beginning "^" "") regexp))))
 
-(defun pyim-cregexp-build:xingma (imobj &optional match-beginning
+(defun pyim-cregexp-build-xingma (imobj &optional match-beginning
                                         first-equal _all-equal code-prefix)
   "从 IMOBJ 创建一个搜索中文的 regexp."
   (cl-flet ((build-regexp
@@ -209,7 +209,7 @@ regexp, 所以搜索单字的时候一般可以搜到生僻字,但搜索句子
                                          (if first-equal
                                              (substring x 0 1)
                                            x))))
-                       (build-regexp (pyim-dcache-get code))))
+                       (build-regexp (pyim-dcache-get code '(code2word)))))
                    imobj "")))
       (unless (equal regexp "")
         (concat (if match-beginning "^" "") regexp)))))
diff --git a/pyim-cstring.el b/pyim-cstring.el
index a392c58aa7..c90b78afec 100644
--- a/pyim-cstring.el
+++ b/pyim-cstring.el
@@ -112,7 +112,7 @@ ADJUST-DUO-YIN-Zi 设置为 t 时, `pyim-cstring-to-pinyin' 会使用 
pyim 已
 2. 多音字校正速度比较慢,实时转换会产生卡顿。
 
 BUG: 当 STRING 中包含其它标点符号,并且设置 SEPERATER 时,结果会
-包含多余的连接符:比如: '你=好' --> 'ni-=-hao'"
+包含多余的连接符:比如: \"你=好\" --> \"ni-=-hao\""
   (if (not (pyim-string-match-p "\\cc" string))
       (if return-list
           (list string)
@@ -167,85 +167,103 @@ BUG: 当 STRING 中包含其它标点符号,并且设置 SEPERATER 时,结
   (pyim-cstring-to-pinyin string shou-zi-mu separator return-list t))
 
 ;; ** 中文字符串到形码的转换工具
-(defun pyim-cstring-to-xingma (string scheme-name &optional return-list)
-  "返回汉字 STRING 对应形码方案 SCHEME-NAME 的 code (不包括
-code-prefix)。当RETURN-LIST 设置为 t 时,返回一个 code list。"
+(cl-defgeneric pyim-cstring-to-xingma (string scheme &optional return-list)
+  "将中文 STRING 转换为 SCHEME 方案对应的形码。")
+
+(cl-defmethod pyim-cstring-to-xingma (string (scheme pyim-scheme-xingma)
+                                             &optional return-list)
+  "将中文 STRING 转换为 SCHEME 方案对应的形码。
+
+返回的形码不包括 code-prefix。当 RETURN-LIST 设置为 t 时,返回一
+个形码 list。"
   (when (string-match-p "^\\cc+\\'" string)
-    (let* ((prefix (pyim-scheme-get-option scheme-name :code-prefix))
-           (func (intern (concat "pyim-cstring-to-xingma:" (symbol-name 
scheme-name))))
-           (dcache-codes (mapcar (lambda (x)
-                                   (when (string-prefix-p prefix x)
-                                     (string-remove-prefix prefix x)))
-                                 (sort (cl-copy-list (pyim-dcache-call-api 
'search-word-code string))
-                                       (lambda (a b) (> (length a) (length 
b))))))
-           (codes (or (remove nil dcache-codes)
-                      (and (functionp func)
-                           (funcall func string scheme-name)))))
+    (let* ((prefix (pyim-scheme-code-prefix scheme))
+           (dcache-codes
+            (mapcar (lambda (x)
+                      (when (string-prefix-p prefix x)
+                        (string-remove-prefix prefix x)))
+                    (sort (cl-copy-list (pyim-dcache-call-api 
'search-word-code string))
+                          (lambda (a b)
+                            (> (length a) (length b))))))
+           (codes (remove nil dcache-codes)))
       (when codes
         (if return-list
             codes
           ;; 如果要返回一个字符串,那就返回第一个,也就是最长的那个编码。
           (car codes))))))
 
-(defun pyim-cstring-to-xingma:wubi (string &optional scheme-name)
-  "返回汉字 STRING 的五笔编码 (不包括 code-prefix) 编码列表。"
-  (let ((length (length string))
-        (string (split-string string "" t)))
-    (cond
-     ;; 双字词,分别取两个字的前两个编码
-     ((eq length 2)
-      (let ((s1 (pyim-cstring-to-xingma (nth 0 string) scheme-name))
-            (s2 (pyim-cstring-to-xingma (nth 1 string) scheme-name)))
-        (when (and s1 s2)
-          (list (concat (substring s1 0 2)
-                        (substring s2 0 2))))))
-     ;; 三字词,取前二字的首编码,及第三个字的前两个编码
-     ((eq length 3)
-      (let ((s1 (pyim-cstring-to-xingma (nth 0 string) scheme-name))
-            (s2 (pyim-cstring-to-xingma (nth 1 string) scheme-name))
-            (s3 (pyim-cstring-to-xingma (nth 2 string) scheme-name)))
-        (when (and s1 s2 s3)
-          (list (concat (substring s1 0 1)
-                        (substring s2 0 1)
-                        (substring s3 0 2))))))
-     ;; 四字词及以上,分别前三个字及最后一个字的首编码
-     ((> length 3)
-      (let ((s1 (pyim-cstring-to-xingma (nth 0 string) scheme-name))
-            (s2 (pyim-cstring-to-xingma (nth 1 string) scheme-name))
-            (s3 (pyim-cstring-to-xingma (nth 2 string) scheme-name))
-            (s4 (pyim-cstring-to-xingma (nth (1- length) string) scheme-name)))
-        (when (and s1 s2 s3 s4)
-          (list (concat (substring s1 0 1)
-                        (substring s2 0 1)
-                        (substring s3 0 1)
-                        (substring s4 0 1))))))
-     (t nil))))
-
-(defun pyim-cstring-to-codes (string scheme-name &optional criteria)
-  "将 STRING 转换为 SCHEME-NAME 对应的 codes.
-
-当 pyim class 为拼音,并且提供 CRITERIA 字符串时,检索到的所有
-codes 会和这个字符串进行比较,然后选择一个相似度最高的 code 作为
-输出,这种处理方式适合拼音输入法,形码输入法一般不需要类似的操作。
+(cl-defmethod pyim-cstring-to-xingma (string (scheme pyim-scheme-wubi)
+                                             &optional return-list)
+  "将中文 STRING 转换为五笔编码。
+
+得到的五笔编码包括 code-prefix。当 RETURN-LIST 设置为 t 时,返回
+一个五笔编码 list。"
+  (or (cl-call-next-method)
+      (let ((length (length string))
+            (string (split-string string "" t))
+            code)
+        (cond
+         ;; 双字词,分别取两个字的前两个编码
+         ((eq length 2)
+          (let ((s1 (pyim-cstring-to-xingma (nth 0 string) scheme))
+                (s2 (pyim-cstring-to-xingma (nth 1 string) scheme)))
+            (when (and s1 s2)
+              (setq code (concat (substring s1 0 2)
+                                 (substring s2 0 2))))))
+         ;; 三字词,取前二字的首编码,及第三个字的前两个编码
+         ((eq length 3)
+          (let ((s1 (pyim-cstring-to-xingma (nth 0 string) scheme))
+                (s2 (pyim-cstring-to-xingma (nth 1 string) scheme))
+                (s3 (pyim-cstring-to-xingma (nth 2 string) scheme)))
+            (when (and s1 s2 s3)
+              (setq code (concat (substring s1 0 1)
+                                 (substring s2 0 1)
+                                 (substring s3 0 2))))))
+         ;; 四字词及以上,分别前三个字及最后一个字的首编码
+         ((> length 3)
+          (let ((s1 (pyim-cstring-to-xingma (nth 0 string) scheme))
+                (s2 (pyim-cstring-to-xingma (nth 1 string) scheme))
+                (s3 (pyim-cstring-to-xingma (nth 2 string) scheme))
+                (s4 (pyim-cstring-to-xingma (nth (1- length) string) scheme)))
+            (when (and s1 s2 s3 s4)
+              (setq code (concat (substring s1 0 1)
+                                 (substring s2 0 1)
+                                 (substring s3 0 1)
+                                 (substring s4 0 1))))))
+         (t nil))
+        (when code
+          (if return-list
+              (list code)
+            code)))))
+
+(cl-defgeneric pyim-cstring-to-codes (string scheme &optional criteria)
+  "将 STRING 转换为 SCHEME 对应的 codes.")
+
+(cl-defmethod pyim-cstring-to-codes (string (scheme pyim-scheme-xingma) 
&optional _)
+  "将中文字符串 STRING 转换为对应的形码."
+  (pyim-cstring-to-xingma string scheme t))
+
+(cl-defmethod pyim-cstring-to-codes (string (_scheme pyim-scheme-quanpin) 
&optional criteria)
+  "将中文字符串 STRING 转换为对应的拼音。
+
+如果用户提供 CRITERIA 字符串,那么检索到的所有 codes 会和这个字符
+串进行字符串相似度比较,然后选择一个相似度最高的 code 作为输出,
+这种处理方式适合拼音输入法,形码输入法一般不需要类似的操作。
 
 CRITERIA 字符串一般是通过 imobjs 构建的,它保留了用户原始的输入信
 息。"
-  (let ((class (pyim-scheme-get-option scheme-name :class)))
-    (cond ((eq class 'xingma)
-           (pyim-cstring-to-xingma string scheme-name t))
-          ;;拼音使用了多音字校正
-          (t (let ((codes (pyim-cstring-to-pinyin string nil "-" t nil t))
-                   codes-sorted)
-               (if (< (length criteria) 1)
-                   codes
-                 ;; 将 所有 codes 与 criteria 字符串比对,选取相似度最高的一个
-                 ;; code. 这种处理方式适合拼音输入法。
-                 (setq codes-sorted
-                       (sort codes
-                             (lambda (a b)
-                               (< (pyim-string-distance a criteria)
-                                  (pyim-string-distance b criteria)))))
-                 (list (car codes-sorted))))))))
+  (let ((codes (pyim-cstring-to-pinyin string nil "-" t nil t))
+        codes-sorted)
+    (if (< (length criteria) 1)
+        codes
+      ;; 将 所有 codes 与 criteria 字符串比对,选取相似度最高的一个
+      ;; code. 这种处理方式适合拼音输入法。
+      (setq codes-sorted
+            (sort codes
+                  (lambda (a b)
+                    (< (pyim-string-distance a criteria)
+                       (pyim-string-distance b criteria)))))
+      (list (car codes-sorted)))))
 
 ;; PYIM 重构以前使用的一些函数名称,alias 一下,便于兼容。
 (defalias 'pyim-hanzi2pinyin-simple 'pyim-cstring-to-pinyin-simple)
diff --git a/pyim-dcache.el b/pyim-dcache.el
index 0a9885569d..59e37922e6 100644
--- a/pyim-dcache.el
+++ b/pyim-dcache.el
@@ -272,8 +272,7 @@ non-nil,文件存在时将会提示用户是否覆盖,默认为覆盖模式"
 当词库文件加载完成后,pyim 就可以用这个函数从词库缓存中搜索某个
 code 对应的中文词条了."
   (when code
-    `(,@(pyim-dcache-call-api 'get code from)
-      ,@(pyim-pymap-py2cchar-get code t t))))
+    (pyim-dcache-call-api 'get code from)))
 
 ;; * Footer
 (provide 'pyim-dcache)
diff --git a/pyim-imobjs.el b/pyim-imobjs.el
index f606e67bae..d22fbf1adc 100644
--- a/pyim-imobjs.el
+++ b/pyim-imobjs.el
@@ -81,21 +81,17 @@ imobj 组合构成在一起,构成了 imobjs 这个概念。比如:
 
 (pyim-register-local-variables '(pyim-imobjs))
 
-(defun pyim-imobjs-create (entered &optional scheme-name)
-  "按照 SCHEME-NAME 对应的输入法方案,从 ENTERED 字符串中创建一个
-或者多个 imobj 组成的列表,不同的输入法,imobj 的结构也是不一样的。"
-  (let ((class (pyim-scheme-get-option scheme-name :class)))
-    (when class
-      (funcall (intern (format "pyim-imobjs-create:%S" class))
-               entered scheme-name))))
-
-(defun pyim-imobjs-create:quanpin (entered &optional _)
+(cl-defgeneric pyim-imobjs-create (entered scheme)
+  "按照 SCHEME 对应的输入法方案,从 ENTERED 字符串中创建一个
+或者多个 imobj 组成的列表,不同的输入法,imobj 的结构也是不一样的。")
+
+(cl-defmethod pyim-imobjs-create (entered (scheme pyim-scheme-quanpin))
   "从用户输入的字符串 ENTERED 创建一个输入法内部对象列表: imobjs.
 
 这个 imobjs 可能包含一个 imobj, 也可能包含多个,每个 imobj 都包含
 声母和韵母的相关信息,比如:
 
-    (pyim-imobjs-create:quanpin \"woaimeinv\" 'quanpin)
+    (pyim-imobjs-create \"woaimeinv\" (pyim-scheme-get (quote quanpin)))
 
 结果为:
 
@@ -106,7 +102,7 @@ imobj 组合构成在一起,构成了 imobjs 这个概念。比如:
 
 如果字符串无法正确处理,则返回 nil, 比如:
 
-   (pyim-imobjs-create \"ua\" 'quanpin)
+   (pyim-imobjs-create \"ua\" (pyim-scheme-get (quote quanpin)))
 
 全拼输入法的 imelem 是四个字符串组成的 list, 类似:
 
@@ -141,11 +137,11 @@ imobj 组合构成在一起,构成了 imobjs 这个概念。比如:
                     (concat "'" (nth 2 (car x)))))
             (setq output (append output x)))))
       (when output
-        (pyim-imobjs-find-fuzzy:quanpin (list output))))))
+        (pyim-imobjs-find-fuzzy (list output) scheme)))))
 
 ;; "nihc" -> (((\"n\" \"i\" \"n\" \"i\") (\"h\" \"ao\" \"h\" \"c\")))
-(defun pyim-imobjs-create:shuangpin (entered &optional scheme-name)
-  (let ((keymaps (pyim-scheme-get-option scheme-name :keymaps))
+(cl-defmethod pyim-imobjs-create (entered (scheme pyim-scheme-shuangpin))
+  (let ((keymaps (pyim-scheme-shuangpin-keymaps scheme))
         (list (string-to-list (replace-regexp-in-string "-" "" entered)))
         results)
     (while list
@@ -167,11 +163,10 @@ imobj 组合构成在一起,构成了 imobjs 这个概念。比如:
 
         (when (and one-word-pinyins (> (length one-word-pinyins) 0))
           (push one-word-pinyins results))))
-    (pyim-imobjs-find-fuzzy:quanpin
-     (pyim-permutate-list (nreverse results)))))
+    (pyim-imobjs-find-fuzzy (pyim-permutate-list (nreverse results)) scheme)))
 
-(defun pyim-imobjs-create:xingma (entered &optional scheme-name)
-  (let ((n (pyim-scheme-get-option scheme-name :code-split-length)))
+(cl-defmethod pyim-imobjs-create (entered (scheme pyim-scheme-xingma))
+  (let ((n (pyim-scheme-xingma-code-split-length scheme)))
     (let (output)
       (mapc (lambda (x)
               (while (not (string-empty-p x))
@@ -184,7 +179,10 @@ imobj 组合构成在一起,构成了 imobjs 这个概念。比如:
             (split-string entered "'"))
       (list (nreverse output)))))
 
-(defun pyim-imobjs-find-fuzzy:quanpin (imobjs)
+(cl-defgeneric pyim-imobjs-find-fuzzy (imobjs scheme)
+  "用于处理模糊音的函数。")
+
+(cl-defmethod pyim-imobjs-find-fuzzy (imobjs (_scheme pyim-scheme-quanpin))
   "用于处理模糊音的函数。"
   (let (fuzzy-imobjs result1 result2)
     (dolist (imobj imobjs)
diff --git a/pyim-liberime.el b/pyim-liberime.el
index b7167796a8..70efcbe71b 100644
--- a/pyim-liberime.el
+++ b/pyim-liberime.el
@@ -46,6 +46,15 @@
 (require 'pyim)
 (require 'liberime nil t)
 
+(cl-defstruct (pyim-scheme-rime
+               (:include pyim-scheme)
+               (:constructor pyim-scheme-rime-create)
+               (:copier nil))
+  "Rime 输入法方案。"
+  code-prefix-history
+  code-split-length
+  code-maximum-length)
+
 (pyim-scheme-add
  '(rime
    :document
@@ -94,52 +103,68 @@
 (declare-function liberime-process-key "liberime" (keycode &optional mask))
 (declare-function liberime-select-candidate "liberime" (num))
 
-(defun pyim-liberime-scheme-name (orig_func &optional default)
-  "Advice function of `pyim-scheme-name'."
-  (let* ((scheme-name (funcall orig_func default))
-         (class (pyim-scheme-get-option scheme-name :class)))
-    (if (eq class 'rime)
+(defun pyim-liberime-scheme (orig_func &optional default)
+  "Advice function of `pyim-scheme-current'."
+  (let* ((scheme (funcall orig_func default)))
+    (if (pyim-scheme-rime-p scheme)
         (if (featurep 'liberime-core)
-            scheme-name
-          'quanpin)
-      scheme-name)))
+            scheme
+          (pyim-scheme-get 'quanpin))
+      scheme)))
 
-(advice-add 'pyim-scheme-name :around #'pyim-liberime-scheme-name)
+(advice-add 'pyim-scheme-current :around #'pyim-liberime-scheme)
 
-(defun pyim-imobjs-create:rime (entered &optional _)
+(cl-defmethod pyim-imobjs-create (entered (_scheme pyim-scheme-rime))
   (list (list entered)))
 
-(defun pyim-codes-create:rime (imobj scheme-name &optional first-n)
-  (pyim-codes-create:xingma imobj scheme-name first-n))
+(cl-defmethod pyim-codes-create (imobj (scheme pyim-scheme-rime) &optional 
first-n)
+  (when scheme
+    (let ((code-prefix (pyim-scheme-code-prefix scheme)))
+      (mapcar
+       (lambda (x)
+         (concat (or code-prefix "")
+                 (if (numberp first-n)
+                     (substring x 0 (min first-n (length x)))
+                   x)))
+       imobj))))
+
+(cl-defmethod pyim-candidates-create (imobjs (scheme pyim-scheme-rime))
+  "适用于 rime 的 `pyim-candidates-create' 方法。"
+  (let* ((code (car (pyim-codes-create (car imobjs) scheme)))
+         (code-prefix (pyim-scheme-code-prefix scheme))
+         (s (replace-regexp-in-string "-" "" code))
+         ;; `liberime-search' 搜索的时候不需要 code-prefix, 去除。
+         (s (if code-prefix
+                (string-remove-prefix code-prefix s)
+              s))
+         (words (liberime-search s (* pyim-page-length 2))))
+    words))
 
-(defun pyim-candidates-create:rime (imobjs scheme-name &optional async)
-  "`pyim-candidates-create' 处理 rime 输入法的函数."
-  (let* ((code (car (pyim-codes-create (car imobjs) scheme-name)))
-         (code-prefix (pyim-scheme-get-option scheme-name :code-prefix))
+(cl-defmethod pyim-candidates-create-async (imobjs (scheme pyim-scheme-rime))
+  "适用于 rime 的 `pyim-candidates-create-async' 方法。"
+  (let* ((code (car (pyim-codes-create (car imobjs) scheme)))
+         (code-prefix (pyim-scheme-code-prefix scheme))
          (s (replace-regexp-in-string "-" "" code))
          ;; `liberime-search' 搜索的时候不需要 code-prefix, 去除。
          (s (if code-prefix
                 (string-remove-prefix code-prefix s)
               s))
-         (words (liberime-search s (if async
-                                       nil
-                                     (* pyim-page-length 2)))))
+         (words (liberime-search s nil)))
     words))
 
-(defun pyim-page-preview-create:rime (&optional separator)
+(cl-defmethod pyim-page-preview-create ((_scheme pyim-scheme-rime) &optional 
separator)
   (let* ((preedit (or (liberime-get-preedit)
                       (pyim-entered-get 'point-before))))
     (pyim-process-with-entered-buffer
-     (if (equal 1 (point))
-         (concat "|" preedit)
-       (concat (replace-regexp-in-string (concat separator "'") "'" preedit)
-               " |" (buffer-substring-no-properties (point) (point-max)))))))
+      (if (equal 1 (point))
+          (concat "|" preedit)
+        (concat (replace-regexp-in-string (concat separator "'") "'" preedit)
+                " |" (buffer-substring-no-properties (point) (point-max)))))))
 
 (defvar pyim-liberime-code-log nil)
 (defvar pyim-liberime-word-log nil)
-(defun pyim-select-word:rime ()
+(cl-defmethod pyim-select-word-really ((_scheme pyim-scheme-rime))
   "从选词框中选择当前词条,然后删除该词条对应拼音。"
-  (interactive)
   (pyim-process-outcome-handle 'candidate)
   (let* ((entered (pyim-entered-get 'point-before))
          (word (string-remove-prefix
@@ -174,9 +199,8 @@
 
 (defun pyim-autoselector-rime (&rest _args)
   "适用于RIME的自动上屏器."
-  (let* ((scheme-name (pyim-scheme-name))
-         (class (pyim-scheme-get-option scheme-name :class)))
-    (when (eq class 'rime)
+  (let* ((scheme (pyim-scheme-current)))
+    (when (pyim-scheme-rime-p scheme)
       (let* ((commit (liberime-get-commit))
              (context (liberime-get-context))
              (composition (alist-get 'composition context))
@@ -270,7 +294,8 @@ Please see: https://github.com/rime/librime/issues/349";
    ;; 找不到通用的处理方式的话就不做截取处理。
    (t input)))
 
-(defun pyim-process-terminate:rime ()
+(cl-defmethod pyim-process-terminate-really ((_scheme pyim-scheme-rime))
+  (cl-call-next-method)
   (liberime-clear-commit)
   (liberime-clear-composition))
 
diff --git a/pyim-outcome.el b/pyim-outcome.el
index 55963409d9..cf8f40a18b 100644
--- a/pyim-outcome.el
+++ b/pyim-outcome.el
@@ -165,16 +165,9 @@ pyim 的 translate-trigger-char 要占用一个键位,为了防止用户
               (char-to-string user-trigger)
             (when (= (length user-trigger) 1)
               user-trigger)))
-         (first-char (pyim-scheme-get-option
-                      (pyim-scheme-name)
-                      :first-chars))
-         (prefer-triggers (or (pyim-scheme-get-option
-                               (pyim-scheme-name)
-                               :prefer-triggers)
-                              ;; 向后兼容
-                              (list (pyim-scheme-get-option
-                                     (pyim-scheme-name)
-                                     :prefer-trigger-chars)))))
+         (first-char (pyim-scheme-first-chars (pyim-scheme-current)))
+         (prefer-triggers (pyim-scheme-prefer-triggers
+                           (pyim-scheme-current))))
     (if (pyim-string-match-p (regexp-quote user-trigger) first-char)
         (progn
           ;; (message "注意:pyim-outcome-trigger 设置和当前输入法冲突,使用推荐设置:\"%s\""
diff --git a/pyim-page.el b/pyim-page.el
index 923dfd2d6b..c68c3f9a46 100644
--- a/pyim-page.el
+++ b/pyim-page.el
@@ -126,39 +126,6 @@ Only useful when use posframe.")
 (defvar pyim-page-last-minibuffer-string nil
   "函数 `pyim-page-show-with-minibuffer' 上一次处理的消息字符串。")
 
-(defun pyim-page-current-page ()
-  "计算当前选择的词条在第几页面.
-
-细节信息请参考 `pyim-page-refresh' 的 docstring."
-  (1+ (/ (1- (pyim-process-get-candidate-position)) pyim-page-length)))
-
-(defun pyim-page-total-page ()
-  "计算 page 总共有多少页.
-
-细节信息请参考 `pyim-page-refresh' 的 docstring."
-  (1+ (/ (1- (pyim-process-candidates-length)) pyim-page-length)))
-
-(defun pyim-page-start (&optional candidate-position)
-  "计算当前所在页的第一个词条的位置.
-
-细节信息请参考 `pyim-page-refresh' 的 docstring."
-  (let ((pos (min (pyim-process-candidates-length)
-                  (or candidate-position
-                      (pyim-process-get-candidate-position)))))
-    (1+ (* (/ (1- pos) pyim-page-length) pyim-page-length))))
-
-(defun pyim-page-end ()
-  "计算当前所在页的最后一个词条的位置,
-
-细节信息请参考 `pyim-page-refresh' 的 docstring."
-  (let* ((whole (pyim-process-candidates-length))
-         (len pyim-page-length)
-         (pos (pyim-process-get-candidate-position))
-         (last (* (/ (+ (1- pos) len) len) len)))
-    (if (< last whole)
-        last
-      whole)))
-
 (defun pyim-page-refresh (&optional hightlight-current)
   "刷新 page 页面的函数.
 
@@ -203,177 +170,166 @@ page 的概念,比如,上面的 “nihao” 的 *待选词列表* 就可以
 这个 sublist 的起点为 `pyim-page-start' 的返回值,终点为
 `pyim-page-end' 的返回值。并保存到一个 hashtable 的 :candidates
 关键字对应的位置,这个 hastable 最终会做为参数传递给
-`pyim-page-style' 相关的函数,用于生成用于在选词框中显示的字符串。
-"
-  (let* ((start (1- (pyim-page-start)))
-         (end (pyim-page-end))
-         (candidates (pyim-process-get-candidates))
-         (candidate-showed
-          (mapcar (lambda (x)
-                    (let ((comment (get-text-property 0 :comment x)))
-                      (if comment
-                          (concat x comment)
-                        x)))
-                  (cl-subseq candidates start end)))
-         (pos (- (min (pyim-process-get-candidate-position)
-                      (length candidates))
-                 start))
+`pyim-page-style' 相关的函数,用于生成用于在选词框中显示的字符串。"
+  (let* ((candidate-showed (pyim-page-get-showed-candidates))
+         (positon (pyim-page-get-selected-word-position))
          (page-info (make-hash-table))
-         (tooltip (pyim-page-get-valid-tooltip)))
+         (tooltip (pyim-page-get-valid-tooltip))
+         (style (pyim-page-get-page-style tooltip)))
+    (puthash :scheme (pyim-scheme-current) page-info)
     (puthash :current-page (pyim-page-current-page) page-info)
     (puthash :total-page (pyim-page-total-page) page-info)
     (puthash :candidates candidate-showed page-info)
-    (puthash :position pos page-info)
+    (puthash :position positon page-info)
     (puthash :hightlight-current hightlight-current page-info)
+    (puthash :assistant-enable pyim-assistant-scheme-enable page-info)
     ;; Show page.
     (when (and (null unread-command-events)
                (null unread-post-input-method-events))
       (pyim-page-show
-       (pyim-page-info-format page-info tooltip)
+       (pyim-page-info-format style page-info)
        (funcall pyim-process-ui-position-function)
        tooltip))))
 
 (add-hook 'pyim-process-ui-refresh-hook #'pyim-page-refresh)
 
-(defun pyim-page-next-page (arg)
-  "Pyim page 翻页命令."
-  (interactive "p")
-  (if (= (length (pyim-process-get-entered 'point-before)) 0)
-      (progn
-        (pyim-process-outcome-handle 'last-char)
-        (pyim-process-terminate))
-    (let* ((new (+ (pyim-process-get-candidate-position)
-                   (* pyim-page-length arg) 1))
-           (maxpos (+ 1 (pyim-process-candidates-length))))
-      (pyim-process-set-candidate-position
-       (pyim-page-start
-        (if (> new 0)
-            (if (> new maxpos) 1 new)
-          maxpos)))
-      (pyim-process-ui-refresh))))
+(defun pyim-page-get-showed-candidates ()
+  "从 CANDIDATES 中获取当前 page 显示需要显示的部分内容。"
+  (mapcar (lambda (x)
+            (let ((comment (get-text-property 0 :comment x)))
+              (if comment
+                  (concat x comment)
+                x)))
+          (cl-subseq (pyim-process-get-candidates)
+                     (1- (pyim-page-start))
+                     (pyim-page-end))))
 
-(defun pyim-page-previous-page (arg)
-  (interactive "p")
-  (pyim-page-next-page (- arg)))
+(defun pyim-page-start (&optional candidate-position)
+  "计算当前所在页的第一个词条的位置.
 
-(defun pyim-page-next-word (arg)
-  (interactive "p")
-  (if (= (length (pyim-process-get-entered 'point-before)) 0)
-      (progn
-        (pyim-process-outcome-handle 'last-char)
-        (pyim-process-terminate))
-    (let ((new (+ (pyim-process-get-candidate-position) arg))
-          (len (pyim-process-candidates-length)))
-      (pyim-process-set-candidate-position
-       (if (>= len new)
-           (if (> new 0) new len)
-         1))
-      (pyim-process-ui-refresh 'hightlight-current))))
+细节信息请参考 `pyim-page-refresh' 的 docstring."
+  (let ((pos (min (pyim-process-candidates-length)
+                  (or candidate-position
+                      (pyim-process-get-candidate-position)))))
+    (1+ (* (/ (1- pos) pyim-page-length) pyim-page-length))))
 
-(defun pyim-page-previous-word (arg)
-  (interactive "p")
-  (pyim-page-next-word (- arg)))
+(defun pyim-page-end ()
+  "计算当前所在页的最后一个词条的位置,
 
-(defun pyim-page-preview-create (&optional separator)
-  "这个函数用于创建在 page 中显示的预览字符串。
+细节信息请参考 `pyim-page-refresh' 的 docstring."
+  (let* ((whole (pyim-process-candidates-length))
+         (len pyim-page-length)
+         (pos (pyim-process-get-candidate-position))
+         (last (* (/ (+ (1- pos) len) len) len)))
+    (if (< last whole)
+        last
+      whole)))
 
-这个预览是在 page 中显示,而 `pyim-preview-refresh' 对应的预览
-是在 buffer 光标处显示,两者要做区别。"
-  (let* ((scheme-name (pyim-scheme-name))
-         (class (pyim-scheme-get-option scheme-name :class)))
-    (when class
-      (funcall (intern (format "pyim-page-preview-create:%S" class)) 
separator))))
+(defun pyim-page-get-selected-word-position ()
+  "获取当前选择的词条在 candidates 中的位置。"
+  (- (min (pyim-process-get-candidate-position)
+          (pyim-process-candidates-length))
+     (pyim-page-start)))
 
-(defun pyim-page-preview-create:quanpin (&optional separator)
-  (let* ((separator (or separator " "))
-         (translated (string-join (mapcar (lambda (w)
-                                            (concat (nth 0 w) (nth 1 w)))
-                                          (pyim-process-get-first-imobj))
-                                  separator)))
-    (concat
-     ;; | 显示光标位置的字符
-     (pyim-process-with-entered-buffer
-       (if (equal 1 (point))
-           (concat "|" translated)
-         (concat (replace-regexp-in-string (concat separator "'") "'" 
translated)
-                 " |" (buffer-substring-no-properties (point) (point-max)))))
-     ;; 用于标记辅助输入法
-     (when (and (eq pyim-assistant-scheme 'quanpin)
-                (eq pyim-assistant-scheme-enable t))
-       (let ((code (pyim-cstring-to-xingma
-                    (nth (1- (pyim-process-get-candidate-position))
-                         (pyim-process-get-candidates))
-                    (pyim-scheme-name 'default))))
-         (if (> (length code) 0)
-             (format " [%s](辅)" code)
-           " (辅)"))))))
-
-(defun pyim-page-preview-create:shuangpin (&optional separator)
-  (let ((keymaps (pyim-scheme-get-option (pyim-scheme-name) :keymaps))
-        result)
-    (dolist (w (pyim-process-get-first-imobj))
-      (let ((sm (nth 0 w))
-            (ym (nth 1 w)))
-        (if (equal sm "")
-            (push (car (rassoc (list ym) keymaps)) result)
-          (push
-           (concat (cl-some
-                    (lambda (x)
-                      (when (equal sm (nth 1 x))
-                        (car x)))
-                    keymaps)
-                   (cl-some
-                    (lambda (x)
-                      (when (or (equal ym (nth 2 x))
-                                (equal ym (nth 3 x)))
-                        (car x)))
-                    keymaps))
-           result))))
-    (string-join (reverse result) (or separator " "))))
+(defun pyim-page-get-valid-tooltip ()
+  "获取一个可用的 tooltip."
+  (cond
+   ;; NOTE: 以前在 minibuffer 中试用过 posframe, linux 环境下运行效果还不错,但
+   ;; 在 windows 环境下,似乎有很严重的性能问题,原因未知。
+   ((eq (selected-window) (minibuffer-window)) 'minibuffer)
+   ;; 在 exwm-xim 环境下输入中文时,只能使用 minibuffer, 因为应用窗口遮挡的缘故,
+   ;; 其它方式不可用。
+   ((pyim-exwm-xim-environment-p) 'minibuffer)
+   (t (or (cl-find-if (lambda (tp)
+                        (or (and (eq tp 'posframe)
+                                 (functionp 'posframe-workable-p)
+                                 (posframe-workable-p))
+                            (and (eq tp 'popup)
+                                 (featurep 'popup))
+                            (eq tp 'minibuffer)))
+                      (if (listp pyim-page-tooltip)
+                          pyim-page-tooltip
+                        (list pyim-page-tooltip)))
+          'minibuffer))))
 
-(defun pyim-page-preview-create:xingma (&optional _separator)
-  ;; | 显示光标位置的字符
-  (pyim-process-with-entered-buffer
-    (if (equal (point) (point-max))
-        (buffer-substring-no-properties (point-min) (point-max))
-      (concat (buffer-substring-no-properties (point-min) (point))
-              "| "
-              (buffer-substring-no-properties (point) (point-max))))))
+(defun pyim-page-get-page-style (tooltip)
+  "依照 TOOLTIP 和 `pyim-page-style', 得到一个 page style."
+  (or (cdr (assoc tooltip pyim-page-tooltip-style-alist))
+      pyim-page-style))
 
-(defun pyim-page-menu-create (candidates position &optional separator 
hightlight-current)
-  "这个函数用于创建在 page 中显示的备选词条菜单。"
-  (let ((i 0) result)
-    (dolist (candidate candidates)
-      (let ((str (substring-no-properties
-                  (if (consp candidate)
-                      (concat (car candidate) (cdr candidate))
-                    candidate))))
-        (dolist (n (pyim-process-get-outcome-subword-info))
-          (when (<= n (length str))
-            (set-text-properties (- n 1) n '(face pyim-page-subword) str)))
-        (setq i (1+ i))
-        ;; 高亮当前选择的词条,用于 `pyim-page-next-word'
-        (push
-         (if (and hightlight-current
-                  (= i position))
-             (format "%d%s" i
-                     (propertize
-                      (format "[%s]" str)
-                      'face 'pyim-page-selection))
-           (format "%d.%s " i str))
-         result)))
-    (string-join (nreverse result) (or separator ""))))
+(defun pyim-page-current-page ()
+  "计算当前选择的词条在第几页面.
+
+细节信息请参考 `pyim-page-refresh' 的 docstring."
+  (1+ (/ (1- (pyim-process-get-candidate-position)) pyim-page-length)))
+
+(defun pyim-page-total-page ()
+  "计算 page 总共有多少页.
+
+细节信息请参考 `pyim-page-refresh' 的 docstring."
+  (1+ (/ (1- (pyim-process-candidates-length)) pyim-page-length)))
+
+(cl-defgeneric pyim-page-show (string position tooltip)
+  "在 POSITION 位置,使用 posframe 或者 popup 显示字符串 STRING.")
+
+(cl-defmethod pyim-page-show (string position (_tooltip (eql posframe)))
+  "在 POSITION 位置,使用 posframe STRING."
+  (posframe-show pyim-page-posframe-buffer
+                 :string string
+                 :position position
+                 :min-width pyim-page-posframe-min-width
+                 :background-color (face-attribute 'pyim-page :background)
+                 :foreground-color (face-attribute 'pyim-page :foreground)
+                 :border-width pyim-page-posframe-border-width
+                 :border-color (face-attribute 'pyim-page-border :background)))
+
+(cl-defmethod pyim-page-show (string _position (_tooltip (eql minibufer)))
+  "使用 minibuffer 来显示 STRING。"
+  (let ((max-mini-window-height (+ pyim-page-length 2))
+        (message-log-max nil))
+    (if (not (eq (selected-window) (minibuffer-window)))
+        (message string)
+      (message nil)
+      ;; 在类似 vertico-posframe 这样的环境中,posframe window-point 同步问题不
+      ;; 太好处理,这里使用一个简单粗暴的方式:在输入过程中,隐藏真实的 cursor
+      ;; 并显示一个伪 cursor, 输入完成之后再恢复。
+      (setq-local cursor-type nil)
 
-(defun pyim-page-info-format (page-info tooltip)
-  "将 PAGE-INFO 按照 `pyim-page-style' 格式化为选词框中显示的字符串。"
-  (let ((style (or (cdr (assoc tooltip pyim-page-tooltip-style-alist))
-                   pyim-page-style)))
-    (let ((func (intern (format "pyim-page-style:%S" style))))
-      (if (functionp func)
-          (funcall func page-info)
-        (pyim-page-style:two-lines page-info)))))
+      ;; 异步获取词条的时候,上一次的 page 字符串可能还在 Minibuffer 中,所以首
+      ;; 先要将其去除,否则会出现两个 page.
+      (delete-char (length pyim-page-last-minibuffer-string))
+      (save-excursion
+        (insert
+         (setq pyim-page-last-minibuffer-string
+               (concat
+                ;; 显示一个伪 cursor.
+                (propertize " " 'face 'cursor)
+                (or pyim-page-minibuffer-separator
+                    (let* ((width (string-width (buffer-string)))
+                           (n (- (* 20 (+ 1 (/ width 20))) width)))
+                      (make-string n ?\ )))
+                string)))))))
+
+(declare-function 'popup-tip "popup")
+(declare-function 'popup-delete "popup")
+(defvar popup-version)
+
+(cl-defmethod pyim-page-show (string position (_tooltip (eql popup)))
+  "Show STRING at POSITION with the help of popup-el."
+  (when pyim-page-last-popup
+    ;; 异步获取词条的时候,如果不把已经存在的 popup 删除,就会出现两个 page.
+    (popup-delete pyim-page-last-popup))
+  (setq pyim-page-last-popup
+        (apply #'popup-tip string
+               :point position :around t :nowait t :nostrip t
+               ;; popup v0.5.9 以后才支持 face 参数
+               (unless (version<= popup-version "0.5.8")
+                 (list :face 'pyim-page)))))
+
+(cl-defgeneric pyim-page-info-format (style page-info)
+  "将 PAGE-INFO 按照 STYLE 格式化为选词框中显示的字符串。")
 
-(defun pyim-page-style:two-lines (page-info)
+(cl-defmethod pyim-page-info-format ((_style (eql two-lines)) page-info)
   "将 PAGE-INFO 格式化为选词框中显示的字符串.
 
 样式类似:
@@ -382,8 +338,10 @@ page 的概念,比如,上面的 “nihao” 的 *待选词列表* 就可以
 | ni hao [1/9]               |
 | 1.你好 2.你号 ...          |
 +----------------------------+"
-  (format "=> %s [%s/%s]: \n%s"
-          (pyim-page-preview-create)
+  (format "=> %s%s [%s/%s]: \n%s"
+          (pyim-page-preview-create
+           (gethash :scheme page-info))
+          (if (gethash :assistant-enable page-info) " (辅)" "")
           (gethash :current-page page-info)
           (gethash :total-page page-info)
           (pyim-page-menu-create
@@ -392,7 +350,7 @@ page 的概念,比如,上面的 “nihao” 的 *待选词列表* 就可以
            nil
            (gethash :hightlight-current page-info))))
 
-(defun pyim-page-style:one-line (page-info)
+(cl-defmethod pyim-page-info-format ((_style (eql one-line)) page-info)
   "将 PAGE-INFO 格式化为选词框中显示的字符串.
 
 样式类似:
@@ -400,8 +358,10 @@ page 的概念,比如,上面的 “nihao” 的 *待选词列表* 就可以
 +-----------------------------------+
 | [ni hao]: 1.你好 2.你号 ... (1/9) |
 +-----------------------------------+"
-  (format "[%s]: %s(%s/%s)"
-          (pyim-page-preview-create " ")
+  (format "[%s%s]: %s(%s/%s)"
+          (pyim-page-preview-create
+           (gethash :scheme page-info) " ")
+          (if (gethash :assistant-enable page-info) " (辅)" "")
           (pyim-page-menu-create
            (gethash :candidates page-info)
            (gethash :position page-info)
@@ -410,7 +370,7 @@ page 的概念,比如,上面的 “nihao” 的 *待选词列表* 就可以
           (gethash :current-page page-info)
           (gethash :total-page page-info)))
 
-(defun pyim-page-style:vertical (page-info)
+(cl-defmethod pyim-page-info-format ((_style (eql vertical)) page-info)
   "将 PAGE-INFO 格式化为选词框中显示的字符串.
 
 样式类似:
@@ -420,8 +380,10 @@ page 的概念,比如,上面的 “nihao” 的 *待选词列表* 就可以
 | 1.你好       |
 | 2.你号 ...   |
 +--------------+"
-  (format "=> %s [%s/%s]: \n%s"
-          (pyim-page-preview-create)
+  (format "=> %s%s [%s/%s]: \n%s"
+          (pyim-page-preview-create
+           (gethash :scheme page-info))
+          (if (gethash :assistant-enable page-info) " (辅)" "")
           (gethash :current-page page-info)
           (gethash :total-page page-info)
           (pyim-page-menu-create
@@ -430,7 +392,7 @@ page 的概念,比如,上面的 “nihao” 的 *待选词列表* 就可以
            "\n"
            (gethash :hightlight-current page-info))))
 
-(defun pyim-page-style:minibuffer (page-info)
+(cl-defmethod pyim-page-info-format ((_style (eql minibuffer)) page-info)
   "将 PAGE-INFO 格式化为选词框中显示的字符串.
 
 样式类似:
@@ -440,8 +402,10 @@ page 的概念,比如,上面的 “nihao” 的 *待选词列表* 就可以
 +------------------------------------+"
   ;; 在 minibuffer 中显示 page 的时候,page 字符串直接插入到 minibuffer 现有的内
   ;; 容中, 为了便于区分,在 page 后面添加一个显眼的字符。
-  (format "[%-15s]: %s(%s/%s) $ "
-          (pyim-page-preview-create)
+  (format "[%-15s%s]: %s(%s/%s) $ "
+          (pyim-page-preview-create
+           (gethash :scheme page-info))
+          (if (gethash :assistant-enable page-info) " (辅)" "")
           (pyim-page-menu-create
            (gethash :candidates page-info)
            (gethash :position page-info)
@@ -450,102 +414,147 @@ page 的概念,比如,上面的 “nihao” 的 *待选词列表* 就可以
           (gethash :current-page page-info)
           (gethash :total-page page-info)))
 
-(defun pyim-page-get-valid-tooltip ()
-  "获取一个可用的 tooltip."
-  (cond
-   ;; NOTE: 以前在 minibuffer 中试用过 posframe, linux 环境下运行效果还不错,但
-   ;; 在 windows 环境下,似乎有很严重的性能问题,原因未知。
-   ((eq (selected-window) (minibuffer-window)) 'minibuffer)
-   ;; 在 exwm-xim 环境下输入中文时,只能使用 minibuffer, 因为应用窗口遮挡的缘故,
-   ;; 其它方式不可用。
-   ((pyim-exwm-xim-environment-p) 'minibuffer)
-   (t (or (cl-find-if (lambda (tp)
-                        (or (and (eq tp 'posframe)
-                                 (functionp 'posframe-workable-p)
-                                 (posframe-workable-p))
-                            (and (eq tp 'popup)
-                                 (featurep 'popup))
-                            (eq tp 'minibuffer)))
-                      (if (listp pyim-page-tooltip)
-                          pyim-page-tooltip
-                        (list pyim-page-tooltip)))
-          'minibuffer))))
+(cl-defgeneric pyim-page-preview-create (scheme &optional separator)
+  "这个函数用于创建在 page 中显示的预览字符串。
 
-(defun pyim-page-show (string position tooltip)
-  "在 POSITION 位置,使用 posframe 或者 popup 显示字符串 STRING."
-  (cond ((eq tooltip 'posframe)
-         (posframe-show pyim-page-posframe-buffer
-                        :string string
-                        :position position
-                        :min-width pyim-page-posframe-min-width
-                        :background-color (face-attribute 'pyim-page 
:background)
-                        :foreground-color (face-attribute 'pyim-page 
:foreground)
-                        :border-width pyim-page-posframe-border-width
-                        :border-color (face-attribute 'pyim-page-border 
:background)))
-        ((eq tooltip 'popup)
-         (pyim-page-show-with-popup :string string
-                                    :position position))
-        (t (pyim-page-show-with-minibuffer string))))
-
-(defun pyim-page-show-with-minibuffer (string)
-  "使用 minibuffer 来显示 STRING。"
-  (let ((max-mini-window-height (+ pyim-page-length 2))
-        (message-log-max nil))
-    (if (not (eq (selected-window) (minibuffer-window)))
-        (message string)
-      (message nil)
-      ;; 在类似 vertico-posframe 这样的环境中,posframe window-point 同步问题不
-      ;; 太好处理,这里使用一个简单粗暴的方式:在输入过程中,隐藏真实的 cursor
-      ;; 并显示一个伪 cursor, 输入完成之后再恢复。
-      (setq-local cursor-type nil)
+这个预览是在 page 中显示,而 `pyim-preview-refresh' 对应的预览
+是在 buffer 光标处显示,两者要做区别。")
 
-      ;; 异步获取词条的时候,上一次的 page 字符串可能还在 Minibuffer 中,所以首
-      ;; 先要将其去除,否则会出现两个 page.
-      (delete-char (length pyim-page-last-minibuffer-string))
-      (save-excursion
-        (insert
-         (setq pyim-page-last-minibuffer-string
-               (concat
-                ;; 显示一个伪 cursor.
-                (propertize " " 'face 'cursor)
-                (or pyim-page-minibuffer-separator
-                    (let* ((width (string-width (buffer-string)))
-                           (n (- (* 20 (+ 1 (/ width 20))) width)))
-                      (make-string n ?\ )))
-                string)))))))
+(cl-defmethod pyim-page-preview-create ((_scheme pyim-scheme-quanpin) 
&optional separator)
+  (let* ((separator (or separator " "))
+         (translated (string-join (mapcar (lambda (w)
+                                            (concat (nth 0 w) (nth 1 w)))
+                                          (pyim-process-get-first-imobj))
+                                  separator)))
+    (concat
+     ;; | 显示光标位置的字符
+     (pyim-process-with-entered-buffer
+       (if (equal 1 (point))
+           (concat "|" translated)
+         (concat (replace-regexp-in-string (concat separator "'") "'" 
translated)
+                 " |" (buffer-substring-no-properties (point) 
(point-max))))))))
 
-(declare-function 'popup-tip "popup")
-(declare-function 'popup-delete "popup")
-(defvar popup-version)
+(cl-defmethod pyim-page-preview-create ((scheme pyim-scheme-shuangpin) 
&optional separator)
+  (let ((keymaps (pyim-scheme-shuangpin-keymaps scheme))
+        result)
+    (dolist (w (pyim-process-get-first-imobj))
+      (let ((sm (nth 0 w))
+            (ym (nth 1 w)))
+        (if (equal sm "")
+            (push (car (rassoc (list ym) keymaps)) result)
+          (push
+           (concat (cl-some
+                    (lambda (x)
+                      (when (equal sm (nth 1 x))
+                        (car x)))
+                    keymaps)
+                   (cl-some
+                    (lambda (x)
+                      (when (or (equal ym (nth 2 x))
+                                (equal ym (nth 3 x)))
+                        (car x)))
+                    keymaps))
+           result))))
+    (string-join (reverse result) (or separator " "))))
 
-(cl-defun pyim-page-show-with-popup (&key string position)
-  "Show STRING at POSITION with the help of popup-el."
-  (when pyim-page-last-popup
-    ;; 异步获取词条的时候,如果不把已经存在的 popup 删除,就会出现两个 page.
-    (popup-delete pyim-page-last-popup))
-  (setq pyim-page-last-popup
-        (apply #'popup-tip string
-               :point position :around t :nowait t :nostrip t
-               ;; popup v0.5.9 以后才支持 face 参数
-               (unless (version<= popup-version "0.5.8")
-                 (list :face 'pyim-page)))))
+(cl-defmethod pyim-page-preview-create ((_scheme pyim-scheme-xingma) &optional 
_separator)
+  ;; | 显示光标位置的字符
+  (pyim-process-with-entered-buffer
+    (if (equal (point) (point-max))
+        (buffer-substring-no-properties (point-min) (point-max))
+      (concat (buffer-substring-no-properties (point-min) (point))
+              "| "
+              (buffer-substring-no-properties (point) (point-max))))))
+
+(defun pyim-page-menu-create (candidates position &optional separator 
hightlight-current)
+  "这个函数用于创建在 page 中显示的备选词条菜单。"
+  (let ((i 0) result)
+    (dolist (candidate candidates)
+      (let ((str (substring-no-properties
+                  (if (consp candidate)
+                      (concat (car candidate) (cdr candidate))
+                    candidate))))
+        (dolist (n (pyim-process-get-outcome-subword-info))
+          (when (<= n (length str))
+            (set-text-properties (- n 1) n '(face pyim-page-subword) str)))
+        (setq i (1+ i))
+        ;; 高亮当前选择的词条,用于 `pyim-page-next-word'
+        (push
+         (if (and hightlight-current
+                  (= i position))
+             (format "%d%s" i
+                     (propertize
+                      (format "[%s]" str)
+                      'face 'pyim-page-selection))
+           (format "%d.%s " i str))
+         result)))
+    (string-join (nreverse result) (or separator ""))))
+
+(defun pyim-page-next-page (arg)
+  "Pyim page 翻页命令."
+  (interactive "p")
+  (if (= (length (pyim-process-get-entered 'point-before)) 0)
+      (progn
+        (pyim-process-outcome-handle 'last-char)
+        (pyim-process-terminate))
+    (let* ((new (+ (pyim-process-get-candidate-position)
+                   (* pyim-page-length arg) 1))
+           (maxpos (+ 1 (pyim-process-candidates-length))))
+      (pyim-process-set-candidate-position
+       (pyim-page-start
+        (if (> new 0)
+            (if (> new maxpos) 1 new)
+          maxpos)))
+      (pyim-process-ui-refresh))))
+
+(defun pyim-page-previous-page (arg)
+  (interactive "p")
+  (pyim-page-next-page (- arg)))
+
+(defun pyim-page-next-word (arg)
+  (interactive "p")
+  (if (= (length (pyim-process-get-entered 'point-before)) 0)
+      (progn
+        (pyim-process-outcome-handle 'last-char)
+        (pyim-process-terminate))
+    (let ((new (+ (pyim-process-get-candidate-position) arg))
+          (len (pyim-process-candidates-length)))
+      (pyim-process-set-candidate-position
+       (if (>= len new)
+           (if (> new 0) new len)
+         1))
+      (pyim-process-ui-refresh 'hightlight-current))))
+
+(defun pyim-page-previous-word (arg)
+  (interactive "p")
+  (pyim-page-next-word (- arg)))
 
 (defun pyim-page-hide ()
   "Hide pyim page."
-  (let ((tooltip (pyim-page-get-valid-tooltip)))
-    (cond
-     ((eq tooltip 'popup)
-      (popup-delete pyim-page-last-popup))
-     ((eq tooltip 'posframe)
-      (posframe-hide pyim-page-posframe-buffer))
-     (t (when (eq (selected-window) (minibuffer-window))
-          ;; 从 minibuffer 中删除 page 字符串。
-          (delete-char (length pyim-page-last-minibuffer-string))
-          ;; 在类似 vertico-posframe 这样的环境中,posframe window-point 同步问题
-          ;; 不太好处理,这里使用一个简单粗暴的方式:在输入过程中,隐藏真实的
-          ;; cursor 并显示一个伪 cursor, 输入完成之后再恢复。
-          (setq-local cursor-type t))
-        (setq pyim-page-last-minibuffer-string nil)))))
+  (pyim-page-hide-tooltip (pyim-page-get-valid-tooltip)))
+
+(cl-defgeneric pyim-page-hide-tooltip (tooltip)
+  "Hide TOOLTIP.")
+
+(cl-defmethod pyim-page-hide-tooltip ((_tooltip (eql popup)))
+  "Hide popup tooltip."
+  (popup-delete pyim-page-last-popup))
+
+(cl-defmethod pyim-page-hide-tooltip ((_tooltip (eql posframe)))
+  "Hide posframe tooltip."
+  (posframe-hide pyim-page-posframe-buffer))
+
+(cl-defmethod pyim-page-hide-tooltip ((_tooltip (eql minibuffer)))
+  "Hide minibuffer tooltip."
+  (popup-delete pyim-page-last-popup)
+  (when (eq (selected-window) (minibuffer-window))
+    ;; 从 minibuffer 中删除 page 字符串。
+    (delete-char (length pyim-page-last-minibuffer-string))
+    ;; 在类似 vertico-posframe 这样的环境中,posframe window-point 同步问题
+    ;; 不太好处理,这里使用一个简单粗暴的方式:在输入过程中,隐藏真实的
+    ;; cursor 并显示一个伪 cursor, 输入完成之后再恢复。
+    (setq-local cursor-type t))
+  (setq pyim-page-last-minibuffer-string nil))
 
 (add-hook 'pyim-process-ui-hide-hook #'pyim-page-hide)
 
diff --git a/pyim-preview.el b/pyim-preview.el
index 24fe56d469..1e9a8c2327 100644
--- a/pyim-preview.el
+++ b/pyim-preview.el
@@ -67,7 +67,8 @@
 
 这个函数会在 `pyim-input-method' 中调用,用于删除
 `pyim-preview-overlay' 中保存的 overlay。"
-  (if (and (overlayp pyim-preview-overlay) (overlay-start 
pyim-preview-overlay))
+  (if (and (overlayp pyim-preview-overlay)
+           (overlay-start pyim-preview-overlay))
       (delete-overlay pyim-preview-overlay)))
 
 (defun pyim-preview-refresh (&rest _)
@@ -76,22 +77,8 @@
 pyim 会使用 Emacs overlay 机制在 *待输入buffer* 光标处高亮显示一
 个预览字符串,让用户可以查看将要输入的字符串,这个函数用于更新这
 个字符串的内容。"
-  (let* ((class (pyim-scheme-get-option (pyim-scheme-name) :class))
-         (candidates (pyim-process-get-candidates))
-         (pos (1- (min (pyim-process-get-candidate-position)
-                       (length candidates))))
-         (preview (concat (pyim-process-get-outcome)
-                          (nth pos candidates))))
-    (when (memq class '(quanpin))
-      (let ((rest (mapconcat
-                   (lambda (py)
-                     (concat (nth 0 py) (nth 1 py)))
-                   (nthcdr (length preview)
-                           (pyim-process-get-first-imobj))
-                   "'")))
-        (when (string< "" rest)
-          (setq preview (concat preview rest)))))
-    (setq preview (pyim-process-subword-and-magic-convert preview))
+  (let* ((scheme (pyim-scheme-current))
+         (preview (pyim-preview-string scheme)))
     ;; Delete old preview string.
     (pyim-preview-delete-string)
     ;; Insert new preview string.
@@ -102,6 +89,35 @@ pyim 会使用 Emacs overlay 机制在 *待输入buffer* 光标处高亮显示
 
 (add-hook 'pyim-process-ui-refresh-hook #'pyim-preview-refresh)
 
+(cl-defgeneric pyim-preview-string (scheme)
+  "获得 preview 字符串。")
+
+(cl-defmethod pyim-preview-string (_scheme)
+  "获得 preview 字符串。"
+  (let* ((candidates (pyim-process-get-candidates))
+         (pos (1- (min (pyim-process-get-candidate-position)
+                       (length candidates))))
+         (preview (concat (pyim-process-get-outcome)
+                          (nth pos candidates))))
+    (pyim-process-subword-and-magic-convert preview)))
+
+(cl-defmethod pyim-preview-string ((_scheme pyim-scheme-quanpin))
+  "获得 preview 字符串,适用于全拼输入法。"
+  (let* ((candidates (pyim-process-get-candidates))
+         (pos (1- (min (pyim-process-get-candidate-position)
+                       (length candidates))))
+         (preview (concat (pyim-process-get-outcome)
+                          (nth pos candidates)))
+         (rest (mapconcat
+                (lambda (py)
+                  (concat (nth 0 py) (nth 1 py)))
+                (nthcdr (length preview)
+                        (pyim-process-get-first-imobj))
+                "'")))
+    (when (string< "" rest)
+      (setq preview (concat preview rest)))
+    (pyim-process-subword-and-magic-convert preview)))
+
 (defun pyim-preview-delete-string ()
   "删除已经插入 buffer 的 preview 预览字符串。"
   (when (and pyim-preview-overlay (overlay-start pyim-preview-overlay))
diff --git a/pyim-probe.el b/pyim-probe.el
index 8189032afb..aba8c32296 100644
--- a/pyim-probe.el
+++ b/pyim-probe.el
@@ -80,7 +80,8 @@
   "激活这个 pyim 探针函数后,可以解决 org-speed-commands 与 pyim 冲突问题。
 
 用于:`pyim-english-input-switch-functions' 。"
-  (and (string= major-mode "org-mode")
+  (and (> emacs-major-version 25)
+       (string= major-mode "org-mode")
        (bolp)
        (looking-at org-heading-regexp)
        org-use-speed-commands))
@@ -184,10 +185,13 @@
          (member (char-to-string char) puncts))))
 
 (defun pyim-probe-org-latex-mode ()
-  "org-mode 中的 latex fragment 和 latex 宏指令中自动切换到英文输入."
-  (when (eq major-mode 'org-mode)
-    (or (not (eq (org-inside-LaTeX-fragment-p) nil))
-        (not (eq (org-inside-latex-macro-p) nil)))))
+  "org-mode 中的 latex fragment 和 latex 宏指令中自动切换到英文输入.
+
+FIXME: 这个 probe 在 Emacs 25 上运行可能存在问题。"
+  (when (and (> emacs-major-version 25)
+             (eq major-mode 'org-mode))
+    (or (org-inside-LaTeX-fragment-p)
+        (org-inside-latex-macro-p))))
 
 (defun pyim-probe-exwm-xim-environment ()
   "测试当前是否是 exwm-xim 输入法环境。
@@ -204,6 +208,8 @@
           'xwidget-webkit-pass-command-event-with-input-method)
       (bound-and-true-p xwidget-webkit-isearch--read-string-buffer)))
 
+(cl-pushnew #'pyim-probe-exwm-xim-environment 
pyim-force-input-chinese-functions)
+(cl-pushnew #'pyim-probe-xwidget-webkit-environment 
pyim-force-input-chinese-functions)
 
 ;; * Footer
 (provide 'pyim-probe)
diff --git a/pyim-process.el b/pyim-process.el
index c807e0aa22..af5337358f 100644
--- a/pyim-process.el
+++ b/pyim-process.el
@@ -50,9 +50,7 @@
 运行结果为 t 时,pyim 开启英文输入功能。"
   :type 'symbol)
 
-(defcustom pyim-force-input-chinese-functions
-  (list 'pyim-probe-exwm-xim-environment
-        'pyim-probe-xwidget-webkit-environment)
+(defcustom pyim-force-input-chinese-functions nil
   "让 pyim 强制输入中文.
 
 这个变量的取值为一个函数列表,这个函数列表中的任意一个函数的运行
@@ -61,7 +59,7 @@
   :type 'symbol)
 
 (defvaralias 'pyim-autoselector 'pyim-process-autoselector)
-(defcustom pyim-process-autoselector '(pyim-autoselector-xingma)
+(defcustom pyim-process-autoselector nil
   "已经启用的自动上屏器.
 
 自动上屏器是一个函数。假设用户已经输入 \"nihao\", 并按下 \"m\" 键,
@@ -189,11 +187,11 @@ entered (nihaom) 的第一个候选词。
 
 如果 SEARCH-FORWARD 为 t, 则向前搜索,反之,向后搜索。"
   (pyim-entered-with-entered-buffer
-    (let* ((scheme-name (pyim-scheme-name))
+    (let* ((scheme (pyim-scheme-current))
            (start (or start (point)))
            (end-position start)
            (string (buffer-substring-no-properties (point-min) start))
-           (orig-imobj-len (length (car (pyim-imobjs-create string 
scheme-name))))
+           (orig-imobj-len (length (car (pyim-imobjs-create string scheme))))
            imobj pos)
       (if search-forward
           ;; "ni|haoshijie" -> "nihao|shijie"
@@ -201,7 +199,7 @@ entered (nihaom) 的第一个候选词。
             (setq pos (point-max))
             (while (and (> pos start) (= end-position start))
               (setq string (buffer-substring-no-properties (point-min) pos)
-                    imobj (car (pyim-imobjs-create string scheme-name)))
+                    imobj (car (pyim-imobjs-create string scheme)))
               (if (>= (+ orig-imobj-len num) (length imobj))
                   (setq end-position pos)
                 (cl-decf pos))))
@@ -211,7 +209,7 @@ entered (nihaom) 的第一个候选词。
           (setq pos start)
           (while (and (>= pos (point-min)) (= end-position start))
             (setq string (buffer-substring-no-properties (point-min) pos)
-                  imobj (car (pyim-imobjs-create string scheme-name)))
+                  imobj (car (pyim-imobjs-create string scheme)))
             (if (= (- orig-imobj-len num) (length imobj))
                 (setq end-position pos)
               (cl-decf pos)))))
@@ -242,9 +240,9 @@ entered (nihaom) 的第一个候选词。
 
 (defun pyim-process-input-chinese-p ()
   "确定 pyim 是否需要启动中文输入模式."
-  (let* ((scheme-name (pyim-scheme-name))
-         (first-chars (pyim-scheme-get-option scheme-name :first-chars))
-         (rest-chars (pyim-scheme-get-option scheme-name :rest-chars)))
+  (let* ((scheme (pyim-scheme-current))
+         (first-chars (pyim-scheme-first-chars scheme))
+         (rest-chars (pyim-scheme-rest-chars scheme)))
     (and (or (pyim-process-force-input-chinese-p)
              (and (not pyim-process-input-ascii)
                   (not (pyim-process-auto-switch-english-input-p))))
@@ -278,13 +276,13 @@ entered (nihaom) 的第一个候选词。
 
 (defun pyim-process-run-1 ()
   "查询 `pyim-entered-buffer' 光标前的拼音字符串(如果光标在行首则为光标后的), 显示备选词等待用户选择。"
-  (let* ((scheme-name (pyim-scheme-name))
+  (let* ((scheme (pyim-scheme-current))
          entered-to-translate)
     (setq entered-to-translate
           (pyim-entered-get 'point-before))
-    (setq pyim-imobjs (pyim-imobjs-create entered-to-translate scheme-name))
+    (setq pyim-imobjs (pyim-imobjs-create entered-to-translate scheme))
     (setq pyim-candidates
-          (or (delete-dups (pyim-candidates-create pyim-imobjs scheme-name))
+          (or (delete-dups (pyim-candidates-create pyim-imobjs scheme))
               (list entered-to-translate)))
     (pyim-process-run-async-timer-reset)
     ;; 当用户选择词条时,如果停顿超过1秒,就激活异步流程,不同的输入法异步流程定
@@ -361,8 +359,8 @@ entered (nihaom) 的第一个候选词。
 
 (defun pyim-process-run-async ()
   "Function used by `pyim-process-run-async-timer'"
-  (let* ((scheme-name (pyim-scheme-name))
-         (words (delete-dups (pyim-candidates-create pyim-imobjs scheme-name 
t))))
+  (let* ((scheme (pyim-scheme-current))
+         (words (delete-dups (pyim-candidates-create-async pyim-imobjs 
scheme))))
     (when words
       (setq pyim-candidates words)
       (pyim-process-ui-refresh))))
@@ -592,7 +590,7 @@ alist 列表。"
   (setq pyim-process-code-criteria
         (let ((str (string-join
                     (pyim-codes-create (pyim-process-get-first-imobj)
-                                       (pyim-scheme-name)))))
+                                       (pyim-scheme-current)))))
           (if (> (length pyim-process-code-criteria)
                  (length str))
               pyim-process-code-criteria
@@ -629,10 +627,10 @@ BUG:拼音无法有效地处理多音字。"
     ;; 记录最近创建的词条,用于快速删词功能。
     (setq pyim-process-last-created-words
           (cons word (remove word pyim-process-last-created-words)))
-    (let* ((scheme-name (pyim-scheme-name))
-           (code-prefix (pyim-scheme-get-option scheme-name :code-prefix))
+    (let* ((scheme (pyim-scheme-current))
+           (code-prefix (pyim-scheme-code-prefix scheme))
            (codes (pyim-cstring-to-codes
-                   word scheme-name
+                   word scheme
                    (or criteria pyim-process-code-criteria))))
       ;; 保存对应词条的词频
       (when (> (length word) 0)
@@ -664,6 +662,12 @@ BUG:拼音无法有效地处理多音字。"
 
 (defun pyim-process-terminate ()
   "Terminate the translation of the current key."
+  (pyim-process-terminate-really (pyim-scheme-current)))
+
+(cl-defgeneric pyim-process-terminate-really (scheme)
+  "Terminate the translation of the current key.")
+
+(cl-defmethod pyim-process-terminate-really (_scheme)
   (setq pyim-process-translating nil)
   (pyim-entered-erase-buffer)
   (setq pyim-process-code-criteria nil)
@@ -671,11 +675,7 @@ BUG:拼音无法有效地处理多音字。"
   (setq pyim-candidates nil)
   (setq pyim-candidates-last nil)
   (pyim-process-run-async-timer-reset)
-  (pyim-process-ui-hide)
-  (let* ((class (pyim-scheme-get-option (pyim-scheme-name) :class))
-         (func (intern (format "pyim-process-terminate:%S" class))))
-    (when (and class (functionp func))
-      (funcall func))))
+  (pyim-process-ui-hide))
 
 (defun pyim-process-ui-hide ()
   "隐藏 pyim 相关 UI."
diff --git a/pyim-scheme.el b/pyim-scheme.el
index da6369ef66..718ad82bae 100644
--- a/pyim-scheme.el
+++ b/pyim-scheme.el
@@ -53,10 +53,56 @@
 (defvar pyim-schemes nil
   "Pyim 支持的所有拼音方案.")
 
+(cl-defstruct (pyim-scheme
+               (:constructor pyim-scheme-create)
+               (:copier nil))
+  "输入法方案通用的 slots."
+  class
+  name
+  document
+  first-chars
+  rest-chars
+  code-prefix
+  prefer-triggers)
+
+(cl-defstruct (pyim-scheme-quanpin
+               (:include pyim-scheme)
+               (:constructor pyim-scheme-quanpin-create)
+               (:copier nil))
+  "全拼输入法方案类型。")
+
+(cl-defstruct (pyim-scheme-shuangpin
+               (:include pyim-scheme-quanpin)
+               (:constructor pyim-scheme-shuangpin-create)
+               (:copier nil))
+  "双拼输入法方案类型。
+
+在 PYIM 中,双拼输入法是建立在全拼输入法的基础上的,所以将其定义
+为全拼输入法类型的子类型。"
+  keymaps)
+
+(cl-defstruct (pyim-scheme-xingma
+               (:include pyim-scheme)
+               (:constructor pyim-scheme-xingma-create)
+               (:copier nil))
+  "形码输入法方案类型。"
+  code-prefix-history
+  code-split-length
+  code-maximum-length)
+
+(cl-defstruct (pyim-scheme-wubi
+               (:include pyim-scheme-xingma)
+               (:constructor pyim-scheme-wubi-create)
+               (:copier nil))
+  "五笔输入法方案类型。
+
+单独创建五笔方案类型,是为了支持五笔反查功能,因为1到4字的中文词
+语, 五笔编码有固定的规则,其它形码没有类似特点。" )
+
 ;;;###autoload
 (defun pyim-default-scheme (&optional scheme-name)
   (interactive)
-  (let* ((scheme-names (mapcar #'car pyim-schemes))
+  (let* ((scheme-names (mapcar #'pyim-scheme-name pyim-schemes))
          (scheme-name
           (or scheme-name
               (intern (completing-read "PYIM: 将 pyim-default-scheme 设置为:" 
scheme-names)))))
@@ -68,45 +114,42 @@
       (message "PYIM: %s 不是一个有效的 scheme 名称, 继续使用 %s." scheme-name 
pyim-default-scheme)
       nil)))
 
-(defun pyim-scheme-add (scheme)
+(defun pyim-scheme-add (scheme-config)
   "Add SCHEME to `pyim-schemes'"
-  (if (listp scheme)
-      (let ((scheme-name (car scheme)))
-        (when (symbolp scheme-name)
-          (setq pyim-schemes
-                (remove (assoc scheme-name pyim-schemes)
-                        pyim-schemes)))
-        (push scheme pyim-schemes))
+  (if (listp scheme-config)
+      (let* ((scheme-name (car scheme-config))
+             (scheme-type (plist-get (cdr scheme-config) :class))
+             (func (intern (format "pyim-scheme-%s-create" scheme-type)))
+             (scheme (apply func :name scheme-name (cdr scheme-config)))
+             schemes update-p)
+        (when (and (symbolp scheme-name)
+                   (functionp func))
+          (dolist (x pyim-schemes)
+            (push (if (equal (pyim-scheme-name x) scheme-name)
+                      (progn (setq update-p t)
+                             scheme)
+                    x)
+                  schemes))
+          (unless update-p
+            (push scheme schemes))
+          (setq pyim-schemes (reverse schemes))))
     (message "PYIM: Invalid pyim scheme config!")))
 
+(defun pyim-scheme-current ()
+  "获取当前正在使用的 scheme。"
+  (or (pyim-scheme-get
+       (if pyim-assistant-scheme-enable
+           pyim-assistant-scheme
+         pyim-default-scheme))
+      (pyim-scheme-get 'quanpin)))
+
 (defun pyim-scheme-get (scheme-name)
-  "获取名称为 SCHEME-NAME 的输入法方案。"
+  "获取名称为 SCHEME-NAME 的 scheme."
   (when scheme-name
-    (assoc scheme-name pyim-schemes)))
-
-(defun pyim-scheme-name (&optional default)
-  "获取输入法 scheme"
-  (let (scheme-name)
-    (if (and pyim-assistant-scheme-enable
-             (not default))
-        (setq scheme-name
-              (or pyim-assistant-scheme
-                  pyim-default-scheme))
-      (setq scheme-name pyim-default-scheme))
-    (if (assq scheme-name pyim-schemes)
-        scheme-name
-      'quanpin)))
-
-(defun pyim-scheme-get-option (scheme-name option)
-  "获取名称为 SCHEME-NAME 的输入法方案,并提取其属性 OPTION 。"
-  (when scheme-name
-    (let* ((scheme (pyim-scheme-get scheme-name))
-           (scheme-inherit
-            (car (pyim-scheme-get
-                  (plist-get (cdr scheme) :inherit)))))
-      (if (member option (cdr scheme))
-          (plist-get (cdr scheme) option)
-        (pyim-scheme-get-option scheme-inherit option)))))
+    (cl-find-if
+     (lambda (x)
+       (equal (pyim-scheme-name x) scheme-name))
+     pyim-schemes)))
 
 (pyim-scheme-add
  '(quanpin
@@ -119,7 +162,7 @@
 (pyim-scheme-add
  '(wubi
    :document "五笔输入法。"
-   :class xingma
+   :class wubi
    :first-chars "abcdefghijklmnopqrstuvwxyz"
    :rest-chars "abcdefghijklmnopqrstuvwxyz'"
    :code-prefix "wubi/" ;五笔词库中所有的 code 都以 "wubi/" 开头,防止和其它词库冲突。
diff --git a/pyim.el b/pyim.el
index 5be5f4c66b..eda4526175 100644
--- a/pyim.el
+++ b/pyim.el
@@ -514,15 +514,12 @@ FILE 的格式与 `pyim-dcache-export' 生成的文件格式相同,
       (progn
         (pyim-process-outcome-handle 'last-char)
         (pyim-process-terminate))
-    (let* ((class (pyim-scheme-get-option (pyim-scheme-name) :class))
-           (func (intern (format "pyim-select-word:%S" class))))
-      (if (and class (functionp func))
-          (funcall func)
-        (call-interactively #'pyim-select-word:pinyin)))))
+    (pyim-select-word-really (pyim-scheme-current))))
 
-(defun pyim-select-word:pinyin ()
+(cl-defgeneric pyim-select-word-really (scheme))
+
+(cl-defmethod pyim-select-word-really ((_scheme pyim-scheme-quanpin))
   "从选词框中选择当前词条,然后删除该词条对应拼音。"
-  (interactive)
   (pyim-process-outcome-handle 'candidate)
   (let* ((imobj (pyim-process-get-first-imobj))
          (length-selected-word
@@ -579,9 +576,8 @@ FILE 的格式与 `pyim-dcache-export' 生成的文件格式相同,
       ;; pyim 使用这个 hook 来处理联想词。
       (run-hooks 'pyim-select-finish-hook))))
 
-(defun pyim-select-word:xingma ()
+(cl-defmethod pyim-select-word-really ((_scheme pyim-scheme-xingma))
   "从选词框中选择当前词条,然后删除该词条对应编码。"
-  (interactive)
   (pyim-process-outcome-handle 'candidate)
   (if (pyim-process-with-entered-buffer
         (and (> (point) 1)
@@ -751,9 +747,9 @@ FILE 的格式与 `pyim-dcache-export' 生成的文件格式相同,
   (unless (equal input-method-function 'pyim-input-method)
     (activate-input-method 'pyim))
   (let* ((case-fold-search nil)
-         (scheme-name (pyim-scheme-name))
-         (first-chars (pyim-scheme-get-option scheme-name :first-chars))
-         (rest-chars (pyim-scheme-get-option scheme-name :rest-chars))
+         (scheme (pyim-scheme-current))
+         (first-chars (pyim-scheme-first-chars scheme))
+         (rest-chars (pyim-scheme-rest-chars scheme))
          (string (if mark-active
                      (buffer-substring-no-properties
                       (region-beginning) (region-end))
diff --git a/tests/pyim-tests.el b/tests/pyim-tests.el
index 82ed76af8b..7c11fca835 100644
--- a/tests/pyim-tests.el
+++ b/tests/pyim-tests.el
@@ -25,7 +25,36 @@
 ;; along with GNU Emacs.  If not, see <http://www.gnu.org/licenses/>.
 
 ;;; Commentary:
-;;  pyim test case.
+;; pyim test case.
+;; 1.  TODO   pyim-autoselector.el
+;; 2.  [90%]  pyim-candidates.el
+;; 3.  [95%]  pyim-cloudim.el
+;; 4.  DONE   pyim-codes.el
+;; 5.  DONE   pyim-common.el
+;; 6.  [95%]  pyim-cregexp.el
+;; 7.  [95%]  pyim-cregexp-utils.el
+;; 8.  DONE   pyim-cstring.el
+;; 9.  DONE   pyim-cstring-utils.el
+;; 10. DONE   pyim-dcache.el
+;; 11. [95%]  pyim-dhashcache.el
+;; 12. DONE   pyim-dict.el
+;; 13. IGNORE pyim-dict-manager.el
+;; 14. [20%]  pyim-dregcache.el
+;; 15. TODO   pyim.el
+;; 16. DONE   pyim-entered.el
+;; 17. DONE   pyim-imobjs.el
+;; 18. TODO   pyim-indicator.el
+;; 19. IGNORE pyim-liberime.el
+;; 20. TODO   pyim-outcome.el
+;; 21. [30%]  pyim-page.el
+;; 22. DONE   pyim-pinyin.el
+;; 23. [30%]  pyim-preview.el
+;; 24. [95%]  pyim-probe.el
+;; 25. TODO   pyim-process.el
+;; 26. DONE   pyim-punctuation.el
+;; 27. DONE   pyim-pymap.el
+;; 28. IGNORE pyim-pymap-utils.el
+;; 29  DONE   pyim-scheme.el
 
 ;;; Code:
 ;; * 代码                                                                 :code:
@@ -59,23 +88,69 @@
 ;; ** pyim-schemes 相关单元测试
 (ert-deftest pyim-tests-pyim-schemes ()
   (let ((pyim-default-scheme 'wubi))
-    (should (equal (pyim-scheme-name) 'wubi)))
+    (should (equal (pyim-scheme-name
+                    (pyim-scheme-current))
+                   'wubi)))
 
   (let ((pyim-default-scheme 'wuci))
-    (should (equal (pyim-scheme-name) 'quanpin)))
+    (should (equal (pyim-scheme-name
+                    (pyim-scheme-current))
+                   'quanpin)))
 
   (let ((pyim-default-scheme 'wubi)
         (pyim-assistant-scheme 'cangjie)
         (pyim-assistant-scheme-enable t))
-    (should (equal (pyim-scheme-name) 'cangjie)))
+    (should (equal (pyim-scheme-name
+                    (pyim-scheme-current))
+                   'cangjie)))
 
   (let ((pyim-default-scheme 'wubi)
         (pyim-assistant-scheme 'cangjie)
         (pyim-assistant-scheme-enable nil))
-    (should (equal (pyim-scheme-name) 'wubi)))
-
-  (should (equal (pyim-scheme-get-option 'quanpin :class) 'quanpin))
-  (should (equal (pyim-scheme-get-option 'wubi :class) 'xingma)))
+    (should (equal (pyim-scheme-name
+                    (pyim-scheme-current))
+                   'wubi)))
+
+  (should (equal (pyim-scheme-name
+                  (pyim-scheme-get 'quanpin))
+                 'quanpin))
+
+  (should-not (pyim-scheme-get 'quanpin1))
+
+  (should (equal (pyim-scheme-name
+                  (pyim-scheme-get 'wubi))
+                 'wubi)))
+
+(ert-deftest pyim-tests-pyim-scheme-add ()
+  (let ((pyim-schemes nil))
+    (pyim-scheme-add
+     '(quanpin
+       :document "test1"
+       :class quanpin
+       :first-chars "abcdefghijklmnopqrstuwxyz"
+       :rest-chars "vmpfwckzyjqdltxuognbhsrei'-a"
+       :prefer-triggers ("v")))
+
+    (pyim-scheme-add
+     '(quanpin
+       :document "test2"
+       :class quanpin
+       :first-chars "abcdefghijklmnopqrstuwxyz"
+       :rest-chars "vmpfwckzyjqdltxuognbhsrei'-a"
+       :prefer-triggers ("v")))
+
+    (pyim-scheme-add
+     '(quanpin1
+       :document "test3"
+       :class quanpin
+       :first-chars "abcdefghijklmnopqrstuwxyz"
+       :rest-chars "vmpfwckzyjqdltxuognbhsrei'-a"
+       :prefer-triggers ("v")))
+
+    (pyim-scheme-add "error")
+
+    (should (equal (mapcar #'pyim-scheme-document pyim-schemes)
+                   '("test2" "test3")))))
 
 ;; ** pyim-common 相关单元测试
 (ert-deftest pyim-tests-pyim-char-before/after-to-string ()
@@ -351,70 +426,229 @@
 (ert-deftest pyim-tests-pyim-imobjs ()
   (let ((pyim-pinyin-fuzzy-alist '(("en" "eng")
                                    ("in" "ing")
-                                   ("un" "ong"))))
-    (should (equal (pyim-imobjs-create "nihao" 'quanpin)
+                                   ("un" "ong")))
+        (quanpin (pyim-scheme-get 'quanpin))
+        (wubi (pyim-scheme-get 'wubi))
+        (cangjie (pyim-scheme-get 'cangjie))
+        (pyim-shuangpin (pyim-scheme-get 'pyim-shuangpin)))
+    (should (equal (pyim-imobjs-create "nihao" quanpin)
                    '((("n" "i" "n" "i") ("h" "ao" "h" "ao")))))
-    (should (equal (pyim-imobjs-create "nh" 'quanpin)
+    (should (equal (pyim-imobjs-create "nh" quanpin)
                    '((("n" "" "n" "") ("h" nil "h" nil)))))
-    (should (equal (pyim-imobjs-create "xi'an" 'quanpin)
+    (should (equal (pyim-imobjs-create "xi'an" quanpin)
                    '((("x" "i" "x" "i") ("'" "an" "'" "an")))))
-    (should (equal (pyim-imobjs-create "xian" 'quanpin)
+    (should (equal (pyim-imobjs-create "xian" quanpin)
                    '((("x" "ian" "x" "ian")))))
-    (should (equal (pyim-imobjs-create "fenyun" 'quanpin)
+    (should (equal (pyim-imobjs-create "fenyun" quanpin)
                    '((("f" "en" "f" "en") ("y" "un" "y" "un"))
                      (("f" "en" "f" "en") ("y" "ong" "y" "un"))
                      (("f" "eng" "f" "en") ("y" "un" "y" "un"))
                      (("f" "eng" "f" "en") ("y" "ong" "y" "un")))))
-    (should (equal (pyim-imobjs-create "xian" 'wubi)
+    (should (equal (pyim-imobjs-create "xian" wubi)
                    '(("xian"))))
-    (should (equal (pyim-imobjs-create "xian" 'cangjie)
+    (should (equal (pyim-imobjs-create "xian" cangjie)
                    '(("xian"))))
-    (should (equal (pyim-imobjs-create "nihc" 'pyim-shuangpin)
+    (should (equal (pyim-imobjs-create "nihc" pyim-shuangpin)
                    '((("n" "i" "n" "i") ("h" "ao" "h" "c")))))))
 
 ;; ** pyim-codes 相关单元测试
 (ert-deftest pyim-tests-pyim-codes ()
-  (should (equal (pyim-codes-create
-                  (car (pyim-imobjs-create "nihao" 'quanpin))
-                  'quanpin)
-                 '("ni" "hao")))
-  (should (equal (pyim-codes-create
-                  (car (pyim-imobjs-create "aaaa" 'wubi))
-                  'wubi)
-                 '("wubi/aaaa")))
-  (should (equal (pyim-codes-create
-                  (car (pyim-imobjs-create "aaaa" 'wubi))
-                  'wubi 2)
-                 '("wubi/aa")))
-  (should (equal (pyim-codes-create
-                  (car (pyim-imobjs-create "aaaa" 'wubi))
-                  'wubi 1)
-                 '("wubi/a")))
-  (should (equal (pyim-codes-create
-                  (car (pyim-imobjs-create "aaaa" 'cangjie))
-                  'cangjie)
-                 '("cangjie/aaaa"))))
+  (let ((quanpin (pyim-scheme-get 'quanpin))
+        (wubi (pyim-scheme-get 'wubi))
+        (cangjie (pyim-scheme-get 'cangjie))
+        (pyim-shuangpin (pyim-scheme-get 'pyim-shuangpin)))
+    (should (equal (pyim-codes-create
+                    (car (pyim-imobjs-create "nihao" quanpin))
+                    quanpin)
+                   '("ni" "hao")))
+    (should (equal (pyim-codes-create
+                    (car (pyim-imobjs-create "aaaa" wubi))
+                    wubi)
+                   '("wubi/aaaa")))
+    (should (equal (pyim-codes-create
+                    (car (pyim-imobjs-create "aaaa" wubi))
+                    wubi 2)
+                   '("wubi/aa")))
+    (should (equal (pyim-codes-create
+                    (car (pyim-imobjs-create "aaaa" wubi))
+                    wubi 1)
+                   '("wubi/a")))
+    (should (equal (pyim-codes-create
+                    (car (pyim-imobjs-create "aaaa" cangjie))
+                    cangjie)
+                   '("cangjie/aaaa")))))
 
 ;; ** pyim-candidates 相关单元测试
+(ert-deftest pyim-tests-pyim-candidates-get-chief ()
+  (let ((wubi (pyim-scheme-get 'wubi))
+        (personal-words1 '("呵呵" "天" "恭恭敬敬"))
+        (personal-words2 '("呵呵" "恭恭敬敬"))
+        (common-words1 '("工" "恭恭敬敬"))
+        (common-words2 '("恭恭敬敬" "工")))
+
+    ;; 形码输入法选择第一位词条的规则是:
+    ;; 1. 如果从公共词库里面获取到的第一个词条是汉字,就选择它。
+    ;; 2. 如果不是,就从个人词库里面按排列的先后顺序,获取一个汉字。
+    (should (equal (pyim-candidates-get-chief wubi personal-words1 
common-words1)
+                   "工"))
+    (should (equal (pyim-candidates-get-chief wubi personal-words1 
common-words2)
+                   "天"))
+    (should-not (pyim-candidates-get-chief wubi personal-words2 
common-words2)))
+
+  (let ((quanpin (pyim-scheme-get 'quanpin))
+        (pyim-dhashcache-iword2count-recent-10-words
+         (read "#s(hash-table size 65 test equal rehash-size 1.5 
rehash-threshold 0.8125 data (:all-words (\"就\" \"不是\" \"如果\" \"是\" \"规则\" 
\"的\" \"词条\" \"第一位\" \"选择\" \"输入法\") \"如果\" 1 \"的\" 1 \"就\" 1 \"输入法\" 2 \"词条\" 
1 \"不是\" 1 \"选择\" 1 \"第一位\" 1 \"规则\" 1 \"是\" 1))"))
+        (pyim-dhashcache-iword2count-recent-50-words
+         (read "#s(hash-table size 65 test equal rehash-size 1.5 
rehash-threshold 0.8125 data (:all-words (\"就\" \"不是\" \"如果\" \"是\" \"规则\" 
\"的\" \"词条\" \"第一位\" \"选择\" \"输入法\" \"形码\" \"天\" \"网\" \"呵呵\" \"工\" \"你\" 
\"天空\" \"蓝天\" \"大地\" \"不好\" \"我\" \"你好\") \"你好\" 1 \"你\" 3 \"我\" 1 \"不好\" 1 
\"天空\" 2 \"天\" 3 \"大地\" 1 \"蓝天\" 4 \"工\" 2 \"呵呵\" 1 \"网\" 1 \"形码\" 1 \"输入法\" 1 
\"选择\" 1 \"第一位\" 1 \"词条\" 1 \"的\" 1 \"规则\" 1 \"是\" 1 \"如果\" 1 \"不是\" 1 \"就\" 
1))"))
+        (personal-words1 '("输入罚" "输入法"))
+        (personal-words2 '("蓝田" "蓝天"))
+        (personal-words3 '("美丽" "魅力")))
+
+    ;; 1. 最近输入的10个不同的词中出现一次以上。
+    (should (equal (pyim-candidates-get-chief quanpin personal-words1 nil)
+                   "输入法"))
+    ;; 2. 最近输入的50个不同的词中出现过三次以上。
+    (should (equal (pyim-candidates-get-chief quanpin personal-words2 nil)
+                   "蓝天"))
+    ;; 3. 个人词条中的第一个词。
+    (should (equal (pyim-candidates-get-chief quanpin personal-words3 nil)
+                   "美丽"))))
+
+(ert-deftest pyim-tests-pyim-candidates-create-xingma ()
+  (let ((wubi (pyim-scheme-get 'wubi))
+        (pyim-dhashcache-icode2word
+         (read "#s(hash-table size 1642 test equal rehash-size 1.5 
rehash-threshold 0.8125 data (\"wubi/aaaa\" (\"工\" \"㠭\") \"wubi/bbbb\" (\"子\" 
\"子子孙孙孙孙\") \"wubi/cccc\" (\"又\" \"叕\")))"))
+        (pyim-dhashcache-code2word
+         (read "#s(hash-table size 1642 test equal rehash-size 1.5 
rehash-threshold 0.8125 data (\"wubi/aaaa\" (\"㠭\") \"wubi/bbbb\" (\"子子孙孙\" 
\"子\") \"wubi/cccc\" (\"叕\" \"又\")))")))
+    (should (equal (pyim-candidates-create
+                    (pyim-imobjs-create "aaaa" wubi)
+                    wubi)
+                   '("㠭" "工")))
+    (should (equal (pyim-candidates-create
+                    (pyim-imobjs-create "bbbb" wubi)
+                    wubi)
+                   '("子" "子子孙孙孙孙" "子子孙孙")))
+    (should (equal (pyim-candidates-create
+                    (pyim-imobjs-create "cccc" wubi)
+                    wubi)
+                   '("叕" "又")))
+    (should (equal (pyim-candidates-create
+                    (pyim-imobjs-create "aaaabbbb" wubi)
+                    wubi)
+                   '("㠭子" "㠭子子孙孙孙孙" "㠭子子孙孙")))
+    (should (equal
+             (pyim-candidates-create
+              (pyim-imobjs-create "aaaabbbbcccc" wubi)
+              wubi)
+             '("㠭子叕" "㠭子又")))))
+
+(ert-deftest pyim-tests-pyim-candidates-znabc-words ()
+  (let* ((pyim-dhashcache-code2word (make-hash-table :test #'equal))
+         (pyim-dhashcache-icode2word (make-hash-table :test #'equal))
+         (quanpin (pyim-scheme-get 'quanpin))
+         (imobjs (pyim-imobjs-create "nihaomapengyou" quanpin)))
+    (puthash "ni-hao" (list "你好" "尼耗") pyim-dhashcache-code2word)
+    (puthash "ni-hao-ma" (list "你好吗" "你好马") pyim-dhashcache-code2word)
+    (puthash "ni-hao-ma-peng-you" (list "你好吗朋友" "你好吗喷油") 
pyim-dhashcache-code2word)
+    (should (equal (pyim-candidates-znabc-words imobjs quanpin)
+                   '("你好吗朋友" "你好吗" "你好" "你好吗喷油" "你好马" "尼耗")))
+    (should (equal (pyim-candidates-znabc-words imobjs quanpin t)
+                   '("你好吗朋友" "你好吗" "你好")))))
+
+(ert-deftest pyim-tests-pyim-candidates-jianpin-words ()
+  (let* ((pyim-dhashcache-code2word (make-hash-table :test #'equal))
+         (pyim-dhashcache-icode2word (make-hash-table :test #'equal))
+         (pyim-dhashcache-ishortcode2word (make-hash-table :test #'equal))
+         (quanpin (pyim-scheme-get 'quanpin))
+         (imobjs1 (pyim-imobjs-create "nih" quanpin))
+         (imobjs2 (pyim-imobjs-create "ni" quanpin)))
+    (puthash "n-h" (list "你好" "你坏" "尼耗" "南好" "内核" "内河") 
pyim-dhashcache-ishortcode2word)
+    (should (equal (pyim-candidates-jianpin-words imobjs1 quanpin)
+                   '("你好" "你坏" "尼耗")))
+    (should-not (pyim-candidates-jianpin-words imobjs2 quanpin))))
+
+(ert-deftest pyim-tests-pyim-candidates-get-dcache-words ()
+  (let* ((pyim-dhashcache-code2word (make-hash-table :test #'equal))
+         (pyim-dhashcache-icode2word (make-hash-table :test #'equal))
+         (pyim-dhashcache-shortcode2word (make-hash-table :test #'equal))
+         (pyim-dhashcache-ishortcode2word (make-hash-table :test #'equal))
+         (quanpin (pyim-scheme-get 'quanpin))
+         (imobjs1 (pyim-imobjs-create "n" quanpin))
+         (imobjs2 (pyim-imobjs-create "ni" quanpin))
+         (imobjs3 (pyim-imobjs-create "ni-hao" quanpin)))
+    (puthash "n" (list "你" "您" "妮") pyim-dhashcache-ishortcode2word)
+    (puthash "ni" (list "你" "尼") pyim-dhashcache-icode2word)
+    (puthash "ni" (list "尼" "你") pyim-dhashcache-code2word)
+    (puthash "ni-hao" (list "你好" "尼耗" "呢耗") pyim-dhashcache-icode2word)
+    (puthash "ni-hao" (list "你好" "尼耗") pyim-dhashcache-code2word)
+    (puthash "n-h" (list "你好" "你坏" "尼耗" "南好" "内核" "内河") 
pyim-dhashcache-ishortcode2word)
+    (should (equal (pyim-candidates-dcache-quanpin-words imobjs1 quanpin nil 
10)
+                   '(("你" "您" "妮") nil nil ("南" "乃" "囊" "脑" "呢" "内" "嫩" "能" 
"你" "年"))))
+    (should (equal (pyim-candidates-dcache-quanpin-words imobjs2 quanpin nil 
10)
+                   '(("你" "尼") ("尼" "你") ("你" "尼" "尼" "你") nil)))
+    (should (equal (pyim-candidates-dcache-quanpin-words imobjs3 quanpin nil 
10)
+                   '(("你好" "尼耗" "呢耗") ("你好" "尼耗") ("你好" "尼耗" "呢耗" "你好" "尼耗") 
nil)))))
+
+(ert-deftest pyim-tests-pyim-candidates-quanpin-personal-words ()
+  (let* ((pyim-dhashcache-icode2word (make-hash-table :test #'equal))
+         (pyim-dhashcache-ishortcode2word (make-hash-table :test #'equal))
+         (quanpin (pyim-scheme-get 'quanpin))
+         (imobjs1 (pyim-imobjs-create "n" quanpin))
+         (imobjs2 (pyim-imobjs-create "ni" quanpin))
+         (imobjs3 (pyim-imobjs-create "nh" quanpin)))
+    (puthash "n" (list "你" "您" "妮") pyim-dhashcache-ishortcode2word)
+    (puthash "ni" (list "你" "尼") pyim-dhashcache-icode2word)
+    (puthash "n-h" (list "呢耗") pyim-dhashcache-icode2word)
+    (puthash "n-h" (list "你好" "你坏" "尼耗") pyim-dhashcache-ishortcode2word)
+    (should (equal (pyim-candidates-quanpin-personal-words (car imobjs1) 
quanpin)
+                   '("你" "您" "妮")))
+    (should (equal (pyim-candidates-quanpin-personal-words (car imobjs2) 
quanpin)
+                   '("你" "尼")))
+    (should (equal (pyim-candidates-quanpin-personal-words (car imobjs3) 
quanpin)
+                   '("呢耗" "你好" "你坏" "尼耗")))))
+
+(ert-deftest pyim-tests-pyim-candidates-quanpin-common-words ()
+  (let* ((pyim-dhashcache-code2word (make-hash-table :test #'equal))
+         (pyim-dhashcache-shortcode2word (make-hash-table :test #'equal))
+         (quanpin (pyim-scheme-get 'quanpin))
+         (imobjs1 (pyim-imobjs-create "n" quanpin))
+         (imobjs2 (pyim-imobjs-create "ni" quanpin))
+         (imobjs3 (pyim-imobjs-create "nh" quanpin)))
+    (puthash "n" (list "你" "您" "妮") pyim-dhashcache-shortcode2word)
+    (puthash "ni" (list "你" "尼") pyim-dhashcache-code2word)
+    (puthash "n-h" (list "呢耗") pyim-dhashcache-code2word)
+    (puthash "n-h" (list "你好" "你坏" "尼耗") pyim-dhashcache-shortcode2word)
+    (should (equal (pyim-candidates-quanpin-common-words (car imobjs1) quanpin)
+                   '("你" "您" "妮")))
+    (should (equal (pyim-candidates-quanpin-common-words (car imobjs2) quanpin)
+                   '("你" "尼")))
+    (should (equal (pyim-candidates-quanpin-common-words (car imobjs3) quanpin)
+                   '("呢耗" "你好" "你坏" "尼耗")))))
+
+(ert-deftest pyim-tests-pyim-candidates-quanpin-chars ()
+  (let* ((pyim-dhashcache-icode2word (make-hash-table :test #'equal))
+         (pyim-dhashcache-code2word (make-hash-table :test #'equal))
+         (quanpin (pyim-scheme-get 'quanpin))
+         (imobjs (pyim-imobjs-create "nihao" quanpin)))
+    (puthash "ni" (list "你" "呢") pyim-dhashcache-icode2word)
+    (puthash "ni" (list "你" "尼") pyim-dhashcache-code2word)
+    (should (equal (pyim-candidates-quanpin-chars (car imobjs) quanpin)
+                   '("你" "呢" "你" "尼")))))
+
+(ert-deftest pyim-tests-pyim-candidates-pymap-chars ()
+  (should (equal (pyim-candidates-pymap-chars "ni" 10)
+                 '("你" "年" "娘" "鸟" "摄" "您" "宁" "牛" "尼" "念"))))
+
 (ert-deftest pyim-tests-pyim-candidates-search-buffer ()
   (with-temp-buffer
     (insert "你好你好你坏你坏你话牛蛤牛和牛蛤牛蛤牛蛤牛蛤牛蛤")
     (should (equal (pyim-candidates-search-buffer (pyim-cregexp-build "nh" 3 
t))
                    '("牛蛤" "你坏" "你好" "牛和" "你话")))
     (let ((words (pyim-candidates-search-buffer (pyim-cregexp-build "nh" 3 
t))))
-      (should (equal (get-text-property 0 :comment (car words)) "(Buf)")))))
+      (should (equal (get-text-property 0 :comment (car words)) "(buf)")))))
 
 (ert-deftest pyim-tests-pyim-candidates-cloud-search ()
-  (let ((pyim-candidates-cloud-search-function
-         (lambda (x y)
-           (list x y "b"))))
-    (should (equal (pyim-candidates-cloud-search "a" 'quanpin) '("a" quanpin 
"b"))))
-
-  (let ((pyim-candidates-cloud-search-function nil))
-    (should (not (pyim-candidates-cloud-search "a" 'quanpin))))
-
-  (let ((pyim-candidates-cloud-search-function "xxx"))
-    (should (not (pyim-candidates-cloud-search "a" 'quanpin)))))
+  (should-not (pyim-candidates-cloud-search "a" t)))
 
 ;; ** pyim-cstring 相关单元测试
 (ert-deftest pyim-tests-pyim-cstring-partition ()
@@ -528,15 +762,20 @@
                            "Hello -yin-hang-hen-hang- Hi")))))
 
 (ert-deftest pyim-tests-pyim-cstring-to-xingma ()
-  (let ((pyim-dhashcache-word2code (make-hash-table :test #'equal)))
+  (let ((pyim-dhashcache-word2code (make-hash-table :test #'equal))
+        (wubi (pyim-scheme-get 'wubi))
+        (cangjie (pyim-scheme-get 'cangjie)))
     (puthash "工" (list "wubi/aaaa" "cangjie/mlm" "gong") 
pyim-dhashcache-word2code)
     (puthash "房" (list "wubi/yny") pyim-dhashcache-word2code)
     (puthash "丛" (list "wubi/wwg") pyim-dhashcache-word2code)
-    (should (equal (pyim-cstring-to-xingma "工" 'wubi) "aaaa"))
-    (should (equal (pyim-cstring-to-xingma "工房" 'wubi) "aayn"))
-    (should (equal (pyim-cstring-to-xingma "工房丛" 'wubi) "ayww"))
-    (should (equal (pyim-cstring-to-xingma "工房丛房" 'wubi) "aywy"))
-    (should (equal (pyim-cstring-to-xingma "工" 'wubi t) '("aaaa")))))
+    (should (equal (pyim-cstring-to-xingma "工" cangjie) "mlm"))
+    (should-not (pyim-cstring-to-xingma "工房" cangjie))
+    (should (equal (pyim-cstring-to-xingma "工" wubi) "aaaa"))
+    (should (equal (pyim-cstring-to-xingma "工房" wubi) "aayn"))
+    (should (equal (pyim-cstring-to-xingma "工房丛" wubi) "ayww"))
+    (should (equal (pyim-cstring-to-xingma "工房丛房" wubi) "aywy"))
+    (should (equal (pyim-cstring-to-xingma "工" wubi t) '("aaaa")))
+    (should (equal (pyim-cstring-to-xingma "工房" wubi t) '("aayn")))))
 
 (ert-deftest pyim-tests-pyim-cstring-words-at-point ()
   (let ((pyim-dhashcache-code2word (make-hash-table :test #'equal)))
@@ -633,8 +872,8 @@
     (should-not (string-match-p regexp2 str)))
 
   (let* ((imobj '(("d" "a" "d" "a") ("w" "ang" "w" "ang")))
-         (regexp1 (pyim-cregexp-build:quanpin imobj))
-         (regexp2 (pyim-cregexp-build:quanpin imobj nil nil t)))
+         (regexp1 (pyim-cregexp-build-quanpin imobj))
+         (regexp2 (pyim-cregexp-build-quanpin imobj nil nil t)))
     (should (string-match-p regexp1 "大王"))
     (should (string-match-p regexp1 "当王"))
     (should (string-match-p regexp2 "大王"))
@@ -1013,7 +1252,7 @@ yin-xing 因行
 
     ;; test dregcache api
     (setq words (pyim-dcache-get "a"))
-    (should (eq (length words) 16))
+    (should (eq (length words) 8))
     (should (string= (nth 0 words) "阿"))
 
     (setq  words (pyim-dcache-get "za-cao"))
@@ -1035,9 +1274,9 @@ yin-xing 因行
       (message "search by code \"zun-yi\" takes %s seconds" 
(benchmark-run-compiled 1 (pyim-dcache-get "zun-yi"))))
 
     ;; `pyim-dregcache-get' calls `pyim-pymap-py2cchar-get' before return 
result
-    (should (eq (length words) 51))))
-
+    (should (eq (length words) 26))))
 
+;; ** pyim-cloudim 相关单元测试
 (ert-deftest pyim-tests-pyim-cloudim ()
   (with-temp-buffer
     (insert "HTTP/1.1 200 OK
@@ -1070,20 +1309,266 @@ Transfer-Encoding: chunked
     (should (equal (get-text-property 0 :comment (car 
(pyim-cloudim-parse-google-buffer))) "(云)")))
 
   (when (not noninteractive)
-    (should (equal (pyim-cloudim:baidu "nihao" 'quanpin) '("你好")))
-    (should (equal (pyim-cloudim:google "nihao" 'quanpin) '("你好")))
+    (let ((wubi (pyim-scheme-get 'wubi)))
+      (should (equal (pyim-candidates-cloud-search "nihao" wubi) nil))
+      (should (equal (pyim-candidates-cloud-search "nihao" wubi) nil)))
+
+    (let ((pyim-cloudim 'baidu)
+          (quanpin (pyim-scheme-get 'quanpin)))
+      (should (equal (pyim-candidates-cloud-search "nihao" quanpin) '("你好"))))
+
+    (let ((pyim-cloudim 'google)
+          (quanpin (pyim-scheme-get 'quanpin)))
+      (should (equal (pyim-candidates-cloud-search "nihao" quanpin) '("你好"))))
+
+    (let ((pyim-cloudim 'xxx)
+          (quanpin (pyim-scheme-get 'quanpin)))
+      (should (not (pyim-candidates-cloud-search "nihao" quanpin))))
+
+    (let ((pyim-cloudim nil)
+          (quanpin (pyim-scheme-get 'quanpin)))
+      (should (not (pyim-candidates-cloud-search "nihao" quanpin))))))
+
+;; ** pyim-probe 相关单元测试
+(ert-deftest pyim-tests-pyim-probe-program-mode ()
+  (with-temp-buffer
+    (emacs-lisp-mode)
+    (insert ";; comment")
+    (should-not (pyim-probe-program-mode))
+    (insert "\n")
+    (should (pyim-probe-program-mode))
+    (insert "()")
+    (backward-char 1)
+    (should (pyim-probe-program-mode))
+    (insert "setq test")
+    (should (pyim-probe-program-mode))
+    (insert " \"\"")
+    (backward-char 1)
+    (should-not (pyim-probe-program-mode))))
+
+(ert-deftest pyim-tests-pyim-probe-program-mode ()
+  ;; Isearch mode 不好写测试,这里假设 isearch 命令运行时,至少有一个 buffer 中
+  ;; 变量 isearch-mode 取值为 t. 参考了 `isearch-define-mode-toggle'.
+  (let ((pyim-isearch-mode t))
+    (with-current-buffer (get-buffer-create "test")
+      (setq-local isearch-mode t))
+    (should (pyim-probe-isearch-mode))))
+
+(ert-deftest pyim-tests-pyim-probe-org-speed-commands ()
+  (when (> emacs-major-version 25)
+    (with-temp-buffer
+      (let ((org-use-speed-commands t))
+        (org-mode)
+        (insert "* heading")
+        (goto-char (line-beginning-position))
+        (should (pyim-probe-org-speed-commands))
+        (forward-char 1)
+        (should-not (pyim-probe-org-speed-commands))
+        (forward-char 1)
+        (should-not (pyim-probe-org-speed-commands))))))
+
+(ert-deftest pyim-tests-pyim-probe-org-structure-template ()
+  (with-temp-buffer
+    (org-mode)
+    (insert "<")
+    (should (pyim-probe-org-structure-template))
+    (insert "abcde")
+    (should (pyim-probe-org-structure-template))
+    (insert " ")
+    (should-not (pyim-probe-org-structure-template))
+
+    (erase-buffer)
+
+    (insert "    <")
+    (should (pyim-probe-org-structure-template))
+    (insert "abcde")
+    (should (pyim-probe-org-structure-template))
+    (insert " ")
+    (should-not (pyim-probe-org-structure-template))))
+
+(ert-deftest pyim-tests-pyim-probe-dynamic-english ()
+  (with-temp-buffer
+    ;; 从光标往前找第一个非数字的字符,为其他字符时,输入下一个字符时默认开启英文输入
+    (insert "english")
+    (should (pyim-probe-dynamic-english))
 
-    (let ((pyim-cloudim 'baidu))
-      (should (equal (pyim-cloudim "nihao" 'quanpin) '("你好"))))
+    (insert "123")
+    (should (pyim-probe-dynamic-english))
 
-    (let ((pyim-cloudim 'google))
-      (should (equal (pyim-cloudim "nihao" 'quanpin) '("你好"))))
+    (insert ",")
+    (should (pyim-probe-dynamic-english))
+
+    (insert "  ")
+    (should (pyim-probe-dynamic-english))
+
+    ;; 从光标往前找第一个非数字的字符,为中文字符时,输入下一个字符时默认开启中文输入
+    (insert "中文")
+    (should-not (pyim-probe-dynamic-english))))
+
+(ert-deftest pyim-tests-pyim-probe-auto-english ()
+  (with-temp-buffer
+    ;; 1. 当前字符为英文字符(不包括空格)时,输入下一个字符为英文字符
+    (insert "english")
+    (should (pyim-probe-auto-english))
+    ;; 当前字符为中文字符或输入字符为行首字符时,输入的字符为中文字符
+    (insert "\n")
+    (should-not (pyim-probe-auto-english))
+
+    (insert "中文")
+    (should-not (pyim-probe-auto-english))
+    ;; 以单个空格为界,自动切换中文和英文字符
+
+    (insert " ")
+    (should (pyim-probe-auto-english))
+
+    (insert "english ")
+    (should-not (pyim-probe-auto-english))
+
+    (insert "中文 ")
+    (should (pyim-probe-auto-english))))
+
+(ert-deftest pyim-tests-pyim-probe-evil-normal-mode ()
+  (when (featurep 'evil)
+    (with-temp-buffer
+      (evil-local-mode)
+      (should (pyim-probe-evil-normal-mode))
+      (evil-local-mode -1)
+      (should-not (pyim-probe-evil-normal-mode)))))
+
+(ert-deftest pyim-tests-pyim-probe-punctuation-line-beginning ()
+  (with-temp-buffer
+    (should (pyim-probe-punctuation-line-beginning ?.))
+    (insert "fff")
+    (should-not (pyim-probe-punctuation-line-beginning ?.))
+    (insert "\n")
+    (should (pyim-probe-punctuation-line-beginning ?.))))
+
+(ert-deftest pyim-tests-pyim-probe-punctuation-after-punctuation ()
+  (with-temp-buffer
+    (should-not (pyim-probe-punctuation-after-punctuation ?.))
+    (insert "'")
+    (should (pyim-probe-punctuation-after-punctuation ?.))
+    (insert "abc")
+    (should-not (pyim-probe-punctuation-after-punctuation ?.))
+    (insert "123")
+    (should-not (pyim-probe-punctuation-after-punctuation ?.))
+    (insert "。")
+    (should-not (pyim-probe-punctuation-after-punctuation ?.))
+    (insert ".")
+    (should (pyim-probe-punctuation-after-punctuation ?.))))
+
+(ert-deftest pyim-tests-pyim-probe-org-latex-mode ()
+  (when (> emacs-major-version 25)
+    (with-temp-buffer
+      (org-mode)
+      (insert "\\begin{equation}")
+      (save-excursion
+        (insert "\\end{equation}"))
+      (should (pyim-probe-org-latex-mode))
+
+      (erase-buffer)
+      (insert "$$")
+      (backward-char 1)
+      (should (pyim-probe-org-latex-mode))
+
+      (erase-buffer)
+      (insert "\\documentclass{article}")
+      (should (pyim-probe-org-latex-mode))))
 
-    (let ((pyim-cloudim 'xxx))
-      (should (not (pyim-cloudim "nihao" 'quanpin))))
+  (when (< emacs-major-version 26)
+    (should-not (pyim-probe-org-latex-mode))))
+
+(ert-deftest pyim-tests-pyim-probe-exwm-xim-environment ()
+  (with-temp-buffer
+    (setq-local exwm-xim-buffer-p t)
+    (should (pyim-probe-exwm-xim-environment))
+    (setq-local exwm-xim-buffer-p nil)
+    (should-not (pyim-probe-exwm-xim-environment))))
+
+(ert-deftest pyim-tests-pyim-probe-xwidget-webkit-environment ()
+  ;; TODO
+  )
+
+;; ** pyim-probe 相关单元测试
+(ert-deftest pyim-tests-pyim-page-get-page-style ()
+  (let ((pyim-page-tooltip-style-alist
+         '((minibuffer . minibuffer)))
+        (pyim-page-style 'test))
+    (should (equal (pyim-page-get-page-style 'minibuffer)
+                   'minibuffer))
+    (should (equal (pyim-page-get-page-style 'test)
+                   'test))))
+
+(ert-deftest pyim-tests-pyim-page-info-format ()
+  (let ((page-info (make-hash-table)))
+    (puthash :scheme (pyim-scheme-get 'quanpin) page-info)
+    (puthash :current-page 1 page-info)
+    (puthash :total-page 26 page-info)
+    (puthash :candidates '("你好" "尼耗" "您耗" "您好" "你") page-info)
+    (puthash :position 3 page-info)
+    (puthash :hightlight-current 'hightlight-current page-info)
+    (puthash :assistant-enable nil page-info)
+
+    (should (equal (pyim-page-info-format 'two-lines page-info)
+                   "=> | [1/26]: 
+1.你好 2.尼耗 3[您耗]4.您好 5.你 "))
+    (should (equal (pyim-page-info-format 'one-line page-info)
+                   "[|]: 1.你好 2.尼耗 3[您耗]4.您好 5.你 (1/26)"))
+    (should (equal (pyim-page-info-format 'vertical page-info)
+                   "=> | [1/26]: 
+1.你好 
+2.尼耗 
+3[您耗]
+4.您好 
+5.你 "))
+    (should (equal (pyim-page-info-format 'minibuffer page-info)
+                   "[|              ]: 1.你好 2.尼耗 3[您耗]4.您好 5.你 (1/26) $ "))))
+
+(ert-deftest pyim-tests-pyim-page-menu-create ()
+  (should
+   (equal (pyim-page-menu-create '("你好" "尼耗" "您耗" "您好" "你") 0 nil t)
+          #("1.你好 2.尼耗 3.您耗 4.您好 5.你 " 13 17 (face pyim-page-selection))))
+  (should
+   (equal (pyim-page-menu-create '("你好" "尼耗" "您耗" "您好" "你") 1 nil t)
+          #("1[你好]2.尼耗 3.您耗 4.您好 5.你 " 1 5 (face pyim-page-selection))))
+  (should
+   (equal (pyim-page-menu-create '("你好" "尼耗" "您耗" "您好" "你") 3 nil t)
+          #("1.你好 2.尼耗 3[您耗]4.您好 5.你 " 11 15 (face pyim-page-selection)))))
+
+;; ** pyim-preview 相关单元测试
+(ert-deftest pyim-tests-pyim-preview-string ()
+  (let ((pyim-candidates '("世界" "时节" "使节" "视界" ))
+        (pyim-candidate-position 1)
+        (pyim-outcome-history '("你好"))
+        (pyim-imobjs '((("sh" "i" "sh" "i") ("j" "ie" "j" "ie"))))
+        (scheme (pyim-scheme-get 'quanpin)))
+    (should (equal (pyim-preview-string scheme)
+                   "你好世界")))
+
+  (let ((pyim-candidates '("世界" "时节" "使节" "视界" ))
+        (pyim-candidate-position 2)
+        (pyim-outcome-history nil)
+        (pyim-imobjs '((("sh" "i" "sh" "i") ("j" "ie" "j" "ie"))))
+        (scheme (pyim-scheme-get 'quanpin)))
+    (should (equal (pyim-preview-string scheme)
+                   "时节")))
+
+  (let ((pyim-candidates '("这是" "蛰是" "这时" "真实" "这使" "这事" "这" "者" "着" "折" "哲" 
"浙" "遮"))
+        (pyim-candidate-position 10)
+        (pyim-outcome-history nil)
+        (pyim-imobjs '((("zh" "e" "zh" "e") ("sh" "i" "sh" "i"))))
+        (scheme (pyim-scheme-get 'quanpin)))
+    (should (equal (pyim-preview-string scheme)
+                   "折shi")))
+
+  (let ((pyim-candidates '("工" "藏匿" "工工" "花花草草" "㠭"))
+        (pyim-candidate-position 4)
+        (pyim-outcome-history nil)
+        (pyim-imobjs '(("aaaa")))
+        (scheme (pyim-scheme-get 'wubi)))
+    (should (equal (pyim-preview-string scheme)
+                   "花花草草"))))
 
-    (let ((pyim-cloudim nil))
-      (should (not (pyim-cloudim "nihao" 'quanpin))))))
 
 (ert-run-tests-batch-and-exit)
 ;; * Footer



reply via email to

[Prev in Thread] Current Thread [Next in Thread]