Subject: Re: bug#72210: 31.0.50; Feature request: multi-category support in `icomplete-fido-kill'

From: Stefan Monnier <[email-masked]>
Date: Tue, 17 Sep 2024 17:15:18 -0400

> #+begin_src emacs-lisp
> (progn
>   (package-initialize)
>   (load-library "consult")
>   (icomplete-vertical-mode t)
>   (keymap-set icomplete-minibuffer-map "C-k" 'icomplete-fido-kill))
> #+end_src
>
> - Then, ``M-x consult-buffer'';
> - Type *M (this should turn the `*Messages*' buffer the current
>   candidate);
> - Then ``C-k''
>
> The following message should appear in the minibuffer:
>
> [Sorry, don’t know how to kill things for ‘multi-category’]

Oh, I see, so the issue is that `consult-buffer` lists not just actual
buffers but also recently visited files and other sources, so it
specifies as `category` the symbol `multi-category` and then it uses
some internal convention about how each completion candidate is
annotated with its type.

> Follows attached!  This is my first patch, so I might have made
> mistakes, but I'm here to adjust whatever you deem necessary.

And your patch adds support to `icomplete.el` for this
specific convention.

There are a few too many things that are "ad-hoc" in that approach for
my taste, but I agree that there's a good justification for adding
support for completion tables that can use a mix of different types and
thus need to offer some way to know further details about the category
of any given completion candidate.

Maybe a better option would be something like the patch below,
so `consult` could define its own method for its own category, which
could even extend the semantics to do thing like delete bookmarks when
applied to bookmarks, etc...

        Stefan
diff --git a/lisp/icomplete.el b/lisp/icomplete.el
index 2ea5e36fa88..8013f68dcf4 100644
--- a/lisp/icomplete.el
+++ b/lisp/icomplete.el
@@ -317,6 +317,20 @@ icomplete-vertical-goto-last
 
 ;;;_* Helpers for `fido-mode' (or `ido-mode' emulation)
 
+(cl-defgeneric icomplete-fkill-candidate (category _candidate)
+  (error "Sorry, don't know how to kill things for `%s'" category))
+
+(cl-defmethod icomplete-kill-candidate ((_ (eq 'buffer)) thing)
+  (when (yes-or-no-p (concat "Kill buffer " thing "? "))
+    (kill-buffer thing)))
+
+(cl-defmethod icomplete-kill-candidate ((_ (eq 'file)) thing)
+  (let* ((dir (file-name-directory (icomplete--field-string)))
+         (file (expand-file-name thing dir)))
+    (when (yes-or-no-p (concat "Delete file " file "? "))
+      (delete-file file) t))))
+
+
 (defun icomplete-fido-kill ()
   "Kill line or current completion, like `ido-mode'.
 If killing to the end of line make sense, call `kill-line',
@@ -331,26 +345,12 @@ icomplete-fido-kill
         (call-interactively 'kill-line)
       (let* ((all (completion-all-sorted-completions))
              (thing (car all))
-             (cat (icomplete--category))
-             (action
-              (cl-case cat
-                (buffer
-                 (lambda ()
-                   (when (yes-or-no-p (concat "Kill buffer " thing "? "))
-                     (kill-buffer thing))))
-                ((project-file file)
-                 (lambda ()
-                   (let* ((dir (file-name-directory (icomplete--field-string)))
-                          (path (expand-file-name thing dir)))
-                     (when (yes-or-no-p (concat "Delete file " path "? "))
-                       (delete-file path) t))))
-                (t
-                 (error "Sorry, don't know how to kill things for `%s'" cat)))))
+             (cat (icomplete--category)))
         (when (let (;; Allow `yes-or-no-p' to work and don't let it
                     ;; `icomplete-exhibit' anything.
                     (enable-recursive-minibuffers t)
                     (icomplete-mode nil))
-                (funcall action))
+                (icomplete-kill-candidate cat thing))
           (completion--cache-all-sorted-completions
            (icomplete--field-beg)
            (icomplete--field-end)