From d91d0f6b0e8a8fcb4b4926bb3da70006cb347b27 Mon Sep 17 00:00:00 2001 From: Yuanle Song <sylecn@gmail.com> Date: Tue, 4 Feb 2020 18:36:07 +0800 Subject: [PATCH] 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 --- zero-input.el | 177 ++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 127 insertions(+), 50 deletions(-) diff --git a/zero-input.el b/zero-input.el index 741c5c2..e4016a5 100644 --- a/zero-input.el +++ b/zero-input.el @@ -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,8 +422,9 @@ 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 - zero-input-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 (funcall zero-input-get-preedit-str-for-panel-func) @@ -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)) - (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))) + (progn + (zero-input-debug "will fetch more candidates") + (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-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,24 +1479,42 @@ DIGIT 0 means delete 10th candidate." ;; 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))) +(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) + (: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 ;;============ +(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) -- GitLab