mh-seq.el 40 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020
  1. ;;; mh-seq.el --- MH-E sequences support
  2. ;; Copyright (C) 1993, 1995, 2001-2012 Free Software Foundation, Inc.
  3. ;; Author: Bill Wohler <wohler@newt.com>
  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. ;; Sequences are stored in the alist `mh-seq-list' in the form:
  20. ;; ((seq-name msgs ...) (seq-name msgs ...) ...)
  21. ;;; Change Log:
  22. ;;; Code:
  23. (require 'mh-e)
  24. (mh-require-cl)
  25. (require 'mh-scan)
  26. (require 'font-lock)
  27. ;;; Variables
  28. (defvar mh-last-seq-used nil
  29. "Name of seq to which a msg was last added.")
  30. (defvar mh-non-seq-mode-line-annotation nil
  31. "Saved value of `mh-mode-line-annotation' when narrowed to a seq.")
  32. (make-variable-buffer-local 'mh-non-seq-mode-line-annotation)
  33. (defvar mh-internal-seqs '(answered cur deleted forwarded printed))
  34. ;;; Macros
  35. (defsubst mh-make-seq (name msgs)
  36. "Create sequence NAME with the given MSGS."
  37. (cons name msgs))
  38. (defsubst mh-seq-name (sequence)
  39. "Extract sequence name from the given SEQUENCE."
  40. (car sequence))
  41. ;;; MH-Folder Commands
  42. ;; Alphabetical.
  43. ;;;###mh-autoload
  44. (defun mh-catchup (range)
  45. "Delete RANGE from the \"unseen\" sequence.
  46. Check the documentation of `mh-interactive-range' to see how
  47. RANGE is read in interactive use."
  48. (interactive (list (mh-interactive-range "Catchup"
  49. (cons (point-min) (point-max)))))
  50. (mh-delete-msg-from-seq range mh-unseen-seq))
  51. ;;;###mh-autoload
  52. (defun mh-delete-msg-from-seq (range sequence &optional internal-flag)
  53. "Delete RANGE from SEQUENCE.
  54. Check the documentation of `mh-interactive-range' to see how
  55. RANGE is read in interactive use.
  56. In a program, non-nil INTERNAL-FLAG means do not inform MH of the
  57. change."
  58. (interactive (list (mh-interactive-range "Delete")
  59. (mh-read-seq-default "Delete from" t)
  60. nil))
  61. (let ((entry (mh-find-seq sequence))
  62. (user-sequence-flag (not (mh-internal-seq sequence)))
  63. (folders-changed (list mh-current-folder))
  64. (msg-list ()))
  65. (when entry
  66. (mh-iterate-on-range msg range
  67. (push msg msg-list)
  68. ;; Calling "mark" repeatedly takes too long. So we will pretend here
  69. ;; that we are just modifying an internal sequence...
  70. (when (memq msg (cdr entry))
  71. (mh-remove-sequence-notation msg (not user-sequence-flag)))
  72. (mh-delete-a-msg-from-seq msg sequence t))
  73. ;; ... and here we will "mark" all the messages at one go.
  74. (unless internal-flag (mh-undefine-sequence sequence msg-list))
  75. (when (and mh-index-data (not internal-flag))
  76. (setq folders-changed
  77. (append folders-changed
  78. (mh-index-delete-from-sequence sequence msg-list))))
  79. (when (and (eq sequence mh-unseen-seq) (mh-speed-flists-active-p))
  80. (apply #'mh-speed-flists t folders-changed)))))
  81. ;;;###mh-autoload
  82. (defun mh-delete-seq (sequence)
  83. "Delete SEQUENCE.
  84. You are prompted for the sequence to delete. Note that this
  85. deletes only the sequence, not the messages in the sequence. If
  86. you want to delete the messages, use \"\\[universal-argument]
  87. \\[mh-delete-msg]\"."
  88. (interactive (list (mh-read-seq-default "Delete" t)))
  89. (let ((msg-list (mh-seq-to-msgs sequence))
  90. (internal-flag (mh-internal-seq sequence))
  91. (folders-changed (list mh-current-folder)))
  92. (mh-iterate-on-range msg sequence
  93. (mh-remove-sequence-notation msg internal-flag))
  94. (mh-undefine-sequence sequence '("all"))
  95. (mh-delete-seq-locally sequence)
  96. (when mh-index-data
  97. (setq folders-changed
  98. (append folders-changed
  99. (mh-index-delete-from-sequence sequence msg-list))))
  100. (when (and (eq sequence mh-unseen-seq) (mh-speed-flists-active-p))
  101. (apply #'mh-speed-flists t folders-changed))))
  102. ;; Shush compiler.
  103. (defvar view-exit-action)
  104. ;;;###mh-autoload
  105. (defun mh-list-sequences ()
  106. "List all sequences in folder.
  107. The list appears in a buffer named \"*MH-E Sequences*\"."
  108. (interactive)
  109. (let ((folder mh-current-folder)
  110. (temp-buffer mh-sequences-buffer)
  111. (seq-list mh-seq-list)
  112. (max-len 0))
  113. (with-output-to-temp-buffer temp-buffer
  114. (with-current-buffer temp-buffer
  115. (erase-buffer)
  116. (message "Listing sequences ...")
  117. (insert "Sequences in folder " folder ":\n")
  118. (let ((seq-list seq-list))
  119. (while seq-list
  120. (setq max-len
  121. (max (length (symbol-name (mh-seq-name (pop seq-list))))
  122. max-len)))
  123. (setq max-len (+ 2 max-len)))
  124. (while seq-list
  125. (let ((name (mh-seq-name (car seq-list)))
  126. (sorted-seq-msgs
  127. (mh-coalesce-msg-list
  128. (sort (copy-sequence (mh-seq-msgs (car seq-list))) '<)))
  129. name-spec)
  130. (insert (setq name-spec (format (format "%%%ss:" max-len) name)))
  131. (while sorted-seq-msgs
  132. (let ((next-element (format " %s" (pop sorted-seq-msgs))))
  133. (when (>= (+ (current-column) (length next-element))
  134. (window-width))
  135. (insert "\n")
  136. (insert (format (format "%%%ss" (length name-spec)) "")))
  137. (insert next-element)))
  138. (insert "\n"))
  139. (setq seq-list (cdr seq-list)))
  140. (goto-char (point-min))
  141. (mh-view-mode-enter)
  142. (setq view-exit-action 'kill-buffer)
  143. (message "Listing sequences...done")))))
  144. ;;;###mh-autoload
  145. (defun mh-msg-is-in-seq (message)
  146. "Display the sequences in which the current message appears.
  147. Use a prefix argument to display the sequences in which another
  148. MESSAGE appears."
  149. (interactive "P")
  150. (if (not message)
  151. (setq message (mh-get-msg-num t)))
  152. (let* ((dest-folder (loop for seq in mh-refile-list
  153. when (member message (cdr seq)) return (car seq)
  154. finally return nil))
  155. (deleted-flag (unless dest-folder (member message mh-delete-list))))
  156. (message "Message %d%s is in sequences: %s"
  157. message
  158. (cond (dest-folder (format " (to be refiled to %s)" dest-folder))
  159. (deleted-flag (format " (to be deleted)"))
  160. (t ""))
  161. (mapconcat 'concat
  162. (mh-list-to-string (mh-seq-containing-msg message t))
  163. " "))))
  164. ;; Shush compiler.
  165. (mh-do-in-xemacs
  166. (defvar tool-bar-mode))
  167. (defvar tool-bar-map)
  168. ;;;###mh-autoload
  169. (defun mh-narrow-to-seq (sequence)
  170. "Restrict display to messages in SEQUENCE.
  171. You are prompted for the name of the sequence. What this command
  172. does is show only those messages that are in the selected
  173. sequence in the MH-Folder buffer. In addition, it limits further
  174. MH-E searches to just those messages.
  175. When you want to widen the view to all your messages again, use
  176. \\[mh-widen]."
  177. (interactive (list (mh-read-seq "Narrow to" t)))
  178. (with-mh-folder-updating (t)
  179. (cond ((mh-seq-to-msgs sequence)
  180. (mh-remove-all-notation)
  181. (let ((eob (point-max))
  182. (msg-at-cursor (mh-get-msg-num nil)))
  183. (push mh-thread-scan-line-map mh-thread-scan-line-map-stack)
  184. (setq mh-thread-scan-line-map (make-hash-table :test #'eql))
  185. (mh-copy-seq-to-eob sequence)
  186. (push (buffer-substring-no-properties (point-min) eob)
  187. mh-folder-view-stack)
  188. (delete-region (point-min) eob)
  189. (mh-notate-deleted-and-refiled)
  190. (mh-notate-cur)
  191. (when msg-at-cursor (mh-goto-msg msg-at-cursor t t))
  192. (setq mh-non-seq-mode-line-annotation mh-mode-line-annotation)
  193. (setq mh-mode-line-annotation (symbol-name sequence))
  194. (mh-make-folder-mode-line)
  195. (mh-recenter nil)
  196. (when (and (boundp 'tool-bar-mode) tool-bar-mode)
  197. (set (make-local-variable 'tool-bar-map)
  198. mh-folder-seq-tool-bar-map)
  199. (when (buffer-live-p (get-buffer mh-show-buffer))
  200. (with-current-buffer mh-show-buffer
  201. (set (make-local-variable 'tool-bar-map)
  202. mh-show-seq-tool-bar-map))))
  203. (push 'widen mh-view-ops)))
  204. (t
  205. (error "No messages in sequence %s" (symbol-name sequence))))))
  206. ;;;###mh-autoload
  207. (defun mh-narrow-to-tick ()
  208. "Limit to ticked messages.
  209. What this command does is show only those messages that are in
  210. the \"tick\" sequence (which you can customize via the
  211. `mh-tick-seq' option) in the MH-Folder buffer. In addition, it
  212. limits further MH-E searches to just those messages. When you
  213. want to widen the view to all your messages again, use
  214. \\[mh-widen]."
  215. (interactive)
  216. (cond ((not mh-tick-seq)
  217. (error "Enable ticking by customizing `mh-tick-seq'"))
  218. ((null (mh-seq-msgs (mh-find-seq mh-tick-seq)))
  219. (message "No messages in %s sequence" mh-tick-seq))
  220. (t (mh-narrow-to-seq mh-tick-seq))))
  221. ;;;###mh-autoload
  222. (defun mh-put-msg-in-seq (range sequence)
  223. "Add RANGE to SEQUENCE\\<mh-folder-mode-map>.
  224. Give this command a RANGE and you can add all the messages in a
  225. sequence to another sequence (for example,
  226. \"\\[universal-argument] \\[mh-put-msg-in-seq] SourceSequence RET
  227. DestSequence RET\"). Check the documentation of
  228. `mh-interactive-range' to see how RANGE is read in interactive
  229. use."
  230. (interactive (list (mh-interactive-range "Add messages from")
  231. (mh-read-seq-default "Add to" nil)))
  232. (unless (mh-valid-seq-p sequence)
  233. (error "Can't put message in invalid sequence %s" sequence))
  234. (let* ((internal-seq-flag (mh-internal-seq sequence))
  235. (original-msgs (mh-seq-msgs (mh-find-seq sequence)))
  236. (folders (list mh-current-folder))
  237. (msg-list (mh-range-to-msg-list range)))
  238. (mh-add-msgs-to-seq msg-list sequence nil t)
  239. (mh-iterate-on-range m range
  240. (unless (memq m original-msgs)
  241. (mh-add-sequence-notation m internal-seq-flag)))
  242. (if (not internal-seq-flag)
  243. (setq mh-last-seq-used sequence))
  244. (when mh-index-data
  245. (setq folders
  246. (append folders (mh-index-add-to-sequence sequence msg-list))))
  247. (when (and (eq sequence mh-unseen-seq) (mh-speed-flists-active-p))
  248. (apply #'mh-speed-flists t folders))))
  249. ;;;###mh-autoload
  250. (defun mh-toggle-tick (range)
  251. "Toggle tick mark of RANGE.
  252. This command adds messages to the \"tick\" sequence (which you can customize
  253. via the option `mh-tick-seq'). This sequence can be viewed later with the
  254. \\[mh-index-ticked-messages] command.
  255. Check the documentation of `mh-interactive-range' to see how RANGE is read in
  256. interactive use."
  257. (interactive (list (mh-interactive-range "Tick")))
  258. (unless mh-tick-seq
  259. (error "Enable ticking by customizing `mh-tick-seq'"))
  260. (let* ((tick-seq (mh-find-seq mh-tick-seq))
  261. (tick-seq-msgs (mh-seq-msgs tick-seq))
  262. (ticked ())
  263. (unticked ()))
  264. (mh-iterate-on-range msg range
  265. (cond ((member msg tick-seq-msgs)
  266. (push msg unticked)
  267. (setcdr tick-seq (delq msg (cdr tick-seq)))
  268. (when (null (cdr tick-seq)) (setq mh-last-seq-used nil))
  269. (mh-remove-sequence-notation msg (mh-colors-in-use-p)))
  270. (t
  271. (push msg ticked)
  272. (setq mh-last-seq-used mh-tick-seq)
  273. (let ((mh-seq-list (cons `(,mh-tick-seq ,msg) mh-seq-list)))
  274. (mh-add-sequence-notation msg (mh-colors-in-use-p))))))
  275. (mh-add-msgs-to-seq ticked mh-tick-seq nil t)
  276. (mh-undefine-sequence mh-tick-seq unticked)
  277. (when mh-index-data
  278. (mh-index-add-to-sequence mh-tick-seq ticked)
  279. (mh-index-delete-from-sequence mh-tick-seq unticked))))
  280. ;;;###mh-autoload
  281. (defun mh-widen (&optional all-flag)
  282. "Remove last restriction.
  283. Each limit or sequence restriction can be undone in turn with
  284. this command. Give this command a prefix argument ALL-FLAG to
  285. remove all limits and sequence restrictions."
  286. (interactive "P")
  287. (let ((msg (mh-get-msg-num nil)))
  288. (when mh-folder-view-stack
  289. (cond (all-flag
  290. (while (cdr mh-view-ops)
  291. (setq mh-view-ops (cdr mh-view-ops)))
  292. (when (eq (car mh-view-ops) 'widen)
  293. (setq mh-view-ops (cdr mh-view-ops))))
  294. ((mh-valid-view-change-operation-p 'widen) nil)
  295. ((memq 'widen mh-view-ops)
  296. (while (not (eq (car mh-view-ops) 'widen))
  297. (setq mh-view-ops (cdr mh-view-ops)))
  298. (setq mh-view-ops (cdr mh-view-ops)))
  299. (t (error "Widening is not applicable")))
  300. ;; If ALL-FLAG is non-nil then rewind stacks
  301. (when all-flag
  302. (while (cdr mh-thread-scan-line-map-stack)
  303. (setq mh-thread-scan-line-map-stack
  304. (cdr mh-thread-scan-line-map-stack)))
  305. (while (cdr mh-folder-view-stack)
  306. (setq mh-folder-view-stack (cdr mh-folder-view-stack))))
  307. (setq mh-thread-scan-line-map (pop mh-thread-scan-line-map-stack))
  308. (with-mh-folder-updating (t)
  309. (delete-region (point-min) (point-max))
  310. (insert (pop mh-folder-view-stack))
  311. (mh-remove-all-notation)
  312. (setq mh-mode-line-annotation mh-non-seq-mode-line-annotation)
  313. (mh-make-folder-mode-line))
  314. (if msg
  315. (mh-goto-msg msg t t))
  316. (mh-notate-deleted-and-refiled)
  317. (mh-notate-user-sequences)
  318. (mh-notate-cur)
  319. (mh-recenter nil)))
  320. (when (and (null mh-folder-view-stack) (boundp 'tool-bar-mode) tool-bar-mode)
  321. (set (make-local-variable 'tool-bar-map) mh-folder-tool-bar-map)
  322. (when (buffer-live-p (get-buffer mh-show-buffer))
  323. (with-current-buffer mh-show-buffer
  324. (set (make-local-variable 'tool-bar-map) mh-show-tool-bar-map)))))
  325. ;;; Support Routines
  326. (defvar mh-sequence-history ())
  327. ;;;###mh-autoload
  328. (defun mh-read-seq-default (prompt not-empty)
  329. "Read and return sequence name with default narrowed or previous sequence.
  330. PROMPT is the prompt to use when reading. If NOT-EMPTY is non-nil
  331. then a non-empty sequence is read."
  332. (mh-read-seq prompt not-empty
  333. (or mh-last-seq-used
  334. (car (mh-seq-containing-msg (mh-get-msg-num nil) nil)))))
  335. (defun mh-read-seq (prompt not-empty &optional default)
  336. "Read and return a sequence name.
  337. Prompt with PROMPT, raise an error if the sequence is empty and
  338. the NOT-EMPTY flag is non-nil, and supply an optional DEFAULT
  339. sequence. A reply of '%' defaults to the first sequence
  340. containing the current message."
  341. (let* ((input (completing-read (format "%s sequence%s: " prompt
  342. (if default
  343. (format " (default %s)" default)
  344. ""))
  345. (mh-seq-names mh-seq-list)
  346. nil nil nil 'mh-sequence-history))
  347. (seq (cond ((equal input "%")
  348. (car (mh-seq-containing-msg (mh-get-msg-num t) nil)))
  349. ((equal input "") default)
  350. (t (intern input))))
  351. (msgs (mh-seq-to-msgs seq)))
  352. (if (and (null msgs) not-empty)
  353. (error "No messages in sequence %s" seq))
  354. seq))
  355. (defun mh-internal-seq (name)
  356. "Return non-nil if NAME is the name of an internal MH-E sequence."
  357. (or (memq name mh-internal-seqs)
  358. (eq name mh-unseen-seq)
  359. (and (mh-colors-in-use-p) mh-tick-seq (eq name mh-tick-seq))
  360. (eq name mh-previous-seq)
  361. (mh-folder-name-p name)))
  362. ;;;###mh-autoload
  363. (defun mh-valid-seq-p (name)
  364. "Return non-nil if NAME is a valid MH sequence name."
  365. (and (symbolp name)
  366. (string-match "^[a-zA-Z][a-zA-Z0-9]*$" (symbol-name name))))
  367. ;;;###mh-autoload
  368. (defun mh-find-seq (name)
  369. "Return sequence NAME."
  370. (assoc name mh-seq-list))
  371. ;;;###mh-autoload
  372. (defun mh-seq-to-msgs (seq)
  373. "Return a list of the messages in SEQ."
  374. (mh-seq-msgs (mh-find-seq seq)))
  375. (defun mh-seq-containing-msg (msg &optional include-internal-flag)
  376. "Return a list of the sequences containing MSG.
  377. If INCLUDE-INTERNAL-FLAG non-nil, include MH-E internal sequences
  378. in list."
  379. (let ((l mh-seq-list)
  380. (seqs ()))
  381. (while l
  382. (and (memq msg (mh-seq-msgs (car l)))
  383. (or include-internal-flag
  384. (not (mh-internal-seq (mh-seq-name (car l)))))
  385. (setq seqs (cons (mh-seq-name (car l)) seqs)))
  386. (setq l (cdr l)))
  387. seqs))
  388. ;;;###mh-autoload
  389. (defun mh-define-sequence (seq msgs)
  390. "Define the SEQ to contain the list of MSGS.
  391. Do not mark pseudo-sequences or empty sequences.
  392. Signals an error if SEQ is an invalid name."
  393. (if (and msgs
  394. (mh-valid-seq-p seq)
  395. (not (mh-folder-name-p seq)))
  396. (save-excursion
  397. (mh-exec-cmd-error nil "mark" mh-current-folder "-add" "-zero"
  398. "-sequence" (symbol-name seq)
  399. (mh-coalesce-msg-list msgs)))))
  400. ;;;###mh-autoload
  401. (defun mh-undefine-sequence (seq msgs)
  402. "Remove from the SEQ the list of MSGS."
  403. (when (and (mh-valid-seq-p seq) msgs)
  404. (apply #'mh-exec-cmd "mark" mh-current-folder "-delete"
  405. "-sequence" (symbol-name seq) (mh-coalesce-msg-list msgs))))
  406. ;;;###mh-autoload
  407. (defun mh-add-msgs-to-seq (msgs seq &optional internal-flag dont-annotate-flag)
  408. "Add MSGS to SEQ.
  409. Remove duplicates and keep sequence sorted. If optional
  410. INTERNAL-FLAG is non-nil, do not mark the message in the scan
  411. listing or inform MH of the addition.
  412. If DONT-ANNOTATE-FLAG is non-nil then the annotations in the
  413. folder buffer are not updated."
  414. (let ((entry (mh-find-seq seq))
  415. (internal-seq-flag (mh-internal-seq seq)))
  416. (if (and msgs (atom msgs)) (setq msgs (list msgs)))
  417. (if (null entry)
  418. (setq mh-seq-list
  419. (cons (mh-make-seq seq (mh-canonicalize-sequence msgs))
  420. mh-seq-list))
  421. (if msgs (setcdr entry (mh-canonicalize-sequence
  422. (append msgs (mh-seq-msgs entry))))))
  423. (unless internal-flag
  424. (mh-add-to-sequence seq msgs)
  425. (when (not dont-annotate-flag)
  426. (mh-iterate-on-range msg msgs
  427. (unless (memq msg (cdr entry))
  428. (mh-add-sequence-notation msg internal-seq-flag)))))))
  429. (defun mh-add-to-sequence (seq msgs)
  430. "The sequence SEQ is augmented with the messages in MSGS."
  431. ;; Add to a SEQUENCE each message the list of MSGS.
  432. (if (and (mh-valid-seq-p seq) (not (mh-folder-name-p seq)))
  433. (if msgs
  434. (apply 'mh-exec-cmd "mark" mh-current-folder "-add"
  435. "-sequence" (symbol-name seq)
  436. (mh-coalesce-msg-list msgs)))))
  437. (defun mh-canonicalize-sequence (msgs)
  438. "Sort MSGS in decreasing order and remove duplicates."
  439. (let* ((sorted-msgs (sort (copy-sequence msgs) '>))
  440. (head sorted-msgs))
  441. (while (cdr head)
  442. (if (= (car head) (cadr head))
  443. (setcdr head (cddr head))
  444. (setq head (cdr head))))
  445. sorted-msgs))
  446. (defun mh-delete-a-msg-from-seq (msg sequence internal-flag)
  447. "Delete MSG from SEQUENCE.
  448. If INTERNAL-FLAG is non-nil, then do not inform MH of the
  449. change."
  450. (let ((entry (mh-find-seq sequence)))
  451. (when (and entry (memq msg (mh-seq-msgs entry)))
  452. (if (not internal-flag)
  453. (mh-undefine-sequence sequence (list msg)))
  454. (setcdr entry (delq msg (mh-seq-msgs entry))))))
  455. (defun mh-delete-seq-locally (seq)
  456. "Remove MH-E's record of SEQ."
  457. (let ((entry (mh-find-seq seq)))
  458. (setq mh-seq-list (delq entry mh-seq-list))))
  459. (defun mh-copy-seq-to-eob (seq)
  460. "Copy SEQ to the end of the buffer."
  461. ;; It is quite involved to write something which will work at any place in
  462. ;; the buffer, so we will write something which works only at the end of
  463. ;; the buffer. If we ever need to insert sequences in the middle of the
  464. ;; buffer, this will need to be fixed.
  465. (save-excursion
  466. (let* ((msgs (mh-seq-to-msgs seq))
  467. (coalesced-msgs (mh-coalesce-msg-list msgs)))
  468. (goto-char (point-max))
  469. (save-restriction
  470. (narrow-to-region (point) (point))
  471. (mh-regenerate-headers coalesced-msgs t)
  472. (cond ((memq 'unthread mh-view-ops)
  473. ;; Populate restricted scan-line map
  474. (mh-remove-all-notation)
  475. (mh-iterate-on-range msg (cons (point-min) (point-max))
  476. (setf (gethash msg mh-thread-scan-line-map)
  477. (mh-thread-parse-scan-line)))
  478. ;; Remove scan lines and read results from pre-computed tree
  479. (delete-region (point-min) (point-max))
  480. (mh-thread-print-scan-lines
  481. (mh-thread-generate mh-current-folder ()))
  482. (mh-notate-user-sequences))
  483. (mh-index-data
  484. (mh-index-insert-folder-headers)))))))
  485. ;;;###mh-autoload
  486. (defun mh-valid-view-change-operation-p (op)
  487. "Check if the view change operation can be performed.
  488. OP is one of 'widen and 'unthread."
  489. (cond ((eq (car mh-view-ops) op)
  490. (pop mh-view-ops))
  491. (t nil)))
  492. ;;; Ranges
  493. (defvar mh-range-seq-names)
  494. (defvar mh-range-history ())
  495. (defvar mh-range-completion-map (copy-keymap minibuffer-local-completion-map))
  496. (define-key mh-range-completion-map " " 'self-insert-command)
  497. ;;;###mh-autoload
  498. (defun mh-interactive-range (range-prompt &optional default)
  499. "Return interactive specification for message, sequence, range or region.
  500. By convention, the name of this argument is RANGE.
  501. If variable `transient-mark-mode' is non-nil and the mark is active,
  502. then this function returns a cons-cell of the region.
  503. If optional prefix argument is provided, then prompt for message range
  504. with RANGE-PROMPT. A list of messages in that range is returned.
  505. If a MH range is given, say something like last:20, then a list
  506. containing the messages in that range is returned.
  507. If DEFAULT non-nil then it is returned.
  508. Otherwise, the message number at point is returned.
  509. This function is usually used with `mh-iterate-on-range' in order to
  510. provide a uniform interface to MH-E functions."
  511. (cond ((mh-mark-active-p t) (cons (region-beginning) (region-end)))
  512. (current-prefix-arg (mh-read-range range-prompt nil nil t t))
  513. (default default)
  514. (t (mh-get-msg-num t))))
  515. ;;;###mh-autoload
  516. (defun mh-read-range (prompt &optional folder default
  517. expand-flag ask-flag number-as-range-flag)
  518. "Read a message range with PROMPT.
  519. If FOLDER is non-nil then a range is read from that folder, otherwise
  520. use `mh-current-folder'.
  521. If DEFAULT is a string then use that as default range to return. If
  522. DEFAULT is nil then ask user with default answer a range based on the
  523. sequences that seem relevant. Finally if DEFAULT is t, try to avoid
  524. prompting the user. Unseen messages, if present, are returned. If the
  525. folder has fewer than `mh-large-folder' messages then \"all\" messages
  526. are returned. Finally as a last resort prompt the user.
  527. If EXPAND-FLAG is non-nil then a list of message numbers corresponding
  528. to the input is returned. If this list is empty then an error is
  529. raised. If EXPAND-FLAG is nil just return the input string. In this
  530. case we don't check if the range is empty.
  531. If ASK-FLAG is non-nil, then the user is always queried for a range of
  532. messages. If ASK-FLAG is nil, then the function checks if the unseen
  533. sequence is non-empty. If that is the case, `mh-unseen-seq', or the
  534. list of messages in it depending on the value of EXPAND, is returned.
  535. Otherwise if the folder has fewer than `mh-large-folder' messages then
  536. the list of messages corresponding to \"all\" is returned. If neither
  537. of the above holds then as a last resort the user is queried for a
  538. range of messages.
  539. If NUMBER-AS-RANGE-FLAG is non-nil, then if a number, N is read as
  540. input, it is interpreted as the range \"last:N\".
  541. This function replaces the existing function `mh-read-msg-range'.
  542. Calls to:
  543. (mh-read-msg-range folder flag)
  544. should be replaced with:
  545. (mh-read-range \"Suitable prompt\" folder t nil flag
  546. mh-interpret-number-as-range-flag)"
  547. (setq default (or default mh-last-seq-used
  548. (car (mh-seq-containing-msg (mh-get-msg-num nil) t)))
  549. prompt (format "%s range" prompt))
  550. (let* ((folder (or folder mh-current-folder))
  551. (guess (eq default t))
  552. (counts (and guess (mh-folder-size folder)))
  553. (unseen (and counts (> (cadr counts) 0)))
  554. (large (and counts mh-large-folder (> (car counts) mh-large-folder)))
  555. (default (cond ((and guess large) (format "last:%s" mh-large-folder))
  556. ((and guess (not large)) "all")
  557. ((stringp default) default)
  558. ((symbolp default) (symbol-name default))))
  559. (prompt (cond ((and guess large default)
  560. (format "%s (folder has %s messages, default %s)"
  561. prompt (car counts) default))
  562. ((and guess large)
  563. (format "%s (folder has %s messages)"
  564. prompt (car counts)))
  565. (default
  566. (format "%s (default %s)" prompt default))))
  567. (minibuffer-local-completion-map mh-range-completion-map)
  568. (seq-list (if (eq folder mh-current-folder)
  569. mh-seq-list
  570. (mh-read-folder-sequences folder nil)))
  571. (mh-range-seq-names
  572. (append '(("first") ("last") ("all") ("prev") ("next"))
  573. (mh-seq-names seq-list)))
  574. (input (cond ((and (not ask-flag) unseen) (symbol-name mh-unseen-seq))
  575. ((and (not ask-flag) (not large)) "all")
  576. (t (completing-read (format "%s: " prompt)
  577. 'mh-range-completion-function nil nil
  578. nil 'mh-range-history default))))
  579. msg-list)
  580. (when (and number-as-range-flag
  581. (string-match "^[ \t]*\\([0-9]+\\)[ \t]*$" input))
  582. (setq input (concat "last:" (match-string 1 input))))
  583. (cond ((not expand-flag) input)
  584. ((assoc (intern input) seq-list)
  585. (cdr (assoc (intern input) seq-list)))
  586. ((setq msg-list (mh-translate-range folder input)) msg-list)
  587. (t (error "No messages in range %s" input)))))
  588. ;;;###mh-autoload
  589. (defun mh-range-to-msg-list (range)
  590. "Return a list of messages for RANGE.
  591. Check the documentation of `mh-interactive-range' to see how
  592. RANGE is read in interactive use."
  593. (let (msg-list)
  594. (mh-iterate-on-range msg range
  595. (push msg msg-list))
  596. (nreverse msg-list)))
  597. ;;;###mh-autoload
  598. (defun mh-translate-range (folder expr)
  599. "In FOLDER, translate the string EXPR to a list of messages numbers."
  600. (save-excursion
  601. (let ((strings (delete "" (split-string expr "[ \t\n]")))
  602. (result ()))
  603. (ignore-errors
  604. (apply #'mh-exec-cmd-quiet nil "mhpath" folder strings)
  605. (set-buffer mh-temp-buffer)
  606. (goto-char (point-min))
  607. (while (re-search-forward "/\\([0-9]*\\)$" nil t)
  608. (push (string-to-number (match-string 1)) result))
  609. (nreverse result)))))
  610. (defun mh-range-completion-function (string predicate flag)
  611. "Programmable completion of message ranges.
  612. STRING is the user input that is to be completed. PREDICATE if non-nil is a
  613. function used to filter the possible choices and FLAG determines whether the
  614. completion is over."
  615. (let* ((candidates mh-range-seq-names)
  616. (last-char (and (not (equal string ""))
  617. (aref string (1- (length string)))))
  618. (last-word (cond ((null last-char) "")
  619. ((memq last-char '(? ?- ?:)) "")
  620. (t (car (last (split-string string "[ -:]+"))))))
  621. (prefix (substring string 0 (- (length string) (length last-word)))))
  622. (cond ((eq flag nil)
  623. (let ((res (try-completion last-word candidates predicate)))
  624. (cond ((null res) nil)
  625. ((eq res t) t)
  626. (t (concat prefix res)))))
  627. ((eq flag t)
  628. (all-completions last-word candidates predicate))
  629. ((eq flag 'lambda)
  630. (loop for x in candidates
  631. when (equal x last-word) return t
  632. finally return nil)))))
  633. (defun mh-seq-names (seq-list)
  634. "Return an alist containing the names of the SEQ-LIST."
  635. (mapcar (lambda (entry) (list (symbol-name (mh-seq-name entry))))
  636. seq-list))
  637. (defun mh-folder-size (folder)
  638. "Find size of FOLDER."
  639. (if mh-flists-present-flag
  640. (mh-folder-size-flist folder)
  641. (mh-folder-size-folder folder)))
  642. (defun mh-folder-size-flist (folder)
  643. "Find size of FOLDER using \"flist\"."
  644. (with-temp-buffer
  645. (call-process (expand-file-name "flist" mh-progs) nil t nil "-showzero"
  646. "-norecurse" folder "-sequence" (symbol-name mh-unseen-seq))
  647. (goto-char (point-min))
  648. (multiple-value-bind (folder unseen total)
  649. (values-list
  650. (mh-parse-flist-output-line
  651. (buffer-substring (point) (mh-line-end-position))))
  652. (list total unseen folder))))
  653. (defun mh-folder-size-folder (folder)
  654. "Find size of FOLDER using \"folder\"."
  655. (with-temp-buffer
  656. (let ((u (length (cdr (assoc mh-unseen-seq
  657. (mh-read-folder-sequences folder nil))))))
  658. (call-process (expand-file-name "folder" mh-progs) nil t nil
  659. "-norecurse" folder)
  660. (goto-char (point-min))
  661. (if (re-search-forward " has \\([0-9]+\\) " nil t)
  662. (list (string-to-number (match-string 1)) u folder)
  663. (list 0 u folder)))))
  664. ;;;###mh-autoload
  665. (defun mh-parse-flist-output-line (line &optional current-folder)
  666. "Parse LINE to generate folder name, unseen messages and total messages.
  667. If CURRENT-FOLDER is non-nil then it contains the current folder
  668. name and it is used to avoid problems in corner cases involving
  669. folders whose names end with a '+' character."
  670. (with-temp-buffer
  671. (insert line)
  672. (goto-char (point-max))
  673. (let (folder unseen total p)
  674. (when (search-backward " out of " (point-min) t)
  675. (setq total (string-to-number
  676. (buffer-substring-no-properties
  677. (match-end 0) (mh-line-end-position))))
  678. (when (search-backward " in sequence " (point-min) t)
  679. (setq p (point))
  680. (when (search-backward " has " (point-min) t)
  681. (setq unseen (string-to-number (buffer-substring-no-properties
  682. (match-end 0) p)))
  683. (while (eq (char-after) ? )
  684. (backward-char))
  685. (setq folder (buffer-substring-no-properties
  686. (point-min) (1+ (point))))
  687. (when (and (equal (aref folder (1- (length folder))) ?+)
  688. (equal current-folder folder))
  689. (setq folder (substring folder 0 (1- (length folder)))))
  690. (list (format "+%s" folder) unseen total)))))))
  691. ;;;###mh-autoload
  692. (defun mh-read-folder-sequences (folder save-refiles)
  693. "Read and return the predefined sequences for a FOLDER.
  694. If SAVE-REFILES is non-nil, then keep the sequences
  695. that note messages to be refiled."
  696. (let ((seqs ()))
  697. (cond (save-refiles
  698. (mh-mapc (function (lambda (seq) ; Save the refiling sequences
  699. (if (mh-folder-name-p (mh-seq-name seq))
  700. (setq seqs (cons seq seqs)))))
  701. mh-seq-list)))
  702. (save-excursion
  703. (if (eq 0 (mh-exec-cmd-quiet nil "mark" folder "-list"))
  704. (progn
  705. ;; look for name in line of form "cur: 4" or "myseq (private): 23"
  706. (while (re-search-forward "^[^: ]+" nil t)
  707. (setq seqs (cons (mh-make-seq (intern (buffer-substring
  708. (match-beginning 0)
  709. (match-end 0)))
  710. (mh-read-msg-list))
  711. seqs)))
  712. (delete-region (point-min) (point))))) ; avoid race with
  713. ; mh-process-daemon
  714. seqs))
  715. (defun mh-read-msg-list ()
  716. "Return a list of message numbers from point to the end of the line.
  717. Expands ranges into set of individual numbers."
  718. (let ((msgs ())
  719. (end-of-line (point-at-eol))
  720. num)
  721. (while (re-search-forward "[0-9]+" end-of-line t)
  722. (setq num (string-to-number (buffer-substring (match-beginning 0)
  723. (match-end 0))))
  724. (cond ((looking-at "-") ; Message range
  725. (forward-char 1)
  726. (re-search-forward "[0-9]+" end-of-line t)
  727. (let ((num2 (string-to-number
  728. (buffer-substring (match-beginning 0)
  729. (match-end 0)))))
  730. (if (< num2 num)
  731. (error "Bad message range: %d-%d" num num2))
  732. (while (<= num num2)
  733. (setq msgs (cons num msgs))
  734. (setq num (1+ num)))))
  735. ((not (zerop num)) ;"pick" outputs "0" to mean no match
  736. (setq msgs (cons num msgs)))))
  737. msgs))
  738. ;;; Notation
  739. ;;;###mh-autoload
  740. (defun mh-notate (msg notation offset)
  741. "Mark MSG with the character NOTATION at position OFFSET.
  742. Null MSG means the message at cursor.
  743. If NOTATION is nil then no change in the buffer occurs."
  744. (save-excursion
  745. (if (or (null msg)
  746. (mh-goto-msg msg t t))
  747. (with-mh-folder-updating (t)
  748. (beginning-of-line)
  749. (forward-char offset)
  750. (let* ((change-stack-flag
  751. (and (equal offset
  752. (+ mh-cmd-note mh-scan-field-destination-offset))
  753. (not (eq notation mh-note-seq))))
  754. (msg (and change-stack-flag (or msg (mh-get-msg-num nil))))
  755. (stack (and msg (gethash msg mh-sequence-notation-history)))
  756. (notation (or notation (char-after))))
  757. (if stack
  758. ;; The presence of the stack tells us that we don't need to
  759. ;; notate the message, since the notation would be replaced
  760. ;; by a sequence notation. So we will just put the notation
  761. ;; at the bottom of the stack. If the sequence is deleted,
  762. ;; the correct notation will be shown.
  763. (setf (gethash msg mh-sequence-notation-history)
  764. (reverse (cons notation (cdr (reverse stack)))))
  765. ;; Since we don't have any sequence notations in the way, just
  766. ;; notate the scan line.
  767. (delete-char 1)
  768. (insert notation))
  769. (when change-stack-flag
  770. (mh-thread-update-scan-line-map msg notation offset)))))))
  771. ;;;###mh-autoload
  772. (defun mh-notate-cur ()
  773. "Mark the MH sequence cur.
  774. In addition to notating the current message with `mh-note-cur'
  775. the function uses `overlay-arrow-position' to put a marker in the
  776. fringe."
  777. (let ((cur (car (mh-seq-to-msgs 'cur))))
  778. (when (and cur (mh-goto-msg cur t t))
  779. (beginning-of-line)
  780. (when (looking-at mh-scan-good-msg-regexp)
  781. (mh-notate nil mh-note-cur mh-cmd-note))
  782. (setq mh-arrow-marker (set-marker mh-arrow-marker (point)))
  783. (setq overlay-arrow-position mh-arrow-marker))))
  784. ;;;###mh-autoload
  785. (defun mh-remove-cur-notation ()
  786. "Remove old cur notation."
  787. (let ((cur-msg (car (mh-seq-to-msgs 'cur))))
  788. (save-excursion
  789. (when (and cur-msg
  790. (mh-goto-msg cur-msg t t)
  791. (looking-at mh-scan-cur-msg-number-regexp))
  792. (mh-notate nil ? mh-cmd-note)
  793. (setq overlay-arrow-position nil)))))
  794. ;; FIXME? We may want to clear all notations and add one for current-message
  795. ;; and process user sequences.
  796. ;;;###mh-autoload
  797. (defun mh-notate-deleted-and-refiled ()
  798. "Notate messages marked for deletion or refiling.
  799. Messages to be deleted are given by `mh-delete-list' while
  800. messages to be refiled are present in `mh-refile-list'."
  801. (let ((refiled-hash (make-hash-table))
  802. (deleted-hash (make-hash-table)))
  803. (dolist (msg mh-delete-list)
  804. (setf (gethash msg deleted-hash) t))
  805. (dolist (dest-msg-list mh-refile-list)
  806. (dolist (msg (cdr dest-msg-list))
  807. (setf (gethash msg refiled-hash) t)))
  808. (mh-iterate-on-messages-in-region msg (point-min) (point-max)
  809. (cond ((gethash msg refiled-hash)
  810. (mh-notate nil mh-note-refiled mh-cmd-note))
  811. ((gethash msg deleted-hash)
  812. (mh-notate nil mh-note-deleted mh-cmd-note))))))
  813. ;;;###mh-autoload
  814. (defun mh-notate-user-sequences (&optional range)
  815. "Mark user-defined sequences in RANGE.
  816. Check the documentation of `mh-interactive-range' to see how
  817. RANGE is read in interactive use; if nil all messages are
  818. notated."
  819. (unless range
  820. (setq range (cons (point-min) (point-max))))
  821. (let ((seqs mh-seq-list)
  822. (msg-hash (make-hash-table)))
  823. (dolist (seq seqs)
  824. (dolist (msg (mh-seq-msgs seq))
  825. (push (car seq) (gethash msg msg-hash))))
  826. (mh-iterate-on-range msg range
  827. (loop for seq in (gethash msg msg-hash)
  828. do (mh-add-sequence-notation msg (mh-internal-seq seq))))))
  829. (defun mh-add-sequence-notation (msg internal-seq-flag)
  830. "Add sequence notation to the MSG on the current line.
  831. If INTERNAL-SEQ-FLAG is non-nil, then refontify the scan line if
  832. font-lock is turned on."
  833. (with-mh-folder-updating (t)
  834. (save-excursion
  835. (beginning-of-line)
  836. (if internal-seq-flag
  837. (progn
  838. ;; Change the buffer so that if transient-mark-mode is active
  839. ;; and there is an active region it will get deactivated as in
  840. ;; the case of user sequences.
  841. (mh-notate nil nil mh-cmd-note)
  842. (when font-lock-mode
  843. (font-lock-fontify-region (point) (mh-line-end-position))))
  844. (forward-char (+ mh-cmd-note mh-scan-field-destination-offset))
  845. (let ((stack (gethash msg mh-sequence-notation-history)))
  846. (setf (gethash msg mh-sequence-notation-history)
  847. (cons (char-after) stack)))
  848. (mh-notate nil mh-note-seq
  849. (+ mh-cmd-note mh-scan-field-destination-offset))))))
  850. (defun mh-remove-sequence-notation (msg internal-seq-flag &optional all)
  851. "Remove sequence notation from the MSG on the current line.
  852. If INTERNAL-SEQ-FLAG is non-nil, then `font-lock' was used to
  853. highlight the sequence. In that case, no notation needs to be removed.
  854. Otherwise the effect of inserting `mh-note-seq' needs to be reversed.
  855. If ALL is non-nil, then all sequence marks on the scan line are
  856. removed."
  857. (with-mh-folder-updating (t)
  858. ;; This takes care of internal sequences...
  859. (mh-notate nil nil mh-cmd-note)
  860. (unless internal-seq-flag
  861. ;; ... and this takes care of user sequences.
  862. (let ((stack (gethash msg mh-sequence-notation-history)))
  863. (while (and all (cdr stack))
  864. (setq stack (cdr stack)))
  865. (when stack
  866. (save-excursion
  867. (beginning-of-line)
  868. (forward-char (+ mh-cmd-note mh-scan-field-destination-offset))
  869. (delete-char 1)
  870. (insert (car stack))))
  871. (setf (gethash msg mh-sequence-notation-history) (cdr stack))))))
  872. ;;;###mh-autoload
  873. (defun mh-remove-all-notation ()
  874. "Remove all notations on all scan lines that MH-E introduces."
  875. (save-excursion
  876. (setq overlay-arrow-position nil)
  877. (goto-char (point-min))
  878. (mh-iterate-on-range msg (cons (point-min) (point-max))
  879. (mh-notate nil ? mh-cmd-note)
  880. (mh-remove-sequence-notation msg nil t))
  881. (clrhash mh-sequence-notation-history)))
  882. ;; XXX Unused, delete, or create bind key?
  883. (defun mh-rename-seq (sequence new-name)
  884. "Rename SEQUENCE to have NEW-NAME."
  885. (interactive (list (mh-read-seq "Old" t)
  886. (intern (read-string "New sequence name: "))))
  887. (let ((old-seq (mh-find-seq sequence)))
  888. (or old-seq
  889. (error "Sequence %s does not exist" sequence))
  890. ;; Create new sequence first, since it might raise an error.
  891. (mh-define-sequence new-name (mh-seq-msgs old-seq))
  892. (mh-undefine-sequence sequence (mh-seq-msgs old-seq))
  893. (rplaca old-seq new-name)))
  894. (provide 'mh-seq)
  895. ;; Local Variables:
  896. ;; indent-tabs-mode: nil
  897. ;; sentence-end-double-space: nil
  898. ;; End:
  899. ;;; mh-seq.el ends here