all messages for Emacs-related lists mirrored at yhetil.org
 help / color / mirror / code / Atom feed
From: Matthew White <mehw.is.me@inventati.org>
To: Karl Fogel <kfogel@red-bean.com>, emacs-devel@gnu.org
Subject: Re: Add new functions to mark/unmark/delete all bookmarks
Date: Sat, 25 Jul 2020 12:46:18 +0200	[thread overview]
Message-ID: <20200725124618.49a073b1@pineapple> (raw)
In-Reply-To: <87pn8ku3y9.fsf@red-bean.com>


[-- Attachment #1.1: Type: text/plain, Size: 5595 bytes --]

> Hi, Matthew.  Thanks so much for this.  I have reviewed the patch
> against 'master' (since it applies cleanly there, whereas it does not
> quite apply cleanly to 'emacs-27').  It is very good; I have just
> some minor comments below.

Hi Karl. I'm flattered, thanks!

I appreciate so much that you took the time to review my patch, and
I eagerly followed your suggestions!

You will find attached both a patch that applies to master, and one
that applies to emacs-27... They are the same in term of changes.

I'm still waiting a reply to the copyright assignment... But in the
meantime we can freely discuss about the patch...

> 1) It helps to include a summary line at the top of your commit
> message, followed by a blank line.  The summary line should be
> limited to 50 characters or fewer, if possible.  (This is all
> documented in the "Commit messages" section in the 'CONTRIBUTE' file
> in the top level of the Emacs source tree, by the way).  In your
> case, the Subject header of your email almost does the job -- we can
> shorten it a bit to fit within 50 characters:
> 
>   "Add ability to mark/unmark/delete all bookmarks"

Thanks for pointing that out. Now that I read the 'CONTRIBUTE' file,
I have some questions (by the way, I needed a remainder to read...):

1) I did commit a message with a summary line, as you described...

   [ Actually my line was the subject of the thread, yours (above)
     is better, hence I switched to your idea, thanks again ;) ]

   And indeed I use `git format-patch -1` as suggested in 'CONTRIBUTE'
   to produce a patch, but the command "strips" the summary line from
   the commit message and uses it as 'Subject:' in the patch, where it
   is prefixed with '[PATCH] ' if the option -k isn't also given...

   Once the patch is applied via `git am`, the 'Subject:' is put back
   in the commit message as summary, without the '[PATCH] ' prefix.

   So, I wonder if you used `git am` to apply the patch...? Could you
   tip me off, please?

2) With which quotes should I surround the variable/function names in
   the commit message? `' or '' or ‘’? I could not figure that out...

> 2) In the commit message, you don't need to repeat the filename ("*
> lisp/bookmark.el") each time.  You can just write it once, and then
> underneath it show everything affected in that file.  Taking the
> first three entries in your original log message as an example:
> 
>    * lisp/bookmark.el (bookmark-delete-all): New function to delete
> all bookmarks.
>    * lisp/bookmark.el (bookmark-bmenu-mark-all): New function to mark
> all bookmarks in the bookmark list buffer.
>    * lisp/bookmark.el (bookmark-bmenu-unmark-all): New function to
> unmark all bookmarks in the bookmark list buffer.
> 
> ...they would instead be written like this:
> 
>    * lisp/bookmark.el (bookmark-delete-all): New function to delete
> all bookmarks.
>      (bookmark-bmenu-mark-all): New function to mark all bookmarks in
> the bookmark list buffer. 
>      (bookmark-bmenu-unmark-all): New function to unmark all
> bookmarks in the bookmark list buffer.
> 
> Quick digression:
> 
> You can use 'git log' to see this style used in past Emacs commits.

[x] Don't repeat the file name each time in the commit message.

    ...And yes, I did take a look at the `git log`...

> Better to call ARG something like NO-CONFIRM, so its name reflects
> its purpose.
>
> And a suggestion: have the prompt say "Permanently delete all
> bookmarks? " instead.

In `bookmark-delete-all':

[x] Chage ARG to NO-CONFIRM.
[x] Prompt "Permanently delete all bookmarks? ".

> Because of subtle assumptions associated with English word order, the
> current prompt ("Delete all bookmarks permanently? ") implies that
> the alternative might be to delete the bookmarks non-permanently.

That's a very smart thought... I'm truly impressed!

> Regarding the "FIXME" comment: that assumption is embedded throughout
> the bookmark-bmenu-* code, so I think there's no need for the "FIXME"
> (and indeed the comment may cause confusion, since its presence calls
> the assumption into question).
> 
> It might be good to make a followup change in which we document the
> last-line-is-empty assumption, but that's a separate change of course.

[x] Remove the "FIXME" code comments.

> >+       ;; FIXME: This assumes that the last line is empty.
> >+       (while (not (eobp))

> These tests are a huge contribution -- thanks for taking the time to
> write them.

Following your guidelines is very instructing, Karl!

I also:

[x] Change `nameN' and `fileN' into `name-N' and `file-N' (i.e. name0
    -> name-0) in all new functions and in the new test-list.bmk file.
[x] Indent new functions to split long lines into shorter ones.
[x] bookmark-tests.el (bookmark-test-bmenu-any-marks-list): New function
    to test `bookmark-bmenu-any-marks' with a list of bookmarks.

> This patch looks good to me.  I've tested it myself and it works as
> advertised, and the new tests pass 'make check'.
> 
> If you have time to revise the patch slightly as per above (if you
> agree with the comments, of course -- otherwise, let's discuss here)
> that would be great.  I see that Stefan has already sent you the
> assignment paperwork.
> 
> Best regards,
> -Karl

Again, thank you very much Karl for all your effort reviewing my patch,
and especially for your brilliant insights!

I hope to get back soon with good news about the copyright assignment.

Catch you later!

[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #1.2: master-Add-ability-to-mark-unmark-delete-all-bookmarks.patch --]
[-- Type: text/x-patch, Size: 24349 bytes --]

From 26b3590648720466a0a558e7c85da4eb7126827e Mon Sep 17 00:00:00 2001
From: Matthew White <mehw.is.me@inventati.org>
Date: Thu, 23 Jul 2020 21:14:32 +0000
Subject: [PATCH] Add ability to mark/unmark/delete all bookmarks

* lisp/bookmark.el (bookmark-delete-all): New function to delete all
  bookmarks.
  (bookmark-bmenu-mark-all): New function to mark all bookmarks in the
  bookmark list buffer.
  (bookmark-bmenu-unmark-all): New function to unmark all bookmarks in
  the bookmark list buffer.
  (bookmark-bmenu-delete-all): New function to mark for deletion all
  bookmarks in the bookmark list buffer.
  (bookmark-map): Map "D" to `bookmark-delete-all'.
  (bookmark-bmenu-mode-map): New mappping for "M" to
  `bookmark-bmenu-mark-all'.
  (bookmark-bmenu-mode-map): New mappping for "U" to
  `bookmark-bmenu-unmark-all'.
  (bookmark-bmenu-mode-map): New mappping for "D" to
  `bookmark-bmenu-delete-all'.
  (bookmark-bmenu-mark-all): New bookmark menu to
  `bookmark-delete-all'.
  (easy-menu-define): New bookmark menu to `bookmark-bmenu-mark-all'.
  (easy-menu-define): New bookmark menu to
  `bookmark-bmenu-unmark-all'.
  (easy-menu-define): New bookmark menu to
  `bookmark-bmenu-delete-all'.
  (bookmark-bmenu-select): Update docstring to include a reference to
  `bookmark-bmenu-mark-all'.
  (bookmark-bmenu-mode): Update docstring. Add/Update description:
  `bookmark-bmenu-mark-all', `bookmark-bmenu-delete-all',
  `bookmark-bmenu-execute-deletions', and `bookmark-bmenu-unmark-all'.
* test/lisp/bookmark-resources/test-list.bmk: New bookmark file to
  test a list of bookmarks.
* test/lisp/bookmark-tests.el (bookmark-tests-bookmark-file-list): New
  reference to the bookmark file used for testing a list of bookmarks.
  (bookmark-tests-bookmark-list-0, bookmark-tests-bookmark-list-1,
  bookmark-tests-bookmark-list-2): New cached values for testing a
  list of bookmark.
  (bookmark-tests-cache-timestamp-list): New variable to set
  `bookmark-bookmarks-timestamp'.
  (with-bookmark-test-list): New macro environment to test a list of
  bookmarks.
  (with-bookmark-test-file-list): New macro environment to test a list
  of bookmarks with example.txt.
  (with-bookmark-bmenu-test-list): New macro environment to test
  functions about a list of bookmarks from `bookmark-bmenu-list'.
  (bookmark-tests-all-names-list, bookmark-tests-get-bookmark-list,
  bookmark-tests-get-bookmark-record-list): New functions to test the
  records of the list of bookmarks.
  (bookmark-tests-make-record-list): New function to test the creation
  of a record from example.txt with a list of bookmarks loaded.
  (bookmark-tests-delete-all): New function to test
  `bookmark-delete-all'.
  (bookmark-test-bmenu-any-marks-list): New function to test
  `bookmark-bmenu-any-marks' with a list of bookmarks.
  (bookmark-test-bmenu-mark-all): New function to test
  `bookmark-bmenu-mark-all'.
  (bookmark-test-bmenu-unmark-all): New function to test
  `bookmark-bmenu-unmark-all'.
  (bookmark-test-bmenu-delete-all): New function to test
  `bookmark-bmenu-delete-all'.
---
 lisp/bookmark.el                           |  77 +++++++-
 test/lisp/bookmark-resources/test-list.bmk |  20 ++
 test/lisp/bookmark-tests.el                | 215 +++++++++++++++++++++
 3 files changed, 310 insertions(+), 2 deletions(-)
 create mode 100644 test/lisp/bookmark-resources/test-list.bmk

diff --git a/lisp/bookmark.el b/lisp/bookmark.el
index de7d60f97e..870c41d5a8 100644
--- a/lisp/bookmark.el
+++ b/lisp/bookmark.el
@@ -200,6 +200,7 @@ A non-nil value may result in truncated bookmark names."
     (define-key map "f" 'bookmark-insert-location) ;"f"ind
     (define-key map "r" 'bookmark-rename)
     (define-key map "d" 'bookmark-delete)
+    (define-key map "D" 'bookmark-delete-all)
     (define-key map "l" 'bookmark-load)
     (define-key map "w" 'bookmark-write)
     (define-key map "s" 'bookmark-save)
@@ -1374,6 +1375,22 @@ probably because we were called from there."
     (bookmark-save)))
 
 
+;;;###autoload
+(defun bookmark-delete-all (&optional no-confirm)
+  "Permanently delete all bookmarks.
+Doesn't ask for confirmation if NO-CONFIRM is non-nil."
+  (interactive "P")
+  (when (or no-confirm
+            (yes-or-no-p "Permanently delete all bookmarks? "))
+    (bookmark-maybe-load-default-file)
+    (setq bookmark-alist-modification-count
+          (+ bookmark-alist-modification-count (length bookmark-alist)))
+    (setq bookmark-alist nil)
+    (bookmark-bmenu-surreptitiously-rebuild-list)
+    (when (bookmark-time-to-save-p)
+      (bookmark-save))))
+
+
 (defun bookmark-time-to-save-p (&optional final-time)
   "Return t if it is time to save bookmarks to disk, nil otherwise.
 Optional argument FINAL-TIME means this is being called when Emacs
@@ -1600,12 +1617,15 @@ unique numeric suffixes \"<2>\", \"<3>\", etc."
     (define-key map "\C-d" 'bookmark-bmenu-delete-backwards)
     (define-key map "x" 'bookmark-bmenu-execute-deletions)
     (define-key map "d" 'bookmark-bmenu-delete)
+    (define-key map "D" 'bookmark-bmenu-delete-all)
     (define-key map " " 'next-line)
     (define-key map "n" 'next-line)
     (define-key map "p" 'previous-line)
     (define-key map "\177" 'bookmark-bmenu-backup-unmark)
     (define-key map "u" 'bookmark-bmenu-unmark)
+    (define-key map "U" 'bookmark-bmenu-unmark-all)
     (define-key map "m" 'bookmark-bmenu-mark)
+    (define-key map "M" 'bookmark-bmenu-mark-all)
     (define-key map "l" 'bookmark-bmenu-load)
     (define-key map "r" 'bookmark-bmenu-rename)
     (define-key map "R" 'bookmark-bmenu-relocate)
@@ -1627,8 +1647,10 @@ unique numeric suffixes \"<2>\", \"<3>\", etc."
     ["Select Marked Bookmarks" bookmark-bmenu-select t]
     "---"
     ["Mark Bookmark" bookmark-bmenu-mark t]
+    ["Mark all Bookmarks" bookmark-bmenu-mark-all t]
     ["Unmark Bookmark" bookmark-bmenu-unmark  t]
     ["Unmark Backwards" bookmark-bmenu-backup-unmark  t]
+    ["Unmark all Bookmarks" bookmark-bmenu-unmark-all  t]
     ["Toggle Display of Filenames" bookmark-bmenu-toggle-filenames  t]
     ["Display Location of Bookmark" bookmark-bmenu-locate  t]
     "---"
@@ -1636,6 +1658,7 @@ unique numeric suffixes \"<2>\", \"<3>\", etc."
      ["Rename Bookmark" bookmark-bmenu-rename  t]
      ["Relocate Bookmark's File" bookmark-bmenu-relocate  t]
      ["Mark Bookmark for Deletion" bookmark-bmenu-delete  t]
+     ["Mark all Bookmarks for Deletion" bookmark-bmenu-delete-all  t]
      ["Delete Marked Bookmarks" bookmark-bmenu-execute-deletions  t])
     ("Annotations"
      ["Show Annotation for Current Bookmark" bookmark-bmenu-show-annotation  t]
@@ -1748,6 +1771,7 @@ Letters do not insert themselves; instead, they are commands.
 Bookmark names preceded by a \"*\" have annotations.
 \\<bookmark-bmenu-mode-map>
 \\[bookmark-bmenu-mark] -- mark bookmark to be displayed.
+\\[bookmark-bmenu-mark-all] -- mark all listed bookmarks to be displayed.
 \\[bookmark-bmenu-select] -- select bookmark of line point is on.
   Also show bookmarks marked using m in other windows.
 \\[bookmark-bmenu-toggle-filenames] -- toggle displaying of filenames (they may obscure long bookmark names).
@@ -1764,13 +1788,15 @@ Bookmark names preceded by a \"*\" have annotations.
 \\[bookmark-bmenu-relocate] -- relocate this bookmark's file (prompts for new file).
 \\[bookmark-bmenu-delete] -- mark this bookmark to be deleted, and move down.
 \\[bookmark-bmenu-delete-backwards] -- mark this bookmark to be deleted, and move up.
-\\[bookmark-bmenu-execute-deletions] -- delete bookmarks marked with `\\[bookmark-bmenu-delete]'.
+\\[bookmark-bmenu-delete-all] -- mark all listed bookmarks as to be deleted.
+\\[bookmark-bmenu-execute-deletions] -- delete bookmarks marked with `\\[bookmark-bmenu-delete]' or `\\[bookmark-bmenu-delete-all]'.
 \\[bookmark-bmenu-save] -- save the current bookmark list in the default file.
   With a prefix arg, prompts for a file to save in.
 \\[bookmark-bmenu-load] -- load in a file of bookmarks (prompts for file.)
 \\[bookmark-bmenu-unmark] -- remove all kinds of marks from current line.
   With prefix argument, also move up one line.
 \\[bookmark-bmenu-backup-unmark] -- back up a line and remove marks.
+\\[bookmark-bmenu-unmark-all] -- remove all kinds of marks from all listed bookmarks.
 \\[bookmark-bmenu-show-annotation] -- show the annotation, if it exists, for the current bookmark
   in another buffer.
 \\[bookmark-bmenu-show-all-annotations] -- show the annotations of all bookmarks in another buffer.
@@ -1937,9 +1963,23 @@ If the annotation does not exist, do nothing."
      (bookmark-bmenu-ensure-position))))
 
 
+(defun bookmark-bmenu-mark-all ()
+  "Mark all listed bookmarks to be displayed by \\<bookmark-bmenu-mode-map>\\[bookmark-bmenu-select]."
+  (interactive)
+  (save-excursion
+    (goto-char (point-min))
+    (bookmark-bmenu-ensure-position)
+    (with-buffer-modified-unmodified
+     (let ((inhibit-read-only t))
+       (while (not (eobp))
+         (delete-char 1)
+         (insert ?>)
+         (forward-line 1))))))
+
+
 (defun bookmark-bmenu-select ()
   "Select this line's bookmark; also display bookmarks marked with `>'.
-You can mark bookmarks with the \\<bookmark-bmenu-mode-map>\\[bookmark-bmenu-mark] command."
+You can mark bookmarks with the \\<bookmark-bmenu-mode-map>\\[bookmark-bmenu-mark] or \\<bookmark-bmenu-mode-map>\\[bookmark-bmenu-mark-all] commands."
   (interactive)
   (let ((bmrk (bookmark-bmenu-bookmark))
         (menu (current-buffer))
@@ -2108,6 +2148,20 @@ Optional BACKUP means move up."
   (bookmark-bmenu-ensure-position))
 
 
+(defun bookmark-bmenu-unmark-all ()
+  "Cancel all requested operations on all listed bookmarks."
+  (interactive)
+  (save-excursion
+    (goto-char (point-min))
+    (bookmark-bmenu-ensure-position)
+    (with-buffer-modified-unmodified
+     (let ((inhibit-read-only t))
+       (while (not (eobp))
+         (delete-char 1)
+         (insert " ")
+         (forward-line 1))))))
+
+
 (defun bookmark-bmenu-delete ()
   "Mark bookmark on this line to be deleted.
 To carry out the deletions that you've marked, use \\<bookmark-bmenu-mode-map>\\[bookmark-bmenu-execute-deletions]."
@@ -2133,6 +2187,22 @@ To carry out the deletions that you've marked, use \\<bookmark-bmenu-mode-map>\\
   (bookmark-bmenu-ensure-position))
 
 
+(defun bookmark-bmenu-delete-all ()
+  "Mark all listed bookmarks as to be deleted.
+To remove all deletion marks, use \\<bookmark-bmenu-mode-map>\\[bookmark-bmenu-unmark-all].
+To carry out the deletions that you've marked, use \\<bookmark-bmenu-mode-map>\\[bookmark-bmenu-execute-deletions]."
+  (interactive)
+  (save-excursion
+    (goto-char (point-min))
+    (bookmark-bmenu-ensure-position)
+    (with-buffer-modified-unmodified
+     (let ((inhibit-read-only t))
+       (while (not (eobp))
+         (delete-char 1)
+         (insert ?D)
+         (forward-line 1))))))
+
+
 (defun bookmark-bmenu-execute-deletions ()
   "Delete bookmarks flagged `D'."
   (interactive)
@@ -2292,6 +2362,9 @@ strings returned are not."
     (bindings--define-key map [delete]
       '(menu-item "Delete Bookmark..." bookmark-delete
 		  :help "Delete a bookmark from the bookmark list"))
+    (bindings--define-key map [delete-all]
+      '(menu-item "Delete all Bookmarks..." bookmark-delete-all
+		  :help "Delete all bookmarks from the bookmark list"))
     (bindings--define-key map [rename]
       '(menu-item "Rename Bookmark..." bookmark-rename
 		  :help "Change the name of a bookmark"))
diff --git a/test/lisp/bookmark-resources/test-list.bmk b/test/lisp/bookmark-resources/test-list.bmk
new file mode 100644
index 0000000000..696d64979b
--- /dev/null
+++ b/test/lisp/bookmark-resources/test-list.bmk
@@ -0,0 +1,20 @@
+;;;; Emacs Bookmark Format Version 1 ;;;; -*- coding: utf-8-emacs -*-
+;;; This format is meant to be slightly human-readable;
+;;; nevertheless, you probably don't want to edit it.
+;;; -*- End Of Bookmark File Format Version Stamp -*-
+(("name-0"
+ (filename . "/some/file-0")
+ (front-context-string . "abc")
+ (rear-context-string . "def")
+ (position . 3))
+("name-1"
+ (filename . "/some/file-1")
+ (front-context-string . "abc")
+ (rear-context-string . "def")
+ (position . 3))
+("name-2"
+ (filename . "/some/file-2")
+ (front-context-string . "abc")
+ (rear-context-string . "def")
+ (position . 3))
+)
diff --git a/test/lisp/bookmark-tests.el b/test/lisp/bookmark-tests.el
index b9c6ff9c54..d5f7f24317 100644
--- a/test/lisp/bookmark-tests.el
+++ b/test/lisp/bookmark-tests.el
@@ -83,6 +83,70 @@ the lexically-bound variable `buffer'."
           ,@body)
        (kill-buffer buffer))))
 
+(defvar bookmark-tests-bookmark-file-list
+  (expand-file-name "test-list.bmk" bookmark-tests-data-dir)
+  "Bookmark file used for testing a list of bookmarks.")
+
+;; The values below should match `bookmark-tests-bookmark-file-list'
+;; content.  We cache these values to speed up tests.
+(eval-and-compile  ; needed by `with-bookmark-test-list' macro
+  (defvar bookmark-tests-bookmark-list-0 '("name-0"
+                            (filename . "/some/file-0")
+                            (front-context-string . "ghi")
+                            (rear-context-string . "jkl")
+                            (position . 4))
+    "Cached value used in bookmark-tests.el."))
+
+;; The values below should match `bookmark-tests-bookmark-file-list'
+;; content.  We cache these values to speed up tests.
+(eval-and-compile  ; needed by `with-bookmark-test-list' macro
+  (defvar bookmark-tests-bookmark-list-1 '("name-1"
+                            (filename . "/some/file-1")
+                            (front-context-string . "mno")
+                            (rear-context-string . "pqr")
+                            (position . 5))
+    "Cached value used in bookmark-tests.el."))
+
+;; The values below should match `bookmark-tests-bookmark-file-list'
+;; content.  We cache these values to speed up tests.
+(eval-and-compile  ; needed by `with-bookmark-test-list' macro
+  (defvar bookmark-tests-bookmark-list-2 '("name-2"
+                            (filename . "/some/file-2")
+                            (front-context-string . "stu")
+                            (rear-context-string . "vwx")
+                            (position . 6))
+    "Cached value used in bookmark-tests.el."))
+
+(defvar bookmark-tests-cache-timestamp-list
+  (cons bookmark-tests-bookmark-file-list
+        (nth 5 (file-attributes
+                bookmark-tests-bookmark-file-list)))
+  "Cached value used in bookmark-tests.el.")
+
+(defmacro with-bookmark-test-list (&rest body)
+  "Create environment for testing bookmark.el and evaluate BODY.
+Ensure a clean environment for testing, and do not change user
+data when running tests interactively."
+  `(with-temp-buffer
+     (let ((bookmark-alist (quote (,(copy-sequence bookmark-tests-bookmark-list-0)
+                                   ,(copy-sequence bookmark-tests-bookmark-list-1)
+                                   ,(copy-sequence bookmark-tests-bookmark-list-2))))
+           (bookmark-default-file bookmark-tests-bookmark-file-list)
+           (bookmark-bookmarks-timestamp bookmark-tests-cache-timestamp-list)
+           bookmark-save-flag)
+       ,@body)))
+
+(defmacro with-bookmark-test-file-list (&rest body)
+  "Create environment for testing bookmark.el and evaluate BODY.
+Same as `with-bookmark-test-list' but also opens the resource file
+example.txt in a buffer, which can be accessed by callers through
+the lexically-bound variable `buffer'."
+  `(let ((buffer (find-file-noselect bookmark-tests-example-file)))
+     (unwind-protect
+         (with-bookmark-test-list
+          ,@body)
+       (kill-buffer buffer))))
+
 (ert-deftest bookmark-tests-all-names ()
   (with-bookmark-test
    (should (equal (bookmark-all-names) '("name")))))
@@ -95,6 +159,30 @@ the lexically-bound variable `buffer'."
   (with-bookmark-test
    (should (equal (bookmark-get-bookmark-record "name") (cdr bookmark-tests-bookmark)))))
 
+(ert-deftest bookmark-tests-all-names-list ()
+  (with-bookmark-test-list
+   (should (equal (bookmark-all-names) '("name-0"
+                                         "name-1"
+                                         "name-2")))))
+
+(ert-deftest bookmark-tests-get-bookmark-list ()
+  (with-bookmark-test-list
+   (should (equal (bookmark-get-bookmark "name-0")
+                  bookmark-tests-bookmark-list-0))
+   (should (equal (bookmark-get-bookmark "name-1")
+                  bookmark-tests-bookmark-list-1))
+   (should (equal (bookmark-get-bookmark "name-2")
+                  bookmark-tests-bookmark-list-2))))
+
+(ert-deftest bookmark-tests-get-bookmark-record-list ()
+  (with-bookmark-test-list
+   (should (equal (bookmark-get-bookmark-record "name-0")
+                  (cdr bookmark-tests-bookmark-list-0)))
+   (should (equal (bookmark-get-bookmark-record "name-1")
+                  (cdr bookmark-tests-bookmark-list-1)))
+   (should (equal (bookmark-get-bookmark-record "name-2")
+                  (cdr bookmark-tests-bookmark-list-2)))))
+
 (ert-deftest bookmark-tests-record-getters-and-setters-new ()
   (with-temp-buffer
     (let* ((buffer-file-name "test")
@@ -130,6 +218,19 @@ the lexically-bound variable `buffer'."
        ;; calling twice gives same record
        (should (equal (bookmark-make-record) record))))))
 
+(ert-deftest bookmark-tests-make-record-list ()
+  (with-bookmark-test-file-list
+   (let* ((record `("example.txt" (filename . ,bookmark-tests-example-file)
+                    (front-context-string . "is text file is ")
+                    (rear-context-string)
+                    (position . 3)
+                    (defaults "example.txt"))))
+     (with-current-buffer buffer
+       (goto-char 3)
+       (should (equal (bookmark-make-record) record))
+       ;; calling twice gives same record
+       (should (equal (bookmark-make-record) record))))))
+
 (ert-deftest bookmark-tests-make-record-function ()
   (with-bookmark-test
    (let ((buffer-file-name "test"))
@@ -267,6 +368,11 @@ the lexically-bound variable `buffer'."
    (bookmark-delete "name")
    (should (equal bookmark-alist nil))))
 
+(ert-deftest bookmark-tests-delete-all ()
+  (with-bookmark-test-list
+   (bookmark-delete-all t)
+   (should (equal bookmark-alist nil))))
+
 (defmacro with-bookmark-test-save-load (&rest body)
   "Create environment for testing bookmark.el and evaluate BODY.
 Same as `with-bookmark-test' but also sets a temporary
@@ -340,6 +446,18 @@ testing `bookmark-bmenu-list'."
             ,@body)
         (kill-buffer bookmark-bmenu-buffer)))))
 
+(defmacro with-bookmark-bmenu-test-list (&rest body)
+  "Create environment for testing `bookmark-bmenu-list' and evaluate BODY.
+Same as `with-bookmark-test-list' but with additions suitable for
+testing `bookmark-bmenu-list'."
+  `(with-bookmark-test-list
+    (let ((bookmark-bmenu-buffer "*Bookmark List - Testing*"))
+      (unwind-protect
+          (save-window-excursion
+            (bookmark-bmenu-list)
+            ,@body)
+        (kill-buffer bookmark-bmenu-buffer)))))
+
 (ert-deftest bookmark-test-bmenu-edit-annotation/show-annotation ()
   (with-bookmark-bmenu-test
    (bookmark-set-annotation "name" "foo")
@@ -402,6 +520,52 @@ testing `bookmark-bmenu-list'."
    (beginning-of-line)
    (should (bookmark-bmenu-any-marks))))
 
+(ert-deftest bookmark-test-bmenu-mark-all ()
+  (with-bookmark-bmenu-test-list
+   (let ((here (point-max)))
+     ;; Expect to not move the point
+     (goto-char here)
+     (bookmark-bmenu-mark-all)
+     (should (equal here (point)))
+     ;; Verify that all bookmarks are marked
+     (goto-char (point-min))
+     (bookmark-bmenu-ensure-position)
+     (should (looking-at "^> "))
+     (should (equal bookmark-tests-bookmark-list-0
+                    (bookmark-get-bookmark (bookmark-bmenu-bookmark))))
+     (forward-line 1)
+     (should (looking-at "^> "))
+     (should (equal bookmark-tests-bookmark-list-1
+                    (bookmark-get-bookmark (bookmark-bmenu-bookmark))))
+     (forward-line 1)
+     (should (looking-at "^> "))
+     (should (equal bookmark-tests-bookmark-list-2
+                    (bookmark-get-bookmark (bookmark-bmenu-bookmark)))))))
+
+(ert-deftest bookmark-test-bmenu-any-marks-list ()
+  (with-bookmark-bmenu-test-list
+   ;; Mark just the second item
+   (goto-char (point-min))
+   (bookmark-bmenu-ensure-position)
+   (forward-line 1)
+   (bookmark-bmenu-mark)
+   ;; Verify that only the second item is marked
+   (goto-char (point-min))
+   (bookmark-bmenu-ensure-position)
+   (should (looking-at "^  "))
+   (should (equal bookmark-tests-bookmark-list-0
+                  (bookmark-get-bookmark (bookmark-bmenu-bookmark))))
+   (forward-line 1)
+   (should (looking-at "^> "))
+   (should (equal bookmark-tests-bookmark-list-1
+                  (bookmark-get-bookmark (bookmark-bmenu-bookmark))))
+   (forward-line 1)
+   (should (looking-at "^> "))
+   (should (equal bookmark-tests-bookmark-list-2
+                  (bookmark-get-bookmark (bookmark-bmenu-bookmark))))
+   ;; There should be at least one mark
+   (should (bookmark-bmenu-any-marks))))
+
 (ert-deftest bookmark-test-bmenu-unmark ()
   (with-bookmark-bmenu-test
    (bookmark-bmenu-mark)
@@ -410,12 +574,63 @@ testing `bookmark-bmenu-list'."
    (beginning-of-line)
    (should (looking-at "^  "))))
 
+(ert-deftest bookmark-test-bmenu-unmark-all ()
+  (with-bookmark-bmenu-test-list
+   (bookmark-bmenu-mark-all)
+   (let ((here (point-max)))
+     ;; Expect to not move the point
+     (goto-char here)
+     (bookmark-bmenu-unmark-all)
+     (should (equal here (point)))
+     ;; Verify that all bookmarks are unmarked
+     (goto-char (point-min))
+     (bookmark-bmenu-ensure-position)
+     (should (looking-at "^  "))
+     (should (equal bookmark-tests-bookmark-list-0
+                    (bookmark-get-bookmark (bookmark-bmenu-bookmark))))
+     (forward-line 1)
+     (should (looking-at "^  "))
+     (should (equal bookmark-tests-bookmark-list-1
+                    (bookmark-get-bookmark (bookmark-bmenu-bookmark))))
+     (forward-line 1)
+     (should (looking-at "^  "))
+     (should (equal bookmark-tests-bookmark-list-2
+                    (bookmark-get-bookmark (bookmark-bmenu-bookmark)))))))
+
 (ert-deftest bookmark-test-bmenu-delete ()
   (with-bookmark-bmenu-test
    (bookmark-bmenu-delete)
    (bookmark-bmenu-execute-deletions)
    (should (equal (length bookmark-alist) 0))))
 
+(ert-deftest bookmark-test-bmenu-delete-all ()
+  (with-bookmark-bmenu-test-list
+   ;; Verify that unmarked bookmarks aren't deleted
+   (bookmark-bmenu-execute-deletions)
+   (should-not (eq bookmark-alist nil))
+   (let ((here (point-max)))
+     ;; Expect to not move the point
+     (goto-char here)
+     (bookmark-bmenu-delete-all)
+     (should (equal here (point)))
+     ;; Verify that all bookmarks are marked for deletion
+     (goto-char (point-min))
+     (bookmark-bmenu-ensure-position)
+     (should (looking-at "^D "))
+     (should (equal bookmark-tests-bookmark-list-0
+                    (bookmark-get-bookmark (bookmark-bmenu-bookmark))))
+     (forward-line 1)
+     (should (looking-at "^D "))
+     (should (equal bookmark-tests-bookmark-list-1
+                    (bookmark-get-bookmark (bookmark-bmenu-bookmark))))
+     (forward-line 1)
+     (should (looking-at "^D "))
+     (should (equal bookmark-tests-bookmark-list-2
+                    (bookmark-get-bookmark (bookmark-bmenu-bookmark))))
+     ;; Verify that all bookmarks are deleted
+     (bookmark-bmenu-execute-deletions)
+     (should (eq bookmark-alist nil)))))
+
 (ert-deftest bookmark-test-bmenu-locate ()
   (let (msg)
     (cl-letf (((symbol-function 'message)
-- 
2.26.2


[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #1.3: emacs-27-Add-ability-to-mark-unmark-delete-all-bookmarks.patch --]
[-- Type: text/x-patch, Size: 23933 bytes --]

From 3e7dd3655b3e28e47be88f60f3bb5f03f251e9ee Mon Sep 17 00:00:00 2001
From: Matthew White <mehw.is.me@inventati.org>
Date: Thu, 23 Jul 2020 21:14:32 +0000
Subject: [PATCH] Add ability to mark/unmark/delete all bookmarks

* lisp/bookmark.el (bookmark-delete-all): New function to delete all
  bookmarks.
  (bookmark-bmenu-mark-all): New function to mark all bookmarks in the
  bookmark list buffer.
  (bookmark-bmenu-unmark-all): New function to unmark all bookmarks in
  the bookmark list buffer.
  (bookmark-bmenu-delete-all): New function to mark for deletion all
  bookmarks in the bookmark list buffer.
  (bookmark-map): Map "D" to `bookmark-delete-all'.
  (bookmark-bmenu-mode-map): New mappping for "M" to
  `bookmark-bmenu-mark-all'.
  (bookmark-bmenu-mode-map): New mappping for "U" to
  `bookmark-bmenu-unmark-all'.
  (bookmark-bmenu-mode-map): New mappping for "D" to
  `bookmark-bmenu-delete-all'.
  (bookmark-bmenu-mark-all): New bookmark menu to
  `bookmark-delete-all'.
  (easy-menu-define): New bookmark menu to `bookmark-bmenu-mark-all'.
  (easy-menu-define): New bookmark menu to
  `bookmark-bmenu-unmark-all'.
  (easy-menu-define): New bookmark menu to
  `bookmark-bmenu-delete-all'.
  (bookmark-bmenu-select): Update docstring to include a reference to
  `bookmark-bmenu-mark-all'.
  (bookmark-bmenu-mode): Update docstring. Add/Update description:
  `bookmark-bmenu-mark-all', `bookmark-bmenu-delete-all',
  `bookmark-bmenu-execute-deletions', and `bookmark-bmenu-unmark-all'.
* test/lisp/bookmark-resources/test-list.bmk: New bookmark file to
  test a list of bookmarks.
* test/lisp/bookmark-tests.el (bookmark-tests-bookmark-file-list): New
  reference to the bookmark file used for testing a list of bookmarks.
  (bookmark-tests-bookmark-list-0, bookmark-tests-bookmark-list-1,
  bookmark-tests-bookmark-list-2): New cached values for testing a
  list of bookmark.
  (bookmark-tests-cache-timestamp-list): New variable to set
  `bookmark-bookmarks-timestamp'.
  (with-bookmark-test-list): New macro environment to test a list of
  bookmarks.
  (with-bookmark-test-file-list): New macro environment to test a list
  of bookmarks with example.txt.
  (with-bookmark-bmenu-test-list): New macro environment to test
  functions about a list of bookmarks from `bookmark-bmenu-list'.
  (bookmark-tests-all-names-list, bookmark-tests-get-bookmark-list,
  bookmark-tests-get-bookmark-record-list): New functions to test the
  records of the list of bookmarks.
  (bookmark-tests-make-record-list): New function to test the creation
  of a record from example.txt with a list of bookmarks loaded.
  (bookmark-tests-delete-all): New function to test
  `bookmark-delete-all'.
  (bookmark-test-bmenu-any-marks-list): New function to test
  `bookmark-bmenu-any-marks' with a list of bookmarks.
  (bookmark-test-bmenu-mark-all): New function to test
  `bookmark-bmenu-mark-all'.
  (bookmark-test-bmenu-unmark-all): New function to test
  `bookmark-bmenu-unmark-all'.
  (bookmark-test-bmenu-delete-all): New function to test
  `bookmark-bmenu-delete-all'.
---
 lisp/bookmark.el                           |  77 +++++++-
 test/lisp/bookmark-resources/test-list.bmk |  20 ++
 test/lisp/bookmark-tests.el                | 215 +++++++++++++++++++++
 3 files changed, 310 insertions(+), 2 deletions(-)
 create mode 100644 test/lisp/bookmark-resources/test-list.bmk

diff --git a/lisp/bookmark.el b/lisp/bookmark.el
index e69d9f529c..c45a5103fe 100644
--- a/lisp/bookmark.el
+++ b/lisp/bookmark.el
@@ -200,6 +200,7 @@ A non-nil value may result in truncated bookmark names."
     (define-key map "f" 'bookmark-insert-location) ;"f"ind
     (define-key map "r" 'bookmark-rename)
     (define-key map "d" 'bookmark-delete)
+    (define-key map "D" 'bookmark-delete-all)
     (define-key map "l" 'bookmark-load)
     (define-key map "w" 'bookmark-write)
     (define-key map "s" 'bookmark-save)
@@ -1372,6 +1373,22 @@ probably because we were called from there."
     (bookmark-save)))
 
 
+;;;###autoload
+(defun bookmark-delete-all (&optional no-confirm)
+  "Permanently delete all bookmarks.
+Doesn't ask for confirmation if NO-CONFIRM is non-nil."
+  (interactive "P")
+  (when (or no-confirm
+            (yes-or-no-p "Permanently delete all bookmarks? "))
+    (bookmark-maybe-load-default-file)
+    (setq bookmark-alist-modification-count
+          (+ bookmark-alist-modification-count (length bookmark-alist)))
+    (setq bookmark-alist nil)
+    (bookmark-bmenu-surreptitiously-rebuild-list)
+    (when (bookmark-time-to-save-p)
+      (bookmark-save))))
+
+
 (defun bookmark-time-to-save-p (&optional final-time)
   "Return t if it is time to save bookmarks to disk, nil otherwise.
 Optional argument FINAL-TIME means this is being called when Emacs
@@ -1598,12 +1615,15 @@ unique numeric suffixes \"<2>\", \"<3>\", etc."
     (define-key map "\C-d" 'bookmark-bmenu-delete-backwards)
     (define-key map "x" 'bookmark-bmenu-execute-deletions)
     (define-key map "d" 'bookmark-bmenu-delete)
+    (define-key map "D" 'bookmark-bmenu-delete-all)
     (define-key map " " 'next-line)
     (define-key map "n" 'next-line)
     (define-key map "p" 'previous-line)
     (define-key map "\177" 'bookmark-bmenu-backup-unmark)
     (define-key map "u" 'bookmark-bmenu-unmark)
+    (define-key map "U" 'bookmark-bmenu-unmark-all)
     (define-key map "m" 'bookmark-bmenu-mark)
+    (define-key map "M" 'bookmark-bmenu-mark-all)
     (define-key map "l" 'bookmark-bmenu-load)
     (define-key map "r" 'bookmark-bmenu-rename)
     (define-key map "R" 'bookmark-bmenu-relocate)
@@ -1625,8 +1645,10 @@ unique numeric suffixes \"<2>\", \"<3>\", etc."
     ["Select Marked Bookmarks" bookmark-bmenu-select t]
     "---"
     ["Mark Bookmark" bookmark-bmenu-mark t]
+    ["Mark all Bookmarks" bookmark-bmenu-mark-all t]
     ["Unmark Bookmark" bookmark-bmenu-unmark  t]
     ["Unmark Backwards" bookmark-bmenu-backup-unmark  t]
+    ["Unmark all Bookmarks" bookmark-bmenu-unmark-all  t]
     ["Toggle Display of Filenames" bookmark-bmenu-toggle-filenames  t]
     ["Display Location of Bookmark" bookmark-bmenu-locate  t]
     "---"
@@ -1634,6 +1656,7 @@ unique numeric suffixes \"<2>\", \"<3>\", etc."
      ["Rename Bookmark" bookmark-bmenu-rename  t]
      ["Relocate Bookmark's File" bookmark-bmenu-relocate  t]
      ["Mark Bookmark for Deletion" bookmark-bmenu-delete  t]
+     ["Mark all Bookmarks for Deletion" bookmark-bmenu-delete-all  t]
      ["Delete Marked Bookmarks" bookmark-bmenu-execute-deletions  t])
     ("Annotations"
      ["Show Annotation for Current Bookmark" bookmark-bmenu-show-annotation  t]
@@ -1746,6 +1769,7 @@ Letters do not insert themselves; instead, they are commands.
 Bookmark names preceded by a \"*\" have annotations.
 \\<bookmark-bmenu-mode-map>
 \\[bookmark-bmenu-mark] -- mark bookmark to be displayed.
+\\[bookmark-bmenu-mark-all] -- mark all listed bookmarks to be displayed.
 \\[bookmark-bmenu-select] -- select bookmark of line point is on.
   Also show bookmarks marked using m in other windows.
 \\[bookmark-bmenu-toggle-filenames] -- toggle displaying of filenames (they may obscure long bookmark names).
@@ -1762,13 +1786,15 @@ Bookmark names preceded by a \"*\" have annotations.
 \\[bookmark-bmenu-relocate] -- relocate this bookmark's file (prompts for new file).
 \\[bookmark-bmenu-delete] -- mark this bookmark to be deleted, and move down.
 \\[bookmark-bmenu-delete-backwards] -- mark this bookmark to be deleted, and move up.
-\\[bookmark-bmenu-execute-deletions] -- delete bookmarks marked with `\\[bookmark-bmenu-delete]'.
+\\[bookmark-bmenu-delete-all] -- mark all listed bookmarks as to be deleted.
+\\[bookmark-bmenu-execute-deletions] -- delete bookmarks marked with `\\[bookmark-bmenu-delete]' or `\\[bookmark-bmenu-delete-all]'.
 \\[bookmark-bmenu-save] -- save the current bookmark list in the default file.
   With a prefix arg, prompts for a file to save in.
 \\[bookmark-bmenu-load] -- load in a file of bookmarks (prompts for file.)
 \\[bookmark-bmenu-unmark] -- remove all kinds of marks from current line.
   With prefix argument, also move up one line.
 \\[bookmark-bmenu-backup-unmark] -- back up a line and remove marks.
+\\[bookmark-bmenu-unmark-all] -- remove all kinds of marks from all listed bookmarks.
 \\[bookmark-bmenu-show-annotation] -- show the annotation, if it exists, for the current bookmark
   in another buffer.
 \\[bookmark-bmenu-show-all-annotations] -- show the annotations of all bookmarks in another buffer.
@@ -1935,9 +1961,23 @@ If the annotation does not exist, do nothing."
      (bookmark-bmenu-ensure-position))))
 
 
+(defun bookmark-bmenu-mark-all ()
+  "Mark all listed bookmarks to be displayed by \\<bookmark-bmenu-mode-map>\\[bookmark-bmenu-select]."
+  (interactive)
+  (save-excursion
+    (goto-char (point-min))
+    (bookmark-bmenu-ensure-position)
+    (with-buffer-modified-unmodified
+     (let ((inhibit-read-only t))
+       (while (not (eobp))
+         (delete-char 1)
+         (insert ?>)
+         (forward-line 1))))))
+
+
 (defun bookmark-bmenu-select ()
   "Select this line's bookmark; also display bookmarks marked with `>'.
-You can mark bookmarks with the \\<bookmark-bmenu-mode-map>\\[bookmark-bmenu-mark] command."
+You can mark bookmarks with the \\<bookmark-bmenu-mode-map>\\[bookmark-bmenu-mark] or \\<bookmark-bmenu-mode-map>\\[bookmark-bmenu-mark-all] commands."
   (interactive)
   (let ((bmrk (bookmark-bmenu-bookmark))
         (menu (current-buffer))
@@ -2106,6 +2146,20 @@ Optional BACKUP means move up."
   (bookmark-bmenu-ensure-position))
 
 
+(defun bookmark-bmenu-unmark-all ()
+  "Cancel all requested operations on all listed bookmarks."
+  (interactive)
+  (save-excursion
+    (goto-char (point-min))
+    (bookmark-bmenu-ensure-position)
+    (with-buffer-modified-unmodified
+     (let ((inhibit-read-only t))
+       (while (not (eobp))
+         (delete-char 1)
+         (insert " ")
+         (forward-line 1))))))
+
+
 (defun bookmark-bmenu-delete ()
   "Mark bookmark on this line to be deleted.
 To carry out the deletions that you've marked, use \\<bookmark-bmenu-mode-map>\\[bookmark-bmenu-execute-deletions]."
@@ -2131,6 +2185,22 @@ To carry out the deletions that you've marked, use \\<bookmark-bmenu-mode-map>\\
   (bookmark-bmenu-ensure-position))
 
 
+(defun bookmark-bmenu-delete-all ()
+  "Mark all listed bookmarks as to be deleted.
+To remove all deletion marks, use \\<bookmark-bmenu-mode-map>\\[bookmark-bmenu-unmark-all].
+To carry out the deletions that you've marked, use \\<bookmark-bmenu-mode-map>\\[bookmark-bmenu-execute-deletions]."
+  (interactive)
+  (save-excursion
+    (goto-char (point-min))
+    (bookmark-bmenu-ensure-position)
+    (with-buffer-modified-unmodified
+     (let ((inhibit-read-only t))
+       (while (not (eobp))
+         (delete-char 1)
+         (insert ?D)
+         (forward-line 1))))))
+
+
 (defun bookmark-bmenu-execute-deletions ()
   "Delete bookmarks flagged `D'."
   (interactive)
@@ -2290,6 +2360,9 @@ strings returned are not."
     (bindings--define-key map [delete]
       '(menu-item "Delete Bookmark..." bookmark-delete
 		  :help "Delete a bookmark from the bookmark list"))
+    (bindings--define-key map [delete-all]
+      '(menu-item "Delete all Bookmarks..." bookmark-delete-all
+		  :help "Delete all bookmarks from the bookmark list"))
     (bindings--define-key map [rename]
       '(menu-item "Rename Bookmark..." bookmark-rename
 		  :help "Change the name of a bookmark"))
diff --git a/test/lisp/bookmark-resources/test-list.bmk b/test/lisp/bookmark-resources/test-list.bmk
new file mode 100644
index 0000000000..696d64979b
--- /dev/null
+++ b/test/lisp/bookmark-resources/test-list.bmk
@@ -0,0 +1,20 @@
+;;;; Emacs Bookmark Format Version 1 ;;;; -*- coding: utf-8-emacs -*-
+;;; This format is meant to be slightly human-readable;
+;;; nevertheless, you probably don't want to edit it.
+;;; -*- End Of Bookmark File Format Version Stamp -*-
+(("name-0"
+ (filename . "/some/file-0")
+ (front-context-string . "abc")
+ (rear-context-string . "def")
+ (position . 3))
+("name-1"
+ (filename . "/some/file-1")
+ (front-context-string . "abc")
+ (rear-context-string . "def")
+ (position . 3))
+("name-2"
+ (filename . "/some/file-2")
+ (front-context-string . "abc")
+ (rear-context-string . "def")
+ (position . 3))
+)
diff --git a/test/lisp/bookmark-tests.el b/test/lisp/bookmark-tests.el
index 7e0384b724..726db52943 100644
--- a/test/lisp/bookmark-tests.el
+++ b/test/lisp/bookmark-tests.el
@@ -82,6 +82,70 @@ the lexically-bound variable `buffer'."
           ,@body)
        (kill-buffer buffer))))
 
+(defvar bookmark-tests-bookmark-file-list
+  (expand-file-name "test-list.bmk" bookmark-tests-data-dir)
+  "Bookmark file used for testing a list of bookmarks.")
+
+;; The values below should match `bookmark-tests-bookmark-file-list'
+;; content.  We cache these values to speed up tests.
+(eval-and-compile  ; needed by `with-bookmark-test-list' macro
+  (defvar bookmark-tests-bookmark-list-0 '("name-0"
+                            (filename . "/some/file-0")
+                            (front-context-string . "ghi")
+                            (rear-context-string . "jkl")
+                            (position . 4))
+    "Cached value used in bookmark-tests.el."))
+
+;; The values below should match `bookmark-tests-bookmark-file-list'
+;; content.  We cache these values to speed up tests.
+(eval-and-compile  ; needed by `with-bookmark-test-list' macro
+  (defvar bookmark-tests-bookmark-list-1 '("name-1"
+                            (filename . "/some/file-1")
+                            (front-context-string . "mno")
+                            (rear-context-string . "pqr")
+                            (position . 5))
+    "Cached value used in bookmark-tests.el."))
+
+;; The values below should match `bookmark-tests-bookmark-file-list'
+;; content.  We cache these values to speed up tests.
+(eval-and-compile  ; needed by `with-bookmark-test-list' macro
+  (defvar bookmark-tests-bookmark-list-2 '("name-2"
+                            (filename . "/some/file-2")
+                            (front-context-string . "stu")
+                            (rear-context-string . "vwx")
+                            (position . 6))
+    "Cached value used in bookmark-tests.el."))
+
+(defvar bookmark-tests-cache-timestamp-list
+  (cons bookmark-tests-bookmark-file-list
+        (nth 5 (file-attributes
+                bookmark-tests-bookmark-file-list)))
+  "Cached value used in bookmark-tests.el.")
+
+(defmacro with-bookmark-test-list (&rest body)
+  "Create environment for testing bookmark.el and evaluate BODY.
+Ensure a clean environment for testing, and do not change user
+data when running tests interactively."
+  `(with-temp-buffer
+     (let ((bookmark-alist (quote (,(copy-sequence bookmark-tests-bookmark-list-0)
+                                   ,(copy-sequence bookmark-tests-bookmark-list-1)
+                                   ,(copy-sequence bookmark-tests-bookmark-list-2))))
+           (bookmark-default-file bookmark-tests-bookmark-file-list)
+           (bookmark-bookmarks-timestamp bookmark-tests-cache-timestamp-list)
+           bookmark-save-flag)
+       ,@body)))
+
+(defmacro with-bookmark-test-file-list (&rest body)
+  "Create environment for testing bookmark.el and evaluate BODY.
+Same as `with-bookmark-test-list' but also opens the resource file
+example.txt in a buffer, which can be accessed by callers through
+the lexically-bound variable `buffer'."
+  `(let ((buffer (find-file-noselect bookmark-tests-example-file)))
+     (unwind-protect
+         (with-bookmark-test-list
+          ,@body)
+       (kill-buffer buffer))))
+
 (ert-deftest bookmark-tests-all-names ()
   (with-bookmark-test
    (should (equal (bookmark-all-names) '("name")))))
@@ -94,6 +158,30 @@ the lexically-bound variable `buffer'."
   (with-bookmark-test
    (should (equal (bookmark-get-bookmark-record "name") (cdr bookmark-tests-bookmark)))))
 
+(ert-deftest bookmark-tests-all-names-list ()
+  (with-bookmark-test-list
+   (should (equal (bookmark-all-names) '("name-0"
+                                         "name-1"
+                                         "name-2")))))
+
+(ert-deftest bookmark-tests-get-bookmark-list ()
+  (with-bookmark-test-list
+   (should (equal (bookmark-get-bookmark "name-0")
+                  bookmark-tests-bookmark-list-0))
+   (should (equal (bookmark-get-bookmark "name-1")
+                  bookmark-tests-bookmark-list-1))
+   (should (equal (bookmark-get-bookmark "name-2")
+                  bookmark-tests-bookmark-list-2))))
+
+(ert-deftest bookmark-tests-get-bookmark-record-list ()
+  (with-bookmark-test-list
+   (should (equal (bookmark-get-bookmark-record "name-0")
+                  (cdr bookmark-tests-bookmark-list-0)))
+   (should (equal (bookmark-get-bookmark-record "name-1")
+                  (cdr bookmark-tests-bookmark-list-1)))
+   (should (equal (bookmark-get-bookmark-record "name-2")
+                  (cdr bookmark-tests-bookmark-list-2)))))
+
 (ert-deftest bookmark-tests-record-getters-and-setters-new ()
   (with-temp-buffer
     (let* ((buffer-file-name "test")
@@ -129,6 +217,19 @@ the lexically-bound variable `buffer'."
        ;; calling twice gives same record
        (should (equal (bookmark-make-record) record))))))
 
+(ert-deftest bookmark-tests-make-record-list ()
+  (with-bookmark-test-file-list
+   (let* ((record `("example.txt" (filename . ,bookmark-tests-example-file)
+                    (front-context-string . "is text file is ")
+                    (rear-context-string)
+                    (position . 3)
+                    (defaults "example.txt"))))
+     (with-current-buffer buffer
+       (goto-char 3)
+       (should (equal (bookmark-make-record) record))
+       ;; calling twice gives same record
+       (should (equal (bookmark-make-record) record))))))
+
 (ert-deftest bookmark-tests-make-record-function ()
   (with-bookmark-test
    (let ((buffer-file-name "test"))
@@ -266,6 +367,11 @@ the lexically-bound variable `buffer'."
    (bookmark-delete "name")
    (should (equal bookmark-alist nil))))
 
+(ert-deftest bookmark-tests-delete-all ()
+  (with-bookmark-test-list
+   (bookmark-delete-all t)
+   (should (equal bookmark-alist nil))))
+
 (defmacro with-bookmark-test-save-load (&rest body)
   "Create environment for testing bookmark.el and evaluate BODY.
 Same as `with-bookmark-test' but also sets a temporary
@@ -339,6 +445,18 @@ testing `bookmark-bmenu-list'."
             ,@body)
         (kill-buffer bookmark-bmenu-buffer)))))
 
+(defmacro with-bookmark-bmenu-test-list (&rest body)
+  "Create environment for testing `bookmark-bmenu-list' and evaluate BODY.
+Same as `with-bookmark-test-list' but with additions suitable for
+testing `bookmark-bmenu-list'."
+  `(with-bookmark-test-list
+    (let ((bookmark-bmenu-buffer "*Bookmark List - Testing*"))
+      (unwind-protect
+          (save-window-excursion
+            (bookmark-bmenu-list)
+            ,@body)
+        (kill-buffer bookmark-bmenu-buffer)))))
+
 (ert-deftest bookmark-bmenu.enu-edit-annotation/show-annotation ()
   (with-bookmark-bmenu-test
    (bookmark-set-annotation "name" "foo")
@@ -362,5 +480,102 @@ testing `bookmark-bmenu-list'."
    (should (equal (buffer-name (current-buffer)) bookmark-bmenu-buffer))
    (should (looking-at "name"))))
 
+(ert-deftest bookmark-test-bmenu-mark-all ()
+  (with-bookmark-bmenu-test-list
+   (let ((here (point-max)))
+     ;; Expect to not move the point
+     (goto-char here)
+     (bookmark-bmenu-mark-all)
+     (should (equal here (point)))
+     ;; Verify that all bookmarks are marked
+     (goto-char (point-min))
+     (bookmark-bmenu-ensure-position)
+     (should (looking-at "^> "))
+     (should (equal bookmark-tests-bookmark-list-0
+                    (bookmark-get-bookmark (bookmark-bmenu-bookmark))))
+     (forward-line 1)
+     (should (looking-at "^> "))
+     (should (equal bookmark-tests-bookmark-list-1
+                    (bookmark-get-bookmark (bookmark-bmenu-bookmark))))
+     (forward-line 1)
+     (should (looking-at "^> "))
+     (should (equal bookmark-tests-bookmark-list-2
+                    (bookmark-get-bookmark (bookmark-bmenu-bookmark)))))))
+
+(ert-deftest bookmark-test-bmenu-any-marks-list ()
+  (with-bookmark-bmenu-test-list
+   ;; Mark just the second item
+   (goto-char (point-min))
+   (bookmark-bmenu-ensure-position)
+   (forward-line 1)
+   (bookmark-bmenu-mark)
+   ;; Verify that only the second item is marked
+   (goto-char (point-min))
+   (bookmark-bmenu-ensure-position)
+   (should (looking-at "^  "))
+   (should (equal bookmark-tests-bookmark-list-0
+                  (bookmark-get-bookmark (bookmark-bmenu-bookmark))))
+   (forward-line 1)
+   (should (looking-at "^> "))
+   (should (equal bookmark-tests-bookmark-list-1
+                  (bookmark-get-bookmark (bookmark-bmenu-bookmark))))
+   (forward-line 1)
+   (should (looking-at "^> "))
+   (should (equal bookmark-tests-bookmark-list-2
+                  (bookmark-get-bookmark (bookmark-bmenu-bookmark))))
+   ;; There should be at least one mark
+   (should (bookmark-bmenu-any-marks))))
+
+(ert-deftest bookmark-test-bmenu-unmark-all ()
+  (with-bookmark-bmenu-test-list
+   (bookmark-bmenu-mark-all)
+   (let ((here (point-max)))
+     ;; Expect to not move the point
+     (goto-char here)
+     (bookmark-bmenu-unmark-all)
+     (should (equal here (point)))
+     ;; Verify that all bookmarks are unmarked
+     (goto-char (point-min))
+     (bookmark-bmenu-ensure-position)
+     (should (looking-at "^  "))
+     (should (equal bookmark-tests-bookmark-list-0
+                    (bookmark-get-bookmark (bookmark-bmenu-bookmark))))
+     (forward-line 1)
+     (should (looking-at "^  "))
+     (should (equal bookmark-tests-bookmark-list-1
+                    (bookmark-get-bookmark (bookmark-bmenu-bookmark))))
+     (forward-line 1)
+     (should (looking-at "^  "))
+     (should (equal bookmark-tests-bookmark-list-2
+                    (bookmark-get-bookmark (bookmark-bmenu-bookmark)))))))
+
+(ert-deftest bookmark-test-bmenu-delete-all ()
+  (with-bookmark-bmenu-test-list
+   ;; Verify that unmarked bookmarks aren't deleted
+   (bookmark-bmenu-execute-deletions)
+   (should-not (eq bookmark-alist nil))
+   (let ((here (point-max)))
+     ;; Expect to not move the point
+     (goto-char here)
+     (bookmark-bmenu-delete-all)
+     (should (equal here (point)))
+     ;; Verify that all bookmarks are marked for deletion
+     (goto-char (point-min))
+     (bookmark-bmenu-ensure-position)
+     (should (looking-at "^D "))
+     (should (equal bookmark-tests-bookmark-list-0
+                    (bookmark-get-bookmark (bookmark-bmenu-bookmark))))
+     (forward-line 1)
+     (should (looking-at "^D "))
+     (should (equal bookmark-tests-bookmark-list-1
+                    (bookmark-get-bookmark (bookmark-bmenu-bookmark))))
+     (forward-line 1)
+     (should (looking-at "^D "))
+     (should (equal bookmark-tests-bookmark-list-2
+                    (bookmark-get-bookmark (bookmark-bmenu-bookmark))))
+     ;; Verify that all bookmarks are deleted
+     (bookmark-bmenu-execute-deletions)
+     (should (eq bookmark-alist nil)))))
+
 (provide 'bookmark-tests)
 ;;; bookmark-tests.el ends here
-- 
2.26.2


[-- Attachment #2: OpenPGP digital signature --]
[-- Type: application/pgp-signature, Size: 488 bytes --]

  reply	other threads:[~2020-07-25 10:46 UTC|newest]

Thread overview: 52+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2020-07-23 23:00 Add new functions to mark/unmark/delete all bookmarks Matthew White
2020-07-24  3:15 ` Drew Adams
2020-07-24  9:07   ` Matthew White
2020-07-24 15:07     ` Stefan Monnier
2020-07-24 15:18       ` Lars Ingebrigtsen
2020-07-24 17:03         ` Matthew White
2020-07-24 17:09           ` Stefan Monnier
2020-07-28 15:54             ` Matthew White
2020-07-28 16:14               ` Stefan Monnier
2020-07-28 16:36                 ` Matthew White
2020-07-28 18:06               ` Eli Zaretskii
2020-07-28 18:15                 ` Matthew White
2020-07-24 17:07         ` Drew Adams
2020-07-24 17:07       ` Drew Adams
2020-07-24 16:43     ` Drew Adams
2020-07-24  5:31 ` Yuri Khan
2020-07-24 15:05   ` Stefan Monnier
2020-07-24 16:56     ` Noam Postavsky
2020-07-24 19:00     ` Extend tabulated-list-mode to support marks Yuri Khan
2020-07-25 13:46       ` Dirk-Jan C. Binnema
2020-07-25 14:23       ` Stefan Monnier
2020-07-25 14:32         ` Yuri Khan
2020-07-26 12:43       ` Stefan Kangas
2020-07-24 17:07   ` Add new functions to mark/unmark/delete all bookmarks Drew Adams
2020-07-24 19:07 ` Karl Fogel
2020-07-25 10:46   ` Matthew White [this message]
2020-07-25 10:58     ` Eli Zaretskii
2020-07-25 15:33       ` Michael Albinus
2020-07-25 15:38         ` Eli Zaretskii
2020-07-25 15:56           ` Michael Albinus
2020-07-25 16:30             ` Eli Zaretskii
2020-07-25 16:33               ` Eli Zaretskii
2020-07-25 16:43                 ` Michael Albinus
2020-07-25 16:55                   ` Eli Zaretskii
2020-07-25 18:28                     ` Matthew White
2020-07-25 18:54                       ` Eli Zaretskii
2020-07-25 20:36     ` Karl Fogel
2020-07-25 21:14       ` Stefan Monnier
2020-07-25 22:40         ` Karl Fogel
2020-07-29 22:16     ` Karl Fogel
2020-07-31  2:58       ` Matthew White
2020-07-31  6:01         ` Karl Fogel
2020-08-02 22:13         ` Karl Fogel
2020-08-06 17:59           ` Matthew White
2020-08-07  1:10             ` Karl Fogel
2020-08-07  8:33               ` Matthew White
     [not found]               ` <jwveeojyxh1.fsf-monnier+emacs@gnu.org>
     [not found]                 ` <87a6z715do.fsf@red-bean.com>
2020-08-07 17:05                   ` Matthew White
2020-08-07 17:18                     ` Eli Zaretskii
2020-08-08  8:23                       ` Matthew White
2020-08-09 20:19                     ` Karl Fogel
2020-08-10  5:41                       ` Matthew White
     [not found] <<20200724005105.11f85d5f@pineapple>
     [not found] ` <<87pn8ku3y9.fsf@red-bean.com>
     [not found]   ` <<20200725124618.49a073b1@pineapple>
     [not found]     ` <<83lfj7dfp2.fsf@gnu.org>
     [not found]       ` <<87tuxvpq1r.fsf@gmx.de>
     [not found]         ` <<83d04jd2pm.fsf@gnu.org>
2020-07-25 16:07           ` Drew Adams

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20200725124618.49a073b1@pineapple \
    --to=mehw.is.me@inventati.org \
    --cc=emacs-devel@gnu.org \
    --cc=kfogel@red-bean.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
Code repositories for project(s) associated with this external index

	https://git.savannah.gnu.org/cgit/emacs.git
	https://git.savannah.gnu.org/cgit/emacs/org-mode.git

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.