Commit d91d0f6b authored by Yuanle Song's avatar Yuanle Song
Browse files

v2.1.0 zero pinyin supports sync and async mode.

- bugfix on zero-input-pinyin: sometimes after GetCandidate dbus call,
  zero-input-fetch-size and zero-input-candidates is not updated. This
  was because the async function callback didn't update the metadata in
  some places.
- zero pinyin supports sync and async mode.
  to switch between them, run
  M-x zero-input-pinyin-enable-async
  M-x zero-input-pinyin-disable-async
parent cd075a5f
Loading
Loading
Loading
Loading
+127 −50
Original line number Diff line number Diff line
@@ -12,7 +12,7 @@
;; See the License for the specific language governing permissions and
;; limitations under the License.

;; Version: 2.0.6
;; Version: 2.1.0
;; URL: https://gitlab.emacsos.com/sylecn/zero-el
;; Package-Requires: ((emacs "24.3") (s "1.2.0"))

@@ -243,7 +243,7 @@ If item is not in lst, return nil."

;; zero-input-el version
(defvar zero-input-version nil "Zero package version.")
(setq zero-input-version "2.0.7")
(setq zero-input-version "2.1.0")

;; FSM state
(defconst zero-input--state-im-off 'IM-OFF)
@@ -306,11 +306,14 @@ Change will be effective only in new `zero-input-mode' buffer."
  :group 'zero
  :type 'integer)
(defvar-local zero-input-current-page 0 "Current page number.  count from 0.")
(defvar-local zero-input-initial-fetch-size 20
  "How many candidates to fetch for the first call to GetCandidates.")
(defvar-local zero-input-initial-fetch-size 21
  "How many candidates to fetch for the first call to GetCandidates.

It's best set to (1+ (* zero-input-candidates-per-page N)) where
N is number of pages you want to fetch in initial fetch.")
;; 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.
;; zero-input-fetch-size is set to fetch-size in build-candidates-async
;; complete-func lambda.
(defvar-local 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.")
@@ -419,7 +422,8 @@ enough elements, return lst as it is."

(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
  (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
@@ -438,19 +442,15 @@ enough elements, return lst as it is."
(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."
Try to find at least FETCH-SIZE number of candidates for PREEDIT-STR.
Return a list of candidates."
  ;; (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))
  ;; Note that zero-input-candidates and zero-input-fetch-size is updated in
  ;; async complete callback. This function only care about building the
  ;; candidates.
  (funcall zero-input-build-candidates-func preedit-str fetch-size))

(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'.
@@ -459,6 +459,7 @@ 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")
  ;; default implementation just call sync version `zero-input-build-candidates'.
  (let ((candidates (zero-input-build-candidates preedit-str fetch-size)))
    ;; update cache to make SPC and digit key selection possible.
    (funcall complete-func candidates)))
@@ -574,23 +575,27 @@ Return CH's Chinese punctuation if CH is converted. Return nil otherwise."
  (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-debug "showing candidates on page %s\n" 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))))
	(new-fetch-size (1+ (* zero-input-candidates-per-page (+ 2 zero-input-current-page)))))
    (zero-input-debug "decide whether to fetch more candidates, has %s candidates, last fetch size=%s, new-fetch-size=%s\n" len zero-input-fetch-size new-fetch-size)
    (if (and (< len new-fetch-size)
	     (< zero-input-fetch-size new-fetch-size))
	(progn
	  (zero-input-debug "will fetch more candidates")
	  (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-candidates candidates)
		     (setq zero-input-fetch-size (max new-fetch-size
						      (length candidates)))
		   (zero-input-just-page-down)))
		     (zero-input-just-page-down))))
      (zero-input-just-page-down))))

(defun zero-input-handle-preedit-char-default (ch)
@@ -644,11 +649,26 @@ N is the argument passed to `self-insert-command'."
      (zero-input-debug "unexpected state: %s\n" zero-input-state)
      (self-insert-command n)))))

(defun zero-input-get-initial-fetch-size ()
  "Return initial fetch size."
  (cond
   ((<= zero-input-initial-fetch-size zero-input-candidates-per-page)
    (1+ zero-input-candidates-per-page))
   ((zerop (mod zero-input-initial-fetch-size zero-input-candidates-per-page))
    (1+ zero-input-initial-fetch-size))
   (t zero-input-initial-fetch-size)))

(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))
  (let ((new-fetch-size (zero-input-get-initial-fetch-size)))
    (funcall zero-input-build-candidates-async-func
	     zero-input-preedit-str new-fetch-size
	     (lambda (candidates)
	       (setq zero-input-candidates candidates)
	       (setq zero-input-fetch-size (max new-fetch-size (length candidates)))
	       (zero-input-show-candidates candidates)))))

(defun zero-input-backspace-default ()
  "Handle backspace key in `zero-input--state-im-preediting' state."
@@ -1162,6 +1182,9 @@ You can find the xml file locally at
`zero-input-pinyin-service-interface-xml-url'."
  :type 'integer
  :group 'zero-input-pinyin)
(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
@@ -1208,10 +1231,14 @@ You can find the xml file locally at
  "Synchronously build candidates list.

PREEDIT-STR the preedit string.
FETCH-SIZE fetch at least this many candidates if possible."
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)))
    (setq zero-input-fetch-size (max fetch-size (length (cl-first result))))
    ;; 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)))
@@ -1228,9 +1255,10 @@ COMPLETE-FUNC the callback function when async call completes. it's called with
   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)
     (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))))
@@ -1243,11 +1271,34 @@ COMPLETE-FUNC the callback function when async call completes. it's called with
       (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)
  (zero-input-pinyin-build-candidates-async zero-input-pinyin-pending-preedit-str zero-input-initial-fetch-size 'zero-input-build-candidates-complete))
  (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."
@@ -1333,16 +1384,24 @@ Otherwise, just return nil."

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))))
	(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-pinyin-build-candidates-async
	(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-build-candidates-complete candidates)
	   (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)
@@ -1420,11 +1479,15 @@ DIGIT 0 means delete 10th candidate."
;; register IM to zero framework
;;===============================

(defun zero-input-pinyin-register-im ()
  "Register pinyin input method in zero framework."
  (zero-input-register-im
   'pinyin
   (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)
   ;; 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)
@@ -1432,12 +1495,26 @@ DIGIT 0 means delete 10th candidate."
      (:init . zero-input-pinyin-init)
      (:shutdown . zero-input-pinyin-shutdown)
      (:preedit-start . zero-input-pinyin-preedit-start)
   (:preedit-end . zero-input-pinyin-preedit-end)))
      (:preedit-end . zero-input-pinyin-preedit-end)))))

;;============
;; 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)