Merge from emacs--rel--22
[bpt/emacs.git] / lisp / international / robin.el
CommitLineData
40e89154
KH
1;;; robin.el --- yet another input method (smaller than quail)
2
33244355 3;; Copyright (C) 2003, 2004, 2005, 2006, 2007, 2008
40e89154 4;; National Institute of Advanced Industrial Science and Technology (AIST)
1be3d7c3 5;; Registration Number: H15PRO110
40e89154
KH
6
7;; Author: TAKAHASHI Naoto <ntakahas@m17n.org>
8;; Keywords: mule, multilingual, input method
9
33244355
GM
10;; This file is part of GNU Emacs.
11
12;; GNU Emacs is free software; you can redistribute it and/or
40e89154 13;; modify it under the terms of the GNU General Public License as
33244355 14;; published by the Free Software Foundation; either version 3, or (at
40e89154
KH
15;; your option) any later version.
16
17;; This program is distributed in the hope that it will be useful, but
18;; WITHOUT ANY WARRANTY; without even the implied warranty of
19;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
20;; General Public License for more details.
21
22;; You should have received a copy of the GNU General Public License
33244355
GM
23;; along with GNU Emacs; see the file COPYING. If not, write to the
24;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
25;; Boston, MA 02110-1301, USA.
40e89154
KH
26
27;;; Comentary:
28
29;; Functionalities
30;; ---------------
31
32;; Robin is a new input method for GNU Emacs. It has three
33;; functionalities:
34
35;; 1. It serves as a simple input method. When the user types an ASCII
36;; key sequence, robin converts it into a string. This functionality
37;; is most likely used to input non-ASCII characters.
38
39;; 2. It converts existing buffer substring into another string.
40;; This functionality is similar to the 1. above, but the input is
41;; buffer substring rather than key strokes.
42
43;; 3. It offers reverse conversion. Each character produced by a
44;; robin rule can hold the original ASCII sequence as a
45;; char-code-property.
46
47
48;; How to define conversion rules
49;; ------------------------------
50
51;; Each conversion rule belongs to a robin package. A robin package is
52;; identified by a string called package name. Use robin-define-package
53;; to define a robin package.
54
55;; (robin-define-package NAME DOCTSTRING
56;; (INPUT1 OUPUT1)
57;; (INPUT2 OUPUT2)
58;; ...)
59
60;; NAME is a string identifying the robin package. It often starts with a
61;; language name and followed by a method name. For example,
62;; french-postfix, greek-prefix, etc.
63
64;; DOCSTRING is a documentation string for the robin method.
65
66;; Each INPUTn is a string. It represents a transliteration of the
67;; corresponding OUTPUTn.
68
69;; Each OUTPUTn is a string or a character that is to be inserted as the
70;; result of conversion.
71
72;; Neither INPUT* nor OUTPUT* are evaluated. Do not use a variable or a
73;; function in those parts. Instead, use a string or character literal
74;; directly.
75
76;; If multiple rules have the same input pattern but different output
77;; patterns, only the latest definition is effective.
78
79
80;; Example
81;; -------
82
83;; (robin-define-package "german-example"
84;; "An example for German
85
86;; AE -> Ä OE -> Ö UE -> Ü
87;; ae -> ä oe -> ö ue -> ü ss -> ß
88
89;; Repeat E or S to input itself.
90
91;; AEE -> AE OEE -> OE UEE -> UE
92;; aee -> ae oee -> oe uee -> ue sss -> ss"
93
94;; ("AE" ?Ä)
95;; ("OE" ?Ö)
96;; ("UE" ?Ü)
97;; ("ae" ?ä)
98;; ("oe" ?ö)
99;; ("ue" ?ü)
100;; ("ss" ?ß)
101
102;; ("AEE" "AE")
103;; ("OEE" "OE")
104;; ("UEE" "UE")
105;; ("aee" "ae")
106;; ("oee" "oe")
107;; ("uee" "ue")
108;; ("sss" "ss")
109;; )
110
111
112;; Using robin as an input method
113;; ------------------------------
114
115;; To use a defined robin package as an input method, register it with
116;; the register-input-method function. For example,
117
118;; (register-input-method
119;; "german-example"
120;; "german"
121;; 'robin-use-package
122;; "de"
123;; "An example for German")
124
125;; The first argument is the robin package name.
126
127;; The second argument is the language environment for which this robin
128;; package is used.
129
130;; Use the symbol `robin-use-package' as the third argument.
131
132;; The fourth argument is the prompt that appears in modeline when this
133;; input method is active.
134
135;; The fifth argument is a documentation string; it may or may not be
136;; identical to the one that you specified in robin-define-package.
137
138;; You can activate the robin input method by typing
139
140;; C-u C-\ german-example RET
141
142;; Just like a quail package, only C-\ suffices for subsequent
143;; invocation.
144
145
146;; Using robin as a buffer translator
147;; ----------------------------------
148
149;; To transliterate buffer substring, use the following functions.
150
151;; (robin-convert-buffer &optional name)
152
153;; Convert the content of current buffer using a robin package.
154
155;; NAME, if given, is a string specifying a robin package. If NAME is
156;; not given or nil, the value of `robin-current-package-name' is used.
157
158;; (robin-convert-region begin end &optional name)
159
160;; Convert the region using a robin package.
161
162;; NAME, if given, is a string specifying a robin package. If NAME is
163;; not given or nil, the value of `robin-current-package-name' is used.
164
165
166;; Reverse conversion
167;; ------------------
168
169;; If the output pattern defined in a robin rule is a character, robin
170;; gives to the character a char-code-property whose key is the symbol
171;; representation of the robin package name and whose value is the input
172;; pattern of that character. For example, with the "german-example"
173;; definition above,
174
175;; (get-char-code-property ?Ä 'german-example) => "AE"
176
177;; etc.
178
179;; If you do not want to assign a char-code-property to a character, use
180;; a string of length one as the output pattern, e.g.
181
182;; (robin-define-package "german-example2"
183;; "Another example for German."
184
185;; ("AE" "Ä")
186;; ("OE" "Ö")
187;; ...)
188
189;; Then
190
191;; (get-char-code-property ?Ä 'german-example2) => nil
192
193;; etc.
194
195;; If multiple input patterns in a robin package generate the same
196;; character, the lastly used input pattern is given as the value of the
197;; char-code-property.
198
199;; There are two functions for reverse conversion.
200
201;; (robin-invert-buffer &optional name)
202
203;; Apply reverse conversion to the content of current buffer. NAME, if
204;; given, is a string specifying a robin package. If NAME is not given
205;; or nil, the value of `robin-current-package-name' is used.
206
207;; (robin-invert-region begin end &optional name)
208
209;; Apply reverse conversion to the region. NAME, if given, is a string
210;; specifying a robin package. If NAME is not given or nil, the value of
211;; `robin-current-package-name' is used.
212
213
214;; Modifying an existing rule
215;; --------------------------
216
217;; Use the robin-modify-package function to modify a rule already defined
218;; in a Robin package.
219
220;; (robin-modify-package name input output)
221
222;; Change a rule in an already defined Robin package.
223;; NAME is the string specifying a robin package.
224;; INPUT is a string that specifies the input pattern.
225;; OUTPUT is either a character or a string to be generated.
226
227
228;; The name of the game
229;; --------------------
230
231;; As stated in Murphy's law, it took longer than expected to develop the
232;; very first version of Japanese input subsystem in NEmacs (Nihongo
233;; Emacs). So the subsystem was named "TAMAGO", which is an acronym of
234;; "TAkusan Matasete GOmennasai" (Sorry to have kept you waiting so
235;; long). "Tamago" as a Japanese word means "egg", so the word "egg" was
236;; also used for related filenames and function names.
237
238;; Since it was designed to input CJK characters, Egg was rather big as a
239;; subsystem. So later in Mule (Multilingual Enhancement to GNU Emacs),
240;; we designed and implemented a smaller input subsystem. We had to give
241;; it a name. "So, what's smaller than an egg?" "A quail egg, of
242;; course." Therefore it was named "quail".
243
244;; As time went by, quail became more and more complicated. That
245;; tendency was inevitable as long as we support CJK input. However, if
246;; we can limit ourselves to non-CJK characters, a much simpler
247;; transliteration mechanism suffices. So I wrote "robin", whose name
248;; was chosen because a robin is smaller than a quail. I could name it
249;; "hummingbird" or "nightingale", but those spellings seemed too long.
250
251
252;;; Code:
253
254(defvar robin-package-alist nil
255 "List of robin packages.
256A robin pacakge is of the form (NAME DOCSTRING &rest RULES).
257NAME is a string specifying a particular robin package.
258DOCSTRING is a documentation string for the robin package.
259
260RULE is of the form (KEY OUTPUT &rest rules).
261KEY is a string.
262OUTPUT is a character or a string.
263For example, if you evaluate the following,
264
265(robin-define-package \"test\" \"Uppercase input characters\"
266 (\"a\" \"A\")
267 (\"ab\" \"AB\")
268 (\"ac\" \"AC\")
269 (\"acd\" \"ACD\")
270 (\"ace\" \"ACE\")
271 (\"b\" \"B\"))
272
273this robin package will be the following.
274
275 (\"test\" \"Uppercase input characters\"
276 (?a \"A\"
277 (?b \"AB\")
278 (?c \"AC\"
279 (?d \"ACD\")
280 (?e \"ACE\")))
281 (?b \"B\"))
282")
283
284;;;###autoload
285(defmacro robin-define-package (name docstring &rest rules)
286 "Define a robin package.
287
288NAME is the string of this robin package.
289DOCSTRING is the documentation string of this robin package.
290Each RULE is of the form (INPUT OUTPUT) where INPUT is a string and
291OUTPUT is either a character or a string. RULES are not evaluated.
292
293If there already exists a robin package whose name is NAME, the new
294one replaces the old one."
295
3ffdf71e 296 (let ((iname (intern name))
40e89154 297 (new (list name "")) ; "" as a fake output
38d035de 298 input output pairs)
40e89154
KH
299 (dolist (r rules)
300 (setq input (car r)
301 output (cadr r))
302 (robin-add-rule name new input output)
303 (cond
304 ((not (stringp input))
305 (error "Bad input sequence %S" r))
38d035de
KH
306 ((characterp output)
307 (setq pairs
308 (cons (cons input output)
309 pairs)))
40e89154
KH
310 ((not (stringp output))
311 (error "Bad output pattern %S" r))))
312 (setcar (cdr new) docstring) ; replace "" above with real docstring
06e38a9b 313 `(let ((slot (assoc ,name robin-package-alist))
38d035de
KH
314 (newdef ',new)
315 (prop ',iname)
316 (lst ',pairs))
06e38a9b
KH
317 (if slot
318 (setcdr slot (cdr newdef))
319 (setq robin-package-alist
38d035de
KH
320 (cons newdef robin-package-alist)))
321 (dolist (l lst)
322 (put-char-code-property (cdr l) prop (car l))))))
40e89154
KH
323
324;;;###autoload
325(defun robin-modify-package (name input output)
326 "Change a rule in an already defined robin package.
327
328NAME is the string specifying a robin package.
329INPUT is a string that specifies the input pattern.
330OUTPUT is either a character or a string to be generated."
331
332 (let ((tree (assoc name robin-package-alist))
333 docstring)
334 (if (not tree)
335 (error "No such robin package")
336 (setq docstring (cadr tree))
337 (setcar (cdr tree) "")
338 (robin-add-rule name tree input output)
339 (setcar (cdr tree) docstring)
340 (if (characterp output)
341 (put-char-code-property output (intern name) input))))
342 output)
343
344(defun robin-add-rule (name tree input output)
345 "Add translation rule (INPUT OUTPUT) to TREE whose name is NAME.
346Internal use only."
347
348 (let* ((head (aref input 0))
349 (branch (assoc head tree))
350 (sofar (cadr tree)))
351
352 (if (= (length input) 1)
353 (if branch
354
355 ;; A definition already exists for this input.
3ffdf71e
KH
356 ;; We do not cancel old char-code-property of OUTPUT
357 ;; so that n-to-1 reverse conversion is possible.
358 (setcar (cdr branch) output)
40e89154
KH
359
360 ;; New definition for this input.
361 (setcdr (last tree) (list (list head output))))
362
363 (unless branch
364 (if (characterp sofar)
365 (setq sofar (char-to-string sofar)))
366 (setq branch
367 (list head
368 (concat sofar
369 (char-to-string head))))
370 (setcdr (last tree) (list branch)))
371
372 (robin-add-rule name branch (substring input 1) output))))
373
374;;; Interactive use
375
376(defvar robin-mode nil
377 "If non-nil, `robin-input-method' is active.")
378(make-variable-buffer-local 'robin-mode)
379
380(defvar robin-current-package-name nil
381 "String representing the name of the current robin package.
382Nil means no packages is selected.")
383(make-variable-buffer-local 'robin-current-package-name)
384
385;;;###autoload
386(defun robin-use-package (name)
387 "Start using robin package NAME, which is a string."
388
389 (let ((package (assoc name robin-package-alist)))
390 (unless package
391 (error "No such robin package"))
392 (setq robin-current-package-name name)
393 (robin-activate)))
394
395(defun robin-inactivate ()
396 "Inactivate robin input method."
397
398 (interactive)
399 (robin-activate -1))
400
401(defun robin-activate (&optional arg)
402 "Activate robin input method.
403
404With ARG, activate robin input method iff ARG is positive.
405
406While this input method is active, the variable
407`input-method-function' is bound to the function `robin-input-method'."
408 (if (and arg
409 (< (prefix-numeric-value arg) 0))
410
411 ;; inactivate robin input method.
412 (unwind-protect
413 (progn
414 (setq robin-mode nil)
415 (setq describe-current-input-method-function nil)
416 (run-hooks 'robin-inactivate-hook))
417 (kill-local-variable 'input-method-function))
418
419 ;; activate robin input method.
420 (setq robin-mode t
421 describe-current-input-method-function 'robin-help
422 inactivate-current-input-method-function 'robin-inactivate)
423 (if (eq (selected-window) (minibuffer-window))
424 (add-hook 'minibuffer-exit-hook 'robin-exit-from-minibuffer))
425 (run-hooks 'input-method-activate-hook
426 'robin-activate-hook)
427 (set (make-local-variable 'input-method-function)
428 'robin-input-method)))
429
430(defun robin-exit-from-minibuffer ()
431 (inactivate-input-method)
432 (if (<= (minibuffer-depth) 1)
433 (remove-hook 'minibuffer-exit-hook 'robin-exit-from-minibuffer)))
434
435(defun robin-input-method (key)
436 "Interpret typed key sequence and insert into buffer."
437
438 (if (or buffer-read-only
439 overriding-terminal-local-map
440 overriding-local-map)
441 (list key)
442
443 (let ((echo-keystrokes 0)
444 (input-method-function nil)
445 (start (point))
446 (tree (cddr (assoc robin-current-package-name robin-package-alist)))
447 branch
448 output)
449
450 (while (setq branch (assq key tree))
451 (delete-region start (point))
452 (insert (setq output (cadr branch)))
453 (setq tree (cddr branch))
454 (if tree
455 (setq key (read-event))
456 (setq key nil)))
457
458 (if (null output)
459 ;; body of the `while' above was not executed
460 (list key)
461 (delete-region start (point))
462 (if key
463 (setq unread-command-events (list key)))
464 (if (stringp output)
465 (string-to-list output)
466 (list output))))))
467
468(defun robin-help ()
469 "Display the docstring of the current robin package."
470
471 (interactive)
472 (let ((buf (get-buffer-create "*Robin Help*"))
473 (doc (cadr (assoc robin-current-package-name robin-package-alist))))
474 (set-buffer buf)
475 (erase-buffer)
476 (insert doc)
477 (goto-char (point-min))
478 (display-buffer buf)))
479
480;;; Batch mode
481
482(defun robin-convert-buffer (&optional name)
483 "Convert the content of current buffer using a robin package.
484NAME, if given, is a string specifying a robin package. If NAME
485is not given or nil, the value of `robin-current-package-name' is
486used."
487
488 (interactive "*")
489 (robin-convert-region (point-min) (point-max) name))
490
491(defun robin-convert-region (begin end &optional name)
492 "Convert the region using a robin package.
493NAME, if given, is a string specifying a robin package. If NAME
494is not given or nil, the value of `robin-current-package-name' is
495used."
496
497 (interactive "*r")
498 (or name
499 (setq name robin-current-package-name)
500 (error "No robin package specified"))
501
502 (let ((tree (assoc name robin-package-alist)))
503 (unless tree
504 (error "No such robin package"))
505
506 (save-excursion
507 (save-restriction
508 (narrow-to-region begin end)
509 (goto-char (point-min))
510 (while (not (eobp))
511 (robin-convert-region-internal tree))))))
512
513(defun robin-convert-region-internal (tree)
514 "Apply a robin rule defined in TREE to the current point.
515Use the longest match method to select a rule."
516
517 (let ((begin (point))
518 end branch)
519 (while (setq branch (assq (following-char) tree))
520 (setq tree branch)
521 (forward-char 1))
522
523 (setq end (point))
524 (if (= begin end)
525 ;; no matching rule found; leave it as it is
526 (forward-char 1)
527 ;; replace the string
528 (goto-char begin)
529 (insert (cadr tree))
530 (delete-char (- end begin)))))
531
532;; for backward compatibility
533
534(fset 'robin-transliterate-region 'robin-convert-region)
535(fset 'robin-transliterate-buffer 'robin-convert-buffer)
536
537;;; Reverse conversion
538
539(defun robin-invert-buffer (&optional name)
540 "Apply reverse conversion to the content of current buffer.
541NAME, if given, is a string specifying a robin package. If NAME
542is not given or nil, the value of `robin-current-package-name' is
543used."
544
545 (interactive "*")
546 (robin-invert-region (point-min) (point-max) name))
547
548(defun robin-invert-region (begin end &optional name)
549 "Apply reverse conversion to the region.
550NAME, if given, is a string specifying a robin package. If NAME
551is not given or nil, the value of `robin-current-package-name' is
552used."
553
554 (interactive "*r")
555 (or name
556 (setq name robin-current-package-name)
557 (error "No robin package specified"))
558
559 (setq name (intern name))
560 (let (str)
561 (save-restriction
562 (narrow-to-region begin end)
563 (goto-char (point-min))
564 (while (not (eobp))
565 (if (not (setq str (get-char-code-property (following-char) name)))
566 (forward-char 1)
567 (insert str)
568 (delete-char 1))))))
569
570(provide 'robin)
571
572;;; Local Variables:
573;;; coding: utf-8-emacs
574;;; End:
575
f603a66a 576;; arch-tag: ba995140-7436-4a57-b875-747fc340f605
40e89154 577;;; robin.el ends here