mh-acros.el 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331
  1. ;;; mh-acros.el --- macros used in MH-E
  2. ;; Copyright (C) 2004, 2006-2012 Free Software Foundation, Inc.
  3. ;; Author: Satyaki Das <satyaki@theforce.stanford.edu>
  4. ;; Maintainer: Bill Wohler <wohler@newt.com>
  5. ;; Keywords: mail
  6. ;; See: mh-e.el
  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. ;; This file contains all macros that are used in more than one file.
  20. ;; If you run "make recompile" in Bazaar Emacs and see the message
  21. ;; "Source is newer than compiled," it is a sign that macro probably
  22. ;; needs to be moved here.
  23. ;; Historically, it was so named with a silent "m" so that it would be
  24. ;; compiled first. Otherwise, "make recompile" in Bazaar Emacs would use
  25. ;; compiled files with stale macro definitions. Later, no-byte-compile
  26. ;; was added to the Local Variables section to avoid this problem and
  27. ;; because it's pointless to compile a file full of macros. But we
  28. ;; kept the name.
  29. ;;; Change Log:
  30. ;;; Code:
  31. (require 'cl)
  32. ;;; Compatibility
  33. ;;;###mh-autoload
  34. (defmacro mh-require-cl ()
  35. "Macro to load \"cl\" if needed.
  36. Emacs coding conventions require that the \"cl\" package not be
  37. required at runtime. However, the \"cl\" package in Emacs 21.4
  38. and earlier left \"cl\" routines in their macro expansions. In
  39. particular, the expansion of (setf (gethash ...) ...) used
  40. functions in \"cl\" at run time. This macro recognizes that and
  41. loads \"cl\" appropriately."
  42. (if (eq (car (macroexpand '(setf (gethash foo bar) baz))) 'cl-puthash)
  43. `(require 'cl)
  44. `(eval-when-compile (require 'cl))))
  45. ;;;###mh-autoload
  46. (defmacro mh-do-in-gnu-emacs (&rest body)
  47. "Execute BODY if in GNU Emacs."
  48. (declare (debug t))
  49. (unless (featurep 'xemacs) `(progn ,@body)))
  50. (put 'mh-do-in-gnu-emacs 'lisp-indent-hook 'defun)
  51. ;;;###mh-autoload
  52. (defmacro mh-do-in-xemacs (&rest body)
  53. "Execute BODY if in XEmacs."
  54. (declare (debug t))
  55. (when (featurep 'xemacs) `(progn ,@body)))
  56. (put 'mh-do-in-xemacs 'lisp-indent-hook 'defun)
  57. ;;;###mh-autoload
  58. (defmacro mh-funcall-if-exists (function &rest args)
  59. "Call FUNCTION with ARGS as parameters if it exists."
  60. (when (fboundp function)
  61. `(when (fboundp ',function)
  62. (funcall ',function ,@args))))
  63. ;;;###mh-autoload
  64. (defmacro defun-mh (name function arg-list &rest body)
  65. "Create function NAME.
  66. If FUNCTION exists, then NAME becomes an alias for FUNCTION.
  67. Otherwise, create function NAME with ARG-LIST and BODY."
  68. (let ((defined-p (fboundp function)))
  69. (if defined-p
  70. `(defalias ',name ',function)
  71. `(defun ,name ,arg-list ,@body))))
  72. (put 'defun-mh 'lisp-indent-function 'defun)
  73. (put 'defun-mh 'doc-string-elt 4)
  74. ;;;###mh-autoload
  75. (defmacro defmacro-mh (name macro arg-list &rest body)
  76. "Create macro NAME.
  77. If MACRO exists, then NAME becomes an alias for MACRO.
  78. Otherwise, create macro NAME with ARG-LIST and BODY."
  79. (let ((defined-p (fboundp macro)))
  80. (if defined-p
  81. `(defalias ',name ',macro)
  82. `(defmacro ,name ,arg-list ,@body))))
  83. (put 'defmacro-mh 'lisp-indent-function 'defun)
  84. (put 'defmacro-mh 'doc-string-elt 4)
  85. ;;; Miscellaneous
  86. ;;;###mh-autoload
  87. (defmacro mh-make-local-hook (hook)
  88. "Make HOOK local if needed.
  89. XEmacs and versions of GNU Emacs before 21.1 require
  90. `make-local-hook' to be called."
  91. (when (and (fboundp 'make-local-hook)
  92. (not (get 'make-local-hook 'byte-obsolete-info)))
  93. `(make-local-hook ,hook)))
  94. ;;;###mh-autoload
  95. (defmacro mh-mark-active-p (check-transient-mark-mode-flag)
  96. "A macro that expands into appropriate code in XEmacs and nil in GNU Emacs.
  97. In GNU Emacs if CHECK-TRANSIENT-MARK-MODE-FLAG is non-nil then
  98. check if variable `transient-mark-mode' is active."
  99. (cond ((featurep 'xemacs) ;XEmacs
  100. `(and (boundp 'zmacs-regions) zmacs-regions (region-active-p)))
  101. ((not check-transient-mark-mode-flag) ;GNU Emacs
  102. `(and (boundp 'mark-active) mark-active))
  103. (t ;GNU Emacs
  104. `(and (boundp 'transient-mark-mode) transient-mark-mode
  105. (boundp 'mark-active) mark-active))))
  106. ;; Shush compiler.
  107. (mh-do-in-xemacs
  108. (defvar struct)
  109. (defvar x)
  110. (defvar y))
  111. ;;;###mh-autoload
  112. (defmacro mh-defstruct (name-spec &rest fields)
  113. "Replacement for `defstruct' from the \"cl\" package.
  114. The `defstruct' in the \"cl\" library produces compiler warnings,
  115. and generates code that uses functions present in \"cl\" at
  116. run-time. This is a partial replacement, that avoids these
  117. issues.
  118. NAME-SPEC declares the name of the structure, while FIELDS
  119. describes the various structure fields. Lookup `defstruct' for
  120. more details."
  121. (let* ((struct-name (if (atom name-spec) name-spec (car name-spec)))
  122. (conc-name (or (and (consp name-spec)
  123. (cadr (assoc :conc-name (cdr name-spec))))
  124. (format "%s-" struct-name)))
  125. (predicate (intern (format "%s-p" struct-name)))
  126. (constructor (or (and (consp name-spec)
  127. (cadr (assoc :constructor (cdr name-spec))))
  128. (intern (format "make-%s" struct-name))))
  129. (field-names (mapcar #'(lambda (x) (if (atom x) x (car x))) fields))
  130. (field-init-forms (mapcar #'(lambda (x) (and (consp x) (cadr x)))
  131. fields))
  132. (struct (gensym "S"))
  133. (x (gensym "X"))
  134. (y (gensym "Y")))
  135. `(progn
  136. (defun* ,constructor (&key ,@(mapcar* #'(lambda (x y) (list x y))
  137. field-names field-init-forms))
  138. (list (quote ,struct-name) ,@field-names))
  139. (defun ,predicate (arg)
  140. (and (consp arg) (eq (car arg) (quote ,struct-name))))
  141. ,@(loop for x from 1
  142. for y in field-names
  143. collect `(defmacro ,(intern (format "%s%s" conc-name y)) (z)
  144. (list 'nth ,x z)))
  145. (quote ,struct-name))))
  146. ;;;###mh-autoload
  147. (defmacro with-mh-folder-updating (save-modification-flag &rest body)
  148. "Format is (with-mh-folder-updating (SAVE-MODIFICATION-FLAG) &body BODY).
  149. Execute BODY, which can modify the folder buffer without having to
  150. worry about file locking or the read-only flag, and return its result.
  151. If SAVE-MODIFICATION-FLAG is non-nil, the buffer's modification flag
  152. is unchanged, otherwise it is cleared."
  153. (declare (debug t))
  154. (setq save-modification-flag (car save-modification-flag)) ; CL style
  155. `(prog1
  156. (let ((mh-folder-updating-mod-flag (buffer-modified-p))
  157. (buffer-read-only nil)
  158. (buffer-file-name nil)) ;don't let the buffer get locked
  159. (prog1
  160. (progn
  161. ,@body)
  162. (mh-set-folder-modified-p mh-folder-updating-mod-flag)))
  163. ,@(if (not save-modification-flag)
  164. '((mh-set-folder-modified-p nil)))))
  165. (put 'with-mh-folder-updating 'lisp-indent-hook 'defun)
  166. ;;;###mh-autoload
  167. (defmacro mh-in-show-buffer (show-buffer &rest body)
  168. "Format is (mh-in-show-buffer (SHOW-BUFFER) &body BODY).
  169. Display buffer SHOW-BUFFER in other window and execute BODY in it.
  170. Stronger than `save-excursion', weaker than `save-window-excursion'."
  171. (declare (debug t))
  172. (setq show-buffer (car show-buffer)) ; CL style
  173. `(let ((mh-in-show-buffer-saved-window (selected-window)))
  174. (switch-to-buffer-other-window ,show-buffer)
  175. (if mh-bury-show-buffer-flag (bury-buffer (current-buffer)))
  176. (unwind-protect
  177. (progn
  178. ,@body)
  179. (select-window mh-in-show-buffer-saved-window))))
  180. (put 'mh-in-show-buffer 'lisp-indent-hook 'defun)
  181. ;;;###mh-autoload
  182. (defmacro mh-do-at-event-location (event &rest body)
  183. "Switch to the location of EVENT and execute BODY.
  184. After BODY has been executed return to original window. The
  185. modification flag of the buffer in the event window is
  186. preserved."
  187. (declare (debug t))
  188. (let ((event-window (make-symbol "event-window"))
  189. (event-position (make-symbol "event-position"))
  190. (original-window (make-symbol "original-window"))
  191. (original-position (make-symbol "original-position"))
  192. (modified-flag (make-symbol "modified-flag")))
  193. `(save-excursion
  194. (let* ((,event-window
  195. (or (mh-funcall-if-exists posn-window (event-start ,event))
  196. (mh-funcall-if-exists event-window ,event)))
  197. (,event-position
  198. (or (mh-funcall-if-exists posn-point (event-start ,event))
  199. (mh-funcall-if-exists event-closest-point ,event)))
  200. (,original-window (selected-window))
  201. (,original-position (progn
  202. (set-buffer (window-buffer ,event-window))
  203. (set-marker (make-marker) (point))))
  204. (,modified-flag (buffer-modified-p))
  205. (buffer-read-only nil))
  206. (unwind-protect (progn
  207. (select-window ,event-window)
  208. (goto-char ,event-position)
  209. ,@body)
  210. (set-buffer-modified-p ,modified-flag)
  211. (goto-char ,original-position)
  212. (set-marker ,original-position nil)
  213. (select-window ,original-window))))))
  214. (put 'mh-do-at-event-location 'lisp-indent-hook 'defun)
  215. ;;; Sequences and Ranges
  216. ;;;###mh-autoload
  217. (defsubst mh-seq-msgs (sequence)
  218. "Extract messages from the given SEQUENCE."
  219. (cdr sequence))
  220. ;;;###mh-autoload
  221. (defmacro mh-iterate-on-messages-in-region (var begin end &rest body)
  222. "Iterate over region.
  223. VAR is bound to the message on the current line as we loop
  224. starting from BEGIN till END. In each step BODY is executed.
  225. If VAR is nil then the loop is executed without any binding."
  226. (declare (debug (symbolp body)))
  227. (unless (symbolp var)
  228. (error "Can not bind the non-symbol %s" var))
  229. (let ((binding-needed-flag var))
  230. `(save-excursion
  231. (goto-char ,begin)
  232. (beginning-of-line)
  233. (while (and (<= (point) ,end) (not (eobp)))
  234. (when (looking-at mh-scan-valid-regexp)
  235. (let ,(if binding-needed-flag `((,var (mh-get-msg-num t))) ())
  236. ,@body))
  237. (forward-line 1)))))
  238. (put 'mh-iterate-on-messages-in-region 'lisp-indent-hook 'defun)
  239. ;;;###mh-autoload
  240. (defmacro mh-iterate-on-range (var range &rest body)
  241. "Iterate an operation over a region or sequence.
  242. VAR is bound to each message in turn in a loop over RANGE, which
  243. can be a message number, a list of message numbers, a sequence, a
  244. region in a cons cell, or a MH range (something like last:20) in
  245. a string. In each iteration, BODY is executed.
  246. The parameter RANGE is usually created with
  247. `mh-interactive-range' in order to provide a uniform interface to
  248. MH-E functions."
  249. (declare (debug (symbolp body)))
  250. (unless (symbolp var)
  251. (error "Can not bind the non-symbol %s" var))
  252. (let ((binding-needed-flag var)
  253. (msgs (make-symbol "msgs"))
  254. (seq-hash-table (make-symbol "seq-hash-table")))
  255. `(cond ((numberp ,range)
  256. (when (mh-goto-msg ,range t t)
  257. (let ,(if binding-needed-flag `((,var ,range)) ())
  258. ,@body)))
  259. ((and (consp ,range)
  260. (numberp (car ,range)) (numberp (cdr ,range)))
  261. (mh-iterate-on-messages-in-region ,var
  262. (car ,range) (cdr ,range)
  263. ,@body))
  264. (t (let ((,msgs (cond ((and ,range (symbolp ,range))
  265. (mh-seq-to-msgs ,range))
  266. ((stringp ,range)
  267. (mh-translate-range mh-current-folder
  268. ,range))
  269. (t ,range)))
  270. (,seq-hash-table (make-hash-table)))
  271. (dolist (msg ,msgs)
  272. (setf (gethash msg ,seq-hash-table) t))
  273. (mh-iterate-on-messages-in-region v (point-min) (point-max)
  274. (when (gethash v ,seq-hash-table)
  275. (let ,(if binding-needed-flag `((,var v)) ())
  276. ,@body))))))))
  277. (put 'mh-iterate-on-range 'lisp-indent-hook 'defun)
  278. (provide 'mh-acros)
  279. ;; Local Variables:
  280. ;; no-byte-compile: t
  281. ;; indent-tabs-mode: nil
  282. ;; sentence-end-double-space: nil
  283. ;; End:
  284. ;;; mh-acros.el ends here