| 1 | ;;; -*- Package: Hemlock; Log: hemlock.log -*-
|
|---|
| 2 | ;;;
|
|---|
| 3 | ;;; **********************************************************************
|
|---|
| 4 | ;;; This code was written as part of the CMU Common Lisp project at
|
|---|
| 5 | ;;; Carnegie Mellon University, and has been placed in the public domain.
|
|---|
| 6 | ;;;
|
|---|
| 7 | #+CMU (ext:file-comment
|
|---|
| 8 | "$Header$")
|
|---|
| 9 | ;;;
|
|---|
| 10 | ;;; **********************************************************************
|
|---|
| 11 | ;;;
|
|---|
| 12 | ;;; This is a mailer interface to MH.
|
|---|
| 13 | ;;;
|
|---|
| 14 | ;;; Written by Bill Chiles.
|
|---|
| 15 | ;;;
|
|---|
| 16 |
|
|---|
| 17 | (in-package :hemlock)
|
|---|
| 18 |
|
|---|
| 19 |
|
|---|
| 20 | |
|---|
| 21 |
|
|---|
| 22 | ;;;; General stuff.
|
|---|
| 23 |
|
|---|
| 24 | (defvar *new-mail-buffer* nil)
|
|---|
| 25 |
|
|---|
| 26 | (defvar *mh-utility-bit-bucket* (make-broadcast-stream))
|
|---|
| 27 |
|
|---|
| 28 |
|
|---|
| 29 | (defattribute "Digit"
|
|---|
| 30 | "This is just a (mod 2) attribute for base 10 digit characters.")
|
|---|
| 31 | ;;;
|
|---|
| 32 | (dotimes (i 10)
|
|---|
| 33 | (setf (character-attribute :digit (digit-char i)) 1))
|
|---|
| 34 |
|
|---|
| 35 |
|
|---|
| 36 | (defmacro number-string (number)
|
|---|
| 37 | `(let ((*print-base* 10))
|
|---|
| 38 | (prin1-to-string ,number)))
|
|---|
| 39 |
|
|---|
| 40 |
|
|---|
| 41 | (defmacro do-headers-buffers ((buffer-var folder &optional hinfo-var)
|
|---|
| 42 | &rest forms)
|
|---|
| 43 | "The Forms are evaluated with Buffer-Var bound to each buffer containing
|
|---|
| 44 | headers lines for folder. Optionally Hinfo-Var is bound to the
|
|---|
| 45 | headers-information structure."
|
|---|
| 46 | (let ((folder-var (gensym))
|
|---|
| 47 | (hinfo (gensym)))
|
|---|
| 48 | `(let ((,folder-var ,folder))
|
|---|
| 49 | (declare (simple-string ,folder-var))
|
|---|
| 50 | (dolist (,buffer-var *buffer-list*)
|
|---|
| 51 | (when (hemlock-bound-p 'headers-information :buffer ,buffer-var)
|
|---|
| 52 | (let ((,hinfo (variable-value 'headers-information
|
|---|
| 53 | :buffer ,buffer-var)))
|
|---|
| 54 | (when (string= (the simple-string (headers-info-folder ,hinfo))
|
|---|
| 55 | ,folder-var)
|
|---|
| 56 | ,@(if hinfo-var
|
|---|
| 57 | `((let ((,hinfo-var ,hinfo))
|
|---|
| 58 | ,@forms))
|
|---|
| 59 | forms))))))))
|
|---|
| 60 |
|
|---|
| 61 | (defmacro do-headers-lines ((hbuffer &key line-var mark-var) &rest forms)
|
|---|
| 62 | "Forms are evaluated for each non-blank line. When supplied Line-Var and
|
|---|
| 63 | Mark-Var are to the line and a :left-inserting mark at the beginning of the
|
|---|
| 64 | line. This works with DELETE-HEADERS-BUFFER-LINE, but one should be careful
|
|---|
| 65 | using this to modify the hbuffer."
|
|---|
| 66 | (let ((line-var (or line-var (gensym)))
|
|---|
| 67 | (mark-var (or mark-var (gensym)))
|
|---|
| 68 | (id (gensym)))
|
|---|
| 69 | `(with-mark ((,mark-var (buffer-point ,hbuffer) :left-inserting))
|
|---|
| 70 | (buffer-start ,mark-var)
|
|---|
| 71 | (loop
|
|---|
| 72 | (let* ((,line-var (mark-line ,mark-var))
|
|---|
| 73 | (,id (line-message-id ,line-var)))
|
|---|
| 74 | (unless (blank-line-p ,line-var)
|
|---|
| 75 | ,@forms)
|
|---|
| 76 | (if (or (not (eq ,line-var (mark-line ,mark-var)))
|
|---|
| 77 | (string/= ,id (line-message-id ,line-var)))
|
|---|
| 78 | (line-start ,mark-var)
|
|---|
| 79 | (unless (line-offset ,mark-var 1 0) (return))))))))
|
|---|
| 80 |
|
|---|
| 81 | (defmacro with-headers-mark ((mark-var hbuffer msg) &rest forms)
|
|---|
| 82 | "Forms are executed with Mark-Var bound to a :left-inserting mark at the
|
|---|
| 83 | beginning of the headers line representing msg. If no such line exists,
|
|---|
| 84 | no execution occurs."
|
|---|
| 85 | (let ((line (gensym)))
|
|---|
| 86 | `(do-headers-lines (,hbuffer :line-var ,line :mark-var ,mark-var)
|
|---|
| 87 | (when (string= (the simple-string (line-message-id ,line))
|
|---|
| 88 | (the simple-string ,msg))
|
|---|
| 89 | ,@forms
|
|---|
| 90 | (return)))))
|
|---|
| 91 |
|
|---|
| 92 |
|
|---|
| 93 | |
|---|
| 94 |
|
|---|
| 95 | ;;;; Headers Mode.
|
|---|
| 96 |
|
|---|
| 97 | (defmode "Headers" :major-p t)
|
|---|
| 98 |
|
|---|
| 99 | (defhvar "Headers Information"
|
|---|
| 100 | "This holds the information about the current headers buffer."
|
|---|
| 101 | :value nil)
|
|---|
| 102 |
|
|---|
| 103 | (defstruct (headers-info (:print-function print-headers-info))
|
|---|
| 104 | buffer ;Buffer for these headers.
|
|---|
| 105 | folder ;String name of folder with leading MH "+".
|
|---|
| 106 | msg-seq ;MH sequence of messages in buffer.
|
|---|
| 107 | msg-strings ;List of strings representing msg-seq.
|
|---|
| 108 | other-msg-bufs ;List of message buffers referencing this headers buffer.
|
|---|
| 109 | draft-bufs ;List of draft buffers referencing this headers buffer.
|
|---|
| 110 | msg-buffer)
|
|---|
| 111 |
|
|---|
| 112 | (defun print-headers-info (obj str n)
|
|---|
| 113 | (declare (ignore n))
|
|---|
| 114 | (format str "#<Headers Info ~S>" (headers-info-folder obj)))
|
|---|
| 115 |
|
|---|
| 116 | (defmacro line-message-deleted (line)
|
|---|
| 117 | `(getf (line-plist ,line) 'mh-msg-deleted))
|
|---|
| 118 |
|
|---|
| 119 | (defmacro line-message-id (line)
|
|---|
| 120 | `(getf (line-plist ,line) 'mh-msg-id))
|
|---|
| 121 |
|
|---|
| 122 | (defun headers-current-message (hinfo)
|
|---|
| 123 | (let* ((point (buffer-point (headers-info-buffer hinfo)))
|
|---|
| 124 | (line (mark-line point)))
|
|---|
| 125 | (unless (blank-line-p line)
|
|---|
| 126 | (values (line-message-id line)
|
|---|
| 127 | (copy-mark point)))))
|
|---|
| 128 |
|
|---|
| 129 | (defcommand "Message Headers" (p)
|
|---|
| 130 | "Prompts for a folder and messages, displaying headers in a buffer in the
|
|---|
| 131 | current window. With an argument, prompt for a pick expression."
|
|---|
| 132 | "Show some headers."
|
|---|
| 133 | (let ((folder (prompt-for-folder)))
|
|---|
| 134 | (new-message-headers
|
|---|
| 135 | folder
|
|---|
| 136 | (prompt-for-message :prompt (if p
|
|---|
| 137 | "MH messages to pick from: "
|
|---|
| 138 | "MH messages: ")
|
|---|
| 139 | :folder folder
|
|---|
| 140 | :messages "all")
|
|---|
| 141 | p)))
|
|---|
| 142 |
|
|---|
| 143 | (defcommand "Pick Headers" (p)
|
|---|
| 144 | "Further narrow the selection of this folders headers.
|
|---|
| 145 | Prompts for a pick expression to pick over the headers in the current
|
|---|
| 146 | buffer. Entering an empty expression displays all the headers for that
|
|---|
| 147 | folder."
|
|---|
| 148 | "Prompts for a pick expression to pick over the headers in the current
|
|---|
| 149 | buffer."
|
|---|
| 150 | (declare (ignore p))
|
|---|
| 151 | (let ((hinfo (value headers-information)))
|
|---|
| 152 | (unless hinfo
|
|---|
| 153 | (editor-error "Pick Headers only works in a headers buffer."))
|
|---|
| 154 | (pick-message-headers hinfo)))
|
|---|
| 155 |
|
|---|
| 156 | ;;; PICK-MESSAGE-HEADERS picks messages from info's messages based on an
|
|---|
| 157 | ;;; expression provided by the user. If the expression is empty, we do
|
|---|
| 158 | ;;; headers on all the messages in folder. The buffer's name is changed to
|
|---|
| 159 | ;;; reflect the messages picked over and the expression used.
|
|---|
| 160 | ;;;
|
|---|
| 161 | (defun pick-message-headers (hinfo)
|
|---|
| 162 | (let ((folder (headers-info-folder hinfo))
|
|---|
| 163 | (msgs (headers-info-msg-strings hinfo)))
|
|---|
| 164 | (multiple-value-bind (pick user-pick)
|
|---|
| 165 | (prompt-for-pick-expression)
|
|---|
| 166 | (let* ((hbuffer (headers-info-buffer hinfo))
|
|---|
| 167 | (new-mail-buf-p (eq hbuffer *new-mail-buffer*))
|
|---|
| 168 | (region (cond (pick
|
|---|
| 169 | (message-headers-to-region
|
|---|
| 170 | folder (pick-messages folder msgs pick)))
|
|---|
| 171 | (new-mail-buf-p
|
|---|
| 172 | (maybe-get-new-mail-msg-hdrs folder))
|
|---|
| 173 | (t (message-headers-to-region folder
|
|---|
| 174 | (list "all"))))))
|
|---|
| 175 | (with-writable-buffer (hbuffer)
|
|---|
| 176 | (revamp-headers-buffer hbuffer hinfo)
|
|---|
| 177 | (when region (insert-message-headers hbuffer hinfo region)))
|
|---|
| 178 | (setf (buffer-modified hbuffer) nil)
|
|---|
| 179 | (buffer-start (buffer-point hbuffer))
|
|---|
| 180 | (setf (buffer-name hbuffer)
|
|---|
| 181 | (cond (pick (format nil "Headers ~A ~A ~A" folder msgs user-pick))
|
|---|
| 182 | (new-mail-buf-p (format nil "Unseen Headers ~A" folder))
|
|---|
| 183 | (t (format nil "Headers ~A (all)" folder))))))))
|
|---|
| 184 |
|
|---|
| 185 | ;;; NEW-MESSAGE-HEADERS picks over msgs if pickp is non-nil, or it just scans
|
|---|
| 186 | ;;; msgs. It is important to pick and get the message headers region before
|
|---|
| 187 | ;;; making the buffer and info structures since PICK-MESSAGES and
|
|---|
| 188 | ;;; MESSAGE-HEADERS-TO-REGION will call EDITOR-ERROR if they fail. The buffer
|
|---|
| 189 | ;;; name is chosen based on folder, msgs, and an optional pick expression.
|
|---|
| 190 | ;;;
|
|---|
| 191 | (defun new-message-headers (folder msgs &optional pickp)
|
|---|
| 192 | (multiple-value-bind (pick-exp user-pick)
|
|---|
| 193 | (if pickp (prompt-for-pick-expression))
|
|---|
| 194 | (let* ((pick (if pick-exp (pick-messages folder msgs pick-exp)))
|
|---|
| 195 | (region (message-headers-to-region folder (or pick msgs)))
|
|---|
| 196 | (hbuffer (maybe-make-mh-buffer (format nil "Headers ~A ~A~:[~; ~S~]"
|
|---|
| 197 | folder msgs pick user-pick)
|
|---|
| 198 | :headers))
|
|---|
| 199 | (hinfo (make-headers-info :buffer hbuffer :folder folder)))
|
|---|
| 200 | (insert-message-headers hbuffer hinfo region)
|
|---|
| 201 | (defhvar "Headers Information"
|
|---|
| 202 | "This holds the information about the current headers buffer."
|
|---|
| 203 | :value hinfo :buffer hbuffer)
|
|---|
| 204 | (setf (buffer-modified hbuffer) nil)
|
|---|
| 205 | (setf (buffer-writable hbuffer) nil)
|
|---|
| 206 | (buffer-start (buffer-point hbuffer))
|
|---|
| 207 | (change-to-buffer hbuffer))))
|
|---|
| 208 |
|
|---|
| 209 | (defhvar "MH Scan Line Form"
|
|---|
| 210 | "This is a pathname of a file containing an MH format expression for headers
|
|---|
| 211 | lines."
|
|---|
| 212 | :value (pathname "library:mh-scan"))
|
|---|
| 213 |
|
|---|
| 214 | ;;; MESSAGE-HEADERS-TO-REGION uses the MH "scan" utility output headers into
|
|---|
| 215 | ;;; buffer for folder and msgs.
|
|---|
| 216 | ;;;
|
|---|
| 217 | ;;; (value fill-column) should really be done as if the buffer were current,
|
|---|
| 218 | ;;; but Hemlock doesn't let you do this without the buffer being current.
|
|---|
| 219 | ;;;
|
|---|
| 220 | (defun message-headers-to-region (folder msgs &optional width)
|
|---|
| 221 | (let ((region (make-empty-region)))
|
|---|
| 222 | (with-output-to-mark (*standard-output* (region-end region) :full)
|
|---|
| 223 | (mh "scan"
|
|---|
| 224 | `(,folder ,@msgs
|
|---|
| 225 | "-form" ,(namestring (truename (value mh-scan-line-form)))
|
|---|
| 226 | "-width" ,(number-string (or width (value fill-column)))
|
|---|
| 227 | "-noheader")))
|
|---|
| 228 | region))
|
|---|
| 229 |
|
|---|
| 230 | (defun insert-message-headers (hbuffer hinfo region)
|
|---|
| 231 | (ninsert-region (buffer-point hbuffer) region)
|
|---|
| 232 | (let ((seq (set-message-headers-ids hbuffer :return-seq)))
|
|---|
| 233 | (setf (headers-info-msg-seq hinfo) seq)
|
|---|
| 234 | (setf (headers-info-msg-strings hinfo) (mh-sequence-strings seq)))
|
|---|
| 235 | (when (value virtual-message-deletion)
|
|---|
| 236 | (note-deleted-headers hbuffer
|
|---|
| 237 | (mh-sequence-list (headers-info-folder hinfo)
|
|---|
| 238 | "hemlockdeleted"))))
|
|---|
| 239 |
|
|---|
| 240 | (defun set-message-headers-ids (hbuffer &optional return-seq)
|
|---|
| 241 | (let ((msgs nil))
|
|---|
| 242 | (do-headers-lines (hbuffer :line-var line)
|
|---|
| 243 | (let* ((line-str (line-string line))
|
|---|
| 244 | (num (parse-integer line-str :junk-allowed t)))
|
|---|
| 245 | (declare (simple-string line-str))
|
|---|
| 246 | (unless num
|
|---|
| 247 | (editor-error "MH scan lines must contain the message id as the ~
|
|---|
| 248 | first thing on the line for the Hemlock interface."))
|
|---|
| 249 | (setf (line-message-id line) (number-string num))
|
|---|
| 250 | (when return-seq (setf msgs (mh-sequence-insert num msgs)))))
|
|---|
| 251 | msgs))
|
|---|
| 252 |
|
|---|
| 253 | (defun note-deleted-headers (hbuffer deleted-seq)
|
|---|
| 254 | (when deleted-seq
|
|---|
| 255 | (do-headers-lines (hbuffer :line-var line :mark-var hmark)
|
|---|
| 256 | (if (mh-sequence-member-p (line-message-id line) deleted-seq)
|
|---|
| 257 | (note-deleted-message-at-mark hmark)
|
|---|
| 258 | (setf (line-message-deleted line) nil)))))
|
|---|
| 259 |
|
|---|
| 260 | ;;; PICK-MESSAGES -- Internal Interface.
|
|---|
| 261 | ;;;
|
|---|
| 262 | ;;; This takes a folder (with a + in front of the name), messages to pick
|
|---|
| 263 | ;;; over, and an MH pick expression (in the form returned by
|
|---|
| 264 | ;;; PROMPT-FOR-PICK-EXPRESSION). Sequence is an MH sequence to set to exactly
|
|---|
| 265 | ;;; those messages chosen by the pick when zerop is non-nil; when zerop is nil,
|
|---|
| 266 | ;;; pick adds the messages to the sequence along with whatever messages were
|
|---|
| 267 | ;;; already in the sequence. This returns a list of message specifications.
|
|---|
| 268 | ;;;
|
|---|
| 269 | (defun pick-messages (folder msgs expression &optional sequence (zerop t))
|
|---|
| 270 | (let* ((temp (with-output-to-string (*standard-output*)
|
|---|
| 271 | (unless
|
|---|
| 272 | ;; If someone bound *signal-mh-errors* to nil around this
|
|---|
| 273 | ;; function, MH pick outputs bogus messages (for example,
|
|---|
| 274 | ;; "0"), and MH would return without calling EDITOR-ERROR.
|
|---|
| 275 | (mh "pick" `(,folder
|
|---|
| 276 | ,@msgs
|
|---|
| 277 | ,@(if sequence `("-sequence" ,sequence))
|
|---|
| 278 | ,@(if zerop '("-zero"))
|
|---|
| 279 | "-list" ; -list must follow -sequence.
|
|---|
| 280 | ,@expression))
|
|---|
| 281 | (return-from pick-messages nil))))
|
|---|
| 282 | (len (length temp))
|
|---|
| 283 | (start 0)
|
|---|
| 284 | (result nil))
|
|---|
| 285 | (declare (simple-string temp))
|
|---|
| 286 | (loop
|
|---|
| 287 | (let ((end (position #\newline temp :start start :test #'char=)))
|
|---|
| 288 | (cond ((not end)
|
|---|
| 289 | (return (nreverse (cons (subseq temp start) result))))
|
|---|
| 290 | ((= start end)
|
|---|
| 291 | (return (nreverse result)))
|
|---|
| 292 | (t
|
|---|
| 293 | (push (subseq temp start end) result)
|
|---|
| 294 | (when (>= (setf start (1+ end)) len)
|
|---|
| 295 | (return (nreverse result)))))))))
|
|---|
| 296 |
|
|---|
| 297 |
|
|---|
| 298 | (defcommand "Delete Headers Buffer and Message Buffers" (p &optional buffer)
|
|---|
| 299 | "Prompts for a headers buffer to delete along with its associated message
|
|---|
| 300 | buffers. Any associated draft buffers are left alone, but their associated
|
|---|
| 301 | message buffers will be deleted."
|
|---|
| 302 | "Deletes the current headers buffer and its associated message buffers."
|
|---|
| 303 | (declare (ignore p))
|
|---|
| 304 | (let* ((default (cond ((value headers-information) (current-buffer))
|
|---|
| 305 | ((value message-information) (value headers-buffer))))
|
|---|
| 306 | (buffer (or buffer
|
|---|
| 307 | (prompt-for-buffer :default default
|
|---|
| 308 | :default-string
|
|---|
| 309 | (if default (buffer-name default))))))
|
|---|
| 310 | (unless (hemlock-bound-p 'headers-information :buffer buffer)
|
|---|
| 311 | (editor-error "Not a headers buffer -- ~A" (buffer-name buffer)))
|
|---|
| 312 | (let* ((hinfo (variable-value 'headers-information :buffer buffer))
|
|---|
| 313 | ;; Copy list since buffer cleanup hook is destructive.
|
|---|
| 314 | (other-bufs (copy-list (headers-info-other-msg-bufs hinfo)))
|
|---|
| 315 | (msg-buf (headers-info-msg-buffer hinfo)))
|
|---|
| 316 | (when msg-buf (delete-buffer-if-possible msg-buf))
|
|---|
| 317 | (dolist (b other-bufs) (delete-buffer-if-possible b))
|
|---|
| 318 | (delete-buffer-if-possible (headers-info-buffer hinfo)))))
|
|---|
| 319 |
|
|---|
| 320 | (defhvar "Expunge Messages Confirm"
|
|---|
| 321 | "When set (the default), \"Expunge Messages\" and \"Quit Headers\" will ask
|
|---|
| 322 | for confirmation before expunging messages and packing the folder's message
|
|---|
| 323 | id's."
|
|---|
| 324 | :value t)
|
|---|
| 325 |
|
|---|
| 326 | (defhvar "Temporary Draft Folder"
|
|---|
| 327 | "This is the folder name where MH fcc: messages are kept that are intended
|
|---|
| 328 | to be deleted and expunged when messages are expunged for any other
|
|---|
| 329 | folder -- \"Expunge Messages\" and \"Quit Headers\"."
|
|---|
| 330 | :value nil)
|
|---|
| 331 |
|
|---|
| 332 | ;;; "Quit Headers" doesn't expunge or compact unless there is a deleted
|
|---|
| 333 | ;;; sequence. This collapses other headers buffers into the same folder
|
|---|
| 334 | ;;; differently than "Expunge Messages" since the latter assumes there will
|
|---|
| 335 | ;;; always be one remaining headers buffer. This command folds all headers
|
|---|
| 336 | ;;; buffers into the folder that are not the current buffer or the new mail
|
|---|
| 337 | ;;; buffer into one buffer. When the current buffer is the new mail buffer
|
|---|
| 338 | ;;; we do not check for more unseen headers since we are about to delete
|
|---|
| 339 | ;;; the buffer anyway. The other headers buffers must be deleted before
|
|---|
| 340 | ;;; making the new one due to aliasing the buffer structure and
|
|---|
| 341 | ;;; MAYBE-MAKE-MH-BUFFER.
|
|---|
| 342 | ;;;
|
|---|
| 343 | (defcommand "Quit Headers" (p)
|
|---|
| 344 | "Quit headers buffer possibly expunging deleted messages.
|
|---|
| 345 | This affects the current headers buffer. When there are deleted messages
|
|---|
| 346 | the user is asked for confirmation on expunging the messages and packing the
|
|---|
| 347 | folder's message id's. Then the buffer and all its associated message
|
|---|
| 348 | buffers are deleted. Setting \"Quit Headers Confirm\" to nil inhibits
|
|---|
| 349 | prompting. When \"Temporary Draft Folder\" is bound, this folder's messages
|
|---|
| 350 | are deleted and expunged."
|
|---|
| 351 | "This affects the current headers buffer. When there are deleted messages
|
|---|
| 352 | the user is asked for confirmation on expunging the messages and packing
|
|---|
| 353 | the folder. Then the buffer and all its associated message buffers are
|
|---|
| 354 | deleted."
|
|---|
| 355 | (declare (ignore p))
|
|---|
| 356 | (let* ((hinfo (value headers-information))
|
|---|
| 357 | (minfo (value message-information))
|
|---|
| 358 | (hdrs-buf (cond (hinfo (current-buffer))
|
|---|
| 359 | (minfo (value headers-buffer)))))
|
|---|
| 360 | (unless hdrs-buf
|
|---|
| 361 | (editor-error "Not in or associated with any headers buffer."))
|
|---|
| 362 | (let* ((folder (cond (hinfo (headers-info-folder hinfo))
|
|---|
| 363 | (minfo (message-info-folder minfo))))
|
|---|
| 364 | (deleted-seq (mh-sequence-list folder "hemlockdeleted")))
|
|---|
| 365 | (when (and deleted-seq
|
|---|
| 366 | (or (not (value expunge-messages-confirm))
|
|---|
| 367 | (prompt-for-y-or-n
|
|---|
| 368 | :prompt (list "Expunge messages and pack folder ~A? "
|
|---|
| 369 | folder)
|
|---|
| 370 | :default t
|
|---|
| 371 | :default-string "Y")))
|
|---|
| 372 | (message "Deleting messages ...")
|
|---|
| 373 | (mh "rmm" (list folder "hemlockdeleted"))
|
|---|
| 374 | (let ((*standard-output* *mh-utility-bit-bucket*))
|
|---|
| 375 | (message "Compacting folder ...")
|
|---|
| 376 | (mh "folder" (list folder "-fast" "-pack")))
|
|---|
| 377 | (message "Maintaining consistency ...")
|
|---|
| 378 | (let (hbufs)
|
|---|
| 379 | (declare (list hbufs))
|
|---|
| 380 | (do-headers-buffers (b folder)
|
|---|
| 381 | (unless (or (eq b hdrs-buf) (eq b *new-mail-buffer*))
|
|---|
| 382 | (push b hbufs)))
|
|---|
| 383 | (dolist (b hbufs)
|
|---|
| 384 | (delete-headers-buffer-and-message-buffers-command nil b))
|
|---|
| 385 | (when hbufs
|
|---|
| 386 | (new-message-headers folder (list "all"))))
|
|---|
| 387 | (expunge-messages-fix-draft-buffers folder)
|
|---|
| 388 | (unless (eq hdrs-buf *new-mail-buffer*)
|
|---|
| 389 | (expunge-messages-fix-unseen-headers folder))
|
|---|
| 390 | (delete-and-expunge-temp-drafts)))
|
|---|
| 391 | (delete-headers-buffer-and-message-buffers-command nil hdrs-buf)))
|
|---|
| 392 |
|
|---|
| 393 | ;;; DELETE-AND-EXPUNGE-TEMP-DRAFTS deletes all the messages in the
|
|---|
| 394 | ;;; temporary draft folder if there is one defined. Any headers buffers
|
|---|
| 395 | ;;; into this folder are deleted with their message buffers. We have to
|
|---|
| 396 | ;;; create a list of buffers to delete since buffer deletion destructively
|
|---|
| 397 | ;;; modifies the same list DO-HEADERS-BUFFERS uses. "rmm" is run without
|
|---|
| 398 | ;;; error reporting since it signals an error if there are no messages to
|
|---|
| 399 | ;;; delete. This function must return; for example, "Quit Headers" would
|
|---|
| 400 | ;;; not complete successfully if this ended up calling EDITOR-ERROR.
|
|---|
| 401 | ;;;
|
|---|
| 402 | (defun delete-and-expunge-temp-drafts ()
|
|---|
| 403 | (let ((temp-draft-folder (value temporary-draft-folder)))
|
|---|
| 404 | (when temp-draft-folder
|
|---|
| 405 | (setf temp-draft-folder (coerce-folder-name temp-draft-folder))
|
|---|
| 406 | (message "Deleting and expunging temporary drafts ...")
|
|---|
| 407 | (when (mh "rmm" (list temp-draft-folder "all") :errorp nil)
|
|---|
| 408 | (let (hdrs)
|
|---|
| 409 | (declare (list hdrs))
|
|---|
| 410 | (do-headers-buffers (b temp-draft-folder)
|
|---|
| 411 | (push b hdrs))
|
|---|
| 412 | (dolist (b hdrs)
|
|---|
| 413 | (delete-headers-buffer-and-message-buffers-command nil b)))))))
|
|---|
| 414 |
|
|---|
| 415 |
|
|---|
| 416 | |
|---|
| 417 |
|
|---|
| 418 | ;;;; Message Mode.
|
|---|
| 419 |
|
|---|
| 420 | (defmode "Message" :major-p t)
|
|---|
| 421 |
|
|---|
| 422 | (defhvar "Message Information"
|
|---|
| 423 | "This holds the information about the current message buffer."
|
|---|
| 424 | :value nil)
|
|---|
| 425 |
|
|---|
| 426 | (defstruct message/draft-info
|
|---|
| 427 | headers-mark) ;Mark pointing to a headers line in a headers buffer.
|
|---|
| 428 |
|
|---|
| 429 | (defstruct (message-info (:include message/draft-info)
|
|---|
| 430 | (:print-function print-message-info))
|
|---|
| 431 | folder ;String name of folder with leading MH "+".
|
|---|
| 432 | msgs ;List of strings representing messages to be shown.
|
|---|
| 433 | draft-buf ;Possible draft buffer reference.
|
|---|
| 434 | keep) ;Whether message buffer may be re-used.
|
|---|
| 435 |
|
|---|
| 436 | (defun print-message-info (obj str n)
|
|---|
| 437 | (declare (ignore n))
|
|---|
| 438 | (format str "#<Message Info ~S ~S>"
|
|---|
| 439 | (message-info-folder obj) (message-info-msgs obj)))
|
|---|
| 440 |
|
|---|
| 441 |
|
|---|
| 442 | (defcommand "Next Message" (p)
|
|---|
| 443 | "Show the next message.
|
|---|
| 444 | When in a message buffer, shows the next message in the associated headers
|
|---|
| 445 | buffer. When in a headers buffer, moves point down a line and shows that
|
|---|
| 446 | message."
|
|---|
| 447 | "When in a message buffer, shows the next message in the associated headers
|
|---|
| 448 | buffer. When in a headers buffer, moves point down a line and shows that
|
|---|
| 449 | message."
|
|---|
| 450 | (declare (ignore p))
|
|---|
| 451 | (show-message-offset 1))
|
|---|
| 452 |
|
|---|
| 453 | (defcommand "Previous Message" (p)
|
|---|
| 454 | "Show the previous message.
|
|---|
| 455 | When in a message buffer, shows the previous message in the associated
|
|---|
| 456 | headers buffer. When in a headers buffer, moves point up a line and shows
|
|---|
| 457 | that message."
|
|---|
| 458 | "When in a message buffer, shows the previous message in the associated
|
|---|
| 459 | headers buffer. When in a headers buffer, moves point up a line and
|
|---|
| 460 | shows that message."
|
|---|
| 461 | (declare (ignore p))
|
|---|
| 462 | (show-message-offset -1))
|
|---|
| 463 |
|
|---|
| 464 | (defcommand "Next Undeleted Message" (p)
|
|---|
| 465 | "Show the next undeleted message.
|
|---|
| 466 | When in a message buffer, shows the next undeleted message in the associated
|
|---|
| 467 | headers buffer. When in a headers buffer, moves point down to a line
|
|---|
| 468 | without a deleted message and shows that message."
|
|---|
| 469 | "When in a message buffer, shows the next undeleted message in the associated
|
|---|
| 470 | headers buffer. When in a headers buffer, moves point down to a line without
|
|---|
| 471 | a deleted message and shows that message."
|
|---|
| 472 | (declare (ignore p))
|
|---|
| 473 | (show-message-offset 1 :undeleted))
|
|---|
| 474 |
|
|---|
| 475 | (defcommand "Previous Undeleted Message" (p)
|
|---|
| 476 | "Show the previous undeleted message.
|
|---|
| 477 | When in a message buffer, shows the previous undeleted message in the
|
|---|
| 478 | associated headers buffer. When in a headers buffer, moves point up a line
|
|---|
| 479 | without a deleted message and shows that message."
|
|---|
| 480 | "When in a message buffer, shows the previous undeleted message in the
|
|---|
| 481 | associated headers buffer. When in a headers buffer, moves point up a line
|
|---|
| 482 | without a deleted message and shows that message."
|
|---|
| 483 | (declare (ignore p))
|
|---|
| 484 | (show-message-offset -1 :undeleted))
|
|---|
| 485 |
|
|---|
| 486 | (defun show-message-offset (offset &optional undeleted)
|
|---|
| 487 | (let ((minfo (value message-information)))
|
|---|
| 488 | (cond
|
|---|
| 489 | ((not minfo)
|
|---|
| 490 | (let ((hinfo (value headers-information)))
|
|---|
| 491 | (unless hinfo (editor-error "Not in a message or headers buffer."))
|
|---|
| 492 | (show-message-offset-hdrs-buf hinfo offset undeleted)))
|
|---|
| 493 | ((message-info-keep minfo)
|
|---|
| 494 | (let ((hbuf (value headers-buffer)))
|
|---|
| 495 | (unless hbuf (editor-error "Not associated with a headers buffer."))
|
|---|
| 496 | (let ((hinfo (variable-value 'headers-information :buffer hbuf))
|
|---|
| 497 | (point (buffer-point hbuf)))
|
|---|
| 498 | (move-mark point (message-info-headers-mark minfo))
|
|---|
| 499 | (show-message-offset-hdrs-buf hinfo offset undeleted))))
|
|---|
| 500 | (t
|
|---|
| 501 | (show-message-offset-msg-buf minfo offset undeleted)))))
|
|---|
| 502 |
|
|---|
| 503 | (defun show-message-offset-hdrs-buf (hinfo offset undeleted)
|
|---|
| 504 | (unless hinfo (editor-error "Not in a message or headers buffer."))
|
|---|
| 505 | (unless (show-message-offset-mark (buffer-point (headers-info-buffer hinfo))
|
|---|
| 506 | offset undeleted)
|
|---|
| 507 | (editor-error "No ~:[previous~;next~] ~:[~;undeleted ~]message."
|
|---|
| 508 | (plusp offset) undeleted))
|
|---|
| 509 | (show-headers-message hinfo))
|
|---|
| 510 |
|
|---|
| 511 | (defun show-message-offset-msg-buf (minfo offset undeleted)
|
|---|
| 512 | (let ((msg-mark (message-info-headers-mark minfo)))
|
|---|
| 513 | (unless msg-mark (editor-error "Not associated with a headers buffer."))
|
|---|
| 514 | (unless (show-message-offset-mark msg-mark offset undeleted)
|
|---|
| 515 | (let ((hbuf (value headers-buffer))
|
|---|
| 516 | (mbuf (current-buffer)))
|
|---|
| 517 | (setf (current-buffer) hbuf)
|
|---|
| 518 | (setf (window-buffer (current-window)) hbuf)
|
|---|
| 519 | (delete-buffer-if-possible mbuf))
|
|---|
| 520 | (editor-error "No ~:[previous~;next~] ~:[~;undeleted ~]message."
|
|---|
| 521 | (plusp offset) undeleted))
|
|---|
| 522 | (move-mark (buffer-point (line-buffer (mark-line msg-mark))) msg-mark)
|
|---|
| 523 | (let* ((next-msg (line-message-id (mark-line msg-mark)))
|
|---|
| 524 | (folder (message-info-folder minfo))
|
|---|
| 525 | (mbuffer (current-buffer)))
|
|---|
| 526 | (with-writable-buffer (mbuffer)
|
|---|
| 527 | (delete-region (buffer-region mbuffer))
|
|---|
| 528 | (setf (buffer-name mbuffer) (get-storable-msg-buf-name folder next-msg))
|
|---|
| 529 | (setf (message-info-msgs minfo) next-msg)
|
|---|
| 530 | (read-mh-file (merge-pathnames next-msg
|
|---|
| 531 | (merge-relative-pathnames
|
|---|
| 532 | (strip-folder-name folder)
|
|---|
| 533 | (mh-directory-pathname)))
|
|---|
| 534 | mbuffer)
|
|---|
| 535 | (let ((unseen-seq (mh-profile-component "unseen-sequence")))
|
|---|
| 536 | (when unseen-seq
|
|---|
| 537 | (mark-one-message folder next-msg unseen-seq :delete))))))
|
|---|
| 538 | (let ((dbuffer (message-info-draft-buf minfo)))
|
|---|
| 539 | (when dbuffer
|
|---|
| 540 | (delete-variable 'message-buffer :buffer dbuffer)
|
|---|
| 541 | (setf (message-info-draft-buf minfo) nil))))
|
|---|
| 542 |
|
|---|
| 543 | (defun get-storable-msg-buf-name (folder msg)
|
|---|
| 544 | (let ((name (format nil "Message ~A ~A" folder msg)))
|
|---|
| 545 | (if (not (getstring name *buffer-names*))
|
|---|
| 546 | name
|
|---|
| 547 | (let ((n 2))
|
|---|
| 548 | (loop
|
|---|
| 549 | (setf name (format nil "Message ~A ~A copy ~D" folder msg n))
|
|---|
| 550 | (unless (getstring name *buffer-names*)
|
|---|
| 551 | (return name))
|
|---|
| 552 | (incf n))))))
|
|---|
| 553 |
|
|---|
| 554 | (defun show-message-offset-mark (msg-mark offset undeleted)
|
|---|
| 555 | (with-mark ((temp msg-mark))
|
|---|
| 556 | (let ((winp
|
|---|
| 557 | (cond (undeleted
|
|---|
| 558 | (loop
|
|---|
| 559 | (unless (and (line-offset temp offset 0)
|
|---|
| 560 | (not (blank-line-p (mark-line temp))))
|
|---|
| 561 | (return nil))
|
|---|
| 562 | (unless (line-message-deleted (mark-line temp))
|
|---|
| 563 | (return t))))
|
|---|
| 564 | ((and (line-offset temp offset 0)
|
|---|
| 565 | (not (blank-line-p (mark-line temp)))))
|
|---|
| 566 | (t nil))))
|
|---|
| 567 | (if winp (move-mark msg-mark temp)))))
|
|---|
| 568 |
|
|---|
| 569 |
|
|---|
| 570 | (defcommand "Show Message" (p)
|
|---|
| 571 | "Shows the current message.
|
|---|
| 572 | Prompts for a folder and message(s), displaying this in the current window.
|
|---|
| 573 | When invoked in a headers buffer, shows the message on the current line."
|
|---|
| 574 | "Show a message."
|
|---|
| 575 | (declare (ignore p))
|
|---|
| 576 | (let ((hinfo (value headers-information)))
|
|---|
| 577 | (if hinfo
|
|---|
| 578 | (show-headers-message hinfo)
|
|---|
| 579 | (let ((folder (prompt-for-folder)))
|
|---|
| 580 | (show-prompted-message folder (prompt-for-message :folder folder))))))
|
|---|
| 581 |
|
|---|
| 582 | ;;; SHOW-HEADERS-MESSAGE shows the current message for hinfo. If there is a
|
|---|
| 583 | ;;; main message buffer, clobber it, and we don't have to deal with kept
|
|---|
| 584 | ;;; messages or draft associations since those operations should have moved
|
|---|
| 585 | ;;; the message buffer into the others list. Remove the message from the
|
|---|
| 586 | ;;; unseen sequence, and make sure the message buffer is displayed in some
|
|---|
| 587 | ;;; window.
|
|---|
| 588 | ;;;
|
|---|
| 589 | (defun show-headers-message (hinfo)
|
|---|
| 590 | (multiple-value-bind (cur-msg cur-mark)
|
|---|
| 591 | (headers-current-message hinfo)
|
|---|
| 592 | (unless cur-msg (editor-error "Not on a header line."))
|
|---|
| 593 | (let* ((mbuffer (headers-info-msg-buffer hinfo))
|
|---|
| 594 | (folder (headers-info-folder hinfo))
|
|---|
| 595 | (buf-name (get-storable-msg-buf-name folder cur-msg))
|
|---|
| 596 | (writable nil))
|
|---|
| 597 | (cond (mbuffer
|
|---|
| 598 | (setf (buffer-name mbuffer) buf-name)
|
|---|
| 599 | (setf writable (buffer-writable mbuffer))
|
|---|
| 600 | (setf (buffer-writable mbuffer) t)
|
|---|
| 601 | (delete-region (buffer-region mbuffer))
|
|---|
| 602 | (let ((minfo (variable-value 'message-information :buffer mbuffer)))
|
|---|
| 603 | (move-mark (message-info-headers-mark minfo) cur-mark)
|
|---|
| 604 | (delete-mark cur-mark)
|
|---|
| 605 | (setf (message-info-msgs minfo) cur-msg)))
|
|---|
| 606 | (t (setf mbuffer (maybe-make-mh-buffer buf-name :message))
|
|---|
| 607 | (setf (headers-info-msg-buffer hinfo) mbuffer)
|
|---|
| 608 | (defhvar "Message Information"
|
|---|
| 609 | "This holds the information about the current headers buffer."
|
|---|
| 610 | :value (make-message-info :folder folder
|
|---|
| 611 | :msgs cur-msg
|
|---|
| 612 | :headers-mark cur-mark)
|
|---|
| 613 | :buffer mbuffer)
|
|---|
| 614 | (defhvar "Headers Buffer"
|
|---|
| 615 | "This is bound in message and draft buffers to their
|
|---|
| 616 | associated headers buffer."
|
|---|
| 617 | :value (headers-info-buffer hinfo) :buffer mbuffer)))
|
|---|
| 618 | (read-mh-file (merge-pathnames
|
|---|
| 619 | cur-msg
|
|---|
| 620 | (merge-relative-pathnames (strip-folder-name folder)
|
|---|
| 621 | (mh-directory-pathname)))
|
|---|
| 622 | mbuffer)
|
|---|
| 623 | (setf (buffer-writable mbuffer) writable)
|
|---|
| 624 | (let ((unseen-seq (mh-profile-component "unseen-sequence")))
|
|---|
| 625 | (when unseen-seq (mark-one-message folder cur-msg unseen-seq :delete)))
|
|---|
| 626 | (get-message-buffer-window mbuffer))))
|
|---|
| 627 |
|
|---|
| 628 | ;;; SHOW-PROMPTED-MESSAGE takes an arbitrary message spec and blasts those
|
|---|
| 629 | ;;; messages into a message buffer. First we pick the message to get them
|
|---|
| 630 | ;;; individually specified as normalized message ID's -- all integers and
|
|---|
| 631 | ;;; no funny names such as "last".
|
|---|
| 632 | ;;;
|
|---|
| 633 | (defun show-prompted-message (folder msgs)
|
|---|
| 634 | (let* ((msgs (pick-messages folder msgs nil))
|
|---|
| 635 | (mbuffer (maybe-make-mh-buffer (format nil "Message ~A ~A" folder msgs)
|
|---|
| 636 | :message)))
|
|---|
| 637 | (defhvar "Message Information"
|
|---|
| 638 | "This holds the information about the current headers buffer."
|
|---|
| 639 | :value (make-message-info :folder folder :msgs msgs)
|
|---|
| 640 | :buffer mbuffer)
|
|---|
| 641 | (let ((*standard-output* (make-hemlock-output-stream (buffer-point mbuffer)
|
|---|
| 642 | :full)))
|
|---|
| 643 | (mh "show" `(,folder ,@msgs "-noshowproc" "-noheader"))
|
|---|
| 644 | (setf (buffer-modified mbuffer) nil))
|
|---|
| 645 | (buffer-start (buffer-point mbuffer))
|
|---|
| 646 | (setf (buffer-writable mbuffer) nil)
|
|---|
| 647 | (get-message-buffer-window mbuffer)))
|
|---|
| 648 |
|
|---|
| 649 | ;;; GET-MESSAGE-BUFFER-WINDOW currently just changes to buffer, unless buffer
|
|---|
| 650 | ;;; has any windows, in which case it uses the first one. It could prompt for
|
|---|
| 651 | ;;; a window, split the current window, split the current window or use the
|
|---|
| 652 | ;;; next one if there is one, funcall an Hvar. It could take a couple
|
|---|
| 653 | ;;; arguments to control its behaviour. Whatever.
|
|---|
| 654 | ;;;
|
|---|
| 655 | (defun get-message-buffer-window (mbuffer)
|
|---|
| 656 | (let ((wins (buffer-windows mbuffer)))
|
|---|
| 657 | (cond (wins
|
|---|
| 658 | (setf (current-buffer) mbuffer)
|
|---|
| 659 | (setf (current-window) (car wins)))
|
|---|
| 660 | (t (change-to-buffer mbuffer)))))
|
|---|
| 661 |
|
|---|
| 662 |
|
|---|
| 663 | (defhvar "Scroll Message Showing Next"
|
|---|
| 664 | "When this is set, \"Scroll Message\" shows the next message when the end
|
|---|
| 665 | of the current message is visible."
|
|---|
| 666 | :value t)
|
|---|
| 667 |
|
|---|
| 668 | (defcommand "Scroll Message" (p)
|
|---|
| 669 | "Scroll the current window down through the current message.
|
|---|
| 670 | If the end of the message is visible, then show the next undeleted message
|
|---|
| 671 | if \"Scroll Message Showing Next\" is non-nil."
|
|---|
| 672 | "Scroll the current window down through the current message."
|
|---|
| 673 | (if (and (not p)
|
|---|
| 674 | (displayed-p (buffer-end-mark (current-buffer)) (current-window))
|
|---|
| 675 | (value scroll-message-showing-next))
|
|---|
| 676 | (show-message-offset 1 :undeleted)
|
|---|
| 677 | (scroll-window-down-command p)))
|
|---|
| 678 |
|
|---|
| 679 |
|
|---|
| 680 | (defcommand "Keep Message" (p)
|
|---|
| 681 | "Keeps the current message buffer from being re-used. Also, if the buffer
|
|---|
| 682 | would be deleted due to a draft completion, it will not be."
|
|---|
| 683 | "Keeps the current message buffer from being re-used. Also, if the buffer
|
|---|
| 684 | would be deleted due to a draft completion, it will not be."
|
|---|
| 685 | (declare (ignore p))
|
|---|
| 686 | (let ((minfo (value message-information)))
|
|---|
| 687 | (unless minfo (editor-error "Not in a message buffer."))
|
|---|
| 688 | (let ((hbuf (value headers-buffer)))
|
|---|
| 689 | (when hbuf
|
|---|
| 690 | (let ((mbuf (current-buffer))
|
|---|
| 691 | (hinfo (variable-value 'headers-information :buffer hbuf)))
|
|---|
| 692 | (when (eq (headers-info-msg-buffer hinfo) mbuf)
|
|---|
| 693 | (setf (headers-info-msg-buffer hinfo) nil)
|
|---|
| 694 | (push mbuf (headers-info-other-msg-bufs hinfo))))))
|
|---|
| 695 | (setf (message-info-keep minfo) t)))
|
|---|
| 696 |
|
|---|
| 697 | (defcommand "Edit Message Buffer" (p)
|
|---|
| 698 | "Recursively edit message buffer.
|
|---|
| 699 | Puts the current message buffer into \"Text\" mode allowing modifications in
|
|---|
| 700 | a recursive edit. While in this state, the buffer is associated with the
|
|---|
| 701 | pathname of the message, so saving the file is possible."
|
|---|
| 702 | "Puts the current message buffer into \"Text\" mode allowing modifications in
|
|---|
| 703 | a recursive edit. While in this state, the buffer is associated with the
|
|---|
| 704 | pathname of the message, so saving the file is possible."
|
|---|
| 705 | (declare (ignore p))
|
|---|
| 706 | (let* ((minfo (value message-information)))
|
|---|
| 707 | (unless minfo (editor-error "Not in a message buffer."))
|
|---|
| 708 | (let* ((msgs (message-info-msgs minfo))
|
|---|
| 709 | (mbuf (current-buffer))
|
|---|
| 710 | (mbuf-name (buffer-name mbuf))
|
|---|
| 711 | (writable (buffer-writable mbuf))
|
|---|
| 712 | (abortp t))
|
|---|
| 713 | (when (consp msgs)
|
|---|
| 714 | (editor-error
|
|---|
| 715 | "There appears to be more than one message in this buffer."))
|
|---|
| 716 | (unwind-protect
|
|---|
| 717 | (progn
|
|---|
| 718 | (setf (buffer-writable mbuf) t)
|
|---|
| 719 | (setf (buffer-pathname mbuf)
|
|---|
| 720 | (merge-pathnames
|
|---|
| 721 | msgs
|
|---|
| 722 | (merge-relative-pathnames
|
|---|
| 723 | (strip-folder-name (message-info-folder minfo))
|
|---|
| 724 | (mh-directory-pathname))))
|
|---|
| 725 | (setf (buffer-major-mode mbuf) "Text")
|
|---|
| 726 | (do-recursive-edit)
|
|---|
| 727 | (setf abortp nil))
|
|---|
| 728 | (when (and (not abortp)
|
|---|
| 729 | (buffer-modified mbuf)
|
|---|
| 730 | (prompt-for-y-or-n
|
|---|
| 731 | :prompt "Message buffer modified, save it? "
|
|---|
| 732 | :default t))
|
|---|
| 733 | (save-file-command nil mbuf))
|
|---|
| 734 | (setf (buffer-modified mbuf) nil)
|
|---|
| 735 | ;; "Save File", which the user may have used, changes the buffer's name.
|
|---|
| 736 | (unless (getstring mbuf-name *buffer-names*)
|
|---|
| 737 | (setf (buffer-name mbuf) mbuf-name))
|
|---|
| 738 | (setf (buffer-writable mbuf) writable)
|
|---|
| 739 | (setf (buffer-pathname mbuf) nil)
|
|---|
| 740 | (setf (buffer-major-mode mbuf) "Message")))))
|
|---|
| 741 |
|
|---|
| 742 |
|
|---|
| 743 | |
|---|
| 744 |
|
|---|
| 745 | ;;;; Draft Mode.
|
|---|
| 746 |
|
|---|
| 747 | (defmode "Draft")
|
|---|
| 748 |
|
|---|
| 749 | (defhvar "Draft Information"
|
|---|
| 750 | "This holds the information about the current draft buffer."
|
|---|
| 751 | :value nil)
|
|---|
| 752 |
|
|---|
| 753 | (defstruct (draft-info (:include message/draft-info)
|
|---|
| 754 | (:print-function print-draft-info))
|
|---|
| 755 | folder ;String name of draft folder with leading MH "+".
|
|---|
| 756 | message ;String id of draft folder message.
|
|---|
| 757 | pathname ;Pathname of draft in the draft folder directory.
|
|---|
| 758 | delivered ;This is set when the draft was really sent.
|
|---|
| 759 | replied-to-folder ;Folder of message draft is in reply to.
|
|---|
| 760 | replied-to-msg) ;Message draft is in reply to.
|
|---|
| 761 |
|
|---|
| 762 | (defun print-draft-info (obj str n)
|
|---|
| 763 | (declare (ignore n))
|
|---|
| 764 | (format str "#<Draft Info ~A>" (draft-info-message obj)))
|
|---|
| 765 |
|
|---|
| 766 |
|
|---|
| 767 | (defhvar "Reply to Message Prefix Action"
|
|---|
| 768 | "This is one of :cc-all, :no-cc-all, or nil. When an argument is supplied to
|
|---|
| 769 | \"Reply to Message\", this value determines how arguments passed to the
|
|---|
| 770 | MH utility."
|
|---|
| 771 | :value nil)
|
|---|
| 772 |
|
|---|
| 773 | (defcommand "Reply to Message" (p)
|
|---|
| 774 | "Sets up a draft in reply to the current message.
|
|---|
| 775 | Prompts for a folder and message to reply to. When in a headers buffer,
|
|---|
| 776 | replies to the message on the current line. When in a message buffer,
|
|---|
| 777 | replies to that message. With an argument, regard \"Reply to Message Prefix
|
|---|
| 778 | Action\" for carbon copy arguments to the MH utility."
|
|---|
| 779 | "Prompts for a folder and message to reply to. When in a headers buffer,
|
|---|
| 780 | replies to the message on the current line. When in a message buffer,
|
|---|
| 781 | replies to that message."
|
|---|
| 782 | (let ((hinfo (value headers-information))
|
|---|
| 783 | (minfo (value message-information)))
|
|---|
| 784 | (cond (hinfo
|
|---|
| 785 | (multiple-value-bind (cur-msg cur-mark)
|
|---|
| 786 | (headers-current-message hinfo)
|
|---|
| 787 | (unless cur-msg (editor-error "Not on a header line."))
|
|---|
| 788 | (setup-reply-draft (headers-info-folder hinfo)
|
|---|
| 789 | cur-msg hinfo cur-mark p)))
|
|---|
| 790 | (minfo
|
|---|
| 791 | (setup-message-buffer-draft (current-buffer) minfo :reply p))
|
|---|
| 792 | (t
|
|---|
| 793 | (let ((folder (prompt-for-folder)))
|
|---|
| 794 | (setup-reply-draft folder
|
|---|
| 795 | (car (prompt-for-message :folder folder))
|
|---|
| 796 | nil nil p))))))
|
|---|
| 797 |
|
|---|
| 798 | ;;; SETUP-REPLY-DRAFT takes a folder and msg to draft a reply to. Optionally,
|
|---|
| 799 | ;;; a headers buffer and mark are associated with the draft. First, the draft
|
|---|
| 800 | ;;; buffer is associated with the headers buffer if there is one. Then the
|
|---|
| 801 | ;;; message buffer is created and associated with the drafter buffer and
|
|---|
| 802 | ;;; headers buffer. Argument may be used to pass in the argument from the
|
|---|
| 803 | ;;; command.
|
|---|
| 804 | ;;;
|
|---|
| 805 | (defun setup-reply-draft (folder msg &optional hinfo hmark argument)
|
|---|
| 806 | (let* ((dbuffer (sub-setup-message-draft
|
|---|
| 807 | "repl" :end-of-buffer
|
|---|
| 808 | `(,folder ,msg
|
|---|
| 809 | ,@(if argument
|
|---|
| 810 | (case (value reply-to-message-prefix-action)
|
|---|
| 811 | (:no-cc-all '("-nocc" "all"))
|
|---|
| 812 | (:cc-all '("-cc" "all")))))))
|
|---|
| 813 | (dinfo (variable-value 'draft-information :buffer dbuffer))
|
|---|
| 814 | (h-buf (if hinfo (headers-info-buffer hinfo))))
|
|---|
| 815 | (setf (draft-info-replied-to-folder dinfo) folder)
|
|---|
| 816 | (setf (draft-info-replied-to-msg dinfo) msg)
|
|---|
| 817 | (when h-buf
|
|---|
| 818 | (defhvar "Headers Buffer"
|
|---|
| 819 | "This is bound in message and draft buffers to their associated
|
|---|
| 820 | headers buffer."
|
|---|
| 821 | :value h-buf :buffer dbuffer)
|
|---|
| 822 | (setf (draft-info-headers-mark dinfo) hmark)
|
|---|
| 823 | (push dbuffer (headers-info-draft-bufs hinfo)))
|
|---|
| 824 | (let ((msg-buf (maybe-make-mh-buffer (format nil "Message ~A ~A" folder msg)
|
|---|
| 825 | :message)))
|
|---|
| 826 | (defhvar "Message Information"
|
|---|
| 827 | "This holds the information about the current headers buffer."
|
|---|
| 828 | :value (make-message-info :folder folder :msgs msg
|
|---|
| 829 | :headers-mark
|
|---|
| 830 | (if h-buf (copy-mark hmark) hmark)
|
|---|
| 831 | :draft-buf dbuffer)
|
|---|
| 832 | :buffer msg-buf)
|
|---|
| 833 | (when h-buf
|
|---|
| 834 | (defhvar "Headers Buffer"
|
|---|
| 835 | "This is bound in message and draft buffers to their associated
|
|---|
| 836 | headers buffer."
|
|---|
| 837 | :value h-buf :buffer msg-buf)
|
|---|
| 838 | (push msg-buf (headers-info-other-msg-bufs hinfo)))
|
|---|
| 839 | (read-mh-file (merge-pathnames
|
|---|
| 840 | msg
|
|---|
| 841 | (merge-relative-pathnames (strip-folder-name folder)
|
|---|
| 842 | (mh-directory-pathname)))
|
|---|
| 843 | msg-buf)
|
|---|
| 844 | (setf (buffer-writable msg-buf) nil)
|
|---|
| 845 | (defhvar "Message Buffer"
|
|---|
| 846 | "This is bound in draft buffers to their associated message buffer."
|
|---|
| 847 | :value msg-buf :buffer dbuffer))
|
|---|
| 848 | (get-draft-buffer-window dbuffer)))
|
|---|
| 849 |
|
|---|
| 850 |
|
|---|
| 851 | (defcommand "Forward Message" (p)
|
|---|
| 852 | "Forward current message.
|
|---|
| 853 | Prompts for a folder and message to forward. When in a headers buffer,
|
|---|
| 854 | forwards the message on the current line. When in a message buffer,
|
|---|
| 855 | forwards that message."
|
|---|
| 856 | "Prompts for a folder and message to reply to. When in a headers buffer,
|
|---|
| 857 | replies to the message on the current line. When in a message buffer,
|
|---|
| 858 | replies to that message."
|
|---|
| 859 | (declare (ignore p))
|
|---|
| 860 | (let ((hinfo (value headers-information))
|
|---|
| 861 | (minfo (value message-information)))
|
|---|
| 862 | (cond (hinfo
|
|---|
| 863 | (multiple-value-bind (cur-msg cur-mark)
|
|---|
| 864 | (headers-current-message hinfo)
|
|---|
| 865 | (unless cur-msg (editor-error "Not on a header line."))
|
|---|
| 866 | (setup-forward-draft (headers-info-folder hinfo)
|
|---|
| 867 | cur-msg hinfo cur-mark)))
|
|---|
| 868 | (minfo
|
|---|
| 869 | (setup-message-buffer-draft (current-buffer) minfo :forward))
|
|---|
| 870 | (t
|
|---|
| 871 | (let ((folder (prompt-for-folder)))
|
|---|
| 872 | (setup-forward-draft folder
|
|---|
| 873 | (car (prompt-for-message :folder folder))))))))
|
|---|
| 874 |
|
|---|
| 875 | ;;; SETUP-FORWARD-DRAFT sets up a draft forwarding folder's msg. When there
|
|---|
| 876 | ;;; is a headers buffer involved (hinfo and hmark), the draft is associated
|
|---|
| 877 | ;;; with it.
|
|---|
| 878 | ;;;
|
|---|
| 879 | ;;; This function is like SETUP-REPLY-DRAFT (in addition to "forw" and
|
|---|
| 880 | ;;; :to-field), but it does not setup a message buffer. If this is added as
|
|---|
| 881 | ;;; something forward drafts want, then SETUP-REPLY-DRAFT should be
|
|---|
| 882 | ;;; parameterized and renamed.
|
|---|
| 883 | ;;;
|
|---|
| 884 | (defun setup-forward-draft (folder msg &optional hinfo hmark)
|
|---|
| 885 | (let* ((dbuffer (sub-setup-message-draft "forw" :to-field
|
|---|
| 886 | (list folder msg)))
|
|---|
| 887 | (dinfo (variable-value 'draft-information :buffer dbuffer))
|
|---|
| 888 | (h-buf (if hinfo (headers-info-buffer hinfo))))
|
|---|
| 889 | (when h-buf
|
|---|
| 890 | (defhvar "Headers Buffer"
|
|---|
| 891 | "This is bound in message and draft buffers to their associated
|
|---|
| 892 | headers buffer."
|
|---|
| 893 | :value h-buf :buffer dbuffer)
|
|---|
| 894 | (setf (draft-info-headers-mark dinfo) hmark)
|
|---|
| 895 | (push dbuffer (headers-info-draft-bufs hinfo)))
|
|---|
| 896 | (get-draft-buffer-window dbuffer)))
|
|---|
| 897 |
|
|---|
| 898 |
|
|---|
| 899 | (defcommand "Send Message" (p)
|
|---|
| 900 | "Setup a draft buffer.
|
|---|
| 901 | Setup a draft buffer, reserving a draft folder message. When invoked in a
|
|---|
| 902 | headers buffer, the current message is available in an associated message
|
|---|
| 903 | buffer."
|
|---|
| 904 | "Setup a draft buffer, reserving a draft folder message. When invoked in
|
|---|
| 905 | a headers buffer, the current message is available in an associated
|
|---|
| 906 | message buffer."
|
|---|
| 907 | (declare (ignore p))
|
|---|
| 908 | (let ((hinfo (value headers-information))
|
|---|
| 909 | (minfo (value message-information)))
|
|---|
| 910 | (cond (hinfo (setup-headers-message-draft hinfo))
|
|---|
| 911 | (minfo (setup-message-buffer-draft (current-buffer) minfo :compose))
|
|---|
| 912 | (t (setup-message-draft)))))
|
|---|
| 913 |
|
|---|
| 914 | (defun setup-message-draft ()
|
|---|
| 915 | (get-draft-buffer-window (sub-setup-message-draft "comp" :to-field)))
|
|---|
| 916 |
|
|---|
| 917 | ;;; SETUP-HEADERS-MESSAGE-DRAFT sets up a draft buffer associated with a
|
|---|
| 918 | ;;; headers buffer and a message buffer. The headers current message is
|
|---|
| 919 | ;;; inserted in the message buffer which is also associated with the headers
|
|---|
| 920 | ;;; buffer. The draft buffer is associated with the message buffer.
|
|---|
| 921 | ;;;
|
|---|
| 922 | (defun setup-headers-message-draft (hinfo)
|
|---|
| 923 | (multiple-value-bind (cur-msg cur-mark)
|
|---|
| 924 | (headers-current-message hinfo)
|
|---|
| 925 | (unless cur-msg (message "Draft not associated with any message."))
|
|---|
| 926 | (let* ((dbuffer (sub-setup-message-draft "comp" :to-field))
|
|---|
| 927 | (dinfo (variable-value 'draft-information :buffer dbuffer))
|
|---|
| 928 | (h-buf (headers-info-buffer hinfo)))
|
|---|
| 929 | (when cur-msg
|
|---|
| 930 | (defhvar "Headers Buffer"
|
|---|
| 931 | "This is bound in message and draft buffers to their associated headers
|
|---|
| 932 | buffer."
|
|---|
| 933 | :value h-buf :buffer dbuffer)
|
|---|
| 934 | (push dbuffer (headers-info-draft-bufs hinfo)))
|
|---|
| 935 | (when cur-msg
|
|---|
| 936 | (setf (draft-info-headers-mark dinfo) cur-mark)
|
|---|
| 937 | (let* ((folder (headers-info-folder hinfo))
|
|---|
| 938 | (msg-buf (maybe-make-mh-buffer
|
|---|
| 939 | (format nil "Message ~A ~A" folder cur-msg)
|
|---|
| 940 | :message)))
|
|---|
| 941 | (defhvar "Message Information"
|
|---|
| 942 | "This holds the information about the current headers buffer."
|
|---|
| 943 | :value (make-message-info :folder folder :msgs cur-msg
|
|---|
| 944 | :headers-mark (copy-mark cur-mark)
|
|---|
| 945 | :draft-buf dbuffer)
|
|---|
| 946 | :buffer msg-buf)
|
|---|
| 947 | (defhvar "Headers Buffer"
|
|---|
| 948 | "This is bound in message and draft buffers to their associated
|
|---|
| 949 | headers buffer."
|
|---|
| 950 | :value h-buf :buffer msg-buf)
|
|---|
| 951 | (push msg-buf (headers-info-other-msg-bufs hinfo))
|
|---|
| 952 | (read-mh-file (merge-pathnames
|
|---|
| 953 | cur-msg
|
|---|
| 954 | (merge-relative-pathnames (strip-folder-name folder)
|
|---|
| 955 | (mh-directory-pathname)))
|
|---|
| 956 | msg-buf)
|
|---|
| 957 | (setf (buffer-writable msg-buf) nil)
|
|---|
| 958 | (defhvar "Message Buffer"
|
|---|
| 959 | "This is bound in draft buffers to their associated message buffer."
|
|---|
| 960 | :value msg-buf :buffer dbuffer)))
|
|---|
| 961 | (get-draft-buffer-window dbuffer))))
|
|---|
| 962 |
|
|---|
| 963 | ;;; SETUP-MESSAGE-BUFFER-DRAFT takes a message buffer and its message
|
|---|
| 964 | ;;; information. A draft buffer is created according to type, and the two
|
|---|
| 965 | ;;; buffers are associated. Any previous association of the message buffer and
|
|---|
| 966 | ;;; a draft buffer is dropped. Any association between the message buffer and
|
|---|
| 967 | ;;; a headers buffer is propagated to the draft buffer, and if the message
|
|---|
| 968 | ;;; buffer is the headers buffer's main message buffer, it is moved to "other"
|
|---|
| 969 | ;;; status. Argument may be used to pass in the argument from the command.
|
|---|
| 970 | ;;;
|
|---|
| 971 | (defun setup-message-buffer-draft (msg-buf minfo type &optional argument)
|
|---|
| 972 | (let* ((msgs (message-info-msgs minfo))
|
|---|
| 973 | (cur-msg (if (consp msgs) (car msgs) msgs))
|
|---|
| 974 | (folder (message-info-folder minfo))
|
|---|
| 975 | (dbuffer
|
|---|
| 976 | (ecase type
|
|---|
| 977 | (:reply
|
|---|
| 978 | (sub-setup-message-draft
|
|---|
| 979 | "repl" :end-of-buffer
|
|---|
| 980 | `(,folder ,cur-msg
|
|---|
| 981 | ,@(if argument
|
|---|
| 982 | (case (value reply-to-message-prefix-action)
|
|---|
| 983 | (:no-cc-all '("-nocc" "all"))
|
|---|
| 984 | (:cc-all '("-cc" "all")))))))
|
|---|
| 985 | (:compose
|
|---|
| 986 | (sub-setup-message-draft "comp" :to-field))
|
|---|
| 987 | (:forward
|
|---|
| 988 | (sub-setup-message-draft "forw" :to-field
|
|---|
| 989 | (list folder cur-msg)))))
|
|---|
| 990 | (dinfo (variable-value 'draft-information :buffer dbuffer)))
|
|---|
| 991 | (when (message-info-draft-buf minfo)
|
|---|
| 992 | (delete-variable 'message-buffer :buffer (message-info-draft-buf minfo)))
|
|---|
| 993 | (setf (message-info-draft-buf minfo) dbuffer)
|
|---|
| 994 | (when (eq type :reply)
|
|---|
| 995 | (setf (draft-info-replied-to-folder dinfo) folder)
|
|---|
| 996 | (setf (draft-info-replied-to-msg dinfo) cur-msg))
|
|---|
| 997 | (when (hemlock-bound-p 'headers-buffer :buffer msg-buf)
|
|---|
| 998 | (let* ((hbuf (variable-value 'headers-buffer :buffer msg-buf))
|
|---|
| 999 | (hinfo (variable-value 'headers-information :buffer hbuf)))
|
|---|
| 1000 | (defhvar "Headers Buffer"
|
|---|
| 1001 | "This is bound in message and draft buffers to their associated
|
|---|
| 1002 | headers buffer."
|
|---|
| 1003 | :value hbuf :buffer dbuffer)
|
|---|
| 1004 | (setf (draft-info-headers-mark dinfo)
|
|---|
| 1005 | (copy-mark (message-info-headers-mark minfo)))
|
|---|
| 1006 | (push dbuffer (headers-info-draft-bufs hinfo))
|
|---|
| 1007 | (when (eq (headers-info-msg-buffer hinfo) msg-buf)
|
|---|
| 1008 | (setf (headers-info-msg-buffer hinfo) nil)
|
|---|
| 1009 | (push msg-buf (headers-info-other-msg-bufs hinfo)))))
|
|---|
| 1010 | (defhvar "Message Buffer"
|
|---|
| 1011 | "This is bound in draft buffers to their associated message buffer."
|
|---|
| 1012 | :value msg-buf :buffer dbuffer)
|
|---|
| 1013 | (get-draft-buffer-window dbuffer)))
|
|---|
| 1014 |
|
|---|
| 1015 | (defvar *draft-to-pattern*
|
|---|
| 1016 | (new-search-pattern :string-insensitive :forward "To:"))
|
|---|
| 1017 |
|
|---|
| 1018 | (defun sub-setup-message-draft (utility point-action &optional args)
|
|---|
| 1019 | (mh utility `(,@args "-nowhatnowproc"))
|
|---|
| 1020 | (let* ((folder (mh-draft-folder))
|
|---|
| 1021 | (draft-msg (mh-current-message folder))
|
|---|
| 1022 | (msg-pn (merge-pathnames draft-msg (mh-draft-folder-pathname)))
|
|---|
| 1023 | (dbuffer (maybe-make-mh-buffer (format nil "Draft ~A" draft-msg)
|
|---|
| 1024 | :draft)))
|
|---|
| 1025 | (read-mh-file msg-pn dbuffer)
|
|---|
| 1026 | (setf (buffer-pathname dbuffer) msg-pn)
|
|---|
| 1027 | (defhvar "Draft Information"
|
|---|
| 1028 | "This holds the information about the current draft buffer."
|
|---|
| 1029 | :value (make-draft-info :folder (coerce-folder-name folder)
|
|---|
| 1030 | :message draft-msg
|
|---|
| 1031 | :pathname msg-pn)
|
|---|
| 1032 | :buffer dbuffer)
|
|---|
| 1033 | (let ((point (buffer-point dbuffer)))
|
|---|
| 1034 | (ecase point-action
|
|---|
| 1035 | (:to-field
|
|---|
| 1036 | (when (find-pattern point *draft-to-pattern*)
|
|---|
| 1037 | (line-end point)))
|
|---|
| 1038 | (:end-of-buffer (buffer-end point))))
|
|---|
| 1039 | dbuffer))
|
|---|
| 1040 |
|
|---|
| 1041 | (defun read-mh-file (pathname buffer)
|
|---|
| 1042 | (unless (probe-file pathname)
|
|---|
| 1043 | (editor-error "No such message -- ~A" (namestring pathname)))
|
|---|
| 1044 | (read-file pathname (buffer-point buffer))
|
|---|
| 1045 | (setf (buffer-write-date buffer) (file-write-date pathname))
|
|---|
| 1046 | (buffer-start (buffer-point buffer))
|
|---|
| 1047 | (setf (buffer-modified buffer) nil))
|
|---|
| 1048 |
|
|---|
| 1049 |
|
|---|
| 1050 | (defvar *draft-buffer-window-fun* 'change-to-buffer
|
|---|
| 1051 | "This is called by GET-DRAFT-BUFFER-WINDOW to display a new draft buffer.
|
|---|
| 1052 | The default is CHANGE-TO-BUFFER which uses the current window.")
|
|---|
| 1053 |
|
|---|
| 1054 | ;;; GET-DRAFT-BUFFER-WINDOW is called to display a new draft buffer.
|
|---|
| 1055 | ;;;
|
|---|
| 1056 | (defun get-draft-buffer-window (dbuffer)
|
|---|
| 1057 | (funcall *draft-buffer-window-fun* dbuffer))
|
|---|
| 1058 |
|
|---|
| 1059 |
|
|---|
| 1060 | (defcommand "Reply to Message in Other Window" (p)
|
|---|
| 1061 | "Reply to message, creating another window for draft buffer.
|
|---|
| 1062 | Prompts for a folder and message to reply to. When in a headers buffer,
|
|---|
| 1063 | replies to the message on the current line. When in a message buffer,
|
|---|
| 1064 | replies to that message. The current window is split displaying the draft
|
|---|
| 1065 | buffer in the new window and the message buffer in the current."
|
|---|
| 1066 | "Prompts for a folder and message to reply to. When in a headers buffer,
|
|---|
| 1067 | replies to the message on the current line. When in a message buffer,
|
|---|
| 1068 | replies to that message."
|
|---|
| 1069 | (let ((*draft-buffer-window-fun* #'draft-buffer-in-other-window))
|
|---|
| 1070 | (reply-to-message-command p)))
|
|---|
| 1071 |
|
|---|
| 1072 | (defun draft-buffer-in-other-window (dbuffer)
|
|---|
| 1073 | (when (hemlock-bound-p 'message-buffer :buffer dbuffer)
|
|---|
| 1074 | (let ((mbuf (variable-value 'message-buffer :buffer dbuffer)))
|
|---|
| 1075 | (when (not (eq (current-buffer) mbuf))
|
|---|
| 1076 | (change-to-buffer mbuf))))
|
|---|
| 1077 | (setf (current-buffer) dbuffer)
|
|---|
| 1078 | (setf (current-window) (make-window (buffer-start-mark dbuffer)))
|
|---|
| 1079 | (defhvar "Split Window Draft"
|
|---|
| 1080 | "Indicates window needs to be cleaned up for draft."
|
|---|
| 1081 | :value t :buffer dbuffer))
|
|---|
| 1082 |
|
|---|
| 1083 | (defhvar "Deliver Message Confirm"
|
|---|
| 1084 | "When set, \"Deliver Message\" will ask for confirmation before sending the
|
|---|
| 1085 | draft. This is off by default since \"Deliver Message\" is not bound to
|
|---|
| 1086 | any key by default."
|
|---|
| 1087 | :value t)
|
|---|
| 1088 |
|
|---|
| 1089 | (defcommand "Deliver Message" (p)
|
|---|
| 1090 | "Save and deliver the current draft buffer.
|
|---|
| 1091 | When in a draft buffer, this saves the file and uses SEND to deliver the
|
|---|
| 1092 | draft. Otherwise, this prompts for a draft message id, invoking SEND."
|
|---|
| 1093 | "When in a draft buffer, this saves the file and uses SEND to deliver the
|
|---|
| 1094 | draft. Otherwise, this prompts for a draft message id, invoking SEND."
|
|---|
| 1095 | (declare (ignore p))
|
|---|
| 1096 | (let ((dinfo (value draft-information)))
|
|---|
| 1097 | (cond (dinfo
|
|---|
| 1098 | (deliver-draft-buffer-message dinfo))
|
|---|
| 1099 | (t
|
|---|
| 1100 | (let* ((folder (coerce-folder-name (mh-draft-folder)))
|
|---|
| 1101 | (msg (prompt-for-message :folder folder)))
|
|---|
| 1102 | (mh "send" `("-draftfolder" ,folder "-draftmessage" ,@msg)))))))
|
|---|
| 1103 |
|
|---|
| 1104 | (defun deliver-draft-buffer-message (dinfo)
|
|---|
| 1105 | (when (draft-info-delivered dinfo)
|
|---|
| 1106 | (editor-error "This draft has already been delivered."))
|
|---|
| 1107 | (when (or (not (value deliver-message-confirm))
|
|---|
| 1108 | (prompt-for-y-or-n :prompt "Deliver message? " :default t))
|
|---|
| 1109 | (let ((dbuffer (current-buffer)))
|
|---|
| 1110 | (when (buffer-modified dbuffer)
|
|---|
| 1111 | (write-buffer-file dbuffer (buffer-pathname dbuffer)))
|
|---|
| 1112 | (message "Delivering draft ...")
|
|---|
| 1113 | (mh "send" `("-draftfolder" ,(draft-info-folder dinfo)
|
|---|
| 1114 | "-draftmessage" ,(draft-info-message dinfo)))
|
|---|
| 1115 | (setf (draft-info-delivered dinfo) t)
|
|---|
| 1116 | (let ((replied-folder (draft-info-replied-to-folder dinfo))
|
|---|
| 1117 | (replied-msg (draft-info-replied-to-msg dinfo)))
|
|---|
| 1118 | (when replied-folder
|
|---|
| 1119 | (message "Annotating message being replied to ...")
|
|---|
| 1120 | (mh "anno" `(,replied-folder ,replied-msg "-component" "replied"))
|
|---|
| 1121 | (do-headers-buffers (hbuf replied-folder)
|
|---|
| 1122 | (with-headers-mark (hmark hbuf replied-msg)
|
|---|
| 1123 | (mark-to-note-replied-msg hmark)
|
|---|
| 1124 | (with-writable-buffer (hbuf)
|
|---|
| 1125 | (setf (next-character hmark) #\A))))
|
|---|
| 1126 | (dolist (b *buffer-list*)
|
|---|
| 1127 | (when (and (hemlock-bound-p 'message-information :buffer b)
|
|---|
| 1128 | (buffer-modeline-field-p b :replied-to-message))
|
|---|
| 1129 | (dolist (w (buffer-windows b))
|
|---|
| 1130 | (update-modeline-field b w :replied-to-message))))))
|
|---|
| 1131 | (maybe-delete-extra-draft-window dbuffer (current-window))
|
|---|
| 1132 | (let ((mbuf (value message-buffer)))
|
|---|
| 1133 | (when (and mbuf
|
|---|
| 1134 | (not (hemlock-bound-p 'netnews-message-info :buffer mbuf)))
|
|---|
| 1135 | (let ((minfo (variable-value 'message-information :buffer mbuf)))
|
|---|
| 1136 | (when (and minfo (not (message-info-keep minfo)))
|
|---|
| 1137 | (delete-buffer-if-possible mbuf)))))
|
|---|
| 1138 | (delete-buffer-if-possible dbuffer))))
|
|---|
| 1139 |
|
|---|
| 1140 | (defcommand "Delete Draft and Buffer" (p)
|
|---|
| 1141 | "Delete the current draft and associated message and buffer."
|
|---|
| 1142 | "Delete the current draft and associated message and buffer."
|
|---|
| 1143 | (declare (ignore p))
|
|---|
| 1144 | (let ((dinfo (value draft-information))
|
|---|
| 1145 | (dbuffer (current-buffer)))
|
|---|
| 1146 | (unless dinfo (editor-error "No draft associated with buffer."))
|
|---|
| 1147 | (maybe-delete-extra-draft-window dbuffer (current-window))
|
|---|
| 1148 | (delete-file (draft-info-pathname dinfo))
|
|---|
| 1149 | (let ((mbuf (value message-buffer)))
|
|---|
| 1150 | (when (and mbuf
|
|---|
| 1151 | (not (hemlock-bound-p 'netnews-message-info :buffer mbuf)))
|
|---|
| 1152 | (let ((minfo (variable-value 'message-information :buffer mbuf)))
|
|---|
| 1153 | (when (and minfo (not (message-info-keep minfo)))
|
|---|
| 1154 | (delete-buffer-if-possible mbuf)))))
|
|---|
| 1155 | (delete-buffer-if-possible dbuffer)))
|
|---|
| 1156 |
|
|---|
| 1157 | ;;; MAYBE-DELETE-EXTRA-DRAFT-WINDOW -- Internal.
|
|---|
| 1158 | ;;;
|
|---|
| 1159 | ;;; This takes a draft buffer and a window into it that should not be deleted.
|
|---|
| 1160 | ;;; If "Split Window Draft" is bound in the buffer, and there are at least two
|
|---|
| 1161 | ;;; windows in dbuffer-window's group, then we delete some window. Blow away
|
|---|
| 1162 | ;;; the variable, so we don't think this is still a split window draft buffer.
|
|---|
| 1163 | ;;;
|
|---|
| 1164 | (defun maybe-delete-extra-draft-window (dbuffer dbuffer-window)
|
|---|
| 1165 | (when (and (hemlock-bound-p 'split-window-draft :buffer dbuffer)
|
|---|
| 1166 | ;; Since we know bitmap devices have window groups, this loop is
|
|---|
| 1167 | ;; more correct than testing the length of *window-list* and
|
|---|
| 1168 | ;; accounting for *echo-area-window* being in there.
|
|---|
| 1169 | (do ((start dbuffer-window)
|
|---|
| 1170 | (count 1 (1+ count))
|
|---|
| 1171 | (w (next-window dbuffer-window) (next-window w)))
|
|---|
| 1172 | ((eq start w) (> count 1))))
|
|---|
| 1173 | (delete-window (next-window dbuffer-window))
|
|---|
| 1174 | (delete-variable 'split-window-draft :buffer dbuffer)))
|
|---|
| 1175 |
|
|---|
| 1176 | (defcommand "Remail Message" (p)
|
|---|
| 1177 | "Prompts for a folder and message to remail. Prompts for a resend-to
|
|---|
| 1178 | address string and resend-cc address string. When in a headers buffer,
|
|---|
| 1179 | remails the message on the current line. When in a message buffer,
|
|---|
| 1180 | remails that message."
|
|---|
| 1181 | "Prompts for a folder and message to remail. Prompts for a resend-to
|
|---|
| 1182 | address string and resend-cc address string. When in a headers buffer,
|
|---|
| 1183 | remails the message on the current line. When in a message buffer,
|
|---|
| 1184 | remails that message."
|
|---|
| 1185 | (declare (ignore p))
|
|---|
| 1186 | (let ((hinfo (value headers-information))
|
|---|
| 1187 | (minfo (value message-information)))
|
|---|
| 1188 | (cond (hinfo
|
|---|
| 1189 | (multiple-value-bind (cur-msg cur-mark)
|
|---|
| 1190 | (headers-current-message hinfo)
|
|---|
| 1191 | (unless cur-msg (editor-error "Not on a header line."))
|
|---|
| 1192 | (delete-mark cur-mark)
|
|---|
| 1193 | (remail-message (headers-info-folder hinfo) cur-msg
|
|---|
| 1194 | (prompt-for-string :prompt "Resend To: ")
|
|---|
| 1195 | (prompt-for-string :prompt "Resend Cc: "))))
|
|---|
| 1196 | (minfo
|
|---|
| 1197 | (remail-message (message-info-folder minfo)
|
|---|
| 1198 | (message-info-msgs minfo)
|
|---|
| 1199 | (prompt-for-string :prompt "Resend To: ")
|
|---|
| 1200 | (prompt-for-string :prompt "Resend Cc: ")))
|
|---|
| 1201 | (t
|
|---|
| 1202 | (let ((folder (prompt-for-folder)))
|
|---|
| 1203 | (remail-message folder
|
|---|
| 1204 | (car (prompt-for-message :folder folder))
|
|---|
| 1205 | (prompt-for-string :prompt "Resend To: ")
|
|---|
| 1206 | (prompt-for-string :prompt "Resend Cc: "))))))
|
|---|
| 1207 | (message "Message remailed."))
|
|---|
| 1208 |
|
|---|
| 1209 |
|
|---|
| 1210 | ;;; REMAIL-MESSAGE claims a draft folder message with "dist". This is then
|
|---|
| 1211 | ;;; sucked into a buffer and modified by inserting the supplied addresses.
|
|---|
| 1212 | ;;; "send" is used to deliver the draft, but it requires certain evironment
|
|---|
| 1213 | ;;; variables to make it do the right thing. "mhdist" says the draft is only
|
|---|
| 1214 | ;;; remailing information, and "mhaltmsg" is the message to send. "mhannotate"
|
|---|
| 1215 | ;;; must be set due to a bug in MH's "send"; it will not notice the "mhdist"
|
|---|
| 1216 | ;;; flag unless there is some message to be annotated. This command does not
|
|---|
| 1217 | ;;; provide for annotation of the remailed message.
|
|---|
| 1218 | ;;;
|
|---|
| 1219 | (defun remail-message (folder msg resend-to resend-cc)
|
|---|
| 1220 | (mh "dist" `(,folder ,msg "-nowhatnowproc"))
|
|---|
| 1221 | (let* ((draft-folder (mh-draft-folder))
|
|---|
| 1222 | (draft-msg (mh-current-message draft-folder)))
|
|---|
| 1223 | (setup-remail-draft-message draft-msg resend-to resend-cc)
|
|---|
| 1224 | (mh "send" `("-draftfolder" ,draft-folder "-draftmessage" ,draft-msg)
|
|---|
| 1225 | :environment
|
|---|
| 1226 | `((:|mhdist| . "1")
|
|---|
| 1227 | (:|mhannotate| . "1")
|
|---|
| 1228 | (:|mhaltmsg| . ,(namestring
|
|---|
| 1229 | (merge-pathnames msg (merge-relative-pathnames
|
|---|
| 1230 | (strip-folder-name folder)
|
|---|
| 1231 | (mh-directory-pathname)))))))))
|
|---|
| 1232 |
|
|---|
| 1233 | ;;; SETUP-REMAIL-DRAFT-MESSAGE takes a draft folder and message that have been
|
|---|
| 1234 | ;;; created with the MH "dist" utility. A buffer is created with this
|
|---|
| 1235 | ;;; message's pathname, searching for "resent-to:" and "resent-cc:", filling in
|
|---|
| 1236 | ;;; the supplied argument values. After writing out the results, the buffer
|
|---|
| 1237 | ;;; is deleted.
|
|---|
| 1238 | ;;;
|
|---|
| 1239 | (defvar *draft-resent-to-pattern*
|
|---|
| 1240 | (new-search-pattern :string-insensitive :forward "resent-to:"))
|
|---|
| 1241 | (defvar *draft-resent-cc-pattern*
|
|---|
| 1242 | (new-search-pattern :string-insensitive :forward "resent-cc:"))
|
|---|
| 1243 |
|
|---|
| 1244 | (defun setup-remail-draft-message (msg resend-to resend-cc)
|
|---|
| 1245 | (let* ((msg-pn (merge-pathnames msg (mh-draft-folder-pathname)))
|
|---|
| 1246 | (dbuffer (maybe-make-mh-buffer (format nil "Draft ~A" msg)
|
|---|
| 1247 | :draft))
|
|---|
| 1248 | (point (buffer-point dbuffer)))
|
|---|
| 1249 | (read-mh-file msg-pn dbuffer)
|
|---|
| 1250 | (when (find-pattern point *draft-resent-to-pattern*)
|
|---|
| 1251 | (line-end point)
|
|---|
| 1252 | (insert-string point resend-to))
|
|---|
| 1253 | (buffer-start point)
|
|---|
| 1254 | (when (find-pattern point *draft-resent-cc-pattern*)
|
|---|
| 1255 | (line-end point)
|
|---|
| 1256 | (insert-string point resend-cc))
|
|---|
| 1257 | (write-file (buffer-region dbuffer) msg-pn :keep-backup nil)
|
|---|
| 1258 | ;; The draft buffer delete hook expects this to be bound.
|
|---|
| 1259 | (defhvar "Draft Information"
|
|---|
| 1260 | "This holds the information about the current draft buffer."
|
|---|
| 1261 | :value :ignore
|
|---|
| 1262 | :buffer dbuffer)
|
|---|
| 1263 | (delete-buffer dbuffer)))
|
|---|
| 1264 |
|
|---|
| 1265 |
|
|---|
| 1266 | |
|---|
| 1267 |
|
|---|
| 1268 | ;;;; Message and Draft Stuff.
|
|---|
| 1269 |
|
|---|
| 1270 | (defhvar "Headers Buffer"
|
|---|
| 1271 | "This is bound in message and draft buffers to their associated headers
|
|---|
| 1272 | buffer."
|
|---|
| 1273 | :value nil)
|
|---|
| 1274 |
|
|---|
| 1275 | (defcommand "Goto Headers Buffer" (p)
|
|---|
| 1276 | "Selects associated headers buffer if it exists.
|
|---|
| 1277 | The headers buffer's point is moved to the appropriate line, pushing a
|
|---|
| 1278 | buffer mark where point was."
|
|---|
| 1279 | "Selects associated headers buffer if it exists."
|
|---|
| 1280 | (declare (ignore p))
|
|---|
| 1281 | (let ((h-buf (value headers-buffer)))
|
|---|
| 1282 | (unless h-buf (editor-error "No associated headers buffer."))
|
|---|
| 1283 | (let ((info (or (value message-information) (value draft-information))))
|
|---|
| 1284 | (change-to-buffer h-buf)
|
|---|
| 1285 | (push-buffer-mark (copy-mark (current-point)))
|
|---|
| 1286 | (move-mark (current-point) (message/draft-info-headers-mark info)))))
|
|---|
| 1287 |
|
|---|
| 1288 | (defhvar "Message Buffer"
|
|---|
| 1289 | "This is bound in draft buffers to their associated message buffer."
|
|---|
| 1290 | :value nil)
|
|---|
| 1291 |
|
|---|
| 1292 | (defcommand "Goto Message Buffer" (p)
|
|---|
| 1293 | "Selects associated message buffer if it exists."
|
|---|
| 1294 | "Selects associated message buffer if it exists."
|
|---|
| 1295 | (declare (ignore p))
|
|---|
| 1296 | (let ((msg-buf (value message-buffer)))
|
|---|
| 1297 | (unless msg-buf (editor-error "No associated message buffer."))
|
|---|
| 1298 | (change-to-buffer msg-buf)))
|
|---|
| 1299 |
|
|---|
| 1300 |
|
|---|
| 1301 | (defhvar "Message Insertion Prefix"
|
|---|
| 1302 | "This is a fill prefix that is used when inserting text from a message buffer
|
|---|
| 1303 | into a draft buffer by \"Insert Message Region\". It defaults to three
|
|---|
| 1304 | spaces."
|
|---|
| 1305 | :value " ")
|
|---|
| 1306 |
|
|---|
| 1307 | (defhvar "Message Insertion Column"
|
|---|
| 1308 | "This is a fill column that is used when inserting text from a message buffer
|
|---|
| 1309 | into a draft buffer by \"Insert Message Region\"."
|
|---|
| 1310 | :value 75)
|
|---|
| 1311 |
|
|---|
| 1312 | (defcommand "Insert Message Region" (p)
|
|---|
| 1313 | "Copy the current region into the associated draft or post buffer. When
|
|---|
| 1314 | in a message buffer that has an associated draft or post buffer, the
|
|---|
| 1315 | current active region is copied into the draft or post buffer. It is
|
|---|
| 1316 | filled using \"Message Insertion Prefix\" and \"Message Insertion
|
|---|
| 1317 | Column\". If an argument is supplied, the filling is inhibited.
|
|---|
| 1318 | If both a draft buffer and post buffer are associated with this, then it
|
|---|
| 1319 | is inserted into the draft buffer."
|
|---|
| 1320 | "When in a message buffer that has an associated draft or post buffer,
|
|---|
| 1321 | the current active region is copied into the post or draft buffer. It is
|
|---|
| 1322 | filled using \"Message Insertion Prefix\" and \"Message Insertion
|
|---|
| 1323 | Column\". If an argument is supplied, the filling is inhibited."
|
|---|
| 1324 | (let* ((minfo (value message-information))
|
|---|
| 1325 | (nm-info (if (hemlock-bound-p 'netnews-message-info)
|
|---|
| 1326 | (value netnews-message-info)))
|
|---|
| 1327 | (post-buffer (and nm-info (nm-info-post-buffer nm-info)))
|
|---|
| 1328 | (post-info (and post-buffer
|
|---|
| 1329 | (variable-value 'post-info :buffer post-buffer)))
|
|---|
| 1330 | dbuf kind)
|
|---|
| 1331 | (cond (minfo
|
|---|
| 1332 | (setf kind :mail)
|
|---|
| 1333 | (setf dbuf (message-info-draft-buf minfo)))
|
|---|
| 1334 | (nm-info
|
|---|
| 1335 | (setf kind :netnews)
|
|---|
| 1336 | (setf dbuf (or (nm-info-draft-buffer nm-info)
|
|---|
| 1337 | (nm-info-post-buffer nm-info))))
|
|---|
| 1338 | (t (editor-error "Not in a netnews message or message buffer.")))
|
|---|
| 1339 | (unless dbuf
|
|---|
| 1340 | (editor-error "Message buffer not associated with any draft or post ~
|
|---|
| 1341 | buffer."))
|
|---|
| 1342 | (let* ((region (copy-region (current-region)))
|
|---|
| 1343 | (dbuf-point (buffer-point dbuf))
|
|---|
| 1344 | (dbuf-mark (copy-mark dbuf-point)))
|
|---|
| 1345 | (cond ((and (eq kind :mail)
|
|---|
| 1346 | (hemlock-bound-p 'split-window-draft :buffer dbuf)
|
|---|
| 1347 | (> (length (the list *window-list*)) 2)
|
|---|
| 1348 | (buffer-windows dbuf))
|
|---|
| 1349 | (setf (current-buffer) dbuf
|
|---|
| 1350 | (current-window) (car (buffer-windows dbuf))))
|
|---|
| 1351 | ((and (eq kind :netnews)
|
|---|
| 1352 | (and (member (post-info-message-window post-info)
|
|---|
| 1353 | *window-list*)
|
|---|
| 1354 | (member (post-info-reply-window post-info)
|
|---|
| 1355 | *window-list*)))
|
|---|
| 1356 | (setf (current-buffer) dbuf
|
|---|
| 1357 | (current-window) (post-info-reply-window post-info)))
|
|---|
| 1358 | (t (change-to-buffer dbuf)))
|
|---|
| 1359 | (push-buffer-mark dbuf-mark)
|
|---|
| 1360 | (ninsert-region dbuf-point region)
|
|---|
| 1361 | (unless p
|
|---|
| 1362 | (fill-region-by-paragraphs (region dbuf-mark dbuf-point)
|
|---|
| 1363 | (value message-insertion-prefix)
|
|---|
| 1364 | (value message-insertion-column)))))
|
|---|
| 1365 | (setf (last-command-type) :ephemerally-active))
|
|---|
| 1366 |
|
|---|
| 1367 |
|
|---|
| 1368 | (defhvar "Message Buffer Insertion Prefix"
|
|---|
| 1369 | "This is a line prefix that is inserted at the beginning of every line in
|
|---|
| 1370 | a message buffer when inserting those lines into a draft buffer with
|
|---|
| 1371 | \"Insert Message Buffer\". It defaults to four spaces."
|
|---|
| 1372 | :value " ")
|
|---|
| 1373 |
|
|---|
| 1374 | (defcommand "Insert Message Buffer" (p)
|
|---|
| 1375 | "Insert entire (associated) message buffer into (associated) draft or
|
|---|
| 1376 | post buffer. When in a draft or post buffer with an associated message
|
|---|
| 1377 | buffer, or when in a message buffer that has an associated draft or post
|
|---|
| 1378 | buffer, the message buffer is inserted into the draft buffer. When
|
|---|
| 1379 | there are both an associated draft and post buffer, the text is inserted
|
|---|
| 1380 | into the draft buffer. Each inserted line is modified by prefixing it
|
|---|
| 1381 | with \"Message Buffer Insertion Prefix\". If an argument is supplied
|
|---|
| 1382 | the prefixing is inhibited."
|
|---|
| 1383 | "When in a draft or post buffer with an associated message buffer, or
|
|---|
| 1384 | when in a message buffer that has an associated draft or post buffer, the
|
|---|
| 1385 | message buffer is inserted into the draft buffer. Each inserted line is
|
|---|
| 1386 | modified by prefixing it with \"Message Buffer Insertion Prefix\". If an
|
|---|
| 1387 | argument is supplied the prefixing is inhibited."
|
|---|
| 1388 | (let ((minfo (value message-information))
|
|---|
| 1389 | (dinfo (value draft-information))
|
|---|
| 1390 | mbuf dbuf message-kind)
|
|---|
| 1391 | (cond (minfo
|
|---|
| 1392 | (setf message-kind :mail)
|
|---|
| 1393 | (setf dbuf (message-info-draft-buf minfo))
|
|---|
| 1394 | (unless dbuf
|
|---|
| 1395 | (editor-error
|
|---|
| 1396 | "Message buffer not associated with any draft buffer."))
|
|---|
| 1397 | (setf mbuf (current-buffer))
|
|---|
| 1398 | (change-to-buffer dbuf))
|
|---|
| 1399 | (dinfo
|
|---|
| 1400 | (setf message-kind :mail)
|
|---|
| 1401 | (setf mbuf (value message-buffer))
|
|---|
| 1402 | (unless mbuf
|
|---|
| 1403 | (editor-error
|
|---|
| 1404 | "Draft buffer not associated with any message buffer."))
|
|---|
| 1405 | (setf dbuf (current-buffer)))
|
|---|
| 1406 | ((hemlock-bound-p 'netnews-message-info)
|
|---|
| 1407 | (setf message-kind :netnews)
|
|---|
| 1408 | (setf mbuf (current-buffer))
|
|---|
| 1409 | (let ((nm-info (value netnews-message-info)))
|
|---|
| 1410 | (setf dbuf (or (nm-info-draft-buffer nm-info)
|
|---|
| 1411 | (nm-info-post-buffer nm-info)))
|
|---|
| 1412 | (unless dbuf
|
|---|
| 1413 | (editor-error "Message buffer not associated with any draft ~
|
|---|
| 1414 | or post buffer.")))
|
|---|
| 1415 | (change-to-buffer dbuf))
|
|---|
| 1416 | ((hemlock-bound-p 'post-info)
|
|---|
| 1417 | (setf message-kind :netnews)
|
|---|
| 1418 | (let ((post-info (value post-info)))
|
|---|
| 1419 | (setf mbuf (post-info-message-buffer post-info))
|
|---|
| 1420 | (unless mbuf
|
|---|
| 1421 | (editor-error "Post buffer not associated with any message ~
|
|---|
| 1422 | buffer.")))
|
|---|
| 1423 | (setf dbuf (current-buffer)))
|
|---|
| 1424 | (t (editor-error "Not in a draft, message, news-message, or post ~
|
|---|
| 1425 | buffer.")))
|
|---|
| 1426 | (let* ((dbuf-point (buffer-point dbuf))
|
|---|
| 1427 | (dbuf-mark (copy-mark dbuf-point)))
|
|---|
| 1428 | (push-buffer-mark dbuf-mark)
|
|---|
| 1429 | (insert-region dbuf-point (buffer-region mbuf))
|
|---|
| 1430 | (unless p
|
|---|
| 1431 | (let ((prefix (value message-buffer-insertion-prefix)))
|
|---|
| 1432 | (with-mark ((temp dbuf-mark :left-inserting))
|
|---|
| 1433 | (loop
|
|---|
| 1434 | (when (mark>= temp dbuf-point) (return))
|
|---|
| 1435 | (insert-string temp prefix)
|
|---|
| 1436 | (unless (line-offset temp 1 0) (return)))))))
|
|---|
| 1437 | (ecase message-kind
|
|---|
| 1438 | (:mail
|
|---|
| 1439 | (insert-message-buffer-cleanup-split-draft dbuf mbuf))
|
|---|
| 1440 | (:netnews
|
|---|
| 1441 | (nn-reply-cleanup-split-windows dbuf))))
|
|---|
| 1442 | (setf (last-command-type) :ephemerally-active))
|
|---|
| 1443 |
|
|---|
| 1444 | ;;; INSERT-MESSAGE-BUFFER-CLEANUP-SPLIT-DRAFT tries to delete an extra window
|
|---|
| 1445 | ;;; due to "Reply to Message in Other Window". Since we just inserted the
|
|---|
| 1446 | ;;; message buffer in the draft buffer, we don't need the other window into
|
|---|
| 1447 | ;;; the message buffer.
|
|---|
| 1448 | ;;;
|
|---|
| 1449 | (defun insert-message-buffer-cleanup-split-draft (dbuf mbuf)
|
|---|
| 1450 | (when (and (hemlock-bound-p 'split-window-draft :buffer dbuf)
|
|---|
| 1451 | (> (length (the list *window-list*)) 2))
|
|---|
| 1452 | (let ((win (car (buffer-windows mbuf))))
|
|---|
| 1453 | (cond
|
|---|
| 1454 | (win
|
|---|
| 1455 | (when (eq win (current-window))
|
|---|
| 1456 | (let ((dwin (car (buffer-windows dbuf))))
|
|---|
| 1457 | (unless dwin
|
|---|
| 1458 | (editor-error "Couldn't fix windows for split window draft."))
|
|---|
| 1459 | (setf (current-buffer) dbuf)
|
|---|
| 1460 | (setf (current-window) dwin)))
|
|---|
| 1461 | (delete-window win))
|
|---|
| 1462 | (t ;; This happens when invoked with the message buffer current.
|
|---|
| 1463 | (let ((dwins (buffer-windows dbuf)))
|
|---|
| 1464 | (when (> (length (the list dwins)) 1)
|
|---|
| 1465 | (delete-window (find-if #'(lambda (w)
|
|---|
| 1466 | (not (eq w (current-window))))
|
|---|
| 1467 | dwins)))))))
|
|---|
| 1468 | (delete-variable 'split-window-draft :buffer dbuf)))
|
|---|
| 1469 |
|
|---|
| 1470 |
|
|---|
| 1471 | ;;; CLEANUP-MESSAGE-BUFFER is called when a buffer gets deleted. It cleans
|
|---|
| 1472 | ;;; up references to a message buffer.
|
|---|
| 1473 | ;;;
|
|---|
| 1474 | (defun cleanup-message-buffer (buffer)
|
|---|
| 1475 | (let ((minfo (variable-value 'message-information :buffer buffer)))
|
|---|
| 1476 | (when (hemlock-bound-p 'headers-buffer :buffer buffer)
|
|---|
| 1477 | (let* ((hinfo (variable-value 'headers-information
|
|---|
| 1478 | :buffer (variable-value 'headers-buffer
|
|---|
| 1479 | :buffer buffer)))
|
|---|
| 1480 | (msg-buf (headers-info-msg-buffer hinfo)))
|
|---|
| 1481 | (if (eq msg-buf buffer)
|
|---|
| 1482 | (setf (headers-info-msg-buffer hinfo) nil)
|
|---|
| 1483 | (setf (headers-info-other-msg-bufs hinfo)
|
|---|
| 1484 | (delete buffer (headers-info-other-msg-bufs hinfo)
|
|---|
| 1485 | :test #'eq))))
|
|---|
| 1486 | (delete-mark (message-info-headers-mark minfo))
|
|---|
| 1487 | ;;
|
|---|
| 1488 | ;; Do this for MAYBE-MAKE-MH-BUFFER since it isn't necessary for GC.
|
|---|
| 1489 | (delete-variable 'headers-buffer :buffer buffer))
|
|---|
| 1490 | (when (message-info-draft-buf minfo)
|
|---|
| 1491 | (delete-variable 'message-buffer
|
|---|
| 1492 | :buffer (message-info-draft-buf minfo)))))
|
|---|
| 1493 |
|
|---|
| 1494 | ;;; CLEANUP-DRAFT-BUFFER is called when a buffer gets deleted. It cleans
|
|---|
| 1495 | ;;; up references to a draft buffer.
|
|---|
| 1496 | ;;;
|
|---|
| 1497 | (defun cleanup-draft-buffer (buffer)
|
|---|
| 1498 | (let ((dinfo (variable-value 'draft-information :buffer buffer)))
|
|---|
| 1499 | (when (hemlock-bound-p 'headers-buffer :buffer buffer)
|
|---|
| 1500 | (let* ((hinfo (variable-value 'headers-information
|
|---|
| 1501 | :buffer (variable-value 'headers-buffer
|
|---|
| 1502 | :buffer buffer))))
|
|---|
| 1503 | (setf (headers-info-draft-bufs hinfo)
|
|---|
| 1504 | (delete buffer (headers-info-draft-bufs hinfo) :test #'eq))
|
|---|
| 1505 | (delete-mark (draft-info-headers-mark dinfo))))
|
|---|
| 1506 | (when (hemlock-bound-p 'message-buffer :buffer buffer)
|
|---|
| 1507 | (setf (message-info-draft-buf
|
|---|
| 1508 | (variable-value 'message-information
|
|---|
| 1509 | :buffer (variable-value 'message-buffer
|
|---|
| 1510 | :buffer buffer)))
|
|---|
| 1511 | nil))))
|
|---|
| 1512 |
|
|---|
| 1513 | ;;; CLEANUP-HEADERS-BUFFER is called when a buffer gets deleted. It cleans
|
|---|
| 1514 | ;;; up references to a headers buffer.
|
|---|
| 1515 | ;;;
|
|---|
| 1516 | (defun cleanup-headers-buffer (buffer)
|
|---|
| 1517 | (let* ((hinfo (variable-value 'headers-information :buffer buffer))
|
|---|
| 1518 | (msg-buf (headers-info-msg-buffer hinfo)))
|
|---|
| 1519 | (when msg-buf
|
|---|
| 1520 | (cleanup-headers-reference
|
|---|
| 1521 | msg-buf (variable-value 'message-information :buffer msg-buf)))
|
|---|
| 1522 | (dolist (b (headers-info-other-msg-bufs hinfo))
|
|---|
| 1523 | (cleanup-headers-reference
|
|---|
| 1524 | b (variable-value 'message-information :buffer b)))
|
|---|
| 1525 | (dolist (b (headers-info-draft-bufs hinfo))
|
|---|
| 1526 | (cleanup-headers-reference
|
|---|
| 1527 | b (variable-value 'draft-information :buffer b)))))
|
|---|
| 1528 |
|
|---|
| 1529 | (defun cleanup-headers-reference (buffer info)
|
|---|
| 1530 | (delete-mark (message/draft-info-headers-mark info))
|
|---|
| 1531 | (setf (message/draft-info-headers-mark info) nil)
|
|---|
| 1532 | (delete-variable 'headers-buffer :buffer buffer)
|
|---|
| 1533 | (when (typep info 'draft-info)
|
|---|
| 1534 | (setf (draft-info-replied-to-folder info) nil)
|
|---|
| 1535 | (setf (draft-info-replied-to-msg info) nil)))
|
|---|
| 1536 |
|
|---|
| 1537 | ;;; REVAMP-HEADERS-BUFFER cleans up a headers buffer for immediate re-use.
|
|---|
| 1538 | ;;; After deleting the buffer's region, there will be one line in the buffer
|
|---|
| 1539 | ;;; because of how Hemlock regions work, so we have to delete that line's
|
|---|
| 1540 | ;;; plist. Then we clean up any references to the buffer and delete the
|
|---|
| 1541 | ;;; main message buffer. The other message buffers are left alone assuming
|
|---|
| 1542 | ;;; they are on the "others" list because they are being used in some
|
|---|
| 1543 | ;;; particular way (for example, a draft buffer refers to one or the user has
|
|---|
| 1544 | ;;; kept it). Then some slots of the info structure are set to nil.
|
|---|
| 1545 | ;;;
|
|---|
| 1546 | (defun revamp-headers-buffer (hbuffer hinfo)
|
|---|
| 1547 | (delete-region (buffer-region hbuffer))
|
|---|
| 1548 | (setf (line-plist (mark-line (buffer-point hbuffer))) nil)
|
|---|
| 1549 | (let ((msg-buf (headers-info-msg-buffer hinfo)))
|
|---|
| 1550 | ;; Deleting the buffer sets the slot to nil.
|
|---|
| 1551 | (when msg-buf (delete-buffer-if-possible msg-buf))
|
|---|
| 1552 | (cleanup-headers-buffer hbuffer))
|
|---|
| 1553 | (setf (headers-info-other-msg-bufs hinfo) nil)
|
|---|
| 1554 | (setf (headers-info-draft-bufs hinfo) nil)
|
|---|
| 1555 | (setf (headers-info-msg-seq hinfo) nil)
|
|---|
| 1556 | (setf (headers-info-msg-strings hinfo) nil))
|
|---|
| 1557 |
|
|---|
| 1558 |
|
|---|
| 1559 | |
|---|
| 1560 |
|
|---|
| 1561 | ;;;; Incorporating new mail.
|
|---|
| 1562 |
|
|---|
| 1563 | (defhvar "New Mail Folder"
|
|---|
| 1564 | "This is the folder new mail is incorporated into."
|
|---|
| 1565 | :value "+inbox")
|
|---|
| 1566 |
|
|---|
| 1567 | (defcommand "Incorporate New Mail" (p)
|
|---|
| 1568 | "Incorporates new mail into \"New Mail Folder\", displaying INC output in
|
|---|
| 1569 | a pop-up window."
|
|---|
| 1570 | "Incorporates new mail into \"New Mail Folder\", displaying INC output in
|
|---|
| 1571 | a pop-up window."
|
|---|
| 1572 | (declare (ignore p))
|
|---|
| 1573 | (with-pop-up-display (s)
|
|---|
| 1574 | (incorporate-new-mail s)))
|
|---|
| 1575 |
|
|---|
| 1576 | (defhvar "Unseen Headers Message Spec"
|
|---|
| 1577 | "This is an MH message spec suitable any message prompt. It is used to
|
|---|
| 1578 | supply headers for the unseen headers buffer, in addition to the
|
|---|
| 1579 | unseen-sequence name that is taken from the user's MH profile, when
|
|---|
| 1580 | incorporating new mail and after expunging. This value is a string."
|
|---|
| 1581 | :value nil)
|
|---|
| 1582 |
|
|---|
| 1583 | (defcommand "Incorporate and Read New Mail" (p)
|
|---|
| 1584 | "Incorporates new mail and generates a headers buffer.
|
|---|
| 1585 | Incorporates new mail into \"New Mail Folder\", and creates a headers buffer
|
|---|
| 1586 | with the new messages. To use this, you must define an unseen- sequence in
|
|---|
| 1587 | your profile. Each time this is invoked the unseen-sequence is SCAN'ed, and
|
|---|
| 1588 | the headers buffer's contents are replaced."
|
|---|
| 1589 | "Incorporates new mail into \"New Mail Folder\", and creates a headers
|
|---|
| 1590 | buffer with the new messages. This buffer will be appended to with
|
|---|
| 1591 | successive uses of this command."
|
|---|
| 1592 | (declare (ignore p))
|
|---|
| 1593 | (let ((unseen-seq (mh-profile-component "unseen-sequence")))
|
|---|
| 1594 | (unless unseen-seq
|
|---|
| 1595 | (editor-error "No unseen-sequence defined in MH profile."))
|
|---|
| 1596 | (incorporate-new-mail)
|
|---|
| 1597 | (let* ((folder (value new-mail-folder))
|
|---|
| 1598 | ;; Stash current message before fetching unseen headers.
|
|---|
| 1599 | (cur-msg (mh-current-message folder))
|
|---|
| 1600 | (region (get-new-mail-msg-hdrs folder unseen-seq)))
|
|---|
| 1601 | ;; Fetch message headers before possibly making buffer in case we error.
|
|---|
| 1602 | (when (not (and *new-mail-buffer*
|
|---|
| 1603 | (member *new-mail-buffer* *buffer-list* :test #'eq)))
|
|---|
| 1604 | (let ((name (format nil "Unseen Headers ~A" folder)))
|
|---|
| 1605 | (when (getstring name *buffer-names*)
|
|---|
| 1606 | (editor-error "There already is a buffer named ~S!" name))
|
|---|
| 1607 | (setf *new-mail-buffer*
|
|---|
| 1608 | (make-buffer name :modes (list "Headers")
|
|---|
| 1609 | :delete-hook '(new-mail-buf-delete-hook)))
|
|---|
| 1610 | (setf (buffer-writable *new-mail-buffer*) nil)))
|
|---|
| 1611 | (cond ((hemlock-bound-p 'headers-information
|
|---|
| 1612 | :buffer *new-mail-buffer*)
|
|---|
| 1613 | (let ((hinfo (variable-value 'headers-information
|
|---|
| 1614 | :buffer *new-mail-buffer*)))
|
|---|
| 1615 | (unless (string= (headers-info-folder hinfo) folder)
|
|---|
| 1616 | (editor-error
|
|---|
| 1617 | "An unseen headers buffer already exists but into another ~
|
|---|
| 1618 | folder. Your mail has already been incorporated into the ~
|
|---|
| 1619 | specified folder."))
|
|---|
| 1620 | (with-writable-buffer (*new-mail-buffer*)
|
|---|
| 1621 | (revamp-headers-buffer *new-mail-buffer* hinfo))
|
|---|
| 1622 | ;; Restore the name in case someone used "Pick Headers".
|
|---|
| 1623 | (setf (buffer-name *new-mail-buffer*)
|
|---|
| 1624 | (format nil "Unseen Headers ~A" folder))
|
|---|
| 1625 | (insert-new-mail-message-headers hinfo region cur-msg)))
|
|---|
| 1626 | (t
|
|---|
| 1627 | (let ((hinfo (make-headers-info :buffer *new-mail-buffer*
|
|---|
| 1628 | :folder folder)))
|
|---|
| 1629 | (defhvar "Headers Information"
|
|---|
| 1630 | "This holds the information about the current headers buffer."
|
|---|
| 1631 | :value hinfo :buffer *new-mail-buffer*)
|
|---|
| 1632 | (insert-new-mail-message-headers hinfo region cur-msg)))))))
|
|---|
| 1633 |
|
|---|
| 1634 | ;;; NEW-MAIL-BUF-DELETE-HOOK is invoked whenever the new mail buffer is
|
|---|
| 1635 | ;;; deleted.
|
|---|
| 1636 | ;;;
|
|---|
| 1637 | (defun new-mail-buf-delete-hook (buffer)
|
|---|
| 1638 | (declare (ignore buffer))
|
|---|
| 1639 | (setf *new-mail-buffer* nil))
|
|---|
| 1640 |
|
|---|
| 1641 | ;;; GET-NEW-MAIL-MSG-HDRS takes a folder and the unseen-sequence name. It
|
|---|
| 1642 | ;;; returns a region with the unseen message headers and any headers due to
|
|---|
| 1643 | ;;; the "Unseen Headers Message Spec" variable.
|
|---|
| 1644 | ;;;
|
|---|
| 1645 | (defun get-new-mail-msg-hdrs (folder unseen-seq)
|
|---|
| 1646 | (let* ((unseen-headers-message-spec (value unseen-headers-message-spec))
|
|---|
| 1647 | (other-msgs (if unseen-headers-message-spec
|
|---|
| 1648 | (breakup-message-spec
|
|---|
| 1649 | (string-trim '(#\space #\tab)
|
|---|
| 1650 | unseen-headers-message-spec))))
|
|---|
| 1651 | (msg-spec (cond ((null other-msgs)
|
|---|
| 1652 | (list unseen-seq))
|
|---|
| 1653 | ((member unseen-seq other-msgs :test #'string=)
|
|---|
| 1654 | other-msgs)
|
|---|
| 1655 | (t (cons unseen-seq other-msgs)))))
|
|---|
| 1656 | (message-headers-to-region folder msg-spec)))
|
|---|
| 1657 |
|
|---|
| 1658 | ;;; INSERT-NEW-MAIL-MESSAGE-HEADERS inserts region in the new mail buffer.
|
|---|
| 1659 | ;;; Then we look for the header line with cur-msg id, moving point there.
|
|---|
| 1660 | ;;; There may have been unseen messages before incorporating new mail, and
|
|---|
| 1661 | ;;; cur-msg should be the first new message. Then we either switch to the
|
|---|
| 1662 | ;;; new mail headers, or show the current message.
|
|---|
| 1663 | ;;;
|
|---|
| 1664 | (defun insert-new-mail-message-headers (hinfo region cur-msg)
|
|---|
| 1665 | (declare (simple-string cur-msg))
|
|---|
| 1666 | (with-writable-buffer (*new-mail-buffer*)
|
|---|
| 1667 | (insert-message-headers *new-mail-buffer* hinfo region))
|
|---|
| 1668 | (let ((point (buffer-point *new-mail-buffer*)))
|
|---|
| 1669 | (buffer-start point)
|
|---|
| 1670 | (with-headers-mark (cur-mark *new-mail-buffer* cur-msg)
|
|---|
| 1671 | (move-mark point cur-mark)))
|
|---|
| 1672 | (change-to-buffer *new-mail-buffer*))
|
|---|
| 1673 |
|
|---|
| 1674 |
|
|---|
| 1675 | (defhvar "Incorporate New Mail Hook"
|
|---|
| 1676 | "Functions on this hook are invoked immediately after new mail is
|
|---|
| 1677 | incorporated."
|
|---|
| 1678 | :value nil)
|
|---|
| 1679 |
|
|---|
| 1680 | (defun incorporate-new-mail (&optional stream)
|
|---|
| 1681 | "Incorporates new mail, passing INC's output to stream. When stream is
|
|---|
| 1682 | nil, output is flushed."
|
|---|
| 1683 | (unless (new-mail-p) (editor-error "No new mail."))
|
|---|
| 1684 | (let ((args `(,(coerce-folder-name (value new-mail-folder))
|
|---|
| 1685 | ,@(if stream nil '("-silent"))
|
|---|
| 1686 | "-form" ,(namestring (truename (value mh-scan-line-form)))
|
|---|
| 1687 | "-width" ,(number-string (value fill-column)))))
|
|---|
| 1688 | (message "Incorporating new mail ...")
|
|---|
| 1689 | (mh "inc" args))
|
|---|
| 1690 | (when (value incorporate-new-mail-hook)
|
|---|
| 1691 | (message "Invoking new mail hooks ..."))
|
|---|
| 1692 | (invoke-hook incorporate-new-mail-hook))
|
|---|
| 1693 |
|
|---|
| 1694 |
|
|---|
| 1695 | |
|---|
| 1696 |
|
|---|
| 1697 | ;;;; Deletion.
|
|---|
| 1698 |
|
|---|
| 1699 | (defhvar "Virtual Message Deletion"
|
|---|
| 1700 | "When set, \"Delete Message\" merely MARK's a message into the
|
|---|
| 1701 | \"hemlockdeleted\" sequence; otherwise, RMM is invoked."
|
|---|
| 1702 | :value t)
|
|---|
| 1703 |
|
|---|
| 1704 | (defcommand "Delete Message and Show Next" (p)
|
|---|
| 1705 | "Delete message and show next undeleted message.
|
|---|
| 1706 | This command is only valid in a headers buffer or a message buffer
|
|---|
| 1707 | associated with some headers buffer. The current message is deleted, and
|
|---|
| 1708 | the next undeleted one is shown."
|
|---|
| 1709 | "Delete the current message and show the next undeleted one."
|
|---|
| 1710 | (declare (ignore p))
|
|---|
| 1711 | (let ((hinfo (value headers-information))
|
|---|
| 1712 | (minfo (value message-information)))
|
|---|
| 1713 | (cond (hinfo
|
|---|
| 1714 | (multiple-value-bind (cur-msg cur-mark)
|
|---|
| 1715 | (headers-current-message hinfo)
|
|---|
| 1716 | (unless cur-msg (editor-error "Not on a header line."))
|
|---|
| 1717 | (delete-mark cur-mark)
|
|---|
| 1718 | (delete-message (headers-info-folder hinfo) cur-msg)))
|
|---|
| 1719 | (minfo
|
|---|
| 1720 | (delete-message (message-info-folder minfo)
|
|---|
| 1721 | (message-info-msgs minfo)))
|
|---|
| 1722 | (t
|
|---|
| 1723 | (editor-error "Not in a headers or message buffer."))))
|
|---|
| 1724 | (show-message-offset 1 :undeleted))
|
|---|
| 1725 |
|
|---|
| 1726 | (defcommand "Delete Message and Down Line" (p)
|
|---|
| 1727 | "Deletes the current message, moving point to the next line.
|
|---|
| 1728 | When in a headers buffer, deletes the message on the current line. Then it
|
|---|
| 1729 | moves point to the next non-blank line."
|
|---|
| 1730 | "Deletes current message and moves point down a line."
|
|---|
| 1731 | (declare (ignore p))
|
|---|
| 1732 | (let ((hinfo (value headers-information)))
|
|---|
| 1733 | (unless hinfo (editor-error "Not in a headers buffer."))
|
|---|
| 1734 | (multiple-value-bind (cur-msg cur-mark)
|
|---|
| 1735 | (headers-current-message hinfo)
|
|---|
| 1736 | (unless cur-msg (editor-error "Not on a header line."))
|
|---|
| 1737 | (delete-message (headers-info-folder hinfo) cur-msg)
|
|---|
| 1738 | (when (line-offset cur-mark 1)
|
|---|
| 1739 | (unless (blank-line-p (mark-line cur-mark))
|
|---|
| 1740 | (move-mark (current-point) cur-mark)))
|
|---|
| 1741 | (delete-mark cur-mark))))
|
|---|
| 1742 |
|
|---|
| 1743 | ;;; "Delete Message" unlike "Headers Delete Message" cannot know for sure
|
|---|
| 1744 | ;;; which message id's have been deleted, so when virtual message deletion
|
|---|
| 1745 | ;;; is not used, we cannot use DELETE-HEADERS-BUFFER-LINE to keep headers
|
|---|
| 1746 | ;;; buffers consistent. However, the message id's in the buffer (if deleted)
|
|---|
| 1747 | ;;; will generate MH errors if operations are attempted with them, and
|
|---|
| 1748 | ;;; if the user ever packs the folder with "Expunge Messages", the headers
|
|---|
| 1749 | ;;; buffer will be updated.
|
|---|
| 1750 | ;;;
|
|---|
| 1751 | (defcommand "Delete Message" (p)
|
|---|
| 1752 | "Prompts for a folder, messages to delete, and pick expression. When in
|
|---|
| 1753 | a headers buffer into the same folder specified, the messages prompt
|
|---|
| 1754 | defaults to those messages in the buffer; \"all\" may be entered if this is
|
|---|
| 1755 | not what is desired. When \"Virtual Message Deletion\" is set, messages are
|
|---|
| 1756 | only MARK'ed for deletion. See \"Expunge Messages\". When this feature is
|
|---|
| 1757 | not used, headers and message buffers message id's my not be consistent
|
|---|
| 1758 | with MH."
|
|---|
| 1759 | "Prompts for a folder and message to delete. When \"Virtual Message
|
|---|
| 1760 | Deletion\" is set, messages are only MARK'ed for deletion. See \"Expunge
|
|---|
| 1761 | Messages\"."
|
|---|
| 1762 | (declare (ignore p))
|
|---|
| 1763 | (let* ((folder (prompt-for-folder))
|
|---|
| 1764 | (hinfo (value headers-information))
|
|---|
| 1765 | (temp-msgs (prompt-for-message
|
|---|
| 1766 | :folder folder
|
|---|
| 1767 | :messages
|
|---|
| 1768 | (if (and hinfo
|
|---|
| 1769 | (string= folder
|
|---|
| 1770 | (the simple-string
|
|---|
| 1771 | (headers-info-folder hinfo))))
|
|---|
| 1772 | (headers-info-msg-strings hinfo))
|
|---|
| 1773 | :prompt "MH messages to pick from: "))
|
|---|
| 1774 | (pick-exp (prompt-for-pick-expression))
|
|---|
| 1775 | (msgs (pick-messages folder temp-msgs pick-exp))
|
|---|
| 1776 | (virtually (value virtual-message-deletion)))
|
|---|
| 1777 | (declare (simple-string folder))
|
|---|
| 1778 | (if virtually
|
|---|
| 1779 | (mh "mark" `(,folder ,@msgs "-sequence" "hemlockdeleted" "-add"))
|
|---|
| 1780 | (mh "rmm" `(,folder ,@msgs)))
|
|---|
| 1781 | (if virtually
|
|---|
| 1782 | (let ((deleted-seq (mh-sequence-list folder "hemlockdeleted")))
|
|---|
| 1783 | (when deleted-seq
|
|---|
| 1784 | (do-headers-buffers (hbuf folder)
|
|---|
| 1785 | (with-writable-buffer (hbuf)
|
|---|
| 1786 | (note-deleted-headers hbuf deleted-seq)))))
|
|---|
| 1787 | (do-headers-buffers (hbuf folder hinfo)
|
|---|
| 1788 | (do-headers-lines (hbuf :line-var line :mark-var hmark)
|
|---|
| 1789 | (when (member (line-message-id line) msgs :test #'string=)
|
|---|
| 1790 | (delete-headers-buffer-line hinfo hmark)))))))
|
|---|
| 1791 |
|
|---|
| 1792 | (defcommand "Headers Delete Message" (p)
|
|---|
| 1793 | "Delete current message.
|
|---|
| 1794 | When in a headers buffer, deletes the message on the current line. When
|
|---|
| 1795 | in a message buffer, deletes that message. When \"Virtual Message
|
|---|
| 1796 | Deletion\" is set, messages are only MARK'ed for deletion. See \"Expunge
|
|---|
| 1797 | Messages\"."
|
|---|
| 1798 | "When in a headers buffer, deletes the message on the current line. When
|
|---|
| 1799 | in a message buffer, deletes that message. When \"Virtual Message
|
|---|
| 1800 | Deletion\" is set, messages are only MARK'ed for deletion. See \"Expunge
|
|---|
| 1801 | Messages\"."
|
|---|
| 1802 | (declare (ignore p))
|
|---|
| 1803 | (let ((hinfo (value headers-information))
|
|---|
| 1804 | (minfo (value message-information)))
|
|---|
| 1805 | (cond (hinfo
|
|---|
| 1806 | (multiple-value-bind (cur-msg cur-mark)
|
|---|
| 1807 | (headers-current-message hinfo)
|
|---|
| 1808 | (unless cur-msg (editor-error "Not on a header line."))
|
|---|
| 1809 | (delete-mark cur-mark)
|
|---|
| 1810 | (delete-message (headers-info-folder hinfo) cur-msg)))
|
|---|
| 1811 | (minfo
|
|---|
| 1812 | (let ((msgs (message-info-msgs minfo)))
|
|---|
| 1813 | (delete-message (message-info-folder minfo)
|
|---|
| 1814 | (if (consp msgs) (car msgs) msgs)))
|
|---|
| 1815 | (message "Message deleted."))
|
|---|
| 1816 | (t (editor-error "Not in a headers or message buffer.")))))
|
|---|
| 1817 |
|
|---|
| 1818 | ;;; DELETE-MESSAGE takes a folder and message id and either flags this message
|
|---|
| 1819 | ;;; for deletion or deletes it. All headers buffers into folder are updated,
|
|---|
| 1820 | ;;; either by flagging a headers line or deleting it.
|
|---|
| 1821 | ;;;
|
|---|
| 1822 | (defun delete-message (folder msg)
|
|---|
| 1823 | (cond ((value virtual-message-deletion)
|
|---|
| 1824 | (mark-one-message folder msg "hemlockdeleted" :add)
|
|---|
| 1825 | (do-headers-buffers (hbuf folder)
|
|---|
| 1826 | (with-headers-mark (hmark hbuf msg)
|
|---|
| 1827 | (with-writable-buffer (hbuf)
|
|---|
| 1828 | (note-deleted-message-at-mark hmark)))))
|
|---|
| 1829 | (t (mh "rmm" (list folder msg))
|
|---|
| 1830 | (do-headers-buffers (hbuf folder hinfo)
|
|---|
| 1831 | (with-headers-mark (hmark hbuf msg)
|
|---|
| 1832 | (delete-headers-buffer-line hinfo hmark)))))
|
|---|
| 1833 | (dolist (b *buffer-list*)
|
|---|
| 1834 | (when (and (hemlock-bound-p 'message-information :buffer b)
|
|---|
| 1835 | (buffer-modeline-field-p b :deleted-message))
|
|---|
| 1836 | (dolist (w (buffer-windows b))
|
|---|
| 1837 | (update-modeline-field b w :deleted-message)))))
|
|---|
| 1838 |
|
|---|
| 1839 | ;;; NOTE-DELETED-MESSAGE-AT-MARK takes a mark at the beginning of a valid
|
|---|
| 1840 | ;;; headers line, sticks a "D" on the line, and frobs the line's deleted
|
|---|
| 1841 | ;;; property. This assumes the headers buffer is modifiable.
|
|---|
| 1842 | ;;;
|
|---|
| 1843 | (defun note-deleted-message-at-mark (mark)
|
|---|
| 1844 | (find-attribute mark :digit)
|
|---|
| 1845 | (find-attribute mark :digit #'zerop)
|
|---|
| 1846 | (character-offset mark 2)
|
|---|
| 1847 | (setf (next-character mark) #\D)
|
|---|
| 1848 | (setf (line-message-deleted (mark-line mark)) t))
|
|---|
| 1849 |
|
|---|
| 1850 | ;;; DELETE-HEADERS-BUFFER-LINE takes a headers information and a mark on the
|
|---|
| 1851 | ;;; line to be deleted. Before deleting the line, we check to see if any
|
|---|
| 1852 | ;;; message or draft buffers refer to the buffer because of the line. Due
|
|---|
| 1853 | ;;; to how regions are deleted, line plists get messed up, so they have to
|
|---|
| 1854 | ;;; be regenerated. We regenerate them for the whole buffer, so we don't have
|
|---|
| 1855 | ;;; to hack the code to know which lines got messed up.
|
|---|
| 1856 | ;;;
|
|---|
| 1857 | (defun delete-headers-buffer-line (hinfo hmark)
|
|---|
| 1858 | (delete-headers-line-references hinfo hmark)
|
|---|
| 1859 | (let ((id (line-message-id (mark-line hmark)))
|
|---|
| 1860 | (hbuf (headers-info-buffer hinfo)))
|
|---|
| 1861 | (with-writable-buffer (hbuf)
|
|---|
| 1862 | (with-mark ((end (line-start hmark) :left-inserting))
|
|---|
| 1863 | (unless (line-offset end 1 0) (buffer-end end))
|
|---|
| 1864 | (delete-region (region hmark end))))
|
|---|
| 1865 | (let ((seq (mh-sequence-delete id (headers-info-msg-seq hinfo))))
|
|---|
| 1866 | (setf (headers-info-msg-seq hinfo) seq)
|
|---|
| 1867 | (setf (headers-info-msg-strings hinfo) (mh-sequence-strings seq)))
|
|---|
| 1868 | (set-message-headers-ids hbuf)
|
|---|
| 1869 | (when (value virtual-message-deletion)
|
|---|
| 1870 | (let ((deleted-seq (mh-sequence-list (headers-info-folder hinfo)
|
|---|
| 1871 | "hemlockdeleted")))
|
|---|
| 1872 | (do-headers-lines (hbuf :line-var line)
|
|---|
| 1873 | (setf (line-message-deleted line)
|
|---|
| 1874 | (mh-sequence-member-p (line-message-id line) deleted-seq)))))))
|
|---|
| 1875 |
|
|---|
| 1876 |
|
|---|
| 1877 | ;;; DELETE-HEADERS-LINE-REFERENCES removes any message buffer or draft buffer
|
|---|
| 1878 | ;;; pointers to a headers buffer or marks into the headers buffer. Currently
|
|---|
| 1879 | ;;; message buffers and draft buffers are identified differently for no good
|
|---|
| 1880 | ;;; reason; probably message buffers should be located in the same way draft
|
|---|
| 1881 | ;;; buffers are. Also, we currently assume only one of other-msg-bufs could
|
|---|
| 1882 | ;;; refer to the line (similarly for draft-bufs), but this might be bug
|
|---|
| 1883 | ;;; prone. The message buffer case couldn't happen since the buffer name
|
|---|
| 1884 | ;;; would cause MAYBE-MAKE-MH-BUFFER to re-use the buffer, but you could reply
|
|---|
| 1885 | ;;; to the same message twice simultaneously.
|
|---|
| 1886 | ;;;
|
|---|
| 1887 | (defun delete-headers-line-references (hinfo hmark)
|
|---|
| 1888 | (let ((msg-id (line-message-id (mark-line hmark)))
|
|---|
| 1889 | (main-msg-buf (headers-info-msg-buffer hinfo)))
|
|---|
| 1890 | (declare (simple-string msg-id))
|
|---|
| 1891 | (when main-msg-buf
|
|---|
| 1892 | (let ((minfo (variable-value 'message-information :buffer main-msg-buf)))
|
|---|
| 1893 | (when (string= (the simple-string (message-info-msgs minfo))
|
|---|
| 1894 | msg-id)
|
|---|
| 1895 | (cond ((message-info-draft-buf minfo)
|
|---|
| 1896 | (cleanup-headers-reference main-msg-buf minfo)
|
|---|
| 1897 | (setf (headers-info-msg-buffer hinfo) nil))
|
|---|
| 1898 | (t (delete-buffer-if-possible main-msg-buf))))))
|
|---|
| 1899 | (dolist (mbuf (headers-info-other-msg-bufs hinfo))
|
|---|
| 1900 | (let ((minfo (variable-value 'message-information :buffer mbuf)))
|
|---|
| 1901 | (when (string= (the simple-string (message-info-msgs minfo))
|
|---|
| 1902 | msg-id)
|
|---|
| 1903 | (cond ((message-info-draft-buf minfo)
|
|---|
| 1904 | (cleanup-headers-reference mbuf minfo)
|
|---|
| 1905 | (setf (headers-info-other-msg-bufs hinfo)
|
|---|
| 1906 | (delete mbuf (headers-info-other-msg-bufs hinfo)
|
|---|
| 1907 | :test #'eq)))
|
|---|
| 1908 | (t (delete-buffer-if-possible mbuf)))
|
|---|
| 1909 | (return)))))
|
|---|
| 1910 | (dolist (dbuf (headers-info-draft-bufs hinfo))
|
|---|
| 1911 | (let ((dinfo (variable-value 'draft-information :buffer dbuf)))
|
|---|
| 1912 | (when (same-line-p (draft-info-headers-mark dinfo) hmark)
|
|---|
| 1913 | (cleanup-headers-reference dbuf dinfo)
|
|---|
| 1914 | (setf (headers-info-draft-bufs hinfo)
|
|---|
| 1915 | (delete dbuf (headers-info-draft-bufs hinfo) :test #'eq))
|
|---|
| 1916 | (return)))))
|
|---|
| 1917 |
|
|---|
| 1918 |
|
|---|
| 1919 | (defcommand "Undelete Message" (p)
|
|---|
| 1920 | "Prompts for a folder, messages to undelete, and pick expression. When in
|
|---|
| 1921 | a headers buffer into the same folder specified, the messages prompt
|
|---|
| 1922 | defaults to those messages in the buffer; \"all\" may be entered if this is
|
|---|
| 1923 | not what is desired. This command is only meaningful if you have
|
|---|
| 1924 | \"Virtual Message Deletion\" set."
|
|---|
| 1925 | "Prompts for a folder, messages to undelete, and pick expression. When in
|
|---|
| 1926 | a headers buffer into the same folder specified, the messages prompt
|
|---|
| 1927 | defaults to those messages in the buffer; \"all\" may be entered if this is
|
|---|
| 1928 | not what is desired. This command is only meaningful if you have
|
|---|
| 1929 | \"Virtual Message Deletion\" set."
|
|---|
| 1930 | (declare (ignore p))
|
|---|
| 1931 | (unless (value virtual-message-deletion)
|
|---|
| 1932 | (editor-error "You don't use virtual message deletion."))
|
|---|
| 1933 | (let* ((folder (prompt-for-folder))
|
|---|
| 1934 | (hinfo (value headers-information))
|
|---|
| 1935 | (temp-msgs (prompt-for-message
|
|---|
| 1936 | :folder folder
|
|---|
| 1937 | :messages
|
|---|
| 1938 | (if (and hinfo
|
|---|
| 1939 | (string= folder
|
|---|
| 1940 | (the simple-string
|
|---|
| 1941 | (headers-info-folder hinfo))))
|
|---|
| 1942 | (headers-info-msg-strings hinfo))
|
|---|
| 1943 | :prompt "MH messages to pick from: "))
|
|---|
| 1944 | (pick-exp (prompt-for-pick-expression))
|
|---|
| 1945 | (msgs (if pick-exp
|
|---|
| 1946 | (or (pick-messages folder temp-msgs pick-exp) temp-msgs)
|
|---|
| 1947 | temp-msgs)))
|
|---|
| 1948 | (declare (simple-string folder))
|
|---|
| 1949 | (mh "mark" `(,folder ,@msgs "-sequence" "hemlockdeleted" "-delete"))
|
|---|
| 1950 | (let ((deleted-seq (mh-sequence-list folder "hemlockdeleted")))
|
|---|
| 1951 | (do-headers-buffers (hbuf folder)
|
|---|
| 1952 | (with-writable-buffer (hbuf)
|
|---|
| 1953 | (do-headers-lines (hbuf :line-var line :mark-var hmark)
|
|---|
| 1954 | (when (and (line-message-deleted line)
|
|---|
| 1955 | (not (mh-sequence-member-p (line-message-id line)
|
|---|
| 1956 | deleted-seq)))
|
|---|
| 1957 | (note-undeleted-message-at-mark hmark))))))))
|
|---|
| 1958 |
|
|---|
| 1959 | (defcommand "Headers Undelete Message" (p)
|
|---|
| 1960 | "Undelete the current message.
|
|---|
| 1961 | When in a headers buffer, undeletes the message on the current line. When
|
|---|
| 1962 | in a message buffer, undeletes that message. This command is only
|
|---|
| 1963 | meaningful if you have \"Virtual Message Deletion\" set."
|
|---|
| 1964 | "When in a headers buffer, undeletes the message on the current line. When
|
|---|
| 1965 | in a message buffer, undeletes that message. This command is only
|
|---|
| 1966 | meaningful if you have \"Virtual Message Deletion\" set."
|
|---|
| 1967 | (declare (ignore p))
|
|---|
| 1968 | (unless (value virtual-message-deletion)
|
|---|
| 1969 | (editor-error "You don't use virtual message deletion."))
|
|---|
| 1970 | (let ((hinfo (value headers-information))
|
|---|
| 1971 | (minfo (value message-information)))
|
|---|
| 1972 | (cond (hinfo
|
|---|
| 1973 | (multiple-value-bind (cur-msg cur-mark)
|
|---|
| 1974 | (headers-current-message hinfo)
|
|---|
| 1975 | (unless cur-msg (editor-error "Not on a header line."))
|
|---|
| 1976 | (delete-mark cur-mark)
|
|---|
| 1977 | (undelete-message (headers-info-folder hinfo) cur-msg)))
|
|---|
| 1978 | (minfo
|
|---|
| 1979 | (undelete-message (message-info-folder minfo)
|
|---|
| 1980 | (message-info-msgs minfo))
|
|---|
| 1981 | (message "Message undeleted."))
|
|---|
| 1982 | (t (editor-error "Not in a headers or message buffer.")))))
|
|---|
| 1983 |
|
|---|
| 1984 | ;;; UNDELETE-MESSAGE takes a folder and a message id. All headers buffers into
|
|---|
| 1985 | ;;; folder are updated.
|
|---|
| 1986 | ;;;
|
|---|
| 1987 | (defun undelete-message (folder msg)
|
|---|
| 1988 | (mark-one-message folder msg "hemlockdeleted" :delete)
|
|---|
| 1989 | (do-headers-buffers (hbuf folder)
|
|---|
| 1990 | (with-headers-mark (hmark hbuf msg)
|
|---|
| 1991 | (with-writable-buffer (hbuf)
|
|---|
| 1992 | (note-undeleted-message-at-mark hmark))))
|
|---|
| 1993 | (dolist (b *buffer-list*)
|
|---|
| 1994 | (when (and (hemlock-bound-p 'message-information :buffer b)
|
|---|
| 1995 | (buffer-modeline-field-p b :deleted-message))
|
|---|
| 1996 | (dolist (w (buffer-windows b))
|
|---|
| 1997 | (update-modeline-field b w :deleted-message)))))
|
|---|
| 1998 |
|
|---|
| 1999 | ;;; NOTE-UNDELETED-MESSAGE-AT-MARK takes a mark at the beginning of a valid
|
|---|
| 2000 | ;;; headers line, sticks a space on the line in place of a "D", and frobs the
|
|---|
| 2001 | ;;; line's deleted property. This assumes the headers buffer is modifiable.
|
|---|
| 2002 | ;;;
|
|---|
| 2003 | (defun note-undeleted-message-at-mark (hmark)
|
|---|
| 2004 | (find-attribute hmark :digit)
|
|---|
| 2005 | (find-attribute hmark :digit #'zerop)
|
|---|
| 2006 | (character-offset hmark 2)
|
|---|
| 2007 | (setf (next-character hmark) #\space)
|
|---|
| 2008 | (setf (line-message-deleted (mark-line hmark)) nil))
|
|---|
| 2009 |
|
|---|
| 2010 |
|
|---|
| 2011 | (defcommand "Expunge Messages" (p)
|
|---|
| 2012 | "Expunges messages marked for deletion.
|
|---|
| 2013 | This command prompts for a folder, invoking RMM on the \"hemlockdeleted\"
|
|---|
| 2014 | sequence after asking the user for confirmation. Setting \"Quit Headers
|
|---|
| 2015 | Confirm\" to nil inhibits prompting. The folder's message id's are packed
|
|---|
| 2016 | with FOLDER -pack. When in a headers buffer, uses that folder. When in a
|
|---|
| 2017 | message buffer, uses its folder, updating any associated headers buffer.
|
|---|
| 2018 | When \"Temporary Draft Folder\" is bound, this folder's messages are deleted
|
|---|
| 2019 | and expunged."
|
|---|
| 2020 | "Prompts for a folder, invoking RMM on the \"hemlockdeleted\" sequence and
|
|---|
| 2021 | packing the message id's with FOLDER -pack. When in a headers buffer,
|
|---|
| 2022 | uses that folder."
|
|---|
| 2023 | (declare (ignore p))
|
|---|
| 2024 | (let* ((hinfo (value headers-information))
|
|---|
| 2025 | (minfo (value message-information))
|
|---|
| 2026 | (folder (cond (hinfo (headers-info-folder hinfo))
|
|---|
| 2027 | (minfo (message-info-folder minfo))
|
|---|
| 2028 | (t (prompt-for-folder))))
|
|---|
| 2029 | (deleted-seq (mh-sequence-list folder "hemlockdeleted")))
|
|---|
| 2030 | ;;
|
|---|
| 2031 | ;; Delete the messages if there are any.
|
|---|
| 2032 | ;; This deletes "hemlockdeleted" from sequence file; we don't have to.
|
|---|
| 2033 | (when (and deleted-seq
|
|---|
| 2034 | (or (not (value expunge-messages-confirm))
|
|---|
| 2035 | (prompt-for-y-or-n
|
|---|
| 2036 | :prompt (list "Expunge messages and pack folder ~A? "
|
|---|
| 2037 | folder)
|
|---|
| 2038 | :default t
|
|---|
| 2039 | :default-string "Y")))
|
|---|
| 2040 | (message "Deleting messages ...")
|
|---|
| 2041 | (mh "rmm" (list folder "hemlockdeleted"))
|
|---|
| 2042 | ;;
|
|---|
| 2043 | ;; Compact the message id's after deletion.
|
|---|
| 2044 | (let ((*standard-output* *mh-utility-bit-bucket*))
|
|---|
| 2045 | (message "Compacting folder ...")
|
|---|
| 2046 | (mh "folder" (list folder "-fast" "-pack")))
|
|---|
| 2047 | ;;
|
|---|
| 2048 | ;; Do a bunch of consistency maintenance.
|
|---|
| 2049 | (let ((new-buf-p (eq (current-buffer) *new-mail-buffer*)))
|
|---|
| 2050 | (message "Maintaining consistency ...")
|
|---|
| 2051 | (expunge-messages-fold-headers-buffers folder)
|
|---|
| 2052 | (expunge-messages-fix-draft-buffers folder)
|
|---|
| 2053 | (expunge-messages-fix-unseen-headers folder)
|
|---|
| 2054 | (when new-buf-p (change-to-buffer *new-mail-buffer*)))
|
|---|
| 2055 | (delete-and-expunge-temp-drafts))))
|
|---|
| 2056 |
|
|---|
| 2057 | ;;; EXPUNGE-MESSAGES-FOLD-HEADERS-BUFFERS deletes all headers buffers into the
|
|---|
| 2058 | ;;; compacted folder. We can only update the headers buffers by installing all
|
|---|
| 2059 | ;;; headers, so there may as well be only one such buffer. First we get a list
|
|---|
| 2060 | ;;; of the buffers since DO-HEADERS-BUFFERS is trying to iterate over a list
|
|---|
| 2061 | ;;; being destructively modified by buffer deletions.
|
|---|
| 2062 | ;;;
|
|---|
| 2063 | (defun expunge-messages-fold-headers-buffers (folder)
|
|---|
| 2064 | (let (hbufs)
|
|---|
| 2065 | (declare (list hbufs))
|
|---|
| 2066 | (do-headers-buffers (b folder)
|
|---|
| 2067 | (unless (eq b *new-mail-buffer*)
|
|---|
| 2068 | (push b hbufs)))
|
|---|
| 2069 | (unless (zerop (length hbufs))
|
|---|
| 2070 | (dolist (b hbufs)
|
|---|
| 2071 | (delete-headers-buffer-and-message-buffers-command nil b))
|
|---|
| 2072 | (new-message-headers folder (list "all")))))
|
|---|
| 2073 |
|
|---|
| 2074 | ;;; EXPUNGE-MESSAGES-FIX-DRAFT-BUFFERS finds any draft buffer that was set up
|
|---|
| 2075 | ;;; as a reply to some message in folder, removing this relationship in case
|
|---|
| 2076 | ;;; that message id does not exist after expunge folder compaction.
|
|---|
| 2077 | ;;;
|
|---|
| 2078 | (defun expunge-messages-fix-draft-buffers (folder)
|
|---|
| 2079 | (declare (simple-string folder))
|
|---|
| 2080 | (dolist (b *buffer-list*)
|
|---|
| 2081 | (when (hemlock-bound-p 'draft-information :buffer b)
|
|---|
| 2082 | (let* ((dinfo (variable-value 'draft-information :buffer b))
|
|---|
| 2083 | (reply-folder (draft-info-replied-to-folder dinfo)))
|
|---|
| 2084 | (when (and reply-folder
|
|---|
| 2085 | (string= (the simple-string reply-folder) folder))
|
|---|
| 2086 | (setf (draft-info-replied-to-folder dinfo) nil)
|
|---|
| 2087 | (setf (draft-info-replied-to-msg dinfo) nil))))))
|
|---|
| 2088 |
|
|---|
| 2089 | ;;; EXPUNGE-MESSAGES-FIX-UNSEEN-HEADERS specially handles the unseen headers
|
|---|
| 2090 | ;;; buffer apart from the other headers buffers into the same folder when
|
|---|
| 2091 | ;;; messages have been expunged. We must delete the associated message buffers
|
|---|
| 2092 | ;;; since REVAMP-HEADERS-BUFFER does not, and these potentially reference bad
|
|---|
| 2093 | ;;; message id's. When doing this we must copy the other-msg-bufs list since
|
|---|
| 2094 | ;;; the delete buffer cleanup hook for them is destructive. Then we check for
|
|---|
| 2095 | ;;; more unseen messages.
|
|---|
| 2096 | ;;;
|
|---|
| 2097 | (defun expunge-messages-fix-unseen-headers (folder)
|
|---|
| 2098 | (declare (simple-string folder))
|
|---|
| 2099 | (when *new-mail-buffer*
|
|---|
| 2100 | (let ((hinfo (variable-value 'headers-information
|
|---|
| 2101 | :buffer *new-mail-buffer*)))
|
|---|
| 2102 | (when (string= (the simple-string (headers-info-folder hinfo))
|
|---|
| 2103 | folder)
|
|---|
| 2104 | (let ((other-bufs (copy-list (headers-info-other-msg-bufs hinfo))))
|
|---|
| 2105 | (dolist (b other-bufs) (delete-buffer-if-possible b)))
|
|---|
| 2106 | (with-writable-buffer (*new-mail-buffer*)
|
|---|
| 2107 | (revamp-headers-buffer *new-mail-buffer* hinfo)
|
|---|
| 2108 | ;; Restore the name in case someone used "Pick Headers".
|
|---|
| 2109 | (setf (buffer-name *new-mail-buffer*)
|
|---|
| 2110 | (format nil "Unseen Headers ~A" folder))
|
|---|
| 2111 | (let ((region (maybe-get-new-mail-msg-hdrs folder)))
|
|---|
| 2112 | (when region
|
|---|
| 2113 | (insert-message-headers *new-mail-buffer* hinfo region))))))))
|
|---|
| 2114 |
|
|---|
| 2115 | ;;; MAYBE-GET-NEW-MAIL-MSG-HDRS returns a region suitable for a new mail buffer
|
|---|
| 2116 | ;;; or nil. Folder is probed for unseen headers, and if there are some, then
|
|---|
| 2117 | ;;; we call GET-NEW-MAIL-MSG-HDRS which also uses "Unseen Headers Message Spec".
|
|---|
| 2118 | ;;; If there are no unseen headers, we only look for "Unseen Headers Message
|
|---|
| 2119 | ;;; Spec" messages. We go through these contortions to keep MH from outputting
|
|---|
| 2120 | ;;; errors.
|
|---|
| 2121 | ;;;
|
|---|
| 2122 | (defun maybe-get-new-mail-msg-hdrs (folder)
|
|---|
| 2123 | (let ((unseen-seq-name (mh-profile-component "unseen-sequence")))
|
|---|
| 2124 | (multiple-value-bind (unseen-seq foundp)
|
|---|
| 2125 | (mh-sequence-list folder unseen-seq-name)
|
|---|
| 2126 | (if (and foundp unseen-seq)
|
|---|
| 2127 | (get-new-mail-msg-hdrs folder unseen-seq-name)
|
|---|
| 2128 | (let ((spec (value unseen-headers-message-spec)))
|
|---|
| 2129 | (when spec
|
|---|
| 2130 | (message-headers-to-region
|
|---|
| 2131 | folder
|
|---|
| 2132 | (breakup-message-spec (string-trim '(#\space #\tab) spec)))))))))
|
|---|
| 2133 |
|
|---|
| 2134 |
|
|---|
| 2135 | |
|---|
| 2136 |
|
|---|
| 2137 | ;;;; Folders.
|
|---|
| 2138 |
|
|---|
| 2139 | (defvar *folder-name-table* nil)
|
|---|
| 2140 |
|
|---|
| 2141 | (defun check-folder-name-table ()
|
|---|
| 2142 | (unless *folder-name-table*
|
|---|
| 2143 | (message "Finding folder names ...")
|
|---|
| 2144 | (setf *folder-name-table* (make-string-table))
|
|---|
| 2145 | (let* ((output (with-output-to-string (*standard-output*)
|
|---|
| 2146 | (mh "folders" '("-fast"))))
|
|---|
| 2147 | (length (length output))
|
|---|
| 2148 | (start 0))
|
|---|
| 2149 | (declare (simple-string output))
|
|---|
| 2150 | (loop
|
|---|
| 2151 | (when (> start length) (return))
|
|---|
| 2152 | (let ((nl (position #\newline output :start start)))
|
|---|
| 2153 | (unless nl (return))
|
|---|
| 2154 | (unless (= start nl)
|
|---|
| 2155 | (setf (getstring (subseq output start nl) *folder-name-table*) t))
|
|---|
| 2156 | (setf start (1+ nl)))))))
|
|---|
| 2157 |
|
|---|
| 2158 | (defcommand "List Folders" (p)
|
|---|
| 2159 | "Pop up a list of folders at top-level."
|
|---|
| 2160 | "Pop up a list of folders at top-level."
|
|---|
| 2161 | (declare (ignore p))
|
|---|
| 2162 | (check-folder-name-table)
|
|---|
| 2163 | (with-pop-up-display (s)
|
|---|
| 2164 | (do-strings (f ignore *folder-name-table*)
|
|---|
| 2165 | (declare (ignore ignore))
|
|---|
| 2166 | (write-line f s))))
|
|---|
| 2167 |
|
|---|
| 2168 | (defcommand "Create Folder" (p)
|
|---|
| 2169 | "Creates a folder. If the folder already exists, an error is signaled."
|
|---|
| 2170 | "Creates a folder. If the folder already exists, an error is signaled."
|
|---|
| 2171 | (declare (ignore p))
|
|---|
| 2172 | (let ((folder (prompt-for-folder :must-exist nil)))
|
|---|
| 2173 | (when (folder-existsp folder)
|
|---|
| 2174 | (editor-error "Folder already exists -- ~S!" folder))
|
|---|
| 2175 | (create-folder folder)))
|
|---|
| 2176 |
|
|---|
| 2177 | (defcommand "Delete Folder" (p)
|
|---|
| 2178 | "Prompts for a folder and uses RMF to delete it."
|
|---|
| 2179 | "Prompts for a folder and uses RMF to delete it."
|
|---|
| 2180 | (declare (ignore p))
|
|---|
| 2181 | (let* ((folder (prompt-for-folder))
|
|---|
| 2182 | (*standard-output* *mh-utility-bit-bucket*))
|
|---|
| 2183 | (mh "rmf" (list folder))
|
|---|
| 2184 | ;; RMF doesn't recognize this documented switch.
|
|---|
| 2185 | ;; "-nointeractive"))))
|
|---|
| 2186 | (check-folder-name-table)
|
|---|
| 2187 | (delete-string (strip-folder-name folder) *folder-name-table*)))
|
|---|
| 2188 |
|
|---|
| 2189 |
|
|---|
| 2190 | (defvar *refile-default-destination* nil)
|
|---|
| 2191 |
|
|---|
| 2192 | (defcommand "Refile Message" (p)
|
|---|
| 2193 | "Prompts for a source folder, messages, pick expression, and a destination
|
|---|
| 2194 | folder to refile the messages."
|
|---|
| 2195 | "Prompts for a source folder, messages, pick expression, and a destination
|
|---|
| 2196 | folder to refile the messages."
|
|---|
| 2197 | (declare (ignore p))
|
|---|
| 2198 | (let* ((src-folder (prompt-for-folder :prompt "Source folder: "))
|
|---|
| 2199 | (hinfo (value headers-information))
|
|---|
| 2200 | (temp-msgs (prompt-for-message
|
|---|
| 2201 | :folder src-folder
|
|---|
| 2202 | :messages
|
|---|
| 2203 | (if (and hinfo
|
|---|
| 2204 | (string= src-folder
|
|---|
| 2205 | (the simple-string
|
|---|
| 2206 | (headers-info-folder hinfo))))
|
|---|
| 2207 | (headers-info-msg-strings hinfo))
|
|---|
| 2208 | :prompt "MH messages to pick from: "))
|
|---|
| 2209 | (pick-exp (prompt-for-pick-expression))
|
|---|
| 2210 | ;; Return pick result or temp-msgs individually specified in a list.
|
|---|
| 2211 | (msgs (pick-messages src-folder temp-msgs pick-exp)))
|
|---|
| 2212 | (declare (simple-string src-folder))
|
|---|
| 2213 | (refile-message src-folder msgs
|
|---|
| 2214 | (prompt-for-folder :must-exist nil
|
|---|
| 2215 | :prompt "Destination folder: "
|
|---|
| 2216 | :default *refile-default-destination*))))
|
|---|
| 2217 |
|
|---|
| 2218 | (defcommand "Headers Refile Message" (p)
|
|---|
| 2219 | "Refile the current message.
|
|---|
| 2220 | When in a headers buffer, refiles the message on the current line, and when
|
|---|
| 2221 | in a message buffer, refiles that message, prompting for a destination
|
|---|
| 2222 | folder."
|
|---|
| 2223 | "When in a headers buffer, refiles the message on the current line, and when
|
|---|
| 2224 | in a message buffer, refiles that message, prompting for a destination
|
|---|
| 2225 | folder."
|
|---|
| 2226 | (declare (ignore p))
|
|---|
| 2227 | (let ((hinfo (value headers-information))
|
|---|
| 2228 | (minfo (value message-information)))
|
|---|
| 2229 | (cond (hinfo
|
|---|
| 2230 | (multiple-value-bind (cur-msg cur-mark)
|
|---|
| 2231 | (headers-current-message hinfo)
|
|---|
| 2232 | (unless cur-msg (editor-error "Not on a header line."))
|
|---|
| 2233 | (delete-mark cur-mark)
|
|---|
| 2234 | (refile-message (headers-info-folder hinfo) cur-msg
|
|---|
| 2235 | (prompt-for-folder
|
|---|
| 2236 | :must-exist nil
|
|---|
| 2237 | :prompt "Destination folder: "
|
|---|
| 2238 | :default *refile-default-destination*))))
|
|---|
| 2239 | (minfo
|
|---|
| 2240 | (refile-message
|
|---|
| 2241 | (message-info-folder minfo) (message-info-msgs minfo)
|
|---|
| 2242 | (prompt-for-folder :must-exist nil
|
|---|
| 2243 | :prompt "Destination folder: "
|
|---|
| 2244 | :default *refile-default-destination*))
|
|---|
| 2245 | (message "Message refiled."))
|
|---|
| 2246 | (t
|
|---|
| 2247 | (editor-error "Not in a headers or message buffer.")))))
|
|---|
| 2248 |
|
|---|
| 2249 | ;;; REFILE-MESSAGE refiles msg from src-folder to dst-folder. If dst-buffer
|
|---|
| 2250 | ;;; doesn't exist, the user is prompted for creating it. All headers buffers
|
|---|
| 2251 | ;;; concerning src-folder are updated. When msg is a list, we did a general
|
|---|
| 2252 | ;;; message prompt, and we cannot know which headers lines to delete.
|
|---|
| 2253 | ;;;
|
|---|
| 2254 | (defun refile-message (src-folder msg dst-folder)
|
|---|
| 2255 | (unless (folder-existsp dst-folder)
|
|---|
| 2256 | (cond ((prompt-for-y-or-n
|
|---|
| 2257 | :prompt "Destination folder doesn't exist. Create it? "
|
|---|
| 2258 | :default t :default-string "Y")
|
|---|
| 2259 | (create-folder dst-folder))
|
|---|
| 2260 | (t (editor-error "Not refiling message."))))
|
|---|
| 2261 | (mh "refile" `(,@(if (listp msg) msg (list msg))
|
|---|
| 2262 | "-src" ,src-folder ,dst-folder))
|
|---|
| 2263 | (setf *refile-default-destination* (strip-folder-name dst-folder))
|
|---|
| 2264 | (if (listp msg)
|
|---|
| 2265 | (do-headers-buffers (hbuf src-folder hinfo)
|
|---|
| 2266 | (do-headers-lines (hbuf :line-var line :mark-var hmark)
|
|---|
| 2267 | (when (member (line-message-id line) msg :test #'string=)
|
|---|
| 2268 | (delete-headers-buffer-line hinfo hmark))))
|
|---|
| 2269 | (do-headers-buffers (hbuf src-folder hinfo)
|
|---|
| 2270 | (with-headers-mark (hmark hbuf msg)
|
|---|
| 2271 | (delete-headers-buffer-line hinfo hmark)))))
|
|---|
| 2272 |
|
|---|
| 2273 |
|
|---|
| 2274 | |
|---|
| 2275 |
|
|---|
| 2276 | ;;;; Miscellaneous commands.
|
|---|
| 2277 |
|
|---|
| 2278 | (defcommand "Mark Message" (p)
|
|---|
| 2279 | "Prompts for a folder, message, and sequence. By default the message is
|
|---|
| 2280 | added, but if an argument is supplied, the message is deleted. When in
|
|---|
| 2281 | a headers buffer or message buffer, only a sequence is prompted for."
|
|---|
| 2282 | "Prompts for a folder, message, and sequence. By default the message is
|
|---|
| 2283 | added, but if an argument is supplied, the message is deleted. When in
|
|---|
| 2284 | a headers buffer or message buffer, only a sequence is prompted for."
|
|---|
| 2285 | (let* ((hinfo (value headers-information))
|
|---|
| 2286 | (minfo (value message-information)))
|
|---|
| 2287 | (cond (hinfo
|
|---|
| 2288 | (multiple-value-bind (cur-msg cur-mark)
|
|---|
| 2289 | (headers-current-message hinfo)
|
|---|
| 2290 | (unless cur-msg (editor-error "Not on a header line."))
|
|---|
| 2291 | (delete-mark cur-mark)
|
|---|
| 2292 | (let ((seq-name (prompt-for-string :prompt "Sequence name: "
|
|---|
| 2293 | :trim t)))
|
|---|
| 2294 | (declare (simple-string seq-name))
|
|---|
| 2295 | (when (string= "" seq-name)
|
|---|
| 2296 | (editor-error "Sequence name cannot be empty."))
|
|---|
| 2297 | (mark-one-message (headers-info-folder hinfo)
|
|---|
| 2298 | cur-msg seq-name (if p :delete :add)))))
|
|---|
| 2299 | (minfo
|
|---|
| 2300 | (let ((msgs (message-info-msgs minfo))
|
|---|
| 2301 | (seq-name (prompt-for-string :prompt "Sequence name: "
|
|---|
| 2302 | :trim t)))
|
|---|
| 2303 | (declare (simple-string seq-name))
|
|---|
| 2304 | (when (string= "" seq-name)
|
|---|
| 2305 | (editor-error "Sequence name cannot be empty."))
|
|---|
| 2306 | (mark-one-message (message-info-folder minfo)
|
|---|
| 2307 | (if (consp msgs) (car msgs) msgs)
|
|---|
| 2308 | seq-name (if p :delete :add))))
|
|---|
| 2309 | (t
|
|---|
| 2310 | (let ((folder (prompt-for-folder))
|
|---|
| 2311 | (seq-name (prompt-for-string :prompt "Sequence name: "
|
|---|
| 2312 | :trim t)))
|
|---|
| 2313 | (declare (simple-string seq-name))
|
|---|
| 2314 | (when (string= "" seq-name)
|
|---|
| 2315 | (editor-error "Sequence name cannot be empty."))
|
|---|
| 2316 | (mh "mark" `(,folder ,@(prompt-for-message :folder folder)
|
|---|
| 2317 | "-sequence" ,seq-name
|
|---|
| 2318 | ,(if p "-delete" "-add"))))))))
|
|---|
| 2319 |
|
|---|
| 2320 |
|
|---|
| 2321 | (defcommand "List Mail Buffers" (p)
|
|---|
| 2322 | "Show a list of all mail associated buffers.
|
|---|
| 2323 | If the buffer has an associated message buffer, it is displayed to the right
|
|---|
| 2324 | of the buffer name. If there is no message buffer, but the buffer is
|
|---|
| 2325 | associated with a headers buffer, then it is displayed. If the buffer is
|
|---|
| 2326 | modified then a * is displayed before the name."
|
|---|
| 2327 | "Display the names of all buffers in a with-random-typeout window."
|
|---|
| 2328 | (declare (ignore p))
|
|---|
| 2329 | (let ((buffers nil))
|
|---|
| 2330 | (declare (list buffers))
|
|---|
| 2331 | (do-strings (n b *buffer-names*)
|
|---|
| 2332 | (declare (ignore n))
|
|---|
| 2333 | (unless (eq b *echo-area-buffer*)
|
|---|
| 2334 | (cond ((hemlock-bound-p 'message-buffer :buffer b)
|
|---|
| 2335 | ;; Catches draft buffers associated with message buffers first.
|
|---|
| 2336 | (push (cons b (variable-value 'message-buffer :buffer b))
|
|---|
| 2337 | buffers))
|
|---|
| 2338 | ((hemlock-bound-p 'headers-buffer :buffer b)
|
|---|
| 2339 | ;; Then draft or message buffers associated with headers buffers.
|
|---|
| 2340 | (push (cons b (variable-value 'headers-buffer :buffer b))
|
|---|
| 2341 | buffers))
|
|---|
| 2342 | ((or (hemlock-bound-p 'draft-information :buffer b)
|
|---|
| 2343 | (hemlock-bound-p 'message-information :buffer b)
|
|---|
| 2344 | (hemlock-bound-p 'headers-information :buffer b))
|
|---|
| 2345 | (push b buffers)))))
|
|---|
| 2346 | (with-pop-up-display (s :height (length buffers))
|
|---|
| 2347 | (dolist (ele (nreverse buffers))
|
|---|
| 2348 | (let* ((association (if (consp ele) (cdr ele)))
|
|---|
| 2349 | (b (if association (car ele) ele))
|
|---|
| 2350 | (buffer-pathname (buffer-pathname b))
|
|---|
| 2351 | (buffer-name (buffer-name b)))
|
|---|
| 2352 | (write-char (if (buffer-modified b) #\* #\space) s)
|
|---|
| 2353 | (if buffer-pathname
|
|---|
| 2354 | (format s "~A ~A~:[~;~50T~:*~A~]~%"
|
|---|
| 2355 | (file-namestring buffer-pathname)
|
|---|
| 2356 | (directory-namestring buffer-pathname)
|
|---|
| 2357 | (if association (buffer-name association)))
|
|---|
| 2358 | (format s "~A~:[~;~50T~:*~A~]~%"
|
|---|
| 2359 | buffer-name
|
|---|
| 2360 | (if association (buffer-name association)))))))))
|
|---|
| 2361 |
|
|---|
| 2362 |
|
|---|
| 2363 | (defcommand "Message Help" (p)
|
|---|
| 2364 | "Show this help."
|
|---|
| 2365 | "Show this help."
|
|---|
| 2366 | (declare (ignore p))
|
|---|
| 2367 | (describe-mode-command nil "Message"))
|
|---|
| 2368 |
|
|---|
| 2369 | (defcommand "Headers Help" (p)
|
|---|
| 2370 | "Show this help."
|
|---|
| 2371 | "Show this help."
|
|---|
| 2372 | (declare (ignore p))
|
|---|
| 2373 | (describe-mode-command nil "Headers"))
|
|---|
| 2374 |
|
|---|
| 2375 | (defcommand "Draft Help" (p)
|
|---|
| 2376 | "Show this help."
|
|---|
| 2377 | "Show this help."
|
|---|
| 2378 | (declare (ignore p))
|
|---|
| 2379 | (describe-mode-command nil "Draft"))
|
|---|
| 2380 |
|
|---|
| 2381 |
|
|---|
| 2382 | |
|---|
| 2383 |
|
|---|
| 2384 | ;;;; Prompting.
|
|---|
| 2385 |
|
|---|
| 2386 | ;;; Folder prompting.
|
|---|
| 2387 | ;;;
|
|---|
| 2388 |
|
|---|
| 2389 | (defun prompt-for-folder (&key (must-exist t) (prompt "MH Folder: ")
|
|---|
| 2390 | (default (mh-current-folder)))
|
|---|
| 2391 | "Prompts for a folder, using MH's idea of the current folder as a default.
|
|---|
| 2392 | The result will have a leading + in the name."
|
|---|
| 2393 | (check-folder-name-table)
|
|---|
| 2394 | (let ((folder (prompt-for-keyword (list *folder-name-table*)
|
|---|
| 2395 | :must-exist must-exist :prompt prompt
|
|---|
| 2396 | :default default :default-string default
|
|---|
| 2397 | :help "Enter folder name.")))
|
|---|
| 2398 | (declare (simple-string folder))
|
|---|
| 2399 | (when (string= folder "") (editor-error "Must supply folder!"))
|
|---|
| 2400 | (let ((name (coerce-folder-name folder)))
|
|---|
| 2401 | (when (and must-exist (not (folder-existsp name)))
|
|---|
| 2402 | (editor-error "Folder does not exist -- ~S." name))
|
|---|
| 2403 | name)))
|
|---|
| 2404 |
|
|---|
| 2405 | (defun coerce-folder-name (folder)
|
|---|
| 2406 | (if (char= (schar folder 0) #\+)
|
|---|
| 2407 | folder
|
|---|
| 2408 | (concatenate 'simple-string "+" folder)))
|
|---|
| 2409 |
|
|---|
| 2410 | (defun strip-folder-name (folder)
|
|---|
| 2411 | (if (char= (schar folder 0) #\+)
|
|---|
| 2412 | (subseq folder 1)
|
|---|
| 2413 | folder))
|
|---|
| 2414 |
|
|---|
| 2415 |
|
|---|
| 2416 | ;;; Message prompting.
|
|---|
| 2417 | ;;;
|
|---|
| 2418 |
|
|---|
| 2419 | (defun prompt-for-message (&key (folder (mh-current-folder))
|
|---|
| 2420 | (prompt "MH messages: ")
|
|---|
| 2421 | messages)
|
|---|
| 2422 | "Prompts for a message spec, using messages as a default. If messages is
|
|---|
| 2423 | not supplied, then the current message for folder is used. The result is
|
|---|
| 2424 | a list of strings which are the message ids, intervals, and/or sequence
|
|---|
| 2425 | names the user entered."
|
|---|
| 2426 | (let* ((cur-msg (cond ((not messages) (mh-current-message folder))
|
|---|
| 2427 | ((stringp messages) messages)
|
|---|
| 2428 | ((consp messages)
|
|---|
| 2429 | (if (= (length (the list messages)) 1)
|
|---|
| 2430 | (car messages)
|
|---|
| 2431 | (format nil "~{~A~^ ~}" messages))))))
|
|---|
| 2432 | (breakup-message-spec (prompt-for-string :prompt prompt
|
|---|
| 2433 | :default cur-msg
|
|---|
| 2434 | :default-string cur-msg
|
|---|
| 2435 | :trim t
|
|---|
| 2436 | :help "Enter MH message id(s)."))))
|
|---|
| 2437 |
|
|---|
| 2438 | (defun breakup-message-spec (msgs)
|
|---|
| 2439 | (declare (simple-string msgs))
|
|---|
| 2440 | (let ((start 0)
|
|---|
| 2441 | (result nil))
|
|---|
| 2442 | (loop
|
|---|
| 2443 | (let ((end (position #\space msgs :start start :test #'char=)))
|
|---|
| 2444 | (unless end
|
|---|
| 2445 | (return (if (zerop start)
|
|---|
| 2446 | (list msgs)
|
|---|
| 2447 | (nreverse (cons (subseq msgs start) result)))))
|
|---|
| 2448 | (push (subseq msgs start end) result)
|
|---|
| 2449 | (setf start (1+ end))))))
|
|---|
| 2450 |
|
|---|
| 2451 |
|
|---|
| 2452 | ;;; PICK expression prompting.
|
|---|
| 2453 | ;;;
|
|---|
| 2454 |
|
|---|
| 2455 | (defhvar "MH Lisp Expression"
|
|---|
| 2456 | "When this is set (the default), MH expression prompts are read in a Lisp
|
|---|
| 2457 | syntax. Otherwise, the input is as if it had been entered on a shell
|
|---|
| 2458 | command line."
|
|---|
| 2459 | :value t)
|
|---|
| 2460 |
|
|---|
| 2461 | ;;; This is dynamically bound to nil for argument processing routines.
|
|---|
| 2462 | ;;;
|
|---|
| 2463 | (defvar *pick-expression-strings* nil)
|
|---|
| 2464 |
|
|---|
| 2465 | (defun prompt-for-pick-expression ()
|
|---|
| 2466 | "Prompts for an MH PICK-like expression that is converted to a list of
|
|---|
| 2467 | strings suitable for EXT:RUN-PROGRAM. As a second value, the user's
|
|---|
| 2468 | expression is as typed in is returned."
|
|---|
| 2469 | (let ((exp (prompt-for-string :prompt "MH expression: "
|
|---|
| 2470 | :help "Expression to PICK over mail messages."
|
|---|
| 2471 | :trim t))
|
|---|
| 2472 | (*pick-expression-strings* nil))
|
|---|
| 2473 | (if (value mh-lisp-expression)
|
|---|
| 2474 | (let ((exp (let ((*package* *keyword-package*))
|
|---|
| 2475 | (read-from-string exp))))
|
|---|
| 2476 | (if exp
|
|---|
| 2477 | (if (consp exp)
|
|---|
| 2478 | (lisp-to-pick-expression exp)
|
|---|
| 2479 | (editor-error "Lisp PICK expressions cannot be atomic."))))
|
|---|
| 2480 | (expand-mh-pick-spec exp))
|
|---|
| 2481 | (values (nreverse *pick-expression-strings*)
|
|---|
| 2482 | exp)))
|
|---|
| 2483 |
|
|---|
| 2484 | (defun lisp-to-pick-expression (exp)
|
|---|
| 2485 | (ecase (car exp)
|
|---|
| 2486 | (:and (lpe-and/or exp "-and"))
|
|---|
| 2487 | (:or (lpe-and/or exp "-or"))
|
|---|
| 2488 | (:not (push "-not" *pick-expression-strings*)
|
|---|
| 2489 | (let ((nexp (cadr exp)))
|
|---|
| 2490 | (unless (consp nexp) (editor-error "Bad expression -- ~S" nexp))
|
|---|
| 2491 | (lisp-to-pick-expression nexp)))
|
|---|
| 2492 |
|
|---|
| 2493 | (:cc (lpe-output-and-go exp "-cc"))
|
|---|
| 2494 | (:date (lpe-output-and-go exp "-date"))
|
|---|
| 2495 | (:from (lpe-output-and-go exp "-from"))
|
|---|
| 2496 | (:search (lpe-output-and-go exp "-search"))
|
|---|
| 2497 | (:subject (lpe-output-and-go exp "-subject"))
|
|---|
| 2498 | (:to (lpe-output-and-go exp "-to"))
|
|---|
| 2499 | (:-- (lpe-output-and-go (cdr exp)
|
|---|
| 2500 | (concatenate 'simple-string
|
|---|
| 2501 | "--" (string (cadr exp)))))
|
|---|
| 2502 |
|
|---|
| 2503 | (:before (lpe-after-and-before exp "-before"))
|
|---|
| 2504 | (:after (lpe-after-and-before exp "-after"))
|
|---|
| 2505 | (:datefield (lpe-output-and-go exp "-datefield"))))
|
|---|
| 2506 |
|
|---|
| 2507 | (defun lpe-after-and-before (exp op)
|
|---|
| 2508 | (let ((operand (cadr exp)))
|
|---|
| 2509 | (when (numberp operand)
|
|---|
| 2510 | (setf (cadr exp)
|
|---|
| 2511 | (if (plusp operand)
|
|---|
| 2512 | (number-string (- operand))
|
|---|
| 2513 | (number-string operand)))))
|
|---|
| 2514 | (lpe-output-and-go exp op))
|
|---|
| 2515 |
|
|---|
| 2516 | (defun lpe-output-and-go (exp op)
|
|---|
| 2517 | (push op *pick-expression-strings*)
|
|---|
| 2518 | (let ((operand (cadr exp)))
|
|---|
| 2519 | (etypecase operand
|
|---|
| 2520 | (string (push operand *pick-expression-strings*))
|
|---|
| 2521 | (symbol (push (symbol-name operand)
|
|---|
| 2522 | *pick-expression-strings*)))))
|
|---|
| 2523 |
|
|---|
| 2524 | (defun lpe-and/or (exp op)
|
|---|
| 2525 | (push "-lbrace" *pick-expression-strings*)
|
|---|
| 2526 | (dolist (ele (cdr exp))
|
|---|
| 2527 | (lisp-to-pick-expression ele)
|
|---|
| 2528 | (push op *pick-expression-strings*))
|
|---|
| 2529 | (pop *pick-expression-strings*) ;Clear the extra "-op" arg.
|
|---|
| 2530 | (push "-rbrace" *pick-expression-strings*))
|
|---|
| 2531 |
|
|---|
| 2532 | ;;; EXPAND-MH-PICK-SPEC takes a string of "words" assumed to be separated
|
|---|
| 2533 | ;;; by single spaces. If a "word" starts with a quotation mark, then
|
|---|
| 2534 | ;;; everything is grabbed up to the next one and used as a single word.
|
|---|
| 2535 | ;;; Currently, this does not worry about extra spaces (or tabs) between
|
|---|
| 2536 | ;;; "words".
|
|---|
| 2537 | ;;;
|
|---|
| 2538 | (defun expand-mh-pick-spec (spec)
|
|---|
| 2539 | (declare (simple-string spec))
|
|---|
| 2540 | (let ((start 0))
|
|---|
| 2541 | (loop
|
|---|
| 2542 | (let ((end (position #\space spec :start start :test #'char=)))
|
|---|
| 2543 | (unless end
|
|---|
| 2544 | (if (zerop start)
|
|---|
| 2545 | (setf *pick-expression-strings* (list spec))
|
|---|
| 2546 | (push (subseq spec start) *pick-expression-strings*))
|
|---|
| 2547 | (return))
|
|---|
| 2548 | (cond ((char= #\" (schar spec start))
|
|---|
| 2549 | (setf end (position #\" spec :start (1+ start) :test #'char=))
|
|---|
| 2550 | (unless end (editor-error "Bad quoting syntax."))
|
|---|
| 2551 | (push (subseq spec (1+ start) end) *pick-expression-strings*)
|
|---|
| 2552 | (setf start (+ end 2)))
|
|---|
| 2553 | (t (push (subseq spec start end) *pick-expression-strings*)
|
|---|
| 2554 | (setf start (1+ end))))))))
|
|---|
| 2555 |
|
|---|
| 2556 |
|
|---|
| 2557 | ;;; Password prompting.
|
|---|
| 2558 | ;;;
|
|---|
| 2559 |
|
|---|
| 2560 | (defun prompt-for-password (&optional (prompt "Password: "))
|
|---|
| 2561 | "Prompts for password with prompt."
|
|---|
| 2562 | (let ((hi::*parse-verification-function* #'(lambda (string) (list string))))
|
|---|
| 2563 | (let ((hi::*parse-prompt* prompt))
|
|---|
| 2564 | (hi::display-prompt-nicely))
|
|---|
| 2565 | (let ((start-window (current-window)))
|
|---|
| 2566 | (move-mark *parse-starting-mark* (buffer-point *echo-area-buffer*))
|
|---|
| 2567 | (setf (current-window) *echo-area-window*)
|
|---|
| 2568 | (unwind-protect
|
|---|
| 2569 | (use-buffer *echo-area-buffer*
|
|---|
| 2570 | (let ((result ()))
|
|---|
| 2571 | (declare (list result))
|
|---|
| 2572 | (loop
|
|---|
| 2573 | (let ((key-event (get-key-event *editor-input*)))
|
|---|
| 2574 | (ring-pop hi::*key-event-history*)
|
|---|
| 2575 | (cond ((eq key-event #k"return")
|
|---|
| 2576 | (return (prog1 (coerce (nreverse result)
|
|---|
| 2577 | 'simple-string)
|
|---|
| 2578 | (fill result nil))))
|
|---|
| 2579 | ((or (eq key-event #k"control-u")
|
|---|
| 2580 | (eq key-event #k"control-U"))
|
|---|
| 2581 | (setf result nil))
|
|---|
| 2582 | (t (push (ext:key-event-char key-event) result)))))))
|
|---|
| 2583 | (setf (current-window) start-window)))))
|
|---|
| 2584 |
|
|---|
| 2585 |
|
|---|
| 2586 |
|
|---|
| 2587 | |
|---|
| 2588 |
|
|---|
| 2589 | ;;;; Making mail buffers.
|
|---|
| 2590 |
|
|---|
| 2591 | ;;; MAYBE-MAKE-MH-BUFFER looks up buffer with name, returning it if it exists
|
|---|
| 2592 | ;;; after cleaning it up to a state "good as new". Currently, we don't
|
|---|
| 2593 | ;;; believe it is possible to try to make two draft buffers with the same name
|
|---|
| 2594 | ;;; since that would mean that composition, draft folder interaction, and
|
|---|
| 2595 | ;;; draft folder current message didn't do what we expected -- or some user
|
|---|
| 2596 | ;;; was modifying the draft folder in some evil way.
|
|---|
| 2597 | ;;;
|
|---|
| 2598 | (defun maybe-make-mh-buffer (name use)
|
|---|
| 2599 | (let ((buf (getstring name *buffer-names*)))
|
|---|
| 2600 | (cond ((not buf)
|
|---|
| 2601 | (ecase use
|
|---|
| 2602 | (:headers (make-buffer name
|
|---|
| 2603 | :modes '("Headers")
|
|---|
| 2604 | :delete-hook '(cleanup-headers-buffer)))
|
|---|
| 2605 |
|
|---|
| 2606 | (:message
|
|---|
| 2607 | (make-buffer name :modes '("Message")
|
|---|
| 2608 | :modeline-fields
|
|---|
| 2609 | (value default-message-modeline-fields)
|
|---|
| 2610 | :delete-hook '(cleanup-message-buffer)))
|
|---|
| 2611 |
|
|---|
| 2612 | (:draft
|
|---|
| 2613 | (let ((buf (make-buffer
|
|---|
| 2614 | name :delete-hook '(cleanup-draft-buffer))))
|
|---|
| 2615 | (setf (buffer-minor-mode buf "Draft") t)
|
|---|
| 2616 | buf))))
|
|---|
| 2617 | ((hemlock-bound-p 'headers-information :buffer buf)
|
|---|
| 2618 | (setf (buffer-writable buf) t)
|
|---|
| 2619 | (delete-region (buffer-region buf))
|
|---|
| 2620 | (cleanup-headers-buffer buf)
|
|---|
| 2621 | (delete-variable 'headers-information :buffer buf)
|
|---|
| 2622 | buf)
|
|---|
| 2623 | ((hemlock-bound-p 'message-information :buffer buf)
|
|---|
| 2624 | (setf (buffer-writable buf) t)
|
|---|
| 2625 | (delete-region (buffer-region buf))
|
|---|
| 2626 | (cleanup-message-buffer buf)
|
|---|
| 2627 | (delete-variable 'message-information :buffer buf)
|
|---|
| 2628 | buf)
|
|---|
| 2629 | ((hemlock-bound-p 'draft-information :buffer buf)
|
|---|
| 2630 | (error "Attempt to create multiple draft buffers to same draft ~
|
|---|
| 2631 | folder message -- ~S"
|
|---|
| 2632 | name)))))
|
|---|
| 2633 |
|
|---|
| 2634 | |
|---|
| 2635 |
|
|---|
| 2636 | ;;;; Message buffer modeline fields.
|
|---|
| 2637 |
|
|---|
| 2638 | (make-modeline-field
|
|---|
| 2639 | :name :deleted-message :width 2
|
|---|
| 2640 | :function
|
|---|
| 2641 | #'(lambda (buffer window)
|
|---|
| 2642 | "Returns \"D \" when message in buffer is deleted."
|
|---|
| 2643 | (declare (ignore window))
|
|---|
| 2644 | (let* ((minfo (variable-value 'message-information :buffer buffer))
|
|---|
| 2645 | (hmark (message-info-headers-mark minfo)))
|
|---|
| 2646 | (cond ((not hmark)
|
|---|
| 2647 | (let ((msgs (message-info-msgs minfo)))
|
|---|
| 2648 | (if (and (value virtual-message-deletion)
|
|---|
| 2649 | (mh-sequence-member-p
|
|---|
| 2650 | (if (consp msgs) (car msgs) msgs)
|
|---|
| 2651 | (mh-sequence-list (message-info-folder minfo)
|
|---|
| 2652 | "hemlockdeleted")))
|
|---|
| 2653 | "D "
|
|---|
| 2654 | "")))
|
|---|
| 2655 | ((line-message-deleted (mark-line hmark))
|
|---|
| 2656 | "D ")
|
|---|
| 2657 | (t "")))))
|
|---|
| 2658 |
|
|---|
| 2659 | (make-modeline-field
|
|---|
| 2660 | :name :replied-to-message :width 1
|
|---|
| 2661 | :function
|
|---|
| 2662 | #'(lambda (buffer window)
|
|---|
| 2663 | "Returns \"A\" when message in buffer is deleted."
|
|---|
| 2664 | (declare (ignore window))
|
|---|
| 2665 | (let* ((minfo (variable-value 'message-information :buffer buffer))
|
|---|
| 2666 | (hmark (message-info-headers-mark minfo)))
|
|---|
| 2667 | (cond ((not hmark)
|
|---|
| 2668 | ;; Could do something nasty here to figure out the right value.
|
|---|
| 2669 | "")
|
|---|
| 2670 | (t
|
|---|
| 2671 | (mark-to-note-replied-msg hmark)
|
|---|
| 2672 | (if (char= (next-character hmark) #\A)
|
|---|
| 2673 | "A"
|
|---|
| 2674 | ""))))))
|
|---|
| 2675 |
|
|---|
| 2676 | ;;; MARK-TO-NOTE-REPLIED-MSG moves the headers-buffer mark to a line position
|
|---|
| 2677 | ;;; suitable for checking or setting the next character with respect to noting
|
|---|
| 2678 | ;;; that a message has been replied to.
|
|---|
| 2679 | ;;;
|
|---|
| 2680 | (defun mark-to-note-replied-msg (hmark)
|
|---|
| 2681 | (line-start hmark)
|
|---|
| 2682 | (find-attribute hmark :digit)
|
|---|
| 2683 | (find-attribute hmark :digit #'zerop)
|
|---|
| 2684 | (character-offset hmark 1))
|
|---|
| 2685 |
|
|---|
| 2686 |
|
|---|
| 2687 | (defhvar "Default Message Modeline Fields"
|
|---|
| 2688 | "This is the default list of modeline-field objects for message buffers."
|
|---|
| 2689 | :value
|
|---|
| 2690 | (list (modeline-field :hemlock-literal) (modeline-field :package)
|
|---|
| 2691 | (modeline-field :modes) (modeline-field :buffer-name)
|
|---|
| 2692 | (modeline-field :replied-to-message) (modeline-field :deleted-message)
|
|---|
| 2693 | (modeline-field :buffer-pathname) (modeline-field :modifiedp)))
|
|---|
| 2694 |
|
|---|
| 2695 |
|
|---|
| 2696 | |
|---|
| 2697 |
|
|---|
| 2698 | ;;;; MH interface.
|
|---|
| 2699 |
|
|---|
| 2700 | ;;; Running an MH utility.
|
|---|
| 2701 | ;;;
|
|---|
| 2702 |
|
|---|
| 2703 | (defhvar "MH Utility Pathname"
|
|---|
| 2704 | "MH utility names are merged with this. The default is
|
|---|
| 2705 | \"/usr/misc/.mh/bin/\"."
|
|---|
| 2706 | :value (pathname "/usr/misc/.mh/bin/"))
|
|---|
| 2707 |
|
|---|
| 2708 | (defvar *signal-mh-errors* t
|
|---|
| 2709 | "This is the default value for whether MH signals errors. It is useful to
|
|---|
| 2710 | bind this to nil when using PICK-MESSAGES with the \"Incorporate New Mail
|
|---|
| 2711 | Hook\".")
|
|---|
| 2712 |
|
|---|
| 2713 | (defvar *mh-error-output* (make-string-output-stream))
|
|---|
| 2714 |
|
|---|
| 2715 | (defun mh (utility args &key (errorp *signal-mh-errors*) environment)
|
|---|
| 2716 | "Runs the MH utility with the list of args (suitable for EXT:RUN-PROGRAM),
|
|---|
| 2717 | outputting to *standard-output*. Environment is a list of strings
|
|---|
| 2718 | appended with ext:*environment-list*. This returns t, unless there is
|
|---|
| 2719 | an error. When errorp, this reports any MH errors in the echo area as
|
|---|
| 2720 | an editor error, and this does not return; otherwise, nil and the error
|
|---|
| 2721 | output from the MH utility are returned."
|
|---|
| 2722 | (fresh-line)
|
|---|
| 2723 | (let* ((utility
|
|---|
| 2724 | (namestring
|
|---|
| 2725 | (or (probe-file (merge-pathnames utility
|
|---|
| 2726 | (value mh-utility-pathname)))
|
|---|
| 2727 | utility)))
|
|---|
| 2728 | (proc (ext:run-program
|
|---|
| 2729 | utility args
|
|---|
| 2730 | :output *standard-output*
|
|---|
| 2731 | :error *mh-error-output*
|
|---|
| 2732 | :env (append environment ext:*environment-list*))))
|
|---|
| 2733 | (fresh-line)
|
|---|
| 2734 | (ext:process-close proc)
|
|---|
| 2735 | (cond ((zerop (ext:process-exit-code proc))
|
|---|
| 2736 | (values t nil))
|
|---|
| 2737 | (errorp
|
|---|
| 2738 | (editor-error "MH Error -- ~A"
|
|---|
| 2739 | (get-output-stream-string *mh-error-output*)))
|
|---|
| 2740 | (t (values nil (get-output-stream-string *mh-error-output*))))))
|
|---|
| 2741 |
|
|---|
| 2742 |
|
|---|
| 2743 |
|
|---|
| 2744 | ;;; Draft folder name and pathname.
|
|---|
| 2745 | ;;;
|
|---|
| 2746 |
|
|---|
| 2747 | (defun mh-draft-folder ()
|
|---|
| 2748 | (let ((drafts (mh-profile-component "draft-folder")))
|
|---|
| 2749 | (unless drafts
|
|---|
| 2750 | (error "There must be a draft-folder component in your profile."))
|
|---|
| 2751 | drafts))
|
|---|
| 2752 |
|
|---|
| 2753 | (defun mh-draft-folder-pathname ()
|
|---|
| 2754 | "Returns the pathname of the MH draft folder directory."
|
|---|
| 2755 | (let ((drafts (mh-profile-component "draft-folder")))
|
|---|
| 2756 | (unless drafts
|
|---|
| 2757 | (error "There must be a draft-folder component in your profile."))
|
|---|
| 2758 | (merge-relative-pathnames drafts (mh-directory-pathname))))
|
|---|
| 2759 |
|
|---|
| 2760 |
|
|---|
| 2761 | ;;; Current folder name.
|
|---|
| 2762 | ;;;
|
|---|
| 2763 |
|
|---|
| 2764 | (defun mh-current-folder ()
|
|---|
| 2765 | "Returns the current MH folder from the context file."
|
|---|
| 2766 | (mh-profile-component "current-folder" (mh-context-pathname)))
|
|---|
| 2767 |
|
|---|
| 2768 |
|
|---|
| 2769 | ;;; Current message name.
|
|---|
| 2770 | ;;;
|
|---|
| 2771 |
|
|---|
| 2772 | (defun mh-current-message (folder)
|
|---|
| 2773 | "Returns the current MH message from the folder's sequence file."
|
|---|
| 2774 | (declare (simple-string folder))
|
|---|
| 2775 | (let ((folder (strip-folder-name folder)))
|
|---|
| 2776 | (mh-profile-component
|
|---|
| 2777 | "cur"
|
|---|
| 2778 | (merge-pathnames ".mh_sequences"
|
|---|
| 2779 | (merge-relative-pathnames folder
|
|---|
| 2780 | (mh-directory-pathname))))))
|
|---|
| 2781 |
|
|---|
| 2782 |
|
|---|
| 2783 | ;;; Context pathname.
|
|---|
| 2784 | ;;;
|
|---|
| 2785 |
|
|---|
| 2786 | (defvar *mh-context-pathname* nil)
|
|---|
| 2787 |
|
|---|
| 2788 | (defun mh-context-pathname ()
|
|---|
| 2789 | "Returns the pathname of the MH context file."
|
|---|
| 2790 | (or *mh-context-pathname*
|
|---|
| 2791 | (setf *mh-context-pathname*
|
|---|
| 2792 | (merge-pathnames (or (mh-profile-component "context") "context")
|
|---|
| 2793 | (mh-directory-pathname)))))
|
|---|
| 2794 |
|
|---|
| 2795 |
|
|---|
| 2796 | ;;; MH directory pathname.
|
|---|
| 2797 | ;;;
|
|---|
| 2798 |
|
|---|
| 2799 | (defvar *mh-directory-pathname* nil)
|
|---|
| 2800 |
|
|---|
| 2801 | ;;; MH-DIRECTORY-PATHNAME fetches the "path" MH component and bashes it
|
|---|
| 2802 | ;;; appropriately to get an absolute directory pathname.
|
|---|
| 2803 | ;;;
|
|---|
| 2804 | (defun mh-directory-pathname ()
|
|---|
| 2805 | "Returns the pathname of the MH directory."
|
|---|
| 2806 | (if *mh-directory-pathname*
|
|---|
| 2807 | *mh-directory-pathname*
|
|---|
| 2808 | (let ((path (mh-profile-component "path")))
|
|---|
| 2809 | (unless path (error "MH profile does not contain a Path component."))
|
|---|
| 2810 | (setf *mh-directory-pathname*
|
|---|
| 2811 | (truename (merge-relative-pathnames path
|
|---|
| 2812 | (user-homedir-pathname)))))))
|
|---|
| 2813 |
|
|---|
| 2814 | ;;; Profile components.
|
|---|
| 2815 | ;;;
|
|---|
| 2816 |
|
|---|
| 2817 | (defun mh-profile-component (name &optional (pathname (mh-profile-pathname))
|
|---|
| 2818 | (error-on-open t))
|
|---|
| 2819 | "Returns the trimmed string value for the MH profile component name. If
|
|---|
| 2820 | the component is not present, nil is returned. This may be used on MH
|
|---|
| 2821 | context and sequence files as well due to their having the same format.
|
|---|
| 2822 | Error-on-open indicates that errors generated by OPEN should not be ignored,
|
|---|
| 2823 | which is the default. When opening a sequence file, it is better to supply
|
|---|
| 2824 | this as nil since the file may not exist or be readable in another user's
|
|---|
| 2825 | MH folder, and returning nil meaning the sequence could not be found is just
|
|---|
| 2826 | as useful."
|
|---|
| 2827 | (with-open-stream (s (if error-on-open
|
|---|
| 2828 | (open pathname)
|
|---|
| 2829 | (ignore-errors (open pathname))))
|
|---|
| 2830 | (if s
|
|---|
| 2831 | (loop
|
|---|
| 2832 | (multiple-value-bind (line eofp) (read-line s nil :eof)
|
|---|
| 2833 | (when (eq line :eof) (return nil))
|
|---|
| 2834 | (let ((colon (position #\: (the simple-string line) :test #'char=)))
|
|---|
| 2835 | (unless colon
|
|---|
| 2836 | (error "Bad record ~S in file ~S." line (namestring pathname)))
|
|---|
| 2837 | (when (string-equal name line :end2 colon)
|
|---|
| 2838 | (return (string-trim '(#\space #\tab)
|
|---|
| 2839 | (subseq line (1+ colon))))))
|
|---|
| 2840 | (when eofp (return nil)))))))
|
|---|
| 2841 |
|
|---|
| 2842 |
|
|---|
| 2843 | ;;; Profile pathname.
|
|---|
| 2844 | ;;;
|
|---|
| 2845 |
|
|---|
| 2846 | (defvar *mh-profile-pathname* nil)
|
|---|
| 2847 |
|
|---|
| 2848 | (defun mh-profile-pathname ()
|
|---|
| 2849 | "Returns the pathname of the MH profile."
|
|---|
| 2850 | (or *mh-profile-pathname*
|
|---|
| 2851 | (setf *mh-profile-pathname*
|
|---|
| 2852 | (merge-pathnames (or (cdr (assoc :mh ext:*environment-list*))
|
|---|
| 2853 | ".mh_profile")
|
|---|
| 2854 | (truename (user-homedir-pathname))))))
|
|---|
| 2855 |
|
|---|
| 2856 |
|
|---|
| 2857 | |
|---|
| 2858 |
|
|---|
| 2859 | ;;;; Sequence handling.
|
|---|
| 2860 |
|
|---|
| 2861 | (declaim (optimize (speed 2))); byte compile off
|
|---|
| 2862 |
|
|---|
| 2863 | (defun mark-one-message (folder msg sequence add-or-delete)
|
|---|
| 2864 | "Msg is added or deleted to the sequence named sequence in the folder's
|
|---|
| 2865 | \".mh_sequence\" file. Add-or-delete is either :add or :delete."
|
|---|
| 2866 | (let ((seq-list (mh-sequence-list folder sequence)))
|
|---|
| 2867 | (ecase add-or-delete
|
|---|
| 2868 | (:add
|
|---|
| 2869 | (write-mh-sequence folder sequence (mh-sequence-insert msg seq-list)))
|
|---|
| 2870 | (:delete
|
|---|
| 2871 | (when (mh-sequence-member-p msg seq-list)
|
|---|
| 2872 | (write-mh-sequence folder sequence
|
|---|
| 2873 | (mh-sequence-delete msg seq-list)))))))
|
|---|
| 2874 |
|
|---|
| 2875 |
|
|---|
| 2876 | (defun mh-sequence-list (folder name)
|
|---|
| 2877 | "Returns a list representing the messages and ranges of id's for the
|
|---|
| 2878 | sequence name in folder from the \".mh_sequences\" file. A second value
|
|---|
| 2879 | is returned indicating whether the sequence was found or not."
|
|---|
| 2880 | (declare (simple-string folder))
|
|---|
| 2881 | (let* ((folder (strip-folder-name folder))
|
|---|
| 2882 | (seq-string (mh-profile-component
|
|---|
| 2883 | name
|
|---|
| 2884 | (merge-pathnames ".mh_sequences"
|
|---|
| 2885 | (merge-relative-pathnames
|
|---|
| 2886 | folder (mh-directory-pathname)))
|
|---|
| 2887 | nil)))
|
|---|
| 2888 | (if (not seq-string)
|
|---|
| 2889 | (values nil nil)
|
|---|
| 2890 | (let ((length (length (the simple-string seq-string)))
|
|---|
| 2891 | (result ())
|
|---|
| 2892 | (intervalp nil)
|
|---|
| 2893 | (start 0))
|
|---|
| 2894 | (declare (fixnum length start))
|
|---|
| 2895 | (loop
|
|---|
| 2896 | (multiple-value-bind (msg index)
|
|---|
| 2897 | (parse-integer seq-string
|
|---|
| 2898 | :start start :end length
|
|---|
| 2899 | :junk-allowed t)
|
|---|
| 2900 | (unless msg (return))
|
|---|
| 2901 | (cond ((or (= index length)
|
|---|
| 2902 | (char/= (schar seq-string index) #\-))
|
|---|
| 2903 | (if intervalp
|
|---|
| 2904 | (setf (cdar result) msg)
|
|---|
| 2905 | (push (cons msg msg) result))
|
|---|
| 2906 | (setf intervalp nil)
|
|---|
| 2907 | (setf start index))
|
|---|
| 2908 | (t
|
|---|
| 2909 | (push (cons msg nil) result)
|
|---|
| 2910 | (setf intervalp t)
|
|---|
| 2911 | (setf start (1+ index)))))
|
|---|
| 2912 | (when (>= start length) (return)))
|
|---|
| 2913 | (values (nreverse result) t)))))
|
|---|
| 2914 |
|
|---|
| 2915 | (defun write-mh-sequence (folder name seq-list)
|
|---|
| 2916 | "Writes seq-list to folder's \".mh_sequences\" file. If seq-list is nil,
|
|---|
| 2917 | the sequence is removed from the file."
|
|---|
| 2918 | (declare (simple-string folder))
|
|---|
| 2919 | (let* ((folder (strip-folder-name folder))
|
|---|
| 2920 | (input (merge-pathnames ".mh_sequences"
|
|---|
| 2921 | (merge-relative-pathnames
|
|---|
| 2922 | folder (mh-directory-pathname))))
|
|---|
| 2923 | (input-dir (pathname (directory-namestring input)))
|
|---|
| 2924 | (output (loop (let* ((sym (gensym))
|
|---|
| 2925 | (f (merge-pathnames
|
|---|
| 2926 | (format nil "sequence-file-~A.tmp" sym)
|
|---|
| 2927 | input-dir)))
|
|---|
| 2928 | (unless (probe-file f) (return f)))))
|
|---|
| 2929 | (found nil))
|
|---|
| 2930 | (cond ((not (hemlock-ext:file-writable output))
|
|---|
| 2931 | (loud-message "Cannot write sequence temp file ~A.~%~
|
|---|
| 2932 | Aborting output of ~S sequence."
|
|---|
| 2933 | name (namestring output)))
|
|---|
| 2934 | (t
|
|---|
| 2935 | (with-open-file (in input)
|
|---|
| 2936 | (with-open-file (out output :direction :output)
|
|---|
| 2937 | (loop
|
|---|
| 2938 | (multiple-value-bind (line eofp) (read-line in nil :eof)
|
|---|
| 2939 | (when (eq line :eof)
|
|---|
| 2940 | (return nil))
|
|---|
| 2941 | (let ((colon (position #\: (the simple-string line)
|
|---|
| 2942 | :test #'char=)))
|
|---|
| 2943 | (unless colon
|
|---|
| 2944 | (error "Bad record ~S in file ~S."
|
|---|
| 2945 | line (namestring input)))
|
|---|
| 2946 | (cond ((and (not found) (string-equal name line
|
|---|
| 2947 | :end2 colon))
|
|---|
| 2948 | (sub-write-mh-sequence
|
|---|
| 2949 | out (subseq line 0 colon) seq-list)
|
|---|
| 2950 | (setf found t))
|
|---|
| 2951 | (t (write-line line out))))
|
|---|
| 2952 | (when eofp (return))))
|
|---|
| 2953 | (unless found
|
|---|
| 2954 | (fresh-line out)
|
|---|
| 2955 | (sub-write-mh-sequence out name seq-list))))
|
|---|
| 2956 | (hacking-rename-file output input)))))
|
|---|
| 2957 |
|
|---|
| 2958 | (defun sub-write-mh-sequence (stream name seq-list)
|
|---|
| 2959 | (when seq-list
|
|---|
| 2960 | (write-string name stream)
|
|---|
| 2961 | (write-char #\: stream)
|
|---|
| 2962 | (let ((*print-base* 10))
|
|---|
| 2963 | (dolist (range seq-list)
|
|---|
| 2964 | (write-char #\space stream)
|
|---|
| 2965 | (let ((low (car range))
|
|---|
| 2966 | (high (cdr range)))
|
|---|
| 2967 | (declare (fixnum low high))
|
|---|
| 2968 | (cond ((= low high)
|
|---|
| 2969 | (prin1 low stream))
|
|---|
| 2970 | (t (prin1 low stream)
|
|---|
| 2971 | (write-char #\- stream)
|
|---|
| 2972 | (prin1 high stream))))))
|
|---|
| 2973 | (terpri stream)))
|
|---|
| 2974 |
|
|---|
| 2975 |
|
|---|
| 2976 | ;;; MH-SEQUENCE-< keeps SORT from consing rest args when FUNCALL'ing #'<.
|
|---|
| 2977 | ;;;
|
|---|
| 2978 | (defun mh-sequence-< (x y)
|
|---|
| 2979 | (< x y))
|
|---|
| 2980 |
|
|---|
| 2981 | (defun mh-sequence-insert (item seq-list)
|
|---|
| 2982 | "Inserts item into an mh sequence list. Item can be a string (\"23\"),
|
|---|
| 2983 | number (23), or a cons of two numbers ((23 . 23) or (3 . 5))."
|
|---|
| 2984 | (let ((range (typecase item
|
|---|
| 2985 | (string (let ((id (parse-integer item)))
|
|---|
| 2986 | (cons id id)))
|
|---|
| 2987 | (cons item)
|
|---|
| 2988 | (number (cons item item)))))
|
|---|
| 2989 | (cond (seq-list
|
|---|
| 2990 | (setf seq-list (sort (cons range seq-list)
|
|---|
| 2991 | #'mh-sequence-< :key #'car))
|
|---|
| 2992 | (coelesce-mh-sequence-ranges seq-list))
|
|---|
| 2993 | (t (list range)))))
|
|---|
| 2994 |
|
|---|
| 2995 | (defun coelesce-mh-sequence-ranges (seq-list)
|
|---|
| 2996 | (when seq-list
|
|---|
| 2997 | (let* ((current seq-list)
|
|---|
| 2998 | (next (cdr seq-list))
|
|---|
| 2999 | (current-range (car current))
|
|---|
| 3000 | (current-end (cdr current-range)))
|
|---|
| 3001 | (declare (fixnum current-end))
|
|---|
| 3002 | (loop
|
|---|
| 3003 | (unless next
|
|---|
| 3004 | (setf (cdr current-range) current-end)
|
|---|
| 3005 | (setf (cdr current) nil)
|
|---|
| 3006 | (return))
|
|---|
| 3007 | (let* ((next-range (car next))
|
|---|
| 3008 | (next-start (car next-range))
|
|---|
| 3009 | (next-end (cdr next-range)))
|
|---|
| 3010 | (declare (fixnum next-start next-end))
|
|---|
| 3011 | (cond ((<= (1- next-start) current-end)
|
|---|
| 3012 | ;;
|
|---|
| 3013 | ;; Extend the current range since the next one overlaps.
|
|---|
| 3014 | (when (> next-end current-end)
|
|---|
| 3015 | (setf current-end next-end)))
|
|---|
| 3016 | (t
|
|---|
| 3017 | ;;
|
|---|
| 3018 | ;; Update the current range since the next one doesn't overlap.
|
|---|
| 3019 | (setf (cdr current-range) current-end)
|
|---|
| 3020 | ;;
|
|---|
| 3021 | ;; Make the next range succeed current. Then make it current.
|
|---|
| 3022 | (setf (cdr current) next)
|
|---|
| 3023 | (setf current next)
|
|---|
| 3024 | (setf current-range next-range)
|
|---|
| 3025 | (setf current-end next-end))))
|
|---|
| 3026 | (setf next (cdr next))))
|
|---|
| 3027 | seq-list))
|
|---|
| 3028 |
|
|---|
| 3029 |
|
|---|
| 3030 | (defun mh-sequence-delete (item seq-list)
|
|---|
| 3031 | "Inserts item into an mh sequence list. Item can be a string (\"23\"),
|
|---|
| 3032 | number (23), or a cons of two numbers ((23 . 23) or (3 . 5))."
|
|---|
| 3033 | (let ((range (typecase item
|
|---|
| 3034 | (string (let ((id (parse-integer item)))
|
|---|
| 3035 | (cons id id)))
|
|---|
| 3036 | (cons item)
|
|---|
| 3037 | (number (cons item item)))))
|
|---|
| 3038 | (when seq-list
|
|---|
| 3039 | (do ((id (car range) (1+ id))
|
|---|
| 3040 | (end (cdr range)))
|
|---|
| 3041 | ((> id end))
|
|---|
| 3042 | (setf seq-list (sub-mh-sequence-delete id seq-list)))
|
|---|
| 3043 | seq-list)))
|
|---|
| 3044 |
|
|---|
| 3045 | (defun sub-mh-sequence-delete (id seq-list)
|
|---|
| 3046 | (do ((prev nil seq)
|
|---|
| 3047 | (seq seq-list (cdr seq)))
|
|---|
| 3048 | ((null seq))
|
|---|
| 3049 | (let* ((range (car seq))
|
|---|
| 3050 | (low (car range))
|
|---|
| 3051 | (high (cdr range)))
|
|---|
| 3052 | (cond ((> id high))
|
|---|
| 3053 | ((< id low)
|
|---|
| 3054 | (return))
|
|---|
| 3055 | ((= id low)
|
|---|
| 3056 | (cond ((/= low high)
|
|---|
| 3057 | (setf (car range) (1+ id)))
|
|---|
| 3058 | (prev
|
|---|
| 3059 | (setf (cdr prev) (cdr seq)))
|
|---|
| 3060 | (t (setf seq-list (cdr seq-list))))
|
|---|
| 3061 | (return))
|
|---|
| 3062 | ((= id high)
|
|---|
| 3063 | (setf (cdr range) (1- id))
|
|---|
| 3064 | (return))
|
|---|
| 3065 | ((< low id high)
|
|---|
| 3066 | (setf (cdr range) (1- id))
|
|---|
| 3067 | (setf (cdr seq) (cons (cons (1+ id) high) (cdr seq)))
|
|---|
| 3068 | (return)))))
|
|---|
| 3069 | seq-list)
|
|---|
| 3070 |
|
|---|
| 3071 |
|
|---|
| 3072 | (defun mh-sequence-member-p (item seq-list)
|
|---|
| 3073 | "Returns to or nil whether item is in the mh sequence list. Item can be a
|
|---|
| 3074 | string (\"23\") or a number (23)."
|
|---|
| 3075 | (let ((id (typecase item
|
|---|
| 3076 | (string (parse-integer item))
|
|---|
| 3077 | (number item))))
|
|---|
| 3078 | (dolist (range seq-list nil)
|
|---|
| 3079 | (let ((low (car range))
|
|---|
| 3080 | (high (cdr range)))
|
|---|
| 3081 | (when (<= low id high) (return t))))))
|
|---|
| 3082 |
|
|---|
| 3083 |
|
|---|
| 3084 | (defun mh-sequence-strings (seq-list)
|
|---|
| 3085 | "Returns a list of strings representing the ranges and messages id's in
|
|---|
| 3086 | seq-list."
|
|---|
| 3087 | (let ((result nil))
|
|---|
| 3088 | (dolist (range seq-list)
|
|---|
| 3089 | (let ((low (car range))
|
|---|
| 3090 | (high (cdr range)))
|
|---|
| 3091 | (if (= low high)
|
|---|
| 3092 | (push (number-string low) result)
|
|---|
| 3093 | (push (format nil "~D-~D" low high) result))))
|
|---|
| 3094 | (nreverse result)))
|
|---|
| 3095 |
|
|---|
| 3096 | (declaim (optimize (speed 0))); byte compile again.
|
|---|
| 3097 | |
|---|
| 3098 |
|
|---|
| 3099 | ;;;; CMU Common Lisp support.
|
|---|
| 3100 |
|
|---|
| 3101 | ;;; HACKING-RENAME-FILE renames old to new. This is used instead of Common
|
|---|
| 3102 | ;;; Lisp's RENAME-FILE because it merges new pathname with old pathname,
|
|---|
| 3103 | ;;; which loses when old has a name and type, and new has only a type (a
|
|---|
| 3104 | ;;; Unix-oid "dot" file).
|
|---|
| 3105 | ;;;
|
|---|
| 3106 | (defun hacking-rename-file (old new)
|
|---|
| 3107 | (let ((ses-name1 (namestring old))
|
|---|
| 3108 | (ses-name2 (namestring new)))
|
|---|
| 3109 | (multiple-value-bind (res err) (unix:unix-rename ses-name1 ses-name2)
|
|---|
| 3110 | (unless res
|
|---|
| 3111 | (error "Failed to rename ~A to ~A: ~A."
|
|---|
| 3112 | ses-name1 ses-name2 (unix:get-unix-error-msg err))))))
|
|---|
| 3113 |
|
|---|
| 3114 |
|
|---|
| 3115 | ;;; Folder existence and creation.
|
|---|
| 3116 | ;;;
|
|---|
| 3117 |
|
|---|
| 3118 | (defun folder-existsp (folder)
|
|---|
| 3119 | "Returns t if the directory for folder exists. Folder is a simple-string
|
|---|
| 3120 | specifying a folder name relative to the MH mail directoy."
|
|---|
| 3121 | (declare (simple-string folder))
|
|---|
| 3122 | (let* ((folder (strip-folder-name folder))
|
|---|
| 3123 | (pathname (merge-relative-pathnames folder (mh-directory-pathname)))
|
|---|
| 3124 | (pf (probe-file pathname)))
|
|---|
| 3125 | (and pf
|
|---|
| 3126 | (null (pathname-name pf))
|
|---|
| 3127 | (null (pathname-type pf)))))
|
|---|
| 3128 |
|
|---|
| 3129 | (defun create-folder (folder)
|
|---|
| 3130 | "Creates folder directory with default protection #o711 but considers the
|
|---|
| 3131 | MH profile for the \"Folder-Protect\" component. Folder is a simple-string
|
|---|
| 3132 | specifying a folder name relative to the MH mail directory."
|
|---|
| 3133 | (declare (simple-string folder))
|
|---|
| 3134 | (let* ((folder (strip-folder-name folder))
|
|---|
| 3135 | (pathname (merge-relative-pathnames folder (mh-directory-pathname)))
|
|---|
| 3136 | (ses-name (namestring pathname))
|
|---|
| 3137 | (length-1 (1- (length ses-name)))
|
|---|
| 3138 | (name (if (= (position #\/ ses-name :test #'char= :from-end t)
|
|---|
| 3139 | length-1)
|
|---|
| 3140 | (subseq ses-name 0 (1- (length ses-name)))
|
|---|
| 3141 | ses-name))
|
|---|
| 3142 | (protection (mh-profile-component "folder-protect")))
|
|---|
| 3143 | (when protection
|
|---|
| 3144 | (setf protection
|
|---|
| 3145 | (parse-integer protection :radix 8 :junk-allowed t)))
|
|---|
| 3146 | (multiple-value-bind (winp err)
|
|---|
| 3147 | (unix:unix-mkdir name (or protection #o711))
|
|---|
| 3148 | (unless winp
|
|---|
| 3149 | (error "Couldn't make directory ~S: ~A"
|
|---|
| 3150 | name
|
|---|
| 3151 | (unix:get-unix-error-msg err)))
|
|---|
| 3152 | (check-folder-name-table)
|
|---|
| 3153 | (setf (getstring folder *folder-name-table*) t))))
|
|---|
| 3154 |
|
|---|
| 3155 |
|
|---|
| 3156 | ;;; Checking for mail.
|
|---|
| 3157 | ;;;
|
|---|
| 3158 |
|
|---|
| 3159 | (defvar *mailbox* nil)
|
|---|
| 3160 |
|
|---|
| 3161 | (defun new-mail-p ()
|
|---|
| 3162 | (unless *mailbox*
|
|---|
| 3163 | (setf *mailbox*
|
|---|
| 3164 | (probe-file (or (cdr (assoc :mail ext:*environment-list*))
|
|---|
| 3165 | (cdr (assoc :maildrop ext:*environment-list*))
|
|---|
| 3166 | (mh-profile-component "MailDrop")
|
|---|
| 3167 | (merge-pathnames
|
|---|
| 3168 | (cdr (assoc :user ext:*environment-list*))
|
|---|
| 3169 | "/usr/spool/mail/")))))
|
|---|
| 3170 | (when *mailbox*
|
|---|
| 3171 | (multiple-value-bind (success dev ino mode nlink uid gid rdev size
|
|---|
| 3172 | atime)
|
|---|
| 3173 | (unix:unix-stat (namestring *mailbox*))
|
|---|
| 3174 | (declare (ignore dev ino nlink uid gid rdev atime))
|
|---|
| 3175 | (and success
|
|---|
| 3176 | (plusp (logand unix:s-ifreg mode))
|
|---|
| 3177 | (not (zerop size))))))
|
|---|
| 3178 |
|
|---|
| 3179 |
|
|---|
| 3180 |
|
|---|