robin.el 17 KB

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