From f07f8824f401eed771cac2e61498b742298f3172 Mon Sep 17 00:00:00 2001
From: Yuanle Song <sylecn@gmail.com>
Date: Mon, 28 Oct 2019 11:08:54 +0800
Subject: [PATCH] v2.0.0 renamed package file to zero-input.el

since this is a backward incompatible change, I set version to v2.0.0.
all symbols and functions is now prefixed with zero-input.

This change is suggested by melpa reviewer.
---
 Makefile                                      |   13 +-
 NOTICE                                        |    5 +-
 README                                        |   26 +-
 build.py                                      |   23 +-
 git-hooks/pre-commit                          |    2 +-
 operational                                   |   30 +-
 zero-framework-test.el                        |   44 -
 zero-framework.el                             |  860 ----------
 zero-input-framework-test.el                  |   44 +
 zero-input-framework.el                       |  860 ++++++++++
 ...-panel-test.el => zero-input-panel-test.el |   14 +-
 zero-panel.el => zero-input-panel.el          |   44 +-
 ...st.el => zero-input-pinyin-service-test.el |   26 +-
 ...service.el => zero-input-pinyin-service.el |   70 +-
 zero-input-pinyin-test.el                     |   37 +
 zero-input-pinyin.el                          |  358 ++++
 zero-quickdial.el => zero-input-quickdial.el  |   30 +-
 ...-reload-all.el => zero-input-reload-all.el |   50 +-
 ...-table-test.el => zero-input-table-test.el |   24 +-
 zero-table.el => zero-input-table.el          |   66 +-
 zero-input.el                                 | 1512 +++++++++++++++++
 zero-input.el.in                              |   66 +
 zero-pinyin-test.el                           |   37 -
 zero-pinyin.el                                |  357 ----
 zero.el                                       | 1510 ----------------
 zero.el.in                                    |   65 -
 26 files changed, 3091 insertions(+), 3082 deletions(-)
 delete mode 100644 zero-framework-test.el
 delete mode 100644 zero-framework.el
 create mode 100644 zero-input-framework-test.el
 create mode 100644 zero-input-framework.el
 rename zero-panel-test.el => zero-input-panel-test.el (68%)
 rename zero-panel.el => zero-input-panel.el (67%)
 rename zero-pinyin-service-test.el => zero-input-pinyin-service-test.el (63%)
 rename zero-pinyin-service.el => zero-input-pinyin-service.el (54%)
 create mode 100644 zero-input-pinyin-test.el
 create mode 100644 zero-input-pinyin.el
 rename zero-quickdial.el => zero-input-quickdial.el (59%)
 rename zero-reload-all.el => zero-input-reload-all.el (50%)
 rename zero-table-test.el => zero-input-table-test.el (51%)
 rename zero-table.el => zero-input-table.el (65%)
 create mode 100644 zero-input.el
 create mode 100644 zero-input.el.in
 delete mode 100644 zero-pinyin-test.el
 delete mode 100644 zero-pinyin.el
 delete mode 100644 zero.el
 delete mode 100644 zero.el.in

diff --git a/Makefile b/Makefile
index 02e4efb..0935d2d 100644
--- a/Makefile
+++ b/Makefile
@@ -1,13 +1,13 @@
-VERSION := $(shell grep 'setq zero-version' zero-framework.el | cut -d'"' -f2)
+VERSION := $(shell grep 'setq zero-input-version' zero-input-framework.el | cut -d'"' -f2)
 
 default: dist
 #===============
 # multiple file
 #===============
 check:
-	emacs -Q --batch -l zero-reload-all.el -f zero-rebuild -l zero-table.el -l zero-table-test.el -f ert-run-tests-batch
+	emacs -Q --batch -l zero-input-reload-all.el -f zero-input-rebuild -l zero-input-table.el -l zero-input-table-test.el -f ert-run-tests-batch
 zip:
-	git archive -o zero-el-$(VERSION).zip --prefix=zero/ HEAD
+	git archive -o zero-input-el-$(VERSION).zip --prefix=zero-input/ HEAD
 #==========================
 # single file distribution
 #==========================
@@ -16,9 +16,12 @@ build:
 	if [ ! -x ~/.local/bin/pytest ]; then python3 -m pip install --user pytest; fi
 	~/.local/bin/pytest build.py
 	./build.py
-	sed -i "s/PKG_VERSION/$(VERSION)/g" zero.el
+	sed -i "s/PKG_VERSION/$(VERSION)/g" zero-input.el
 dist-check: build
-	emacs -Q --batch -l ~/.emacs.d/elpa/s-1.11.0/s.el -l zero.el -l zero-panel-test.el -l zero-pinyin-service-test.el -l zero-framework-test.el -l zero-pinyin-test.el -l zero-table.el -l zero-table-test.el -f ert-run-tests-batch
+	@echo "testing byte-compile is clean..."
+	emacs -Q --batch -l ~/.emacs.d/elpa/s-1.11.0/s.el --eval='(byte-compile-file "zero-input.el")'
+	@echo "running unit tests..."
+	emacs -Q --batch -l ~/.emacs.d/elpa/s-1.11.0/s.el -l zero-input.el -l zero-input-panel-test.el -l zero-input-pinyin-service-test.el -l zero-input-framework-test.el -l zero-input-pinyin-test.el -l zero-input-table.el -l zero-input-table-test.el -f ert-run-tests-batch
 #====================
 # other make targets
 #====================
diff --git a/NOTICE b/NOTICE
index f330689..00d1234 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 cecd0e2..3a02985 100644
--- a/README
+++ b/README
@@ -1,44 +1,44 @@
 * 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
+- zero-input.el
 
   It's a generated file for one-file package distribution. Not used for
   development.
 
-- zero-framework.el
+- zero-input-framework.el
 
   zero framework source code. This provides the framework and user interface
   for zero-el.
 
-- zero-panel.el
+- zero-input-panel.el
 
   dbus client to zero-panel service.
 
-- zero-pinyin-service.el
+- zero-input-pinyin-service.el
 
   dbus client to zero-pinyin-service.
 
-- zero-pinyin.el
+- zero-input-pinyin.el
 
   Pinyin input method implemented using zero.el
 
-- zero-quickdial.el
+- zero-input-quickdial.el
 
   proof of concept of how to create an input method in emacs using minor mode.
 
-- zero-reload-all.el
+- zero-input-reload-all.el
 
   zero-el development utility.
 
-- zero-table.el
+- zero-input-table.el
 
   serves as an example of how to use zero framework to create new input
   methods.
@@ -49,5 +49,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
-GPLv3. see NOTICE file.
+zero-input--ibus-compute-pixel-position function in zero-input-framework.el is
+under GPLv3. see NOTICE file.
diff --git a/build.py b/build.py
index 389dd90..79cd58e 100755
--- a/build.py
+++ b/build.py
@@ -2,7 +2,7 @@
 # coding=utf-8
 
 """
-build zero.el from zero.el.in and other el files
+build zero-input.el from zero-input.el.in and other el files
 """
 
 import logging
@@ -18,9 +18,10 @@ def placeholder_for_file(fn):
 
 
 def test_placeholder_for_file():
-    assert placeholder_for_file("zero-panel.el") == "INCLUDE_ZERO_PANEL_EL"
-    assert placeholder_for_file("zero-pinyin-service.el") == (
-        "INCLUDE_ZERO_PINYIN_SERVICE_EL")
+    assert placeholder_for_file("zero-input-panel.el") == (
+        "INCLUDE_ZERO_INPUT_PANEL_EL")
+    assert placeholder_for_file("zero-input-pinyin-service.el") == (
+        "INCLUDE_ZERO_INPUT_PINYIN_SERVICE_EL")
 
 
 def get_el_file_body(fn):
@@ -57,16 +58,16 @@ def main():
     logging.basicConfig(
         format='%(asctime)s [%(module)s] %(levelname)-8s %(message)s',
         level=logging.INFO)
-    with open("zero.el.in") as tpl:
+    with open("zero-input.el.in") as tpl:
         content = tpl.read()
     expanded_content = expand_placeholder_for_files(content, [
-        "zero-panel.el",
-        "zero-framework.el",
-        "zero-table.el",
-        "zero-pinyin-service.el",
-        "zero-pinyin.el",
+        "zero-input-panel.el",
+        "zero-input-framework.el",
+        "zero-input-table.el",
+        "zero-input-pinyin-service.el",
+        "zero-input-pinyin.el",
         ])
-    with open('zero.el', 'w') as out:
+    with open('zero-input.el', 'w') as out:
         out.write(expanded_content)
 
 
diff --git a/git-hooks/pre-commit b/git-hooks/pre-commit
index 8231387..bff5705 100755
--- a/git-hooks/pre-commit
+++ b/git-hooks/pre-commit
@@ -2,5 +2,5 @@
 set -e
 if git branch |grep '^\* master'; then
 	make dist-check
-	git add zero.el
+	git add zero-input.el
 fi
diff --git a/operational b/operational
index d223d03..493fefe 100644
--- a/operational
+++ b/operational
@@ -1,9 +1,9 @@
 * COMMENT -*- mode: org -*-
 #+Date: 2019-10-08
-Time-stamp: <2019-10-23>
+Time-stamp: <2019-10-28>
 #+STARTUP: content
 * notes                                                               :entry:
-** 2019-04-01 zero.el a Chinese IM framework in emacs; FSM              :doc:
+** 2019-04-01 zero-el a Chinese IM framework in emacs; FSM              :doc:
 title was: how to write a modern im for emacs.
 cd ~/lisp/elisp/zero/
 - DONE can I implement it as a minor mode? yes.
@@ -13,8 +13,8 @@ cd ~/lisp/elisp/zero/
 
   | Imp | state            | action                                              | next state       | trigger action                                                                                          |
   |-----+------------------+-----------------------------------------------------+------------------+---------------------------------------------------------------------------------------------------------|
-  | Y   | IM_OFF           | M-x zero-on or zero-toggle                          | IM_WAITING_INPUT | turn on minor mode                                                                                      |
-  | Y   | IM_WAITING_INPUT | type M-x zero-off or zero-toggle                    | IM_OFF           | turn off minor mode                                                                                     |
+  | Y   | IM_OFF           | M-x zero-input-on or zero-input-toggle                          | IM_WAITING_INPUT | turn on minor mode                                                                                      |
+  | Y   | IM_WAITING_INPUT | type M-x zero-input-off or zero-input-toggle                    | IM_OFF           | turn off minor mode                                                                                     |
   | Y   | IM_WAITING_INPUT | type character that can start a sequence            | IM_PREEDITING    | update preedit str, show candidate list                                                                 |
   | Y   | IM_WAITING_INPUT | type character that can not start a sequence        | IM_WAITING_INPUT | insert character (full-width aware)                                                                     |
   | Y   | IM_WAITING_INPUT | type [,.?!\]                                        | IM_WAITING_INPUT | insert Chinese punctuation character (full-width aware)                                                 |
@@ -23,7 +23,7 @@ cd ~/lisp/elisp/zero/
   | Y   | IM_PREEDITING    | type SPC                                            | IM_WAITING_INPUT | commit first candidate or preedit str (full-width aware), reset preedit str                             |
   | Y   | IM_PREEDITING    | type digit keys                                     | IM_WAITING_INPUT | commit nth candidate if it exists, otherwise, append to preedit str                                     |
   |     | IM_PREEDITING    | type C-g                                            | IM_WAITING_INPUT | reset IM (reset preedit str, hide candidate list)                                                       |
-  | Y   | IM_PREEDITING    | type M-x zero-off or zero-toggle                    | IM_OFF           | reset IM, turn off minor mode                                                                           |
+  | Y   | IM_PREEDITING    | type M-x zero-input-off or zero-input-toggle                    | IM_OFF           | reset IM, turn off minor mode                                                                           |
   | Y   | IM_PREEDITING    | type <backspace>, when preedit str is longer than 1 | IM_PREEDITING    | update preedit str, update and show candidate list                                                      |
   | Y   | IM_PREEDITING    | type <backspace>, when preedit str is length 1      | IM_WAITING_INPUT | reset IM                                                                                                |
   | Y   | IM_PREEDITING    | focus in                                            | IM_PREEDITING    | show candidat list                                                                                      |
@@ -35,10 +35,10 @@ cd ~/lisp/elisp/zero/
   in IM_OFF state, zero should not do any preedit try nor do punctuation
   translate.
 
-- DONE make zero-quickdial IM work in emacs.
-  see ~/lisp/elisp/zero/zero-quickdial.el
-- DONE make zero-table IM work in emacs. with zero-panel.
-  see ~/lisp/elisp/zero/zero-table.el
+- DONE make zero-input-quickdial IM work in emacs.
+  see ~/lisp/elisp/zero/zero-input-quickdial.el
+- DONE make zero-input-table IM work in emacs. with zero-input-panel.
+  see ~/lisp/elisp/zero/zero-input-table.el
 - during development, press F8 to byte-compile and load the current el file.
   this will also look for errors in the file.
 
@@ -52,7 +52,7 @@ cd ~/lisp/elisp/zero/
 
     user can click mouse in another emacs window.
     whenever focus is moved outside current buffer, I need a hook to run
-    zero-focus-out.
+    zero-input-focus-out.
 
     how to reproduce the problem
     =============================
@@ -75,7 +75,7 @@ cd ~/lisp/elisp/zero/
   - how to check whether string contains character?
     without converting char to string.
 
-    (zero-table-can-start-sequence) can use this.
+    (zero-input-table-can-start-sequence) can use this.
 ** 2019-10-10 documents
 - Using of D-Bus
   https://www.gnu.org/software/emacs/manual/html_mono/dbus.html
@@ -92,8 +92,8 @@ I can't run them in git pre-commit hook.
   Need to add headers to make it happy.
 
   Because it doesn't have concept on multi-file package, it insists all
-  functions in file start with file name prefix. I can't have zero-* utility
-  function in zero-panel.el
+  functions in file start with file name prefix. I can't have zero-input-* utility
+  function in zero-input-panel.el
 
 - M-x package-lint-current-buffer fail on zero.el file
   this line:
@@ -129,7 +129,7 @@ I can't run them in git pre-commit hook.
   package-lint doesn't support multiple file pkg yet.
 
   - create a single zero.el file for distribution.
-    rename current zero.el to zero-framework.el
+    rename current zero.el to zero-input-framework.el
     create zero.el.in
   - make build
     should generate zero.el and run checks on it.
@@ -146,7 +146,7 @@ I can't run them in git pre-commit hook.
   - 
 
 ** 2019-10-11 move tests to separated files.
-otherwise (require 'zero-pinyin) will fail because (require 'ert) is not in
+otherwise (require 'zero-input-pinyin) will fail because (require 'ert) is not in
 source code.
 
 ** 2019-10-09 release zero-el on melpa
diff --git a/zero-framework-test.el b/zero-framework-test.el
deleted file mode 100644
index 8247263..0000000
--- a/zero-framework-test.el
+++ /dev/null
@@ -1,44 +0,0 @@
-;;; zero-framework-test.el --- tests for zero-framework.el -*- 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.
-
-;;; Commentary:
-
-;; tests for zero-framework.el
-
-;;; Code:
-
-(require 'zero-framework)
-(require 'ert)
-
-(ert-deftest zero-cycle-list ()
-  (should (= (zero-cycle-list '(1 2 3) 1) 2))
-  (should (eq (zero-cycle-list '(a b c) 'a) 'b))
-  (should (eq (zero-cycle-list '(a b c) 'b) 'c))
-  (should (eq (zero-cycle-list '(a b c) 'c) 'a))
-  (should (eq (zero-cycle-list '(a b c) 'd) nil)))
-
-(ert-deftest zero-convert-ch-to-full-width ()
-  (should (= (zero-convert-ch-to-full-width ?\!) ?\!)))
-
-(ert-deftest zero-convert-str-to-full-width ()
-  (should (string-equal "!" (zero-convert-str-to-full-width "!")))
-  (should (string-equal "(" (zero-convert-str-to-full-width "(")))
-  (should (string-equal "(:)" (zero-convert-str-to-full-width "(:)")))
-  (should (string-equal "ABab" (zero-convert-str-to-full-width "ABab")))
-  (should (string-equal "hehe" (zero-convert-str-to-full-width "hehe")))
-  (should (string-equal "(A)" (zero-convert-str-to-full-width "(A)"))))
-
-(provide 'zero-framework-test)
-
-;;; zero-framework-test.el ends here
diff --git a/zero-framework.el b/zero-framework.el
deleted file mode 100644
index 103eefa..0000000
--- a/zero-framework.el
+++ /dev/null
@@ -1,860 +0,0 @@
-;;; zero-framework.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.
-
-;;; Commentary:
-
-;; zero-framework is a Chinese input method framework for Emacs, implemented
-;; as an Emacs minor mode.
-;;
-;; 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*)
-;;
-;; 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:
-
-;;==============
-;; dependencies
-;;==============
-
-(eval-when-compile (require 'cl-lib))
-(require 's)
-(require 'zero-panel)
-
-;;=======
-;; 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)
-
-;;; zero-framework.el ends here
diff --git a/zero-input-framework-test.el b/zero-input-framework-test.el
new file mode 100644
index 0000000..64ae851
--- /dev/null
+++ b/zero-input-framework-test.el
@@ -0,0 +1,44 @@
+;;; zero-input-framework-test.el --- tests for zero-input-framework.el -*- 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.
+
+;;; Commentary:
+
+;; tests for zero-input-framework.el
+
+;;; Code:
+
+(require 'zero-input-framework)
+(require 'ert)
+
+(ert-deftest zero-input-cycle-list ()
+  (should (= (zero-input-cycle-list '(1 2 3) 1) 2))
+  (should (eq (zero-input-cycle-list '(a b c) 'a) 'b))
+  (should (eq (zero-input-cycle-list '(a b c) 'b) 'c))
+  (should (eq (zero-input-cycle-list '(a b c) 'c) 'a))
+  (should (eq (zero-input-cycle-list '(a b c) 'd) nil)))
+
+(ert-deftest zero-input-convert-ch-to-full-width ()
+  (should (= (zero-input-convert-ch-to-full-width ?\!) ?\!)))
+
+(ert-deftest zero-input-convert-str-to-full-width ()
+  (should (string-equal "!" (zero-input-convert-str-to-full-width "!")))
+  (should (string-equal "(" (zero-input-convert-str-to-full-width "(")))
+  (should (string-equal "(:)" (zero-input-convert-str-to-full-width "(:)")))
+  (should (string-equal "ABab" (zero-input-convert-str-to-full-width "ABab")))
+  (should (string-equal "hehe" (zero-input-convert-str-to-full-width "hehe")))
+  (should (string-equal "(A)" (zero-input-convert-str-to-full-width "(A)"))))
+
+(provide 'zero-input-framework-test)
+
+;;; zero-input-framework-test.el ends here
diff --git a/zero-input-framework.el b/zero-input-framework.el
new file mode 100644
index 0000000..017cd05
--- /dev/null
+++ b/zero-input-framework.el
@@ -0,0 +1,860 @@
+;;; zero-input-framework.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.
+
+;;; Commentary:
+
+;; zero-input-framework is a Chinese input method framework for Emacs,
+;; implemented as an Emacs minor mode.
+;;
+;; You can cycle zero-input-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*)
+;;
+;; 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:
+
+;;==============
+;; dependencies
+;;==============
+
+(eval-when-compile (require 'cl-lib))
+(require 's)
+(require 'zero-input-panel)
+
+;;=======
+;; 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)
+
+;;; zero-input-framework.el ends here
diff --git a/zero-panel-test.el b/zero-input-panel-test.el
similarity index 68%
rename from zero-panel-test.el
rename to zero-input-panel-test.el
index ecab78c..e0c8b17 100644
--- a/zero-panel-test.el
+++ b/zero-input-panel-test.el
@@ -1,4 +1,4 @@
-;;; zero-panel-test.el --- tests for zero-panel.el -*- lexical-binding: t -*-
+;;; zero-input-panel-test.el --- tests for zero-input-panel.el -*- 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.
@@ -19,17 +19,17 @@
 ;;; Code:
 
 (require 'ert)
-(require 'zero-panel)
+(require 'zero-input-panel)
 
-(ert-deftest zero-alist-to-asv ()
-  (should (equal (zero-alist-to-asv nil) '(:array :signature "{sv}")))
-  (should (equal (zero-alist-to-asv
+(ert-deftest zero-input-alist-to-asv ()
+  (should (equal (zero-input-alist-to-asv nil) '(:array :signature "{sv}")))
+  (should (equal (zero-input-alist-to-asv
 		  '(("name" "foo")
 		    ("timeout" :int32 10)))
 		 '(:array
 		   (:dict-entry "name" (:variant "foo"))
 		   (:dict-entry "timeout" (:variant :int32 10))))))
 
-(provide 'zero-panel-test)
+(provide 'zero-input-panel-test)
 
-;;; zero-panel-test.el ends here
+;;; zero-input-panel-test.el ends here
diff --git a/zero-panel.el b/zero-input-panel.el
similarity index 67%
rename from zero-panel.el
rename to zero-input-panel.el
index f31f888..2940905 100644
--- a/zero-panel.el
+++ b/zero-input-panel.el
@@ -1,4 +1,4 @@
-;;; zero-panel --- Provide emacs interface for zero-panel dbus service. -*- lexical-binding: t -*-
+;;; zero-input-panel --- Provide emacs interface for zero-input-panel dbus service. -*- 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.
@@ -14,7 +14,7 @@
 
 ;;; Commentary:
 
-;; use dbus to communicate with zero-panel service.
+;; use dbus to communicate with zero-input-panel service.
 
 ;;; Code:
 
@@ -25,19 +25,19 @@
 (require 'dbus)
 (require 's)
 
-(defun zero-panel-error-handler (event error)
+(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-panel dbus failed: %S" (cadr error))))
+    (error "Zero-Input-panel dbus failed: %S" (cadr error))))
 
-(add-hook 'dbus-event-error-functions 'zero-panel-error-handler)
+(add-hook 'dbus-event-error-functions 'zero-input-panel-error-handler)
 
-(defun zero-panel-async-call (method _handler &rest args)
-  "Call METHOD on zero-panel service asynchronously.
+(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."
@@ -52,13 +52,13 @@ ARGS optional extra args to pass to the wrapped function."
 ;; public utility function
 ;;=========================
 
-(defun zero-alist-to-asv (hints)
+(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-alist-to-asv
+\(zero-input-alist-to-asv
   '((\"name\" \"foo\")
     (\"timeout\" :int32 10)))
 =>
@@ -76,34 +76,34 @@ For example,
 ;; public API
 ;;============
 
-(defun zero-panel-move (x y)
+(defun zero-input-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))
+  (zero-input-panel-async-call "Move" nil :int32 x :int32 y))
 
-(defun zero-panel-show-candidates (preedit_str candidate_length candidates &optional hints)
+(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-panel-async-call "ShowCandidates" nil
+  (zero-input-panel-async-call "ShowCandidates" nil
 			 :string preedit_str
 			 :uint32 candidate_length
 			 (or candidates '(:array))
-			 (zero-alist-to-asv hints)))
+			 (zero-input-alist-to-asv hints)))
 
-(defun zero-panel-show ()
+(defun zero-input-panel-show ()
   "Show panel."
-  (zero-panel-async-call "Show" nil))
+  (zero-input-panel-async-call "Show" nil))
 
-(defun zero-panel-hide ()
+(defun zero-input-panel-hide ()
   "Hide panel."
-  (zero-panel-async-call "Hide" nil))
+  (zero-input-panel-async-call "Hide" nil))
 
-(defun zero-panel-quit ()
+(defun zero-input-panel-quit ()
   "Quit panel application."
   (interactive)
-  (zero-panel-async-call "Quit" nil))
+  (zero-input-panel-async-call "Quit" nil))
 
-(provide 'zero-panel)
+(provide 'zero-input-panel)
 
-;;; zero-panel.el ends here
+;;; zero-input-panel.el ends here
diff --git a/zero-pinyin-service-test.el b/zero-input-pinyin-service-test.el
similarity index 63%
rename from zero-pinyin-service-test.el
rename to zero-input-pinyin-service-test.el
index c1845e7..bda7993 100644
--- a/zero-pinyin-service-test.el
+++ b/zero-input-pinyin-service-test.el
@@ -1,4 +1,4 @@
-;;; zero-pinyin-service-test.el --- tests for zero-pinyin-service.el -*- lexical-binding: t -*-
+;;; zero-input-pinyin-service-test.el --- tests for zero-input-pinyin-service.el -*- 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.
@@ -18,42 +18,42 @@
 
 ;;; Code:
 
-(require 'zero-pinyin-service)
+(require 'zero-input-pinyin-service)
 (require 'ert)
 (eval-when-compile (require 'cl-macs))
 
-(ert-deftest zero-pinyin-candidate-pinyin-indices-to-dbus-format ()
-  (should (equal (zero-pinyin-candidate-pinyin-indices-to-dbus-format '((22 31)))
+(ert-deftest zero-input-pinyin-candidate-pinyin-indices-to-dbus-format ()
+  (should (equal (zero-input-pinyin-candidate-pinyin-indices-to-dbus-format '((22 31)))
 		 '(:array (:struct :int32 22 :int32 31))))
-  (should (equal (zero-pinyin-candidate-pinyin-indices-to-dbus-format
+  (should (equal (zero-input-pinyin-candidate-pinyin-indices-to-dbus-format
 		  '((17 46) (7 55)))
 		 '(:array (:struct :int32 17 :int32 46)
 			  (:struct :int32 7 :int32 55)))))
 
-(ert-deftest zero-pinyin-service-get-candidates ()
+(ert-deftest zero-input-pinyin-service-get-candidates ()
   (cl-destructuring-bind (cs ls &rest rest)
-      (zero-pinyin-service-get-candidates "liyifeng" 1)
+      (zero-input-pinyin-service-get-candidates "liyifeng" 1)
     (should (or (and (equal (car cs) "李易峰")
 		     (= (car ls) 8))
 		(and (equal (car cs) "利益")
 		     (= (car ls) 4)))))
   (cl-destructuring-bind (cs ls &rest rest)
-      (zero-pinyin-service-get-candidates "wenti" 1)
+      (zero-input-pinyin-service-get-candidates "wenti" 1)
     (should (equal (car cs) "问题"))
     (should (= (car ls) 5)))
   (cl-destructuring-bind (cs ls &rest rest)
-      (zero-pinyin-service-get-candidates "meiyou" 1)
+      (zero-input-pinyin-service-get-candidates "meiyou" 1)
     (should (equal (car cs) "没有"))
     (should (= (car ls) 6)))
   (cl-destructuring-bind (cs ls &rest rest)
-      (zero-pinyin-service-get-candidates "shi" 1)
+      (zero-input-pinyin-service-get-candidates "shi" 1)
     (should (equal (car cs) "是"))
     (should (= (car ls) 3)))
   (cl-destructuring-bind (cs ls &rest rest)
-      (zero-pinyin-service-get-candidates "de" 1)
+      (zero-input-pinyin-service-get-candidates "de" 1)
     (should (equal (car cs) "的"))
     (should (= (car ls) 2))))
 
-(provide 'zero-pinyin-service-test)
+(provide 'zero-input-pinyin-service-test)
 
-;;; zero-pinyin-service-test.el ends here
+;;; zero-input-pinyin-service-test.el ends here
diff --git a/zero-pinyin-service.el b/zero-input-pinyin-service.el
similarity index 54%
rename from zero-pinyin-service.el
rename to zero-input-pinyin-service.el
index 27e994d..4924890 100644
--- a/zero-pinyin-service.el
+++ b/zero-input-pinyin-service.el
@@ -1,4 +1,4 @@
-;;; zero-pinyin-service.el --- Provide emacs interface for zero-pinyin-service dbus service. -*- lexical-binding: t -*-
+;;; zero-input-pinyin-service.el --- Provide emacs interface for zero-input-pinyin-service dbus service. -*- 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.
@@ -23,67 +23,67 @@
 (require 'dbus)
 (require 's)
 
-(defvar zero-pinyin-service-service-name
+(defvar zero-input-pinyin-service-service-name
   "com.emacsos.zero.ZeroPinyinService1")
-(defvar zero-pinyin-service-path
+(defvar zero-input-pinyin-service-path
   "/com/emacsos/zero/ZeroPinyinService1")
-(defvar zero-pinyin-service-interface
+(defvar zero-input-pinyin-service-interface
   "com.emacsos.zero.ZeroPinyinService1.ZeroPinyinServiceInterface")
-(defvar zero-pinyin-fuzzy-flag 0)
+(defvar zero-input-pinyin-fuzzy-flag 0)
 
-(defun zero-pinyin-service-error-handler (event error)
+(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-pinyin-service-service-name
+  (when (or (string-equal zero-input-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))))
+	    (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-pinyin-service-error-handler)
+(add-hook 'dbus-event-error-functions 'zero-input-pinyin-service-error-handler)
 
-(defun zero-pinyin-service-async-call (method handler &rest args)
-  "Call METHOD on zero-pinin-service asynchronously.
+(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-pinyin-service-service-name
-	 zero-pinyin-service-path
-	 zero-pinyin-service-interface
+	 :session zero-input-pinyin-service-service-name
+	 zero-input-pinyin-service-path
+	 zero-input-pinyin-service-interface
 	 method handler :timeout 1000 args))
 
-(defun zero-pinyin-service-call (method &rest args)
-  "Call METHOD on zero-pinin-service synchronously.
+(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-pinyin-service-service-name
-	 zero-pinyin-service-path
-	 zero-pinyin-service-interface
+	 :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-pinyin-service-get-candidates (preedit-str fetch-size)
+(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-pinyin-service-call "GetCandidatesV2" :string preedit-str :uint32 fetch-size :uint32 zero-pinyin-fuzzy-flag))
+  (zero-input-pinyin-service-call "GetCandidatesV2" :string preedit-str :uint32 fetch-size :uint32 zero-input-pinyin-fuzzy-flag))
 
-(defun zero-pinyin-service-get-candidates-async (preedit-str fetch-size get-candidates-complete)
+(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-pinyin-service-async-call
-   "GetCandidatesV2" get-candidates-complete :string preedit-str :uint32 fetch-size :uint32 zero-pinyin-fuzzy-flag))
+  (zero-input-pinyin-service-async-call
+   "GetCandidatesV2" get-candidates-complete :string preedit-str :uint32 fetch-size :uint32 zero-input-pinyin-fuzzy-flag))
 
-(defun zero-pinyin-candidate-pinyin-indices-to-dbus-format (candidate_pinyin_indices)
+(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)
@@ -94,28 +94,28 @@ GET-CANDIDATES-COMPLETE the async handler function."
 	    result))
     (reverse result)))
 
-(defun zero-pinyin-service-commit-candidate-async (candidate candidate_pinyin_indices)
+(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-pinyin-service-async-call
+  (zero-input-pinyin-service-async-call
    "CommitCandidate" nil
    :string candidate
-   (zero-pinyin-candidate-pinyin-indices-to-dbus-format candidate_pinyin_indices)))
+   (zero-input-pinyin-candidate-pinyin-indices-to-dbus-format candidate_pinyin_indices)))
 
-(defun zero-pinyin-service-delete-candidates-async (candidate delete-candidate-complete)
+(defun zero-input-pinyin-service-delete-candidates-async (candidate delete-candidate-complete)
   "Delete CANDIDATE asynchronously.
 
 DELETE-CANDIDATE-COMPLETE the async handler function."
-  (zero-pinyin-service-async-call
+  (zero-input-pinyin-service-async-call
    "DeleteCandidate" delete-candidate-complete :string candidate))
 
-(defun zero-pinyin-service-quit ()
+(defun zero-input-pinyin-service-quit ()
   "Quit panel application."
-  (zero-pinyin-service-async-call "Quit" nil))
+  (zero-input-pinyin-service-async-call "Quit" nil))
 
-(provide 'zero-pinyin-service)
+(provide 'zero-input-pinyin-service)
 
-;;; zero-pinyin-service.el ends here
+;;; zero-input-pinyin-service.el ends here
diff --git a/zero-input-pinyin-test.el b/zero-input-pinyin-test.el
new file mode 100644
index 0000000..e27ddff
--- /dev/null
+++ b/zero-input-pinyin-test.el
@@ -0,0 +1,37 @@
+;;; zero-input-pinyin-test.el --- tests for zero-input-pinyin.el
+
+;; 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.
+
+;;; Commentary:
+
+;;
+
+;;; Code:
+
+(require 'zero-input-pinyin)
+(require 'ert)
+
+(ert-deftest zero-input-pinyin-can-start-sequence ()
+  (should (zero-input-pinyin-can-start-sequence ?a))
+  (should (zero-input-pinyin-can-start-sequence ?l))
+  (should (zero-input-pinyin-can-start-sequence ?m))
+  (should (zero-input-pinyin-can-start-sequence ?z))
+  (should-not (zero-input-pinyin-can-start-sequence ?1))
+  (should-not (zero-input-pinyin-can-start-sequence ?.))
+  (should-not (zero-input-pinyin-can-start-sequence ?i))
+  (should-not (zero-input-pinyin-can-start-sequence ?u))
+  (should-not (zero-input-pinyin-can-start-sequence ?v)))
+
+(provide 'zero-input-pinyin-test)
+
+;;; zero-input-pinyin-test.el ends here
diff --git a/zero-input-pinyin.el b/zero-input-pinyin.el
new file mode 100644
index 0000000..6fb279c
--- /dev/null
+++ b/zero-input-pinyin.el
@@ -0,0 +1,358 @@
+;;; zero-input-pinyin.el --- A pinyin input method for zero-input-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.
+
+;;; Commentary:
+
+;; To use this input method, add in Emacs init file:
+;;
+;;   (add-to-list 'load-path "~/fromsource/zero")  ;; omit if install from melpa
+;;   (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)
+
+;;; Code:
+
+;;==============
+;; dependencies
+;;==============
+
+(require 'zero-input-framework)
+(require 'zero-input-pinyin-service)
+
+;;===============================
+;; 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)
+
+;;; zero-input-pinyin.el ends here
diff --git a/zero-quickdial.el b/zero-input-quickdial.el
similarity index 59%
rename from zero-quickdial.el
rename to zero-input-quickdial.el
index 989d92b..3c78ad3 100644
--- a/zero-quickdial.el
+++ b/zero-input-quickdial.el
@@ -1,4 +1,4 @@
-;;; zero-quickdial --- quickdial input method written as an emacs minor mode. -*- lexical-binding: t -*-
+;;; zero-input-quickdial --- quickdial input method written as an emacs minor mode. -*- 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.
@@ -15,44 +15,44 @@
 ;;; Commentary:
 
 ;; To use this input method,
-;; M-x zero-quickdial-mode    ; turn on IM
+;; M-x zero-input-quickdial-mode    ; turn on IM
 ;; type 1 will insert one
 ;; type 2 will insert two
 ;; type 3 will insert three.
-;; M-x zero-quickdial-mode    ; turn off IM
+;; M-x zero-input-quickdial-mode    ; turn off IM
 ;;
 ;; This is just a demo of how Emacs minor mode can work as input method.
 
 ;;; Code:
 
-(defun zero-quickdial-insert-one ()
+(defun zero-input-quickdial-insert-one ()
   "Insert \"one\"."
   (interactive)
   (insert "one"))
 
-(defun zero-quickdial-insert-two ()
+(defun zero-input-quickdial-insert-two ()
   "Insert \"two\"."
   (interactive)
   (insert "two"))
 
-(defun zero-quickdial-insert-three ()
+(defun zero-input-quickdial-insert-three ()
   "Insert \"three\"."
   (interactive)
   (insert "three"))
 
-(defvar zero-quickdial-mode-map
+(defvar zero-input-quickdial-mode-map
   '(keymap
-    (49 . zero-quickdial-insert-one)
-    (50 . zero-quickdial-insert-two)
-    (51 . zero-quickdial-insert-three))
-  "Keymap for zero-quickdial-mode.")
+    (49 . zero-input-quickdial-insert-one)
+    (50 . zero-input-quickdial-insert-two)
+    (51 . zero-input-quickdial-insert-three))
+  "Keymap for zero-input-quickdial-mode.")
 
-(define-minor-mode zero-quickdial-mode
+(define-minor-mode zero-input-quickdial-mode
   "a simple input method written as an emacs minor mode"
   nil
   " Quickdial"
-  zero-quickdial-mode-map)
+  zero-input-quickdial-mode-map)
 
-(provide 'zero-quickdial)
+(provide 'zero-input-quickdial)
 
-;;; zero-quickdial.el ends here
+;;; zero-input-quickdial.el ends here
diff --git a/zero-reload-all.el b/zero-input-reload-all.el
similarity index 50%
rename from zero-reload-all.el
rename to zero-input-reload-all.el
index 98360b9..a3b11ea 100644
--- a/zero-reload-all.el
+++ b/zero-input-reload-all.el
@@ -1,4 +1,4 @@
-;;; zero-reload-all.el --- reload zero-el in correct order -*- no-byte-compile: t; -*-
+;;; zero-input-reload-all.el --- reload zero-input-el in correct order -*- no-byte-compile: t; -*-
 
 ;; Licensed under the Apache License, Version 2.0 (the "License");
 ;; you may not use this file except in compliance with the License.
@@ -16,42 +16,42 @@
 
 ;;; Code:
 
-(defun zero-rebuild (&optional source-dir)
-  "Rebuild zero-el.
+(defun zero-input-rebuild (&optional source-dir)
+  "Rebuild zero-input-el.
 
 SOURCE-DIR where to find the zero source dir."
   (interactive)
   ;; for loading s
   (package-initialize)
   (let ((source-dir (or source-dir "~/lisp/elisp/zero/")))
-    (dolist (f '("zero-quickdial.el"
-		 "zero-panel.el"
-		 "zero-panel-test.el"
-		 "zero-framework.el"
-		 "zero-framework-test.el"
-		 "zero-pinyin-service.el"
-		 "zero-pinyin-service-test.el"
-		 "zero-pinyin.el"
-		 "zero-pinyin-test.el"
+    (dolist (f '("zero-input-quickdial.el"
+		 "zero-input-panel.el"
+		 "zero-input-panel-test.el"
+		 "zero-input-framework.el"
+		 "zero-input-framework-test.el"
+		 "zero-input-pinyin-service.el"
+		 "zero-input-pinyin-service-test.el"
+		 "zero-input-pinyin.el"
+		 "zero-input-pinyin-test.el"
 		 ))
       (byte-compile-file (concat source-dir f) t))))
 
-(defun zero-reload-all ()
+(defun zero-input-reload-all ()
   "Recompile and load all zero files."
   (interactive)
   (byte-recompile-directory "~/lisp/elisp/zero/" 0)
-  (dolist (f '("zero-quickdial.elc"
-	       "zero-panel.elc"
-	       "zero-panel-test.elc"
-	       "zero-framework.elc"
-	       "zero-framework-test.elc"
-	       "zero-pinyin-service.elc"
-	       "zero-pinyin-service-test.elc"
-	       "zero-pinyin.elc"
-	       "zero-pinyin-test.elc"
-	       "zero-table.el"
-	       "zero-table-test.el"
+  (dolist (f '("zero-input-quickdial.elc"
+	       "zero-input-panel.elc"
+	       "zero-input-panel-test.elc"
+	       "zero-input-framework.elc"
+	       "zero-input-framework-test.elc"
+	       "zero-input-pinyin-service.elc"
+	       "zero-input-pinyin-service-test.elc"
+	       "zero-input-pinyin.elc"
+	       "zero-input-pinyin-test.elc"
+	       "zero-input-table.el"
+	       "zero-input-table-test.el"
 	       ))
     (load-file f)))
 
-;;; zero-reload-all.el ends here
+;;; zero-input-reload-all.el ends here
diff --git a/zero-table-test.el b/zero-input-table-test.el
similarity index 51%
rename from zero-table-test.el
rename to zero-input-table-test.el
index 00175ae..c562be1 100644
--- a/zero-table-test.el
+++ b/zero-input-table-test.el
@@ -1,4 +1,4 @@
-;;; zero-table-test.el --- tests for zero-table.el -*- no-byte-compile: t; lexical-binding: t -*-
+;;; zero-input-table-test.el --- tests for zero-input-table.el -*- no-byte-compile: t; 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.
@@ -18,22 +18,22 @@
 
 ;;; Code:
 
-(require 'zero-table)
+(require 'zero-input-table)
 (require 'ert)
 
-(ert-deftest zero-table-build-candidates ()
-  (should (equal (zero-table-build-candidates "ph") '("18612345678")))
-  (should (equal (zero-table-build-candidates "m")
+(ert-deftest zero-input-table-build-candidates ()
+  (should (equal (zero-input-table-build-candidates "ph") '("18612345678")))
+  (should (equal (zero-input-table-build-candidates "m")
 		 '("https://msdn.microsoft.com/en-us"
 		   "foo@example.com"
 		   "https://ditu.amap.com/"))))
 
-(ert-deftest zero-table-can-start-sequence ()
-  (should (zero-table-can-start-sequence ?a))
-  (should (zero-table-can-start-sequence ?m))
-  (should-not (zero-table-can-start-sequence ?1))
-  (should-not (zero-table-can-start-sequence ?b)))
+(ert-deftest zero-input-table-can-start-sequence ()
+  (should (zero-input-table-can-start-sequence ?a))
+  (should (zero-input-table-can-start-sequence ?m))
+  (should-not (zero-input-table-can-start-sequence ?1))
+  (should-not (zero-input-table-can-start-sequence ?b)))
 
-(provide 'zero-table-test)
+(provide 'zero-input-table-test)
 
-;;; zero-table-test.el ends here
+;;; zero-input-table-test.el ends here
diff --git a/zero-table.el b/zero-input-table.el
similarity index 65%
rename from zero-table.el
rename to zero-input-table.el
index 9ce321d..4c7a8c1 100644
--- a/zero-table.el
+++ b/zero-input-table.el
@@ -1,4 +1,4 @@
-;;; zero-table.el --- a demo table based input method based on zero.el -*- no-byte-compile: t; lexical-binding: t -*-
+;;; zero-input-table.el --- a demo table based input method based on zero.el -*- no-byte-compile: t; 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.
@@ -14,14 +14,14 @@
 
 ;;; Commentary:
 
-;; when you type the key in `zero-table-table', IM will insert the
+;; when you type the key in `zero-input-table-table', IM will insert the
 ;; corresponding value.
 ;;
 ;; To use this demo IM,
 ;;   (add-to-list 'load-path "~/fromsource/zero")
-;;   (require 'zero-table)
-;;   (zero-set-default-im 'zero-table) ; set as default IM
-;;   or (zero-set-im 'zero-table)      ; set as current buffer's IM
+;;   (require 'zero-input-table)
+;;   (zero-input-set-default-im 'zero-input-table) ; set as default IM
+;;   or (zero-input-set-im 'zero-input-table)      ; set as current buffer's IM
 
 ;;; Code:
 
@@ -29,54 +29,54 @@
 ;; dependencies
 ;;==============
 
-(require 'zero-framework)
+(require 'zero-input-framework)
 
 ;;===============================
 ;; 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'.")
+(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-table-sort-key (lhs rhs)
+(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-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-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-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)))
+;; (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-table-candidates candidates)
-;;     (zero-table-show-candidates candidates)))
+;;     (setq zero-input-table-candidates candidates)
+;;     (zero-input-table-show-candidates candidates)))
 
-(defun zero-table-can-start-sequence (ch)
+(defun zero-input-table-can-start-sequence (ch)
   "Return t if char CH can start a preedit sequence."
-  (member (make-string 1 ch) zero-table-sequence-initials))
+  (member (make-string 1 ch) zero-input-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)))
+(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-table-set-table (alist)
+(defun zero-input-table-set-table (alist)
   "Set the conversion table.
 
 the ALIST should be a list of (key . value) pairs.  when user type
@@ -88,17 +88,17 @@ e.g.
   (\"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
+  (setq zero-input-table-table alist)
+  (setq zero-input-table-sequence-initials
 	(delete-dups (mapcar (lambda (pair) (substring (car pair) 0 1))
-			     zero-table-table))))
+			     zero-input-table-table))))
 
 ;;===========
 ;; test data
 ;;===========
 
-(unless zero-table-table
-  (zero-table-set-table
+(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")
@@ -127,6 +127,6 @@ e.g.
      ("da" . "__da__")
      ("now" . "__now__"))))
 
-(provide 'zero-table)
+(provide 'zero-input-table)
 
-;;; zero-table.el ends here
+;;; zero-input-table.el ends here
diff --git a/zero-input.el b/zero-input.el
new file mode 100644
index 0000000..af795a4
--- /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-input.el.in b/zero-input.el.in
new file mode 100644
index 0000000..88484cb
--- /dev/null
+++ b/zero-input.el.in
@@ -0,0 +1,66 @@
+;;; 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: PKG_VERSION
+;; URL: https://gitlab.emacsos.com/sylecn/zero-el
+;; Package-Version: PKG_VERSION
+;; 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)
+
+INCLUDE_ZERO_INPUT_PANEL_EL
+INCLUDE_ZERO_INPUT_FRAMEWORK_EL
+INCLUDE_ZERO_INPUT_TABLE_EL
+INCLUDE_ZERO_INPUT_PINYIN_SERVICE_EL
+INCLUDE_ZERO_INPUT_PINYIN_EL
+
+(provide 'zero-input)
+
+;;; zero-input.el ends here
diff --git a/zero-pinyin-test.el b/zero-pinyin-test.el
deleted file mode 100644
index 85ac701..0000000
--- a/zero-pinyin-test.el
+++ /dev/null
@@ -1,37 +0,0 @@
-;;; zero-pinyin-test.el --- tests for zero-pinyin.el
-
-;; 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.
-
-;;; Commentary:
-
-;;
-
-;;; Code:
-
-(require 'zero-pinyin)
-(require 'ert)
-
-(ert-deftest zero-pinyin-can-start-sequence ()
-  (should (zero-pinyin-can-start-sequence ?a))
-  (should (zero-pinyin-can-start-sequence ?l))
-  (should (zero-pinyin-can-start-sequence ?m))
-  (should (zero-pinyin-can-start-sequence ?z))
-  (should-not (zero-pinyin-can-start-sequence ?1))
-  (should-not (zero-pinyin-can-start-sequence ?.))
-  (should-not (zero-pinyin-can-start-sequence ?i))
-  (should-not (zero-pinyin-can-start-sequence ?u))
-  (should-not (zero-pinyin-can-start-sequence ?v)))
-
-(provide 'zero-pinyin-test)
-
-;;; zero-pinyin-test.el ends here
diff --git a/zero-pinyin.el b/zero-pinyin.el
deleted file mode 100644
index 9f85949..0000000
--- a/zero-pinyin.el
+++ /dev/null
@@ -1,357 +0,0 @@
-;;; zero-pinyin.el --- A pinyin input method for zero-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.
-
-;;; Commentary:
-
-;; To use this input method, add in Emacs init file:
-;;
-;;   (add-to-list 'load-path "~/fromsource/zero")  ;; omit if install from melpa
-;;   (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)
-
-;;; Code:
-
-;;==============
-;; dependencies
-;;==============
-
-(require 'zero-framework)
-(require 'zero-pinyin-service)
-
-;;===============================
-;; 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)
-
-;;; zero-pinyin.el ends here
diff --git a/zero.el b/zero.el
deleted file mode 100644
index f4ede41..0000000
--- 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
diff --git a/zero.el.in b/zero.el.in
deleted file mode 100644
index 55d26c5..0000000
--- a/zero.el.in
+++ /dev/null
@@ -1,65 +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: PKG_VERSION
-;; URL: https://gitlab.emacsos.com/sylecn/zero-el
-;; Package-Version: PKG_VERSION
-;; 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)
-
-INCLUDE_ZERO_PANEL_EL
-INCLUDE_ZERO_FRAMEWORK_EL
-INCLUDE_ZERO_TABLE_EL
-INCLUDE_ZERO_PINYIN_SERVICE_EL
-INCLUDE_ZERO_PINYIN_EL
-
-(provide 'zero)
-
-;;; zero.el ends here
-- 
GitLab