From: Eshel Yaron Date: Sat, 17 Feb 2024 12:28:13 +0000 (+0100) Subject: Optionally highlight non-matching part on completion input X-Git-Url: http://git.eshelyaron.com/gitweb/?a=commitdiff_plain;h=d9eb00336404d6ac112f0218c90d6bbc381f5686;p=emacs.git Optionally highlight non-matching part on completion input * 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. --- diff --git a/doc/emacs/mini.texi b/doc/emacs/mini.texi index c281b44fd59..92e3101b9ba 100644 --- a/doc/emacs/mini.texi +++ b/doc/emacs/mini.texi @@ -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 diff --git a/etc/NEWS b/etc/NEWS index 35a78d90f49..f37d9e67302 100644 --- 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 '' and '' arrow keys, respectively, they navigate the "*Completions*" buffer vertically by lines, wrapping at the diff --git a/lisp/minibuffer.el b/lisp/minibuffer.el index 5fefb19968a..e28c0226b2b 100644 --- a/lisp/minibuffer.el +++ b/lisp/minibuffer.el @@ -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