1 ;;; vm-motion.el --- Commands to move around in a VM folder
3 ;; Copyright (C) 1989-1997 Kyle E. Jones
4 ;; Copyright (C) 2003-2006 Robert Widhopf-Fenk
6 ;; This program is free software; you can redistribute it and/or modify
7 ;; it under the terms of the GNU General Public License as published by
8 ;; the Free Software Foundation; either version 2 of the License, or
9 ;; (at your option) any later version.
11 ;; This program is distributed in the hope that it will be useful,
12 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
13 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 ;; GNU General Public License for more details.
16 ;; You should have received a copy of the GNU General Public License along
17 ;; with this program; if not, write to the Free Software Foundation, Inc.,
18 ;; 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
21 (defun vm-record-and-change-message-pointer (old new)
22 (intern (buffer-name) vm-buffers-needing-display-update)
23 (vm-garbage-collect-message)
24 (setq vm-last-message-pointer old
25 vm-message-pointer new
26 vm-need-summary-pointer-update t))
29 (defun vm-goto-message (n)
30 "Go to the message numbered N.
31 Interactively N is the prefix argument. If no prefix arg is provided
32 N is prompted for in the minibuffer.
34 If vm-follow-summary-cursor is non-nil this command will go to
35 the message under the cursor in the summary buffer if the summary
36 window is selected. This only happens if no prefix argument is
40 (cond (current-prefix-arg (prefix-numeric-value current-prefix-arg))
41 ((vm-follow-summary-cursor) nil)
42 ((vm-follow-folders-summary-cursor) nil)
44 (let ((last-command last-command)
45 (this-command this-command))
46 (vm-read-number "Go to message: "))))))
48 () ; nil means work has been done already
49 (vm-select-folder-buffer)
50 (vm-check-for-killed-summary)
51 (vm-error-if-folder-empty)
52 (vm-display nil nil '(vm-goto-message) '(vm-goto-message))
53 (let ((cons (nthcdr (1- n) vm-message-list)))
55 (error "No such message."))
56 (if (eq vm-message-pointer cons)
57 (vm-preview-current-message)
58 (vm-record-and-change-message-pointer vm-message-pointer cons)
59 (vm-preview-current-message)))))
62 (defun vm-goto-message-last-seen ()
63 "Go to the message last previewed."
65 (vm-select-folder-buffer)
66 (vm-check-for-killed-summary)
67 (vm-error-if-folder-empty)
68 (vm-display nil nil '(vm-goto-message-last-seen)
69 '(vm-goto-message-last-seen))
70 (if vm-last-message-pointer
72 (vm-record-and-change-message-pointer vm-message-pointer
73 vm-last-message-pointer)
74 (vm-preview-current-message))))
77 (defun vm-goto-parent-message ()
78 "Go to the parent of the current message."
80 (vm-follow-summary-cursor)
81 (vm-select-folder-buffer)
82 (vm-check-for-killed-summary)
83 (vm-error-if-folder-empty)
84 (vm-build-threads-if-unbuilt)
85 (vm-display nil nil '(vm-goto-parent-message)
86 '(vm-goto-parent-message))
87 (let ((list (vm-th-thread-list (car vm-message-pointer)))
90 (message "Message has no parent.")
91 (while (cdr (cdr list))
92 (setq list (cdr list)))
93 (setq message (car (get (car list) 'messages)))
95 (message "Parent message is not in this folder.")
96 (vm-record-and-change-message-pointer vm-message-pointer
97 (memq message vm-message-list))
98 (vm-preview-current-message)))))
100 (defun vm-check-count (count)
102 (if (< (length vm-message-pointer) count)
103 (signal 'end-of-folder nil))
104 (if (< (1+ (- (length vm-message-list) (length vm-message-pointer)))
106 (signal 'beginning-of-folder nil))))
108 (defun vm-move-message-pointer (direction)
109 (let ((mp vm-message-pointer))
110 (if (eq direction 'forward)
114 (if vm-circular-folders
115 (setq mp vm-message-list)
116 (signal 'end-of-folder nil))))
117 (setq mp (vm-reverse-link-of (car mp)))
119 (if vm-circular-folders
120 (setq mp (vm-last vm-message-list))
121 (signal 'beginning-of-folder nil))))
122 (setq vm-message-pointer mp)))
124 (defun vm-should-skip-message (mp &optional skip-dogmatically)
125 (if skip-dogmatically
126 (or (and vm-skip-deleted-messages
127 (vm-deleted-flag (car mp)))
128 (and vm-skip-read-messages
129 (or (vm-deleted-flag (car mp))
130 (not (or (vm-new-flag (car mp))
131 (vm-unread-flag (car mp))))))
132 (and (eq last-command 'vm-next-command-uses-marks)
133 (null (vm-mark-of (car mp)))))
134 (or (and (eq vm-skip-deleted-messages t)
135 (vm-deleted-flag (car mp)))
136 (and (eq vm-skip-read-messages t)
137 (or (vm-deleted-flag (car mp))
138 (not (or (vm-new-flag (car mp))
139 (vm-unread-flag (car mp))))))
140 (and (eq last-command 'vm-next-command-uses-marks)
141 (null (vm-mark-of (car mp)))))))
144 (defun vm-next-message (&optional count retry signal-errors)
145 "Go forward one message and preview it.
146 With prefix arg (optional first argument) COUNT, go forward COUNT
147 messages. A negative COUNT means go backward. If the absolute
148 value of COUNT is greater than 1, then the values of the variables
149 vm-skip-deleted-messages and vm-skip-read-messages are ignored.
151 When invoked on marked messages (via vm-next-command-uses-marks)
152 this command 'sees' marked messages as it moves."
153 ;; second arg RETRY non-nil means retry a failed move, giving
154 ;; not nil-or-t values of the vm-skip variables a chance to
157 ;; third arg SIGNAL-ERRORS non-nil means that if after
158 ;; everything we still have bashed into the end or beginning of
159 ;; folder before completing the move, signal
160 ;; beginning-of-folder or end-of-folder. Otherwise no error
163 ;; Note that interactively all args are 1, so error signaling
164 ;; and retries apply to all interactive moves.
165 (interactive "p\np\np")
167 (vm-follow-summary-cursor))
168 (vm-select-folder-buffer)
169 (vm-check-for-killed-summary)
170 ;; include other commands that call vm-next-message so that the
171 ;; correct window configuration is applied for these particular
172 ;; non-interactive calls.
173 (vm-display nil nil '(vm-next-message
178 (and signal-errors (vm-error-if-folder-empty))
179 (or count (setq count 1))
180 (let ((oldmp vm-message-pointer)
181 (use-marks (eq last-command 'vm-next-command-uses-marks))
183 (direction (if (> count 0) 'forward 'backward))
184 (count (vm-abs count)))
186 ((null vm-message-pointer)
187 (setq vm-message-pointer vm-message-list))
191 (while (not (zerop count))
192 (vm-move-message-pointer direction)
193 (if (and use-marks (null (vm-mark-of (car vm-message-pointer))))
195 (while (and (not (eq vm-message-pointer oldmp))
196 (null (vm-mark-of (car vm-message-pointer))))
197 (vm-move-message-pointer direction))
198 (if (eq vm-message-pointer oldmp)
199 ;; terminate the loop
201 ;; reset for next pass
202 (setq oldmp vm-message-pointer))))
203 (vm-decrement count)))
204 (beginning-of-folder (setq error 'beginning-of-folder))
205 (end-of-folder (setq error 'end-of-folder))))
209 (vm-move-message-pointer direction)
210 (while (and (not (eq oldmp vm-message-pointer))
211 (vm-should-skip-message vm-message-pointer t))
212 (vm-move-message-pointer direction))
213 ;; Retry the move if we've gone a complete circle and
214 ;; retries are allowed and there are other messages
216 (and (eq vm-message-pointer oldmp) retry (cdr vm-message-list)
218 (vm-move-message-pointer direction)
219 (while (and (not (eq oldmp vm-message-pointer))
220 (vm-should-skip-message vm-message-pointer))
221 (vm-move-message-pointer direction)))))
223 ;; we bumped into the beginning of the folder without finding
224 ;; a suitable stopping point; retry the move if we're allowed.
225 (setq vm-message-pointer oldmp)
226 ;; if the retry fails, we make sure the message pointer
227 ;; is restored to its old value.
229 (setq vm-message-pointer
231 (let ((vm-message-pointer vm-message-pointer))
232 (vm-move-message-pointer direction)
233 (while (vm-should-skip-message vm-message-pointer)
234 (vm-move-message-pointer direction))
237 (setq error 'beginning-of-folder)
239 (setq error 'beginning-of-folder)))
241 ;; we bumped into the end of the folder without finding
242 ;; a suitable stopping point; retry the move if we're allowed.
243 (setq vm-message-pointer oldmp)
244 ;; if the retry fails, we make sure the message pointer
245 ;; is restored to its old value.
247 (setq vm-message-pointer
249 (let ((vm-message-pointer vm-message-pointer))
250 (vm-move-message-pointer direction)
251 (while (vm-should-skip-message vm-message-pointer)
252 (vm-move-message-pointer direction))
255 (setq error 'end-of-folder)
257 (setq error 'end-of-folder))))))
258 (if (not (eq vm-message-pointer oldmp))
260 (vm-record-and-change-message-pointer oldmp vm-message-pointer)
261 (vm-preview-current-message)))
262 (and error signal-errors
263 (signal error nil))))
266 (defun vm-previous-message (&optional count retry signal-errors)
267 "Go back one message and preview it.
268 With prefix arg COUNT, go backward COUNT messages. A negative COUNT
269 means go forward. If the absolute value of COUNT > 1 the values of the
270 variables vm-skip-deleted-messages and vm-skip-read-messages are
272 (interactive "p\np\np")
273 (or count (setq count 1))
275 (vm-follow-summary-cursor))
276 (vm-select-folder-buffer)
277 (vm-display nil nil '(vm-previous-message) '(vm-previous-message))
278 (vm-next-message (- count) retry signal-errors))
281 (defun vm-next-message-no-skip (&optional count)
282 "Like vm-next-message but will not skip deleted or read messages."
285 (vm-follow-summary-cursor))
286 (vm-select-folder-buffer)
287 (vm-display nil nil '(vm-next-message-no-skip)
288 '(vm-next-message-no-skip))
289 (let ((vm-skip-deleted-messages nil)
290 (vm-skip-read-messages nil))
291 (vm-next-message count nil t)))
292 ;; backward compatibility
293 (fset 'vm-Next-message 'vm-next-message-no-skip)
296 (defun vm-previous-message-no-skip (&optional count)
297 "Like vm-previous-message but will not skip deleted or read messages."
300 (vm-follow-summary-cursor))
301 (vm-select-folder-buffer)
302 (vm-display nil nil '(vm-previous-message-no-skip)
303 '(vm-previous-message-no-skip))
304 (let ((vm-skip-deleted-messages nil)
305 (vm-skip-read-messages nil))
306 (vm-previous-message count)))
307 ;; backward compatibility
308 (fset 'vm-Previous-message 'vm-previous-message-no-skip)
311 (defun vm-next-unread-message ()
312 "Move forward to the nearest new or unread message, if there is one."
315 (vm-follow-summary-cursor))
316 (vm-select-folder-buffer)
317 (vm-check-for-killed-summary)
318 (vm-display nil nil '(vm-next-unread-message) '(vm-next-unread-message))
320 (let ((vm-skip-read-messages t)
321 (oldmp vm-message-pointer))
322 (vm-next-message 1 nil t)
323 ;; in case vm-circular-folders is non-nil
324 (and (eq vm-message-pointer oldmp) (signal 'end-of-folder nil)))
325 (end-of-folder (message "No next unread message"))))
328 (defun vm-previous-unread-message ()
329 "Move backward to the nearest new or unread message, if there is one."
332 (vm-follow-summary-cursor))
333 (vm-select-folder-buffer)
334 (vm-check-for-killed-summary)
335 (vm-display nil nil '(vm-previous-unread-message)
336 '(vm-previous-unread-message))
338 (let ((vm-skip-read-messages t)
339 (oldmp vm-message-pointer))
340 (vm-previous-message)
341 ;; in case vm-circular-folders is non-nil
342 (and (eq vm-message-pointer oldmp) (signal 'beginning-of-folder nil)))
343 (beginning-of-folder (message "No previous unread message"))))
346 (defun vm-next-message-same-subject ()
347 "Move forward to the nearest message with the same subject.
348 vm-subject-ignored-prefix and vm-subject-ignored-suffix will apply
349 to the subject comparisons."
352 (vm-follow-summary-cursor))
353 (vm-select-folder-buffer)
354 (vm-check-for-killed-summary)
355 (vm-display nil nil '(vm-next-message-same-subject)
356 '(vm-next-message-same-subject))
357 (let ((oldmp vm-message-pointer)
359 (subject (vm-so-sortable-subject (car vm-message-pointer))))
363 (vm-move-message-pointer 'forward)
364 (if (eq oldmp vm-message-pointer)
365 (signal 'end-of-folder nil))
367 (vm-so-sortable-subject (car vm-message-pointer)))
369 (vm-record-and-change-message-pointer oldmp vm-message-pointer)
370 (vm-preview-current-message))
372 (setq vm-message-pointer oldmp)
373 (message "No next message with the same subject")))))
376 (defun vm-previous-message-same-subject ()
377 "Move backward to the nearest message with the same subject.
378 vm-subject-ignored-prefix and vm-subject-ignored-suffix will apply
379 to the subject comparisons."
382 (vm-follow-summary-cursor))
383 (vm-select-folder-buffer)
384 (vm-check-for-killed-summary)
385 (vm-display nil nil '(vm-previous-message-same-subject)
386 '(vm-previous-message-same-subject))
387 (let ((oldmp vm-message-pointer)
389 (subject (vm-so-sortable-subject (car vm-message-pointer))))
393 (vm-move-message-pointer 'backward)
394 (if (eq oldmp vm-message-pointer)
395 (signal 'beginning-of-folder nil))
397 (vm-so-sortable-subject (car vm-message-pointer)))
399 (vm-record-and-change-message-pointer oldmp vm-message-pointer)
400 (vm-preview-current-message))
402 (setq vm-message-pointer oldmp)
403 (message "No previous message with the same subject")))))
405 (defun vm-find-first-unread-message (new)
407 (setq mp vm-message-list)
410 (if (and (vm-new-flag (car mp)) (not (vm-deleted-flag (car mp))))
411 (setq unread-mp mp mp nil)
414 (if (and (or (vm-new-flag (car mp)) (vm-unread-flag (car mp)))
415 (not (vm-deleted-flag (car mp))))
416 (setq unread-mp mp mp nil)
417 (setq mp (cdr mp)))))
420 (defun vm-thoughtfully-select-message ()
421 (let ((new (and vm-jump-to-new-messages (vm-find-first-unread-message t)))
422 (unread (and vm-jump-to-unread-messages
423 (vm-find-first-unread-message nil)))
425 (if (null vm-message-pointer)
426 (setq fix (vm-last vm-message-list)))
427 (setq mp (or new unread fix))
428 (if (and mp (not (eq mp vm-message-pointer)))
430 (vm-record-and-change-message-pointer vm-message-pointer mp)
434 (defun vm-follow-summary-cursor ()
435 (and vm-follow-summary-cursor (eq major-mode 'vm-summary-mode)
436 (let ((point (point))
437 message-pointer message-list mp)
439 (set-buffer vm-mail-buffer)
440 (setq message-pointer vm-message-pointer
441 message-list vm-message-list))
442 (cond ((or (null message-pointer)
443 (and (>= point (vm-su-start-of (car message-pointer)))
444 (< point (vm-su-end-of (car message-pointer)))))
446 ;; the position at eob belongs to the last message
447 ((and (eobp) (= (vm-su-end-of (car message-pointer)) point))
449 ;; make the position at eob belong to the last message
451 (setq mp (vm-last message-pointer))
453 (set-buffer vm-mail-buffer)
454 (vm-record-and-change-message-pointer vm-message-pointer mp)
455 (vm-preview-current-message)
456 ;; return non-nil so the caller will know that
457 ;; a new message was selected.
460 (if (< point (vm-su-start-of (car message-pointer)))
461 (setq mp message-list)
462 (setq mp (cdr message-pointer) message-pointer nil))
463 (while (and (not (eq mp message-pointer))
464 (>= point (vm-su-end-of (car mp))))
466 (if (not (eq mp message-pointer))
468 (set-buffer vm-mail-buffer)
469 (vm-record-and-change-message-pointer
470 vm-message-pointer mp)
471 (vm-preview-current-message)
472 ;; return non-nil so the caller will know that
473 ;; a new message was selected.
478 ;;; vm-motion.el ends here