From e2335f110d65a60fdb499285ac34821aca01a70b Mon Sep 17 00:00:00 2001 From: jao Date: Tue, 26 Jan 2021 14:04:51 +0000 Subject: espotify: playing with levenshtein --- media/espotify.org | 133 +++++++++++++++++++++++++++-------------------------- 1 file changed, 67 insertions(+), 66 deletions(-) (limited to 'media') diff --git a/media/espotify.org b/media/espotify.org index ed3e7f8..d77bd0d 100644 --- a/media/espotify.org +++ b/media/espotify.org @@ -294,7 +294,7 @@ Let's start with an umbrella customization group: (defvar espotify-consult-history nil) - (defun consult-spotify-by (type &optional filter) + (defun espotify-consult-by (type &optional filter) (let ((orderless-matching-styles '(orderless-literal))) (consult--read (format "Search %ss: " type) (espotify--search-generator type filter) @@ -326,27 +326,41 @@ Let's start with an umbrella customization group: case: #+begin_src emacs-lisp - (defvar espotify-search-suffix "=" - "Suffix in the search string launching an actual Web query.") - (defun espotify--async-search (next type filter) - (lambda (action) - (pcase action - ((pred stringp) - (when (string-suffix-p espotify-search-suffix action) - (espotify-search-all - (lambda (x) - (funcall next 'flush) - (funcall next x)) - (substring action 0 (- (length action) - (length espotify-search-suffix))) - type - filter))) - (_ (funcall next action))))) + (let ((current "")) + (lambda (action) + (pcase action + ((pred stringp) + (when-let (term (espotify-check-term current action)) + (setq current term) + (espotify-search-all + (lambda (x) + (funcall next 'flush) + (funcall next x)) + current + type + filter))) + (_ (funcall next action)))))) #+end_src We have introduced the convention that we're only launching a search - when the input string ends in "=", to avoid piling on HTTP requests. + when the input string ends in "=", to avoid piling on HTTP + requests, and also played a bit with Levenshtein distance, both via + the function =espotify-check-search-term=: + + #+begin_src emacs-lisp :tangle espotify.el + (defvar espotify-search-suffix "=" + "Suffix in the search string launching an actual Web query.") + + (defvar espotify-search-threshold 8 + "Threshold to automatically launch an actual Web query.") + + (defun espotify--check-term (prev new) + (when (not (string-blank-p new)) + (cond ((string-suffix-p espotify-search-suffix new) + (substring new 0 (- (length new) (length espotify-search-suffix)))) + ((>= (string-distance prev new) espotify-search-threshold) new)))) + #+end_src When processing the results, we format them as a displayable string, while hiding in a property the URI that will allow us to @@ -401,29 +415,25 @@ Let's start with an umbrella customization group: albums using consult: #+begin_src emacs-lisp - (defun consult-spotify-album (&optional filter) + (defun espotify-consult-album (&optional filter) (interactive) - (espotify--maybe-play (consult-spotify-by 'album filter))) + (espotify--maybe-play (espotify-consult-by 'album filter))) #+end_src And likewise for playlists, artists and combinations thereof: #+begin_src emacs-lisp - (defun consult-spotify-artist (&optional filter) + (defun espotify-consult-artist (&optional filter) (interactive) - (espotify--maybe-play (consult-spotify-by 'artist filter))) - #+end_src + (espotify--maybe-play (espotify-consult-by 'artist filter))) - #+begin_src emacs-lisp - (defun consult-spotify-track (&optional filter) + (defun espotify-consult-track (&optional filter) (interactive) - (espotify--maybe-play (consult-spotify-by 'track filter))) - #+end_src + (espotify--maybe-play (espotify-consult-by 'track filter))) - #+begin_src emacs-lisp - (defun consult-spotify-playlist (&optional filter) + (defun espotify-consult-playlist (&optional filter) (interactive) - (espotify--maybe-play (consult-spotify-by 'playlist filter))) + (espotify--maybe-play (espotify-consult-by 'playlist filter))) #+end_src * Adding metadata to candidates using Marginalia @@ -526,16 +536,18 @@ Let's start with an umbrella customization group: interface, with the flag =dynamic-collection= set. Ivy will wait until we call =ivy-candidate-updates= with our items. - #+begin_src emacs-lisp :tangle no - (defun counsel-espotify--search-by (type filter) - (lambda (term) - (espotify-search-all (lambda (its) - (let ((cs (mapcar #'espotify--format-item its))) - (ivy-update-candidates cs))) - term - type - filter) - 0)) + #+begin_src emacs-lisp + (defun espotify-counsel--search-by (type filter) + (let ((current-term "")) + (lambda (term) + (when-let (term (espotify-check-term current-term term)) + (espotify-search-all (lambda (its) + (let ((cs (mapcar #'espotify--format-item its))) + (ivy-update-candidates cs))) + (setq current-term term) + type + filter)) + 0))) #+end_src With that, we can define our generic completing read: @@ -552,48 +564,37 @@ Let's start with an umbrella customization group: (espotify-play-uri (alist-get 'uri album)) (error "No album for %s" (alist-get 'name item))))) - (defun espotify--by (type filter) + (defun espotify-search-by (type filter) (ivy-read (format "Search %s: " type) - (counsel-espotify--search-by type filter) + (espotify-counsel--search-by type filter) :dynamic-collection t :action `(1 ("a" espotify-counsel--play-album "Play album") ("p" espotify--maybe-play ,(format "Play %s" type))))) #+end_src + and our collection of searching commands: + #+begin_src emacs-lisp - (defun counsel-spotify-album (&optional filter) + (defun espotify-counsel-album (&optional filter) (interactive) - (espotify--by 'album filter)) + (espotify-search-by 'album filter)) - (defun counsel-spotify-artist (&optional filter) + (defun espotify-counsel-artist (&optional filter) (interactive) - (espotify--by 'artist filter)) + (espotify-search-by 'artist filter)) - (defun counsel-spotify-track (&optional filter) + (defun espotify-counsel-track (&optional filter) (interactive) - (espotify--by 'track filter)) + (espotify-search-by 'track filter)) - (defun counsel-spotify-playlist (&optional filter) + (defun espotify-counsel-playlist (&optional filter) (interactive) - (espotify--by 'playlist filter)) + (espotify-search-by 'playlist filter)) #+end_src - Much simpler than consult, although it's true that we already had - part of the job done. And we're missing some of the funcionality, - like on-demand throttling. That part is not difficult to fix: - - #+begin_src emacs-lisp - (defun counsel-espotify--search-by (type filter) - (lambda (term) - (when (string-suffix-p "=" (or term "")) - (espotify-search-all (lambda (its) - (let ((cs (mapcar #'espotify--format-item its))) - (ivy-update-candidates cs))) - term - type - filter)) - 0)) - #+end_src + Simpler than our initial consult, although it's true that we already + had part of the job done. The nice "split search" that counsult + offers out of the box, though, is much more difficult to get. * Postamble -- cgit v1.2.3