Hello Everyone,

This patch adds the ability to search the search results.
It was actually quite simple to do.

History is kept in a cache like the emms-cache-db.
There is a function, emms-browser-show-searches that shows a
textual representation of the current search stack.  I thought that to be a
bit frivolous
but it's actually quite useful and nice.
Quitting the search buffer will quit the current search until there are no
more searches in history
before it quits back to the browser.
A fresh cache-db search resets the history if there is any.

This can be enabled or disabled. It is off by default.
It is so nice and intuitive I can't imagine why anyone wouldn't want it.
I've been using it for a little more than a week and it's great.
Performance is not a problem on my 13 yo macbook pro running arch linux.
I haven't tried it on an RPI yet.  But I dont think there is a
performance tax on this.
Subsequent searches are definitely faster.

I did a search for 'tango' in my 15000 title tango music library.
I couldn't tell the difference.

I can provide another patch that removes the choice of disabling if desired.

Have a Beautiful day,

Erica
From 5a5068a640c2146ec89285724949ac32dbded3a0 Mon Sep 17 00:00:00 2001
From: ericalinag <[email protected]>
Date: Sun, 12 Nov 2023 17:29:01 -0500
Subject: [PATCH 1/4] Add Search caching for recursive searching.

---
 doc/emms.texinfo |   7 +++
 emms-browser.el  | 146 +++++++++++++++++++++++++++++++++++++++++------
 2 files changed, 135 insertions(+), 18 deletions(-)

diff --git a/doc/emms.texinfo b/doc/emms.texinfo
index d869eef..38139ab 100644
--- a/doc/emms.texinfo
+++ b/doc/emms.texinfo
@@ -362,10 +362,12 @@ variables. For example:
 @lisp
 (setq emms-info-asynchronously nil)
 (setq emms-playlist-buffer-name "*Music*")
+(setq emms-browser-search-cache t)
 @end lisp
 
 The first @code{setq} turns off the asynchronous updating of info tags. The
 second sets the default name of the Emms playlist buffer.
+The third turns on the search cache and recursive searching.
 
 Another way to change Emms variables is to use the M-x
 @command{customize} mechanism provided by Emacs.
@@ -1916,6 +1918,11 @@ Search the collection by names.
 @findex emms-browser-search-by-title
 Search the collection by title.
 
+@item s h
+@kindex s h (emms-browser)
+@findex emms-browser-show-searches
+Show the currently active searches in the search cache.
+
 @item b 1
 @kindex b 1 (emms-browser)
 @findex emms-browse-by-albumartist
diff --git a/emms-browser.el b/emms-browser.el
index e00ad84..d9152a9 100644
--- a/emms-browser.el
+++ b/emms-browser.el
@@ -446,6 +446,7 @@ Called once for each directory."
     (define-key map (kbd "s A") #'emms-browser-search-by-album)
     (define-key map (kbd "s t") #'emms-browser-search-by-title)
     (define-key map (kbd "s s") #'emms-browser-search-by-names)
+    (define-key map (kbd "s h") #'emms-browser-show-searches)
     (define-key map (kbd "W o w") #'emms-browser-lookup-albumartist-on-wikipedia)
     (define-key map (kbd "W A w") #'emms-browser-lookup-artist-on-wikipedia)
     (define-key map (kbd "W C w") #'emms-browser-lookup-composer-on-wikipedia)
@@ -461,7 +462,7 @@ Called once for each directory."
 (defvar emms-browser-search-mode-map
   (let ((map (make-sparse-keymap)))
     (set-keymap-parent map emms-browser-mode-map)
-    (define-key map (kbd "q") #'emms-browser-kill-search)
+    (define-key map (kbd "q") #'emms-browser-search-quit)
     map)
   "Keymap for `emms-browser-mode'.")
 
@@ -1590,24 +1591,114 @@ Returns the playlist window."
 ;; Searching
 ;; --------------------------------------------------
 
+;; The search cache
+;; This keeps a list of search results in the same format as the emms-cache-db.
+;; Each set of results is stored in a cons with its search-list as its car.
+;; It might be nice to have a search name, but we don't know it.
+;; We can construct search crumbs from the search-lists.
+;; A fresh search from the emms-cache-db refreshes the cache.
+;; Searches invoked from the browser-search buffer will search the current
+;; search results cache rather than starting over with the emms-cache-db.
+;; Quitting the search buffer returns to the previous search until
+;; finally killing itself and returning to the browser window.
+
+(defvar emms-browser-search-cache nil
+  "Turn on the search cache.")
+
+(defvar emms-browser-search-caches '()
+  "The stack of search result caches.")
+
+(defun emms-browser-cache-search (search-list cache)
+  "Store SEARCH-LIST and CACHE in a stack."
+  (when emms-browser-search-cache
+    (push (cons search-list cache) emms-browser-search-caches)))
+
+(defun emms-browser-get-search-keys ()
+  "Return the search-list keys for the current search cache."
+  (if (and emms-browser-search-cache
+           (< 0 (length emms-browser-search-caches)))
+      (reverse (mapcar #'car emms-browser-search-caches))
+    '()))
+
+(defun emms-browser-format-search-list (search-list)
+  "Create a string format of a SEARCH-LIST."
+  (let ((infos (append (car (car search-list))))
+        (svalue (cdar search-list)))
+    (format "%s - %s"
+            (mapconcat
+             `(lambda (info) (substring (format "%s" (symbol-name info))  5)) infos " : ")
+            svalue)))
+
+(defun emms-browser-search-crumbs ()
+  "Create a list of search crumb strings for the current search cache."
+  (let ((search-lists (emms-browser-get-search-keys)))
+    (mapcar #'emms-browser-format-search-list search-lists)))
+
+(defun emms-browser-show-searches ()
+  "Message the current search cache crumbs."
+  (interactive)
+  (message "Emms - Search Crumbs:")
+  (mapc (lambda (crumb) (message "%s" crumb)) (emms-browser-search-crumbs)))
+
+(defun emms-browser-get-last-search-cache ()
+  "Return the cache portion of the last search cache entry.
+Returns the emms-cache-db if the cache is turned off."
+  (if (and emms-browser-search-cache
+           (< 0 (length emms-browser-search-caches)))
+      (cdar emms-browser-search-caches)
+    emms-cache-db))
+
+(defun emms-browser-cache-to-tracks (cache)
+  "Return a list of tracks from the CACHE given."
+  (let (tracks)
+    (maphash (lambda (_k track)
+               (push track tracks))
+             cache)
+    tracks))
 
-(defun emms-browser-filter-cache (search-list)
-  "Return a list of tracks that match SEARCH-LIST.
+(defun emms-browser-render-last-search ()
+  "Render the results for the top of the search cache stack."
+  (interactive)
+  (emms-with-inhibit-read-only-t
+   (emms-browser-render-search
+    (emms-browser-cache-to-tracks
+     (emms-browser-get-last-search-cache))))
+  (emms-browser-expand-all))
+
+(defun emms-browser-pop-last-search()
+  "Pop the search results cache and then render to show the previous search result.
+Quit when there is no result history left."
+  (pop emms-browser-search-caches)
+  (if (< 0 (length emms-browser-search-caches))
+      (emms-browser-render-last-search)
+    (emms-browser-kill-search)))
+
+(defun emms-browser-filter-tracks (source search-list)
+  "Return a list of tracks in SOURCE that match SEARCH-LIST.
+SOURCE is either emms-cache-db or a cache of search results.
 SEARCH-LIST is a list of cons pairs, in the form:
 
   ((field1 field2) string)
 
 If string matches any of the fields in a cons pair, it will be
-included."
-
-  (let (tracks)
-    (maphash (lambda (_k track)
+included. The resulting tracks are also pushed onto
+emms-browser-last-search-caches in the same format as the emms-cache-db."
+
+  (let ((tracks nil)
+        (search-cache (make-hash-table
+                       :test (if (fboundp 'define-hash-table-test)
+                                 'string-hash
+                               'equal))))
+    (maphash (lambda (path track)
                (when (emms-browser-matches-p track search-list)
-                 (push track tracks)))
-             emms-cache-db)
+                 (push track tracks)
+                 (puthash path track search-cache)))
+             source)
+    (emms-browser-cache-search search-list search-cache)
     tracks))
 
 (defun emms-browser-matches-p (track search-list)
+  "Do the actual string matching for the SEARCH-LIST against TRACK."
   (let (no-match matched)
     (dolist (item search-list)
       (setq matched nil)
@@ -1624,23 +1715,34 @@ included."
   (switch-to-buffer
    (get-buffer-create emms-browser-search-buffer-name))
   (emms-browser-mode t)
-  (use-local-map emms-browser-search-mode-map)
-  (emms-with-inhibit-read-only-t
-   (delete-region (point-min) (point-max))))
+  (use-local-map emms-browser-search-mode-map))
+
+(defun emms-browser-search-source-cache()
+  "Return the last search cache or the emms-cache-db`."
+  (if (string= (buffer-name) emms-browser-search-buffer-name)
+      (emms-browser-get-last-search-cache)
+    emms-cache-db))
 
 (defun emms-browser-search (fields)
-  "Search for STR using FIELDS."
+  "Search in the cache or the last search result cache for STR using FIELDS."
   (let* ((prompt (format "Searching with %S: " fields))
-         (str (read-string prompt)))
-    (emms-browser-search-buffer-go)
+         (str (read-string prompt))
+         (track-cache (emms-browser-search-source-cache)))
+    (when (eq track-cache emms-cache-db)
+      (setq emms-browser-search-caches '())
+      (emms-browser-search-buffer-go))
+
     (emms-with-inhibit-read-only-t
      (emms-browser-render-search
-      (emms-browser-filter-cache
-       (list (list fields str)))))
+      (emms-browser-filter-tracks track-cache
+                                  (list (list fields str)))))
     (emms-browser-expand-all)
     (goto-char (point-min))))
 
 (defun emms-browser-render-search (tracks)
+  "Render a browser tree with TRACKS."
+  (emms-with-inhibit-read-only-t
+   (delete-region (point-min) (point-max)))
   (let ((entries
          (emms-browser-make-sorted-alist emms-browser-default-browse-type tracks)))
     (dolist (entry entries)
@@ -1652,7 +1754,15 @@ included."
 (defun emms-browser-kill-search ()
   "Kill the buffer when q is hit."
   (interactive)
-  (kill-buffer (current-buffer)))
+  (when (string= (buffer-name) emms-browser-search-buffer-name)
+    (kill-buffer (current-buffer))))
+
+(defun emms-browser-search-quit ()
+  "Pop the search cache or quit."
+  (interactive)
+  (if emms-browser-search-cache
+      (emms-browser-pop-last-search)
+    (emms-browser-kill-search)))
 
 (defun emms-browser-search-by-albumartist ()
   (interactive)
-- 
2.42.0


From 7c037b7dd8cd30481f6fd801f692247d65f711d8 Mon Sep 17 00:00:00 2001
From: ericalinag <[email protected]>
Date: Sun, 12 Nov 2023 17:46:53 -0500
Subject: [PATCH 2/4] Surface and document 2 new search cache related
 functions.

---
 doc/emms.texinfo | 9 +++++++++
 emms-browser.el  | 1 +
 2 files changed, 10 insertions(+)

diff --git a/doc/emms.texinfo b/doc/emms.texinfo
index 38139ab..1ebdd85 100644
--- a/doc/emms.texinfo
+++ b/doc/emms.texinfo
@@ -1787,6 +1787,15 @@ Display the browser and order the tracks by genre.
 Display the browser and order the tracks by year.
 @end defun
 
+@defun emms-browser-show-searches
+Show Search crumbs of the active searches.
+@end defun
+
+@defun emms-browser-render-last-search
+Render the results for the last search with current settings.
+@end defun
+
+
 Once the Browser is displayed you can use it to managed your track
 collection and playlists. The Browser is interactive and has its own
 keybindings.
diff --git a/emms-browser.el b/emms-browser.el
index d9152a9..a41138f 100644
--- a/emms-browser.el
+++ b/emms-browser.el
@@ -463,6 +463,7 @@ Called once for each directory."
   (let ((map (make-sparse-keymap)))
     (set-keymap-parent map emms-browser-mode-map)
     (define-key map (kbd "q") #'emms-browser-search-quit)
+    (define-key map (kbd "g") #'emms-browser-render-last-search)
     map)
   "Keymap for `emms-browser-mode'.")
 
-- 
2.42.0


From 76702228c9fc17a0ad321572b173351b19405e66 Mon Sep 17 00:00:00 2001
From: ericalinag <[email protected]>
Date: Sun, 12 Nov 2023 19:26:39 -0500
Subject: [PATCH 3/4] Cleaning up cache code, another review.

---
 emms-browser.el | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/emms-browser.el b/emms-browser.el
index a41138f..e6255d8 100644
--- a/emms-browser.el
+++ b/emms-browser.el
@@ -1627,7 +1627,7 @@ Returns the playlist window."
         (svalue (cdar search-list)))
     (format "%s - %s"
             (mapconcat
-             `(lambda (info) (substring (format "%s" (symbol-name info))  5)) infos " : ")
+             `(lambda (info) (substring (symbol-name info)  5)) infos " : ")
             svalue)))
 
 (defun emms-browser-search-crumbs ()
@@ -1638,8 +1638,8 @@ Returns the playlist window."
 (defun emms-browser-show-searches ()
   "Message the current search cache crumbs."
   (interactive)
-  (message "Emms - Search Crumbs:")
-  (mapc (lambda (crumb) (message "%s" crumb)) (emms-browser-search-crumbs)))
+  (message "Emms Search Crumbs:\n%s"
+           (mapconcat #'identity (emms-browser-search-crumbs) "\n")))
 
 (defun emms-browser-get-last-search-cache ()
   "Return the cache portion of the last search cache entry.
@@ -1768,7 +1768,7 @@ emms-browser-last-search-caches in the same format as the emms-cache-db."
 (defun emms-browser-search-by-albumartist ()
   (interactive)
   (emms-browser-search '(info-albumartist)))
-`
+
 (defun emms-browser-search-by-artist ()
   (interactive)
   (emms-browser-search '(info-artist)))
-- 
2.42.0


From bf71fdc75efef8ffe15302c471868d930edc3a79 Mon Sep 17 00:00:00 2001
From: ericalinag <[email protected]>
Date: Mon, 13 Nov 2023 09:51:19 -0500
Subject: [PATCH 4/4] Eliminate no mark saved message. Change field separator
 in crumbs.

---
 emms-browser.el | 12 +++++++-----
 1 file changed, 7 insertions(+), 5 deletions(-)

diff --git a/emms-browser.el b/emms-browser.el
index e6255d8..ff99f94 100644
--- a/emms-browser.el
+++ b/emms-browser.el
@@ -1569,10 +1569,10 @@ Returns the playlist window."
         ;; make q in the playlist window hide the linked browser
         (when (boundp 'emms-playlist-mode-map)
           (define-key emms-playlist-mode-map (kbd "q")
-            (lambda ()
-              (interactive)
-              (emms-browser-hide-linked-window)
-              (bury-buffer))))
+                      (lambda ()
+                        (interactive)
+                        (emms-browser-hide-linked-window)
+                        (bury-buffer))))
         (setq pwin (get-buffer-window pbuf))))
     pwin))
 
@@ -1627,7 +1627,7 @@ Returns the playlist window."
         (svalue (cdar search-list)))
     (format "%s - %s"
             (mapconcat
-             `(lambda (info) (substring (symbol-name info)  5)) infos " : ")
+             `(lambda (info) (substring (symbol-name info)  5)) infos " | ")
             svalue)))
 
 (defun emms-browser-search-crumbs ()
@@ -1664,6 +1664,7 @@ Returns the emms-cache-db if the cache is turned off."
    (emms-browser-render-search
     (emms-browser-cache-to-tracks
      (emms-browser-get-last-search-cache))))
+  (emms-browser-mark-entry)
   (emms-browser-expand-all))
 
 (defun emms-browser-pop-last-search()
@@ -1737,6 +1738,7 @@ emms-browser-last-search-caches in the same format as the emms-cache-db."
      (emms-browser-render-search
       (emms-browser-filter-tracks track-cache
                                   (list (list fields str)))))
+    (emms-browser-mark-entry)
     (emms-browser-expand-all)
     (goto-char (point-min))))
 
-- 
2.42.0

Reply via email to