1 ;;; edict-edit.el --- Edit an EDICT dictionary.
3 ;; Copyright (C) 1991, 1992 Per Hammarlund (perham@nada.kth.se)
4 ;; Copyright (C) 1998, 2002 Free Software Foundation, Inc.
6 ;; Author: Per Hammarlund <perham@nada.kth.se>
7 ;; Keywords: mule, edict, dictionary
9 ;; Adapted-by: Stephen J. Turnbull <stephen@xemacs.org> for XEmacs
10 ;; Maintainer: Stephen J. Turnbull <stephen@xemacs.org>
12 ;; This file is part of XEmacs.
14 ;; XEmacs is free software; you can redistribute it and/or modify it
15 ;; under the terms of the GNU General Public License as published by
16 ;; the Free Software Foundation; either version 2, or (at your
17 ;; option) any later version.
19 ;; XEmacs is distributed in the hope that it will be useful, but
20 ;; WITHOUT ANY WARRANTY; without even the implied warranty of
21 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
22 ;; General Public License for more details.
24 ;; You should have received a copy of the GNU General Public License
25 ;; along with XEmacs; if not, write to the Free Software Foundation,
26 ;; Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
30 ;; Some code that looks for translations of english and japanese using the
31 ;; EDICTJ Public Domain japanese/english dictionary.
33 ;; Written by Per Hammarlund <perham@nada.kth.se>
34 ;; Morphology and private dictionary handling/editing by Bob Kerns
36 ;; Helpful remarks from Ken-Ichi Handa <handa@etl.go.jp>.
37 ;; The EDICTJ PD dictionary is maintained by Jim Breen
38 ;; <jwb@monu6.cc.monash.edu.au>
48 ;; Have compiled 21.4 code also work on XEmacs binaries with real support
49 ;; for multiple values, by avoiding runtime calls to #'values:
50 (eval-when-compile (when (eq 'list (symbol-function 'values))
51 (define-compiler-macro values (&rest args)
54 ;;; Customizable variables
56 ;; #### does this tristate make sense with Customize support?
57 (defcustom edict-use-electric-henkan nil
58 "*Determines whether to use electric henkan mode in edict buffers.
60 If t, use it; if nil, don't use it. If 'ask, ask and (re)set the flag."
61 :type '(choice (const :tag "yes" t)
63 (const :tag "ask and set flag for this session" ask))
66 (defcustom edict-verbose-electric-henkan t
67 "*If non-nil, warns the user of electric changes in henkan state."
71 (defcustom *brackets-allowed-in-english* nil
72 "*Allow brackets in the english section of dictionary entries, if non-null."
76 ;;; Internal variables
78 ;; The previous configuration before adding an entry to a private dictionary.
79 (defvar edict-previous-window-configuration nil)
81 ;; The previously-selected buffer before adding an entry.
82 (defvar edict-previous-buffer nil)
84 ;; The filename of the file read in to add an entry to.
85 (defvar edict-filename nil)
87 (defvar edict-edit-mode-map nil
88 "Mode map used by edict-add-english/kanji.")
90 ;; Initialize our mode map.
91 (unless edict-edit-mode-map
92 (setq edict-edit-mode-map (make-keymap))
93 (if (featurep 'xemacs)
94 (map-keymap (lambda (key)
95 (define-key edict-edit-mode-map key 'edict-standin))
98 ;; #### I hope this is OK without the check below
99 (define-key edict-edit-mode-map [ i ] 'edict-standin)))
101 ; ;; I don't know how to invoke multi-char commands, so don't hook
103 ; (unless (consp (aref edict-edit-mode-map i))
104 ; (setf (aref edict-edit-mode-map i) 'edict-standin))))
105 (if (featurep 'xemacs)
107 (define-key edict-edit-mode-map [(control c)] nil)
108 (define-key edict-edit-mode-map [(control x)] nil)
109 (define-key edict-edit-mode-map [(escape)] nil))
110 (define-key edict-edit-mode-map [ 3 ] nil)
111 (define-key edict-edit-mode-map [ 24 ] nil)
112 (define-key edict-edit-mode-map [ 27 ] nil))
114 ; (setf (aref edict-edit-mode-map 3) nil
115 ; (aref edict-edit-mode-map 24) nil
116 ; (aref edict-edit-mode-map 27) nil))
117 (define-key edict-edit-mode-map "\C-c\C-c" 'edict-exit)
118 (define-key edict-edit-mode-map "\C-x\C-s" 'edict-exit)
119 (define-key edict-edit-mode-map "\t" 'edict-tab)
120 (define-key edict-edit-mode-map "\r" 'edict-new-entry)
121 (define-key edict-edit-mode-map "\C-A" 'edict-beginning-of-line)
122 (define-key edict-edit-mode-map "\C-E" 'edict-end-of-line)
123 (define-key edict-edit-mode-map "[" 'edict-open-bracket)
124 (define-key edict-edit-mode-map "]" 'edict-close-bracket)
125 (define-key edict-edit-mode-map "/" 'edict-slash))
129 ;; Add an entry to a particular file, and update edict-buffer.
130 ;; Any of kanji/yomi/eigo may be omitted. The user will be given
131 ;; an oportunity to edit and then it will be saved.
133 ;; #### This isn't interactive, but it's not an unreasonable entry point?
134 (defun edict-add-entry-to-file (filename kanji yomi eigo)
136 (setq filename (expand-file-name filename))
137 (let* ((previous-buffer (current-buffer))
138 (buffer (find-file-noselect filename))
139 (window (get-buffer-window buffer)))
141 ;; If it's a new file, give it a version string to print on loadup.
142 (when (equal (point-min) (point-max))
143 (insert (format "
\e$B!)!)!)!)
\e(B /%s's private dictionary/\n"
145 ;; Unless it's already in edict-edit mode, put it in that mode.
146 ;; This gives us our fancy electric-dictionary editing.
147 (unless (eq major-mode 'edict-edit-mode)
149 ;; Unless we already have a configuration to go back to, remember
151 (unless edict-previous-window-configuration
152 (setq edict-previous-window-configuration
153 (current-window-configuration)))
154 (unless edict-previous-buffer
155 (setq edict-previous-buffer previous-buffer))
156 ;; Remember the filename, so we can update it in the *edict* buffer
158 (setq edict-filename filename)
160 (select-window window)
161 (split-window nil 4))
162 (goto-char (point-max))
163 (edict-insert-entry kanji yomi eigo)
164 ;; Go into henkan mode if appropriate
165 (switch-to-buffer buffer)
166 (edict-set-henkan (or (null kanji) (null yomi)))))
169 ;; Turn on or off henkan
170 ;; Should work in any Mule environment, in particular, not require LEIM.
171 ;; #### Probably fails pretty impolitely if no Japanese input methods are
172 ;; registered with Mule.
173 ;; The guts were copied from mule-commands.el (toggle-input-method).
175 (defun edict-set-henkan (henkan-flag)
176 "Electrically turn on or off the current default Japanese text input method.
178 If HENKAN-FLAG is nil, turn it off, otherwise turn it on.
179 With arg, read an input method from minibuffer and turn it on."
181 (if (eq 'ask edict-use-electric-henkan)
182 (if (and (featurep 'xim)
184 "XIM and electric-henkan don't mix. Disable electric-henkan"))
185 (setq edict-use-electric-henkan nil)
186 (setq edict-use-electric-henkan t))
187 (setq edict-use-electric-henkan t))
188 (if edict-use-electric-henkan
189 (let* ((default (or (car input-method-history) default-input-method)))
190 (if (and current-input-method (not henkan-flag))
191 (inactivate-input-method)
192 ;; #### Need to ensure that the IM is Japanese. Could do
193 ;; by looking up in registry, and requiring confirmation
194 ;; if some heuristic isn't satisfied.
195 (activate-input-method
196 (if (or henkan-flag (not default))
197 (read-input-method-name
199 "Japanese input method (default %s): "
200 "Japanese input method: " )
203 (or default-input-method
204 (setq default-input-method current-input-method)))
205 (and edict-verbose-electric-henkan
206 (message "Henkan is electrically %s."
207 (if henkan-flag "on" "off"))))))
209 ;; Insert a dictionary entry at point.
210 (defun edict-insert-entry (kanji yomi eigo)
211 ;; Make sure this is on a line of its own.
214 (unless (equal p (point))
217 ;; Now insert a standard entry.
218 (let ((start (point))
220 ;; Insert a new entry, leaving out any items which are nil,
221 ;; and also leaving out the yomi if the entry consists of only kana.
227 (unless (string-match edict-yomi-regexp yomi)
228 (error "yomi must be in kana: %s." yomi)))
229 ;; "
\e$BF|K\8l
\e(B [
\e$B$K$[$s$4
\e(B]"
231 (string-match edict-yomi-regexp kanji)))
238 ;; "
\e$BF|K\8l
\e(B [
\e$B$K$[$s$4
\e(B] /Japanese language/"
241 (unless p (setq p (point))))
249 (t (error "not a string or list of strings: %s" eigo)))
251 ;; Go to the first un-filled-in field.
252 (goto-char (or p start))))
254 ;; Inverse of edict-insert-entry. Parse an entry.
255 ;; (multiple-value-bind (kanji yomi english) (edict-parse-entry)
256 ;; (edict-insert-entry kanji yomi english))
257 ;; duplicates the current line's entry.
259 (defun edict-parse-entry ()
271 (search-forward " " end)
272 (setq p (1- (point)))
274 (setq kanji (buffer-substring start p)))
275 ;; Pick up the [yomi] if there are any.
276 (when (re-search-forward edict-yomi-part-regexp end t)
277 (setq yomi (buffer-substring (match-beginning 1) (match-end 1)))
278 (goto-char (match-end 0)))
279 ;; Collect up all the definitions.
280 (while (re-search-forward "/\\([^/\n]+\\)/" end t)
281 (goto-char (match-end 1))
282 (push (buffer-substring (match-beginning 1) (match-end 1)) english)))
283 (values kanji yomi english)))
286 (defun edict-edit-mode ()
287 "Major mode for editing edict entries.
288 TAB Tab to next field in this entry.
289 RETURN Start a new entry on the next line.
290 c-A Edit the kanji field, and start entering kanji.
291 c-E Go to the end, and start editing english.
292 C-c C-c Install the edited changes & save the file.
293 C-x C-s Install the edited changes & save the file.
296 (kill-all-local-variables)
297 ;; Associate these with the buffer.
298 (make-local-variable 'edict-previous-window-configuration)
299 (make-local-variable 'edict-previous-buffer)
300 (make-local-variable 'edict-filename)
301 (set-syntax-table text-mode-syntax-table)
302 (use-local-map edict-edit-mode-map)
303 (setq local-abbrev-table text-mode-abbrev-table)
304 (setq major-mode 'edict-edit-mode)
305 (setq mode-name "Edict")
306 (setq paragraph-start "^\\|$")
307 (setq paragraph-separate "^\\|$")
308 (run-hooks 'text-mode-hook))
310 ;; Automagically pick the right mode, based on where we are in the string.
311 ;; That's henkan mode when we're in the entry or yomi sections, and english
312 ;; in the translation section.
313 ;; #### Can this be better done with extents or overlays?
314 (defun edict-auto-set-henkan ()
323 (when (re-search-forward "[]/]" end t)
324 (<= x (match-beginning 0))))))))
326 (defun edict-standin ()
327 "Invoke the command we would otherwise have invoked, after being sure
328 we're in the right mode."
330 ;; #### This is evil, I think.
331 (setq this-command (aref global-map last-command-char))
332 (edict-execute-dictionary-command
334 (command-execute this-command)))))
336 (defun edict-execute-dictionary-command (function)
337 (edict-auto-set-henkan)
338 (let ((buffer (current-buffer)))
339 ;; Canonicalize the end to end in exactly one slash.
340 (unless (<= (point) (point-min))
343 (when (looking-at "//\n")
347 ;; Canonicalize the end of the line to end in exactly one slash.
350 (delete-horizontal-space)
351 (unless (<= (point) (point-min))
353 (while (looking-at "//")
354 ;; Two in a row; delete the second.
359 (unless (looking-at "\n")
360 (unless (looking-at "[/\n]")
362 (unless (edict-line-has-english)
365 ;; Then if we are at the end, make it end in two, for the sake of visual feedback.
366 ;; Except if we're on a blank line, don't add anything.
367 (unless (<= (point) (point-min))
368 (unless (save-excursion
372 (when (looking-at "\n")
377 ;; Make sure there's a trailing newline.
378 (when (>= (point) (point-max))
383 (when (search-forward "/" end t)
384 (when (looking-at "\n")
386 ;; Only set the henkan if we're still in the same buffer.
387 (when (eq buffer (current-buffer))
388 (edict-auto-set-henkan))))
390 (defun edict-line-has-english (&optional complete)
398 (re-search-forward "/[^/\n]+/" end t)
399 (re-search-forward "/" end t))))))
401 (defun edict-open-bracket ()
402 "Begin editing the yomi section of the entry, at the beginning of the entry.
403 Self-inserts if in the english section."
405 (edict-execute-dictionary-command (function (lambda ()
406 (edict-char-bracket t)))))
408 (defun edict-close-bracket ()
409 "Begin editing the yomi section of the entry, at the end of the entry.
410 Self-inserts if in the english section.."
412 (edict-execute-dictionary-command (function (lambda ()
413 (if (looking-at "\\]")
415 (edict-char-bracket nil))))))
417 (defun edict-char-bracket (open-p)
422 (cond ((and *brackets-allowed-in-english*
424 (re-search-forward "/[^\n/]*/" end t))
425 (<= (match-beginning 0) p))
428 ((re-search-forward edict-yomi-part-regexp end t)
429 (goto-char (or (if open-p
433 (1+ (match-beginning 0)))))
434 ((re-search-forward "[ \t]" end t)
435 (goto-char (match-beginning 0))
441 (defun edict-slash ()
442 "Begin editing the english section of the entry, at the start of the entry.
443 Self-inserts if in the english section."
445 (edict-execute-dictionary-command (function edict-slash-internal)))
447 (defun edict-slash-internal ()
448 (if (looking-at "/\n")
454 (cond ((and (save-excursion
455 (re-search-forward "/[^/\n]*/" end t))
456 (<= (match-beginning 0) p))
459 ((search-forward "/" end t))
460 ;; On an empty line, just insert a definition.
464 ;; Otherwise, this line has no english, go to the end and add one.
467 (unless (looking-at " ")
470 (backward-char 1)))))))
473 "Tab to the next edict field in this entry.
474 At the end, wraps back to the beginning.."
476 (edict-execute-dictionary-command (function edict-tab-internal)))
478 (defun edict-tab-internal ()
484 (cond ((re-search-forward "[ \t]\\(\\[\\)\\|\\(/\\)" end t)
485 (let ((f-begin (or (match-beginning 1) (match-beginning 2)))
486 (f-end (or (match-end 1) (match-end 2))))
488 (edict-set-henkan (looking-at "\\["))
490 (t (beginning-of-line)
491 (edict-set-henkan t)))))
493 (defun edict-beginning-of-line ()
494 "Go to the beginning of the edict entry."
496 (edict-execute-dictionary-command (function (lambda ()
498 (edict-set-henkan t)))))
500 (defun edict-end-of-line ()
501 "Go to the beginning of the edict entry."
503 (edict-execute-dictionary-command (function (lambda ()
505 (edict-set-henkan nil)))))
507 (defun edict-new-entry (arg)
508 "Start a new edict entry on the next line.
509 If given an argument, copies the word but not the yomi or english.
510 If given an argument > 4 (i.e. c-U c-U), copies the word and definition,
513 (edict-execute-dictionary-command (function (lambda ()
514 (edict-new-entry-internal arg)))))
516 (defun edict-new-entry-internal (arg)
518 ;;clean up in the dictionary to save space.
519 (delete-horizontal-space)
520 ;;first check that the last thing on this line is a '/', otherwise add one.
521 (unless (<= (point) (point-min))
523 (unless (looking-at "/")
526 (multiple-value-bind (kanji yomi english)
529 (if (>= (point) (point-max))
533 (edict-insert-entry nil nil nil))
534 ((<= (prefix-numeric-value arg) 4)
535 (edict-insert-entry kanji nil nil))
536 (t (edict-insert-entry kanji nil english))))))
539 "Exit the editing of a private edict file, saving the buffer and updating the
540 running copy of the dictionary, and restoring the window configuration."
543 (let* ((buffer (current-buffer))
544 (edict-private-buffer (find-file-noselect (expand-file-name edict-user-dictionary)))
545 (filename (or edict-filename (buffer-file-name edict-private-buffer)))
546 (configuration edict-previous-window-configuration)
547 (previous-buffer edict-previous-buffer))
548 (setq edict-previous-window-configuration nil
549 edict-previous-buffer nil)
550 (set-buffer edict-buffer)
551 (goto-char (point-min))
552 (let ((begin-marker (format "%s %s" *edict-file-begin-marker* filename))
553 (end-marker (format "%s %s" *edict-file-end-marker* filename)))
554 (if (search-forward begin-marker nil t)
558 (search-forward end-marker)
560 (delete-region loc (point))
563 (insert (format "%s\n%s\n" begin-marker end-marker))
565 (insert-buffer buffer)
567 (set-window-configuration configuration))
568 (when previous-buffer
569 (switch-to-buffer previous-buffer)))))
572 (defun edict-add-word ()
573 "Add any word to the private dictionary."
575 (edict-add-entry-to-file edict-user-dictionary nil nil nil))
578 (defun edict-add-english ()
579 "Add the english word at point to the dictionary."
581 (let ((word (edict-get-english-word)))
583 (edict-add-entry-to-file edict-user-dictionary nil nil word))))
586 (defun edict-add-kanji (min max)
587 "Add the region as a kanji entry in the dictionary."
589 (edict-add-entry-to-file edict-user-dictionary
590 (edict-clean-up-kanji (buffer-substring min max))
593 (provide 'edict-edit)
595 ;;; edict-edit.el ends here