Mercurial > emacs
changeset 90124:440c632022e0
New file.
author | Kenichi Handa <handa@m17n.org> |
---|---|
date | Wed, 16 Mar 2005 11:45:07 +0000 |
parents | b34d1b1795af |
children | 8180328ec804 |
files | lisp/international/robin.el |
diffstat | 1 files changed, 569 insertions(+), 0 deletions(-) [+] |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/lisp/international/robin.el Wed Mar 16 11:45:07 2005 +0000 @@ -0,0 +1,569 @@ +;;; robin.el --- yet another input method (smaller than quail) + +;; Copyright (C) 2003, 2004, 2005 +;; National Institute of Advanced Industrial Science and Technology (AIST) +;; Registration Number: H15PRO 110 + +;; Author: TAKAHASHI Naoto <ntakahas@m17n.org> +;; Keywords: mule, multilingual, input method + +;; This program is free software; you can redistribute it and/or +;; modify it under the terms of the GNU General Public License as +;; published by the Free Software Foundation; either version 2, or (at +;; your option) any later version. + +;; This program is distributed in the hope that it will be useful, but +;; WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +;; General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with this program; see the file COPYING. If not, write to +;; the Free Software Foundation, Inc., 59 Temple Place - Suite 330, +;; Boston, MA 02111-1307, USA. + +;;; Comentary: + +;; Functionalities +;; --------------- + +;; Robin is a new input method for GNU Emacs. It has three +;; functionalities: + +;; 1. It serves as a simple input method. When the user types an ASCII +;; key sequence, robin converts it into a string. This functionality +;; is most likely used to input non-ASCII characters. + +;; 2. It converts existing buffer substring into another string. +;; This functionality is similar to the 1. above, but the input is +;; buffer substring rather than key strokes. + +;; 3. It offers reverse conversion. Each character produced by a +;; robin rule can hold the original ASCII sequence as a +;; char-code-property. + + +;; How to define conversion rules +;; ------------------------------ + +;; Each conversion rule belongs to a robin package. A robin package is +;; identified by a string called package name. Use robin-define-package +;; to define a robin package. + +;; (robin-define-package NAME DOCTSTRING +;; (INPUT1 OUPUT1) +;; (INPUT2 OUPUT2) +;; ...) + +;; NAME is a string identifying the robin package. It often starts with a +;; language name and followed by a method name. For example, +;; french-postfix, greek-prefix, etc. + +;; DOCSTRING is a documentation string for the robin method. + +;; Each INPUTn is a string. It represents a transliteration of the +;; corresponding OUTPUTn. + +;; Each OUTPUTn is a string or a character that is to be inserted as the +;; result of conversion. + +;; Neither INPUT* nor OUTPUT* are evaluated. Do not use a variable or a +;; function in those parts. Instead, use a string or character literal +;; directly. + +;; If multiple rules have the same input pattern but different output +;; patterns, only the latest definition is effective. + + +;; Example +;; ------- + +;; (robin-define-package "german-example" +;; "An example for German + +;; AE -> Ä OE -> Ö UE -> Ü +;; ae -> ä oe -> ö ue -> ü ss -> ß + +;; Repeat E or S to input itself. + +;; AEE -> AE OEE -> OE UEE -> UE +;; aee -> ae oee -> oe uee -> ue sss -> ss" + +;; ("AE" ?Ä) +;; ("OE" ?Ö) +;; ("UE" ?Ü) +;; ("ae" ?ä) +;; ("oe" ?ö) +;; ("ue" ?ü) +;; ("ss" ?ß) + +;; ("AEE" "AE") +;; ("OEE" "OE") +;; ("UEE" "UE") +;; ("aee" "ae") +;; ("oee" "oe") +;; ("uee" "ue") +;; ("sss" "ss") +;; ) + + +;; Using robin as an input method +;; ------------------------------ + +;; To use a defined robin package as an input method, register it with +;; the register-input-method function. For example, + +;; (register-input-method +;; "german-example" +;; "german" +;; 'robin-use-package +;; "de" +;; "An example for German") + +;; The first argument is the robin package name. + +;; The second argument is the language environment for which this robin +;; package is used. + +;; Use the symbol `robin-use-package' as the third argument. + +;; The fourth argument is the prompt that appears in modeline when this +;; input method is active. + +;; The fifth argument is a documentation string; it may or may not be +;; identical to the one that you specified in robin-define-package. + +;; You can activate the robin input method by typing + +;; C-u C-\ german-example RET + +;; Just like a quail package, only C-\ suffices for subsequent +;; invocation. + + +;; Using robin as a buffer translator +;; ---------------------------------- + +;; To transliterate buffer substring, use the following functions. + +;; (robin-convert-buffer &optional name) + +;; Convert the content of current buffer using a robin package. + +;; NAME, if given, is a string specifying a robin package. If NAME is +;; not given or nil, the value of `robin-current-package-name' is used. + +;; (robin-convert-region begin end &optional name) + +;; Convert the region using a robin package. + +;; NAME, if given, is a string specifying a robin package. If NAME is +;; not given or nil, the value of `robin-current-package-name' is used. + + +;; Reverse conversion +;; ------------------ + +;; If the output pattern defined in a robin rule is a character, robin +;; gives to the character a char-code-property whose key is the symbol +;; representation of the robin package name and whose value is the input +;; pattern of that character. For example, with the "german-example" +;; definition above, + +;; (get-char-code-property ?Ä 'german-example) => "AE" + +;; etc. + +;; If you do not want to assign a char-code-property to a character, use +;; a string of length one as the output pattern, e.g. + +;; (robin-define-package "german-example2" +;; "Another example for German." + +;; ("AE" "Ä") +;; ("OE" "Ö") +;; ...) + +;; Then + +;; (get-char-code-property ?Ä 'german-example2) => nil + +;; etc. + +;; If multiple input patterns in a robin package generate the same +;; character, the lastly used input pattern is given as the value of the +;; char-code-property. + +;; There are two functions for reverse conversion. + +;; (robin-invert-buffer &optional name) + +;; Apply reverse conversion to the content of current buffer. NAME, if +;; given, is a string specifying a robin package. If NAME is not given +;; or nil, the value of `robin-current-package-name' is used. + +;; (robin-invert-region begin end &optional name) + +;; Apply reverse conversion to the region. NAME, if given, is a string +;; specifying a robin package. If NAME is not given or nil, the value of +;; `robin-current-package-name' is used. + + +;; Modifying an existing rule +;; -------------------------- + +;; Use the robin-modify-package function to modify a rule already defined +;; in a Robin package. + +;; (robin-modify-package name input output) + +;; Change a rule in an already defined Robin package. +;; NAME is the string specifying a robin package. +;; INPUT is a string that specifies the input pattern. +;; OUTPUT is either a character or a string to be generated. + + +;; The name of the game +;; -------------------- + +;; As stated in Murphy's law, it took longer than expected to develop the +;; very first version of Japanese input subsystem in NEmacs (Nihongo +;; Emacs). So the subsystem was named "TAMAGO", which is an acronym of +;; "TAkusan Matasete GOmennasai" (Sorry to have kept you waiting so +;; long). "Tamago" as a Japanese word means "egg", so the word "egg" was +;; also used for related filenames and function names. + +;; Since it was designed to input CJK characters, Egg was rather big as a +;; subsystem. So later in Mule (Multilingual Enhancement to GNU Emacs), +;; we designed and implemented a smaller input subsystem. We had to give +;; it a name. "So, what's smaller than an egg?" "A quail egg, of +;; course." Therefore it was named "quail". + +;; As time went by, quail became more and more complicated. That +;; tendency was inevitable as long as we support CJK input. However, if +;; we can limit ourselves to non-CJK characters, a much simpler +;; transliteration mechanism suffices. So I wrote "robin", whose name +;; was chosen because a robin is smaller than a quail. I could name it +;; "hummingbird" or "nightingale", but those spellings seemed too long. + + +;;; Code: + +(defvar robin-package-alist nil + "List of robin packages. +A robin pacakge is of the form (NAME DOCSTRING &rest RULES). +NAME is a string specifying a particular robin package. +DOCSTRING is a documentation string for the robin package. + +RULE is of the form (KEY OUTPUT &rest rules). +KEY is a string. +OUTPUT is a character or a string. +For example, if you evaluate the following, + +(robin-define-package \"test\" \"Uppercase input characters\" + (\"a\" \"A\") + (\"ab\" \"AB\") + (\"ac\" \"AC\") + (\"acd\" \"ACD\") + (\"ace\" \"ACE\") + (\"b\" \"B\")) + +this robin package will be the following. + + (\"test\" \"Uppercase input characters\" + (?a \"A\" + (?b \"AB\") + (?c \"AC\" + (?d \"ACD\") + (?e \"ACE\"))) + (?b \"B\")) +") + +;;;###autoload +(defmacro robin-define-package (name docstring &rest rules) + "Define a robin package. + +NAME is the string of this robin package. +DOCSTRING is the documentation string of this robin package. +Each RULE is of the form (INPUT OUTPUT) where INPUT is a string and +OUTPUT is either a character or a string. RULES are not evaluated. + +If there already exists a robin package whose name is NAME, the new +one replaces the old one." + + (let ((old (assoc name robin-package-alist)) + (new (list name "")) ; "" as a fake output + input output) + (dolist (r rules) + (setq input (car r) + output (cadr r)) + (robin-add-rule name new input output) + (cond + ((not (stringp input)) + (error "Bad input sequence %S" r)) + ((characterp output) + (put-char-code-property output (intern name) input)) + ((not (stringp output)) + (error "Bad output pattern %S" r)))) + (setcar (cdr new) docstring) ; replace "" above with real docstring + (if old + (setcdr old (cdr new)) + (setq robin-package-alist + (cons new robin-package-alist))) + `(setq robin-package-alist ',robin-package-alist))) + +;;;###autoload +(defun robin-modify-package (name input output) + "Change a rule in an already defined robin package. + +NAME is the string specifying a robin package. +INPUT is a string that specifies the input pattern. +OUTPUT is either a character or a string to be generated." + + (let ((tree (assoc name robin-package-alist)) + docstring) + (if (not tree) + (error "No such robin package") + (setq docstring (cadr tree)) + (setcar (cdr tree) "") + (robin-add-rule name tree input output) + (setcar (cdr tree) docstring) + (if (characterp output) + (put-char-code-property output (intern name) input)))) + output) + +(defun robin-add-rule (name tree input output) + "Add translation rule (INPUT OUTPUT) to TREE whose name is NAME. +Internal use only." + + (let* ((head (aref input 0)) + (branch (assoc head tree)) + (sofar (cadr tree))) + + (if (= (length input) 1) + (if branch + + ;; A definition already exists for this input. + (progn + (setcar (cdr branch) output) + ;; Cancel char-code-property for old definition. + (when (characterp output) + (put-char-code-property output (intern name) nil))) + + ;; New definition for this input. + (setcdr (last tree) (list (list head output)))) + + (unless branch + (if (characterp sofar) + (setq sofar (char-to-string sofar))) + (setq branch + (list head + (concat sofar + (char-to-string head)))) + (setcdr (last tree) (list branch))) + + (robin-add-rule name branch (substring input 1) output)))) + +;;; Interactive use + +(defvar robin-mode nil + "If non-nil, `robin-input-method' is active.") +(make-variable-buffer-local 'robin-mode) + +(defvar robin-current-package-name nil + "String representing the name of the current robin package. +Nil means no packages is selected.") +(make-variable-buffer-local 'robin-current-package-name) + +;;;###autoload +(defun robin-use-package (name) + "Start using robin package NAME, which is a string." + + (let ((package (assoc name robin-package-alist))) + (unless package + (error "No such robin package")) + (setq robin-current-package-name name) + (robin-activate))) + +(defun robin-inactivate () + "Inactivate robin input method." + + (interactive) + (robin-activate -1)) + +(defun robin-activate (&optional arg) + "Activate robin input method. + +With ARG, activate robin input method iff ARG is positive. + +While this input method is active, the variable +`input-method-function' is bound to the function `robin-input-method'." + (if (and arg + (< (prefix-numeric-value arg) 0)) + + ;; inactivate robin input method. + (unwind-protect + (progn + (setq robin-mode nil) + (setq describe-current-input-method-function nil) + (run-hooks 'robin-inactivate-hook)) + (kill-local-variable 'input-method-function)) + + ;; activate robin input method. + (setq robin-mode t + describe-current-input-method-function 'robin-help + inactivate-current-input-method-function 'robin-inactivate) + (if (eq (selected-window) (minibuffer-window)) + (add-hook 'minibuffer-exit-hook 'robin-exit-from-minibuffer)) + (run-hooks 'input-method-activate-hook + 'robin-activate-hook) + (set (make-local-variable 'input-method-function) + 'robin-input-method))) + +(defun robin-exit-from-minibuffer () + (inactivate-input-method) + (if (<= (minibuffer-depth) 1) + (remove-hook 'minibuffer-exit-hook 'robin-exit-from-minibuffer))) + +(defun robin-input-method (key) + "Interpret typed key sequence and insert into buffer." + + (if (or buffer-read-only + overriding-terminal-local-map + overriding-local-map) + (list key) + + (let ((echo-keystrokes 0) + (input-method-function nil) + (start (point)) + (tree (cddr (assoc robin-current-package-name robin-package-alist))) + branch + output) + + (while (setq branch (assq key tree)) + (delete-region start (point)) + (insert (setq output (cadr branch))) + (setq tree (cddr branch)) + (if tree + (setq key (read-event)) + (setq key nil))) + + (if (null output) + ;; body of the `while' above was not executed + (list key) + (delete-region start (point)) + (if key + (setq unread-command-events (list key))) + (if (stringp output) + (string-to-list output) + (list output)))))) + +(defun robin-help () + "Display the docstring of the current robin package." + + (interactive) + (let ((buf (get-buffer-create "*Robin Help*")) + (doc (cadr (assoc robin-current-package-name robin-package-alist)))) + (set-buffer buf) + (erase-buffer) + (insert doc) + (goto-char (point-min)) + (display-buffer buf))) + +;;; Batch mode + +(defun robin-convert-buffer (&optional name) + "Convert the content of current buffer using a robin package. +NAME, if given, is a string specifying a robin package. If NAME +is not given or nil, the value of `robin-current-package-name' is +used." + + (interactive "*") + (robin-convert-region (point-min) (point-max) name)) + +(defun robin-convert-region (begin end &optional name) + "Convert the region using a robin package. +NAME, if given, is a string specifying a robin package. If NAME +is not given or nil, the value of `robin-current-package-name' is +used." + + (interactive "*r") + (or name + (setq name robin-current-package-name) + (error "No robin package specified")) + + (let ((tree (assoc name robin-package-alist))) + (unless tree + (error "No such robin package")) + + (save-excursion + (save-restriction + (narrow-to-region begin end) + (goto-char (point-min)) + (while (not (eobp)) + (robin-convert-region-internal tree)))))) + +(defun robin-convert-region-internal (tree) + "Apply a robin rule defined in TREE to the current point. +Use the longest match method to select a rule." + + (let ((begin (point)) + end branch) + (while (setq branch (assq (following-char) tree)) + (setq tree branch) + (forward-char 1)) + + (setq end (point)) + (if (= begin end) + ;; no matching rule found; leave it as it is + (forward-char 1) + ;; replace the string + (goto-char begin) + (insert (cadr tree)) + (delete-char (- end begin))))) + +;; for backward compatibility + +(fset 'robin-transliterate-region 'robin-convert-region) +(fset 'robin-transliterate-buffer 'robin-convert-buffer) + +;;; Reverse conversion + +(defun robin-invert-buffer (&optional name) + "Apply reverse conversion to the content of current buffer. +NAME, if given, is a string specifying a robin package. If NAME +is not given or nil, the value of `robin-current-package-name' is +used." + + (interactive "*") + (robin-invert-region (point-min) (point-max) name)) + +(defun robin-invert-region (begin end &optional name) + "Apply reverse conversion to the region. +NAME, if given, is a string specifying a robin package. If NAME +is not given or nil, the value of `robin-current-package-name' is +used." + + (interactive "*r") + (or name + (setq name robin-current-package-name) + (error "No robin package specified")) + + (setq name (intern name)) + (let (str) + (save-restriction + (narrow-to-region begin end) + (goto-char (point-min)) + (while (not (eobp)) + (if (not (setq str (get-char-code-property (following-char) name))) + (forward-char 1) + (insert str) + (delete-char 1)))))) + +(provide 'robin) + +;;; Local Variables: +;;; coding: utf-8-emacs +;;; End: + +;;; robin.el ends here