Skip to content
zero-input.el 62.8 KiB
Newer Older
	(zero-input-set-im im-name-str))))
   ((symbolp im-name)
    ;; symbol is allowed for backward compatibility.
    (zero-input-set-im (symbol-name im-name)))
   (t (let* ((im-slot (assoc im-name zero-input-ims))
	     (im-functions (cdr im-slot)))
	(if im-slot
	      (zero-input-debug "switching to %s input method" im-name)
	      ;; 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)))
	      (setq zero-input-im im-name))
	  (error "Input method %S not registered in zero" im-name))))))

;;;###autoload
(defun zero-input-set-default-im (im-name)
  "Set given IM-NAME as default zero input method."
  ;; symbol is allowed for backward compatibility.
  (unless (or (stringp im-name) (symbolp im-name))
    (signal 'wrong-type-argument (list 'string-or-symbolp im-name)))
  (let ((im-name-str (if (symbolp im-name) (symbol-name im-name) im-name)))
    (setq-default zero-input-im im-name-str)
    (unless (assoc im-name-str zero-input-ims)
      (warn "Input method %s is not registered with zero-input" im-name-str))))

;;;###autoload
(defun zero-input-on ()
  "Turn on `zero-input-mode'."
  (interactive)
  (zero-input-mode 1))

(defun zero-input-off ()
  "Turn off `zero-input-mode'."
  (interactive)
  (zero-input-mode -1))
;;;###autoload
(define-obsolete-function-alias 'zero-input-toggle 'zero-input-mode
  "Zero-input v2.0.2" "Toggle `zero-input-mode'.")

(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.

To use demo data, you can call:
\(zero-input-table-set-table
 \\='((\"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))))

(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-pinyin-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-pinyin-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
;;===============================

(defcustom zero-input-pinyin-fuzzy-flag 0
  "Non-nil means enable fuzzy pinyin when calling zero pinyin service.

Fuzzy pinyin means some shengmu and some yunmu could be used
interchangeably, such as zh <-> z, l <-> n.

For supported values, please see zero-input-pinyin-service dbus
interface xml comment for GetCandidatesV2 method fuzzy_flag param.

You can find the xml file locally at
/usr/share/dbus-1/interfaces/\
com.emacsos.zero.ZeroPinyinService1.ZeroPinyinServiceInterface.xml
or online at
https://gitlab.emacsos.com/sylecn/zero-pinyin-service/\
blob/master/com.emacsos.zero.ZeroPinyinService1.ZeroPinyinServiceInterface.xml"
  :group 'zero-input-pinyin
  :type 'integer)
(defvar zero-input-pinyin-use-async-fetch nil
  "Non-nil means use async dbus call to get candidates.")
(setq zero-input-pinyin-use-async-fetch nil)
(defvar-local 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
  "Store `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."
  (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))

(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.

Return candidates list"
  (zero-input-debug "zero-input-pinyin building candidate list synchronously\n")
  (let ((result (zero-input-pinyin-service-get-candidates preedit-str fetch-size)))
    ;; update zero-input-candidates and zero-input-fetch-size is done in async
    ;; complete callback. This function only care about building the
    ;; candidates and updating zero-input-pinyin specific.
    (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-candidates candidates)
       (setq zero-input-fetch-size (max fetch-size (length candidates)))
       (setq zero-input-pinyin-used-preedit-str-lengths matched_preedit_str_lengths)
       (setq zero-input-pinyin-candidates-pinyin-indices candidates_pinyin_indices)
       ;; 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-build-candidates-unified (preedit-str fetch-size complete-func)
  "Build candidate list, when done call complete-func on it.

This may call sync or async dbus method depending on
`zero-input-pinyin-use-async-fetch'.

PREEDIT-STR the preedit string.
FETCH-SIZE fetch at least this many candidates if possible.
COMPLETE-FUNC the callback function when sync/async call completes.
              it's called with fetched candidates list as parameter."
  (if zero-input-pinyin-use-async-fetch
      (zero-input-pinyin-build-candidates-async
       preedit-str fetch-size complete-func)
    (let ((candidates (zero-input-pinyin-build-candidates
		       preedit-str fetch-size)))
      (setq zero-input-candidates candidates)
      (setq zero-input-fetch-size (max fetch-size (length candidates)))
      (funcall complete-func candidates))))

(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)
  (let ((fetch-size (zero-input-get-initial-fetch-size))
	(preedit-str zero-input-pinyin-pending-preedit-str))
    (zero-input-pinyin-build-candidates-unified
     preedit-str fetch-size
     #'zero-input-show-candidates)))

(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 (1+ (* zero-input-candidates-per-page (+ 2 zero-input-current-page)))))
    ;; (zero-input-debug
    ;;  "fetch more candidates? on page %s, has %s candidates, last-fetch-size=%s, new-fetch-size=%s\n"
    ;;  zero-input-current-page len zero-input-fetch-size new-fetch-size)
    (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-debug
	   "will fetch more candidates new-fetch-size=%s\n" new-fetch-size)
	  (zero-input-pinyin-build-candidates-unified
	   preedit-str
	   new-fetch-size
	   #'(lambda (_candidates)
	       (zero-input-just-page-down))))
      (zero-input-debug "won't fetch more candidates\n")
      (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)))
	;; ?' is used as pinyin substring separator, never auto commit on ?'
	;; insert when pre-editing.
	(if (and str (not (eq ch ?')))
	    (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
;;===============================

(defun zero-input-pinyin-register-im ()
  "Register pinyin input method in zero framework."
  (zero-input-register-im
   (append
    (if zero-input-pinyin-use-async-fetch
	'((:build-candidates-async . zero-input-pinyin-build-candidates-async))
      nil)
    '((:build-candidates . zero-input-pinyin-build-candidates)
      (: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)))))
(zero-input-pinyin-register-im)

;;============
;; public API
;;============

(defun zero-input-pinyin-enable-async ()
  "Use async call to fetch candidates."
  (interactive)
  (setq zero-input-pinyin-use-async-fetch t)
  (zero-input-pinyin-register-im)
  (message "Enabled async mode"))

(defun zero-input-pinyin-disable-async ()
  "Use sync call to fetch candidates."
  (interactive)
  (setq zero-input-pinyin-use-async-fetch nil)
  (zero-input-pinyin-register-im)
  (message "Disabled async mode"))

(provide 'zero-input-pinyin)


(provide 'zero-input)

;;; zero-input.el ends here