semi 0.72.
[elisp/mu-cite.git] / mu-cite.el
1 ;;; mu-cite.el --- yet another citation tool for GNU Emacs
2
3 ;; Copyright (C) 1995,1996,1997 Free Software Foundation, Inc.
4
5 ;; Author: MORIOKA Tomohiko <morioka@jaist.ac.jp>
6 ;;         MINOURA Makoto <minoura@netlaputa.or.jp>
7 ;;         Shuhei KOBAYASHI <shuhei-k@jaist.ac.jp>
8 ;; Maintainer: Shuhei KOBAYASHI <shuhei-k@jaist.ac.jp>
9 ;; Version: $Revision: 7.48 $
10 ;; Keywords: mail, news, citation
11
12 ;; This file is part of MU (Message Utilities).
13
14 ;; This program is free software; you can redistribute it and/or
15 ;; modify it under the terms of the GNU General Public License as
16 ;; published by the Free Software Foundation; either version 2, or (at
17 ;; your option) any later version.
18
19 ;; This program 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 GNU Emacs; see the file COPYING.  If not, write to the
26 ;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
27 ;; Boston, MA 02111-1307, USA.
28
29 ;;; Commentary:
30
31 ;; - How to use
32 ;;   1. bytecompile this file and copy it to the apropriate directory.
33 ;;   2. put the following lines to your ~/.emacs:
34 ;;      for EMACS 19 or later and XEmacs
35 ;;              (autoload 'mu-cite/cite-original "mu-cite" nil t)
36 ;;              ;; for all but message-mode
37 ;;              (add-hook 'mail-citation-hook 'mu-cite/cite-original)
38 ;;              ;; for message-mode only
39 ;;              (setq message-cite-function (function mu-cite/cite-original))
40 ;;      for EMACS 18
41 ;;              ;; for all but mh-e
42 ;;              (add-hook 'mail-yank-hooks (function mu-cite/cite-original))
43 ;;              ;; for mh-e only
44 ;;              (add-hook 'mh-yank-hooks (function mu-cite/cite-original))
45
46 ;;; Code:
47
48 (require 'std11)
49 (require 'tl-str)
50 (require 'tl-list)
51
52
53 ;;; @ version
54 ;;;
55
56 (defconst mu-cite/RCS-ID
57   "$Id: mu-cite.el,v 7.48 1997/03/10 06:13:17 shuhei-k Exp $")
58 (defconst mu-cite/version (get-version-string mu-cite/RCS-ID))
59
60
61 ;;; @ formats
62 ;;;
63
64 (defvar mu-cite/cited-prefix-regexp "\\(^[^ \t\n<>]+>+[ \t]*\\|^[ \t]*$\\)"
65   "*Regexp to match the citation prefix.
66 If match, mu-cite doesn't insert citation prefix.")
67
68 (defvar mu-cite/prefix-format '(prefix-register-verbose "> ")
69   "*List to represent citation prefix.
70 Each elements must be string or method name.")
71
72 (defvar mu-cite/top-format '(in-id
73                              ">>>>>     " from " wrote:\n")
74   "*List to represent top string of citation.
75 Each elements must be string or method name.")
76
77
78 ;;; @ hooks
79 ;;;
80
81 (defvar mu-cite-load-hook nil
82   "*List of functions called after mu-cite is loaded.
83 Use this hook to add your own methods to `mu-cite/default-methods-alist'.")
84
85 (defvar mu-cite/instantiation-hook nil
86   "*List of functions called just before narrowing to the message.")
87
88 (defvar mu-cite/pre-cite-hook nil
89   "*List of functions called before citing a region of text.")
90
91 (defvar mu-cite/post-cite-hook nil
92   "*List of functions called after citing a region of text.")
93
94
95 ;;; @ field
96 ;;;
97
98 (defvar mu-cite/get-field-value-method-alist
99   (list (cons 'mh-letter-mode
100               (function
101                (lambda (name)
102                  (if (and (stringp mh-sent-from-folder)
103                           (numberp mh-sent-from-msg))
104                      (save-excursion
105                        (set-buffer mh-sent-from-folder)
106                        (set-buffer mh-show-buffer)
107                        (and (boundp 'mime::preview/article-buffer)
108                             (bufferp mime::preview/article-buffer)
109                             (set-buffer mime::preview/article-buffer))
110                        (std11-field-body name)
111                        ))
112                  )))))
113
114 (defun mu-cite/get-field-value (name)
115   (or (std11-field-body name)
116       (let ((method (assq major-mode mu-cite/get-field-value-method-alist)))
117         (if method
118             (funcall (cdr method) name)
119           ))))
120
121
122 ;;; @ prefix registration
123 ;;;
124
125 (defvar mu-cite/registration-file (expand-file-name "~/.mu-cite.el")
126   "*The name of the user environment file for mu-cite.")
127
128 (defvar mu-cite/allow-null-string-registration nil
129   "*If non-nil, null-string citation-name is registered.")
130
131 (defvar mu-cite/registration-symbol 'mu-cite/citation-name-alist)
132
133 (defvar mu-cite/citation-name-alist nil)
134 (or (eq 'mu-cite/citation-name-alist mu-cite/registration-symbol)
135     (setq mu-cite/citation-name-alist
136           (symbol-value mu-cite/registration-symbol))
137     )
138 (defvar mu-cite/minibuffer-history nil)
139
140 ;; get citation-name from the database
141 (defun mu-cite/get-citation-name (from)
142   (assoc-value from mu-cite/citation-name-alist)
143   )
144
145 ;; register citation-name to the database
146 (defun mu-cite/add-citation-name (name from)
147   (setq mu-cite/citation-name-alist
148         (put-alist from name mu-cite/citation-name-alist))
149   (mu-cite/save-registration-file)
150   )
151
152 ;; load/save registration file
153 (defun mu-cite/load-registration-file ()
154   (let* ((file mu-cite/registration-file)
155          (buffer (get-buffer-create " *mu-register*")))
156     (if (file-readable-p file)
157         (unwind-protect
158             (save-excursion
159               (set-buffer buffer)
160               (erase-buffer)
161               (insert-file-contents file)
162               ;; (eval-buffer)
163               (eval-current-buffer))
164           (kill-buffer buffer))
165       )))
166 (add-hook 'mu-cite-load-hook (function mu-cite/load-registration-file))
167
168 (defun mu-cite/save-registration-file ()
169   (let* ((file mu-cite/registration-file)
170          (buffer (get-buffer-create " *mu-register*")))
171     (unwind-protect
172         (save-excursion
173           (set-buffer buffer)
174           (setq buffer-file-name file)
175           (erase-buffer)
176           (insert ";;; " (file-name-nondirectory file) "\n")
177           (insert ";;; This file is generated automatically by mu-cite "
178                   mu-cite/version "\n\n")
179           (insert "(setq "
180                   (symbol-name mu-cite/registration-symbol)
181                   "\n      '(")
182           (insert (mapconcat
183                    (function prin1-to-string)
184                    mu-cite/citation-name-alist "\n        "))
185           (insert "\n        ))\n\n")
186           (insert ";;; "
187                   (file-name-nondirectory file)
188                   " ends here.\n")
189           (save-buffer))
190       (kill-buffer buffer))))
191
192
193 ;;; @ item methods
194 ;;;
195
196 ;;; @@ ML count
197 ;;;
198
199 (defvar mu-cite/ml-count-field-list
200   '("X-Ml-Count" "X-Mail-Count" "X-Seqno" "X-Sequence" "Mailinglist-Id")
201   "*List of header fields which contain sequence number of mailing list.")
202
203 (defun mu-cite/get-ml-count-method ()
204   (let ((field-list mu-cite/ml-count-field-list))
205     (catch 'tag
206       (while field-list
207         (let* ((field (car field-list))
208                (ml-count (mu-cite/get-field-value field)))
209           (if (and ml-count (string-match "[0-9]+" ml-count))
210               (throw 'tag
211                      (substring ml-count
212                                 (match-beginning 0)(match-end 0))
213                      ))
214           (setq field-list (cdr field-list))
215           )))))
216
217
218 ;;; @@ prefix and registration
219 ;;;
220
221 (defun mu-cite/get-prefix-method ()
222   (or (mu-cite/get-citation-name (mu-cite/get-value 'address))
223       ">")
224   )
225
226 (defun mu-cite/get-prefix-register-method ()
227   (let ((addr (mu-cite/get-value 'address)))
228     (or (mu-cite/get-citation-name addr)
229         (let ((return
230                (read-string "Citation name? "
231                             (or (mu-cite/get-value 'x-attribution)
232                                 (mu-cite/get-value 'full-name))
233                             'mu-cite/minibuffer-history)
234                ))
235           (if (and (or mu-cite/allow-null-string-registration
236                        (not (string-equal return "")))
237                    (y-or-n-p (format "Register \"%s\"? " return)))
238               (mu-cite/add-citation-name return addr)
239             )
240           return))))
241
242 (defun mu-cite/get-prefix-register-verbose-method ()
243   (let* ((addr (mu-cite/get-value 'address))
244          (return1 (mu-cite/get-citation-name addr))
245          (return (read-string "Citation name? "
246                               (or return1
247                                   (mu-cite/get-value 'x-attribution)
248                                   (mu-cite/get-value 'full-name))
249                               'mu-cite/minibuffer-history))
250          )
251     (if (and (or mu-cite/allow-null-string-registration
252                  (not (string-equal return "")))
253              (not (string-equal return return1))
254              (y-or-n-p (format "Register \"%s\"? " return))
255              )
256         (mu-cite/add-citation-name return addr)
257       )
258     return))
259
260
261 ;;; @@ set up
262 ;;;
263
264 (defvar mu-cite/default-methods-alist
265   (list (cons 'from
266               (function
267                (lambda ()
268                  (mu-cite/get-field-value "From")
269                  )))
270         (cons 'date
271               (function
272                (lambda ()
273                  (mu-cite/get-field-value "Date")
274                  )))
275         (cons 'message-id
276               (function
277                (lambda ()
278                  (mu-cite/get-field-value "Message-Id")
279                  )))
280         (cons 'subject
281               (function
282                (lambda ()
283                  (mu-cite/get-field-value "Subject")
284                  )))
285         (cons 'ml-name
286               (function
287                (lambda ()
288                  (mu-cite/get-field-value "X-Ml-Name")
289                  )))
290         (cons 'ml-count (function mu-cite/get-ml-count-method))
291         (cons 'address-structure
292               (function
293                (lambda ()
294                  (car
295                   (std11-parse-address-string (mu-cite/get-value 'from))
296                   ))))
297         (cons 'full-name
298               (function
299                (lambda ()
300                  (std11-full-name-string
301                   (mu-cite/get-value 'address-structure))
302                  )))
303         (cons 'address
304               (function
305                (lambda ()
306                  (std11-address-string
307                   (mu-cite/get-value 'address-structure))
308                  )))
309         (cons 'id
310               (function
311                (lambda ()
312                  (let ((ml-name (mu-cite/get-value 'ml-name)))
313                    (if ml-name
314                        (concat "["
315                                ml-name
316                                " : No."
317                                (mu-cite/get-value 'ml-count)
318                                "]")
319                      (mu-cite/get-value 'message-id)
320                      )))))
321         (cons 'in-id
322               (function
323                (lambda ()
324                  (let ((id (mu-cite/get-value 'id)))
325                    (if id
326                        (format ">>>>> In %s \n" id)
327                      "")))))
328         (cons 'prefix (function mu-cite/get-prefix-method))
329         (cons 'prefix-register
330               (function mu-cite/get-prefix-register-method))
331         (cons 'prefix-register-verbose
332               (function mu-cite/get-prefix-register-verbose-method))
333         (cons 'x-attribution
334               (function
335                (lambda ()
336                  (mu-cite/get-field-value "X-Attribution")
337                  )))
338         ))
339
340
341 ;;; @ fundamentals
342 ;;;
343
344 (defvar mu-cite/methods-alist nil)
345
346 (defun mu-cite/make-methods ()
347   (setq mu-cite/methods-alist
348         (copy-alist mu-cite/default-methods-alist))
349   (run-hooks 'mu-cite/instantiation-hook)
350   )
351
352 (defun mu-cite/get-value (item)
353   (let ((ret (assoc-value item mu-cite/methods-alist)))
354     (if (functionp ret)
355         (prog1
356             (setq ret (funcall ret))
357           (set-alist 'mu-cite/methods-alist item ret)
358           )
359       ret)))
360
361 (defun mu-cite/eval-format (list)
362   (mapconcat (function
363               (lambda (elt)
364                 (cond ((stringp elt) elt)
365                       ((symbolp elt) (mu-cite/get-value elt))
366                       )))
367              list "")
368   )
369
370
371 ;;; @ main function
372 ;;;
373
374 (defun mu-cite/cite-original ()
375   "Citing filter function.
376 This is callable from the various mail and news readers' reply
377 function according to the agreed upon standard."
378   (interactive)
379   (mu-cite/make-methods)
380   (save-restriction
381     (if (< (mark t) (point))
382         (exchange-point-and-mark))
383     (narrow-to-region (point)(point-max))
384     (run-hooks 'mu-cite/pre-cite-hook)
385     (let ((last-point (point))
386           (top (mu-cite/eval-format mu-cite/top-format))
387           (prefix (mu-cite/eval-format mu-cite/prefix-format))
388           )
389       (if (re-search-forward "^-*$" nil nil)
390           (forward-line 1)
391         )
392       (widen)
393       (delete-region last-point (point))
394       (insert top)
395       (setq last-point (point))
396       (while (< (point)(mark t))
397         (or (looking-at mu-cite/cited-prefix-regexp)
398             (insert prefix))
399         (forward-line 1))
400       (goto-char last-point)
401       )
402     (run-hooks 'mu-cite/post-cite-hook)
403     ))
404
405
406 ;;; @ message editing utilities
407 ;;;
408
409 (defvar citation-mark-chars ">}|"
410   "*String of characters for citation delimiter. [mu-cite.el]")
411
412 (defvar citation-disable-chars "<{"
413   "*String of characters not allowed as citation-prefix.")
414
415 (defun detect-paragraph-cited-prefix ()
416   (save-excursion
417     (goto-char (point-min))
418     (let ((i 0)
419           (prefix
420            (buffer-substring
421             (progn (beginning-of-line)(point))
422             (progn (end-of-line)(point))
423             ))
424           str ret)
425       (while (and (= (forward-line) 0)
426                   (setq str (buffer-substring
427                              (progn (beginning-of-line)(point))
428                              (progn (end-of-line)(point))))
429                   (setq ret (string-compare-from-top prefix str))
430                   )
431         (setq prefix
432               (if (stringp ret)
433                   ret
434                 (second ret)))
435         (setq i (1+ i))
436         )
437       (cond ((> i 1) prefix)
438             ((> i 0)
439              (goto-char (point-min))
440              (save-restriction
441                (narrow-to-region (point)
442                                  (+ (point)(length prefix)))
443                (goto-char (point-max))
444                (if (re-search-backward
445                     (concat "[" citation-mark-chars "]") nil t)
446                    (progn
447                      (goto-char (match-end 0))
448                      (if (looking-at "[ \t]+")
449                          (goto-char (match-end 0))
450                        )
451                      (buffer-substring (point-min)(point))
452                      )
453                  prefix)))
454             ((progn
455                (goto-char (point-max))
456                (re-search-backward
457                 (concat "[" citation-disable-chars "]") nil t)
458                (re-search-backward
459                 (concat "[" citation-mark-chars "]") nil t)
460                )
461              (goto-char (match-end 0))
462              (if (looking-at "[ \t]+")
463                  (goto-char (match-end 0))
464                )
465              (buffer-substring (point-min)(point))
466              )
467             (t ""))
468       )))
469
470 (defun fill-cited-region (beg end)
471   (interactive "*r")
472   (save-excursion
473     (save-restriction
474       (goto-char end)
475       (and (search-backward "\n" nil t)
476            (setq end (match-end 0))
477            )
478       (narrow-to-region beg end)
479       (let* ((fill-prefix (detect-paragraph-cited-prefix))
480              (pat (concat fill-prefix "\n"))
481              )
482         (goto-char (point-min))
483         (while (search-forward pat nil t)
484           (let ((b (match-beginning 0))
485                 (e (match-end 0))
486                 )
487             (delete-region b e)
488             (if (and (> b (point-min))
489                      (let ((cat (char-category
490                                  (char-before b))))
491                        (or (string-match "a" cat)
492                            (string-match "l" cat)
493                            ))
494                      )
495                 (insert " ")
496               ))
497           )
498         (goto-char (point-min))
499         (fill-region (point-min) (point-max))
500         ))))
501
502 (defun compress-cited-prefix ()
503   (interactive)
504   (save-excursion
505     (goto-char (point-min))
506     (re-search-forward
507      (concat "^" (regexp-quote mail-header-separator) "$") nil t)
508     (while (re-search-forward
509             (concat "^\\([ \t]*[^ \t\n" citation-mark-chars "]*["
510                     citation-mark-chars "]\\)+") nil t)
511       (let* ((b (match-beginning 0))
512              (e (match-end 0))
513              (prefix (buffer-substring b e))
514              ps pe (s 0)
515              (nest (let ((i 0))
516                      (if (string-match "<[^<>]+>" prefix)
517                          (setq prefix (substring prefix 0 (match-beginning 0)))
518                        )
519                      (while (string-match
520                              (concat "\\([" citation-mark-chars "]+\\)[ \t]*")
521                              prefix s)
522                        (setq i (+ i (- (match-end 1)(match-beginning 1)))
523                              ps s
524                              pe (match-beginning 1)
525                              s (match-end 0)
526                              ))
527                      i)))
528         (if (and ps (< ps pe))
529             (progn
530               (delete-region b e)
531               (insert (concat (substring prefix ps pe) (make-string nest ?>)))
532               ))))))
533
534 (defun replace-top-string (old new)
535   (interactive "*sOld string: \nsNew string: ")
536   (while (re-search-forward
537           (concat "^" (regexp-quote old)) nil t)
538     (replace-match new)
539     ))
540
541
542 ;;; @ end
543 ;;;
544
545 (provide 'mu-cite)
546
547 (run-hooks 'mu-cite-load-hook)
548
549 ;;; mu-cite.el ends here