]> git.eshelyaron.com Git - emacs.git/commitdiff
Optionally highlight non-matching part on completion input
authorEshel Yaron <me@eshelyaron.com>
Sat, 17 Feb 2024 12:28:13 +0000 (13:28 +0100)
committerEshel Yaron <me@eshelyaron.com>
Sat, 17 Feb 2024 12:28:13 +0000 (13:28 +0100)
* lisp/minibuffer.el (minibuffer-completing-remote-file-p)
(minibuffer-not-completing-remote-file-p): New functions.
(minibuffer-completion-fail): New face.
(minibuffer-pulse-failing-completion): New user option.
(completion--fail): Respect it.

* doc/emacs/mini.texi (Completion Options): Document it.

* etc/NEWS: Announce it.

doc/emacs/mini.texi
etc/NEWS
lisp/minibuffer.el

index c281b44fd591032290c89ba25e0f72800232dddd..92e3101b9ba25c0b0002a2495148132d1a0771f3 100644 (file)
@@ -1113,6 +1113,48 @@ used them before, @pxref{Minibuffer History}) with face
 highlighting in the @file{*Completions*} buffer, customize
 @code{completions-highlight-previous-inputs} to @code{nil}.
 
+@cindex failed completion highlighting
+@cindex highlighting failed completion
+@cindex non-matching part, of completion input
+@vindex minibuffer-pulse-failing-completion
+When your minibuffer input does not match any of the possible completion
+candidates, Emacs can momentarily highlight (pulse) the
+@dfn{non-matching part}---the part of your input that prevents it from
+matching completion candidate.  If you delete the non-matching part,
+your input will match at least one completion candidate.  For example,
+if the minibuffer input is @samp{foobar}, and the completion candidates
+are @samp{food} and @samp{fish}, then the non-matching part is
+@samp{bar}, because is you delete @samp{bar} the remaining input
+@samp{foo} would match the completion candidates @samp{food}.  The user
+option @code{minibuffer-pulse-failing-completion} controls this
+highlighting.  The possible values of this option are:
+
+@table @code
+@item t
+Enable completion failure highlighting.
+
+@item nil
+Disable completion failure highlighting.
+
+@item @var{pred}
+When completion fails, call the function @var{pred} with no arguments,
+and perform highlighting only if @var{pred} returns non-@code{nil}.
+@end table
+
+@noindent
+By default, @code{minibuffer-pulse-failing-completion} is set to the
+function @code{minibuffer-not-completing-remote-file-p}, which checks
+whether the minibuffer input is a remote file name (@pxref{Remote
+Files}).  This means that you get completion failure highlighting except
+for when you (fail to) complete remote file names, in which case Emacs
+skips this highlighting.  It is usually a good idea to keep this default
+value, because completion failure highlighting can be slow for remote
+file completion.
+
+When Emacs highlights the non-matching part of your input, it also sets
+the mark to the start of the non-matching part so you can quickly select
+it with @kbd{C-x C-x}, and delete it with @kbd{C-w}.  @xref{Mark}.
+
 @node Minibuffer History
 @section Minibuffer History
 @cindex minibuffer history
index 35a78d90f496fe3f29dc2c09b51dbc8c97a92466..f37d9e67302f955896ab0ca6f0c29cfc54323a1f 100644 (file)
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -783,12 +783,20 @@ the file with the whole history of the session.  The default is nil.
 
 ** Minibuffer and Completions
 
++++
 *** 'SPC' in the minibuffer now inserts a space.
 'SPC' no longer completes by word in the minibuffer.  To have 'SPC'
 perform minibuffer completion, add the following form to your init file:
 
   (keymap-set minibuffer-local-completion-map "SPC" #'minibuffer-complete)
 
++++
+*** Highlight the part of your input that prevents matching any completion.
+When completion fails, Emacs now momentarily highlights the part of your
+minibuffer input that prevents it from matching any of the possible
+completion candidates.  You can customize the new user option
+'minibuffer-pulse-failing-completion' to control this behavior.
+
 *** New commands 'previous-line-completion' and 'next-line-completion'.
 Bound to '<up>' and '<down>' arrow keys, respectively, they navigate
 the "*Completions*" buffer vertically by lines, wrapping at the
index 5fefb19968ab2bf8b2902ec357513199f4866c0b..e28c0226b2b15a18d92d7f0f789623ca76ba6155 100644 (file)
@@ -1506,19 +1506,88 @@ pair of a group title string and a list of group candidate strings."
 (defvar completion-fail-discreetly nil
   "If non-nil, stay quiet when there is no match.")
 
+(defun minibuffer-completing-remote-file-p ()
+  "Check if currenlty completing remote file names."
+  (and minibuffer-completing-file-name (file-remote-p (minibuffer-contents))))
+
+(defun minibuffer-not-completing-remote-file-p ()
+  "Negation of `minibuffer-completing-remote-file-p'."
+  (not (minibuffer-completing-remote-file-p)))
+
+(defface minibuffer-completion-fail
+  '((t :inherit isearch-fail))
+  "Face for highlighting minibuffer input part that prevents completion."
+  :version "28.1")
+
+(defcustom minibuffer-pulse-failing-completion
+  #'minibuffer-not-completing-remote-file-p
+  "Whether to pulse the part of your input that prevents completion.
+
+If this option is non-nil and your minibuffer input does not match any
+of the possible completion candidates, Emacs momentarily
+highlights (pulses) the non-matching part of your input; if you delete
+that part, your input would match at least one completion candidate.
+
+When pulsing, Emacs places a mark at each end of the non-matching part,
+so you can quickly select it with \\[exchange-point-and-mark] and kill
+it with \\[kill-region]."
+  :type '(choice (const :tag "Never pulse" nil)
+                 (const :tag "Always pulse" t)
+                 (const :tag "Pulse unless completing remote file names"
+                        minibuffer-not-remote-file-completion-p)
+                 (function :tag "Custom predicate"))
+  :version "30.1")
+
 (defun completion--fail ()
+  "Indicate completion failure."
   (unless completion-fail-discreetly
     (ding)
+    (when (and (minibufferp) minibuffer-pulse-failing-completion)
+      (while-no-input
+        (let* ((beg-end (minibuffer--completion-boundaries))
+               (beg (car beg-end)) (end (cdr beg-end))
+               (ref (minibuffer-prompt-end))
+               (md (completion--field-metadata beg))
+               (test (lambda (pos)
+                       (let* ((completion-lazy-hilit t))
+                         (completion-all-completions
+                          (buffer-substring ref pos)
+                          minibuffer-completion-table
+                          minibuffer-completion-predicate
+                          (- pos ref) md))))
+               (cur nil))
+          (when (and (< beg end)
+                     (or (eq minibuffer-pulse-failing-completion t)
+                         (funcall minibuffer-pulse-failing-completion)))
+            (setq cur
+                  (cond
+                   ((or (funcall test (1- end)) (= beg (1- end))) (1- end))
+                   ((funcall test (- end 2)) (- end 2))
+                   ((named-let search ((beg beg)
+                                       (cur (+ beg (floor (- end beg) 2)))
+                                       (end (- end 2)))
+                      ;; BEG passes TEST, END doesn't.
+                      (if (or (= beg cur) (funcall test cur))
+                          (if (and (< (1+ cur) end) (funcall test (1+ cur)))
+                              (search cur (+ cur (floor (- end cur) 2)) end)
+                            cur)
+                        (search beg (+ beg (floor (- cur beg) 2)) cur))))))
+            (push-mark end)
+            (push-mark cur)
+            (pulse-momentary-highlight-region
+             cur end
+             ;; TODO: Select face based on `minibuffer--require-match'.
+             'minibuffer-completion-fail)))))
     (completion--message
-     (format
-      "No match%s"
-      (if (minibuffer-narrow-completions-p)
-          (substitute-command-keys
-           (concat
-            ", \\[minibuffer-widen-completions] to clear restrictions ("
-            (minibuffer--completion-predicate-description)
-            ")"))
-        "")))))
+     (concat
+      (propertize "No match" 'face 'error)
+      (when (minibuffer-narrow-completions-p)
+        (substitute-command-keys
+         (concat
+          ", \\[minibuffer-widen-completions] to clear restrictions ("
+          (minibuffer--completion-predicate-description)
+          ")")))))))
+
 
 (defun completion--message (msg)
   (if completion-show-inline-help