Remove non-free old and crusty clearcase pkg
[packages] / mule-packages / edict / edict-edit.el
1 ;;; edict-edit.el --- Edit an EDICT dictionary.
2
3 ;; Copyright (C) 1991, 1992 Per Hammarlund (perham@nada.kth.se)
4 ;; Copyright (C) 1998, 2002 Free Software Foundation, Inc.
5
6 ;; Author:      Per Hammarlund <perham@nada.kth.se>
7 ;; Keywords:    mule, edict, dictionary
8 ;; Version:     0.9.9
9 ;; Adapted-by:  Stephen J. Turnbull <stephen@xemacs.org> for XEmacs
10 ;; Maintainer:  Stephen J. Turnbull <stephen@xemacs.org>
11
12 ;;   This file is part of XEmacs.
13
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.
18
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.
23 ;; 
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.
27
28 ;;; Commentary:
29
30 ;; Some code that looks for translations of english and japanese using the
31 ;; EDICTJ Public Domain japanese/english dictionary.
32
33 ;; Written by Per Hammarlund <perham@nada.kth.se>
34 ;; Morphology and private dictionary handling/editing by Bob Kerns
35 ;; <rwk@crl.dec.com>
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>
39
40 ;;; To do:
41
42 ;;; Changelog:
43
44 ;;; Code:
45
46 (require 'cl)
47
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)
52                        `(list ,@args))))
53
54 ;;; Customizable variables
55
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.
59
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)
62                  (const :tag "no" nil)
63                  (const :tag "ask and set flag for this session" ask))
64   :group 'edict)
65
66 (defcustom edict-verbose-electric-henkan t
67   "*If non-nil, warns the user of electric changes in henkan state."
68   :type 'boolean
69   :group 'edict)
70
71 (defcustom *brackets-allowed-in-english* nil
72   "*Allow brackets in the english section of dictionary entries, if non-null."
73   :type 'boolean
74   :group 'edict)
75
76 ;;; Internal variables
77
78 ;; The previous configuration before adding an entry to a private dictionary.
79 (defvar edict-previous-window-configuration nil)
80
81 ;; The previously-selected buffer before adding an entry.
82 (defvar edict-previous-buffer nil)
83
84 ;; The filename of the file read in to add an entry to.
85 (defvar edict-filename nil)
86
87 (defvar edict-edit-mode-map nil
88   "Mode map used by edict-add-english/kanji.")
89
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))
96                   edict-edit-mode-map)
97     (dotimes (i 128)
98       ;; #### I hope this is OK without the check below
99       (define-key edict-edit-mode-map [ i ] 'edict-standin)))
100 ; Emacs 18?
101 ;      ;; I don't know how to invoke multi-char commands, so don't hook
102 ;      ;; those.
103 ;      (unless (consp (aref edict-edit-mode-map i))
104 ;       (setf (aref edict-edit-mode-map i) 'edict-standin))))
105   (if (featurep 'xemacs)
106       (progn
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))
113 ; Emacs 18?
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))
126
127 ;;; Functions
128
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.
132
133 ;; #### This isn't interactive, but it's not an unreasonable entry point?
134 (defun edict-add-entry-to-file (filename kanji yomi eigo)
135   (edict-init)
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)))
140     (set-buffer 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"
144                       (user-login-name))))
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)
148       (edict-edit-mode))
149     ;; Unless we already have a configuration to go back to, remember
150     ;; this one.
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
157     ;; when we finish.
158     (setq edict-filename filename)
159     (if window
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)))))
167
168
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).
174 ;;
175 (defun edict-set-henkan (henkan-flag)
176   "Electrically turn on or off the current default Japanese text input method.
177
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."
180
181   (if (eq 'ask edict-use-electric-henkan)
182       (if (and (featurep 'xim)
183                (y-or-n-p
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
198                 (if default
199                     "Japanese input method (default %s): "
200                   "Japanese input method: " )
201                 default t)  
202              default))
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"))))))
208
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.
212   (let ((p (point)))
213     (beginning-of-line)
214     (unless (equal p (point))
215       (end-of-line)
216       (newline)))
217   ;; Now insert a standard entry.
218   (let ((start (point))
219         (p nil))
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.
222     ;; "\e$BF|K\8l\e(B"
223     (if kanji
224         (insert kanji)
225       (setq p (point)))
226     (when yomi
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]"
230     (cond ((and kanji
231                 (string-match edict-yomi-regexp kanji)))
232           (t (insert " [")
233              (if yomi
234                  (insert yomi)
235                (if (not p)
236                    (setq p (point))))
237              (insert "]")))
238     ;; "\e$BF|K\8l\e(B [\e$B$K$[$s$4\e(B] /Japanese language/"
239     (cond ((null eigo)
240            (insert " /")
241            (unless p (setq p (point))))
242           ((stringp eigo)
243            (insert " /" eigo))
244           ((consp eigo)
245            (insert " ")
246            (dolist (def eigo)
247              (insert "/")
248              (insert def)))
249           (t (error "not a string or list of strings: %s" eigo)))
250     (insert "/\n")
251     ;; Go to the first un-filled-in field.
252     (goto-char (or p start))))
253
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.
258
259 (defun edict-parse-entry ()
260   (let ((kanji nil)
261         (yomi nil)
262         (english nil)
263         (start nil)
264         (p nil)
265         (end nil))
266     (save-excursion
267       (end-of-line)
268       (setq end (point))
269       (beginning-of-line)
270       (setq start (point))
271       (search-forward " " end)
272       (setq p (1- (point)))
273       (when (> p start)
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)))
284
285 ;;;###autoload
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.
294 "
295   (interactive)
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))
309
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 ()
315   (save-excursion
316     (let ((x (point))
317           (end nil))
318       (end-of-line)
319       (setq end (point))
320       (beginning-of-line)
321       (edict-set-henkan
322        (or (looking-at "$")
323            (when (re-search-forward "[]/]" end t)
324              (<= x (match-beginning 0))))))))
325
326 (defun edict-standin ()
327   "Invoke the command we would otherwise have invoked, after being sure
328 we're in the right mode."
329   (interactive)
330   ;; #### This is evil, I think.
331   (setq this-command (aref global-map last-command-char))
332   (edict-execute-dictionary-command
333    (function (lambda ()
334                (command-execute this-command)))))
335
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))
341       (save-excursion
342         (backward-char 1)
343         (when (looking-at "//\n")
344           (forward-char 1)
345           (delete-char 1))))
346     (funcall function)
347     ;; Canonicalize the end of the line to end in exactly one slash.
348     (save-excursion
349       (end-of-line)
350       (delete-horizontal-space)
351       (unless (<= (point) (point-min))
352         (backward-char 2)
353         (while (looking-at "//")
354           ;; Two in a row; delete the second.
355           (forward-char 1)
356           (delete-char 1)
357           (backward-char 2))
358         (forward-char 1)
359         (unless (looking-at "\n")
360           (unless (looking-at "[/\n]")
361             (end-of-line)
362             (unless (edict-line-has-english)
363               (insert " /"))
364             (insert ?/)))))
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
369                 (end-of-line)
370                 (backward-char 1)
371                 (looking-at "\n"))
372         (when (looking-at "\n")
373           (insert "/")
374           (backward-char 1))
375         (save-excursion
376           (end-of-line)
377           ;; Make sure there's a trailing newline.
378           (when (>= (point) (point-max))
379             (newline)
380             (backward-char 1))
381           (let ((end (point)))
382             (beginning-of-line)
383             (when (search-forward "/" end t)
384               (when (looking-at "\n")
385                 (insert "/")))))))
386     ;; Only set the henkan if we're still in the same buffer.
387     (when (eq buffer (current-buffer))
388       (edict-auto-set-henkan))))
389
390 (defun edict-line-has-english (&optional complete)
391   (save-excursion
392     (let ((p (point)))
393       (end-of-line)
394       (let ((end (point)))
395         (goto-char p)
396         (beginning-of-line)
397         (if complete
398             (re-search-forward "/[^/\n]+/" end t)
399           (re-search-forward "/" end t))))))
400
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."
404   (interactive)
405   (edict-execute-dictionary-command (function (lambda ()
406                                                 (edict-char-bracket t)))))
407
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.."
411   (interactive)
412   (edict-execute-dictionary-command (function (lambda ()
413                                                 (if (looking-at "\\]")
414                                                     (edict-tab)
415                                                   (edict-char-bracket nil))))))
416
417 (defun edict-char-bracket (open-p)
418   (let ((p (point)))
419     (end-of-line)
420     (let ((end (point)))
421       (beginning-of-line)
422       (cond ((and *brackets-allowed-in-english*
423                   (save-excursion
424                     (re-search-forward "/[^\n/]*/" end t))
425                   (<= (match-beginning 0) p))
426              (goto-char p)
427              (edict-standin))
428             ((re-search-forward edict-yomi-part-regexp end t)
429              (goto-char (or (if open-p
430                                 (match-beginning 1)
431                               (match-end 1))
432                             ;; Empty
433                             (1+ (match-beginning 0)))))
434             ((re-search-forward "[ \t]" end t)
435              (goto-char (match-beginning 0))
436              (insert " []")
437              (backward-char 1))
438             (t (goto-char p)
439                (edict-standin))))))
440
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."
444   (interactive)
445   (edict-execute-dictionary-command (function edict-slash-internal)))
446
447 (defun edict-slash-internal ()
448   (if (looking-at "/\n")
449       (forward-char)
450     (let ((p (point)))
451       (end-of-line)
452       (let ((end (point)))
453         (beginning-of-line)
454         (cond ((and (save-excursion
455                       (re-search-forward "/[^/\n]*/" end t))
456                     (<= (match-beginning 0) p))
457                (goto-char p)
458                (edict-standin))
459               ((search-forward "/" end t))
460               ;; On an empty line, just insert a definition.
461               ((looking-at "$")
462                (insert " //")
463                (backward-char 1))
464               ;; Otherwise, this line has no english, go to the end and add one.
465               (t (end-of-line)
466                  (backward-char 1)
467                  (unless (looking-at " ")
468                    (insert " "))
469                  (insert "//")
470                  (backward-char 1)))))))
471
472 (defun edict-tab ()
473   "Tab to the next edict field in this entry.
474 At the end, wraps back to the beginning.."
475   (interactive)
476   (edict-execute-dictionary-command (function edict-tab-internal)))
477
478 (defun edict-tab-internal ()
479   (let ((p (point))
480         (end nil))
481     (end-of-line)
482     (setq end (point))
483     (goto-char p)
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))))
487              (goto-char f-begin)
488              (edict-set-henkan (looking-at "\\["))
489              (goto-char f-end)))
490           (t (beginning-of-line)
491              (edict-set-henkan t)))))
492
493 (defun edict-beginning-of-line ()
494   "Go to the beginning of the edict entry."
495   (interactive)
496   (edict-execute-dictionary-command (function (lambda ()
497                                                 (beginning-of-line)
498                                                 (edict-set-henkan t)))))
499
500 (defun edict-end-of-line ()
501   "Go to the beginning of the edict entry."
502   (interactive)
503   (edict-execute-dictionary-command (function (lambda ()
504                                                 (end-of-line)
505                                                 (edict-set-henkan nil)))))
506
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,
511 but not the yomi."
512   (interactive "P")
513   (edict-execute-dictionary-command (function (lambda ()
514                                                 (edict-new-entry-internal arg)))))
515
516 (defun edict-new-entry-internal (arg)
517   (end-of-line)
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))
522     (backward-char)
523     (unless (looking-at "/")
524       (end-of-line)
525       (insert "/"))
526     (multiple-value-bind (kanji yomi english)
527         (edict-parse-entry)
528       (end-of-line)
529       (if (>= (point) (point-max))
530           (newline)
531         (forward-char 1))
532       (cond ((null arg)
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))))))
537
538 (defun edict-exit ()
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."
541   (interactive)
542   (save-buffer)
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)
555           (progn
556             (forward-line 1)
557             (let ((loc (point)))
558               (search-forward end-marker)
559               (forward-line 0)
560               (delete-region loc (point))
561               (goto-char loc)))
562         ;; Handle new file
563         (insert (format "%s\n%s\n" begin-marker end-marker))
564         (forward-line -1))
565       (insert-buffer buffer)
566       (when configuration
567         (set-window-configuration configuration))
568       (when previous-buffer
569         (switch-to-buffer previous-buffer)))))
570
571 ;;;###autoload
572 (defun edict-add-word ()
573   "Add any word to the private dictionary."
574   (interactive)
575   (edict-add-entry-to-file edict-user-dictionary nil nil nil))
576
577 ;;;###autoload
578 (defun edict-add-english ()
579   "Add the english word at point to the dictionary."
580   (interactive)
581   (let ((word (edict-get-english-word)))
582     (when word
583       (edict-add-entry-to-file edict-user-dictionary nil nil word))))
584
585 ;;;###autoload
586 (defun edict-add-kanji (min max)
587   "Add the region as a kanji entry in the dictionary."
588   (interactive "r")
589   (edict-add-entry-to-file edict-user-dictionary
590                            (edict-clean-up-kanji (buffer-substring min max))
591                            nil nil))
592
593 (provide 'edict-edit)
594
595 ;;; edict-edit.el ends here