The following code will offer the keybinding ‘C-M-c’ in the Rmail summary buffer. It will then prompt you for a comma-separated list of regular expressions (defaulting to the subject of the current message), and show you the conversation view.

Here’s what an ordinary summary by topic summary (‘C-M-t’) looks like. Notice how much space is wasted repeating the subject again and again, eventhough you know that it always the same – you filtered by topic, after all:


The conversation view (‘C-M-c’) extracts the beginning of the message body, instead. It also strips extra whitespace, quotes, indented lines, lines ending in “wrote:” etc. The result gives you a much better overview of the conversation:



(require 'rmime) ;; rmime-magic-string

(eval-after-load "rmail"
  (define-key rmail-summary-mode-map "\e\C-C"

(defun rmail-summary-by-topic-no-subject ()
  (with-current-buffer rmail-buffer
    (let ((rmail-summary-vector (make-vector
				 (length rmail-summary-vector)
      (flet ((rmail-make-summary-line-1 (msg)
	       (rmail-make-summary-line-1-no-subject msg)))
	(call-interactively 'rmail-summary-by-topic)))))
(defun rmail-make-summary-line-1-no-subject (msg)
  "Replacement defun that does not take into account the cached summary line."
  (goto-char (rmail-msgbeg msg))
  (let* ((case-fold-search t)
	 (next (rmail-msgend msg))
	 (beg (if (progn (goto-char (rmail-msgbeg msg))
			 (search-forward "\n*** EOOH ***\n" next t))
		(forward-line 1)
	 (end (rmail-msgend msg))
	 pos line)
      (narrow-to-region beg end)
      (goto-char beg)
      (setq line (rmail-make-basic-summary-line-no-subject)))
    (setq pos (string-match "#" line))
    (aset rmail-summary-vector (1- msg)
	  (funcall rmail-summary-line-decoder
		   (concat (format "%5d  " msg)
			   (substring line 0 pos)
			   (substring line (1+ pos)))))))

(defun rmail-make-basic-summary-line-no-subject ()
  (goto-char (point-min))
  (concat (save-excursion
	    (if (not (re-search-forward "^Date:" nil t))
		"      "
	      (cond ((re-search-forward "\\([^0-9:]\\)\\([0-3]?[0-9]\\)\\([- \t_]+\\)\\([adfjmnos][aceopu][bcglnprtvy]\\)"
		      (save-excursion (end-of-line) (point)) t)
		     (format "%2d-%3s"
			     (string-to-number (buffer-substring
                                                (match-beginning 2)
                                                (match-end 2)))
			      (match-beginning 4) (match-end 4))))
		    ((re-search-forward "\\([^a-z]\\)\\([adfjmnos][acepou][bcglnprtvy]\\)\\([-a-z \t_]*\\)\\([0-9][0-9]?\\)"
		      (save-excursion (end-of-line) (point)) t)
		     (format "%2d-%3s"
			     (string-to-number (buffer-substring
                                                (match-beginning 4)
                                                (match-end 4)))
			      (match-beginning 2) (match-end 2))))
		    ((re-search-forward "\\(19\\|20\\)\\([0-9][0-9]\\)-\\([01][0-9]\\)-\\([0-3][0-9]\\)"
		      (save-excursion (end-of-line) (point)) t)
		     (format "%2s%2s%2s"
			      (match-beginning 2) (match-end 2))
			      (match-beginning 3) (match-end 3))
			      (match-beginning 4) (match-end 4))))
		    (t "??????"))))
	  "  "
	    (let* ((from (and (re-search-forward "^From:[ \t]*" nil t)
				(1- (point))
				;; Get all the lines of the From field
				;; so that we get a whole comment if there is one,
				;; so that mail-strip-quoted-names can discard it.
				(let ((opoint (point)))
				  (while (progn (forward-line 1)
						(looking-at "[ \t]")))
				  ;; Back up over newline, then trailing spaces or tabs
				  (forward-char -1)
				  (skip-chars-backward " \t")
		   len mch lo)
	      (if (or (null from)
		       (or rmail-user-mail-address-regexp
			   (concat "^\\("
				   (regexp-quote (user-login-name))
				    ;; Don't lose if run from init file
				    ;; where user-mail-address is not
				    ;; set yet.
				    (or user-mail-address
					(concat (user-login-name) "@"
						(or mail-host-address
		  ;; No From field, or it's this user.
		    (goto-char (point-min))
		    (if (not (re-search-forward "^To:[ \t]*" nil t))
		      (setq from
			    (concat "to: "
				      (progn (end-of-line)
					     (skip-chars-backward " \t")
	      (if (null from)
		  "                         "
		(setq len (length from))
		(setq mch (string-match "[@%]" from))
		(format "%25s"
			(if (or (not mch) (<= len 25))
			    (substring from (max 0 (- len 25)))
			  (substring from
				     (setq lo (cond ((< (- mch 14) 0) 0)
						    ((< len (+ mch 11))
						     (- len 25))
						    (t (- mch 14))))
				     (min len (+ lo 25))))))))
	  "  #"				;The # is part of the format.
	   (re-search-forward "\n\n" nil t)
	   (skip-chars-forward " \t\n\r\l")
	   (or (let* ((re (concat "^--\\|^==\\|^\\*\\*\\|" rmime-magic-string))
		      (max (save-excursion
			     (or (and (re-search-forward re nil t)
				      (match-beginning 0))
		      (str (buffer-substring (point) max)))
		 (setq str (replace-regexp-in-string
			    "^\\(  \\|>\\).*\n" "" str))
		 (setq str (replace-regexp-in-string
			    "^.* wrote:\n" "" str))
		 (setq str (replace-regexp-in-string
			    "\\s-+" " " str))
		 (when (= ?\  (aref str 0))
		   (setq str (substring str 1)))


I’ve had to duplicate some code because it is tricky to hook into the summary generating code. The default summary line is being cached, which I had to circumvent, and there is no way to change the generation of the summary line.

I’d like to rewrite this code to be more generic, eventually. But I guess this might have to wait until after the release.

I’ve started to work on the mbox branch instead of pursuing this patch. The mbox branch no longer does all this weird caching, so these kinds of user interface experiments should be easier to do in the future. – AlexSchroeder


I have tried to run this code in a recent GNU Emacs alas, there is nothing different than running directly C-M-t. Is it normal ? – XavierMaillard

Perhaps the default behaviour simply changed? If so, we should simply mark this code as deprecated. – Alex Schroeder

CategoryMail CategoryMailAddons