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

From: Stefan Monnier <[email-masked]>
Date: Wed, 09 Apr 2025 15:15:49 -0400

> The other is the confirmation prompt, which feels like it should be
> implemented once and for all outside of the generic function.

OK, the solution below is not perfect, but I think it's good enough for now.

Any objection to the patch below?

        Stefan
diff --git a/lisp/icomplete.el b/lisp/icomplete.el
index 35842b53e6b..fba65e734f5 100644
--- a/lisp/icomplete.el
+++ b/lisp/icomplete.el
@@ -381,6 +381,29 @@ icomplete-vertical-goto-last
 
 ;;;_* Helpers for `fido-mode' (or `ido-mode' emulation)
 
+(cl-defgeneric icomplete-kill-candidate (category _candidate)
+  "\"Kill\" CANDIDATE, assuming it is of kind CATEGORY.
+CANDIDATE is a string denoting a completion candidate,
+CATEGORY should be a completion category, as specified
+in `completion-metadata'.
+\"Kill\" here means to actually delete the underlying object, such
+as a file, buffer, ...
+Should return non-nil if the operation was successful."
+  (error "Don't know how to kill things for category `%s'" category))
+
+(cl-defmethod icomplete-kill-candidate ((_ (eql 'buffer)) thing)
+  (kill-buffer thing))
+
+(cl-defmethod icomplete-kill-candidate ((_ (eql 'file)) thing)
+  ;; FIXME: This makes assumptions about completion style: e.g. with
+  ;; partial-completion, `/usr/s/d/ema' can result in DIR being
+  ;; `/usr/s/d/' and THING being `share/doc/emacs', in which case DIR
+  ;; isn't the right base directory to pass to `expand-file-name'!
+  (let* ((dir (file-name-directory (icomplete--field-string)))
+         (file (expand-file-name thing dir)))
+    (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',
@@ -395,26 +418,13 @@ 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))
+                (when (yes-or-no-p (format "Kill %s %s? " cat thing))
+                  (icomplete-kill-candidate cat thing)))
           (completion--cache-all-sorted-completions
            (icomplete--field-beg)
            (icomplete--field-end)
@@ -437,6 +447,8 @@ icomplete-fido-ret
                    (file-name-directory (icomplete--field-string))))
          (current (car completion-all-sorted-completions))
          (probe (and dir current
+                     ;; FIXME: Same problem as in
+                     ;; `icomplete-kill-candidate<file>' above.
                      (expand-file-name (directory-file-name current)
                                        (substitute-env-vars dir)))))
     (cond ((and probe (file-directory-p probe) (not (string= current "./")))