diff --git a/NOTICE b/NOTICE
index f33068961dde04afb77842ec07d6ab305b859cab..00d12340789b7be42239d8ad37666ab8d8c2d326 100644
--- a/NOTICE
+++ b/NOTICE
@@ -1,5 +1,6 @@
 zero-el
 Copyright (C) 2019 Yuanle Song <sylecn@gmail.com>
 
-zero--ibus-compute-pixel-position function in zero.el is copied from ibus.el.
-This function is under GPLv3 license.  Copyright (C) 2010-2012 S. Irie
+zero-input--ibus-compute-pixel-position function in zero-input.el is copied
+from ibus.el.  This function is under GPLv3 license.  Copyright (C) 2010-2012
+S. Irie
diff --git a/README b/README
index cecd0e271a77061e28d9f52c328d3d6b93ce59ad..d0cb3d7af67fdb8ad5dc9e0c50b2a9553f5e30bc 100644
--- a/README
+++ b/README
@@ -1,47 +1,14 @@
 * COMMENT -*- mode: org -*-
 #+Date: 2019-09-01
-Time-stamp: <2019-10-16>
+Time-stamp: <2019-10-28>
 
 * zero-el
 
-zero-el provides zero-pinyin, an Emacs pinyin input method for Chinese and
-zero-framework, which is an emacs Chinese input method framework.
+zero-el provides zero-input-pinyin, an Emacs pinyin input method for Chinese
+and zero-input-framework, which is an emacs Chinese input method framework.
 
-* File list
-- zero.el
-
-  It's a generated file for one-file package distribution. Not used for
-  development.
-
-- zero-framework.el
-
-  zero framework source code. This provides the framework and user interface
-  for zero-el.
-
-- zero-panel.el
-
-  dbus client to zero-panel service.
-
-- zero-pinyin-service.el
-
-  dbus client to zero-pinyin-service.
-
-- zero-pinyin.el
-
-  Pinyin input method implemented using zero.el
-
-- zero-quickdial.el
-
-  proof of concept of how to create an input method in emacs using minor mode.
-
-- zero-reload-all.el
-
-  zero-el development utility.
-
-- zero-table.el
-
-  serves as an example of how to use zero framework to create new input
-  methods.
+This branch is created for one-file ELPA release. For more information, please
+check README in master branch.
 
 * introduce to zero-el
 https://blog.emacsos.com/zero-el.html
@@ -49,5 +16,5 @@ https://blog.emacsos.com/zero-el.html
 * License
 zero-el is under Apache License 2.0
 
-zero--ibus-compute-pixel-position function in zero-framework.el is under
+zero-input--ibus-compute-pixel-position function in zero-input.el is under
 GPLv3. see NOTICE file.
diff --git a/zero-input.el b/zero-input.el
new file mode 100644
index 0000000000000000000000000000000000000000..af795a45f9df7e39704316ed3ffa9b0d8e81bab7
--- /dev/null
+++ b/zero-input.el
@@ -0,0 +1,1512 @@
+;;; zero-input.el --- Zero Chinese input method framework -*- lexical-binding: t -*-
+
+;; Licensed under the Apache License, Version 2.0 (the "License");
+;; you may not use this file except in compliance with the License.
+;; You may obtain a copy of the License at
+;;
+;;     http://www.apache.org/licenses/LICENSE-2.0
+;;
+;; Unless required by applicable law or agreed to in writing, software
+;; distributed under the License is distributed on an "AS IS" BASIS,
+;; WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+;; See the License for the specific language governing permissions and
+;; limitations under the License.
+
+;; Version: 2.0.0
+;; URL: https://gitlab.emacsos.com/sylecn/zero-el
+;; Package-Version: 2.0.0
+;; Package-Requires: ((emacs "24.3") (s "1.2.0"))
+
+;;; Commentary:
+
+;; zero-input.el is auto-generated from multiple other files.  see
+;; zero-input.el.in and build.py for details.  It's created because
+;; package-lint doesn't support multi-file package yet (issue #111).
+;;
+;; zero-input is a Chinese input method framework for Emacs, implemented as an
+;; Emacs minor mode.
+;;
+;; zero-input-pinyin is bundled with zero, to use pinyin input method, add to
+;; ~/.emacs file:
+;;
+;;   (require 'zero-input-pinyin)
+;;   (zero-input-set-default-im 'pinyin)
+;;   ;; Now you may bind a key to zero-input-toggle to make it easy to
+;;   ;; switch on/off the input method.
+;;   (global-set-key (kbd "<f5>") 'zero-input-toggle)
+;;
+;; zero-input supports Chinese punctuation mapping.  There are three modes,
+;; none, basic, and full.  The default is basic mode, which only map most
+;; essential punctuations.  You can cycle zero-punctuation-level in current
+;; buffer by C-c , , You can change default Chinese punctuation level:
+;;
+;;   (setq-default zero-input-punctuation-level
+;;   *zero-input-punctuation-level-full*)
+;;
+;; zero-input supports full-width mode.  You can toggle full-width mode in
+;; current buffer by C-c , . You can enable full-width mode by default:
+;;
+;;   (setq-default zero-input-full-width-mode t)
+;;
+
+;;; Code:
+
+(require 'dbus)
+(eval-when-compile (require 'cl-lib))
+(require 's)
+
+;; body of zero-input-panel.el
+
+;;================
+;; implementation
+;;================
+
+
+(defun zero-input-panel-error-handler (event error)
+  "Handle dbus errors.
+
+EVENT and ERROR are error-handler arguments."
+  (when (or (string-equal "com.emacsos.zero.Panel"
+			  (dbus-event-interface-name event))
+	    (s-contains-p "com.emacsos.zero.Panel" (cadr error)))
+    (error "Zero-Input-panel dbus failed: %S" (cadr error))))
+
+(add-hook 'dbus-event-error-functions 'zero-input-panel-error-handler)
+
+(defun zero-input-panel-async-call (method _handler &rest args)
+  "Call METHOD on zero-input-panel service asynchronously.
+
+This is a wrapper around `dbus-call-method-asynchronously'.
+ARGS optional extra args to pass to the wrapped function."
+  (apply 'dbus-call-method-asynchronously
+	 :session
+	 "com.emacsos.zero.Panel1"	; well known name
+	 "/com/emacsos/zero/Panel1"	; object path
+	 "com.emacsos.zero.Panel1.PanelInterface" ; interface name
+	 method nil :timeout 500 args))
+
+;;=========================
+;; public utility function
+;;=========================
+
+(defun zero-input-alist-to-asv (hints)
+  "Convert Lisp alist to dbus a{sv} data structure.
+
+HINTS should be an alist of form '((k1 [v1type] v1) (k2 [v2type] v2)).
+
+For example,
+\(zero-input-alist-to-asv
+  '((\"name\" \"foo\")
+    (\"timeout\" :int32 10)))
+=>
+'(:array
+  (:dict-entry \"name\" (:variant \"foo\"))
+  (:dict-entry \"timeout\" (:variant :int32 10)))"
+  (if (null hints)
+      '(:array :signature "{sv}")
+    (let ((result '(:array)))
+      (dolist (item hints)
+	(push (list :dict-entry (car item) (cons :variant (cdr item))) result))
+      (reverse result))))
+
+;;============
+;; public API
+;;============
+
+(defun zero-input-panel-move (x y)
+  "Move panel to specific coordinate (X, Y).
+Origin (0, 0) is at screen top left corner."
+  (zero-input-panel-async-call "Move" nil :int32 x :int32 y))
+
+(defun zero-input-panel-show-candidates (preedit_str candidate_length candidates &optional hints)
+  "Show CANDIDATES.
+Argument PREEDIT_STR the preedit string.
+Argument CANDIDATE_LENGTH how many candidates are in candidates list."
+  (zero-input-panel-async-call "ShowCandidates" nil
+			 :string preedit_str
+			 :uint32 candidate_length
+			 (or candidates '(:array))
+			 (zero-input-alist-to-asv hints)))
+
+(defun zero-input-panel-show ()
+  "Show panel."
+  (zero-input-panel-async-call "Show" nil))
+
+(defun zero-input-panel-hide ()
+  "Hide panel."
+  (zero-input-panel-async-call "Hide" nil))
+
+(defun zero-input-panel-quit ()
+  "Quit panel application."
+  (interactive)
+  (zero-input-panel-async-call "Quit" nil))
+
+(provide 'zero-input-panel)
+
+;; body of zero-input-framework.el
+
+;;==============
+;; dependencies
+;;==============
+
+
+;;=======
+;; utils
+;;=======
+
+;; this function is from ibus.el
+(defun zero-input--ibus-compute-pixel-position (&optional pos window)
+  "Return geometry of object at POS in WINDOW as a list like \(X Y H).
+X and Y are pixel coordinates relative to top left corner of frame which
+WINDOW is in.  H is the pixel height of the object.
+
+Omitting POS and WINDOW means use current position and selected window,
+respectively."
+  (let* ((frame (window-frame (or window (selected-window))))
+	 (posn (posn-at-point (or pos (window-point window)) window))
+	 (line (cdr (posn-actual-col-row posn)))
+	 (line-height (and line
+			   (or (window-line-height line window)
+			       (and (redisplay t)
+				    (window-line-height line window)))))
+	 (x-y (or (posn-x-y posn)
+		  (let ((geom (pos-visible-in-window-p
+			       (or pos (window-point window)) window t)))
+		    (and geom (cons (car geom) (cadr geom))))
+		  '(0 . 0)))
+	 (ax (+ (car (window-inside-pixel-edges window))
+		(car x-y)))
+	 (ay (+ (cadr (window-pixel-edges window))
+		(or (nth 2 line-height) (cdr x-y))))
+	 (height (or (car line-height)
+		     (with-current-buffer (window-buffer window)
+		       (cond
+			;; `posn-object-width-height' returns an incorrect value
+			;; when the header line is displayed (Emacs bug #4426).
+			((and posn
+			      (null header-line-format))
+			 (cdr (posn-object-width-height posn)))
+			((and (bound-and-true-p text-scale-mode)
+			      (not (zerop (with-no-warnings
+					    text-scale-mode-amount))))
+			 (round (* (frame-char-height frame)
+				   (with-no-warnings
+				     (expt text-scale-mode-step
+					   text-scale-mode-amount)))))
+			(t
+			 (frame-char-height frame)))))))
+    (list ax ay height)))
+
+(defun zero-input-get-point-position ()
+  "Return current point's position (x y).
+Origin (0, 0) is at screen top left corner."
+  (cl-destructuring-bind (x y line-height) (zero-input--ibus-compute-pixel-position)
+    (cond
+     ((functionp 'window-absolute-pixel-position)
+      ;; introduced in emacs 26
+      (cl-destructuring-bind (x . y) (window-absolute-pixel-position)
+	(list x (+ y line-height))))
+     ((functionp 'frame-edges)
+      ;; introduced in emacs 25
+      (cl-destructuring-bind (frame-x frame-y &rest rest)
+	  (frame-edges nil 'inner-edges)
+	(list (+ frame-x x) (+ frame-y y line-height))))
+     (t
+      ;; <= emacs 24, used guessed pixel size for tool-bar, menu-bar, WM title
+      ;; bar. Since I can't get that from elisp.
+      (list (+ (frame-parameter nil 'left)
+	       (if (and (> (frame-parameter nil 'tool-bar-lines) 0)
+			(eq (frame-parameter nil 'tool-bar-position) 'left))
+		   96 0)
+	       x)
+	    (+ (frame-parameter nil 'top)
+	       (if (and (> (frame-parameter nil 'tool-bar-lines) 0)
+			(eq (frame-parameter nil 'tool-bar-position) 'top))
+		   42 0)
+	       (if (> (frame-parameter nil 'menu-bar-lines) 0) (+ 30 30) 0)
+	       line-height
+	       y))))))
+
+(defun zero-input-cycle-list (lst item)
+  "Return the object next to given ITEM in LST.
+
+If item is the last object, return the first object in lst.
+If item is not in lst, return nil."
+  (let ((r (member item lst)))
+    (cond
+     ((null r) nil)
+     (t (or (cadr r)
+	    (car lst))))))
+
+;;=====================
+;; key logic functions
+;;=====================
+
+;; zero-input-el version
+(defvar zero-input-version nil "Zero package version.")
+(setq zero-input-version "2.0.0")
+
+;; FSM state
+(defconst zero-input--state-im-off 'IM-OFF)
+(defconst zero-input--state-im-waiting-input 'IM-WAITING-INPUT)
+(defconst zero-input--state-im-preediting 'IM-PREEDITING)
+
+(defconst zero-input-punctuation-level-basic 'BASIC)
+(defconst zero-input-punctuation-level-full 'FULL)
+(defconst zero-input-punctuation-level-none 'NONE)
+
+(defvar zero-input-im nil
+  "Stores current input method.
+
+If nil, the empty input method will be used.  In the empty input
+method, only punctuation is handled.  Other keys are pass
+through")
+(defvar zero-input-ims nil
+  "A list of registered input methods.")
+
+(defvar zero-input-buffer nil
+  "Stores the associated buffer.
+this is used to help with buffer focus in/out events")
+
+(defvar zero-input-state zero-input--state-im-off)
+(defvar zero-input-full-width-mode nil
+  "Set to t to enable full-width mode.
+In full-width mode, commit ascii char will insert full-width char if there is a
+corresponding full-width char.  This full-width char map is
+independent from punctuation map.  You can change this via
+`zero-input-toggle-full-width-mode'")
+(defvar zero-input-punctuation-level zero-input-punctuation-level-basic
+  "Punctuation level.
+
+Should be one of
+`zero-input-punctuation-level-basic'
+`zero-input-punctuation-level-full'
+`zero-input-punctuation-level-none'")
+(defvar zero-input-punctuation-levels (list zero-input-punctuation-level-basic
+				      zero-input-punctuation-level-full
+				      zero-input-punctuation-level-none)
+  "Punctuation levels to use when `zero-input-cycle-punctuation-level'.")
+(defvar zero-input-double-quote-flag nil
+  "Non-nil means next double quote insert close quote.
+
+Used when converting double quote to Chinese quote.
+If nil, next double quote insert open quote.
+Otherwise, next double quote insert close quote.")
+(defvar zero-input-single-quote-flag nil
+  "Non-nil means next single quote insert close quote.
+
+Used when converting single quote to Chinese quote.
+If nil, next single quote insert open quote.
+Otherwise, next single quote insert close quote.")
+(defvar zero-input-preedit-str "")
+(defvar zero-input-candidates nil)
+(defcustom zero-input-candidates-per-page 10
+  "How many candidates to show on each page."
+  :group 'zero
+  :type 'integer)
+(defvar zero-input-current-page 0 "Current page number.  count from 0.")
+(defvar zero-input-initial-fetch-size 20
+  "How many candidates to fetch for the first call to GetCandidates.")
+;; zero-input-fetch-size is reset to 0 when preedit-str changes.
+;; zero-input-fetch-size is set to fetch-size in build-candidates-async complete-func
+;; lambda.
+(defvar zero-input-fetch-size 0 "Last GetCandidates call's fetch-size.")
+(defvar zero-input-previous-page-key ?\- "Previous page key.")
+(defvar zero-input-next-page-key ?\= "Next page key.")
+
+;;; concrete input method should define these functions and set them in the
+;;; corresponding *-func variable.
+(defun zero-input-build-candidates-default (_preedit-str _fetch-size)
+  "Default implementation for `zero-input-build-candidates-func'."
+  nil)
+(defun zero-input-can-start-sequence-default (_ch)
+  "Default implementation for `zero-input-can-start-sequence-func'."
+  nil)
+(defun zero-input-get-preedit-str-for-panel-default ()
+  "Default implementation for `zero-input-get-preedit-str-for-panel-func'."
+  zero-input-preedit-str)
+(defvar zero-input-build-candidates-func 'zero-input-build-candidates-default
+  "Contains a function to build candidates from preedit-str.  The function accepts param preedit-str, fetch-size, returns candidate list.")
+(defvar zero-input-build-candidates-async-func 'zero-input-build-candidates-async-default
+  "Contains a function to build candidates from preedit-str.  The function accepts param preedit-str, fetch-size, and a complete-func that should be called on returned candidate list.")
+(defvar zero-input-can-start-sequence-func 'zero-input-can-start-sequence-default
+  "Contains a function to decide whether a char can start a preedit sequence.")
+(defvar zero-input-handle-preedit-char-func 'zero-input-handle-preedit-char-default
+  "Contains a function to handle IM-PREEDITING state char insert.
+The function should return t if char is handled.
+This allow input method to override default logic.")
+(defvar zero-input-get-preedit-str-for-panel-func 'zero-input-get-preedit-str-for-panel-default
+  "Contains a function that return preedit-str to show in zero-input-panel.")
+(defvar zero-input-backspace-func 'zero-input-backspace-default
+  "Contains a function to handle <backward> char.")
+(defvar zero-input-handle-preedit-char-func 'zero-input-handle-preedit-char-default
+  "Hanlde character insert in `zero-input--state-im-preediting' mode.")
+(defvar zero-input-preedit-start-func 'nil
+  "Called when enter `zero-input--state-im-preediting' state.")
+(defvar zero-input-preedit-end-func 'nil
+  "Called when leave `zero-input--state-im-preediting' state.")
+
+(defvar zero-input-enable-debug nil
+  "Whether to enable debug.
+if t, `zero-input-debug' will output debug msg in *zero-input-debug* buffer")
+(defvar zero-input-debug-buffer-max-size 30000
+  "Max characters in *zero-input-debug* buffer.  If reached, first half data will be deleted.")
+
+(defun zero-input-debug (string &rest objects)
+  "Log debug message in *zero-input-debug* buffer.
+
+STRING and OBJECTS are passed to `format'"
+  (if zero-input-enable-debug
+      (with-current-buffer (get-buffer-create "*zero-input-debug*")
+	(goto-char (point-max))
+	(insert (apply 'format string objects))
+	(when (> (point) zero-input-debug-buffer-max-size)
+	  (insert "removing old data\n")
+	  (delete-region (point-min) (/ zero-input-debug-buffer-max-size 2))))))
+
+;; (zero-input-debug "msg1\n")
+;; (zero-input-debug "msg2: %s\n" "some obj")
+;; (zero-input-debug "msg3: %s\n" 24)
+;; (zero-input-debug "msg4: %s %s\n" 24 1)
+
+(defun zero-input-enter-preedit-state ()
+  "Config keymap when enter preedit state."
+  (zero-input-enable-preediting-map)
+  (if (functionp zero-input-preedit-start-func)
+      (funcall zero-input-preedit-start-func)))
+
+(defun zero-input-leave-preedit-state ()
+  "Config keymap when leave preedit state."
+  (zero-input-disable-preediting-map)
+  (if (functionp zero-input-preedit-end-func)
+      (funcall zero-input-preedit-end-func)))
+
+(defun zero-input-set-state (state)
+  "Set zero state to given STATE."
+  (zero-input-debug "set state to %s\n" state)
+  (setq zero-input-state state)
+  (if (eq state zero-input--state-im-preediting)
+      (zero-input-enter-preedit-state)
+    (zero-input-leave-preedit-state)))
+
+(defun zero-input-candidates-on-page (candidates)
+  "Return candidates on current page for given CANDIDATES list."
+  (cl-flet ((take (n lst)
+	       "take the first n element from lst. if there is not
+enough elements, return lst as it is."
+	       (cl-loop
+		for lst* = lst then (cdr lst*)
+		for n* = n then (1- n*)
+		until (or (zerop n*) (null lst*))
+		collect (car lst*)))
+	    (drop (n lst)
+	       "drop the first n elements from lst"
+	       (cl-loop
+		for lst* = lst then (cdr lst*)
+		for n* = n then (1- n*)
+		until (or (zerop n*) (null lst*))
+		finally (return lst*))))
+    (take zero-input-candidates-per-page
+	  (drop (* zero-input-candidates-per-page zero-input-current-page) candidates))))
+
+(defun zero-input-show-candidates (&optional candidates)
+  "Show CANDIDATES using zero-input-panel via IPC/RPC."
+  (let ((candidates-on-page (zero-input-candidates-on-page (or candidates
+							 zero-input-candidates))))
+    (cl-destructuring-bind (x y) (zero-input-get-point-position)
+      (zero-input-panel-show-candidates
+       (funcall zero-input-get-preedit-str-for-panel-func)
+       (length candidates-on-page)
+       candidates-on-page
+       `(("in_emacs" t)
+	 ("filename" ,(or (buffer-file-name) ""))
+	 ("page_number" ,(1+ zero-input-current-page))
+	 ("has_next_page" ,(or (> (length (or candidates zero-input-candidates)) (* zero-input-candidates-per-page (1+ zero-input-current-page))) (< zero-input-fetch-size (* zero-input-candidates-per-page (+ 2 zero-input-current-page)))))
+	 ("has_previous_page" ,(> zero-input-current-page 0))
+	 ("move_x" :int32 ,x)
+	 ("move_y" :int32 ,y)))
+      (zero-input-debug "candidates: %s\n" (s-join ", " candidates-on-page)))))
+
+(defun zero-input-build-candidates (preedit-str fetch-size)
+  "Build candidates list synchronously.
+
+Try to find at least FETCH-SIZE number of candidates for PREEDIT-STR."
+  ;; (zero-input-debug "zero-input-build-candidates\n")
+  (unless (functionp zero-input-build-candidates-func)
+    (signal 'wrong-type-argument (list 'functionp zero-input-build-candidates-func)))
+  (prog1 (funcall zero-input-build-candidates-func preedit-str fetch-size)
+    (setq zero-input-fetch-size (max fetch-size (length zero-input-candidates)))))
+
+(defun zero-input-build-candidates-complete (candidates)
+  "Called when `zero-input-build-candidates-async' return.
+
+CANDIDATES is returned candidates list from async call."
+  (setq zero-input-candidates candidates)
+  (zero-input-show-candidates candidates))
+
+(defun zero-input-build-candidates-async-default (preedit-str fetch-size complete-func)
+  "Build candidate list, when done show it via `zero-input-show-candidates'.
+
+PREEDIT-STR the preedit-str.
+FETCH-SIZE try to find at least this many candidates for preedit-str.
+COMPLETE-FUNC the function to call when build candidates completes."
+  ;; (zero-input-debug "zero-input-build-candidates-async-default\n")
+  (let ((candidates (zero-input-build-candidates preedit-str fetch-size)))
+    ;; update cache to make SPC and digit key selection possible.
+    (funcall complete-func candidates)))
+
+(defvar zero-input-full-width-char-map
+  ;; ascii 33 to 126 map to
+  ;; unicode FF01 to FF5E
+  (cl-loop
+   for i from 33 to 126
+   collect (cons (make-char 'ascii i) (make-char 'unicode 0 255 (- i 32))))
+  "An alist that map half-width char to full-width char.")
+
+(defun zero-input-convert-ch-to-full-width (ch)
+  "Convert half-width char CH to full-width.
+
+If there is no full-width char for CH, return it unchanged."
+  (let ((pair (assoc ch zero-input-full-width-char-map)))
+    (if pair (cdr pair) ch)))
+
+(defun zero-input-convert-str-to-full-width (s)
+  "Convert each char in S to their full-width char if there is one."
+  (concat (mapcar 'zero-input-convert-ch-to-full-width s)))
+
+(defun zero-input-convert-str-to-full-width-maybe (s)
+  "If in `zero-input-full-width-mode', convert char in S to their full-width char; otherwise, return s unchanged."
+  (if zero-input-full-width-mode (zero-input-convert-str-to-full-width s) s))
+
+(defun zero-input-insert-full-width-char (ch)
+  "If in `zero-input-full-width-mode', insert full-width char for given CH and return true, otherwise just return nil."
+  (when zero-input-full-width-mode
+    (let ((full-width-ch (zero-input-convert-ch-to-full-width ch)))
+      (insert full-width-ch)
+      full-width-ch)))
+
+(defun zero-input-convert-punctuation-basic (ch)
+  "Convert punctuation for `zero-input-punctuation-level-basic'.
+
+Return CH's Chinese punctuation if CH is converted.  Return nil otherwise."
+  (cl-case ch
+    (?, ",")
+    (?. "。")				; 0x3002
+    (?? "?")
+    (?! "!")
+    (?\\ "、")				; 0x3001
+    (?: ":")
+    (otherwise nil)))
+
+(defun zero-input-convert-punctuation-full (ch)
+  "Convert punctuation for `zero-input-punctuation-level-full'.
+
+Return CH's Chinese punctuation if CH is converted.  Return nil otherwise"
+  (cl-case ch
+    (?_ "——")
+    (?< "《")				;0x300A
+    (?> "》")				;0x300B
+    (?\( "(")
+    (?\) ")")
+    (?\[ "【")				;0x3010
+    (?\] "】")				;0x3011
+    (?^ "……")
+    (?\" (setq zero-input-double-quote-flag (not zero-input-double-quote-flag))
+	 (if zero-input-double-quote-flag "“" "”"))
+    (?\' (setq zero-input-single-quote-flag (not zero-input-single-quote-flag))
+	 (if zero-input-single-quote-flag "‘" "’"))
+    (?~ "~")
+    (?\; ";")
+    (?$ "¥")
+    (t (zero-input-convert-punctuation-basic ch))))
+
+(defun zero-input-convert-punctuation (ch)
+  "Convert punctuation based on `zero-input-punctuation-level'.
+Return CH's Chinese punctuation if CH is converted.  Return nil otherwise."
+  (cond
+   ((eq zero-input-punctuation-level zero-input-punctuation-level-basic)
+    (zero-input-convert-punctuation-basic ch))
+   ((eq zero-input-punctuation-level zero-input-punctuation-level-full)
+    (zero-input-convert-punctuation-full ch))
+   (t nil)))
+
+(defun zero-input-handle-punctuation (ch)
+  "If CH is a punctuation character, insert mapped Chinese punctuation and return true; otherwise, return false."
+  (let ((str (zero-input-convert-punctuation ch)))
+    (when str
+      (insert str)
+      t)))
+
+(defun zero-input-append-char-to-preedit-str (ch)
+  "Append char CH to preedit str, update and show candidate list."
+  (setq zero-input-preedit-str
+	(concat zero-input-preedit-str (make-string 1 ch)))
+  (zero-input-debug "appended %c, preedit str is: %s\n" ch zero-input-preedit-str)
+  (zero-input-preedit-str-changed))
+
+(defun zero-input-can-start-sequence (ch)
+  "Return t if char CH can start a preedit sequence."
+  (if (functionp zero-input-can-start-sequence-func)
+      (funcall zero-input-can-start-sequence-func ch)
+    (error "`zero-input-can-start-sequence-func' is not a function")))
+
+(defun zero-input-page-up ()
+  "If not at first page, show candidates on previous page."
+  (interactive)
+  (when (> zero-input-current-page 0)
+    (setq zero-input-current-page (1- zero-input-current-page))
+    (zero-input-show-candidates)))
+
+(defun zero-input-just-page-down ()
+  "Just page down using existing candidates."
+  (let ((len (length zero-input-candidates)))
+    (when (> len (* zero-input-candidates-per-page (1+ zero-input-current-page)))
+      (setq zero-input-current-page (1+ zero-input-current-page))
+      (zero-input-show-candidates))))
+
+(defun zero-input-page-down ()
+  "If there is still candidates to be displayed, show candidates on next page."
+  (interactive)
+  (let ((len (length zero-input-candidates))
+	(new-fetch-size (* zero-input-candidates-per-page (+ 2 zero-input-current-page))))
+    (if (and (< len new-fetch-size)
+	     (< zero-input-fetch-size new-fetch-size))
+	(funcall zero-input-build-candidates-async-func
+		 zero-input-preedit-str
+		 new-fetch-size
+		 (lambda (candidates)
+		   (zero-input-build-candidates-complete candidates)
+		   (setq zero-input-fetch-size (max new-fetch-size
+					      (length candidates)))
+		   (zero-input-just-page-down)))
+      (zero-input-just-page-down))))
+
+(defun zero-input-handle-preedit-char-default (ch)
+  "Hanlde character insert in `zero-input--state-im-preediting' state.
+
+CH is the char user has typed."
+  (cond
+   ((= ch ?\s)
+    (zero-input-commit-first-candidate-or-preedit-str))
+   ((and (>= ch ?0) (<= ch ?9))
+    ;; 1 commit the 0th candidate
+    ;; 2 commit the 1st candidate
+    ;; ...
+    ;; 0 commit the 9th candidate
+    (unless (zero-input-commit-nth-candidate (mod (- (- ch ?0) 1) 10))
+      (zero-input-append-char-to-preedit-str ch)))
+   ((= ch zero-input-previous-page-key)
+    (zero-input-page-up))
+   ((= ch zero-input-next-page-key)
+    (zero-input-page-down))
+   (t (let ((str (zero-input-convert-punctuation ch)))
+	(if str
+	    (progn
+	      (zero-input-set-state zero-input--state-im-waiting-input)
+	      (zero-input-commit-first-candidate-or-preedit-str)
+	      (insert str))
+	  (zero-input-append-char-to-preedit-str ch))))))
+
+(defun zero-input-self-insert-command (n)
+  "Handle character `self-insert-command'.  This includes characters and digits.
+
+N is the argument passed to `self-insert-command'."
+  (interactive "p")
+  (let ((ch (elt (this-command-keys-vector) 0)))
+    (zero-input-debug "user typed: %c\n" ch)
+    (cond
+     ((eq zero-input-state zero-input--state-im-waiting-input)
+      (if (zero-input-can-start-sequence ch)
+	  (progn
+	    (zero-input-debug "can start sequence, state=IM_PREEDITING\n")
+	    (zero-input-set-state zero-input--state-im-preediting)
+	    (zero-input-append-char-to-preedit-str ch))
+	(zero-input-debug "cannot start sequence, state=IM_WAITING_INPUT\n")
+	(unless (zero-input-handle-punctuation ch)
+	  (unless (zero-input-insert-full-width-char ch)
+	    (self-insert-command n)))))
+     ((eq zero-input-state zero-input--state-im-preediting)
+      (zero-input-debug "still preediting\n")
+      (funcall zero-input-handle-preedit-char-func ch))
+     (t
+      (zero-input-debug "unexpected state: %s\n" zero-input-state)
+      (self-insert-command n)))))
+
+(defun zero-input-preedit-str-changed ()
+  "Called when preedit str is changed and not empty.  Update and show candidate list."
+  (setq zero-input-fetch-size 0)
+  (setq zero-input-current-page 0)
+  (funcall zero-input-build-candidates-async-func zero-input-preedit-str zero-input-initial-fetch-size 'zero-input-build-candidates-complete))
+
+(defun zero-input-backspace-default ()
+  "Handle backspace key in `zero-input--state-im-preediting' state."
+  (let ((len (length zero-input-preedit-str)))
+    (if (> len 1)
+	(progn
+	  (setq zero-input-preedit-str
+		(substring zero-input-preedit-str 0 (1- len)))
+	  (zero-input-preedit-str-changed))
+      (zero-input-set-state zero-input--state-im-waiting-input)
+      (zero-input-reset))))
+
+(defun zero-input-backspace ()
+  "Handle backspace key in `zero-input--state-im-preediting' state."
+  (interactive)
+  (unless (eq zero-input-state zero-input--state-im-preediting)
+    (error "Error: zero-input-backspace called in non preediting state"))
+  (zero-input-debug "zero-input-backspace\n")
+  (funcall zero-input-backspace-func))
+
+(defun zero-input-commit-text (text)
+  "Commit given TEXT, reset preedit str, hide candidate list."
+  (zero-input-debug "commit text: %s\n" text)
+  (insert text)
+  (setq zero-input-preedit-str "")
+  (setq zero-input-candidates nil)
+  (setq zero-input-current-page 0)
+  (zero-input-hide-candidate-list))
+
+(defun zero-input-return ()
+  "Handle RET key press in `zero-input--state-im-preediting' state."
+  (interactive)
+  (unless (eq zero-input-state zero-input--state-im-preediting)
+    (error "Error: zero-input-return called in non preediting state"))
+  (zero-input-debug "zero-input-return\n")
+  (zero-input-set-state zero-input--state-im-waiting-input)
+  (zero-input-commit-text (zero-input-convert-str-to-full-width-maybe zero-input-preedit-str)))
+
+(defun zero-input-commit-nth-candidate (n)
+  "Commit Nth candidate and return true if it exists; otherwise, return false."
+  (let ((candidate (nth n (zero-input-candidates-on-page zero-input-candidates))))
+    (if candidate
+	(progn
+	  (zero-input-set-state zero-input--state-im-waiting-input)
+	  (zero-input-commit-text candidate)
+	  t)
+      nil)))
+
+(defun zero-input-commit-preedit-str ()
+  "Commit current preedit-str."
+  (zero-input-set-state zero-input--state-im-waiting-input)
+  (zero-input-commit-text (zero-input-convert-str-to-full-width-maybe zero-input-preedit-str)))
+
+(defun zero-input-commit-first-candidate-or-preedit-str ()
+  "Commit first candidate if there is one, otherwise commit preedit str."
+  (unless (zero-input-commit-nth-candidate 0)
+    (zero-input-commit-preedit-str)))
+
+(defun zero-input-hide-candidate-list ()
+  "Hide candidate list."
+  (zero-input-panel-hide)
+  (zero-input-debug "hide candidate list\n"))
+
+(defun zero-input-reset ()
+  "Reset zero states."
+  (interactive)
+  (zero-input-debug "zero-input-reset\n")
+  (zero-input-set-state zero-input--state-im-waiting-input)
+  (setq zero-input-preedit-str "")
+  (setq zero-input-candidates nil)
+  (setq zero-input-current-page 0)
+  (zero-input-hide-candidate-list))
+
+(defun zero-input-focus-in ()
+  "A hook function, run when focus in a buffer."
+  (when (eq zero-input-state zero-input--state-im-preediting)
+    (zero-input-show-candidates zero-input-candidates)
+    (zero-input-enter-preedit-state)))
+
+(defun zero-input-focus-out ()
+  "A hook function, run when focus out a buffer."
+  (when (eq zero-input-state zero-input--state-im-preediting)
+    (zero-input-hide-candidate-list)
+    (zero-input-leave-preedit-state)))
+
+(defun zero-input-buffer-list-changed ()
+  "A hook function, run when buffer list has changed.  This includes user has switched buffer."
+  (if (eq (car (buffer-list)) zero-input-buffer)
+      (zero-input-focus-in)))
+
+;;============
+;; minor mode
+;;============
+
+(defvar zero-input-mode-map
+  (let ((map (make-sparse-keymap)))
+    ;; build zero-input-prefix-map
+    (defvar zero-input-prefix-map (define-prefix-command 'zero-input-prefix-map))
+    (let ((bindings '(("," zero-input-cycle-punctuation-level)
+		      ("." zero-input-toggle-full-width-mode))))
+      (dolist (b bindings)
+	(define-key zero-input-prefix-map (car b) (cadr b))))
+    ;; mount zero-input-prefix-map in C-c , prefix key.
+    (define-key map (kbd "C-c ,") zero-input-prefix-map)
+
+    ;; other keybindings
+    (define-key map [remap self-insert-command]
+      'zero-input-self-insert-command)
+    map)
+  "Keymap for `zero-input-mode'.")
+
+(defun zero-input-enable-preediting-map ()
+  "Enable preediting keymap in `zero-input-mode-map'."
+  (zero-input-debug "zero-input-enable-preediting-map\n")
+  (define-key zero-input-mode-map (kbd "<backspace>") 'zero-input-backspace)
+  (define-key zero-input-mode-map (kbd "RET") 'zero-input-return)
+  (define-key zero-input-mode-map (kbd "<escape>") 'zero-input-reset))
+
+(defun zero-input-disable-preediting-map ()
+  "Disable preediting keymap in `zero-input-mode-map'."
+  (zero-input-debug "zero-input-disable-preediting-map\n")
+  (define-key zero-input-mode-map (kbd "<backspace>") nil)
+  (define-key zero-input-mode-map (kbd "RET") nil)
+  (define-key zero-input-mode-map (kbd "<escape>") nil))
+
+(defun zero-input-modeline-string ()
+  "Build `zero-input-mode' modeline string aka lighter.
+
+If full-width mode is enabled, show ZeroF;
+Otherwise, show Zero."
+  (if zero-input-full-width-mode " ZeroF" " Zero"))
+
+(define-minor-mode zero-input-mode
+  "a Chinese input method framework written as an emacs minor mode.
+
+\\{zero-input-mode-map}"
+  nil
+  (:eval (zero-input-modeline-string))
+  zero-input-mode-map
+  ;; local variables and variable init
+  (make-local-variable 'zero-input-state)
+  (zero-input-set-state  zero-input--state-im-off)
+  (make-local-variable 'zero-input-punctuation-level)
+  (make-local-variable 'zero-input-full-width-mode)
+  (make-local-variable 'zero-input-double-quote-flag)
+  (make-local-variable 'zero-input-single-quote-flag)
+  (set (make-local-variable 'zero-input-preedit-str) "")
+  (set (make-local-variable 'zero-input-candidates) nil)
+  (make-local-variable 'zero-input-candidates-per-page)
+  (make-local-variable 'zero-input-current-page)
+  (make-local-variable 'zero-input-fetch-size)
+  (make-local-variable 'zero-input-im)
+  (make-local-variable 'zero-input-build-candidates-func)
+  (make-local-variable 'zero-input-can-start-sequence-func)
+  (zero-input-set-im zero-input-im)
+  ;; hooks
+  (add-hook 'focus-in-hook 'zero-input-focus-in)
+  (add-hook 'focus-out-hook 'zero-input-focus-out)
+  (set (make-local-variable 'zero-input-buffer) (current-buffer))
+  (add-hook 'buffer-list-update-hook 'zero-input-buffer-list-changed))
+
+;;==================
+;; IM developer API
+;;==================
+
+(defun zero-input-register-im (im-name im-functions-alist)
+  "(Re)register an input method in zero.
+
+After registration, you can use `zero-input-set-default-im' and
+`zero-input-set-im' to select input method to use.
+
+IM-NAME should be a symbol.
+IM-FUNCTIONS-ALIST should be a list of form
+  '((:virtual-function-name . implementation-function-name))
+
+virtual functions                   corresponding variable
+===========================================================================
+:build-candidates                   `zero-input-build-candidates-func'
+:can-start-sequence                 `zero-input-can-start-sequence-func'
+:handle-preedit-char                `zero-input-handle-preedit-char-func'
+:get-preedit-str-for-panel          `zero-input-get-preedit-str-for-panel-func'
+:handle-backspace                   `zero-input-backspace-func'
+:init                               nil
+:shutdown                           nil
+:preedit-start                      `zero-input-preedit-start-func'
+:preedit-end                        `zero-input-preedit-end-func'
+
+registered input method is saved in `zero-input-ims'"
+  ;; add or replace entry in `zero-input-ims'
+  (unless (symbolp im-name)
+    (signal 'wrong-type-argument (list 'symbolp im-name)))
+  (setq zero-input-ims (assq-delete-all im-name zero-input-ims))
+  (setq zero-input-ims (push (cons im-name im-functions-alist) zero-input-ims)))
+
+;;============
+;; public API
+;;============
+
+(defun zero-input-toggle-full-width-mode ()
+  "Toggle `zero-input-full-width-mode' on/off."
+  (interactive)
+  (setq zero-input-full-width-mode (not zero-input-full-width-mode))
+  (message (if zero-input-full-width-mode
+	       "Enabled full-width mode"
+	     "Enabled half-width mode")))
+
+(defun zero-input-set-punctuation-level (level)
+  "Set `zero-input-punctuation-level'.
+
+LEVEL the level to set to."
+  (interactive)
+  (if (not (member level (list zero-input-punctuation-level-basic
+			       zero-input-punctuation-level-full
+			       zero-input-punctuation-level-none)))
+      (error "Level not supported: %s" level)
+    (setq zero-input-punctuation-level level)))
+
+(defun zero-input-set-punctuation-levels (levels)
+  "Set `zero-input-punctuation-levels'.
+
+`zero-input-cycle-punctuation-level' will cycle current
+`zero-input-punctuation-level' among defined LEVELS."
+  (dolist (level levels)
+    (if (not (member level (list zero-input-punctuation-level-basic
+				 zero-input-punctuation-level-full
+				 zero-input-punctuation-level-none)))
+	(error "Level not supported: %s" level)))
+  (setq zero-input-punctuation-levels levels))
+
+(defun zero-input-cycle-punctuation-level ()
+  "Cycle `zero-input-punctuation-level' among `zero-input-punctuation-levels'."
+  (interactive)
+  (setq zero-input-punctuation-level
+	(zero-input-cycle-list zero-input-punctuation-levels zero-input-punctuation-level))
+  (message "punctuation level set to %s" zero-input-punctuation-level))
+
+;;;###autoload
+(defun zero-input-set-im (im-name)
+  "Select zero input method for current buffer.
+
+if IM-NAME is nil, use default empty input method"
+  ;; TODO provide auto completion for im-name
+  (interactive "SSet input method to: ")
+  ;; when switch away from an IM, run last IM's :shutdown function.
+  (if zero-input-im
+      (let ((shutdown-func (cdr (assq :shutdown (cdr (assq zero-input-im zero-input-ims))))))
+	(if (functionp shutdown-func)
+	    (funcall shutdown-func))))
+  (if im-name
+      (let ((im-functions (cdr (assq im-name zero-input-ims))))
+	(if im-functions
+	    (progn
+	      ;; TODO create a macro to reduce code duplication and human
+	      ;; error.
+	      ;;
+	      ;; TODO do some functionp check for the slot functions. if check
+	      ;; fail, keep (or revert to) the old IM.
+	      (setq zero-input-build-candidates-func
+		    (or (cdr (assq :build-candidates im-functions))
+			'zero-input-build-candidates-default))
+	      (setq zero-input-build-candidates-async-func
+		    (or (cdr (assq :build-candidates-async im-functions))
+			'zero-input-build-candidates-async-default))
+	      (setq zero-input-can-start-sequence-func
+		    (or (cdr (assq :can-start-sequence im-functions))
+			'zero-input-can-start-sequence-default))
+	      (setq zero-input-handle-preedit-char-func
+		    (or (cdr (assq :handle-preedit-char im-functions))
+			'zero-input-handle-preedit-char-default))
+	      (setq zero-input-get-preedit-str-for-panel-func
+		    (or (cdr (assq :get-preedit-str-for-panel im-functions))
+			'zero-input-get-preedit-str-for-panel-default))
+	      (setq zero-input-backspace-func
+		    (or (cdr (assq :handle-backspace im-functions))
+			'zero-input-backspace-default))
+	      (setq zero-input-preedit-start-func
+		    (cdr (assq :preedit-start im-functions)))
+	      (setq zero-input-preedit-end-func
+		    (cdr (assq :preedit-end im-functions)))
+	      (unless (functionp zero-input-backspace-func)
+		(signal 'wrong-type-argument
+			(list 'functionp zero-input-backspace-func)))
+	      ;; when switch to a IM, run its :init function
+	      (let ((init-func (cdr (assq :init im-functions))))
+		(if (functionp init-func)
+		    (funcall init-func)))
+	      (set (make-local-variable 'zero-input-im) im-name))
+	  (error "Input method %s not registered in zero" im-name)))
+    (zero-input-debug "using default empty input method")
+    (setq zero-input-build-candidates-func 'zero-input-build-candidates-default)
+    (setq zero-input-build-candidates-async-func 'zero-input-build-candidates-async-default)
+    (setq zero-input-can-start-sequence-func 'zero-input-can-start-sequence-default)
+    (setq zero-input-handle-preedit-char-func 'zero-input-handle-preedit-char-default)
+    (setq zero-input-get-preedit-str-for-panel-func 'zero-input-get-preedit-str-for-panel-default)
+    (setq zero-input-backspace-func 'zero-input-backspace-default)
+    (setq zero-input-preedit-start-func nil)
+    (setq zero-input-preedit-end-func nil)))
+
+;;;###autoload
+(defun zero-input-set-default-im (im-name)
+  "Set given IM-NAME as default zero input method."
+  (unless (symbolp im-name)
+    (signal 'wrong-type-argument (list 'symbolp im-name)))
+  (setq-default zero-input-im im-name))
+
+;;;###autoload
+(defun zero-input-on ()
+  "Turn on `zero-input-mode'."
+  (interactive)
+  (zero-input-debug "zero-input-on\n")
+  (zero-input-mode 1)
+  (if (eq zero-input-state zero-input--state-im-off)
+      (zero-input-set-state zero-input--state-im-waiting-input)))
+
+(defun zero-input-off ()
+  "Turn off `zero-input-mode'."
+  (interactive)
+  (zero-input-debug "zero-input-off\n")
+  (zero-input-mode -1)
+  (zero-input-reset)
+  (zero-input-set-state zero-input--state-im-off))
+
+;;;###autoload
+(defun zero-input-toggle ()
+  "Toggle `zero-input-mode'."
+  (interactive)
+  (if zero-input-mode
+      (zero-input-off)
+    (zero-input-on)))
+
+(provide 'zero-input-framework)
+
+;; body of zero-input-table.el
+
+;;==============
+;; dependencies
+;;==============
+
+
+;;===============================
+;; basic data and emacs facility
+;;===============================
+
+(defvar zero-input-table-table nil
+  "The table used by zero-input-table input method, map string to string.")
+(defvar zero-input-table-sequence-initials nil "Used in `zero-input-table-can-start-sequence'.")
+
+;;=====================
+;; key logic functions
+;;=====================
+
+(defun zero-input-table-sort-key (lhs rhs)
+  "A predicate function to sort candidates.  Return t if LHS should sort before RHS."
+  (string< (car lhs) (car rhs)))
+
+(defun zero-input-table-build-candidates (preedit-str &optional _fetch-size)
+  "Build candidates by looking up PREEDIT-STR in `zero-input-table-table'."
+  (mapcar 'cdr (sort (cl-remove-if-not (lambda (pair) (string-prefix-p preedit-str (car pair))) zero-input-table-table) 'zero-input-table-sort-key)))
+
+;; (defun zero-input-table-build-candidates-async (preedit-str)
+;;   "build candidate list, when done show it via `zero-input-table-show-candidates'"
+;;   (zero-input-table-debug "building candidate list\n")
+;;   (let ((candidates (zero-input-table-build-candidates preedit-str)))
+;;     ;; update cache to make SPC and digit key selection possible.
+;;     (setq zero-input-table-candidates candidates)
+;;     (zero-input-table-show-candidates candidates)))
+
+(defun zero-input-table-can-start-sequence (ch)
+  "Return t if char CH can start a preedit sequence."
+  (member (make-string 1 ch) zero-input-table-sequence-initials))
+
+;;===============================
+;; register IM to zero framework
+;;===============================
+
+(zero-input-register-im
+ 'zero-input-table
+ '((:build-candidates . zero-input-table-build-candidates)
+   (:can-start-sequence . zero-input-table-can-start-sequence)))
+
+;;============
+;; public API
+;;============
+
+(defun zero-input-table-set-table (alist)
+  "Set the conversion table.
+
+the ALIST should be a list of (key . value) pairs.  when user type
+\(part of) key, the IM will show all matching value.
+
+e.g.
+'((\"phone\" . \"18612345678\")
+  (\"mail\" . \"foo@example.com\")
+  (\"map\" . \"https://ditu.amap.com/\")
+  (\"m\" . \"https://msdn.microsoft.com/en-us\")
+  (\"address\" . \"123 Happy Street\"))"
+  (setq zero-input-table-table alist)
+  (setq zero-input-table-sequence-initials
+	(delete-dups (mapcar (lambda (pair) (substring (car pair) 0 1))
+			     zero-input-table-table))))
+
+;;===========
+;; test data
+;;===========
+
+(unless zero-input-table-table
+  (zero-input-table-set-table
+   '(("phone" . "18612345678")
+     ("pyl" . "http://localdocs.emacsos.com/python2/library/%s.html")
+     ("pyli" . "http://localdocs.emacsos.com/python2/index.html")
+     ("pylm" . "http://localdocs.emacsos.com/python2/py-modindex.html")
+     ("py3li" . "http://localdocs.emacsos.com/python2/index.html")
+     ("py3l" . "http://localdocs.emacsos.com/python3/library/%s.html")
+     ("py3lm" . "http://localdocs.emacsos.com/python3/py-modindex.html")
+     ("pyop" . "http://docs.python.org/library/operator.html")
+     ("pyopl" . "http://localdocs.emacsos.com/python2/library/operator.html")
+     ("pympl" . "http://localdocs.emacsos.com/python2/library/multiprocessing.html")
+     ("py2" . "http://docs.python.org/2/library/%s.html")
+     ("py3" . "http://docs.python.org/3/library/%s.html")
+     ("py2i" . "http://docs.python.org/2/")
+     ("py2m" . "http://docs.python.org/2/py-modindex.html")
+     ("py3i" . "http://docs.python.org/3/")
+     ("py3m" . "http://docs.python.org/3/py-modindex.html")
+     ("pycodec" . "http://localdocs.emacsos.com/python2/library/codecs.html#standard-encodings")
+     ("pycodecs" . "http://localdocs.emacsos.com/python2/library/codecs.html#standard-encodings")
+     ("pycodecsr" . "http://docs.python.org/library/codecs.html#standard-encodings")
+     ("pycodecr" . "http://docs.python.org/library/codecs.html#standard-encodings")
+     ("pep328" . "http://www.python.org/dev/peps/pep-0328/")
+     ("mail" . "foo@example.com")
+     ("map" . "https://ditu.amap.com/")
+     ("m" . "https://msdn.microsoft.com/en-us")
+     ("address" . "123 Happy Street")
+     ("da" . "__da__")
+     ("now" . "__now__"))))
+
+(provide 'zero-input-table)
+
+;; body of zero-input-pinyin-service.el
+
+;;================
+;; implementation
+;;================
+
+
+(defvar zero-input-pinyin-service-service-name
+  "com.emacsos.zero.ZeroPinyinService1")
+(defvar zero-input-pinyin-service-path
+  "/com/emacsos/zero/ZeroPinyinService1")
+(defvar zero-input-pinyin-service-interface
+  "com.emacsos.zero.ZeroPinyinService1.ZeroPinyinServiceInterface")
+(defvar zero-input-pinyin-fuzzy-flag 0)
+
+(defun zero-input-pinyin-service-error-handler (event error)
+  "Handle dbus errors.
+
+EVENT, ERROR are arguments passed to the handler."
+  (when (or (string-equal zero-input-pinyin-service-service-name
+			  (dbus-event-interface-name event))
+	    (s-contains-p zero-input-pinyin-service-service-name (cadr error)))
+    (error "`zero-input-pinyin-service' dbus failed: %S" (cadr error))))
+
+(add-hook 'dbus-event-error-functions 'zero-input-pinyin-service-error-handler)
+
+(defun zero-input-pinyin-service-async-call (method handler &rest args)
+  "Call METHOD on zero-input-pinin-service asynchronously.
+This is a wrapper around `dbus-call-method-asynchronously'.
+Argument HANDLER the handler function.
+Optional argument ARGS extra arguments to pass to the wrapped function."
+  (apply 'dbus-call-method-asynchronously
+	 :session zero-input-pinyin-service-service-name
+	 zero-input-pinyin-service-path
+	 zero-input-pinyin-service-interface
+	 method handler :timeout 1000 args))
+
+(defun zero-input-pinyin-service-call (method &rest args)
+  "Call METHOD on zero-input-pinin-service synchronously.
+This is a wrapper around `dbus-call-method'.
+Optional argument ARGS extra arguments to pass to the wrapped function."
+  (apply 'dbus-call-method
+	 :session zero-input-pinyin-service-service-name
+	 zero-input-pinyin-service-path
+	 zero-input-pinyin-service-interface
+	 method :timeout 1000 args))
+
+;;============
+;; public API
+;;============
+
+(defun zero-input-pinyin-service-get-candidates (preedit-str fetch-size)
+  "Get candidates for pinyin in PREEDIT-STR synchronously.
+
+preedit-str the preedit-str, should be pure pinyin string
+FETCH-SIZE try to fetch this many candidates or more"
+  (zero-input-pinyin-service-call "GetCandidatesV2" :string preedit-str :uint32 fetch-size :uint32 zero-input-pinyin-fuzzy-flag))
+
+(defun zero-input-pinyin-service-get-candidates-async (preedit-str fetch-size get-candidates-complete)
+  "Get candidates for pinyin in PREEDIT-STR asynchronously.
+
+PREEDIT-STR the preedit string, should be pure pinyin string.
+FETCH-SIZE try to fetch this many candidates or more.
+GET-CANDIDATES-COMPLETE the async handler function."
+  (zero-input-pinyin-service-async-call
+   "GetCandidatesV2" get-candidates-complete :string preedit-str :uint32 fetch-size :uint32 zero-input-pinyin-fuzzy-flag))
+
+(defun zero-input-pinyin-candidate-pinyin-indices-to-dbus-format (candidate_pinyin_indices)
+  "Convert CANDIDATE_PINYIN_INDICES to Emacs dbus format."
+  (let (result)
+    (push :array result)
+    ;; (push :signature result)
+    ;; (push "(ii)" result)
+    (dolist (pypair candidate_pinyin_indices)
+      (push (list :struct :int32 (cl-first pypair) :int32 (cl-second pypair))
+	    result))
+    (reverse result)))
+
+(defun zero-input-pinyin-service-commit-candidate-async (candidate candidate_pinyin_indices)
+  "Commit candidate asynchronously.
+
+CANDIDATE the candidate user selected.
+CANDIDATE_PINYIN_INDICES the candidate's pinyin shengmu and yunmu index."
+  ;; don't care about the result, so no callback.
+  (zero-input-pinyin-service-async-call
+   "CommitCandidate" nil
+   :string candidate
+   (zero-input-pinyin-candidate-pinyin-indices-to-dbus-format candidate_pinyin_indices)))
+
+(defun zero-input-pinyin-service-delete-candidates-async (candidate delete-candidate-complete)
+  "Delete CANDIDATE asynchronously.
+
+DELETE-CANDIDATE-COMPLETE the async handler function."
+  (zero-input-pinyin-service-async-call
+   "DeleteCandidate" delete-candidate-complete :string candidate))
+
+(defun zero-input-pinyin-service-quit ()
+  "Quit panel application."
+  (zero-input-pinyin-service-async-call "Quit" nil))
+
+(provide 'zero-input-pinyin-service)
+
+;; body of zero-input-pinyin.el
+
+;;==============
+;; dependencies
+;;==============
+
+
+;;===============================
+;; basic data and emacs facility
+;;===============================
+
+;; these two var is only used in docstring to avoid checkdoc line-too-long
+;; error.
+(defvar zero-input-pinyin-service-interface-xml-file
+  "/usr/share/dbus-1/interfaces/com.emacsos.zero.ZeroPinyinService1.ZeroPinyinServiceInterface.xml")
+(defvar zero-input-pinyin-service-interface-xml-url
+  "https://gitlab.emacsos.com/sylecn/zero-input-pinyin-service/blob/master/com.emacsos.zero.ZeroPinyinService1.ZeroPinyinServiceInterface.xml")
+(defcustom zero-input-pinyin-fuzzy-flag 0
+  "Non-nil means use this value as GetCandidatesV2 fuzzy_flag param.
+see zero-input-pinyin-service dbus interface xml for document.
+
+You can find the xml file locally at
+`zero-input-pinyin-service-interface-xml-file' or online at
+`zero-input-pinyin-service-interface-xml-url'."
+  :type 'integer
+  :group 'zero-input-pinyin)
+
+(defvar zero-input-pinyin-state nil "Zero-Input-pinyin internal state.  could be nil or `*zero-input-pinyin-state-im-partial-commit*'.")
+(defconst zero-input-pinyin--state-im-partial-commit 'IM-PARTIAL-COMMIT)
+
+(defvar zero-input-pinyin-used-preedit-str-lengths nil
+  "Accompany `zero-input-candidates', marks how many preedit-str chars are used for each candidate.")
+(defvar zero-input-pinyin-candidates-pinyin-indices nil
+  "Store GetCandidates dbus method candidates_pinyin_indices field.")
+(defvar zero-input-pinyin-pending-str "")
+(defvar zero-input-pinyin-pending-preedit-str "")
+(defvar zero-input-pinyin-pending-pinyin-indices nil
+  "Stores `zero-input-pinyin-pending-str' corresponds pinyin indices.")
+
+;;=====================
+;; key logic functions
+;;=====================
+
+(defun zero-input-pinyin-reset ()
+  "Reset states."
+  (setq zero-input-pinyin-state nil)
+  (setq zero-input-pinyin-used-preedit-str-lengths nil)
+  (setq zero-input-pinyin-pending-str "")
+  (setq zero-input-pinyin-pending-preedit-str ""))
+
+(defun zero-input-pinyin-init ()
+  "Called when this im is turned on."
+  (make-local-variable 'zero-input-pinyin-state)
+  (zero-input-pinyin-reset))
+
+(defun zero-input-pinyin-preedit-start ()
+  "Called when enter `*zero-input-state-im-preediting*' state."
+  (define-key zero-input-mode-map [remap digit-argument] 'zero-input-digit-argument))
+
+(defun zero-input-pinyin-preedit-end ()
+  "Called when leave `*zero-input-state-im-preediting*' state."
+  (define-key zero-input-mode-map [remap digit-argument] nil))
+
+(defun zero-input-pinyin-shutdown ()
+  "Called when this im is turned off."
+  (define-key zero-input-mode-map [remap digit-argument] nil))
+
+(defvar zero-input-pinyin--build-candidates-use-test-data nil
+  "If t, `zero-input-pinyin-build-candidates' will use `zero-input-pinyin-build-candidates-test'.")
+
+(defun zero-input-pinyin-build-candidates (preedit-str fetch-size)
+  "Synchronously build candidates list.
+
+PREEDIT-STR the preedit string.
+FETCH-SIZE fetch at least this many candidates if possible."
+  (if zero-input-pinyin--build-candidates-use-test-data
+      (progn
+	(zero-input-pinyin-build-candidates-test preedit-str)
+	(setq zero-input-fetch-size (max fetch-size (length zero-input-candidates))))
+    (zero-input-debug "zero-input-pinyin building candidate list synchronously\n")
+    (let ((result (zero-input-pinyin-service-get-candidates preedit-str fetch-size)))
+      (setq zero-input-fetch-size (max fetch-size (length (cl-first result))))
+      (setq zero-input-pinyin-used-preedit-str-lengths (cl-second result))
+      (setq zero-input-pinyin-candidates-pinyin-indices (cl-third result))
+      (cl-first result))))
+
+(defun zero-input-pinyin-build-candidates-async (preedit-str fetch-size complete-func)
+  "Asynchronously build candidate list, when done call complete-func on it.
+
+PREEDIT-STR the preedit string.
+FETCH-SIZE fetch at least this many candidates if possible.
+COMPLETE-FUNC the callback function when async call completes.  it's called with
+              fetched candidates list as parameter."
+  (zero-input-debug "zero-input-pinyin building candidate list asynchronously\n")
+  (zero-input-pinyin-service-get-candidates-async
+   preedit-str
+   fetch-size
+   (lambda (candidates matched_preedit_str_lengths candidates_pinyin_indices)
+     (setq zero-input-pinyin-used-preedit-str-lengths matched_preedit_str_lengths)
+     (setq zero-input-pinyin-candidates-pinyin-indices candidates_pinyin_indices)
+     (setq zero-input-fetch-size (max fetch-size (length candidates)))
+     ;; Note: with dynamic binding, this command result in (void-variable
+     ;; complete-func) error.
+     (funcall complete-func candidates))))
+
+(defun zero-input-pinyin-can-start-sequence (ch)
+  "Return t if char CH can start a preedit sequence."
+  (and (>= ch ?a)
+       (<= ch ?z)
+       (not (= ch ?i))
+       (not (= ch ?u))
+       (not (= ch ?v))))
+
+(defun zero-input-pinyin-pending-preedit-str-changed ()
+  "Update zero states when pending preedit string changed."
+  (setq zero-input-fetch-size 0)
+  (setq zero-input-current-page 0)
+  (zero-input-pinyin-build-candidates-async zero-input-pinyin-pending-preedit-str zero-input-initial-fetch-size 'zero-input-build-candidates-complete))
+
+(defun zero-input-pinyin-commit-nth-candidate (n)
+  "Commit Nth candidate and return true if it exists, otherwise, return false."
+  (let* ((n-prime (+ n (* zero-input-candidates-per-page zero-input-current-page)))
+	 (candidate (nth n-prime zero-input-candidates))
+	 (used-len (when candidate
+		     (nth n-prime zero-input-pinyin-used-preedit-str-lengths))))
+    (when candidate
+      (zero-input-debug
+       "zero-input-pinyin-commit-nth-candidate\n    n=%s candidate=%s used-len=%s zero-input-pinyin-pending-preedit-str=%S\n"
+       n candidate used-len zero-input-pinyin-pending-preedit-str)
+      (cond
+       ((null zero-input-pinyin-state)
+	(if (= used-len (length zero-input-preedit-str))
+	    (progn
+	      (zero-input-debug "commit in full\n")
+	      (zero-input-set-state zero-input--state-im-waiting-input)
+	      (zero-input-commit-text candidate)
+	      (zero-input-pinyin-service-commit-candidate-async
+	       candidate
+	       (nth n-prime zero-input-pinyin-candidates-pinyin-indices))
+	      t)
+	  (zero-input-debug "partial commit, in partial commit mode now.\n")
+	  (setq zero-input-pinyin-state zero-input-pinyin--state-im-partial-commit)
+	  (setq zero-input-pinyin-pending-str candidate)
+	  (setq zero-input-pinyin-pending-preedit-str (substring zero-input-preedit-str used-len))
+	  (setq zero-input-pinyin-pending-pinyin-indices
+		(nth n-prime zero-input-pinyin-candidates-pinyin-indices))
+	  (zero-input-pinyin-pending-preedit-str-changed)
+	  t))
+       ((eq zero-input-pinyin-state zero-input-pinyin--state-im-partial-commit)
+	(if (= used-len (length zero-input-pinyin-pending-preedit-str))
+	    (progn
+	      (zero-input-debug "finishes partial commit\n")
+	      (setq zero-input-pinyin-state nil)
+	      (zero-input-set-state zero-input--state-im-waiting-input)
+	      (zero-input-commit-text (concat zero-input-pinyin-pending-str candidate))
+	      (zero-input-pinyin-service-commit-candidate-async
+	       (concat zero-input-pinyin-pending-str candidate)
+	       (append zero-input-pinyin-pending-pinyin-indices
+		       (nth n-prime zero-input-pinyin-candidates-pinyin-indices)))
+	      t)
+	  (zero-input-debug "continue partial commit\n")
+	  (setq zero-input-pinyin-pending-str (concat zero-input-pinyin-pending-str candidate))
+	  (setq zero-input-pinyin-pending-preedit-str (substring zero-input-pinyin-pending-preedit-str used-len))
+	  (setq zero-input-pinyin-pending-pinyin-indices
+		(append zero-input-pinyin-pending-pinyin-indices
+			(nth n-prime zero-input-pinyin-candidates-pinyin-indices)))
+	  (zero-input-pinyin-service-commit-candidate-async
+	   zero-input-pinyin-pending-str
+	   zero-input-pinyin-pending-pinyin-indices)
+	  (zero-input-pinyin-pending-preedit-str-changed)
+	  t))
+       (t (error "Unexpected zero-input-pinyin-state: %s" zero-input-pinyin-state))))))
+
+(defun zero-input-pinyin-commit-first-candidate-or-preedit-str ()
+  "Commit first candidate if there is one, otherwise, commit preedit string."
+  (unless (zero-input-pinyin-commit-nth-candidate 0)
+    (zero-input-commit-preedit-str)))
+
+(defun zero-input-pinyin-commit-first-candidate-in-full ()
+  "Commit first candidate and return t if it consumes all preedit-str.
+Otherwise, just return nil."
+  (let ((candidate (nth 0 (zero-input-candidates-on-page zero-input-candidates)))
+	(used-len (nth (* zero-input-candidates-per-page zero-input-current-page) zero-input-pinyin-used-preedit-str-lengths)))
+    (when candidate
+      (cond
+       ((null zero-input-pinyin-state)
+	(when (= used-len (length zero-input-preedit-str))
+	  (zero-input-set-state zero-input--state-im-waiting-input)
+	  (zero-input-commit-text candidate)
+	  t))
+       ((eq zero-input-pinyin-state zero-input-pinyin--state-im-partial-commit)
+	(when (= used-len (length zero-input-pinyin-pending-preedit-str))
+	  (setq zero-input-pinyin-state nil)
+	  (zero-input-set-state zero-input--state-im-waiting-input)
+	  (zero-input-commit-text (concat zero-input-pinyin-pending-str candidate))
+	  t))
+       (t (error "Unexpected zero-input-pinyin-state: %s" zero-input-pinyin-state))))))
+
+(defun zero-input-pinyin-page-down ()
+  "Handle page down for zero-input-pinyin.
+
+This is different from zero-input-framework because I need to support partial commit"
+  (let ((len (length zero-input-candidates))
+	(new-fetch-size (* zero-input-candidates-per-page (+ 2 zero-input-current-page))))
+    (if (and (< len new-fetch-size)
+	     (< zero-input-fetch-size new-fetch-size))
+	(let ((preedit-str (if (eq zero-input-pinyin-state zero-input-pinyin--state-im-partial-commit) zero-input-pinyin-pending-preedit-str zero-input-preedit-str)))
+	  (zero-input-pinyin-build-candidates-async
+	   preedit-str
+	   new-fetch-size
+	   (lambda (candidates)
+	     (zero-input-build-candidates-complete candidates)
+	     (zero-input-just-page-down))))
+      (zero-input-just-page-down))))
+
+(defun zero-input-pinyin-handle-preedit-char (ch)
+  "Hanlde character insert in `*zero-input-state-im-preediting*' state.
+Override `zero-input-handle-preedit-char-default'.
+
+CH the character user typed."
+  (cond
+   ((= ch ?\s)
+    (zero-input-pinyin-commit-first-candidate-or-preedit-str))
+   ((and (>= ch ?0) (<= ch ?9))
+    ;; 1 commit the 0th candidate
+    ;; 2 commit the 1st candidate
+    ;; ...
+    ;; 0 commit the 9th candidate
+    (unless (zero-input-pinyin-commit-nth-candidate (mod (- (- ch ?0) 1) 10))
+      (zero-input-append-char-to-preedit-str ch)
+      (setq zero-input-pinyin-state nil)))
+   ((= ch zero-input-previous-page-key)
+    (zero-input-handle-preedit-char-default ch))
+   ((= ch zero-input-next-page-key)
+    (zero-input-pinyin-page-down))
+   (t (let ((str (zero-input-convert-punctuation ch)))
+	(if str
+	    (when (zero-input-pinyin-commit-first-candidate-in-full)
+	      (zero-input-set-state zero-input--state-im-waiting-input)
+	      (insert str))
+	  (setq zero-input-pinyin-state nil)
+	  (zero-input-append-char-to-preedit-str ch))))))
+
+(defun zero-input-pinyin-get-preedit-str-for-panel ()
+  "Return the preedit string that should show in panel."
+  (if (eq zero-input-pinyin-state zero-input-pinyin--state-im-partial-commit)
+      (concat zero-input-pinyin-pending-str zero-input-pinyin-pending-preedit-str)
+    zero-input-preedit-str))
+
+(defun zero-input-pinyin-preedit-str-changed ()
+  "Start over for candidate selection process."
+  (setq zero-input-pinyin-state nil)
+  (zero-input-preedit-str-changed))
+
+(defun zero-input-pinyin-backspace ()
+  "Handle backspace key in `*zero-input-state-im-preediting*' state."
+  (if (eq zero-input-pinyin-state zero-input-pinyin--state-im-partial-commit)
+      (zero-input-pinyin-preedit-str-changed)
+    (zero-input-backspace-default)))
+
+(defun zero-input-pinyin-delete-candidate (digit)
+  "Tell backend to delete candidate at DIGIT position.
+
+DIGIT is the digit key used to select nth candidate.
+DIGIT 1 means delete 1st candidate.
+DIGIT 2 means delete 2st candidate.
+...
+DIGIT 0 means delete 10th candidate."
+  (let ((candidate (nth (mod (- digit 1) 10)
+			(zero-input-candidates-on-page zero-input-candidates))))
+    (when candidate
+      (zero-input-pinyin-service-delete-candidates-async
+       candidate 'zero-input-pinyin-preedit-str-changed))))
+
+(defun zero-input-digit-argument ()
+  "Allow C-<digit> to DeleteCandidate in `*zero-input-state-im-preediting*' state."
+  (interactive)
+  (unless (eq zero-input-state zero-input--state-im-preediting)
+    (error "`zero-input-digit-argument' called in non preediting state"))
+  (if (memq 'control (event-modifiers last-command-event))
+      (let* ((char (if (integerp last-command-event)
+		       last-command-event
+		     (get last-command-event 'ascii-character)))
+	     (digit (- (logand char ?\177) ?0)))
+	(zero-input-pinyin-delete-candidate digit))))
+
+;;===============================
+;; register IM to zero framework
+;;===============================
+
+(zero-input-register-im
+ 'pinyin
+ '((:build-candidates . zero-input-pinyin-build-candidates)
+   ;; comment to use sync version, uncomment to use async version.
+   ;; (:build-candidates-async . zero-input-pinyin-build-candidates-async)
+   (:can-start-sequence . zero-input-pinyin-can-start-sequence)
+   (:handle-preedit-char . zero-input-pinyin-handle-preedit-char)
+   (:get-preedit-str-for-panel . zero-input-pinyin-get-preedit-str-for-panel)
+   (:handle-backspace . zero-input-pinyin-backspace)
+   (:init . zero-input-pinyin-init)
+   (:shutdown . zero-input-pinyin-shutdown)
+   (:preedit-start . zero-input-pinyin-preedit-start)
+   (:preedit-end . zero-input-pinyin-preedit-end)))
+
+;;============
+;; public API
+;;============
+
+;;===========
+;; test data
+;;===========
+
+(defun zero-input-pinyin-build-candidates-test (preedit-str)
+  "Test data for testing partial commit.
+
+PREEDIT-STR the preedit string."
+  (cond
+   ((equal preedit-str "liyifeng")
+    (setq zero-input-pinyin-used-preedit-str-lengths '(8 4 4 4 2 2 2))
+    '("李易峰" "利益" "礼仪" "离异" "里" "理" "力"))
+   ((equal preedit-str "feng")
+    (setq zero-input-pinyin-used-preedit-str-lengths '(4 4 4 4 4))
+    '("风" "封" "疯" "丰" "凤"))
+   ((equal preedit-str "yifeng")
+    (setq zero-input-pinyin-used-preedit-str-lengths '(6 6 2 2 2 2))
+    '("一封" "遗风" "艺" "依" "一" "以"))
+   (t nil)))
+
+(provide 'zero-input-pinyin)
+
+
+(provide 'zero-input)
+
+;;; zero-input.el ends here
diff --git a/zero.el b/zero.el
deleted file mode 100644
index f4ede4107c38e19d9b768c2399d690355b127d2f..0000000000000000000000000000000000000000
--- a/zero.el
+++ /dev/null
@@ -1,1510 +0,0 @@
-;;; zero.el --- Zero Chinese input method framework -*- lexical-binding: t -*-
-
-;; Licensed under the Apache License, Version 2.0 (the "License");
-;; you may not use this file except in compliance with the License.
-;; You may obtain a copy of the License at
-;;
-;;     http://www.apache.org/licenses/LICENSE-2.0
-;;
-;; Unless required by applicable law or agreed to in writing, software
-;; distributed under the License is distributed on an "AS IS" BASIS,
-;; WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-;; See the License for the specific language governing permissions and
-;; limitations under the License.
-
-;; Version: 1.3.4
-;; URL: https://gitlab.emacsos.com/sylecn/zero-el
-;; Package-Version: 1.3.4
-;; Package-Requires: ((emacs "24.3") (s "1.2.0"))
-
-;;; Commentary:
-
-;; zero.el is auto-generated from multiple other files.  see zero.el.in and
-;; build.py for details.  It's created because package-lint doesn't support
-;; multi-file package yet (issue #111).
-;;
-;; zero is a Chinese input method framework for Emacs, implemented
-;; as an Emacs minor mode.
-;;
-;; zero-pinyin is bundled with zero, to use pinyin input method, add to
-;; ~/.emacs file:
-;;
-;;   (require 'zero-pinyin)
-;;   (zero-set-default-im 'pinyin)
-;;   ;; Now you may bind a key to zero-toggle to make it easy to
-;;   ;; switch on/off the input method.
-;;   (global-set-key (kbd "<f5>") 'zero-toggle)
-;;
-;; zero supports Chinese punctuation mapping.  There are three modes, none,
-;; basic, and full.  The default is basic mode, which only map most essential
-;; punctuations.  You can cycle zero-punctuation-level in current buffer by
-;; C-c , , You can change default Chinese punctuation level:
-;;
-;;   (setq-default zero-punctuation-level *zero-punctuation-level-full*)
-;;
-;; zero supports full-width mode.  You can toggle full-width mode in current
-;; buffer by C-c , . You can enable full-width mode by default:
-;;
-;;   (setq-default zero-full-width-mode t)
-;;
-
-;;; Code:
-
-(require 'dbus)
-(eval-when-compile (require 'cl-lib))
-(require 's)
-
-;; body of zero-panel.el
-
-;;================
-;; implementation
-;;================
-
-
-(defun zero-panel-error-handler (event error)
-  "Handle dbus errors.
-
-EVENT and ERROR are error-handler arguments."
-  (when (or (string-equal "com.emacsos.zero.Panel"
-			  (dbus-event-interface-name event))
-	    (s-contains-p "com.emacsos.zero.Panel" (cadr error)))
-    (error "Zero-panel dbus failed: %S" (cadr error))))
-
-(add-hook 'dbus-event-error-functions 'zero-panel-error-handler)
-
-(defun zero-panel-async-call (method _handler &rest args)
-  "Call METHOD on zero-panel service asynchronously.
-
-This is a wrapper around `dbus-call-method-asynchronously'.
-ARGS optional extra args to pass to the wrapped function."
-  (apply 'dbus-call-method-asynchronously
-	 :session
-	 "com.emacsos.zero.Panel1"	; well known name
-	 "/com/emacsos/zero/Panel1"	; object path
-	 "com.emacsos.zero.Panel1.PanelInterface" ; interface name
-	 method nil :timeout 500 args))
-
-;;=========================
-;; public utility function
-;;=========================
-
-(defun zero-alist-to-asv (hints)
-  "Convert Lisp alist to dbus a{sv} data structure.
-
-HINTS should be an alist of form '((k1 [v1type] v1) (k2 [v2type] v2)).
-
-For example,
-\(zero-alist-to-asv
-  '((\"name\" \"foo\")
-    (\"timeout\" :int32 10)))
-=>
-'(:array
-  (:dict-entry \"name\" (:variant \"foo\"))
-  (:dict-entry \"timeout\" (:variant :int32 10)))"
-  (if (null hints)
-      '(:array :signature "{sv}")
-    (let ((result '(:array)))
-      (dolist (item hints)
-	(push (list :dict-entry (car item) (cons :variant (cdr item))) result))
-      (reverse result))))
-
-;;============
-;; public API
-;;============
-
-(defun zero-panel-move (x y)
-  "Move panel to specific coordinate (X, Y).
-Origin (0, 0) is at screen top left corner."
-  (zero-panel-async-call "Move" nil :int32 x :int32 y))
-
-(defun zero-panel-show-candidates (preedit_str candidate_length candidates &optional hints)
-  "Show CANDIDATES.
-Argument PREEDIT_STR the preedit string.
-Argument CANDIDATE_LENGTH how many candidates are in candidates list."
-  (zero-panel-async-call "ShowCandidates" nil
-			 :string preedit_str
-			 :uint32 candidate_length
-			 (or candidates '(:array))
-			 (zero-alist-to-asv hints)))
-
-(defun zero-panel-show ()
-  "Show panel."
-  (zero-panel-async-call "Show" nil))
-
-(defun zero-panel-hide ()
-  "Hide panel."
-  (zero-panel-async-call "Hide" nil))
-
-(defun zero-panel-quit ()
-  "Quit panel application."
-  (interactive)
-  (zero-panel-async-call "Quit" nil))
-
-(provide 'zero-panel)
-
-;; body of zero-framework.el
-
-;;==============
-;; dependencies
-;;==============
-
-
-;;=======
-;; utils
-;;=======
-
-;; this function is from ibus.el
-(defun zero--ibus-compute-pixel-position (&optional pos window)
-  "Return geometry of object at POS in WINDOW as a list like \(X Y H).
-X and Y are pixel coordinates relative to top left corner of frame which
-WINDOW is in.  H is the pixel height of the object.
-
-Omitting POS and WINDOW means use current position and selected window,
-respectively."
-  (let* ((frame (window-frame (or window (selected-window))))
-	 (posn (posn-at-point (or pos (window-point window)) window))
-	 (line (cdr (posn-actual-col-row posn)))
-	 (line-height (and line
-			   (or (window-line-height line window)
-			       (and (redisplay t)
-				    (window-line-height line window)))))
-	 (x-y (or (posn-x-y posn)
-		  (let ((geom (pos-visible-in-window-p
-			       (or pos (window-point window)) window t)))
-		    (and geom (cons (car geom) (cadr geom))))
-		  '(0 . 0)))
-	 (ax (+ (car (window-inside-pixel-edges window))
-		(car x-y)))
-	 (ay (+ (cadr (window-pixel-edges window))
-		(or (nth 2 line-height) (cdr x-y))))
-	 (height (or (car line-height)
-		     (with-current-buffer (window-buffer window)
-		       (cond
-			;; `posn-object-width-height' returns an incorrect value
-			;; when the header line is displayed (Emacs bug #4426).
-			((and posn
-			      (null header-line-format))
-			 (cdr (posn-object-width-height posn)))
-			((and (bound-and-true-p text-scale-mode)
-			      (not (zerop (with-no-warnings
-					    text-scale-mode-amount))))
-			 (round (* (frame-char-height frame)
-				   (with-no-warnings
-				     (expt text-scale-mode-step
-					   text-scale-mode-amount)))))
-			(t
-			 (frame-char-height frame)))))))
-    (list ax ay height)))
-
-(defun zero-get-point-position ()
-  "Return current point's position (x y).
-Origin (0, 0) is at screen top left corner."
-  (cl-destructuring-bind (x y line-height) (zero--ibus-compute-pixel-position)
-    (cond
-     ((functionp 'window-absolute-pixel-position)
-      ;; introduced in emacs 26
-      (cl-destructuring-bind (x . y) (window-absolute-pixel-position)
-	(list x (+ y line-height))))
-     ((functionp 'frame-edges)
-      ;; introduced in emacs 25
-      (cl-destructuring-bind (frame-x frame-y &rest rest)
-	  (frame-edges nil 'inner-edges)
-	(list (+ frame-x x) (+ frame-y y line-height))))
-     (t
-      ;; <= emacs 24, used guessed pixel size for tool-bar, menu-bar, WM title
-      ;; bar. Since I can't get that from elisp.
-      (list (+ (frame-parameter nil 'left)
-	       (if (and (> (frame-parameter nil 'tool-bar-lines) 0)
-			(eq (frame-parameter nil 'tool-bar-position) 'left))
-		   96 0)
-	       x)
-	    (+ (frame-parameter nil 'top)
-	       (if (and (> (frame-parameter nil 'tool-bar-lines) 0)
-			(eq (frame-parameter nil 'tool-bar-position) 'top))
-		   42 0)
-	       (if (> (frame-parameter nil 'menu-bar-lines) 0) (+ 30 30) 0)
-	       line-height
-	       y))))))
-
-(defun zero-cycle-list (lst item)
-  "Return the object next to given ITEM in LST.
-
-If item is the last object, return the first object in lst.
-If item is not in lst, return nil."
-  (let ((r (member item lst)))
-    (cond
-     ((null r) nil)
-     (t (or (cadr r)
-	    (car lst))))))
-
-;;=====================
-;; key logic functions
-;;=====================
-
-;; zero-el version
-(defvar zero-version nil "Zero package version.")
-(setq zero-version "1.3.4")
-
-;; FSM state
-(defconst zero--state-im-off 'IM-OFF)
-(defconst zero--state-im-waiting-input 'IM-WAITING-INPUT)
-(defconst zero--state-im-preediting 'IM-PREEDITING)
-
-(defconst zero-punctuation-level-basic 'BASIC)
-(defconst zero-punctuation-level-full 'FULL)
-(defconst zero-punctuation-level-none 'NONE)
-
-(defvar zero-im nil
-  "Stores current input method.
-
-If nil, the empty input method will be used.  In the empty input
-method, only punctuation is handled.  Other keys are pass
-through")
-(defvar zero-ims nil
-  "A list of registered input methods.")
-
-(defvar zero-buffer nil
-  "Stores the associated buffer.
-this is used to help with buffer focus in/out events")
-
-(defvar zero-state zero--state-im-off)
-(defvar zero-full-width-mode nil
-  "Set to t to enable full-width mode.
-In full-width mode, commit ascii char will insert full-width char if there is a
-corresponding full-width char.  This full-width char map is
-independent from punctuation map.  You can change this via
-`zero-toggle-full-width-mode'")
-(defvar zero-punctuation-level zero-punctuation-level-basic
-  "Punctuation level.
-
-Should be one of
-`zero-punctuation-level-basic'
-`zero-punctuation-level-full'
-`zero-punctuation-level-none'")
-(defvar zero-punctuation-levels (list zero-punctuation-level-basic
-				      zero-punctuation-level-full
-				      zero-punctuation-level-none)
-  "Punctuation levels to use when `zero-cycle-punctuation-level'.")
-(defvar zero-double-quote-flag nil
-  "Non-nil means next double quote insert close quote.
-
-Used when converting double quote to Chinese quote.
-If nil, next double quote insert open quote.
-Otherwise, next double quote insert close quote.")
-(defvar zero-single-quote-flag nil
-  "Non-nil means next single quote insert close quote.
-
-Used when converting single quote to Chinese quote.
-If nil, next single quote insert open quote.
-Otherwise, next single quote insert close quote.")
-(defvar zero-preedit-str "")
-(defvar zero-candidates nil)
-(defcustom zero-candidates-per-page 10
-  "How many candidates to show on each page."
-  :group 'zero
-  :type 'integer)
-(defvar zero-current-page 0 "Current page number.  count from 0.")
-(defvar zero-initial-fetch-size 20
-  "How many candidates to fetch for the first call to GetCandidates.")
-;; zero-fetch-size is reset to 0 when preedit-str changes.
-;; zero-fetch-size is set to fetch-size in build-candidates-async complete-func
-;; lambda.
-(defvar zero-fetch-size 0 "Last GetCandidates call's fetch-size.")
-(defvar zero-previous-page-key ?\- "Previous page key.")
-(defvar zero-next-page-key ?\= "Next page key.")
-
-;;; concrete input method should define these functions and set them in the
-;;; corresponding *-func variable.
-(defun zero-build-candidates-default (_preedit-str _fetch-size)
-  "Default implementation for `zero-build-candidates-func'."
-  nil)
-(defun zero-can-start-sequence-default (_ch)
-  "Default implementation for `zero-can-start-sequence-func'."
-  nil)
-(defun zero-get-preedit-str-for-panel-default ()
-  "Default implementation for `zero-get-preedit-str-for-panel-func'."
-  zero-preedit-str)
-(defvar zero-build-candidates-func 'zero-build-candidates-default
-  "Contains a function to build candidates from preedit-str.  The function accepts param preedit-str, fetch-size, returns candidate list.")
-(defvar zero-build-candidates-async-func 'zero-build-candidates-async-default
-  "Contains a function to build candidates from preedit-str.  The function accepts param preedit-str, fetch-size, and a complete-func that should be called on returned candidate list.")
-(defvar zero-can-start-sequence-func 'zero-can-start-sequence-default
-  "Contains a function to decide whether a char can start a preedit sequence.")
-(defvar zero-handle-preedit-char-func 'zero-handle-preedit-char-default
-  "Contains a function to handle IM-PREEDITING state char insert.
-The function should return t if char is handled.
-This allow input method to override default logic.")
-(defvar zero-get-preedit-str-for-panel-func 'zero-get-preedit-str-for-panel-default
-  "Contains a function that return preedit-str to show in zero-panel.")
-(defvar zero-backspace-func 'zero-backspace-default
-  "Contains a function to handle <backward> char.")
-(defvar zero-handle-preedit-char-func 'zero-handle-preedit-char-default
-  "Hanlde character insert in `zero--state-im-preediting' mode.")
-(defvar zero-preedit-start-func 'nil
-  "Called when enter `zero--state-im-preediting' state.")
-(defvar zero-preedit-end-func 'nil
-  "Called when leave `zero--state-im-preediting' state.")
-
-(defvar zero-enable-debug nil
-  "Whether to enable debug.
-if t, `zero-debug' will output debug msg in *zero-debug* buffer")
-(defvar zero-debug-buffer-max-size 30000
-  "Max characters in *zero-debug* buffer.  If reached, first half data will be deleted.")
-
-(defun zero-debug (string &rest objects)
-  "Log debug message in *zero-debug* buffer.
-
-STRING and OBJECTS are passed to `format'"
-  (if zero-enable-debug
-      (with-current-buffer (get-buffer-create "*zero-debug*")
-	(goto-char (point-max))
-	(insert (apply 'format string objects))
-	(when (> (point) zero-debug-buffer-max-size)
-	  (insert "removing old data\n")
-	  (delete-region (point-min) (/ zero-debug-buffer-max-size 2))))))
-
-;; (zero-debug "msg1\n")
-;; (zero-debug "msg2: %s\n" "some obj")
-;; (zero-debug "msg3: %s\n" 24)
-;; (zero-debug "msg4: %s %s\n" 24 1)
-
-(defun zero-enter-preedit-state ()
-  "Config keymap when enter preedit state."
-  (zero-enable-preediting-map)
-  (if (functionp zero-preedit-start-func)
-      (funcall zero-preedit-start-func)))
-
-(defun zero-leave-preedit-state ()
-  "Config keymap when leave preedit state."
-  (zero-disable-preediting-map)
-  (if (functionp zero-preedit-end-func)
-      (funcall zero-preedit-end-func)))
-
-(defun zero-set-state (state)
-  "Set zero state to given STATE."
-  (zero-debug "set state to %s\n" state)
-  (setq zero-state state)
-  (if (eq state zero--state-im-preediting)
-      (zero-enter-preedit-state)
-    (zero-leave-preedit-state)))
-
-(defun zero-candidates-on-page (candidates)
-  "Return candidates on current page for given CANDIDATES list."
-  (cl-flet ((take (n lst)
-	       "take the first n element from lst. if there is not
-enough elements, return lst as it is."
-	       (cl-loop
-		for lst* = lst then (cdr lst*)
-		for n* = n then (1- n*)
-		until (or (zerop n*) (null lst*))
-		collect (car lst*)))
-	    (drop (n lst)
-	       "drop the first n elements from lst"
-	       (cl-loop
-		for lst* = lst then (cdr lst*)
-		for n* = n then (1- n*)
-		until (or (zerop n*) (null lst*))
-		finally (return lst*))))
-    (take zero-candidates-per-page
-	  (drop (* zero-candidates-per-page zero-current-page) candidates))))
-
-(defun zero-show-candidates (&optional candidates)
-  "Show CANDIDATES using zero-panel via IPC/RPC."
-  (let ((candidates-on-page (zero-candidates-on-page (or candidates
-							 zero-candidates))))
-    (cl-destructuring-bind (x y) (zero-get-point-position)
-      (zero-panel-show-candidates
-       (funcall zero-get-preedit-str-for-panel-func)
-       (length candidates-on-page)
-       candidates-on-page
-       `(("in_emacs" t)
-	 ("filename" ,(or (buffer-file-name) ""))
-	 ("page_number" ,(1+ zero-current-page))
-	 ("has_next_page" ,(or (> (length (or candidates zero-candidates)) (* zero-candidates-per-page (1+ zero-current-page))) (< zero-fetch-size (* zero-candidates-per-page (+ 2 zero-current-page)))))
-	 ("has_previous_page" ,(> zero-current-page 0))
-	 ("move_x" :int32 ,x)
-	 ("move_y" :int32 ,y)))
-      (zero-debug "candidates: %s\n" (s-join ", " candidates-on-page)))))
-
-(defun zero-build-candidates (preedit-str fetch-size)
-  "Build candidates list synchronously.
-
-Try to find at least FETCH-SIZE number of candidates for PREEDIT-STR."
-  ;; (zero-debug "zero-build-candidates\n")
-  (unless (functionp zero-build-candidates-func)
-    (signal 'wrong-type-argument (list 'functionp zero-build-candidates-func)))
-  (prog1 (funcall zero-build-candidates-func preedit-str fetch-size)
-    (setq zero-fetch-size (max fetch-size (length zero-candidates)))))
-
-(defun zero-build-candidates-complete (candidates)
-  "Called when `zero-build-candidates-async' return.
-
-CANDIDATES is returned candidates list from async call."
-  (setq zero-candidates candidates)
-  (zero-show-candidates candidates))
-
-(defun zero-build-candidates-async-default (preedit-str fetch-size complete-func)
-  "Build candidate list, when done show it via `zero-show-candidates'.
-
-PREEDIT-STR the preedit-str.
-FETCH-SIZE try to find at least this many candidates for preedit-str.
-COMPLETE-FUNC the function to call when build candidates completes."
-  ;; (zero-debug "zero-build-candidates-async-default\n")
-  (let ((candidates (zero-build-candidates preedit-str fetch-size)))
-    ;; update cache to make SPC and digit key selection possible.
-    (funcall complete-func candidates)))
-
-(defvar zero-full-width-char-map
-  ;; ascii 33 to 126 map to
-  ;; unicode FF01 to FF5E
-  (cl-loop
-   for i from 33 to 126
-   collect (cons (make-char 'ascii i) (make-char 'unicode 0 255 (- i 32))))
-  "An alist that map half-width char to full-width char.")
-
-(defun zero-convert-ch-to-full-width (ch)
-  "Convert half-width char CH to full-width.
-
-If there is no full-width char for CH, return it unchanged."
-  (let ((pair (assoc ch zero-full-width-char-map)))
-    (if pair (cdr pair) ch)))
-
-(defun zero-convert-str-to-full-width (s)
-  "Convert each char in S to their full-width char if there is one."
-  (concat (mapcar 'zero-convert-ch-to-full-width s)))
-
-(defun zero-convert-str-to-full-width-maybe (s)
-  "If in `zero-full-width-mode', convert char in S to their full-width char; otherwise, return s unchanged."
-  (if zero-full-width-mode (zero-convert-str-to-full-width s) s))
-
-(defun zero-insert-full-width-char (ch)
-  "If in `zero-full-width-mode', insert full-width char for given CH and return true, otherwise just return nil."
-  (when zero-full-width-mode
-    (let ((full-width-ch (zero-convert-ch-to-full-width ch)))
-      (insert full-width-ch)
-      full-width-ch)))
-
-(defun zero-convert-punctuation-basic (ch)
-  "Convert punctuation for `zero-punctuation-level-basic'.
-
-Return CH's Chinese punctuation if CH is converted.  Return nil otherwise."
-  (cl-case ch
-    (?, ",")
-    (?. "。")				; 0x3002
-    (?? "?")
-    (?! "!")
-    (?\\ "、")				; 0x3001
-    (?: ":")
-    (otherwise nil)))
-
-(defun zero-convert-punctuation-full (ch)
-  "Convert punctuation for `zero-punctuation-level-full'.
-
-Return CH's Chinese punctuation if CH is converted.  Return nil otherwise"
-  (cl-case ch
-    (?_ "——")
-    (?< "《")				;0x300A
-    (?> "》")				;0x300B
-    (?\( "(")
-    (?\) ")")
-    (?\[ "【")				;0x3010
-    (?\] "】")				;0x3011
-    (?^ "……")
-    (?\" (setq zero-double-quote-flag (not zero-double-quote-flag))
-	 (if zero-double-quote-flag "“" "”"))
-    (?\' (setq zero-single-quote-flag (not zero-single-quote-flag))
-	 (if zero-single-quote-flag "‘" "’"))
-    (?~ "~")
-    (?\; ";")
-    (?$ "¥")
-    (t (zero-convert-punctuation-basic ch))))
-
-(defun zero-convert-punctuation (ch)
-  "Convert punctuation based on `zero-punctuation-level'.
-Return CH's Chinese punctuation if CH is converted.  Return nil otherwise."
-  (cond
-   ((eq zero-punctuation-level zero-punctuation-level-basic)
-    (zero-convert-punctuation-basic ch))
-   ((eq zero-punctuation-level zero-punctuation-level-full)
-    (zero-convert-punctuation-full ch))
-   (t nil)))
-
-(defun zero-handle-punctuation (ch)
-  "If CH is a punctuation character, insert mapped Chinese punctuation and return true; otherwise, return false."
-  (let ((str (zero-convert-punctuation ch)))
-    (when str
-      (insert str)
-      t)))
-
-(defun zero-append-char-to-preedit-str (ch)
-  "Append char CH to preedit str, update and show candidate list."
-  (setq zero-preedit-str
-	(concat zero-preedit-str (make-string 1 ch)))
-  (zero-debug "appended %c, preedit str is: %s\n" ch zero-preedit-str)
-  (zero-preedit-str-changed))
-
-(defun zero-can-start-sequence (ch)
-  "Return t if char CH can start a preedit sequence."
-  (if (functionp zero-can-start-sequence-func)
-      (funcall zero-can-start-sequence-func ch)
-    (error "`zero-can-start-sequence-func' is not a function")))
-
-(defun zero-page-up ()
-  "If not at first page, show candidates on previous page."
-  (interactive)
-  (when (> zero-current-page 0)
-    (setq zero-current-page (1- zero-current-page))
-    (zero-show-candidates)))
-
-(defun zero-just-page-down ()
-  "Just page down using existing candidates."
-  (let ((len (length zero-candidates)))
-    (when (> len (* zero-candidates-per-page (1+ zero-current-page)))
-      (setq zero-current-page (1+ zero-current-page))
-      (zero-show-candidates))))
-
-(defun zero-page-down ()
-  "If there is still candidates to be displayed, show candidates on next page."
-  (interactive)
-  (let ((len (length zero-candidates))
-	(new-fetch-size (* zero-candidates-per-page (+ 2 zero-current-page))))
-    (if (and (< len new-fetch-size)
-	     (< zero-fetch-size new-fetch-size))
-	(funcall zero-build-candidates-async-func
-		 zero-preedit-str
-		 new-fetch-size
-		 (lambda (candidates)
-		   (zero-build-candidates-complete candidates)
-		   (setq zero-fetch-size (max new-fetch-size
-					      (length candidates)))
-		   (zero-just-page-down)))
-      (zero-just-page-down))))
-
-(defun zero-handle-preedit-char-default (ch)
-  "Hanlde character insert in `zero--state-im-preediting' state.
-
-CH is the char user has typed."
-  (cond
-   ((= ch ?\s)
-    (zero-commit-first-candidate-or-preedit-str))
-   ((and (>= ch ?0) (<= ch ?9))
-    ;; 1 commit the 0th candidate
-    ;; 2 commit the 1st candidate
-    ;; ...
-    ;; 0 commit the 9th candidate
-    (unless (zero-commit-nth-candidate (mod (- (- ch ?0) 1) 10))
-      (zero-append-char-to-preedit-str ch)))
-   ((= ch zero-previous-page-key)
-    (zero-page-up))
-   ((= ch zero-next-page-key)
-    (zero-page-down))
-   (t (let ((str (zero-convert-punctuation ch)))
-	(if str
-	    (progn
-	      (zero-set-state zero--state-im-waiting-input)
-	      (zero-commit-first-candidate-or-preedit-str)
-	      (insert str))
-	  (zero-append-char-to-preedit-str ch))))))
-
-(defun zero-self-insert-command (n)
-  "Handle character `self-insert-command'.  This includes characters and digits.
-
-N is the argument passed to `self-insert-command'."
-  (interactive "p")
-  (let ((ch (elt (this-command-keys-vector) 0)))
-    (zero-debug "user typed: %c\n" ch)
-    (cond
-     ((eq zero-state zero--state-im-waiting-input)
-      (if (zero-can-start-sequence ch)
-	  (progn
-	    (zero-debug "can start sequence, state=IM_PREEDITING\n")
-	    (zero-set-state zero--state-im-preediting)
-	    (zero-append-char-to-preedit-str ch))
-	(zero-debug "cannot start sequence, state=IM_WAITING_INPUT\n")
-	(unless (zero-handle-punctuation ch)
-	  (unless (zero-insert-full-width-char ch)
-	    (self-insert-command n)))))
-     ((eq zero-state zero--state-im-preediting)
-      (zero-debug "still preediting\n")
-      (funcall zero-handle-preedit-char-func ch))
-     (t
-      (zero-debug "unexpected state: %s\n" zero-state)
-      (self-insert-command n)))))
-
-(defun zero-preedit-str-changed ()
-  "Called when preedit str is changed and not empty.  Update and show candidate list."
-  (setq zero-fetch-size 0)
-  (setq zero-current-page 0)
-  (funcall zero-build-candidates-async-func zero-preedit-str zero-initial-fetch-size 'zero-build-candidates-complete))
-
-(defun zero-backspace-default ()
-  "Handle backspace key in `zero--state-im-preediting' state."
-  (let ((len (length zero-preedit-str)))
-    (if (> len 1)
-	(progn
-	  (setq zero-preedit-str
-		(substring zero-preedit-str 0 (1- len)))
-	  (zero-preedit-str-changed))
-      (zero-set-state zero--state-im-waiting-input)
-      (zero-reset))))
-
-(defun zero-backspace ()
-  "Handle backspace key in `zero--state-im-preediting' state."
-  (interactive)
-  (unless (eq zero-state zero--state-im-preediting)
-    (error "Error: zero-backspace called in non preediting state"))
-  (zero-debug "zero-backspace\n")
-  (funcall zero-backspace-func))
-
-(defun zero-commit-text (text)
-  "Commit given TEXT, reset preedit str, hide candidate list."
-  (zero-debug "commit text: %s\n" text)
-  (insert text)
-  (setq zero-preedit-str "")
-  (setq zero-candidates nil)
-  (setq zero-current-page 0)
-  (zero-hide-candidate-list))
-
-(defun zero-return ()
-  "Handle RET key press in `zero--state-im-preediting' state."
-  (interactive)
-  (unless (eq zero-state zero--state-im-preediting)
-    (error "Error: zero-return called in non preediting state"))
-  (zero-debug "zero-return\n")
-  (zero-set-state zero--state-im-waiting-input)
-  (zero-commit-text (zero-convert-str-to-full-width-maybe zero-preedit-str)))
-
-(defun zero-commit-nth-candidate (n)
-  "Commit Nth candidate and return true if it exists; otherwise, return false."
-  (let ((candidate (nth n (zero-candidates-on-page zero-candidates))))
-    (if candidate
-	(progn
-	  (zero-set-state zero--state-im-waiting-input)
-	  (zero-commit-text candidate)
-	  t)
-      nil)))
-
-(defun zero-commit-preedit-str ()
-  "Commit current preedit-str."
-  (zero-set-state zero--state-im-waiting-input)
-  (zero-commit-text (zero-convert-str-to-full-width-maybe zero-preedit-str)))
-
-(defun zero-commit-first-candidate-or-preedit-str ()
-  "Commit first candidate if there is one, otherwise commit preedit str."
-  (unless (zero-commit-nth-candidate 0)
-    (zero-commit-preedit-str)))
-
-(defun zero-hide-candidate-list ()
-  "Hide candidate list."
-  (zero-panel-hide)
-  (zero-debug "hide candidate list\n"))
-
-(defun zero-reset ()
-  "Reset zero states."
-  (interactive)
-  (zero-debug "zero-reset\n")
-  (zero-set-state zero--state-im-waiting-input)
-  (setq zero-preedit-str "")
-  (setq zero-candidates nil)
-  (setq zero-current-page 0)
-  (zero-hide-candidate-list))
-
-(defun zero-focus-in ()
-  "A hook function, run when focus in a buffer."
-  (when (eq zero-state zero--state-im-preediting)
-    (zero-show-candidates zero-candidates)
-    (zero-enter-preedit-state)))
-
-(defun zero-focus-out ()
-  "A hook function, run when focus out a buffer."
-  (when (eq zero-state zero--state-im-preediting)
-    (zero-hide-candidate-list)
-    (zero-leave-preedit-state)))
-
-(defun zero-buffer-list-changed ()
-  "A hook function, run when buffer list has changed.  This includes user has switched buffer."
-  (if (eq (car (buffer-list)) zero-buffer)
-      (zero-focus-in)))
-
-;;============
-;; minor mode
-;;============
-
-(defvar zero-mode-map
-  (let ((map (make-sparse-keymap)))
-    ;; build zero-prefix-map
-    (defvar zero-prefix-map (define-prefix-command 'zero-prefix-map))
-    (let ((bindings '(("," zero-cycle-punctuation-level)
-		      ("." zero-toggle-full-width-mode))))
-      (dolist (b bindings)
-	(define-key zero-prefix-map (car b) (cadr b))))
-    ;; mount zero-prefix-map in C-c , prefix key.
-    (define-key map (kbd "C-c ,") zero-prefix-map)
-
-    ;; other keybindings
-    (define-key map [remap self-insert-command]
-      'zero-self-insert-command)
-    map)
-  "Keymap for `zero-mode'.")
-
-(defun zero-enable-preediting-map ()
-  "Enable preediting keymap in `zero-mode-map'."
-  (zero-debug "zero-enable-preediting-map\n")
-  (define-key zero-mode-map (kbd "<backspace>") 'zero-backspace)
-  (define-key zero-mode-map (kbd "RET") 'zero-return)
-  (define-key zero-mode-map (kbd "<escape>") 'zero-reset))
-
-(defun zero-disable-preediting-map ()
-  "Disable preediting keymap in `zero-mode-map'."
-  (zero-debug "zero-disable-preediting-map\n")
-  (define-key zero-mode-map (kbd "<backspace>") nil)
-  (define-key zero-mode-map (kbd "RET") nil)
-  (define-key zero-mode-map (kbd "<escape>") nil))
-
-(defun zero-modeline-string ()
-  "Build `zero-mode' modeline string aka lighter.
-
-If full-width mode is enabled, show ZeroF;
-Otherwise, show Zero."
-  (if zero-full-width-mode " ZeroF" " Zero"))
-
-(define-minor-mode zero-mode
-  "a Chinese input method framework written as an emacs minor mode.
-
-\\{zero-mode-map}"
-  nil
-  (:eval (zero-modeline-string))
-  zero-mode-map
-  ;; local variables and variable init
-  (make-local-variable 'zero-state)
-  (zero-set-state  zero--state-im-off)
-  (make-local-variable 'zero-punctuation-level)
-  (make-local-variable 'zero-full-width-mode)
-  (make-local-variable 'zero-double-quote-flag)
-  (make-local-variable 'zero-single-quote-flag)
-  (set (make-local-variable 'zero-preedit-str) "")
-  (set (make-local-variable 'zero-candidates) nil)
-  (make-local-variable 'zero-candidates-per-page)
-  (make-local-variable 'zero-current-page)
-  (make-local-variable 'zero-fetch-size)
-  (make-local-variable 'zero-im)
-  (make-local-variable 'zero-build-candidates-func)
-  (make-local-variable 'zero-can-start-sequence-func)
-  (zero-set-im zero-im)
-  ;; hooks
-  (add-hook 'focus-in-hook 'zero-focus-in)
-  (add-hook 'focus-out-hook 'zero-focus-out)
-  (set (make-local-variable 'zero-buffer) (current-buffer))
-  (add-hook 'buffer-list-update-hook 'zero-buffer-list-changed))
-
-;;==================
-;; IM developer API
-;;==================
-
-(defun zero-register-im (im-name im-functions-alist)
-  "(Re)register an input method in zero.
-
-After registration, you can use `zero-set-default-im' and
-`zero-set-im' to select input method to use.
-
-IM-NAME should be a symbol.
-IM-FUNCTIONS-ALIST should be a list of form
-  '((:virtual-function-name . implementation-function-name))
-
-virtual functions                       corresponding variable
-===========================================================================
-:build-candidates                       `zero-build-candidates-func'
-:can-start-sequence                     `zero-can-start-sequence-func'
-:handle-preedit-char                    `zero-handle-preedit-char-func'
-:get-preedit-str-for-panel              `zero-get-preedit-str-for-panel-func'
-:handle-backspace                       `zero-backspace-func'
-:init                                   nil
-:shutdown                               nil
-:preedit-start                          `zero-preedit-start-func'
-:preedit-end                            `zero-preedit-end-func'
-
-registered input method is saved in `zero-ims'"
-  ;; add or replace entry in `zero-ims'
-  (unless (symbolp im-name)
-    (signal 'wrong-type-argument (list 'symbolp im-name)))
-  (setq zero-ims (assq-delete-all im-name zero-ims))
-  (setq zero-ims (push (cons im-name im-functions-alist) zero-ims)))
-
-;;============
-;; public API
-;;============
-
-(defun zero-toggle-full-width-mode ()
-  "Toggle `zero-full-width-mode' on/off."
-  (interactive)
-  (setq zero-full-width-mode (not zero-full-width-mode))
-  (message (if zero-full-width-mode
-	       "Enabled full-width mode"
-	     "Enabled half-width mode")))
-
-(defun zero-set-punctuation-level (level)
-  "Set `zero-punctuation-level'.
-
-LEVEL the level to set to."
-  (interactive)
-  (if (not (member level (list zero-punctuation-level-basic
-			       zero-punctuation-level-full
-			       zero-punctuation-level-none)))
-      (error "Level not supported: %s" level)
-    (setq zero-punctuation-level level)))
-
-(defun zero-set-punctuation-levels (levels)
-  "Set `zero-punctuation-levels'.
-
-`zero-cycle-punctuation-level' will cycle current
-`zero-punctuation-level' among defined LEVELS."
-  (dolist (level levels)
-    (if (not (member level (list zero-punctuation-level-basic
-				 zero-punctuation-level-full
-				 zero-punctuation-level-none)))
-	(error "Level not supported: %s" level)))
-  (setq zero-punctuation-levels levels))
-
-(defun zero-cycle-punctuation-level ()
-  "Cycle `zero-punctuation-level' among `zero-punctuation-levels'."
-  (interactive)
-  (setq zero-punctuation-level
-	(zero-cycle-list zero-punctuation-levels zero-punctuation-level))
-  (message "punctuation level set to %s" zero-punctuation-level))
-
-;;;###autoload
-(defun zero-set-im (im-name)
-  "Select zero input method for current buffer.
-
-if IM-NAME is nil, use default empty input method"
-  ;; TODO provide auto completion for im-name
-  (interactive "SSet input method to: ")
-  ;; when switch away from an IM, run last IM's :shutdown function.
-  (if zero-im
-      (let ((shutdown-func (cdr (assq :shutdown (cdr (assq zero-im zero-ims))))))
-	(if (functionp shutdown-func)
-	    (funcall shutdown-func))))
-  (if im-name
-      (let ((im-functions (cdr (assq im-name zero-ims))))
-	(if im-functions
-	    (progn
-	      ;; TODO create a macro to reduce code duplication and human
-	      ;; error.
-	      ;;
-	      ;; TODO do some functionp check for the slot functions. if check
-	      ;; fail, keep (or revert to) the old IM.
-	      (setq zero-build-candidates-func
-		    (or (cdr (assq :build-candidates im-functions))
-			'zero-build-candidates-default))
-	      (setq zero-build-candidates-async-func
-		    (or (cdr (assq :build-candidates-async im-functions))
-			'zero-build-candidates-async-default))
-	      (setq zero-can-start-sequence-func
-		    (or (cdr (assq :can-start-sequence im-functions))
-			'zero-can-start-sequence-default))
-	      (setq zero-handle-preedit-char-func
-		    (or (cdr (assq :handle-preedit-char im-functions))
-			'zero-handle-preedit-char-default))
-	      (setq zero-get-preedit-str-for-panel-func
-		    (or (cdr (assq :get-preedit-str-for-panel im-functions))
-			'zero-get-preedit-str-for-panel-default))
-	      (setq zero-backspace-func
-		    (or (cdr (assq :handle-backspace im-functions))
-			'zero-backspace-default))
-	      (setq zero-preedit-start-func
-		    (cdr (assq :preedit-start im-functions)))
-	      (setq zero-preedit-end-func
-		    (cdr (assq :preedit-end im-functions)))
-	      (unless (functionp zero-backspace-func)
-		(signal 'wrong-type-argument
-			(list 'functionp zero-backspace-func)))
-	      ;; when switch to a IM, run its :init function
-	      (let ((init-func (cdr (assq :init im-functions))))
-		(if (functionp init-func)
-		    (funcall init-func)))
-	      (set (make-local-variable 'zero-im) im-name))
-	  (error "Input method %s not registered in zero" im-name)))
-    (zero-debug "using default empty input method")
-    (setq zero-build-candidates-func 'zero-build-candidates-default)
-    (setq zero-build-candidates-async-func 'zero-build-candidates-async-default)
-    (setq zero-can-start-sequence-func 'zero-can-start-sequence-default)
-    (setq zero-handle-preedit-char-func 'zero-handle-preedit-char-default)
-    (setq zero-get-preedit-str-for-panel-func 'zero-get-preedit-str-for-panel-default)
-    (setq zero-backspace-func 'zero-backspace-default)
-    (setq zero-preedit-start-func nil)
-    (setq zero-preedit-end-func nil)))
-
-;;;###autoload
-(defun zero-set-default-im (im-name)
-  "Set given IM-NAME as default zero input method."
-  (unless (symbolp im-name)
-    (signal 'wrong-type-argument (list 'symbolp im-name)))
-  (setq-default zero-im im-name))
-
-;;;###autoload
-(defun zero-on ()
-  "Turn on `zero-mode'."
-  (interactive)
-  (zero-debug "zero-on\n")
-  (zero-mode 1)
-  (if (eq zero-state zero--state-im-off)
-      (zero-set-state zero--state-im-waiting-input)))
-
-(defun zero-off ()
-  "Turn off `zero-mode'."
-  (interactive)
-  (zero-debug "zero-off\n")
-  (zero-mode -1)
-  (zero-reset)
-  (zero-set-state zero--state-im-off))
-
-;;;###autoload
-(defun zero-toggle ()
-  "Toggle `zero-mode'."
-  (interactive)
-  (if zero-mode
-      (zero-off)
-    (zero-on)))
-
-(provide 'zero-framework)
-
-;; body of zero-table.el
-
-;;==============
-;; dependencies
-;;==============
-
-
-;;===============================
-;; basic data and emacs facility
-;;===============================
-
-(defvar zero-table-table nil
-  "The table used by zero-table input method, map string to string.")
-(defvar zero-table-sequence-initials nil "Used in `zero-table-can-start-sequence'.")
-
-;;=====================
-;; key logic functions
-;;=====================
-
-(defun zero-table-sort-key (lhs rhs)
-  "A predicate function to sort candidates.  Return t if LHS should sort before RHS."
-  (string< (car lhs) (car rhs)))
-
-(defun zero-table-build-candidates (preedit-str &optional _fetch-size)
-  "Build candidates by looking up PREEDIT-STR in `zero-table-table'."
-  (mapcar 'cdr (sort (cl-remove-if-not (lambda (pair) (string-prefix-p preedit-str (car pair))) zero-table-table) 'zero-table-sort-key)))
-
-;; (defun zero-table-build-candidates-async (preedit-str)
-;;   "build candidate list, when done show it via `zero-table-show-candidates'"
-;;   (zero-table-debug "building candidate list\n")
-;;   (let ((candidates (zero-table-build-candidates preedit-str)))
-;;     ;; update cache to make SPC and digit key selection possible.
-;;     (setq zero-table-candidates candidates)
-;;     (zero-table-show-candidates candidates)))
-
-(defun zero-table-can-start-sequence (ch)
-  "Return t if char CH can start a preedit sequence."
-  (member (make-string 1 ch) zero-table-sequence-initials))
-
-;;===============================
-;; register IM to zero framework
-;;===============================
-
-(zero-register-im
- 'zero-table
- '((:build-candidates . zero-table-build-candidates)
-   (:can-start-sequence . zero-table-can-start-sequence)))
-
-;;============
-;; public API
-;;============
-
-(defun zero-table-set-table (alist)
-  "Set the conversion table.
-
-the ALIST should be a list of (key . value) pairs.  when user type
-\(part of) key, the IM will show all matching value.
-
-e.g.
-'((\"phone\" . \"18612345678\")
-  (\"mail\" . \"foo@example.com\")
-  (\"map\" . \"https://ditu.amap.com/\")
-  (\"m\" . \"https://msdn.microsoft.com/en-us\")
-  (\"address\" . \"123 Happy Street\"))"
-  (setq zero-table-table alist)
-  (setq zero-table-sequence-initials
-	(delete-dups (mapcar (lambda (pair) (substring (car pair) 0 1))
-			     zero-table-table))))
-
-;;===========
-;; test data
-;;===========
-
-(unless zero-table-table
-  (zero-table-set-table
-   '(("phone" . "18612345678")
-     ("pyl" . "http://localdocs.emacsos.com/python2/library/%s.html")
-     ("pyli" . "http://localdocs.emacsos.com/python2/index.html")
-     ("pylm" . "http://localdocs.emacsos.com/python2/py-modindex.html")
-     ("py3li" . "http://localdocs.emacsos.com/python2/index.html")
-     ("py3l" . "http://localdocs.emacsos.com/python3/library/%s.html")
-     ("py3lm" . "http://localdocs.emacsos.com/python3/py-modindex.html")
-     ("pyop" . "http://docs.python.org/library/operator.html")
-     ("pyopl" . "http://localdocs.emacsos.com/python2/library/operator.html")
-     ("pympl" . "http://localdocs.emacsos.com/python2/library/multiprocessing.html")
-     ("py2" . "http://docs.python.org/2/library/%s.html")
-     ("py3" . "http://docs.python.org/3/library/%s.html")
-     ("py2i" . "http://docs.python.org/2/")
-     ("py2m" . "http://docs.python.org/2/py-modindex.html")
-     ("py3i" . "http://docs.python.org/3/")
-     ("py3m" . "http://docs.python.org/3/py-modindex.html")
-     ("pycodec" . "http://localdocs.emacsos.com/python2/library/codecs.html#standard-encodings")
-     ("pycodecs" . "http://localdocs.emacsos.com/python2/library/codecs.html#standard-encodings")
-     ("pycodecsr" . "http://docs.python.org/library/codecs.html#standard-encodings")
-     ("pycodecr" . "http://docs.python.org/library/codecs.html#standard-encodings")
-     ("pep328" . "http://www.python.org/dev/peps/pep-0328/")
-     ("mail" . "foo@example.com")
-     ("map" . "https://ditu.amap.com/")
-     ("m" . "https://msdn.microsoft.com/en-us")
-     ("address" . "123 Happy Street")
-     ("da" . "__da__")
-     ("now" . "__now__"))))
-
-(provide 'zero-table)
-
-;; body of zero-pinyin-service.el
-
-;;================
-;; implementation
-;;================
-
-
-(defvar zero-pinyin-service-service-name
-  "com.emacsos.zero.ZeroPinyinService1")
-(defvar zero-pinyin-service-path
-  "/com/emacsos/zero/ZeroPinyinService1")
-(defvar zero-pinyin-service-interface
-  "com.emacsos.zero.ZeroPinyinService1.ZeroPinyinServiceInterface")
-(defvar zero-pinyin-fuzzy-flag 0)
-
-(defun zero-pinyin-service-error-handler (event error)
-  "Handle dbus errors.
-
-EVENT, ERROR are arguments passed to the handler."
-  (when (or (string-equal zero-pinyin-service-service-name
-			  (dbus-event-interface-name event))
-	    (s-contains-p zero-pinyin-service-service-name (cadr error)))
-    (error "`zero-pinyin-service' dbus failed: %S" (cadr error))))
-
-(add-hook 'dbus-event-error-functions 'zero-pinyin-service-error-handler)
-
-(defun zero-pinyin-service-async-call (method handler &rest args)
-  "Call METHOD on zero-pinin-service asynchronously.
-This is a wrapper around `dbus-call-method-asynchronously'.
-Argument HANDLER the handler function.
-Optional argument ARGS extra arguments to pass to the wrapped function."
-  (apply 'dbus-call-method-asynchronously
-	 :session zero-pinyin-service-service-name
-	 zero-pinyin-service-path
-	 zero-pinyin-service-interface
-	 method handler :timeout 1000 args))
-
-(defun zero-pinyin-service-call (method &rest args)
-  "Call METHOD on zero-pinin-service synchronously.
-This is a wrapper around `dbus-call-method'.
-Optional argument ARGS extra arguments to pass to the wrapped function."
-  (apply 'dbus-call-method
-	 :session zero-pinyin-service-service-name
-	 zero-pinyin-service-path
-	 zero-pinyin-service-interface
-	 method :timeout 1000 args))
-
-;;============
-;; public API
-;;============
-
-(defun zero-pinyin-service-get-candidates (preedit-str fetch-size)
-  "Get candidates for pinyin in PREEDIT-STR synchronously.
-
-preedit-str the preedit-str, should be pure pinyin string
-FETCH-SIZE try to fetch this many candidates or more"
-  (zero-pinyin-service-call "GetCandidatesV2" :string preedit-str :uint32 fetch-size :uint32 zero-pinyin-fuzzy-flag))
-
-(defun zero-pinyin-service-get-candidates-async (preedit-str fetch-size get-candidates-complete)
-  "Get candidates for pinyin in PREEDIT-STR asynchronously.
-
-PREEDIT-STR the preedit string, should be pure pinyin string.
-FETCH-SIZE try to fetch this many candidates or more.
-GET-CANDIDATES-COMPLETE the async handler function."
-  (zero-pinyin-service-async-call
-   "GetCandidatesV2" get-candidates-complete :string preedit-str :uint32 fetch-size :uint32 zero-pinyin-fuzzy-flag))
-
-(defun zero-pinyin-candidate-pinyin-indices-to-dbus-format (candidate_pinyin_indices)
-  "Convert CANDIDATE_PINYIN_INDICES to Emacs dbus format."
-  (let (result)
-    (push :array result)
-    ;; (push :signature result)
-    ;; (push "(ii)" result)
-    (dolist (pypair candidate_pinyin_indices)
-      (push (list :struct :int32 (cl-first pypair) :int32 (cl-second pypair))
-	    result))
-    (reverse result)))
-
-(defun zero-pinyin-service-commit-candidate-async (candidate candidate_pinyin_indices)
-  "Commit candidate asynchronously.
-
-CANDIDATE the candidate user selected.
-CANDIDATE_PINYIN_INDICES the candidate's pinyin shengmu and yunmu index."
-  ;; don't care about the result, so no callback.
-  (zero-pinyin-service-async-call
-   "CommitCandidate" nil
-   :string candidate
-   (zero-pinyin-candidate-pinyin-indices-to-dbus-format candidate_pinyin_indices)))
-
-(defun zero-pinyin-service-delete-candidates-async (candidate delete-candidate-complete)
-  "Delete CANDIDATE asynchronously.
-
-DELETE-CANDIDATE-COMPLETE the async handler function."
-  (zero-pinyin-service-async-call
-   "DeleteCandidate" delete-candidate-complete :string candidate))
-
-(defun zero-pinyin-service-quit ()
-  "Quit panel application."
-  (zero-pinyin-service-async-call "Quit" nil))
-
-(provide 'zero-pinyin-service)
-
-;; body of zero-pinyin.el
-
-;;==============
-;; dependencies
-;;==============
-
-
-;;===============================
-;; basic data and emacs facility
-;;===============================
-
-;; these two var is only used in docstring to avoid checkdoc line-too-long
-;; error.
-(defvar zero-pinyin-service-interface-xml-file
-  "/usr/share/dbus-1/interfaces/com.emacsos.zero.ZeroPinyinService1.ZeroPinyinServiceInterface.xml")
-(defvar zero-pinyin-service-interface-xml-url
-  "https://gitlab.emacsos.com/sylecn/zero-pinyin-service/blob/master/com.emacsos.zero.ZeroPinyinService1.ZeroPinyinServiceInterface.xml")
-(defcustom zero-pinyin-fuzzy-flag 0
-  "Non-nil means use this value as GetCandidatesV2 fuzzy_flag param.
-see zero-pinyin-service dbus interface xml for document.
-
-You can find the xml file locally at `zero-pinyin-service-interface-xml-file'
-or online at `zero-pinyin-service-interface-xml-url'."
-  :type 'integer
-  :group 'zero-pinyin)
-
-(defvar zero-pinyin-state nil "Zero-pinyin internal state.  could be nil or `*zero-pinyin-state-im-partial-commit*'.")
-(defconst zero-pinyin--state-im-partial-commit 'IM-PARTIAL-COMMIT)
-
-(defvar zero-pinyin-used-preedit-str-lengths nil
-  "Accompany `zero-candidates', marks how many preedit-str chars are used for each candidate.")
-(defvar zero-pinyin-candidates-pinyin-indices nil
-  "Store GetCandidates dbus method candidates_pinyin_indices field.")
-(defvar zero-pinyin-pending-str "")
-(defvar zero-pinyin-pending-preedit-str "")
-(defvar zero-pinyin-pending-pinyin-indices nil
-  "Stores `zero-pinyin-pending-str' corresponds pinyin indices.")
-
-;;=====================
-;; key logic functions
-;;=====================
-
-(defun zero-pinyin-reset ()
-  "Reset states."
-  (setq zero-pinyin-state nil)
-  (setq zero-pinyin-used-preedit-str-lengths nil)
-  (setq zero-pinyin-pending-str "")
-  (setq zero-pinyin-pending-preedit-str ""))
-
-(defun zero-pinyin-init ()
-  "Called when this im is turned on."
-  (make-local-variable 'zero-pinyin-state)
-  (zero-pinyin-reset))
-
-(defun zero-pinyin-preedit-start ()
-  "Called when enter `*zero-state-im-preediting*' state."
-  (define-key zero-mode-map [remap digit-argument] 'zero-digit-argument))
-
-(defun zero-pinyin-preedit-end ()
-  "Called when leave `*zero-state-im-preediting*' state."
-  (define-key zero-mode-map [remap digit-argument] nil))
-
-(defun zero-pinyin-shutdown ()
-  "Called when this im is turned off."
-  (define-key zero-mode-map [remap digit-argument] nil))
-
-(defvar zero-pinyin--build-candidates-use-test-data nil
-  "If t, `zero-pinyin-build-candidates' will use `zero-pinyin-build-candidates-test'.")
-
-(defun zero-pinyin-build-candidates (preedit-str fetch-size)
-  "Synchronously build candidates list.
-
-PREEDIT-STR the preedit string.
-FETCH-SIZE fetch at least this many candidates if possible."
-  (if zero-pinyin--build-candidates-use-test-data
-      (progn
-	(zero-pinyin-build-candidates-test preedit-str)
-	(setq zero-fetch-size (max fetch-size (length zero-candidates))))
-    (zero-debug "zero-pinyin building candidate list synchronously\n")
-    (let ((result (zero-pinyin-service-get-candidates preedit-str fetch-size)))
-      (setq zero-fetch-size (max fetch-size (length (cl-first result))))
-      (setq zero-pinyin-used-preedit-str-lengths (cl-second result))
-      (setq zero-pinyin-candidates-pinyin-indices (cl-third result))
-      (cl-first result))))
-
-(defun zero-pinyin-build-candidates-async (preedit-str fetch-size complete-func)
-  "Asynchronously build candidate list, when done call complete-func on it.
-
-PREEDIT-STR the preedit string.
-FETCH-SIZE fetch at least this many candidates if possible.
-COMPLETE-FUNC the callback function when async call completes.  it's called with
-              fetched candidates list as parameter."
-  (zero-debug "zero-pinyin building candidate list asynchronously\n")
-  (zero-pinyin-service-get-candidates-async
-   preedit-str
-   fetch-size
-   (lambda (candidates matched_preedit_str_lengths candidates_pinyin_indices)
-     (setq zero-pinyin-used-preedit-str-lengths matched_preedit_str_lengths)
-     (setq zero-pinyin-candidates-pinyin-indices candidates_pinyin_indices)
-     (setq zero-fetch-size (max fetch-size (length candidates)))
-     ;; Note: with dynamic binding, this command result in (void-variable
-     ;; complete-func) error.
-     (funcall complete-func candidates))))
-
-(defun zero-pinyin-can-start-sequence (ch)
-  "Return t if char CH can start a preedit sequence."
-  (and (>= ch ?a)
-       (<= ch ?z)
-       (not (= ch ?i))
-       (not (= ch ?u))
-       (not (= ch ?v))))
-
-(defun zero-pinyin-pending-preedit-str-changed ()
-  "Update zero states when pending preedit string changed."
-  (setq zero-fetch-size 0)
-  (setq zero-current-page 0)
-  (zero-pinyin-build-candidates-async zero-pinyin-pending-preedit-str zero-initial-fetch-size 'zero-build-candidates-complete))
-
-(defun zero-pinyin-commit-nth-candidate (n)
-  "Commit Nth candidate and return true if it exists, otherwise, return false."
-  (let* ((n-prime (+ n (* zero-candidates-per-page zero-current-page)))
-	 (candidate (nth n-prime zero-candidates))
-	 (used-len (when candidate
-		     (nth n-prime zero-pinyin-used-preedit-str-lengths))))
-    (when candidate
-      (zero-debug
-       "zero-pinyin-commit-nth-candidate\n    n=%s candidate=%s used-len=%s zero-pinyin-pending-preedit-str=%S\n"
-       n candidate used-len zero-pinyin-pending-preedit-str)
-      (cond
-       ((null zero-pinyin-state)
-	(if (= used-len (length zero-preedit-str))
-	    (progn
-	      (zero-debug "commit in full\n")
-	      (zero-set-state zero--state-im-waiting-input)
-	      (zero-commit-text candidate)
-	      (zero-pinyin-service-commit-candidate-async
-	       candidate
-	       (nth n-prime zero-pinyin-candidates-pinyin-indices))
-	      t)
-	  (zero-debug "partial commit, in partial commit mode now.\n")
-	  (setq zero-pinyin-state zero-pinyin--state-im-partial-commit)
-	  (setq zero-pinyin-pending-str candidate)
-	  (setq zero-pinyin-pending-preedit-str (substring zero-preedit-str used-len))
-	  (setq zero-pinyin-pending-pinyin-indices
-		(nth n-prime zero-pinyin-candidates-pinyin-indices))
-	  (zero-pinyin-pending-preedit-str-changed)
-	  t))
-       ((eq zero-pinyin-state zero-pinyin--state-im-partial-commit)
-	(if (= used-len (length zero-pinyin-pending-preedit-str))
-	    (progn
-	      (zero-debug "finishes partial commit\n")
-	      (setq zero-pinyin-state nil)
-	      (zero-set-state zero--state-im-waiting-input)
-	      (zero-commit-text (concat zero-pinyin-pending-str candidate))
-	      (zero-pinyin-service-commit-candidate-async
-	       (concat zero-pinyin-pending-str candidate)
-	       (append zero-pinyin-pending-pinyin-indices
-		       (nth n-prime zero-pinyin-candidates-pinyin-indices)))
-	      t)
-	  (zero-debug "continue partial commit\n")
-	  (setq zero-pinyin-pending-str (concat zero-pinyin-pending-str candidate))
-	  (setq zero-pinyin-pending-preedit-str (substring zero-pinyin-pending-preedit-str used-len))
-	  (setq zero-pinyin-pending-pinyin-indices
-		(append zero-pinyin-pending-pinyin-indices
-			(nth n-prime zero-pinyin-candidates-pinyin-indices)))
-	  (zero-pinyin-service-commit-candidate-async
-	   zero-pinyin-pending-str
-	   zero-pinyin-pending-pinyin-indices)
-	  (zero-pinyin-pending-preedit-str-changed)
-	  t))
-       (t (error "Unexpected zero-pinyin-state: %s" zero-pinyin-state))))))
-
-(defun zero-pinyin-commit-first-candidate-or-preedit-str ()
-  "Commit first candidate if there is one, otherwise, commit preedit string."
-  (unless (zero-pinyin-commit-nth-candidate 0)
-    (zero-commit-preedit-str)))
-
-(defun zero-pinyin-commit-first-candidate-in-full ()
-  "Commit first candidate and return t if it consumes all preedit-str.
-Otherwise, just return nil."
-  (let ((candidate (nth 0 (zero-candidates-on-page zero-candidates)))
-	(used-len (nth (* zero-candidates-per-page zero-current-page) zero-pinyin-used-preedit-str-lengths)))
-    (when candidate
-      (cond
-       ((null zero-pinyin-state)
-	(when (= used-len (length zero-preedit-str))
-	  (zero-set-state zero--state-im-waiting-input)
-	  (zero-commit-text candidate)
-	  t))
-       ((eq zero-pinyin-state zero-pinyin--state-im-partial-commit)
-	(when (= used-len (length zero-pinyin-pending-preedit-str))
-	  (setq zero-pinyin-state nil)
-	  (zero-set-state zero--state-im-waiting-input)
-	  (zero-commit-text (concat zero-pinyin-pending-str candidate))
-	  t))
-       (t (error "Unexpected zero-pinyin-state: %s" zero-pinyin-state))))))
-
-(defun zero-pinyin-page-down ()
-  "Handle page down for zero-pinyin.
-
-This is different from zero-framework because I need to support partial commit"
-  (let ((len (length zero-candidates))
-	(new-fetch-size (* zero-candidates-per-page (+ 2 zero-current-page))))
-    (if (and (< len new-fetch-size)
-	     (< zero-fetch-size new-fetch-size))
-	(let ((preedit-str (if (eq zero-pinyin-state zero-pinyin--state-im-partial-commit) zero-pinyin-pending-preedit-str zero-preedit-str)))
-	  (zero-pinyin-build-candidates-async
-	   preedit-str
-	   new-fetch-size
-	   (lambda (candidates)
-	     (zero-build-candidates-complete candidates)
-	     (zero-just-page-down))))
-      (zero-just-page-down))))
-
-(defun zero-pinyin-handle-preedit-char (ch)
-  "Hanlde character insert in `*zero-state-im-preediting*' state.
-Override `zero-handle-preedit-char-default'.
-
-CH the character user typed."
-  (cond
-   ((= ch ?\s)
-    (zero-pinyin-commit-first-candidate-or-preedit-str))
-   ((and (>= ch ?0) (<= ch ?9))
-    ;; 1 commit the 0th candidate
-    ;; 2 commit the 1st candidate
-    ;; ...
-    ;; 0 commit the 9th candidate
-    (unless (zero-pinyin-commit-nth-candidate (mod (- (- ch ?0) 1) 10))
-      (zero-append-char-to-preedit-str ch)
-      (setq zero-pinyin-state nil)))
-   ((= ch zero-previous-page-key)
-    (zero-handle-preedit-char-default ch))
-   ((= ch zero-next-page-key)
-    (zero-pinyin-page-down))
-   (t (let ((str (zero-convert-punctuation ch)))
-	(if str
-	    (when (zero-pinyin-commit-first-candidate-in-full)
-	      (zero-set-state zero--state-im-waiting-input)
-	      (insert str))
-	  (setq zero-pinyin-state nil)
-	  (zero-append-char-to-preedit-str ch))))))
-
-(defun zero-pinyin-get-preedit-str-for-panel ()
-  "Return the preedit string that should show in panel."
-  (if (eq zero-pinyin-state zero-pinyin--state-im-partial-commit)
-      (concat zero-pinyin-pending-str zero-pinyin-pending-preedit-str)
-    zero-preedit-str))
-
-(defun zero-pinyin-preedit-str-changed ()
-  "Start over for candidate selection process."
-  (setq zero-pinyin-state nil)
-  (zero-preedit-str-changed))
-
-(defun zero-pinyin-backspace ()
-  "Handle backspace key in `*zero-state-im-preediting*' state."
-  (if (eq zero-pinyin-state zero-pinyin--state-im-partial-commit)
-      (zero-pinyin-preedit-str-changed)
-    (zero-backspace-default)))
-
-(defun zero-pinyin-delete-candidate (digit)
-  "Tell backend to delete candidate at DIGIT position.
-
-DIGIT is the digit key used to select nth candidate.
-DIGIT 1 means delete 1st candidate.
-DIGIT 2 means delete 2st candidate.
-...
-DIGIT 0 means delete 10th candidate."
-  (let ((candidate (nth (mod (- digit 1) 10)
-			(zero-candidates-on-page zero-candidates))))
-    (when candidate
-      (zero-pinyin-service-delete-candidates-async
-       candidate 'zero-pinyin-preedit-str-changed))))
-
-(defun zero-digit-argument ()
-  "Allow C-<digit> to DeleteCandidate in `*zero-state-im-preediting*' state."
-  (interactive)
-  (unless (eq zero-state zero--state-im-preediting)
-    (error "`zero-digit-argument' called in non preediting state"))
-  (if (memq 'control (event-modifiers last-command-event))
-      (let* ((char (if (integerp last-command-event)
-		       last-command-event
-		     (get last-command-event 'ascii-character)))
-	     (digit (- (logand char ?\177) ?0)))
-	(zero-pinyin-delete-candidate digit))))
-
-;;===============================
-;; register IM to zero framework
-;;===============================
-
-(zero-register-im
- 'pinyin
- '((:build-candidates . zero-pinyin-build-candidates)
-   ;; comment to use sync version, uncomment to use async version.
-   ;; (:build-candidates-async . zero-pinyin-build-candidates-async)
-   (:can-start-sequence . zero-pinyin-can-start-sequence)
-   (:handle-preedit-char . zero-pinyin-handle-preedit-char)
-   (:get-preedit-str-for-panel . zero-pinyin-get-preedit-str-for-panel)
-   (:handle-backspace . zero-pinyin-backspace)
-   (:init . zero-pinyin-init)
-   (:shutdown . zero-pinyin-shutdown)
-   (:preedit-start . zero-pinyin-preedit-start)
-   (:preedit-end . zero-pinyin-preedit-end)))
-
-;;============
-;; public API
-;;============
-
-;;===========
-;; test data
-;;===========
-
-(defun zero-pinyin-build-candidates-test (preedit-str)
-  "Test data for testing partial commit.
-
-PREEDIT-STR the preedit string."
-  (cond
-   ((equal preedit-str "liyifeng")
-    (setq zero-pinyin-used-preedit-str-lengths '(8 4 4 4 2 2 2))
-    '("李易峰" "利益" "礼仪" "离异" "里" "理" "力"))
-   ((equal preedit-str "feng")
-    (setq zero-pinyin-used-preedit-str-lengths '(4 4 4 4 4))
-    '("风" "封" "疯" "丰" "凤"))
-   ((equal preedit-str "yifeng")
-    (setq zero-pinyin-used-preedit-str-lengths '(6 6 2 2 2 2))
-    '("一封" "遗风" "艺" "依" "一" "以"))
-   (t nil)))
-
-(provide 'zero-pinyin)
-
-
-(provide 'zero)
-
-;;; zero.el ends here