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