Subject: bug#46374: 28.0.50; Ask me to save buffers only if they are under callers dir

From: Tino Calancha <[email-masked]>
Date: Sun, 07 Feb 2021 23:32:07 +0100

X-Debbugs-Cc: Stefan Monnier <[email-masked]>, <[email-masked]>
Severity: wishlist
Severity: patch

I wish, by default, to only been prompted for buffers whose default-directory
is under the caller dir (except when closing Emacs).

## Description
Everyday I connect via Tramp with machines at each of my properties (Hawaii,
Maldives, Palawan, and a very large etc.)
Then, in the same Emacs session, I connect to a host in Wall Street to see
how my stock grows, making me richer.
Often, a compilation buffer at New York city prompts me to save a buffer
at Waikiki beach.  Quite distracting! Cannot focus on my money!

I found some people with related issues:
https://emacs.stackexchange.com/questions/7268/package-el-asks-whether-i-want-to-save-modified-files-before-package-installatio
https://emacs.stackexchange.com/questions/40593/automatically-dont-save-buffers-before-compiling

## How to reproduce
Sure, I understand not all of you can possibly reproduce the above conditions.
Maybe you can try the following poor man's recipe:

emacs -Q ~/foo.txt
;; write something and do not save
foo
;; now visit another, for instance, the Emacs source dir
C-x d EMACS-SRC-DIR RET
;; call rgrep with whatever string
M-x rgrep money RET *.el RET RET
;; You will be prompted to save ~/foo.txt

I am aware of `grep-save-buffers', `compilation-save-buffers-predicate' and the solutions
proposed in the links above.

My proposal adds a new option `save-some-buffers-restrict-to-caller-subdirs'.
I am already using it for a while with joy (10 bagger at GameStop using it!).

--8<-----------------------------cut here---------------start------------->8---
commit 85e5399f035fb698fcfbb50ca01980fbbc68707c
Author: Tino Calancha <[email-masked]>
Date:   Thu Feb 4 21:39:37 2021 +0100

    save-some-buffers: Add option restricting to files in a caller's subdir
    
    Restrict the action to buffers with `default-directory' lying in a
    subdir of the `default-directory' from where the command is invoked.
    
    * lisp/files.el (save-some-buffers-restrict-to-caller-subdirs): New option.
    (save-some-buffers)
    (save-buffers-kill-emacs): Use it.
    
    * doc/emacs/files.texi (Save Commands)
    * doc/lispref/files.texi (Saving Buffers): Document it.
    
    * etc/NEWS (Editing Changes in Emacs 28.1): Announce this change.
    
    * lisp/progmodes/grep.el (grep-save-buffers)
    * lisp/progmodes/compile.el (compilation-save-buffers-predicate):
    Mention it in the docstring.
    
    * test/lisp/files-tests.el (files-tests--save-some-buffers): Helper function.
    (files-tests-with-all-permutations)
    (files-tests--with-buffer-offer-save): Helper macros.
    (files-tests-save-some-buffers)
    (files-tests-buffer-offer-save)
    (files-tests-save-buffers-kill-emacs--asks-to-save-buffers): New tests.

diff --git a/doc/emacs/files.texi b/doc/emacs/files.texi
index 6b3bc430d9..36d38218e9 100644
--- a/doc/emacs/files.texi
+++ b/doc/emacs/files.texi
@@ -433,9 +433,11 @@ Save Commands
 
 @noindent
 @vindex save-some-buffers-default-predicate
+@vindex save-some-buffers-restrict-to-caller-subdirs
 You can customize the value of
-@code{save-some-buffers-default-predicate} to control which buffers
-Emacs will ask about.
+@code{save-some-buffers-default-predicate} and
+@code{save-some-buffers-restrict-to-caller-subdirs} to control which
+buffers Emacs will ask about.
 
   @kbd{C-x C-c}, the key sequence to exit Emacs, invokes
 @code{save-some-buffers} and therefore asks the same questions.
diff --git a/doc/lispref/files.texi b/doc/lispref/files.texi
index 4110c51099..a9855fef2b 100644
--- a/doc/lispref/files.texi
+++ b/doc/lispref/files.texi
@@ -371,6 +371,7 @@ Saving Buffers
 querying the user.
 
 @vindex save-some-buffers-default-predicate
+@vindex save-some-buffers-restrict-to-caller-subdirs
 The optional @var{pred} argument provides a predicate that controls
 which buffers to ask about (or to save silently if
 @var{save-silently-p} is non-@code{nil}).  If @var{pred} is
@@ -381,8 +382,12 @@ Saving Buffers
 other non-file buffers---those that have a non-@code{nil} buffer-local
 value of @code{buffer-offer-save} (@pxref{Killing Buffers}).  A user
 who says @samp{yes} to saving a non-file buffer is asked to specify
-the file name to use.  The @code{save-buffers-kill-emacs} function
-passes the value @code{t} for @var{pred}.
+the file name to use.  The option
+@code{save-some-buffers-restrict-to-caller-subdirs} restricts the
+action of this command to buffers with @code{default-directory} in a
+subdirectory of the caller's @code{default-directory}.  The
+@code{save-buffers-kill-emacs} function ignores this option and passes
+the value @code{t} for @var{pred}.
 
 If the predicate is neither @code{t} nor @code{nil}, then it should be
 a function of no arguments.  It will be called in each buffer to decide
diff --git a/etc/NEWS b/etc/NEWS
index b3d53bf73c..f1bd21f26a 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -247,6 +247,11 @@ commands.  The new keystrokes are 'C-x x g' ('revert-buffer'),
 
 * Editing Changes in Emacs 28.1
 
++++
+** The new option 'save-some-buffers-restrict-to-caller-subdirs'
+restricts the action of 'same-some-buffers' to buffers with
+'default-directory' in a subdir of the callers 'default-directory'.
+
 ---
 ** 'eval-expression' now no longer signals an error on incomplete expressions.
 Previously, typing 'M-: ( RET' would result in Emacs saying "End of
diff --git a/lisp/files.el b/lisp/files.el
index dada69c145..d51cd58217 100644
--- a/lisp/files.el
+++ b/lisp/files.el
@@ -5520,6 +5520,19 @@ save-some-buffers-default-predicate
   :type '(choice (const :tag "Default" nil) function)
   :version "26.1")
 
+(defcustom save-some-buffers-restrict-to-caller-subdirs nil
+  "Only save buffers under caller's default directory.
+I.e., only prompt for modified buffers whose `default-directory' is in
+in a subdir of the directory from where `save-some-buffers' is
+invoked.
+Note that `save-buffers-kill-emacs' ignores this value and prompts for
+any unsaved buffer."
+  :group 'auto-save
+  :type '(choice
+          (const :tag "All buffers" nil)
+          (const :tag "Buffers under caller's default directory" t))
+  :version "28.1")
+
 (defun save-some-buffers (&optional arg pred)
   "Save some modified file-visiting buffers.  Asks user about each one.
 You can answer `y' or SPC to save, `n' or DEL not to save, `C-r'
@@ -5543,12 +5556,35 @@ save-some-buffers
 to consider it or not when called with that buffer current.
 PRED defaults to the value of `save-some-buffers-default-predicate'.
 
+You can restrict to modified buffers with `default-directory' under
+the caller's `default-directory' with
+`save-some-buffers-restrict-to-caller-subdirs'.
+
 See `save-some-buffers-action-alist' if you want to
 change the additional actions you can take on files."
   (interactive "P")
-  (unless pred
-    (setq pred save-some-buffers-default-predicate))
-  (let* ((switched-buffer nil)
+  (let* ((caller-dir default-directory)
+         (maybe-save-buffer-p
+          (lambda (buffer)
+            (or (not save-some-buffers-restrict-to-caller-subdirs)
+                (file-in-directory-p (buffer-local-value 'default-directory buffer)
+                                     caller-dir))))
+         (effective-pred
+          (unless (eq t pred)
+            (let ((def-pred save-some-buffers-default-predicate))
+              (lambda () (and (funcall maybe-save-buffer-p (current-buffer))
+                              (if (functionp pred) (funcall pred)
+                                (or (not (functionp def-pred))
+                                    (funcall def-pred))))))))
+         (switched-buffer nil)
+         (non-visiting-buffers-ok (not (null pred)))
+         (buffer-name-matches-filename-p
+          (lambda (buffer)
+            "Return non-nil if BUFFER name is similar to its file name."
+            (let ((file-basename (file-name-nondirectory (buffer-file-name buffer))))
+              (or (equal (buffer-name buffer) file-basename)
+                  (string-match-p (format "\\<%s<[^>]*>\\'" (regexp-quote file-basename))
+                                  (buffer-name buffer))))))
          (save-some-buffers--switch-window-callback
           (lambda (buffer)
             (setq switched-buffer buffer)))
@@ -5578,36 +5614,20 @@ save-some-buffers
                          (buffer-file-name buffer)
                          (with-current-buffer buffer
                            (or (eq buffer-offer-save 'always)
-                               (and pred buffer-offer-save
-                                    (> (buffer-size) 0)))))
-                        (or (not (functionp pred))
-                            (with-current-buffer buffer (funcall pred)))
+                               (and non-visiting-buffers-ok buffer-offer-save (> (buffer-size) 0)))))
+                        (or (not (functionp effective-pred))
+                            (with-current-buffer buffer (funcall effective-pred)))
                         (if arg
-                            t
+                            (funcall maybe-save-buffer-p buffer)
                           (setq queried t)
-                          (if (buffer-file-name buffer)
-                              (if (or
-                                   (equal (buffer-name buffer)
-                                          (file-name-nondirectory
-                                           (buffer-file-name buffer)))
-                                   (string-match
-                                    (concat "\\<"
-                                            (regexp-quote
-                                             (file-name-nondirectory
-                                              (buffer-file-name buffer)))
-                                            "<[^>]*>\\'")
-                                    (buffer-name buffer)))
-                                  ;; The buffer name is similar to the
-                                  ;; file name.
-                                  (format "Save file %s? "
-                                          (buffer-file-name buffer))
-                                ;; The buffer and file names are
-                                ;; dissimilar; display both.
-                                (format "Save file %s (buffer %s)? "
-                                        (buffer-file-name buffer)
-                                        (buffer-name buffer)))
-                            ;; No file name
-                            (format "Save buffer %s? " (buffer-name buffer))))))
+                          (when (funcall maybe-save-buffer-p buffer)
+                            (cond ((null (buffer-file-name buffer))
+                                   (format "Save buffer %s? " (buffer-name buffer)))
+                                  ((funcall buffer-name-matches-filename-p buffer)
+                                   (format "Save file %s? " (buffer-file-name buffer)))
+                                  (t (format "Save file %s (buffer %s)? "
+                                             (buffer-file-name buffer)
+                                             (buffer-name buffer))))))))
                  (lambda (buffer)
                    (with-current-buffer buffer
                      (save-buffer)))
@@ -7362,7 +7382,8 @@ save-buffers-kill-emacs
   (interactive "P")
   ;; Don't use save-some-buffers-default-predicate, because we want
   ;; to ask about all the buffers before killing Emacs.
-  (save-some-buffers arg t)
+  (let ((save-some-buffers-restrict-to-caller-subdirs nil))
+    (save-some-buffers arg t))
   (let ((confirm confirm-kill-emacs))
     (and
      (or (not (memq t (mapcar (lambda (buf)
diff --git a/lisp/progmodes/compile.el b/lisp/progmodes/compile.el
index 48b5ee9973..d3d3849c83 100644
--- a/lisp/progmodes/compile.el
+++ b/lisp/progmodes/compile.el
@@ -790,7 +790,10 @@ compilation-save-buffers-predicate
     (string-prefix-p my-compilation-root (file-truename (buffer-file-name))))
 to limit saving to files located under `my-compilation-root'.
 Note, that, in general, `compilation-directory' cannot be used instead
-of `my-compilation-root' here."
+of `my-compilation-root' here.
+
+See `save-some-buffers-restrict-to-caller-subdirs' for a consistent
+way to achieve this."
   :type '(choice
           (const :tag "Default (save all file-visiting buffers)" nil)
           (const :tag "Save all buffers" t)
diff --git a/lisp/progmodes/grep.el b/lisp/progmodes/grep.el
index d6ee8bb423..5454d47211 100644
--- a/lisp/progmodes/grep.el
+++ b/lisp/progmodes/grep.el
@@ -246,7 +246,11 @@ grep-save-buffers
 buffer should be saved or not.  E.g., one can set this to
   (lambda ()
     (string-prefix-p my-grep-root (file-truename (buffer-file-name))))
-to limit saving to files located under `my-grep-root'."
+to limit saving to files located under `my-grep-root'.
+
+Note that `my-grep-root' is only known at runtime.  See
+`save-some-buffers-restrict-to-caller-subdirs' for a consistent way to
+achieve the same goal."
   :version "26.1"
   :type '(choice
           (const :tag "Ask before saving" ask)
diff --git a/test/lisp/files-tests.el b/test/lisp/files-tests.el
index 149cc689ae..14d6bc099d 100644
--- a/test/lisp/files-tests.el
+++ b/test/lisp/files-tests.el
@@ -1432,5 +1432,206 @@ files-tests-revert-buffer-with-fine-grain
                                (buffer-substring (point-min) (point-max))
                                nil nil)))))
 
-(provide 'files-tests)
+(defun files-tests--save-some-buffers (pred caller-subdirs-only exp-1 exp-2)
+  "Helper function to test `save-some-buffers'.
+
+This function creates two visiting-file buffers, BUF-1, BUF-2 in
+ different directories at the same level, i.e., none of them is a
+ subdir of the other; then, it modifies both buffers; finally it calls
+ `save-some-buffers' from BUF-1 with first arg t and second arg PRED
+ and `save-some-buffers-restrict-to-caller-subdirs' let-bound to
+ CALLER-SUBDIRS-ONLY.
+
+EXP-1 and EXP-1 are the expected values of the modified flags of BUF-1
+and BUF-2 after the `save-some-buffers' call.
+
+The test is repeated with `save-some-buffers-default-predicate'
+let-bound to PRED and passing nil as second arg of
+`save-some-buffers'."
+  (let* ((dir (make-temp-file "testdir" 'dir))
+         (file-1 (expand-file-name "subdir-1/file.foo" dir))
+         (file-2 (expand-file-name "subdir-2/file.bar" dir))
+         (inhibit-message t)
+         buf-1 buf-2)
+    (unwind-protect
+        (progn
+          (make-empty-file file-1 'parens)
+          (make-empty-file file-2 'parens)
+          (setq buf-1 (find-file file-1)
+                buf-2 (find-file file-2))
+          (dolist (buf (list buf-1 buf-2))
+            (with-current-buffer buf (insert "foobar\n")))
+          ;; buf-2 is ignored if `save-some-buffers-restrict-to-caller-subdirs' is non-nil.
+          (with-current-buffer buf-1
+            (let ((save-some-buffers-restrict-to-caller-subdirs caller-subdirs-only))
+              (save-some-buffers t pred))
+            (should (eq exp-1 (buffer-modified-p buf-1)))
+            (should (eq exp-2 (buffer-modified-p buf-2))))
+
+          ;; Set both buffers as modified to repeat the test.
+          (dolist (buf (list buf-1 buf-2))
+            (with-current-buffer buf (set-buffer-modified-p t)))
+          ;; Same result with: `save-some-buffers-default-predicate' -> pred, pred -> nil.
+          (with-current-buffer buf-1
+            (let ((save-some-buffers-restrict-to-caller-subdirs caller-subdirs-only)
+                  (save-some-buffers-default-predicate (and (functionp pred) pred)))
+              (save-some-buffers t nil))
+            (should (eq exp-1 (buffer-modified-p buf-1)))
+            (should (eq exp-2 (buffer-modified-p buf-2)))))
+      ;; Clean up.
+      (dolist (buf (list buf-1 buf-2))
+        (with-current-buffer buf
+          (set-buffer-modified-p nil)
+          (kill-buffer buf)))
+      (delete-directory dir 'recursive))))
+
+(ert-deftest files-tests-save-some-buffers ()
+  "Test `save-some-buffers'.
+Test the 3 cases for the second argument PRED, i.e., `nil', `t' or
+predicate."
+  (let* ((foo-file-p (lambda () (string-suffix-p ".foo" buffer-file-name)))
+         (bar-file-p (lambda () (string-suffix-p ".bar" buffer-file-name)))
+         (args-results `((nil         nil nil nil)
+                         (nil         t   nil t)
+                         (,foo-file-p nil nil t)
+                         (,bar-file-p nil t   nil)
+                         (,foo-file-p t   nil t)
+                         (,bar-file-p t   t   t)
+                         (t           nil nil nil)
+                         (t           t   nil t))))
+    (pcase-dolist (`(,pred ,caller-subdirs-only ,exp-1 ,exp-2) args-results)
+      (files-tests--save-some-buffers pred caller-subdirs-only exp-1 exp-2))))
+
+(defmacro files-tests--with-buffer-offer-save (buffers-offer fn-test fn-binders args-results)
+  "Helper macro to test `save-some-buffers' and `save-buffers-kill-emacs'.
+
+This macro creates several non-visiting-file buffers in different
+ directories at the same level, i.e., none of them is a subdir of the
+ other; then, it modifies the buffers and sets `buffer-offer-save' per
+ each buffer as specified by BUFFERS-OFFER, a list of elements
+ (BUFFER OFFER-SAVE).  Finally it calls FN-TEST from the first
+ buffer.
+
+FN-TEST is the function to test: either `save-some-buffers' or
+`save-buffers-kill-emacs'.  This function is called with
+`save-some-buffers-restrict-to-caller-subdirs' let-bound to a value
+specified inside ARGS-RESULTS.
+
+FN-BINDERS is a list of elements (FUNCTION . BINDING), where FUNCTION
+is a function symbol that this macro temporary binds to BINDING during
+the FN-TEST call.
+ARGS-RESULTS is a list of elements (FN-ARGS CALLERS-DIR RESULTS), where
+ FN-ARGS are the arguments for FN-TEST;
+ CALLERS-DIR specify the value to let-bind
+`save-some-buffers-restrict-to-caller-subdirs';
+ RESULTS are the expected results of the test."
+  (declare (debug (form symbol form form)))
+  (let ((dir (gensym "dir"))
+        (buffers (gensym "buffers")))
+    `(let* ((,dir (make-temp-file "testdir" 'dir))
+            (inhibit-message t)
+            (use-dialog-box nil)
+            ,buffers)
+       (pcase-dolist (`(,bufsym ,offer-save) ,buffers-offer)
+         (let* ((buf (generate-new-buffer (symbol-name bufsym)))
+                (subdir (expand-file-name
+                         (format "subdir-%s" (buffer-name buf))
+                         ,dir)))
+           (make-directory subdir 'parens)
+           (push buf ,buffers)
+           (with-current-buffer buf
+             (cd subdir)
+             (setq buffer-offer-save offer-save)
+             (insert "foobar\n"))))
+       (setq ,buffers (nreverse ,buffers))
+
+       (let ((nb-saved-buffers 0))
+         (unwind-protect
+             (pcase-dolist (`(,fn-test-args ,callers-dir ,expected)
+                            ,args-results)
+               (setq nb-saved-buffers 0)
+               (with-current-buffer (car ,buffers)
+                 (cl-letf
+                     (,@(mapcar (lambda (pair) `((symbol-function ,(car pair)) ,(cdr pair)))
+                                fn-binders)
+                      (save-some-buffers-restrict-to-caller-subdirs callers-dir))
+                   (apply #',fn-test fn-test-args)
+                   (should (equal nb-saved-buffers expected)))))
+           ;; Clean up.
+           (dolist (buf ,buffers)
+             (with-current-buffer buf
+               (set-buffer-modified-p nil)
+               (kill-buffer buf)))
+           (delete-directory ,dir 'recursive))))))
+
+(defmacro files-tests-with-all-permutations (permutation list &rest body)
+  "Execute BODY forms for all permutation of LIST.
+Execute the forms with the symbol PERMUTATION bound to the current
+permutation."
+  (declare (indent 2) (debug (symbol form body)))
+  (let ((vec (gensym "vec")))
+    `(let ((,vec (vconcat ,list)))
+       (cl-labels ((swap (,vec i j)
+                         (let ((tmp (aref ,vec j)))
+                           (aset ,vec j (aref ,vec i))
+                           (aset ,vec i tmp)))
+                   (permute (,vec l r)
+                            (if (= l r)
+                                (let ((,permutation (append ,vec nil)))
+                                  ,@body)
+                              (cl-loop for idx from l below (1+ r) do
+                                       (swap ,vec idx l)
+                                       (permute ,vec (1+ l) r)
+                                       (swap ,vec idx l)))))
+         (permute ,vec 0 (1- (length ,vec)))))))
+
+(ert-deftest files-tests-buffer-offer-save ()
+  "Test `save-some-buffers'.
+Check the expected behavior for non-visiting-file buffers with
+a non-nil value of `buffer-offer-save'."
+  (let* ((buffers-offer-init '((buf-1 t) (buf-2 always) (buf-3 nil)))
+         (nb-might-save
+          (length
+           (cl-remove-if (lambda (l) (null (cadr l))) buffers-offer-init)))
+         (nb-always-save
+          (length
+           (cl-remove-if-not (lambda (l) (eq 'always (cadr l))) buffers-offer-init))))
+    (files-tests-with-all-permutations
+     buffers-offer
+     buffers-offer-init
+     (dolist (must-save `(nil t))
+       (dolist (callers-dir `(nil t))
+         (let* ((head-offer (cadar buffers-offer))
+                (res (if must-save
+                         (if callers-dir (or (and head-offer 1) 0)
+                           nb-might-save)
+                       (if callers-dir (or (and (eq 'always head-offer) 1) 0)
+                         nb-always-save)))
+                (args-res `(((nil ,must-save) ,callers-dir ,res))))
+           (files-tests--with-buffer-offer-save
+            buffers-offer
+            save-some-buffers
+            ;; Increase counter and answer 'n' when prompted to save a buffer.
+            (('read-event . (lambda () (cl-incf nb-saved-buffers) ?n)))
+            args-res)))))))
+
+(ert-deftest files-tests-save-buffers-kill-emacs--asks-to-save-buffers ()
+  "Test that `save-buffers-kill-emacs' asks to save buffers as expected."
+  (let* ((buffers-offer-init '((buf-1 t) (buf-2 always) (buf-3 nil)))
+         (nb-might-save
+          (length
+           (cl-remove-if (lambda (l) (null (cadr l))) buffers-offer-init))))
+    (files-tests-with-all-permutations
+     buffers-offer
+     buffers-offer-init
+     ;; Order doesn't matter: ask to save any buffer with non-nil `buffer-offer-save'.
+     (files-tests--with-buffer-offer-save
+      buffers-offer
+      save-buffers-kill-emacs
+      ;; Increase counter and answer 'n' when prompted to save a buffer.
+      (('read-event . (lambda () (cl-incf nb-saved-buffers) ?n))
+       ('kill-emacs . #'ignore)) ; Do not kill Emacs.
+      `((nil nil ,nb-might-save) (nil t ,nb-might-save))))))
+
+
 ;;; files-tests.el ends here

--8<-----------------------------cut here---------------end--------------->8---

In GNU Emacs 28.0.50 (build 2, x86_64-pc-linux-gnu, X toolkit, cairo version 1.16.0, Xaw scroll bars)
 of 2021-02-07 built on localhost.example.com
Repository revision: 7c5938ad7d8884d03471e2395937e11611faadb9
Repository branch: master
Windowing system distributor 'The X.Org Foundation', version 11.0.12010000
System Description: openSUSE Tumbleweed

Configured using:
 'configure --with-x-toolkit=lucid'

Configured features:
CAIRO DBUS FREETYPE GIF GLIB GMP GNUTLS GSETTINGS HARFBUZZ JPEG
LIBSELINUX LIBXML2 MODULES NOTIFY INOTIFY PDUMPER PNG SOUND THREADS TIFF
TOOLKIT_SCROLL_BARS X11 XDBE XIM XPM LUCID ZLIB

Important settings:
  value of $LANG: en_US.UTF-8
  value of $XMODIFIERS: @im=ibus
  locale-coding-system: utf-8-unix

Major mode: Lisp Interaction

Minor modes in effect:
  tooltip-mode: t
  global-eldoc-mode: t
  eldoc-mode: t
  electric-indent-mode: t
  mouse-wheel-mode: t
  tool-bar-mode: t
  menu-bar-mode: t
  file-name-shadow-mode: t
  global-font-lock-mode: t
  font-lock-mode: t
  blink-cursor-mode: t
  auto-composition-mode: t
  auto-encryption-mode: t
  auto-compression-mode: t
  line-number-mode: t
  transient-mark-mode: t

Load-path shadows:
None found.

Features:
(shadow sort mail-extr emacsbug message rmc puny dired dired-loaddefs
rfc822 mml easymenu mml-sec epa derived epg epg-config gnus-util rmail
rmail-loaddefs auth-source cl-seq eieio eieio-core cl-macs
eieio-loaddefs password-cache json map text-property-search time-date
subr-x seq byte-opt gv bytecomp byte-compile cconv mm-decode mm-bodies
mm-encode mail-parse rfc2231 mailabbrev gmm-utils mailheader cl-loaddefs
cl-lib sendmail rfc2047 rfc2045 ietf-drums mm-util mail-prsvr mail-utils
iso-transl tooltip eldoc electric uniquify ediff-hook vc-hooks
lisp-float-type mwheel term/x-win x-win term/common-win x-dnd tool-bar
dnd fontset image regexp-opt fringe tabulated-list replace newcomment
text-mode elisp-mode lisp-mode prog-mode register page tab-bar menu-bar
rfn-eshadow isearch timer select scroll-bar mouse jit-lock font-lock
syntax facemenu font-core term/tty-colors frame minibuffer cl-generic
cham georgian utf-8-lang misc-lang vietnamese tibetan thai tai-viet lao
korean japanese eucjp-ms cp51932 hebrew greek romanian slovak czech
european ethiopic indian cyrillic chinese composite charscript charprop
case-table epa-hook jka-cmpr-hook help simple abbrev obarray
cl-preloaded nadvice button loaddefs faces cus-face macroexp files
window text-properties overlay sha1 md5 base64 format env code-pages
mule custom widget hashtable-print-readable backquote threads dbusbind
inotify dynamic-setting system-font-setting font-render-setting cairo
x-toolkit x multi-tty make-network-process emacs)

Memory information:
((conses 16 52136 5739)
 (symbols 48 6583 1)
 (strings 32 19227 1884)
 (string-bytes 1 625859)
 (vectors 16 12486)
 (vector-slots 8 169553 9399)
 (floats 8 23 41)
 (intervals 56 211 0)
 (buffers 984 10))