Synch with Oort Gnus v0.02.
[elisp/gnus.git-] / lisp / mm-util.el
1 ;;; mm-util.el --- Utility functions for Mule and low level things
2 ;; Copyright (C) 1998, 1999, 2000, 2001 Free Software Foundation, Inc.
3
4 ;; Author: Lars Magne Ingebrigtsen <larsi@gnus.org>
5 ;;      MORIOKA Tomohiko <morioka@jaist.ac.jp>
6 ;; This file is part of GNU Emacs.
7
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 2, or (at your option)
11 ;; any later version.
12
13 ;; GNU Emacs is distributed in the hope that it will be useful,
14 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
15 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16 ;; GNU General Public License for more details.
17
18 ;; You should have received a copy of the GNU General Public License
19 ;; along with GNU Emacs; see the file COPYING.  If not, write to the
20 ;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
21 ;; Boston, MA 02111-1307, USA.
22
23 ;;; Commentary:
24
25 ;;; Code:
26
27 (eval-when-compile (require 'cl))
28 (eval-when-compile (require 'gnus-clfns))
29 (eval-when-compile (require 'static))
30
31 (require 'mail-prsvr)
32
33 (defvar mm-mime-mule-charset-alist
34   `((us-ascii ascii)
35     (iso-8859-1 latin-iso8859-1)
36     (iso-8859-2 latin-iso8859-2)
37     (iso-8859-3 latin-iso8859-3)
38     (iso-8859-4 latin-iso8859-4)
39     (iso-8859-5 cyrillic-iso8859-5)
40     ;; Non-mule (X)Emacs uses the last mule-charset for 8bit characters.
41     ;; The fake mule-charset, gnus-koi8-r, tells Gnus that the default
42     ;; charset is koi8-r, not iso-8859-5.
43     (koi8-r cyrillic-iso8859-5 gnus-koi8-r)
44     (iso-8859-6 arabic-iso8859-6)
45     (iso-8859-7 greek-iso8859-7)
46     (iso-8859-8 hebrew-iso8859-8)
47     (iso-8859-9 latin-iso8859-9)
48     (iso-8859-14 latin-iso8859-14)
49     (iso-8859-15 latin-iso8859-15)
50     (viscii vietnamese-viscii-lower)
51     (iso-2022-jp latin-jisx0201 japanese-jisx0208 japanese-jisx0208-1978)
52     (euc-kr korean-ksc5601)
53     (gb2312 chinese-gb2312)
54     (big5 chinese-big5-1 chinese-big5-2)
55     (tibetan tibetan)
56     (thai-tis620 thai-tis620)
57     (iso-2022-7bit ethiopic arabic-1-column arabic-2-column)
58     (iso-2022-jp-2 latin-iso8859-1 greek-iso8859-7
59                    latin-jisx0201 japanese-jisx0208-1978
60                    chinese-gb2312 japanese-jisx0208
61                    korean-ksc5601 japanese-jisx0212
62                    katakana-jisx0201)
63     (iso-2022-int-1 latin-iso8859-1 greek-iso8859-7
64                     latin-jisx0201 japanese-jisx0208-1978
65                     chinese-gb2312 japanese-jisx0208
66                     korean-ksc5601 japanese-jisx0212
67                     chinese-cns11643-1 chinese-cns11643-2)
68     (iso-2022-int-1 latin-iso8859-1 latin-iso8859-2
69                     cyrillic-iso8859-5 greek-iso8859-7
70                     latin-jisx0201 japanese-jisx0208-1978
71                     chinese-gb2312 japanese-jisx0208
72                     korean-ksc5601 japanese-jisx0212
73                     chinese-cns11643-1 chinese-cns11643-2
74                     chinese-cns11643-3 chinese-cns11643-4
75                     chinese-cns11643-5 chinese-cns11643-6
76                     chinese-cns11643-7)
77     ,(if (or (not (fboundp 'charsetp)) ;; non-Mule case
78              (not (fboundp 'coding-system-p))
79              (charsetp 'unicode-a)
80              (not (coding-system-p 'mule-utf-8)))
81          '(utf-8 unicode-a unicode-b unicode-c unicode-d unicode-e)
82        ;; If we have utf-8 we're in Mule 5+.
83        (delete 'ascii (coding-system-get 'mule-utf-8 'safe-charsets))))
84   "Alist of MIME-charset/MULE-charsets.")
85
86 (eval-and-compile
87   (mapcar
88    (lambda (elem)
89      (let ((nfunc (intern (format "mm-%s" (car elem)))))
90        (if (fboundp (car elem))
91            (defalias nfunc (car elem))
92          (defalias nfunc (cdr elem)))))
93    '((decode-coding-string . (lambda (s a) s))
94      (encode-coding-string . (lambda (s a) s))
95      (encode-coding-region . ignore)
96      (coding-system-list . ignore)
97      (decode-coding-region . ignore)
98      (char-int . identity)
99      (device-type . ignore)
100      (coding-system-equal . equal)
101      (annotationp . ignore)
102      (set-buffer-file-coding-system . ignore)
103      (make-char
104       . (lambda (charset int)
105           (int-to-char int)))
106      (read-coding-system
107       . (lambda (prompt)
108           "Prompt the user for a coding system."
109           (completing-read
110            prompt (mapcar (lambda (s) (list (symbol-name (car s))))
111                           mm-mime-mule-charset-alist))))
112      (read-charset
113       . (lambda (prompt)
114           "Return a charset."
115           (intern
116            (completing-read
117             prompt
118             (mapcar (lambda (e) (list (symbol-name (car e))))
119                     mm-mime-mule-charset-alist)
120             nil t))))
121      (subst-char-in-string
122       . (lambda (from to string) ;; stolen (and renamed) from nnheader.el
123           "Replace characters in STRING from FROM to TO."
124           (let ((string (substring string 0))   ;Copy string.
125                 (len (length string))
126                 (idx 0))
127             ;; Replace all occurrences of FROM with TO.
128             (while (< idx len)
129               (when (= (aref string idx) from)
130                 (aset string idx to))
131               (setq idx (1+ idx)))
132             string)))
133      (string-as-unibyte . identity)
134      (multibyte-string-p . ignore))))
135
136 (eval-and-compile
137   (defalias 'mm-char-or-char-int-p
138     (cond
139      ((fboundp 'char-or-char-int-p) 'char-or-char-int-p)
140      ((fboundp 'char-valid-p) 'char-valid-p)
141      (t 'identity))))
142
143 (defvar mm-coding-system-list nil)
144 (defun mm-get-coding-system-list ()
145   "Get the coding system list."
146   (or mm-coding-system-list
147       (setq mm-coding-system-list (mm-coding-system-list))))
148
149 (defun mm-coding-system-p (sym)
150   "Return non-nil if SYM is a coding system."
151   (or (and (fboundp 'coding-system-p) (coding-system-p sym))
152       (memq sym (mm-get-coding-system-list))))
153
154 (defvar mm-charset-synonym-alist
155   `((big5 . cn-big5)
156     (gb2312 . cn-gb-2312)
157     (cn-gb . cn-gb-2312)
158     ;; Windows-1252 is actually a superset of Latin-1.  See also
159     ;; `gnus-article-dumbquotes-map'.
160     ,(unless (mm-coding-system-p 'windows-1252) ; should be defined eventually
161        '(windows-1252 . iso-8859-1))
162     (x-ctext . ctext))
163   "A mapping from invalid charset names to the real charset names.")
164
165 (defvar mm-binary-coding-system
166   (cond
167    ((mm-coding-system-p 'binary) 'binary)
168    ((mm-coding-system-p 'no-conversion) 'no-conversion)
169    (t nil))
170   "100% binary coding system.")
171
172 (defvar mm-text-coding-system
173   (or (if (memq system-type '(windows-nt ms-dos ms-windows))
174           (and (mm-coding-system-p 'raw-text-dos) 'raw-text-dos)
175         (and (mm-coding-system-p 'raw-text) 'raw-text))
176       mm-binary-coding-system)
177   "Text-safe coding system (For removing ^M).")
178
179 (defvar mm-text-coding-system-for-write nil
180   "Text coding system for write.")
181
182 (defvar mm-auto-save-coding-system
183   (cond
184    ((mm-coding-system-p 'emacs-mule)
185     (if (memq system-type '(windows-nt ms-dos ms-windows))
186         (if (mm-coding-system-p 'emacs-mule-dos)
187             'emacs-mule-dos mm-binary-coding-system)
188       'emacs-mule))
189    ((mm-coding-system-p 'escape-quoted) 'escape-quoted)
190    (t mm-binary-coding-system))
191   "Coding system of auto save file.")
192
193 ;;; Internal variables:
194
195 ;;; Functions:
196
197 (defun mm-mule-charset-to-mime-charset (charset)
198   "Return the MIME charset corresponding to the given Mule CHARSET."
199   (let ((alist mm-mime-mule-charset-alist)
200         out)
201     (while alist
202       (when (memq charset (cdar alist))
203         (setq out (caar alist)
204               alist nil))
205       (pop alist))
206     out))
207
208 (defun mm-charset-to-coding-system (charset &optional lbt)
209   "Return coding-system corresponding to CHARSET.
210 CHARSET is a symbol naming a MIME charset.
211 If optional argument LBT (`unix', `dos' or `mac') is specified, it is
212 used as the line break code type of the coding system."
213   (when (stringp charset)
214     (setq charset (intern (downcase charset))))
215   (setq charset
216         (or (cdr (assq charset mm-charset-synonym-alist))
217             charset))
218   (when lbt
219     (setq charset (intern (format "%s-%s" charset lbt))))
220   (cond
221    ;; Running in a non-MULE environment.
222    ((null (mm-get-coding-system-list))
223     charset)
224    ;; ascii
225    ((eq charset 'us-ascii)
226     'ascii)
227    ;; Check to see whether we can handle this charset.  (This depends
228    ;; on there being some coding system matching each `mime-charset'
229    ;; coding sysytem property defined, as there should be.)
230    ((memq charset (mm-get-coding-system-list))
231     charset)
232    ;; Nope.
233    (t
234     nil)))
235
236 (defsubst mm-replace-chars-in-string (string from to)
237   (mm-subst-char-in-string from to string))
238
239 (defsubst mm-enable-multibyte ()
240   "Set the multibyte flag of the current buffer.
241 Only do this if the default value of `enable-multibyte-characters' is
242 non-nil.  This is a no-op in XEmacs."
243   (when (and (not (featurep 'xemacs))
244              (boundp 'default-enable-multibyte-characters)
245              default-enable-multibyte-characters
246              (fboundp 'set-buffer-multibyte))
247     (set-buffer-multibyte t)))
248
249 (defsubst mm-disable-multibyte ()
250   "Unset the multibyte flag of in the current buffer.
251 This is a no-op in XEmacs."
252   (when (and (not (featurep 'xemacs))
253              (fboundp 'set-buffer-multibyte))
254     (set-buffer-multibyte nil)))
255
256 (defsubst mm-enable-multibyte-mule4 ()
257   "Enable multibyte in the current buffer.
258 Only used in Emacs Mule 4."
259   (when (and (not (featurep 'xemacs))
260              (boundp 'default-enable-multibyte-characters)
261              default-enable-multibyte-characters
262              (fboundp 'set-buffer-multibyte)
263              (fboundp 'charsetp)
264              (not (charsetp 'eight-bit-control)))
265     (set-buffer-multibyte t)))
266
267 (defsubst mm-disable-multibyte-mule4 ()
268   "Disable multibyte in the current buffer.
269 Only used in Emacs Mule 4."
270   (when (and (not (featurep 'xemacs))
271              (fboundp 'set-buffer-multibyte)
272              (fboundp 'charsetp)
273              (not (charsetp 'eight-bit-control)))
274     (set-buffer-multibyte nil)))
275
276 (defun mm-preferred-coding-system (charset)
277   ;; A typo in some Emacs versions.
278   (or (get-charset-property charset 'prefered-coding-system)
279       (get-charset-property charset 'preferred-coding-system)))
280
281 (defun mm-charset-after (&optional pos)
282   "Return charset of a character in current buffer at position POS.
283 If POS is nil, it defauls to the current point.
284 If POS is out of range, the value is nil.
285 If the charset is `composition', return the actual one."
286   (let ((char (char-after pos)) charset)
287     (if (< (mm-char-int char) 128)
288         (setq charset 'ascii)
289       ;; charset-after is fake in some Emacsen.
290       (setq charset (and (fboundp 'char-charset) (char-charset char)))
291       (if (eq charset 'composition)
292           (let ((p (or pos (point))))
293             (cadr (find-charset-region p (1+ p))))
294         (if (and charset (not (memq charset '(ascii eight-bit-control
295                                                     eight-bit-graphic))))
296             charset
297           (or
298            mail-parse-mule-charset ;; cached mule-charset
299            (progn
300              (setq mail-parse-mule-charset
301                    (and (boundp 'current-language-environment)
302                       (car (last
303                             (assq 'charset
304                                   (assoc current-language-environment
305                                          language-info-alist))))))
306              (if (or (not mail-parse-mule-charset)
307                      (eq mail-parse-mule-charset 'ascii))
308                  (setq mail-parse-mule-charset
309                        (or (car (last (assq mail-parse-charset
310                                             mm-mime-mule-charset-alist)))
311                            ;; Fixme: don't fix that!
312                            'latin-iso8859-1)))
313              mail-parse-mule-charset)))))))
314
315 (defun mm-mime-charset (charset)
316   "Return the MIME charset corresponding to the MULE CHARSET."
317   (if (eq charset 'unknown)
318       (error "The message contains non-printable characters, please use attachment."))
319   (if (and (fboundp 'coding-system-get) (fboundp 'get-charset-property))
320       ;; This exists in Emacs 20.
321       (or
322        (and (mm-preferred-coding-system charset)
323             (coding-system-get
324              (mm-preferred-coding-system charset) 'mime-charset))
325        (and (eq charset 'ascii)
326             'us-ascii)
327        (mm-preferred-coding-system charset)
328        (mm-mule-charset-to-mime-charset charset))
329     ;; This is for XEmacs.
330     (mm-mule-charset-to-mime-charset charset)))
331
332 (defun mm-delete-duplicates (list)
333   "Simple  substitute for CL `delete-duplicates', testing with `equal'."
334   (let (result head)
335     (while list
336       (setq head (car list))
337       (setq list (delete head list))
338       (setq result (cons head result)))
339     (nreverse result)))
340
341 (defun mm-find-mime-charset-region (b e)
342   "Return the MIME charsets needed to encode the region between B and E."
343   (let ((charsets (mapcar 'mm-mime-charset
344                           (delq 'ascii
345                                 (mm-find-charset-region b e)))))
346     (when (memq 'iso-2022-jp-2 charsets)
347       (setq charsets (delq 'iso-2022-jp charsets)))
348     (setq charsets (mm-delete-duplicates charsets))
349     (if (and (> (length charsets) 1)
350              (fboundp 'find-coding-systems-region)
351              (let ((cs (find-coding-systems-region b e)))
352                (or (memq 'utf-8 cs) (memq 'mule-utf-8 cs))))
353         '(utf-8)
354       charsets)))
355
356 (defsubst mm-multibyte-p ()
357   "Say whether multibyte is enabled."
358   (if (and (not (featurep 'xemacs))
359            (boundp 'enable-multibyte-characters))
360       enable-multibyte-characters
361     (featurep 'mule)))
362
363 (defmacro mm-with-unibyte-buffer (&rest forms)
364   "Create a temporary buffer, and evaluate FORMS there like `progn'.
365 Use unibyte mode for this."
366   `(let (default-enable-multibyte-characters)
367      (with-temp-buffer ,@forms)))
368 (put 'mm-with-unibyte-buffer 'lisp-indent-function 0)
369 (put 'mm-with-unibyte-buffer 'edebug-form-spec '(body))
370
371 (defmacro mm-with-unibyte-current-buffer (&rest forms)
372   "Evaluate FORMS with current current buffer temporarily made unibyte.
373 Also bind `default-enable-multibyte-characters' to nil.
374 Equivalent to `progn' in XEmacs"
375   (let ((buffer (make-symbol "buffer")))
376     `(if (and (not (featurep 'xemacs))
377               (boundp 'enable-multibyte-characters)
378               enable-multibyte-characters
379               (fboundp 'set-buffer-multibyte))
380          (let ((,buffer (current-buffer)))
381            (unwind-protect
382                (let (default-enable-multibyte-characters)
383                  (set-buffer-multibyte nil)
384                  ,@forms)
385              (set-buffer ,buffer)
386              (set-buffer-multibyte t)))
387        (let (default-enable-multibyte-characters)
388          ,@forms))))
389 (put 'mm-with-unibyte-current-buffer 'lisp-indent-function 0)
390 (put 'mm-with-unibyte-current-buffer 'edebug-form-spec '(body))
391
392 (defmacro mm-with-unibyte-current-buffer-mule4 (&rest forms)
393   "Evaluate FORMS there like `progn' in current buffer.
394 Mule4 only."
395   (let ((buffer (make-symbol "buffer")))
396     `(if (and (not (featurep 'xemacs))
397               (boundp 'enable-multibyte-characters)
398               enable-multibyte-characters
399               (fboundp 'set-buffer-multibyte)
400               (fboundp 'charsetp)
401               (not (charsetp 'eight-bit-control))) ;; For Emacs Mule 4 only.
402        (let ((,buffer (current-buffer)))
403          (unwind-protect
404              (let (default-enable-multibyte-characters)
405                (set-buffer-multibyte nil)
406                ,@forms)
407            (set-buffer ,buffer)
408            (set-buffer-multibyte t)))
409        (let (default-enable-multibyte-characters)
410          ,@forms))))
411 (put 'mm-with-unibyte-current-buffer-mule4 'lisp-indent-function 0)
412 (put 'mm-with-unibyte-current-buffer-mule4 'edebug-form-spec '(body))
413
414 (defmacro mm-with-unibyte (&rest forms)
415   "Eval the FORMS with the default value of `enable-multibyte-characters' nil, ."
416   `(let (default-enable-multibyte-characters)
417      ,@forms))
418 (put 'mm-with-unibyte 'lisp-indent-function 0)
419 (put 'mm-with-unibyte 'edebug-form-spec '(body))
420
421 (defun mm-find-charset-region (b e)
422   "Return a list of Emacs charsets in the region B to E."
423   (cond
424    ((and (mm-multibyte-p)
425          (fboundp 'find-charset-region))
426     ;; Remove composition since the base charsets have been included.
427     ;; Remove eight-bit-*, treat them as ascii.
428     (let ((css (find-charset-region b e)))
429       (mapcar (lambda (cs) (setq css (delq cs css)))
430               '(composition eight-bit-control eight-bit-graphic))
431       css))
432    (t
433     ;; We are in a unibyte buffer or XEmacs non-mule, so we futz around a bit.
434     (save-excursion
435       (save-restriction
436         (narrow-to-region b e)
437         (goto-char (point-min))
438         (skip-chars-forward "\0-\177")
439         (if (eobp)
440             '(ascii)
441           (let (charset)
442             (setq charset
443                   (and (boundp 'current-language-environment)
444                        (car (last (assq 'charset
445                                         (assoc current-language-environment
446                                                language-info-alist))))))
447             (if (eq charset 'ascii) (setq charset nil))
448             (or charset
449                 (setq charset
450                       (car (last (assq mail-parse-charset
451                                        mm-mime-mule-charset-alist)))))
452             (list 'ascii (or charset 'latin-iso8859-1)))))))))
453
454 (static-if (fboundp 'shell-quote-argument)
455     (defalias 'mm-quote-arg 'shell-quote-argument)
456   (defun mm-quote-arg (arg)
457     "Return a version of ARG that is safe to evaluate in a shell."
458     (let ((pos 0) new-pos accum)
459       ;; *** bug: we don't handle newline characters properly
460       (while (setq new-pos (string-match "[]*[;!'`\"$\\& \t{} |()<>]" arg pos))
461         (push (substring arg pos new-pos) accum)
462         (push "\\" accum)
463         (push (list (aref arg new-pos)) accum)
464         (setq pos (1+ new-pos)))
465       (if (= pos 0)
466           arg
467         (apply 'concat (nconc (nreverse accum) (list (substring arg pos))))))))
468
469 (defun mm-auto-mode-alist ()
470   "Return an `auto-mode-alist' with only the .gz (etc) thingies."
471   (let ((alist auto-mode-alist)
472         out)
473     (while alist
474       (when (listp (cdar alist))
475         (push (car alist) out))
476       (pop alist))
477     (nreverse out)))
478
479 (defvar mm-inhibit-file-name-handlers
480   '(jka-compr-handler image-file-handler)
481   "A list of handlers doing (un)compression (etc) thingies.")
482
483 (defun mm-insert-file-contents (filename &optional visit beg end replace
484                                          inhibit)
485   "Like `insert-file-contents', q.v., but only reads in the file.
486 A buffer may be modified in several ways after reading into the buffer due
487 to advanced Emacs features, such as file-name-handlers, format decoding,
488 find-file-hooks, etc.
489 If INHIBIT is non-nil, inhibit mm-inhibit-file-name-handlers.
490   This function ensures that none of these modifications will take place."
491   (let ((format-alist nil)
492         (auto-mode-alist (if inhibit nil (mm-auto-mode-alist)))
493         (default-major-mode 'fundamental-mode)
494         (enable-local-variables nil)
495         (after-insert-file-functions nil)
496         (enable-local-eval nil)
497         (find-file-hooks nil)
498         (inhibit-file-name-operation (if inhibit
499                                          'insert-file-contents
500                                        inhibit-file-name-operation))
501         (inhibit-file-name-handlers
502          (if inhibit
503              (append mm-inhibit-file-name-handlers
504                      inhibit-file-name-handlers)
505            inhibit-file-name-handlers)))
506     (insert-file-contents filename visit beg end replace)))
507
508 (defun mm-append-to-file (start end filename &optional codesys inhibit)
509   "Append the contents of the region to the end of file FILENAME.
510 When called from a function, expects three arguments,
511 START, END and FILENAME.  START and END are buffer positions
512 saying what text to write.
513 Optional fourth argument specifies the coding system to use when
514 encoding the file.
515 If INHIBIT is non-nil, inhibit mm-inhibit-file-name-handlers."
516   (let ((coding-system-for-write
517          (or codesys mm-text-coding-system-for-write
518              mm-text-coding-system))
519         (inhibit-file-name-operation (if inhibit
520                                          'append-to-file
521                                        inhibit-file-name-operation))
522         (inhibit-file-name-handlers
523          (if inhibit
524              (append mm-inhibit-file-name-handlers
525                      inhibit-file-name-handlers)
526            inhibit-file-name-handlers)))
527     (append-to-file start end filename)))
528
529 (defun mm-write-region (start end filename &optional append visit lockname
530                               coding-system inhibit)
531
532   "Like `write-region'.
533 If INHIBIT is non-nil, inhibit mm-inhibit-file-name-handlers."
534   (let ((coding-system-for-write
535          (or coding-system mm-text-coding-system-for-write
536              mm-text-coding-system))
537         (inhibit-file-name-operation (if inhibit
538                                          'write-region
539                                        inhibit-file-name-operation))
540         (inhibit-file-name-handlers
541          (if inhibit
542              (append mm-inhibit-file-name-handlers
543                      inhibit-file-name-handlers)
544            inhibit-file-name-handlers)))
545     (write-region start end filename append visit lockname)))
546
547 (defun mm-image-load-path (&optional package)
548   (let (dir result)
549     (dolist (path load-path (nreverse result))
550       (if (file-directory-p
551            (setq dir (concat (file-name-directory
552                               (directory-file-name path))
553                              "etc/" (or package "gnus/"))))
554           (push dir result))
555       (push path result))))
556
557 (provide 'mm-util)
558
559 ;;; mm-util.el ends here