nnimap: possibly-change-group get a read-only argument
[gnus] / lisp / nnimap.el
index d26df23..a911f52 100644 (file)
@@ -1,6 +1,6 @@
 ;;; nnimap.el --- IMAP interface for Gnus
 
-;; Copyright (C) 2010-2011 Free Software Foundation, Inc.
+;; Copyright (C) 2010-2012 Free Software Foundation, Inc.
 
 ;; Author: Lars Magne Ingebrigtsen <larsi@gnus.org>
 ;;         Simon Josefsson <simon@josefsson.org>
@@ -82,7 +82,8 @@ back on `network'.")
 
 (defvoo nnimap-inbox nil
   "The mail box where incoming mail arrives and should be split out of.
-For example, \"INBOX\".")
+This can be a string or a list of strings
+For example, \"INBOX\" or (\"INBOX\" \"SENT\").")
 
 (defvoo nnimap-split-methods nil
   "How mail is split.
@@ -117,7 +118,7 @@ some servers.")
 
 (defvoo nnimap-fetch-partial-articles nil
   "If non-nil, Gnus will fetch partial articles.
-If t, nnimap will fetch only the first part.  If a string, it
+If t, Gnus will fetch only the first part.  If a string, it
 will fetch all parts that have types that match that string.  A
 likely value would be \"text/\" to automatically fetch all
 textual parts.")
@@ -134,7 +135,7 @@ textual parts.")
 
 (defstruct nnimap
   group process commands capabilities select-result newlinep server
-  last-command-time greeting examined stream-type)
+  last-command-time greeting examined stream-type initial-resync)
 
 (defvar nnimap-object nil)
 
@@ -189,25 +190,35 @@ textual parts.")
 
 (defun nnimap-transform-headers ()
   (goto-char (point-min))
-  (let (article bytes lines size string)
+  (let (article lines size string)
     (block nil
       (while (not (eobp))
-       (while (not (looking-at "\\* [0-9]+ FETCH.+?UID \\([0-9]+\\)"))
+       (while (not (looking-at "\\* [0-9]+ FETCH"))
          (delete-region (point) (progn (forward-line 1) (point)))
          (when (eobp)
            (return)))
-       (setq article (match-string 1))
+       (goto-char (match-end 0))
        ;; Unfold quoted {number} strings.
-       (while (re-search-forward "[^]][ (]{\\([0-9]+\\)}\r?\n"
-                                 (1+ (line-end-position)) t)
+       (while (re-search-forward
+               "[^]][ (]{\\([0-9]+\\)}\r?\n"
+               (save-excursion
+                 ;; Start of the header section.
+                 (or (re-search-forward "] {[0-9]+}\r?\n" nil t)
+                     ;; Start of the next FETCH.
+                     (re-search-forward "\\* [0-9]+ FETCH" nil t)
+                     (point-max)))
+               t)
          (setq size (string-to-number (match-string 1)))
          (delete-region (+ (match-beginning 0) 2) (point))
          (setq string (buffer-substring (point) (+ (point) size)))
          (delete-region (point) (+ (point) size))
-         (insert (format "%S" string)))
-       (setq bytes (nnimap-get-length)
-             lines nil)
+         (insert (format "%S" (mm-subst-char-in-string ?\n ?\s string))))
        (beginning-of-line)
+       (setq article
+             (and (re-search-forward "UID \\([0-9]+\\)" (line-end-position)
+                                     t)
+                  (match-string 1)))
+       (setq lines nil)
        (setq size
              (and (re-search-forward "RFC822.SIZE \\([0-9]+\\)"
                                      (line-end-position)
@@ -269,18 +280,20 @@ textual parts.")
         result))
       (mapconcat #'identity (nreverse result) ",")))))
 
-(deffoo nnimap-open-server (server &optional defs)
+(deffoo nnimap-open-server (server &optional defs no-reconnect)
   (if (nnimap-server-opened server)
       t
     (unless (assq 'nnimap-address defs)
       (setq defs (append defs (list (list 'nnimap-address server)))))
     (nnoo-change-server 'nnimap server defs)
-    (or (nnimap-find-connection nntp-server-buffer)
-       (nnimap-open-connection nntp-server-buffer))))
+    (if no-reconnect
+       (nnimap-find-connection nntp-server-buffer)
+      (or (nnimap-find-connection nntp-server-buffer)
+         (nnimap-open-connection nntp-server-buffer)))))
 
 (defun nnimap-make-process-buffer (buffer)
   (with-current-buffer
-      (generate-new-buffer (format "*nnimap %s %s %s*"
+      (generate-new-buffer (format " *nnimap %s %s %s*"
                                   nnimap-address nnimap-server-port
                                   (gnus-buffer-exists-p buffer)))
     (mm-disable-multibyte)
@@ -288,7 +301,8 @@ textual parts.")
     (gnus-add-buffer)
     (set (make-local-variable 'after-change-functions) nil)
     (set (make-local-variable 'nnimap-object)
-        (make-nnimap :server (nnoo-current-server 'nnimap)))
+        (make-nnimap :server (nnoo-current-server 'nnimap)
+                     :initial-resync 0))
     (push (list buffer (current-buffer)) nnimap-connection-alist)
     (push (current-buffer) nnimap-process-buffers)
     (current-buffer)))
@@ -345,6 +359,11 @@ textual parts.")
        nil
       stream)))
 
+(defun nnimap-map-port (port)
+  (if (equal port "imaps")
+      "993"
+    port))
+
 (defun nnimap-open-connection-1 (buffer)
   (unless nnimap-keepalive-timer
     (setq nnimap-keepalive-timer (run-at-time (* 60 15) (* 60 15)
@@ -373,7 +392,8 @@ textual parts.")
        (push nnimap-server-port ports))
       (let* ((stream-list
              (open-protocol-stream
-              "*nnimap*" (current-buffer) nnimap-address (car ports)
+              "*nnimap*" (current-buffer) nnimap-address
+              (nnimap-map-port (car ports))
               :type nnimap-stream
               :return-list t
               :shell-command nnimap-shell-program
@@ -391,6 +411,14 @@ textual parts.")
             (stream-type (plist-get props :type)))
        (when (and stream (not (memq (process-status stream) '(open run))))
          (setq stream nil))
+
+        (when (and (fboundp 'set-network-process-option) ;; Not in XEmacs.
+                   (fboundp 'process-type) ;; Emacs 22 doesn't provide it.
+                   (eq (process-type stream) 'network))
+          ;; Use TCP-keepalive so that connections that pass through a NAT
+          ;; router don't hang when left idle.
+          (set-network-process-option stream :keepalive t))
+
        (setf (nnimap-process nnimap-object) stream)
        (setf (nnimap-stream-type nnimap-object) stream-type)
        (if (not stream)
@@ -448,6 +476,8 @@ textual parts.")
            (when nnimap-object
              (when (nnimap-capability "QRESYNC")
                (nnimap-command "ENABLE QRESYNC"))
+              (nnheader-message 7 "Opening connection to %s...done"
+                               nnimap-address)
              (nnimap-process nnimap-object))))))))
 
 (autoload 'rfc2104-hash "rfc2104")
@@ -531,7 +561,7 @@ textual parts.")
     (let ((result (nnimap-possibly-change-group group server))
          parts structure)
       (when (stringp article)
-       (setq article (nnimap-find-article-by-message-id group article)))
+       (setq article (nnimap-find-article-by-message-id group server article)))
       (when (and result
                 article)
        (erase-buffer)
@@ -563,7 +593,7 @@ textual parts.")
   (when (nnimap-possibly-change-group group server)
     (with-current-buffer (nnimap-buffer)
       (when (stringp article)
-       (setq article (nnimap-find-article-by-message-id group article)))
+       (setq article (nnimap-find-article-by-message-id group server article)))
       (if (null article)
          nil
        (nnimap-get-whole-article
@@ -666,12 +696,13 @@ textual parts.")
       (if (consp (caar structure))
          (nnimap-insert-partial-structure (pop structure) parts t)
        (let ((bit (pop structure)))
-         (insert (format  "Content-type: %s/%s"
-                          (downcase (nth 0 bit))
-                          (downcase (nth 1 bit))))
-         (if (member "CHARSET" (nth 2 bit))
+         (insert (format "Content-type: %s/%s"
+                         (downcase (nth 0 bit))
+                         (downcase (nth 1 bit))))
+         (if (member-ignore-case "CHARSET" (nth 2 bit))
              (insert (format
-                      "; charset=%S\n" (cadr (member "CHARSET" (nth 2 bit)))))
+                      "; charset=%S\n"
+                      (cadr (member-ignore-case "CHARSET" (nth 2 bit)))))
            (insert "\n"))
          (insert (format "Content-transfer-encoding: %s\n"
                          (nth 5 bit)))
@@ -836,10 +867,11 @@ textual parts.")
                (cons internal-move-group
                      (or (nnimap-find-uid-response "COPYUID" (cadr result))
                          (nnimap-find-article-by-message-id
-                          internal-move-group message-id)))))
+                          internal-move-group server message-id)))))
          ;; Move the article to a different method.
          (let ((result (eval accept-form)))
            (when result
+             (nnimap-possibly-change-group group server)
              (nnimap-delete-article article)
              result)))))))
 
@@ -937,13 +969,11 @@ textual parts.")
                               (cdr (assoc "SEARCH" (cdr result))))))))))
 
 
-(defun nnimap-find-article-by-message-id (group message-id)
+(defun nnimap-find-article-by-message-id (group server message-id)
+  "Search for message with MESSAGE-ID in GROUP from SERVER."
   (with-current-buffer (nnimap-buffer)
     (erase-buffer)
-    (unless (equal group (nnimap-group nnimap-object))
-      (setf (nnimap-group nnimap-object) nil)
-      (setf (nnimap-examined nnimap-object) group)
-      (nnimap-send-command "EXAMINE %S" (utf7-encode group t)))
+    (nnimap-possibly-change-group group server nil t)
     (let ((sequence
           (nnimap-send-command "UID SEARCH HEADER Message-Id %S" message-id))
          article result)
@@ -978,7 +1008,10 @@ textual parts.")
             nnimap-inbox
             nnimap-split-methods)
     (nnheader-message 7 "nnimap %s splitting mail..." server)
-    (nnimap-split-incoming-mail)
+    (if (listp nnimap-inbox)
+       (dolist (nnimap-inbox nnimap-inbox)
+         (nnimap-split-incoming-mail))
+      (nnimap-split-incoming-mail))
     (nnheader-message 7 "nnimap %s splitting mail...done" server)))
 
 (defun nnimap-marks-to-flags (marks)
@@ -1020,7 +1053,7 @@ textual parts.")
                                 ((eq action 'set) ""))
                                (mapconcat #'identity flags " ")))))))
        ;; Wait for the last command to complete to avoid later
-       ;; syncronisation problems with the stream.
+       ;; synchronization problems with the stream.
        (when sequence
          (nnimap-wait-for-response sequence))))))
 
@@ -1058,7 +1091,7 @@ textual parts.")
              (cons group
                    (or (nnimap-find-uid-response "APPENDUID" (car result))
                        (nnimap-find-article-by-message-id
-                        group message-id))))))))))
+                        group server message-id))))))))))
 
 (defun nnimap-process-quirk (greeting-match type data)
   (when (and (nnimap-greeting nnimap-object)
@@ -1143,48 +1176,35 @@ textual parts.")
   (when (nnimap-possibly-change-group nil server)
     (with-current-buffer nntp-server-buffer
       (erase-buffer)
-      (let ((groups
-            (with-current-buffer (nnimap-buffer)
-              (nnimap-get-groups)))
-           sequences responses)
-       (when groups
-         (with-current-buffer (nnimap-buffer)
-           (setf (nnimap-group nnimap-object) nil)
-           (dolist (group groups)
-             (setf (nnimap-examined nnimap-object) group)
-             (push (list (nnimap-send-command "EXAMINE %S"
-                                              (utf7-encode group t))
-                         group)
-                   sequences))
-           (nnimap-wait-for-response (caar sequences))
-           (setq responses
-                 (nnimap-get-responses (mapcar #'car sequences))))
-         (dolist (response responses)
-           (let* ((sequence (car response))
-                  (response (cadr response))
-                  (group (cadr (assoc sequence sequences))))
-             (when (and group
-                        (equal (caar response) "OK"))
-               (let ((uidnext (nnimap-find-parameter "UIDNEXT" response))
-                     highest exists)
-                 (dolist (elem response)
-                   (when (equal (cadr elem) "EXISTS")
-                     (setq exists (string-to-number (car elem)))))
-                 (when uidnext
-                   (setq highest (1- (string-to-number (car uidnext)))))
-                 (cond
-                  ((null highest)
-                   (insert (format "%S 0 1 y\n" (utf7-decode group t))))
-                  ((zerop exists)
-                   ;; Empty group.
-                   (insert (format "%S %d %d y\n"
-                                   (utf7-decode group t)
-                                   highest (1+ highest))))
-                  (t
-                   ;; Return the widest possible range.
-                   (insert (format "%S %d 1 y\n" (utf7-decode group t)
-                                   (or highest exists)))))))))
-         t)))))
+      (dolist (response
+               (with-current-buffer (nnimap-buffer)
+                 ;; Build a list of (group result-of-EXAMINE) for each group
+                 (mapcar
+                  (lambda (group)
+                    (list group (cdr (nnimap-possibly-change-group group server nil t))))
+                  (nnimap-get-groups))))
+        (let ((group (encode-coding-string (car response) 'utf-8))
+              (response (cadr response)))
+          (when (equal (caar response) "OK")
+            (let ((uidnext (nnimap-find-parameter "UIDNEXT" response))
+                highest exists)
+              (dolist (elem response)
+                (when (equal (cadr elem) "EXISTS")
+                  (setq exists (string-to-number (car elem)))))
+              (when uidnext
+                (setq highest (1- (string-to-number (car uidnext)))))
+              (cond
+               ((null highest)
+                (insert (format "%S 0 1 y\n" group)))
+               ((zerop exists)
+                ;; Empty group.
+                (insert (format "%S %d %d y\n" group
+                                highest (1+ highest))))
+               (t
+                ;; Return the widest possible range.
+                (insert (format "%S %d 1 y\n" group
+                                (or highest exists)))))))))
+      t)))
 
 (deffoo nnimap-request-newgroups (date &optional server)
   (when (nnimap-possibly-change-group nil server)
@@ -1194,16 +1214,19 @@ textual parts.")
                       (nnimap-get-groups)))
        (unless (assoc group nnimap-current-infos)
          ;; Insert dummy numbers here -- they don't matter.
-         (insert (format "%S 0 1 y\n" (utf7-encode group)))))
+         (insert (format "%S 0 1 y\n" (encode-coding-string group 'utf-8)))))
       t)))
 
 (deffoo nnimap-retrieve-group-data-early (server infos)
-  (when (nnimap-possibly-change-group nil server)
+  (when (and (nnimap-possibly-change-group nil server)
+            infos)
     (with-current-buffer (nnimap-buffer)
       (erase-buffer)
       (setf (nnimap-group nnimap-object) nil)
+      (setf (nnimap-initial-resync nnimap-object) 0)
       (let ((qresyncp (nnimap-capability "QRESYNC"))
-           params groups sequences active uidvalidity modseq group)
+           params groups sequences active uidvalidity modseq group
+           unexist)
        ;; Go through the infos and gather the data needed to know
        ;; what and how to request the data.
        (dolist (info infos)
@@ -1211,13 +1234,15 @@ textual parts.")
                group (nnimap-decode-gnus-group
                       (gnus-group-real-name (gnus-info-group info)))
                active (cdr (assq 'active params))
+               unexist (assq 'unexist (gnus-info-marks info))
                uidvalidity (cdr (assq 'uidvalidity params))
                modseq (cdr (assq 'modseq params)))
          (setf (nnimap-examined nnimap-object) group)
          (if (and qresyncp
                   uidvalidity
                   active
-                  modseq)
+                  modseq
+                  unexist)
              (push
               (list (nnimap-send-command "EXAMINE %S (%s (%s %s))"
                                          (utf7-encode group t)
@@ -1226,12 +1251,7 @@ textual parts.")
                     'qresync
                     nil group 'qresync)
               sequences)
-           (let ((start
-                  (if (and active uidvalidity)
-                      ;; Fetch the last 100 flags.
-                      (max 1 (- (cdr active) 100))
-                    1))
-                 (command
+           (let ((command
                   (if uidvalidity
                       "EXAMINE"
                     ;; If we don't have a UIDVALIDITY, then this is
@@ -1239,7 +1259,13 @@ textual parts.")
                     ;; have to do a SELECT (which is slower than an
                     ;; examine), but will tell us whether the group
                     ;; is read-only or not.
-                    "SELECT")))
+                    "SELECT"))
+                 start)
+             (if (and active uidvalidity unexist)
+                 ;; Fetch the last 100 flags.
+                 (setq start (max 1 (- (cdr active) 100)))
+               (incf (nnimap-initial-resync nnimap-object))
+               (setq start 1))
              (push (list (nnimap-send-command "%s %S" command
                                               (utf7-encode group t))
                          (nnimap-send-command "UID FETCH %d:* FLAGS" start)
@@ -1258,11 +1284,11 @@ textual parts.")
 
 (deffoo nnimap-finish-retrieve-group-infos (server infos sequences)
   (when (and sequences
+            (nnimap-possibly-change-group nil server t)
             ;; Check that the process is still alive.
             (get-buffer-process (nnimap-buffer))
             (memq (process-status (get-buffer-process (nnimap-buffer)))
-                  '(open run))
-            (nnimap-possibly-change-group nil server))
+                  '(open run)))
     (with-current-buffer (nnimap-buffer)
       ;; Wait for the final data to trickle in.
       (when (nnimap-wait-for-response (if (eq (cadar sequences) 'qresync)
@@ -1317,7 +1343,8 @@ textual parts.")
             (cdr (assq 'uidvalidity (gnus-info-params info)))))
        (and old-uidvalidity
             (not (equal old-uidvalidity uidvalidity))
-            (> start-article 1)))
+             (or (not start-article)
+                 (> start-article 1))))
       (gnus-group-remove-parameter info 'uidvalidity)
       (gnus-group-remove-parameter info 'modseq))
      ;; We have the data needed to update.
@@ -1414,6 +1441,25 @@ textual parts.")
                      (setq new-marks (gnus-range-nconcat old-marks new-marks)))
                    (when new-marks
                      (push (cons (car type) new-marks) marks)))))
+             ;; Keep track of non-existing articles.
+             (let* ((old-unexists (assq 'unexist marks))
+                    (active (gnus-active group))
+                    (unexists
+                     (if completep
+                         (gnus-range-difference
+                          active
+                          (gnus-compress-sequence existing))
+                       (gnus-add-to-range
+                        (cdr old-unexists)
+                        (gnus-list-range-difference
+                         existing (gnus-active group))))))
+               (when (> (car active) 1)
+                 (setq unexists (gnus-range-add
+                                 (cons 1 (1- (car active)))
+                                 unexists)))
+               (if old-unexists
+                   (setcdr old-unexists unexists)
+                 (push (cons 'unexist unexists) marks)))
              (gnus-info-set-marks info marks t))))
        ;; Tell Gnus whether there are any \Recent messages in any of
        ;; the groups.
@@ -1457,6 +1503,14 @@ textual parts.")
                      (gnus-sorted-complement existing new-marks))))
        (when ticks
          (push (cons (car type) ticks) marks)))
+      (gnus-info-set-marks info marks t))
+    ;; Add vanished to the list of unexisting articles.
+    (when vanished
+      (let* ((old-unexists (assq 'unexist marks))
+            (unexists (gnus-range-add (cdr old-unexists) vanished)))
+       (if old-unexists
+           (setcdr old-unexists unexists)
+         (push (cons 'unexist unexists) marks)))
       (gnus-info-set-marks info marks t))))
 
 (defun nnimap-imap-ranges-to-gnus-ranges (irange)
@@ -1509,7 +1563,8 @@ textual parts.")
 
 (defun nnimap-parse-flags (sequences)
   (goto-char (point-min))
-  ;; Change \Delete etc to %Delete, so that the reader can read it.
+  ;; Change \Delete etc to %Delete, so that the Emacs Lisp reader can
+  ;; read it.
   (subst-char-in-region (point-min) (point-max)
                        ?\\ ?% t)
   ;; Remove any MODSEQ entries in the buffer, because they may contain
@@ -1551,7 +1606,7 @@ textual parts.")
                 (goto-char start)
                 (setq vanished
                       (and (eq flag-sequence 'qresync)
-                           (re-search-forward "^\\* VANISHED .* \\([0-9:,]+\\)"
+                           (re-search-forward "^\\* VANISHED .*? \\([0-9:,]+\\)"
                                               (or end (point-min)) t)
                            (match-string 1)))
                 (goto-char start)
@@ -1580,7 +1635,9 @@ textual parts.")
                             vanished highestmodseq)
                       articles)
                groups)
-         (goto-char end)
+         (if (eq flag-sequence 'qresync)
+             (goto-char end)
+           (setq end (point)))
          (setq articles nil))))
     groups))
 
@@ -1591,9 +1648,12 @@ textual parts.")
   (setq nnimap-status-string "Read-only server")
   nil)
 
+(defvar gnus-refer-thread-use-nnir) ;; gnus-sum.el
 (declare-function gnus-fetch-headers "gnus-sum"
                  (articles &optional limit force-new dependencies))
 
+(autoload 'nnir-search-thread "nnir")
+
 (deffoo nnimap-request-thread (header &optional group server)
   (when group
     (setq group (nnimap-decode-gnus-group group)))
@@ -1605,15 +1665,20 @@ textual parts.")
                        (nnimap-command  "UID SEARCH %s" cmd))))
         (when result
           (gnus-fetch-headers
-           (and (car result) (delete 0 (mapcar #'string-to-number
-                                               (cdr (assoc "SEARCH" (cdr result))))))
+           (and (car result)
+               (delete 0 (mapcar #'string-to-number
+                                 (cdr (assoc "SEARCH" (cdr result))))))
            nil t))))))
 
-(defun nnimap-possibly-change-group (group server)
+(defun nnimap-possibly-change-group (group server &optional no-reconnect read-only)
+  "Possibly change group to GROUP.
+If SERVER is set, check that server is connected, otherwise retry
+to reconnect, unless NO-RECONNECT is set to t.
+if READ-ONLY is set, send EXAMINE rather than SELECT to the server."
   (let ((open-result t))
     (when (and server
               (not (nnimap-server-opened server)))
-      (setq open-result (nnimap-open-server server)))
+      (setq open-result (nnimap-open-server server nil no-reconnect)))
     (cond
      ((not open-result)
       nil)
@@ -1623,7 +1688,11 @@ textual parts.")
       (with-current-buffer (nnimap-buffer)
        (if (equal group (nnimap-group nnimap-object))
            t
-         (let ((result (nnimap-command "SELECT %S" (utf7-encode group t))))
+         (let ((result (nnimap-command "%s %S"
+                                        (if read-only
+                                            "EXAMINE"
+                                          "SELECT")
+                                        (utf7-encode group t))))
            (when (car result)
              (setf (nnimap-group nnimap-object) group
                    (nnimap-select-result nnimap-object) result)
@@ -1660,13 +1729,18 @@ textual parts.")
     (nnimap-wait-for-response nnimap-sequence))
   nnimap-sequence)
 
+(defvar nnimap-record-commands nil
+  "If non-nil, log commands to the \"*imap log*\" buffer.")
+
 (defun nnimap-log-command (command)
-  (with-current-buffer (get-buffer-create "*imap log*")
-    (goto-char (point-max))
-    (insert (format-time-string "%H:%M:%S") " "
-           (if nnimap-inhibit-logging
-               "(inhibited)\n"
-             command)))
+  (when nnimap-record-commands
+    (with-current-buffer (get-buffer-create "*imap log*")
+      (goto-char (point-max))
+      (insert (format-time-string "%H:%M:%S")
+             " [" nnimap-address "] "
+             (if nnimap-inhibit-logging
+                 "(inhibited)\n"
+               command))))
   command)
 
 (defun nnimap-command (&rest args)
@@ -1717,9 +1791,19 @@ textual parts.")
                                      (looking-at "\\*"))))
                        (not (looking-at (format "%d .*\n" sequence)))))
            (when messagep
-             (nnheader-message 7 "nnimap read %dk" (/ (buffer-size) 1000)))
+             (nnheader-message-maybe
+              7 "nnimap read %dk from %s%s" (/ (buffer-size) 1000)
+              nnimap-address
+              (if (not (zerop (nnimap-initial-resync nnimap-object)))
+                  (format " (initial sync of %d group%s; please wait)"
+                          (nnimap-initial-resync nnimap-object)
+                          (if (= (nnimap-initial-resync nnimap-object) 1)
+                              ""
+                            "s"))
+                "")))
            (nnheader-accept-process-output process)
            (goto-char (point-max)))
+         (setf (nnimap-initial-resync nnimap-object) 0)
           openp)
       (quit
        (when debug-on-quit