Loading zero-framework.el +73 −46 Original line number Diff line number Diff line Loading @@ -95,17 +95,22 @@ if item is not in lst, return nil" ;;; concrete input method should define these functions and set them in the ;;; corresponding *-func variable. (defun zero-build-candidates-default (preedit-str) nil) (defun zero-can-start-sequence-default (ch) nil) (defun zero-build-candidates-default (preedit-str) nil) (defun zero-can-start-sequence-default (ch) nil) (defun zero-handle-preedit-char-default (ch) nil) (defun zero-get-preedit-str-for-panel-default () zero-preedit-str) (defvar zero-build-candidates-func 'zero-build-candidates-default "contains a function to build candidates from preedit-str") (defvar zero-can-start-sequence-func 'zero-can-start-sequence-default "contains a function to decide whether a char can start a preedit sequence") ;;; TODO provide hook functions. more complex IM may need to do clean up on these. ;; (defvar zero-on-hook nil) ;; (defvar zero-off-hook nil) (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-im nil "current input method. if nil, the empty input method will be used. Loading Loading @@ -175,7 +180,8 @@ if t, `zero-debug' will output debug msg in *zero-debug* buffer") (let ((candidates-on-page (zero-candidates-on-page (or candidates zero-candidates)))) (zero-panel-show-candidates zero-preedit-str (length candidates-on-page) candidates-on-page) (funcall zero-get-preedit-str-for-panel-func) (length candidates-on-page) candidates-on-page) (zero-debug "candidates: %s\n " (s-join "\n " candidates-on-page)) (destructuring-bind (x y) (zero-get-point-position) (zero-panel-move x y)))) Loading Loading @@ -285,7 +291,10 @@ return ch's Chinese punctuation if ch is converted. return nil otherwise" (self-insert-command n)))) ((eq zero-state *zero-state-im-preediting*) (zero-debug "still preediting\n") (unless (funcall zero-handle-preedit-char-func ch) (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 Loading @@ -303,7 +312,7 @@ return ch's Chinese punctuation if ch is converted. return nil otherwise" (zero-set-state *zero-state-im-waiting-input*) (zero-commit-first-candidate-or-preedit-str) (insert str)) (zero-append-char-to-preedit-str ch)))))) (zero-append-char-to-preedit-str ch))))))) (t (zero-debug "unexpected state: %s\n" zero-state) (self-insert-command n))))) Loading @@ -312,12 +321,8 @@ return ch's Chinese punctuation if ch is converted. return nil otherwise" "called when preedit str is changed and not empty. update and show candidate list" (zero-build-candidates-async zero-preedit-str)) (defun zero-delete-backward-char (n) "handle backspace key" (interactive "p") (unless (integerp n) (signal 'wrong-type-argument (list 'integerp n))) (if (eq zero-state *zero-state-im-preediting*) (defun zero-backspace-default () "handle backspace key in `*zero-state-im-preediting*' state" (let ((len (length zero-preedit-str))) (if (> len 1) (progn Loading @@ -325,7 +330,15 @@ return ch's Chinese punctuation if ch is converted. return nil otherwise" (substring zero-preedit-str 0 (1- len))) (zero-preedit-str-changed)) (zero-set-state *zero-state-im-waiting-input*) (zero-reset))) (zero-reset)))) (defun zero-delete-backward-char (n) "handle backspace key" (interactive "p") (unless (integerp n) (signal 'wrong-type-argument (list 'integerp n))) (if (eq zero-state *zero-state-im-preediting*) (funcall zero-backspace-func) (delete-char (- n)))) (defun zero-commit-text (text) Loading Loading @@ -365,19 +378,14 @@ return ch's Chinese punctuation if ch is converted. return nil otherwise" (unless (zero-commit-nth-candidate 0) (zero-commit-preedit-str))) (defun zero-space (n) "handle SPC key press" (interactive "p") (if (eq zero-state *zero-state-im-preediting*) (zero-commit-first-candidate-or-preedit-str) (self-insert-command n))) (defun zero-hide-candidate-list () (zero-panel-hide) (zero-debug "hide candidate list\n")) (defun zero-reset () (interactive) (zero-debug "reset\n") (setq zero-state *zero-state-im-waiting-input*) (setq zero-preedit-str "") (setq zero-candidates nil) (setq zero-current-page 0) Loading Loading @@ -407,8 +415,8 @@ return ch's Chinese punctuation if ch is converted. return nil otherwise" (defvar zero-mode-map nil "zero-mode keymap") (setq zero-mode-map '(keymap (escape . zero-reset) (13 . zero-return) (32 . zero-space) ;; C-. (67108910 . zero-cycle-punctuation-level) (remap keymap Loading @@ -422,7 +430,9 @@ return ch's Chinese punctuation if ch is converted. return nil otherwise" ))) (define-minor-mode zero-mode "a simple input method written as an emacs minor mode" "a Chinese input method framework written as an emacs minor mode. \\{zero-mode-map}" nil " Zero" zero-mode-map Loading Loading @@ -497,8 +507,9 @@ registered input method is saved in `zero-ims'" "select zero input method for current buffer. if im-name is nil, use default empty input method" ;; TODO provide completion ;; TODO provide auto completion for im-name (interactive "SSet input method to: ") ;; TODO create a macro to reduce code duplication and human error. (if im-name (let ((im-functions (cdr (assq im-name zero-ims)))) (if im-functions Loading @@ -509,11 +520,27 @@ if im-name is nil, use default empty input method" (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)) (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-can-start-sequence-func 'zero-can-start-sequence-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) )) (defun zero-set-default-im (im-name) "set given im as default zero input method" Loading zero-pinyin.el 0 → 100644 +211 −0 Original line number Diff line number Diff line ;; a pinyin input method for zero-framework ;;============== ;; dependencies ;;============== (require 's) (require 'zero-framework) ;;=============================== ;; basic data and emacs facility ;;=============================== (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-pending-str "") (defvar zero-pinyin-pending-preedit-str "") ;;===================== ;; key logic functions ;;===================== (defun zero-pinyin-reset () (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 () (make-local-variable 'zero-pinyin-state) (zero-pinyin-reset)) (defun zero-pinyin-build-candidates (preedit-str) (zero-pinyin-build-candidates-test preedit-str)) ;; (defun zero-pinyin-build-candidates-async (preedit-str) ;; "build candidate list, when done show it via `zero-pinyin-show-candidates'" ;; (zero-pinyin-debug "building candidate list\n") ;; (let ((candidates (zero-pinyin-build-candidates preedit-str))) ;; ;; update cache to make SPC and digit key selection possible. ;; (setq zero-pinyin-candidates candidates) ;; (zero-pinyin-show-candidates 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 ?u)) (not (= ch ?v)))) (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 ?u)) (should-not (zero-pinyin-can-start-sequence ?v))) (defun zero-pinyin-pending-preedit-str-changed () (zero-build-candidates-async zero-pinyin-pending-preedit-str)) (defun zero-pinyin-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))) (used-len (when candidate (nth (+ n (* zero-candidates-per-page zero-current-page)) zero-pinyin-used-preedit-str-lengths)))) (when candidate (zero-debug "zero-pinyin-commit-nth-candidate 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) 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)) (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)) 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)) (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 () (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 first candidate 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-handle-preedit-char (ch) "handle IM-PREEDITING state char insert. return t if char is handled." (cond ((or (= ch zero-previous-page-key) (= ch zero-next-page-key)) nil) ((= ch ?\s) (zero-pinyin-commit-first-candidate-or-preedit-str) t) ((and (>= ch ?0) (<= ch ?9)) ;; 1 commit the 0th candidate ;; 2 commit the 1st candidate ;; ... ;; 0 commit the 9th candidate (if (zero-pinyin-commit-nth-candidate (mod (- (- ch ?0) 1) 10)) t (progn (zero-append-char-to-preedit-str ch) (setq zero-pinyin-state nil) t))) (t (let ((str (zero-convert-punctuation ch))) (if str (if (zero-pinyin-commit-first-candidate-in-full) (progn (zero-set-state *zero-state-im-waiting-input*) (insert str) t) ;; can't commit in full, punctuation key is no-op t) (setq zero-pinyin-state nil) (zero-append-char-to-preedit-str ch) t))))) (defun zero-pinyin-get-preedit-str-for-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-backspace () "handle backspace key in `*zero-state-im-preediting*' state" (if (eq zero-pinyin-state *zero-pinyin-state-im-partial-commit*) (progn ;; start over for candidate selection process. (setq zero-pinyin-state nil) (zero-preedit-str-changed)) (zero-backspace-default))) ;;=============================== ;; register IM to zero framework ;;=============================== (zero-register-im 'pinyin '((:build-candidates . zero-pinyin-build-candidates) (: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))) ;;============ ;; public API ;;============ ;;=========== ;; test data ;;=========== (defun zero-pinyin-build-candidates-test (preedit-str) "test data for testing partial commit" (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) Loading
zero-framework.el +73 −46 Original line number Diff line number Diff line Loading @@ -95,17 +95,22 @@ if item is not in lst, return nil" ;;; concrete input method should define these functions and set them in the ;;; corresponding *-func variable. (defun zero-build-candidates-default (preedit-str) nil) (defun zero-can-start-sequence-default (ch) nil) (defun zero-build-candidates-default (preedit-str) nil) (defun zero-can-start-sequence-default (ch) nil) (defun zero-handle-preedit-char-default (ch) nil) (defun zero-get-preedit-str-for-panel-default () zero-preedit-str) (defvar zero-build-candidates-func 'zero-build-candidates-default "contains a function to build candidates from preedit-str") (defvar zero-can-start-sequence-func 'zero-can-start-sequence-default "contains a function to decide whether a char can start a preedit sequence") ;;; TODO provide hook functions. more complex IM may need to do clean up on these. ;; (defvar zero-on-hook nil) ;; (defvar zero-off-hook nil) (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-im nil "current input method. if nil, the empty input method will be used. Loading Loading @@ -175,7 +180,8 @@ if t, `zero-debug' will output debug msg in *zero-debug* buffer") (let ((candidates-on-page (zero-candidates-on-page (or candidates zero-candidates)))) (zero-panel-show-candidates zero-preedit-str (length candidates-on-page) candidates-on-page) (funcall zero-get-preedit-str-for-panel-func) (length candidates-on-page) candidates-on-page) (zero-debug "candidates: %s\n " (s-join "\n " candidates-on-page)) (destructuring-bind (x y) (zero-get-point-position) (zero-panel-move x y)))) Loading Loading @@ -285,7 +291,10 @@ return ch's Chinese punctuation if ch is converted. return nil otherwise" (self-insert-command n)))) ((eq zero-state *zero-state-im-preediting*) (zero-debug "still preediting\n") (unless (funcall zero-handle-preedit-char-func ch) (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 Loading @@ -303,7 +312,7 @@ return ch's Chinese punctuation if ch is converted. return nil otherwise" (zero-set-state *zero-state-im-waiting-input*) (zero-commit-first-candidate-or-preedit-str) (insert str)) (zero-append-char-to-preedit-str ch)))))) (zero-append-char-to-preedit-str ch))))))) (t (zero-debug "unexpected state: %s\n" zero-state) (self-insert-command n))))) Loading @@ -312,12 +321,8 @@ return ch's Chinese punctuation if ch is converted. return nil otherwise" "called when preedit str is changed and not empty. update and show candidate list" (zero-build-candidates-async zero-preedit-str)) (defun zero-delete-backward-char (n) "handle backspace key" (interactive "p") (unless (integerp n) (signal 'wrong-type-argument (list 'integerp n))) (if (eq zero-state *zero-state-im-preediting*) (defun zero-backspace-default () "handle backspace key in `*zero-state-im-preediting*' state" (let ((len (length zero-preedit-str))) (if (> len 1) (progn Loading @@ -325,7 +330,15 @@ return ch's Chinese punctuation if ch is converted. return nil otherwise" (substring zero-preedit-str 0 (1- len))) (zero-preedit-str-changed)) (zero-set-state *zero-state-im-waiting-input*) (zero-reset))) (zero-reset)))) (defun zero-delete-backward-char (n) "handle backspace key" (interactive "p") (unless (integerp n) (signal 'wrong-type-argument (list 'integerp n))) (if (eq zero-state *zero-state-im-preediting*) (funcall zero-backspace-func) (delete-char (- n)))) (defun zero-commit-text (text) Loading Loading @@ -365,19 +378,14 @@ return ch's Chinese punctuation if ch is converted. return nil otherwise" (unless (zero-commit-nth-candidate 0) (zero-commit-preedit-str))) (defun zero-space (n) "handle SPC key press" (interactive "p") (if (eq zero-state *zero-state-im-preediting*) (zero-commit-first-candidate-or-preedit-str) (self-insert-command n))) (defun zero-hide-candidate-list () (zero-panel-hide) (zero-debug "hide candidate list\n")) (defun zero-reset () (interactive) (zero-debug "reset\n") (setq zero-state *zero-state-im-waiting-input*) (setq zero-preedit-str "") (setq zero-candidates nil) (setq zero-current-page 0) Loading Loading @@ -407,8 +415,8 @@ return ch's Chinese punctuation if ch is converted. return nil otherwise" (defvar zero-mode-map nil "zero-mode keymap") (setq zero-mode-map '(keymap (escape . zero-reset) (13 . zero-return) (32 . zero-space) ;; C-. (67108910 . zero-cycle-punctuation-level) (remap keymap Loading @@ -422,7 +430,9 @@ return ch's Chinese punctuation if ch is converted. return nil otherwise" ))) (define-minor-mode zero-mode "a simple input method written as an emacs minor mode" "a Chinese input method framework written as an emacs minor mode. \\{zero-mode-map}" nil " Zero" zero-mode-map Loading Loading @@ -497,8 +507,9 @@ registered input method is saved in `zero-ims'" "select zero input method for current buffer. if im-name is nil, use default empty input method" ;; TODO provide completion ;; TODO provide auto completion for im-name (interactive "SSet input method to: ") ;; TODO create a macro to reduce code duplication and human error. (if im-name (let ((im-functions (cdr (assq im-name zero-ims)))) (if im-functions Loading @@ -509,11 +520,27 @@ if im-name is nil, use default empty input method" (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)) (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-can-start-sequence-func 'zero-can-start-sequence-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) )) (defun zero-set-default-im (im-name) "set given im as default zero input method" Loading
zero-pinyin.el 0 → 100644 +211 −0 Original line number Diff line number Diff line ;; a pinyin input method for zero-framework ;;============== ;; dependencies ;;============== (require 's) (require 'zero-framework) ;;=============================== ;; basic data and emacs facility ;;=============================== (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-pending-str "") (defvar zero-pinyin-pending-preedit-str "") ;;===================== ;; key logic functions ;;===================== (defun zero-pinyin-reset () (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 () (make-local-variable 'zero-pinyin-state) (zero-pinyin-reset)) (defun zero-pinyin-build-candidates (preedit-str) (zero-pinyin-build-candidates-test preedit-str)) ;; (defun zero-pinyin-build-candidates-async (preedit-str) ;; "build candidate list, when done show it via `zero-pinyin-show-candidates'" ;; (zero-pinyin-debug "building candidate list\n") ;; (let ((candidates (zero-pinyin-build-candidates preedit-str))) ;; ;; update cache to make SPC and digit key selection possible. ;; (setq zero-pinyin-candidates candidates) ;; (zero-pinyin-show-candidates 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 ?u)) (not (= ch ?v)))) (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 ?u)) (should-not (zero-pinyin-can-start-sequence ?v))) (defun zero-pinyin-pending-preedit-str-changed () (zero-build-candidates-async zero-pinyin-pending-preedit-str)) (defun zero-pinyin-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))) (used-len (when candidate (nth (+ n (* zero-candidates-per-page zero-current-page)) zero-pinyin-used-preedit-str-lengths)))) (when candidate (zero-debug "zero-pinyin-commit-nth-candidate 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) 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)) (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)) 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)) (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 () (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 first candidate 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-handle-preedit-char (ch) "handle IM-PREEDITING state char insert. return t if char is handled." (cond ((or (= ch zero-previous-page-key) (= ch zero-next-page-key)) nil) ((= ch ?\s) (zero-pinyin-commit-first-candidate-or-preedit-str) t) ((and (>= ch ?0) (<= ch ?9)) ;; 1 commit the 0th candidate ;; 2 commit the 1st candidate ;; ... ;; 0 commit the 9th candidate (if (zero-pinyin-commit-nth-candidate (mod (- (- ch ?0) 1) 10)) t (progn (zero-append-char-to-preedit-str ch) (setq zero-pinyin-state nil) t))) (t (let ((str (zero-convert-punctuation ch))) (if str (if (zero-pinyin-commit-first-candidate-in-full) (progn (zero-set-state *zero-state-im-waiting-input*) (insert str) t) ;; can't commit in full, punctuation key is no-op t) (setq zero-pinyin-state nil) (zero-append-char-to-preedit-str ch) t))))) (defun zero-pinyin-get-preedit-str-for-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-backspace () "handle backspace key in `*zero-state-im-preediting*' state" (if (eq zero-pinyin-state *zero-pinyin-state-im-partial-commit*) (progn ;; start over for candidate selection process. (setq zero-pinyin-state nil) (zero-preedit-str-changed)) (zero-backspace-default))) ;;=============================== ;; register IM to zero framework ;;=============================== (zero-register-im 'pinyin '((:build-candidates . zero-pinyin-build-candidates) (: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))) ;;============ ;; public API ;;============ ;;=========== ;; test data ;;=========== (defun zero-pinyin-build-candidates-test (preedit-str) "test data for testing partial commit" (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)